Skip to content

Commit 28e0949

Browse files
committed
Add Gravitino metadata integration
1 parent 90cb634 commit 28e0949

9 files changed

Lines changed: 251 additions & 6 deletions

‎Makefile‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: test lint pipeline infra-light infra-serving infra-dashboard infra-stop init-superset dashboard-stop seed-data flink-up flink-submit flink-jobs flink-savepoint flink-cancel flink-restore batch-backfill sync-doris health demo demo-streaming demo-serving clean
1+
.PHONY: test lint pipeline infra-light infra-serving infra-dashboard infra-stop init-superset dashboard-stop seed-data flink-up flink-submit flink-jobs flink-savepoint flink-cancel flink-restore batch-backfill sync-doris health demo demo-streaming demo-serving gravitino-up gravitino-status gravitino-catalogs clean
22

33
test:
44
uv run pytest -v
@@ -10,9 +10,10 @@ pipeline:
1010
uv run python -m scripts.spark_paimon_backfill --input data/raw/mock_llm_requests/events.jsonl
1111

1212
infra-light:
13-
docker compose up -d postgres kafka flink-jobmanager flink-taskmanager
13+
docker compose up -d postgres kafka gravitino flink-jobmanager flink-taskmanager
1414
scripts/prepare_flink_warehouse.sh
1515
scripts/create_kafka_topics.sh
16+
scripts/init_gravitino.sh
1617

1718
infra-serving:
1819
docker compose up -d doris-fe doris-be doris-init
@@ -90,5 +91,15 @@ demo-streaming:
9091
demo-serving:
9192
scripts/run_serving_demo.sh
9293

94+
gravitino-up:
95+
docker compose up -d --wait gravitino
96+
scripts/init_gravitino.sh
97+
98+
gravitino-status:
99+
curl -fsS -H 'Accept: application/vnd.gravitino.v1+json' http://localhost:8090/api/version | python3 -m json.tool
100+
101+
gravitino-catalogs:
102+
curl -fsS -H 'Accept: application/vnd.gravitino.v1+json' http://localhost:8090/api/metalakes/ai_observability/catalogs | python3 -m json.tool
103+
93104
clean:
94105
rm -rf data/

‎README.md‎

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,9 +210,10 @@ Build and start the local Flink runtime manually:
210210

211211
```bash
212212
docker compose build flink-jobmanager
213-
docker compose up -d postgres kafka flink-jobmanager flink-taskmanager
213+
docker compose up -d postgres kafka gravitino flink-jobmanager flink-taskmanager
214214
scripts/prepare_flink_warehouse.sh
215215
scripts/create_kafka_topics.sh
216+
scripts/init_gravitino.sh
216217
```
217218

