3 Home
forgejo_admin edited this page 2026-08-15 19:57:48 +00:00

qlever — SPARQL store over lab-graph

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

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

Where it runs

  • Backend: StatefulSet qlever (pod qlever-0) in namespace pipelines on the RKE2 cluster, Longhorn PVC (data, 20Gi) at /index. QLever engine = upstream docker.io/adfreiburg/qlever.
  • Web UI: Deployment qlever-ui (upstream adfreiburg/qlever-ui, Django/gunicorn :7000) in the same namespace.
  • One host, two paths: https://qlever.192.168.10.46.sslip.io/ serves the UI; **/api** serves the SPARQL server (the ingress rewrites /api → the qlever-server root — same origin, no CORS). In-cluster the backend is qlever.pipelines.svc:7001.
  • No auth (behind the ingress, like Temporal). Only the graph-loader is a lab-built, signed image; both QLever images are upstream (admitted because Kyverno only verifies forgejo…/* refs).
  • Repo: https://forgejo.192.168.10.46.sslip.io/forgejo_admin/qlever

How it works (loader → index → server, + UI)

Each qlever-0 (re)start runs three stages sharing the PVC at /index:

  1. graph-loader (initContainer) — lists lab-graph/**.ttl.gz, gunzips + concatenates into /index/input.ttl and writes input.ttl.sha256 (hash over the sorted object key+etag list). Uses the mover-s3 Secret for S3 creds.
  2. qlever-index (initContainer) — qlever-index -i /index/idx/graph -f /index/input.ttl -F ttl -p false builds the SPARQL index; skips the rebuild when the hash is unchanged, then removes the (~700 MiB) input.
  3. qlever-serverqlever-server -i /index/idx/graph -p 7001 … serves SPARQL.

qlever-ui seeds a single lab-graph backend (pointing at …/api) on startup, so the UI works with no manual setup. Current scale: ~10.7M triples / ~1.39M LogEntrys.

Query it

See the Example queries page for a full, tested catalog (explore, per-workload, time, errors, HTTP-by-reqId, text search, correlation).

Open https://qlever.192.168.10.46.sslip.io for the UI (autocomplete + results table), or hit the SPARQL API directly at /api (GET|POST ?query=<sparql>, Accept: application/sparql-results+json).

Count:

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 (the point of #9):

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

Log volume per container:

PREFIX col: <https://simon.services/ns/collector/v1#>
SELECT ?c (COUNT(?e) AS ?n) WHERE { ?e col:resource ?c } GROUP BY ?c ORDER BY DESC(?n)

Operate

  • Refresh (pick up new lab-graph objects): kubectl -n pipelines rollout restart statefulset/qlever. The loader re-pulls; the index rebuilds only if the input changed.
  • Health: kubectl -n pipelines get pod qlever-0; per-stage logs kubectl -n pipelines logs qlever-0 -c graph-loader | -c qlever-index | qlever-0 (server); UI: kubectl -n pipelines logs deploy/qlever-ui.
  • Nothing here is backed up — the index is a rebuildable view; source of truth is s3://semantix/lab-graph.

Build & release (graph-loader)

graph-loader builds via the Temporal BuildOnly pipeline (webhook POST /build → melange/apko → signed SBOM + vuln scan). After a source change: push to main, let BuildOnly publish forgejo_admin/qlever@sha256:…, pin that digest in deploy/statefulset.yaml, kubectl apply. Both QLever images (adfreiburg/qlever, adfreiburg/qlever-ui) are upstream and digest-pinned.

Troubleshooting (learned the hard way)

  • GET / returns "Unknown path" — that's the bare qlever-server (a SPARQL API). The browsable UI is the qlever-ui Deployment; the ingress serves it at / and the API at /api.
  • CreateContainerConfigError: image has non-numeric user (qlever) — the QLever image declares its USER by name; under runAsNonRoot kubelet needs a numeric uid. Fixed with runAsUser: 999 + fsGroup: 999.
  • Turtle parse must be serial (-p false) — triplify declares @prefix lazily (scattered), so QLever's parallel parser can't be used.
  • Kyverno admits the upstream images because require-signed-sbom only verifies forgejo.192.168.10.46.sslip.io/* refs (same as monitoring's prometheus/grafana).

Not yet (phase 2)

Incremental / git-manifest-driven rebuild + col:inSegment / col:epochSecond enrichment in triplify (the full #9 segment model); /metrics for the loader + Prometheus scrape; SSO in front of the endpoint; mirror+sign the QLever images into the lab registry.