Skip to content

A missing Druid datasource is an availability question first

Marking segments unused removes a datasource from the catalog entirely, and a stopped node makes an existing one answer with the same error as a typo.

A statement that worked yesterday answers Object 'libredb_demo' not found, and the datasource is gone from the tree. The message names a line and a column, so the first instinct is to check the spelling, the quoting and the schema. On Apache Druid that instinct is usually wrong, because the catalog is not a list of things that exist. It is a list of things a server is currently advertising.

What the catalog actually enumerates

Schema introspection on Druid is two INFORMATION_SCHEMA reads, run in parallel: datasources from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA = 'druid', columns from INFORMATION_SCHEMA.COLUMNS where TABLE_SCHEMA = 'druid', ordered by TABLE_NAME, ORDINAL_POSITION. Indexes and foreign keys are always empty arrays, because Druid has no index object to describe and no datasource can reference another.

SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = 'druid';

That is the whole sidebar. It touches no sys table at all, which is why a cluster that declines to describe its own servers still renders a full tree, and why the tree can be confident and wrong at the same time.

The schema predicate is also the only thing keeping the rest out. The same catalog carries the INFORMATION_SCHEMA views and the six sys tables as TABLE_TYPE = 'SYSTEM_TABLE', and a cluster with lookups or views carries rows under a lookup or view schema besides. Five schemas exist under the single catalog Druid reports, always named druid. Only one of them is listed. Everything excluded stays queryable by typing SQL; it is unlisted, not unreachable.

What matters here is what INFORMATION_SCHEMA.TABLES is built from. The Broker assembles it from the segments servers are announcing. A datasource with no announced segments is not a row with a zero in it. It is not a row.

Unused segments and a datasource that disappears

Druid SQL cannot delete. DELETE FROM t WHERE ... answers Unsupported SQL statement [DELETE], and UPDATE answers the same for UPDATE - neither is in the grammar anywhere, on any engine. Data leaves a datasource through the Coordinator instead, by marking segments unused and then submitting a kill task. The Coordinator answers on 8888 when the Router’s management proxy is enabled.

curl -s -XPOST -H 'content-type: application/json' \
  -d '{"interval":"1000-01-01/3000-01-01"}' \
  http://localhost:8888/druid/coordinator/v1/datasources/libredb_rollup/markUnused
# {"numChangedSegments":3,"segmentStateChanged":true}

Verified on Apache Druid 37.0.0: the first step alone is enough. libredb_rollup vanishes from INFORMATION_SCHEMA.TABLES and from sys.segments, and markUsed brings it back. Nothing was deleted. The segment files are still in deep storage and the rows are still in the metadata store, and the catalog has no way to say so.

The practical consequence for anything that renders a schema tree is that there is no empty-datasource state to design. A datasource with every segment unused does not appear as an empty node; it is absent. An empty result from that query means “no servable datasources”, not “no datasources”.

A stopped node: Druid object not found, datasource intact

The second way a datasource leaves the catalog is that the process serving its segments stopped. Verified on the same cluster, with the Historical down and nothing else advertising the data:

docker stop libredb-druid-historical
curl -s -XPOST -H 'content-type: application/json' \
  -d '{"query":"SELECT COUNT(*) FROM libredb_demo"}' http://localhost:8888/druid/v2/sql
HTTP 400
{"error":"druidException","errorCode":"invalidInput","persona":"USER",
 "category":"INVALID_INPUT",
 "errorMessage":"Object 'libredb_demo' not found (line [1], column [27])"}

The datasource still exists in the metadata store. The Broker simply has no server advertising its segments, so it is not in the catalog, and the failure is classified INVALID_INPUT - blaming the statement, and naming a line and a column in it.

Why the status and the category cannot tell them apart

Here is the limit, stated plainly, because a reader who does not know it will spend the outage editing SQL. A datasource whose segments are all unused disappears from the catalog entirely, and a stopped serving node makes an existing datasource answer with the same status and the same category as a mistyped name. SELECT * FROM nope produces the identical envelope: HTTP 400, druidException, errorCode: invalidInput, category: INVALID_INPUT, persona: USER. There is no field in it that separates a typo from an unavailable cluster.

Classification in this provider is on category rather than the HTTP status, because the status misclassifies in both directions - SELECT 1/0 answers HTTP 500 with persona: "ADMIN" and category: UNCATEGORIZED, and reading that 5xx as a broken cluster would send the user to check their host over a division. persona is carried for display and never branched on, for the same reason. But categorising correctly does not help here: the category Druid reports is genuinely the same one, and the wording is Druid’s own. Nothing in a client can improve that message.

Partial unavailability is different, and worth knowing because it is the case that does get a signal. Druid serves a query over segments it cannot reach as an ordinary 200, so a short row set and a correct one look alike in the body. The transport reads the length of the missingSegments list in the X-Druid-Response-Context header and turns a positive count into a result warning: This result is incomplete: N segments of the queried data were unavailable. Some of the data missing is a warning on a success. All of it missing is a not-found error about your spelling.

Where to look before rewriting the statement

Two reads answer it, and neither is in the schema tree.

SELECT server, server_type, curr_size, max_size FROM sys.servers;
SELECT datasource, COUNT(*) AS segments, SUM(num_rows) AS total_rows
FROM sys.segments WHERE is_active = 1 GROUP BY datasource;

A missing historical row in the first is what to look for. The monitoring panels read exactly these: storage is sys.servers where server_type = 'historical', and per-datasource statistics group sys.segments where is_active = 1. If the datasource is absent from both, check the Coordinator for unassigned segments and for an interval someone marked unused.

Two things this cannot become. There is no query log to consult afterwards - Druid keeps none, in no system table, at no endpoint, in no file - so the failed statement leaves no trace to read later. And an agent cannot go and look for you: agent AUTO mode runs only on PostgreSQL, SQLite and DuckDB, because the read-only profile it needs is database-native, and on a Druid connection an auto run ends engine-unsupported. PLAN mode opens on the connection and will draft the sys.servers statement, toolless, for a human to run.