Skip to content

Commit 2071eb1

Browse files
committed
feat: include join to make the spark job more interesting
1 parent 0610488 commit 2071eb1

2 files changed

Lines changed: 46 additions & 13 deletions

File tree

stacks/openlineage/airflow.yaml

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,15 @@ data:
9797
A layered ELT + Spark pipeline that produces cross-engine lineage in Marquez:
9898
9999
tpch.tiny.customer ─┐
100-
├─(Trino)─> lakehouse.demo.customer_orders ─(Spark)─> lakehouse.demo.orders_by_nation
101-
tpch.tiny.orders ──┘
100+
├─(Trino)─> lakehouse.demo.customer_orders ─┐
101+
tpch.tiny.orders ──┘ ├─(Spark join)─> lakehouse.demo.orders_by_nation
102+
tpch.tiny.nation ──(Trino)─> lakehouse.demo.nation ───────────┘
102103
103104
The Trino steps read from the built-in `tpch` catalog and write Iceberg tables into the
104105
`lakehouse` catalog (shared hive-iceberg metastore + MinIO). The final step submits a Spark job
105-
that reads `lakehouse.demo.customer_orders` and writes `lakehouse.demo.orders_by_nation` into the
106-
*same* Iceberg catalog.
106+
that JOINS `lakehouse.demo.customer_orders` with the `lakehouse.demo.nation` dimension and writes
107+
`lakehouse.demo.orders_by_nation` into the *same* Iceberg catalog. The two-input Spark join is
108+
reported by OpenLineage as a fan-in lineage node (both source tables feed the output).
107109
108110
The single, connected end-to-end graph is the *Airflow* one: Airflow is one producer and names
109111
every dataset consistently, and the Spark step is stitched in by declaring its I/O under the same
@@ -379,6 +381,19 @@ data:
379381
),
380382
)
381383
384+
# Nation dimension: the second input to the Spark join. Kept as its own load step (same
385+
# single-statement CREATE OR REPLACE pattern as the others) so it is an independent lineage
386+
# source that fans into the Spark job alongside customer_orders.
387+
load_nation = SQLExecuteQueryOperator(
388+
task_id="load_nation",
389+
conn_id="trino_default",
390+
sql=(
391+
"CREATE OR REPLACE TABLE lakehouse.demo.nation AS "
392+
"SELECT nationkey, name AS nation_name, regionkey "
393+
"FROM tpch.tiny.nation"
394+
),
395+
)
396+
382397
customer_orders = SQLExecuteQueryOperator(
383398
task_id="customer_orders",
384399
conn_id="trino_default",
@@ -410,11 +425,16 @@ data:
410425
task_id="spark_orders_by_nation",
411426
namespace=namespace,
412427
application_file=build_spark_application(spark_app_name, namespace),
413-
openlineage_inputs=[(trino_ol_namespace, "lakehouse.demo.customer_orders")],
428+
openlineage_inputs=[
429+
(trino_ol_namespace, "lakehouse.demo.customer_orders"),
430+
(trino_ol_namespace, "lakehouse.demo.nation"),
431+
],
414432
openlineage_outputs=[(trino_ol_namespace, "lakehouse.demo.orders_by_nation")],
415433
)
416434
417-
create_schema >> [load_customers, load_orders] >> customer_orders >> spark_orders_by_nation
435+
create_schema >> [load_customers, load_orders, load_nation]
436+
[load_customers, load_orders] >> customer_orders
437+
[customer_orders, load_nation] >> spark_orders_by_nation
418438
---
419439
apiVersion: v1
420440
kind: Secret

stacks/openlineage/spark.yaml

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
# PySpark job that the Airflow DAG submits as a SparkApplication (see the inlined
22
# SparkKubernetesOperator in airflow.yaml).
33
#
4-
# It reads the Iceberg table the Trino ELT produced (`lakehouse.demo.customer_orders`) and writes a
5-
# small aggregate (`lakehouse.demo.orders_by_nation`) back into the same Iceberg catalog. Because the
6-
# OpenLineage Spark listener is enabled via sparkConf on the SparkApplication, this read+write is
7-
# reported to Marquez - the Spark half of the lineage graph, joined to the Trino half through the
8-
# shared `lakehouse.demo.customer_orders` table.
4+
# It reads TWO Iceberg tables the Trino ELT produced (`lakehouse.demo.customer_orders` and the
5+
# `lakehouse.demo.nation` dimension), JOINS them on `nationkey`, and writes a small aggregate
6+
# (`lakehouse.demo.orders_by_nation`) back into the same Iceberg catalog. Because the OpenLineage
7+
# Spark listener is enabled via sparkConf on the SparkApplication, this join+write is reported to
8+
# Marquez - the Spark half of the lineage graph, joined to the Trino half through the shared
9+
# `lakehouse.demo.customer_orders` and `lakehouse.demo.nation` tables.
10+
#
11+
# The join is what makes this interesting for lineage: OpenLineage reports the resulting Spark job
12+
# with BOTH source tables as inputs (a fan-in node), and - via the columnLineage facet - which
13+
# output columns derive from which input columns across the join.
914
#
1015
# The Iceberg catalog (`lakehouse`), S3 access and the OpenLineage transport are all configured in
1116
# the SparkApplication's sparkConf, not here.
@@ -21,11 +26,19 @@ data:
2126
2227
spark = SparkSession.builder.appName("orders-by-nation").getOrCreate()
2328
24-
# Source table written by the Trino ELT steps of the same Airflow DAG.
29+
# Two source tables written by the Trino ELT steps of the same Airflow DAG: the per-customer
30+
# order facts and the nation dimension. Joining them is what produces a two-input lineage node.
2531
customer_orders = spark.table("lakehouse.demo.customer_orders")
32+
nation = spark.table("lakehouse.demo.nation")
2633
34+
# Join the fact table to the nation dimension so the aggregate carries human-readable nation
35+
# names. OpenLineage reports BOTH `customer_orders` and `nation` as inputs of the resulting job,
36+
# and (via columnLineage) that `nation_name` derives from `nation` while the measures derive
37+
# from `customer_orders`.
2738
orders_by_nation = (
28-
customer_orders.groupBy("nationkey").agg(
39+
customer_orders.join(nation, on="nationkey", how="inner")
40+
.groupBy("nationkey", "nation_name")
41+
.agg(
2942
count("custkey").alias("num_customers"),
3043
sum_("order_count").alias("total_orders"),
3144
sum_("total_spent").alias("total_spent"),

0 commit comments

Comments
 (0)