218219
Load local JSONL events into the Postgres operational source table:
@@ -248,6 +249,26 @@ The Flink Web UI is available at:
248249
http://localhost:8081
249250
```
250251

252+
Gravitino manages the shared Paimon metadata under the
253+
`ai_observability` metalake. Start and initialize it independently with:
254+
255+
```bash
256+
make gravitino-up
257+
```
258+
259+
Verify the server and registered catalogs with:
260+
261+
```bash
262+
make gravitino-status
263+
make gravitino-catalogs
264+
```
265+
266+
The Gravitino Web V2 UI is available at:
267+
268+
```text
269+
http://localhost:8090
270+
```
271+
251272
## 9. Spark Backfill and Validation
252273

253274
Spark is no longer a parallel warehouse pipeline. Its role is to backfill and validate the shared Paimon warehouse that Flink also writes to.

‎docker-compose.yml‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,30 @@ services:
2020
- postgres_data:/var/lib/postgresql/data
2121
- ./sql/source_postgres_schema.sql:/docker-entrypoint-initdb.d/01_source_postgres_schema.sql:ro
2222

23+
gravitino:
24+
image: apache/gravitino:1.2.0
25+
container_name: ai-observability-gravitino
26+
restart: unless-stopped
27+
ports:
28+
- "8090:8090"
29+
environment:
30+
GRAVITINO_MEM: "-Xms256m -Xmx512m -XX:MaxMetaspaceSize=256m"
31+
GRAVITINO_USE_WEB_V2: "true"
32+
volumes:
33+
- gravitino_data:/root/gravitino/data
34+
- paimon_warehouse:/workspace/data/paimon
35+
healthcheck:
36+
test:
37+
- CMD-SHELL
38+
- >-
39+
curl -fsS
40+
-H 'Accept: application/vnd.gravitino.v1+json'
41+
http://localhost:8090/api/version >/dev/null
42+
interval: 5s
43+
timeout: 3s
44+
retries: 24
45+
start_period: 20s
46+
2347
flink-jobmanager:
2448
build:
2549
context: .
@@ -210,6 +234,7 @@ services:
210234

211235
volumes:
212236
postgres_data:
237+
gravitino_data:
213238
paimon_warehouse:
214239
doris_fe_data:
215240
doris_be_data:

‎docs/runtime_runbook.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ These services are infrastructure processes. They keep running until stopped:
1515
| --- | --- | --- |
1616
| `postgres` | Operational source database with logical WAL enabled | `make infra-light` |
1717
| `kafka` | Real-time ODS buffer and replay log | `make infra-light` |
18+
| `gravitino` | Metadata service and Web UI for the shared Paimon catalog | `make infra-light` or `make gravitino-up` |
1819
| `flink-jobmanager` | Flink control plane and REST API | `make infra-light` |
1920
| `flink-taskmanager` | Flink worker slots for streaming SQL jobs | `make infra-light` |
2021
| `doris-fe` | Doris SQL frontend, only needed for serving checks | `make infra-serving` |
@@ -73,6 +74,25 @@ Avoid repeatedly running `make flink-submit` against an already-running cluster
7374
unless you intentionally want duplicate streaming jobs. Check `make flink-jobs`
7475
first.
7576

77+
### Gravitino metadata service
78+
79+
Start only Gravitino and initialize the `ai_observability` metalake plus the
80+
`paimon_lake` catalog:
81+
82+
```bash
83+
make gravitino-up
84+
```
85+
86+
The initializer is idempotent, so it is safe to rerun after a container restart.
87+
Check the API and catalog registration with:
88+
89+
```bash
90+
make gravitino-status
91+
make gravitino-catalogs
92+
```
93+
94+
Open the Web V2 UI at `http://localhost:8090`.
95+
7696
### Finite batch and serving tasks
7797

7898
These commands are finite tasks. They should start, complete, and exit:

‎flink/sql/30_build_dws_from_dwd.sql‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ WITH session_metrics AS (
194194
session_id,
195195
COUNT(*) AS turn_cnt,
196196
SUM(total_tokens) AS token_cnt,
197-
TIMESTAMPDIFF(MILLISECOND, MIN(created_at), MAX(created_at)) + MAX(latency_ms) AS session_duration_ms
197+
TIMESTAMPDIFF(SECOND, MIN(created_at), MAX(created_at)) * 1000
198+
+ MAX(latency_ms) AS session_duration_ms
198199
FROM paimon_lake.dwd.dwd_ai_llm_request_di
199200
GROUP BY `date`, app_name, feature_name, session_id
200201
),

