Query gateway¶
The query gateway answers queries on the Iceberg tables. It authenticates each request, removes the files that cannot contain a match, scans the files that remain, and streams the rows to the client. It holds no data of its own. Its caches make queries faster, and a new pod fills them from your catalog and your storage.
Open the interactive diagram for pan and zoom, search, and export to PNG or SVG.
Two ways in¶
| Interface | Port | For |
|---|---|---|
| HTTP | queryGateway.serverPort (default 8080) |
GET with a filter in the query string, or POST with a JSON document. |
| PostgreSQL wire protocol | queryGateway.postgres.port |
psql, JDBC and ODBC drivers, BI tools. Read-only. Off unless you configure it. |
The two interfaces use the same engine, the same caches, the same authentication, and the same time-range, time and metadata limits. The steps below apply to both, but step 3 is different for each interface. This page gives HTTP status codes. On the PostgreSQL interface a refusal is an SQLSTATE error, not an HTTP status.
The request path¶
- Authenticate. Each request must carry an OIDC bearer token. The gateway checks the signature, the expiry time, the issuer, the audience (if you configure one) and the claims that you require. There is no anonymous mode and no switch that disables authentication.
- Authorize. If you configure
queryGateway.auth.datasetRoles, the caller's role must be in the list for the dataset. A dataset that is not in the map, or that has an empty list, is denied to all callers.datasetRolesalso needsqueryGateway.auth.roleClaimPath, which gives the location of the roles in the token. If you do not configuredatasetRoles, each authenticated caller can read each dataset. - Validate. On the HTTP interface, the gateway refuses a column name that contains a
character outside
a-z A-Z 0-9 _ .before it parses the filter. It refuses a filter operator that is not in its list. Filter values are typed against the table schema and passed to the engine as values. They are never put into query text. On the PostgreSQL interface, the client sends SQL, and the gateway examines the plan of each statement. See PostgreSQL interface. - Apply the limits. See Limits.
- Prune. Three passes remove files, and row groups in files, that cannot match the filter. See Pruning.
- Scan and stream. The engine reads only the byte ranges that it needs from the files that remain. Rows go to the client as the scan produces them, so the memory that a query uses does not grow with the size of its result.
Authentication runs before the gateway examines the dataset name or the query. A caller with no valid token cannot find out which datasets exist.
HTTP query sequence¶
This is the order of the calls for one HTTP query. The gateway does the dashed calls only when the data is not in its caches.
The gateway sends the status 200 when the plan is ready, before it reads the first row. A failure
after that point cannot change the status, also when it occurs on the first data file. See
When a dependency is unavailable.
Pruning¶
| Pass | Uses | Removes |
|---|---|---|
| 1 | The column statistics (minimum, maximum, null count) in the Iceberg manifests | Whole files. It opens no data file and no sidecar. |
| 2 | The bloom-filter sidecar, for an equality filter on a column in bloomFilterColumns |
Row groups, and files that have none left |
| 3 | The full-text sidecar, for an equality or LIKE filter on a column in tantivy.indexingColumns |
Row groups, and files that have none left |
A pass removes only what it has proved cannot match. If a file has no sidecar, if the sidecar cannot be read, or if the filter has a form that a pass cannot answer, the gateway scans the file. The result is always correct; a missing sidecar only makes the query slower. Files that the compactor has not merged yet have no full-text sidecar, and files that another engine wrote have no sidecars at all.
Caches¶
| Cache | Holds | Limit | Notes |
|---|---|---|---|
| Snapshot cache | The file lists of each table's current snapshot | queryGateway.manifestCache.maxBytes (default 512 MiB) |
The gateway polls the catalog every queryGateway.snapshotPollIntervalSeconds (default 8); each pod changes the interval by a small amount of its own. It loads only the parts of the file list that a query needs. |
| Sidecar cache | Bloom-filter and full-text sidecars | queryGateway.indexCache.maxBytes (default 512 MiB) |
At start, the gateway loads the sidecars of the files added in the last queryGateway.warmup.windowHours (default 24). This does not delay the start. |
| Signing keys | The OIDC provider's keys | The gateway fetches a key that it does not know before it refuses a token. |
A data file and its sidecars never change after they are written, so a cached sidecar is never out of date.
When a dependency is unavailable¶
| Dependency | The gateway |
|---|---|
| Catalog | Continues to answer from the file lists that it has loaded. A query that needs a file list that it has not loaded gets 503. It never returns a partial result as if it were complete. |
| OIDC provider | Continues to use the keys that it has, for queryGateway.auth.maxJwksStalenessSeconds (default 3600). After that, requests get 503, not 401. A pod that starts while the provider is unavailable has no keys, and returns 503 immediately. |
| Storage, while the gateway plans the query | A file list that cannot be read gets 503. A sidecar that cannot be read is not an error: the gateway scans the file. |
| Storage, during the scan (HTTP) | The status 200 is already sent, also when no row was sent. The response ends with an error member. A client must examine each response for error. |
Limits¶
| Limit | Configuration key | Default | Past the limit |
|---|---|---|---|
| Time range of a query that returns raw rows | maxRawWindowDays on the dataset |
7 days | 422 |
| Time to plan and prune a query | queryGateway.queryTimeoutSeconds |
30 seconds | 504 |
| HTTP queries in progress on one pod | queryGateway.maxInFlightHttpQueries |
16 | 503. The gateway refuses the query; it does not queue it. |
| PostgreSQL connections on one pod | queryGateway.postgres.maxConnections |
none; you must set it | The connection is refused. |
| Table metadata that one query holds | queryGateway.manifestCache.maxBytes |
512 MiB | 422 |
| Rows in one HTTP page | 500; maximum 10 000 | A larger value is reduced to the maximum. |
The time-range limit applies to a query that has no aggregate and no grouping. It does not apply to
an aggregate query, or to an equality lookup on a column in bloomFilterColumns. Set
maxRawWindowDays: 0 on a dataset to remove the limit.
The gateway measures the time range on the dataset's timeColumn. The gateway does not start if a
dataset with a limit has no timeColumn, or if that column is not a timestamp column.
The gateway refuses a query if it cannot show that the time range is in the limit. Thus it refuses a query with no filter, a query with a filter only on other columns, and a query that has an upper time bound but no lower one. A lower bound with no upper bound is measured to the current time.
queryGateway.manifestCache.maxBytes is the limit of the snapshot cache, and it is also the limit
of what each query in progress can hold. Thus the largest use of memory for table metadata on one
pod is maxBytes × (1 + maxInFlightHttpQueries + postgres.maxConnections). With the defaults and
no PostgreSQL interface, that is 8.5 GiB.
On the PostgreSQL interface, the time-range limit and the metadata limit give SQLSTATE 54000, the
time limit gives 57014, a catalog or OIDC provider that is unavailable gives 57P03, a storage
failure gives 58030, and a table with delete files gives 55000. The page size and
maxInFlightHttpQueries do not apply there.
PostgreSQL interface¶
- TLS is mandatory. The gateway refuses a connection without TLS before it asks for a password.
- The password is the OIDC access token. The user name and the database name are ignored.
- The gateway validates the token again for each statement. The connection ends at the first
statement after the token expires. A connection that does nothing keeps its place in
maxConnections. - The Iceberg namespace is a schema, and each dataset is a table in it.
- Only queries are accepted.
BEGIN,COMMITandROLLBACKare accepted and do nothing.SETandSHOWoperate on session settings;SET statement_timeoutis refused. All statements that write, including those that create temporary tables, fail. - The rules for column names, filter operators and page sizes on the HTTP interface do not apply.
A
SELECTwith noLIMITreturns all the rows that match. - Dataset authorization applies to each table that a statement reads, which includes each side of a join.
- The catalog tables (
pg_catalog,information_schema) are open to each authenticated caller. They show the names and the columns of all datasets, but no rows.
What the gateway does not do¶
It does not apply Iceberg delete files. If another engine deletes rows with a delete file, the
gateway refuses all queries on that dataset with 501. It does not return rows that the table says
are deleted. Compact the delete files away with the other engine to restore service. Other engines
continue to read the table correctly.
It does not give results in seconds after ingest. New events become visible a few minutes after they reach Kafka. The target is 7 minutes or less for 95 % of events.