Skip to content

Commit df44159

Browse files
committed
Merge branch 'worktree/lucky-river-4604' into wip/ol-spark
2 parents bd54519 + bb96513 commit df44159

8 files changed

Lines changed: 102 additions & 50 deletions

File tree

103 KB
Loading
51.7 KB
Loading
163 KB
Loading
84.3 KB
Loading
117 KB
Loading

docs/modules/demos/pages/openlineage-trino-airflow.adoc

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,15 @@ event-listener.name=openlineage
103103
openlineage-event-listener.transport.type=HTTP
104104
openlineage-event-listener.transport.url=http://marquez:5000
105105
openlineage-event-listener.transport.endpoint=/api/v1/lineage
106-
openlineage-event-listener.trino.uri=http://trino-coordinator:8080
106+
openlineage-event-listener.trino.uri=https://trino-coordinator:8443
107+
openlineage-event-listener.namespace=trino-openlineage-demo
107108
----
108109

109-
See the {trino-openlineage-docs}[Trino OpenLineage event listener docs{external-link-icon}^] for details.
110+
`trino.uri` is Trino's own serving address; it names the *dataset* namespace, so pointing it at the same
111+
`trino-coordinator:8443` that Airflow uses lets both producers report the same tables under one namespace.
112+
`namespace` sets an explicit, colon-free *job* namespace - without it Marquez cannot parse the node ID of a
113+
Trino query job (its ID starts with a digit). See the {trino-openlineage-docs}[Trino OpenLineage event
114+
listener docs{external-link-icon}^] for details.
110115

111116
*Airflow* only needs the provider pointed at Marquez, which is done with `envOverrides`:
112117

@@ -221,56 +226,83 @@ and the Spark job runs as a short-lived `SparkApplication` submitted by the Airf
221226
stacklet.
222227
====
223228

224-
== Explore the lineage in Marquez
229+
== Verify the demo worked
230+
231+
The demo automatically triggers the `openlineage_demo` DAG once during installation, so a completed run
232+
and its lineage should already be present. You can confirm the full flow - Airflow orchestrating, Trino
233+
executing, and Marquez collecting the lineage - across three UIs.
234+
235+
=== Airflow: the DAG tasks ran
236+
237+
Open the Airflow web UI using the `webserver-http` endpoint from the `stackablectl stacklet list` output
238+
above, and log in with the username `admin` and the password `adminadmin`.
239+
240+
Under *Dags* -> `openlineage_demo` -> *Task Instances* you should see the four tasks of the pipeline -
241+
`create_schema`, `load_customers`, `load_orders` and `customer_orders` - all in the `success` state:
242+
243+
image::openlineage/airflow.png[Airflow Task Instances view showing the four openlineage_demo tasks in the success state]
244+
245+
Each task is a `SQLExecuteQueryOperator` that submits a statement to Trino.
246+
247+
=== Trino: the queries finished
248+
249+
Open the Trino web UI using the `coordinator-https` endpoint from the `stackablectl stacklet list`
250+
output, and log in with the username `admin` and the password `adminadmin`.
251+
252+
The *Query Details* page lists the statements Airflow submitted - the `CREATE SCHEMA`, `DROP TABLE` and
253+
`CREATE TABLE ... AS SELECT` statements that build the `memory.demo.*` tables from the `tpch.tiny.*`
254+
source - all in the `FINISHED` state. Their *Source* is `airflow`, confirming they originated from the
255+
DAG rather than an interactive session:
256+
257+
image::openlineage/trino-queries.png[Trino Query Details page showing the finished CREATE TABLE queries submitted by Airflow]
258+
259+
=== Marquez: the lineage was collected
225260

226261
Marquez does not have an external listener. Forward its web UI to your machine:
227262

228263
[source,console]
229264
----
230-
$ kubectl -n openlineage-demo port-forward svc/marquez-web 5000:5000
265+
$ kubectl -n openlineage-demo port-forward svc/marquez-web 3000:3000
231266
----
232267

233-
Then open http://localhost:5000 in your browser.
268+
Then open http://localhost:3000 in your browser.
234269

235-
Use the namespace selector at the top to switch between the namespaces that produced the lineage:
270+
The landing page lists the OpenLineage *Jobs* that Trino and Airflow reported, together with the event,
271+
dataset and source counts. You should see the `openlineage_demo` DAG job and the per-query jobs such as
272+
`openlineage_demo.customer_orders.query.*`, all *COMPLETED*:
236273

237-
* `airflow-openlineage-demo` - the *connected end-to-end graph*. It contains the `openlineage_demo` DAG, its tasks, and
238-
the datasets `tpch.tiny.*`, `lakehouse.demo.customers/orders/customer_orders` and `lakehouse.demo.orders_by_nation`.
239-
* the Trino namespace (named after `openlineage-event-listener.trino.uri`, i.e. `http://trino-coordinator:8080`) - the
240-
Trino event listener's own view, with column-level lineage for each query.
241-
* `spark-openlineage-demo` (plus `s3://lakehouse` and `hive://...`) - the Spark job's own view of its read/write.
274+
image::openlineage/marquez.png[Marquez home page listing the collected OpenLineage jobs as COMPLETED]
242275