‎scripts/init_gravitino.sh‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
GRAVITINO_URL="${GRAVITINO_URL:-http://localhost:8090}"
5+
METALAKE="${GRAVITINO_METALAKE:-ai_observability}"
6+
RESPONSE_FILE="$(mktemp)"
7+
trap 'rm -f "${RESPONSE_FILE}"' EXIT
8+
9+
headers=(
10+
-H "Accept: application/vnd.gravitino.v1+json"
11+
-H "Content-Type: application/json"
12+
)
13+
curl_timeout=(--connect-timeout 3 --max-time 10)
14+
15+
get_status() {
16+
curl -sS "${curl_timeout[@]}" -o "${RESPONSE_FILE}" -w '%{http_code}' \
17+
"${headers[@]}" "$1"
18+
}
19+
20+
create_resource() {
21+
local label="$1"
22+
local url="$2"
23+
local payload="$3"
24+
local status
25+
26+
status="$(
27+
curl -sS "${curl_timeout[@]}" -o "${RESPONSE_FILE}" -w '%{http_code}' \
28+
-X POST "${headers[@]}" -d "${payload}" "${url}"
29+
)"
30+
if [[ "${status}" != "200" && "${status}" != "201" ]]; then
31+
echo "ERROR: failed to create ${label} (HTTP ${status})" >&2
32+
cat "${RESPONSE_FILE}" >&2
33+
return 1
34+
fi
35+
echo "Created ${label}."
36+
}
37+
38+
ready=false
39+
for attempt in $(seq 1 60); do
40+
if curl -fsS "${curl_timeout[@]}" "${headers[@]}" \
41+
"${GRAVITINO_URL}/api/version" > /dev/null 2>&1; then
42+
ready=true
43+
break
44+
fi
45+
echo "Waiting for Gravitino server... (${attempt}/60)"
46+
sleep 2
47+
done
48+
49+
if [[ "${ready}" != "true" ]]; then
50+
echo "ERROR: Gravitino server did not become ready within 120 seconds" >&2
51+
exit 1
52+
fi
53+
54+
metalake_status="$(get_status "${GRAVITINO_URL}/api/metalakes/${METALAKE}")"
55+
case "${metalake_status}" in
56+
200)
57+
echo "Metalake ${METALAKE} already exists."
58+
;;
59+
404)
60+
create_resource "metalake" "${GRAVITINO_URL}/api/metalakes" "$(cat <<JSON
61+
{
62+
"name": "${METALAKE}",
63+
"comment": "AI observability lakehouse metadata"
64+
}
65+
JSON
66+
)"
67+
;;
68+
*)
69+
echo "ERROR: failed to inspect metalake ${METALAKE} (HTTP ${metalake_status})" >&2
70+
cat "${RESPONSE_FILE}" >&2
71+
exit 1
72+
;;
73+
esac
74+
75+
catalog_status="$(get_status "${GRAVITINO_URL}/api/metalakes/${METALAKE}/catalogs/paimon_lake")"
76+
case "${catalog_status}" in
77+
200)
78+
echo "Paimon catalog paimon_lake already exists."
79+
;;
80+
404)
81+
create_resource "Paimon catalog" "${GRAVITINO_URL}/api/metalakes/${METALAKE}/catalogs" '{
82+
"name": "paimon_lake",
83+
"type": "RELATIONAL",
84+
"provider": "lakehouse-paimon",
85+
"comment": "Paimon ODS, DWD, DWS, DIM, and ADS metadata",
86+
"properties": {
87+
"catalog-backend": "filesystem",
88+
"warehouse": "file:///workspace/data/paimon"
89+
}
90+
}'
91+
;;
92+
*)
93+
echo "ERROR: failed to inspect Paimon catalog (HTTP ${catalog_status})" >&2
94+
cat "${RESPONSE_FILE}" >&2
95+
exit 1
96+
;;
97+
esac
98+
99+
echo "Gravitino initialization complete: ${GRAVITINO_URL}"

