SPARQL store over lab-graph (collector #9)
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-08-25 04:35:29 +00:00
cmd/graph-loader graph-loader: emit segment markers into the base (seed the loader ledger) 2026-08-15 23:47:51 +02:00
deploy qlever: raise memory (server 6->12Gi -m6G -c2G, index 6->8Gi) + fold every 12h 2026-08-19 22:13:49 +02:00
internal/s3util qlever: SPARQL store over lab-graph (collector #9) 2026-08-15 20:51:06 +02:00
vendor qlever: SPARQL store over lab-graph (collector #9) 2026-08-15 20:51:06 +02:00
.gitignore qlever: SPARQL store over lab-graph (collector #9) 2026-08-15 20:51:06 +02:00
apko.yaml qlever: SPARQL store over lab-graph (collector #9) 2026-08-15 20:51:06 +02:00
catalog-info.yaml catalog-info: qlever-ui entity (platform #49) 2026-08-25 06:25:31 +02:00
go.mod qlever: SPARQL store over lab-graph (collector #9) 2026-08-15 20:51:06 +02:00
go.sum qlever: SPARQL store over lab-graph (collector #9) 2026-08-15 20:51:06 +02:00
melange.yaml qlever: SPARQL store over lab-graph (collector #9) 2026-08-15 20:51:06 +02:00
README.md qlever: document the web UI (/ = UI, /api = SPARQL) 2026-08-15 21:42:53 +02:00

qlever — SPARQL store over lab-graph

The query tier of the observability-as-linked-data pipeline (collector #9). Stands up QLever as a rebuildable materialized view over the RDF triplify writes to s3://semantix/lab-graph, so correlating logs is a SPARQL query, not a per-signal dashboard.

collector → mover (s3 lab-podlogs) → triplify (RDF → s3 lab-graph) → [ qlever: SPARQL ] → queries

Design rules

  • The index is a materialized view, not a database of record. Source of truth is lab-graph (triplify's sealed .ttl.gz segments). The index rebuilds from them — nothing here is backed up.
  • Triples only, default graph (matches triplify's Turtle output; QLever holds ~10.7M triples / ~1.4M LogEntrys from the current lab-graph).
  • QLever engine = upstream image, docker.io/adfreiburg/qlever (pinned by digest). It's admitted because Kyverno require-signed-sbom only verifies forgejo.192.168.10.46.sslip.io/* refs — the same reason monitoring runs upstream prometheus/grafana. Only graph-loader is a lab-built, signed image.
  • Serial Turtle parse (qlever-index -F ttl -p false): triplify's serializer declares @prefix lazily (scattered through each file), so QLever's parallel parser can't be used.

How it runs

A StatefulSet (namespace pipelines, Longhorn PVC at /index) whose (re)start does three steps:

  1. graph-loader (cmd/graph-loader) — lists lab-graph/**.ttl.gz, gunzips + concatenates to /index/input.ttl, writes input.ttl.sha256 (a hash over the sorted object+etag list).
  2. qlever-index — builds /index/idx/graph.*; skips the rebuild when the input hash is unchanged, then removes the (~700 MiB) input to reclaim space.
  3. qlever-server — serves SPARQL on :7001.

Force a refresh (pick up new lab-graph objects): kubectl -n pipelines rollout restart statefulset/qlever.

Configuration (env — graph-loader)

Env Default Meaning
SRC_PREFIX lab-graph S3 prefix of the RDF to index
OUT_FILE /index/input.ttl concatenated Turtle written for the indexer
S3_ENDPOINT / S3_BUCKET / S3_ACCESS_KEY / S3_SECRET_KEY Hetzner S3 (from Secret mover-s3)

Query

  • Web UI: https://qlever.192.168.10.46.sslip.io — the QLever query interface (autocomplete + results table), pre-pointed at the lab-graph backend.
  • SPARQL API: https://qlever.192.168.10.46.sslip.io/api (in-cluster qlever.pipelines.svc:7001), GET|POST ?query=<sparql> with Accept: application/sparql-results+json.

No auth (behind the ingress, like Temporal). The same host serves both — the ingress routes / to the UI and /api to the SPARQL server.

PREFIX col: <https://simon.services/ns/collector/v1#>
SELECT (COUNT(?e) AS ?n) WHERE { ?e a col:LogEntry }

Correlation — one container's logs in a window:

PREFIX col: <https://simon.services/ns/collector/v1#>
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
SELECT ?t ?msg WHERE {
  ?e col:resource <https://simon.services/id/k8s/lab/<ns>/pod/<uid>/container/<c>> ;
     col:occurredAt ?t ; col:message ?msg .
  FILTER(?t >= "2026-08-10T00:00:00Z"^^xsd:dateTime)
} ORDER BY ?t

Build & deploy

graph-loader builds via the Temporal BuildOnly pipeline (webhook POST /build, melange/apko, signed SBOM). Pin the published graph-loader@sha256:… digest in deploy/statefulset.yaml, then kubectl apply -f deploy/statefulset.yaml. The QLever image is already digest-pinned.

Out of scope (phase 2)

Incremental / git-manifest-driven rebuild + col:inSegment / col:epochSecond load (the full #9 segment model); /metrics; SSO in front of the endpoint; mirror+sign the QLever image into the lab registry for supply-chain coverage.