243-
To see the full flow, open the `lakehouse.demo.orders_by_nation` dataset (or the `openlineage_demo.spark_orders_by_nation`
244-
task) in the `airflow-openlineage-demo` namespace and expand upstream. You get one connected graph:
276+
Selecting a job opens its details. For example, `openlineage_demo.load_orders.query.2` shows the exact
277+
`CREATE TABLE memory.demo.orders AS SELECT ...` statement along with its OpenLineage *job* and *run*
278+
facets rendered as JSON - including the `sql` facet, the `AIRFLOW` integration, and the `externalQuery`
279+
facet that links back to the originating Trino query ID:
245280

246-
[source]
247-
----
248-
tpch.tiny.customer ─▶ load_customers ─▶ customers ─┐
249-
├─▶ customer_orders ─▶ lakehouse.demo.customer_orders
250-
tpch.tiny.orders ─▶ load_orders ─▶ orders ──┘ │
251-
252-
spark_orders_by_nation ─▶ lakehouse.demo.orders_by_nation
253-
----
281+
image::openlineage/job-details-customer-orders-2.png[Marquez job details for load_orders showing the SQL statement and OpenLineage facets as JSON]
282+
283+
== Explore the lineage in Marquez
254284

255-
The real Spark job is not lost: because of the parent-run link, the Spark run reports the
256-
`openlineage_demo.spark_orders_by_nation` task as its *root*, so you can drill from that task into the Spark run itself
257-
(in the `spark-openlineage-demo` namespace).
285+
With the Marquez UI still open at http://localhost:3000, use the namespace selector at the top to switch
286+
between the namespaces that produced the lineage:
258287

259-
[TIP]
260-
====
261-
Open a *dataset* or a *task* node - for example `spark_orders_by_nation` or the `orders_by_nation` dataset - not the
262-
top-level `openlineage_demo` DAG node. Airflow reports the DAG-level job with no inputs/outputs of its own, so that one
263-
node looks empty; the data flow hangs off the task and dataset nodes.
264-
====
288+
* `trino-openlineage-demo` - the Trino query jobs (one per executed statement), set via
289+
`openlineage-event-listener.namespace`, and
290+
* `trino://trino-coordinator:8443` - the datasets (`tpch.tiny.*` and `memory.demo.*`), named after the Trino
291+
coordinator's address. Both the Trino event listener and the Airflow SQL operator report the tables under
292+
this shared namespace, and
293+
* `airflow-openlineage-demo` - the `openlineage_demo` DAG and its tasks.
294+
295+
Selecting the `memory.demo.customer_orders` dataset and expanding upstream shows the full graph back to
296+
`tpch.tiny.customer` and `tpch.tiny.orders`, including column-level lineage for the individual queries:
297+
298+
image::openlineage/customer-orders-2.png[Marquez lineage graph tracing memory.demo.customer_orders upstream through the jobs to tpch.tiny.customer and tpch.tiny.orders]
265299

266300
[NOTE]
267301
====
268-
The Trino event-listener view (`trino://...`) and the Spark job's own view (`spark-openlineage-demo` / `s3://lakehouse`
269-
/ `hive://...`) are separate, self-contained graphs alongside the Airflow one. Marquez identifies datasets by
270-
`namespace + name`, and each engine names the same physical Iceberg table differently (Trino by its coordinator URI,
271-
Spark by the storage location / metastore), so they do not auto-merge. That is expected: each producer reports lineage
272-
from its own perspective, and the *connected* end-to-end view is the Airflow one, where a single producer (the
273-
orchestrator) names every dataset consistently.
302+
Trino and Airflow report lineage independently but, because `trino.uri` matches the address Airflow uses for its
303+
Trino connection, both name the tables under the same `trino://trino-coordinator:8443` namespace. Their lineage
304+
therefore converges on one shared set of dataset nodes: Trino contributes column-level detail per query, while
305+
Airflow contributes the task/DAG structure around the same tables.
274306
====
275307

276308
== Summary

