Skip to content

Connecting to Apache Druid on the Router or the Broker

Both ports serve the identical SQL endpoint and nothing in the connection knows which it reached, and there is no database field because there is one catalog.

The Apache Druid SQL endpoint is POST /druid/v2/sql, served on port 8888 by the Router and on 8082 by the Broker. A connection form that offers you that choice makes it look like the choice changes something. It does not.

One endpoint, two ports, no difference

There is no driver in this path. Druid ships a JDBC driver, but it addresses Avatica and needs a JVM client library, and there is no Node client in Druid’s own distribution. So every statement is a JSON body sent with the runtime’s own fetch, and package.json is untouched: no native module in the Docker image and none in any distribution channel.

That endpoint is the same one on both ports. This was verified on both ports of one cluster rather than assumed: the same request body, the same three-header-row response envelope, the same error envelopes, and sys.servers returning the same six rows from either.

curl -s -XPOST -H 'content-type: application/json' \
  -d '{"query":"SELECT COUNT(*) AS c FROM sys.servers","resultFormat":"array",
       "header":true,"typesHeader":true,"sqlTypesHeader":true}' \
  http://localhost:8082/druid/v2/sql
# [["c"],["LONG"],["BIGINT"],[6]]

Nothing in the provider knows which of the two it reached. Monitoring works on either, because the panels are SQL over sys.* and INFORMATION_SCHEMA on the same path.

The Router is the default only because it fronts more. It carries the SQL API, the web console, and - when druid.router.managementProxy.enabled is set - the Coordinator and Overlord APIs, so one port is enough for both querying and loading data. A Broker-only deployment needs no different configuration; type 8082 and everything works, monitoring included.

So the port is an ingress decision, not a configuration difference. Ask which process your network already exposes, not which one the tool prefers.

Why there is no database selector

The form has exactly four fields: host, port, user, password. There is no database row, and its absence is the engine rather than an omission.

INFORMATION_SCHEMA.SCHEMATA reports exactly one catalog, always named druid. Five schemas exist under it - druid, INFORMATION_SCHEMA, lookup, sys and view - but only druid holds datasources, it is the default schema, and the other four are fixed. A database selector would therefore be a control with no effect, and worse, a control implying a scoping decision the user does not have. Because druid is the default schema, SELECT * FROM "libredb_demo" resolves unqualified.

That same reasoning is why the connection carries no pasted URL, and this is the limit worth reading twice: there is no connection string field on a Druid connection, and the only credential path is HTTP basic authentication for the druid-basic-security extension. Druid has no URI convention for its HTTP SQL API. Its own JDBC driver addresses Avatica as jdbc:avatica:remote:url=http://host:8888/druid/v2/sql/avatica/, which is not a string the shared parser can round-trip into host, port, user and password, and inventing druid:// would add a parser branch for a string no Druid user has ever typed. Meanwhile http:// and https:// are already claimed by another engine in that parser, so pasting a Router URL resolves to the wrong engine entirely. The consequence is recorded rather than hidden: the form has no paste tab, and a test pins both halves of the absence so a later reader does not read it as a gap.

Credentials on a cluster with no security extension

user and password are optional, and optional here means something stronger than usual. A default Druid install loads no security extension and ignores the Authorization header entirely - live-verified, a bogus Basic header still answers 200. Leaving both fields empty against a stock cluster is the correct configuration, not a shortcut.

When they are set, they travel as HTTP basic authentication, and only when user is set. There is no token field and no other scheme, so a managed endpoint that authenticates some other way is not something this connection has been verified against.

TLS is separate from the form. Any ssl mode but disable switches the transport from http to https, and the port is not changed with it, because a TLS Druid serves on whatever druid.tlsPort the deployment chose and there is no well-known value to guess. One more boundary belongs here: ssl.caCert, ssl.clientCert and ssl.rejectUnauthorized are not honoured, because global fetch cannot carry a custom CA or relax verification without an undici dispatcher and undici is not a dependency. A cluster behind a self-signed certificate fails verification; one with a publicly trusted certificate works.

connect() proves the endpoint with one SELECT 1, which the planner answers from a one-row inline datasource, so it succeeds on a cluster that has ingested nothing yet. A wrong port, a Druid process that is not a query endpoint and a rejected credential all surface while you are still looking at the form.

What to expose through an ingress

A Druid cluster is not a single container and cannot pretend to be one. Locally, docker compose -f database-compose.yml --profile druid up -d in the studio repo brings up seven services pinned to apache/druid:37.0.0 - Coordinator and Overlord, Broker, Historical, MiddleManager, Router, plus ZooKeeper and Druid’s own metadata database. Measured with docker stats on the idle cluster it adds about 4 GB of resident memory, which is why the profile is opt-in. Only two ports are published, 8888 and 8082, so the Broker-equivalence claim above can be proven rather than assumed.

Point a connection at 127.0.0.1:8888 with no credentials and you are done. A datasource, though, can only be created by ingestion: there is no CREATE TABLE in Druid’s grammar and no seed sidecar, so the fixture data is loaded by submitting a native batch task with an inline input source to POST /druid/indexer/v1/task through the Router’s management proxy - which is the concrete reason the Router’s extra surface is worth having in a dev cluster.

For a real deployment the rule follows from the architecture this whole product is built on: the tool goes to the data, so what you expose is one HTTP port on one process inside the network the cluster already lives in. Fronting Brokers with the Router or a load balancer is what a Druid deployment does anyway, and that is exactly the host a connection should point at - which matters, because one statement is one fetch to one host with no failover and no retry. A Broker restart surfaces as an error rather than being retried against a second Broker.

What the object browser calls a table

The sidebar does not say “table”. It says Datasource, in the singular and the plural, because Datasource is the Druid word for a table. The tree is built from two parallel INFORMATION_SCHEMA reads - datasources from TABLES where TABLE_SCHEMA = 'druid', columns from COLUMNS with the same filter, ordered by TABLE_NAME, ORDINAL_POSITION. It touches no sys table, so a cluster that declines to describe its servers still renders a full sidebar. Indexes and foreign keys are always empty lists: Druid indexes every dimension inside its segment, but those indexes have no index object to describe, and no datasource can reference another.

No column is reported as primary, __time included. __time is mandatory, is the partitioning and sort key, and is the only column Druid reports as IS_NULLABLE = 'NO' - but it is not unique. In the fixture, 50 rows carry 30 distinct __time values. Primary-key status is stated as fact wherever it is read, so it stays false.

One more thing the tree will not tell you gently. The Druid catalog is a view of what is servable, not of what exists. Marking every segment of a datasource unused removes it from INFORMATION_SCHEMA.TABLES and sys.segments entirely, and stopping the Historical makes an existing datasource answer HTTP 400 Object 'libredb_demo' not found with category: INVALID_INPUT - indistinguishable, in both status and category, from mistyping the name. A datasource that vanished from the tree is an availability question before it is a SQL question.

The rest of what this engine deliberately does not do is published on the engine pages, next to its transport and default port.