Serve SPARQL endpoint when its initial dataset is ready
Decided · 15 August 2026
Context
CI starts sparqld --no-watch over a directory of EARL Turtle reports and
then runs several queries against that one dataset. Today,
serve_at_with_options
loads the complete dataset before it resolves and binds the HTTP addresses, and
logs Serving … at … only afterwards. That makes a client infer readiness from
a human-readable log line before it can reuse the loaded dataset.
The existing loader builds a fresh store and replaces the shared store only
after the complete load finishes
(reload_dataset).
When watching, the watcher is registered before that initial load so events
cannot be missed
(DirectoryWatcher::start_with_patterns).
The decision must retain those properties while giving CI and long-running
clients an endpoint-level startup contract.
Decision
| Criterion | 2. Bind; wait at ordinary requests | 3. Bind; return 503 + readiness route | 1. Load before binding | 4. Load before binding + ready signal | 5. Batch multi-query mode |
|---|---|---|---|---|---|
| Parse shared dataset once | |||||
| Correct results while loading | |||||
| CI and deployment ergonomics | 503. |
||||
| Health, readiness, and liveness | |||||
| Timeout, cancellation, and failure | |||||
| Live reload and atomic replacement | |||||
| Implementation complexity and API stability | /. |
/ API. |
|||
| Outcome | Not selected |
Green column: chosen. Yellow column: satisfies the core constraints but was
not selected. Red columns: excluded; stronger-red cells identify the decisive
shortcoming. denotes a trade-off. The HTTP SPARQL Protocol
defines query request forms, while Kubernetes probes
distinguish traffic readiness from liveness; sparqld deliberately adds neither
/readyz nor /livez, so both continue to be ordinary unknown paths (404)
under the root-only handler.
Bind the listener immediately after synchronous validation of the directory,
patterns, and resolved addresses. Start the initial load in the background.
Until that load reaches a terminal state, every GET or POST request at /,
including the landing GET /, waits without a server-imposed deadline. It is
then passed to the existing root-only request dispatch.
On success, atomically publish the fresh dataset, release all waiting requests,
and run their normal dispatch. Per-file parse errors remain nonfatal: the
catalog already records them,
so that completed load is ready. A client timeout or disconnect cancels only
that request, never the shared load or another waiter. On a fatal initialization
failure, pending and subsequent root requests receive
503 Service Unavailable,
then sparqld shuts down and exits nonzero without a successful Serving log.
Other paths return 404 immediately. After readiness, live reload never
re-gates requests and retains staged atomic replacement.
Logs remain informative for people and diagnostics, not a client readiness
contract. The existing Serving … at … line remains after readiness for
compatibility.
Consequences
- Clients can issue their first ordinary query immediately after starting sparqld, with a client-side timeout appropriate to their CI or deployment.
- The CLI flags, library function signatures, SPARQL request forms, responses,
and non-root
404behavior remain stable; only the startup timing changes. - Release notes document that log parsing is unnecessary and that an immediate root request waits for the initial dataset rather than inferring readiness from the listener or logs.
Implementation Steps
- Add an internal, shared initial-load state that wakes all root-request waiters after success or fatal failure.
- Bind only after validation, begin the initial load in the background, and preserve watcher registration before the first load.
- Dispatch root requests only after successful first publication; return
503after fatal initialization and stop the listener before nonzero exit. - Document the startup contract in the HTTP API and CLI guidance without introducing a health endpoint.
- Test listener reachability during load, concurrent
GETandPOSTwaiters, atomic complete-dataset results, nonfatal file errors, independent client cancellation, fatal-load503and exit, absent probe paths, and post-ready atomic live reload.