stacks/openlineage/marquez.yaml

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,23 @@
1616
# | grep -v '^ namespace: "default"$' > marquez.yaml # strip test hooks + hardcoded ns
1717
# # ...then re-add this header.
1818
#
19-
# VALUES used (the chart applies one shared service.{type,port} to BOTH Services - there is no
20-
# per-service override, so the API and web UI necessarily get the same type/port):
19+
# VALUES used:
2120
# fullnameOverride: marquez
22-
# service: {type: NodePort, port: 5000} # API Service becomes marquez:5000 (what Trino/Airflow
23-
# # target and what the web backend calls); marquez-web
24-
# # NodePort exposes the UI. Postgres stays ClusterIP.
21+
# service: {type: ClusterIP, port: 5000} # API Service marquez:5000 (Trino/Airflow target it and
22+
# # the web pod calls it via MARQUEZ_PORT, all internal)
2523
# web.enabled: true
2624
# postgresql: {enabled: true, auth: marquez/marquez/marquez,
2725
# image.repository: bitnamilegacy/postgresql, global...allowInsecureImages: true}
2826
#
29-
# Resulting Services: marquez:5000 (API), marquez-web:5000 (UI, NodePort), marquez-postgresql:5432.
27+
# POST-RENDER EDITS (the chart forces ONE shared service.{type,port} onto both the API and web
28+
# Services; there is no per-service override, so these two Service specs are hand-adjusted after
29+
# rendering - see the "POST-RENDER EDIT" markers below):
30+
# * marquez (API): kept ClusterIP :5000 - consumed only cluster-internally.
31+
# * marquez-web (UI) : set to NodePort :3000 - the only component end users reach; port now
32+
# matches its container (chart would otherwise publish the UI on :5000).
33+
#
34+
# Resulting Services: marquez:5000 (API, ClusterIP), marquez-web:3000 (UI, NodePort),
35+
# marquez-postgresql:5432 (ClusterIP).
3036
---
3137
# Source: marquez/charts/postgresql/templates/secrets.yaml
3238
apiVersion: v1
@@ -57,7 +63,7 @@ metadata:
5763
type: Opaque
5864
data:
5965
marquez-db-password: "bWFjb25kbw=="
60-
66+
6167

6268
---
6369
# Source: marquez/templates/marquez/configmap.yaml
@@ -180,7 +186,9 @@ metadata:
180186
app.kubernetes.io/component: marquez
181187
annotations:
182188
spec:
183-
type: NodePort
189+
# POST-RENDER EDIT: chart default is a single service.type for both Services. The API is only
190+
# consumed cluster-internally (Trino, Airflow, and the marquez-web pod), so keep it ClusterIP.
191+
type: ClusterIP
184192
ports:
185193
- port: 5000
186194
targetPort: http
@@ -206,9 +214,11 @@ metadata:
206214
app.kubernetes.io/component: web
207215
annotations:
208216
spec:
217+
# POST-RENDER EDIT: the web UI is the only component end users reach, so expose it (NodePort) on
218+
# its own container port 3000 - not the chart's shared service.port of 5000, which was confusing.
209219
type: NodePort
210220
ports:
211-
- port: 5000
221+
- port: 3000
212222
targetPort: http
213223
protocol: TCP
214224
name: http
@@ -409,10 +419,10 @@ spec:
409419
annotations:
410420
spec:
411421
serviceAccountName: default
412-
422+
413423
affinity:
414424
podAffinity:
415-
425+
416426
podAntiAffinity:
417427
preferredDuringSchedulingIgnoredDuringExecution:
418428
- podAffinityTerm:
@@ -424,7 +434,7 @@ spec:
424434
topologyKey: kubernetes.io/hostname
425435
weight: 1
426436
nodeAffinity:
427-
437+
428438
securityContext:
429439
fsGroup: 1001
430440
hostNetwork: false
@@ -508,7 +518,7 @@ spec:
508518
- /bin/sh
509519
- -c
510520
- -e
511-
521+
512522
- |
513523
exec pg_isready -U "marquez" -d "dbname=marquez" -h 127.0.0.1 -p 5432
514524
resources:

stacks/openlineage/trino.yaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,17 @@ data:
5252
openlineage-event-listener.transport.type=HTTP
5353
openlineage-event-listener.transport.url=http://marquez:5000
5454
openlineage-event-listener.transport.endpoint=/api/v1/lineage
55-
openlineage-event-listener.trino.uri=http://trino-coordinator:8080
55+
# Must be Trino's real serving address (HTTPS on 8443 - TLS is enabled and 8080 is not exposed).
56+
# This is the naming string for the *dataset* namespace; matching the address Airflow uses in its
57+
# Trino connection makes both producers name the same tables `trino://trino-coordinator:8443`, so
58+
# their lineage attaches to one shared set of dataset nodes instead of two duplicate namespaces.
59+
openlineage-event-listener.trino.uri=https://trino-coordinator:8443
60+
# Set an explicit, colon-free job namespace. Without this the namespace defaults to the dataset
61+
# namespace (`trino://trino-coordinator:8443`), and Marquez's NodeId parser cannot split
62+
# `job:trino://trino-coordinator:8443:<queryId>` because Trino query IDs start with a digit -
63+
# its URI heuristic then treats the `:<queryId>` delimiter as a `host:port` colon and 500s when
64+
# a job is opened. A plain namespace keeps job NodeIds parseable.
65+
openlineage-event-listener.namespace=trino-openlineage-demo
5666
---
5767
apiVersion: trino.stackable.tech/v1alpha1
5868
kind: TrinoCluster

0 commit comments

Comments
 (0)