‎scripts/run_flink_sql_sequence.sh‎

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ if [[ $# -lt 1 ]]; then
77
fi
88

99
tmp_file="flink/sql/.generated_sequence.sql"
10-
trap 'rm -f "${tmp_file}"' EXIT
10+
output_file="$(mktemp)"
11+
trap 'rm -f "${tmp_file}" "${output_file}"' EXIT
1112
rm -f "${tmp_file}"
1213

1314
for sql_file in "$@"; do
@@ -20,6 +21,20 @@ done
2021

2122
scripts/prepare_flink_warehouse.sh
2223

24+
set +e
2325
docker compose run -T --rm flink-sql-client \
2426
/opt/flink/bin/sql-client.sh \
25-
-f "/workspace/${tmp_file}"
27+
-f "/workspace/${tmp_file}" 2>&1 | tee "${output_file}"
28+
docker_status=${PIPESTATUS[0]}
29+
set -e
30+
31+
if [[ ${docker_status} -ne 0 ]]; then
32+
exit "${docker_status}"
33+
fi
34+
35+
# Flink SQL Client can print a statement-level error and still exit with status 0.
36+
# Treat any such error as a failed sequence so Make/CI cannot report false success.
37+
if grep -q '\[ERROR\]' "${output_file}"; then
38+
echo "ERROR: Flink SQL Client reported one or more failed statements." >&2
39+
exit 1
40+
fi

‎tests/test_flink_sql_assets.py‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,15 @@ def test_flink_sql_sequence_runner_keeps_catalog_in_one_session():
347347
assert "docker compose run -T --rm flink-sql-client" in script
348348
assert "/opt/flink/bin/sql-client.sh" in script
349349
assert "-f \"/workspace/${tmp_file}\"" in script
350+
assert "PIPESTATUS[0]" in script
351+
assert "\\[ERROR\\]" in script
352+
353+
354+
def test_dws_session_duration_uses_a_flink_supported_timestampdiff_unit():
355+
sql = read_asset("flink/sql/30_build_dws_from_dwd.sql")
356+
357+
assert "TIMESTAMPDIFF(MILLISECOND" not in sql
358+
assert "TIMESTAMPDIFF(SECOND, MIN(created_at), MAX(created_at)) * 1000" in sql
350359

351360

352361
def test_flink_savepoint_restore_helpers_are_available():

‎tests/test_gravitino_assets.py‎

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from pathlib import Path
2+
3+
4+
REPO_ROOT = Path(__file__).resolve().parents[1]
5+
6+
7+
def read_asset(relative_path: str) -> str:
8+
return (REPO_ROOT / relative_path).read_text(encoding="utf-8")
9+
10+
11+
def test_compose_defines_a_health_checked_gravitino_service():
12+
compose = read_asset("docker-compose.yml")
13+
14+
assert "gravitino:" in compose
15+
assert "apache/gravitino:1.2.0" in compose
16+
assert '"8090:8090"' in compose
17+
assert "GRAVITINO_USE_WEB_V2: \"true\"" in compose
18+
assert "healthcheck:" in compose
19+
assert "http://localhost:8090/api/version" in compose
20+
assert "gravitino_data:/root/gravitino/data" in compose
21+
assert "paimon_warehouse:/workspace/data/paimon" in compose
22+
23+
24+
def test_gravitino_initializer_is_strict_and_idempotent():
25+
script = read_asset("scripts/init_gravitino.sh")
26+
27+
assert "set -euo pipefail" in script
28+
assert 'METALAKE="${GRAVITINO_METALAKE:-ai_observability}"' in script
29+
assert 'get_status "${GRAVITINO_URL}/api/metalakes/${METALAKE}"' in script
30+
assert 'create_resource "metalake"' in script
31+
assert 'get_status "${GRAVITINO_URL}/api/metalakes/${METALAKE}/catalogs/paimon_lake"' in script
32+
assert 'create_resource "Paimon catalog"' in script
33+
assert "--connect-timeout 3 --max-time 10" in script
34+
assert '|| echo "Metalake already exists"' not in script
35+
36+
37+
def test_makefile_exposes_gravitino_lifecycle_targets():
38+
makefile = read_asset("Makefile")
39+
40+
assert "gravitino-up:" in makefile
41+
assert "docker compose up -d --wait gravitino" in makefile
42+
assert "scripts/init_gravitino.sh" in makefile
43+
assert "gravitino-status:" in makefile
44+
assert "gravitino-catalogs:" in makefile

0 commit comments

Comments
 (0)