KDB knowledge

Pinned queries

Pin a SELECT so its result is always ready in the result cache - computed when the database opens, never pushed out, computed again after a write.

A pinned query is a SELECT the server keeps ready. Pin the slow question a dashboard asks on every load, and from then on every request for it is answered from the result cache:

The rules of the cache still apply: the statement must be one cacheable SELECT of this database (no now(), no random functions, no other database), and the row limit is part of the key: pin it with the max_rows the requests use (/exec sends 10,000 unless limit= says otherwise; the console sends 10,000).

A pin is saved code that later runs by itself, without a rights check: only a server admin can pin or unpin, and every change is written to the audit log (event pin).

Pins are a feature of kdb serve. A server started without them answers /api/pins with 404.

GET /api/pins

Syntax:

GET /api/pins[?db=<name>]

The pins of one database (the default database without db=). Needs read rights on the database.

FieldMeaning
database, sql, max_rows, enabledthe pin as saved
created_ms, created_bywhen it was saved and by whom; empty = the token
openthe database is open now; the fields below are live only then
cached, cached_bytesits result is in the cache right now, and its size
last_run_ms, last_ok, last_errorthe last time the server computed it by itself (at open, or after a write); last_ok is null before the first
errorwhy the database refused the pin when it was applied (a column that no longer exists, for example); empty when it took it

now_ms at the top is the server's clock. GET /api/stats shows cache_pinned per database: how many pinned results are in the cache now.

POST /api/pins

Syntax:

POST /api/pins[?db=<name>]
Content-Type: application/json

{"sql": "SELECT ...", "max_rows": 10000, "enabled": true}

Pins a statement, or replaces the pin with the same SQL. Returns the pin.

FieldMeaning
sqlone SELECT, at most 64 KB
max_rowsoptional, 1 to 1,000,000 (default 10,000): the row limit the requests use
enabledoptional (default true); false keeps the pin saved without applying it

A statement that is not one SELECT, does not parse, or reads another database is a 400 that says so. So is a max_rows out of range.

HTTP: Pin the totals a dashboard shows on every load

Setup:

CREATE TABLE trades (id LONG PRIMARY KEY, symbol SYMBOL, qty LONG, price DOUBLE);
INSERT INTO trades VALUES (1, 'CITY', 100, 510.5), (2, 'CITY', 50, 512), (3, 'VOLT', 200, 301);
POST /api/pins
Content-Type: application/json

{"sql": "SELECT symbol, sum(qty) AS qty, sum(qty * price) AS amount FROM trades GROUP BY symbol ORDER BY symbol"}
###
GET /api/pins

Response:

POST /api/pins

HTTP 200 (application/json)

{
  "database": "demo",
  "sql": "SELECT symbol, sum(qty) AS qty, sum(qty * price) AS amount FROM trades GROUP BY symbol ORDER BY symbol",
  "max_rows": 10000,
  "enabled": true,
  "created_ms": 1792647859769,
  "created_by": "",
  "open": true,
  "cached": false,
  "cached_bytes": 0,
  "last_run_ms": 1792647859769,
  "last_ok": true,
  "last_error": "",
  "error": ""
}

GET /api/pins

HTTP 200 (application/json)

{
  "now_ms": 1792647859811,
  "pins": [
    {
      "database": "demo",
      "sql": "SELECT symbol, sum(qty) AS qty, sum(qty * price) AS amount FROM trades GROUP BY symbol ORDER BY symbol",
      "max_rows": 10000,
      "enabled": true,
      "created_ms": 1792647859769,
      "created_by": "",
      "open": true,
      "cached": false,
      "cached_bytes": 0,
      "last_run_ms": 1792647859769,
      "last_ok": true,
      "last_error": "",
      "error": ""
    }
  ]
}

HTTP: Only one SELECT can be pinned

POST /api/pins
Content-Type: application/json

{"sql": "DELETE FROM trades"}

Response:

HTTP 400 (application/json)

{"error": "a pinned statement is one SELECT"}

DELETE /api/pins

Syntax:

DELETE /api/pins[?db=<name>]
Content-Type: application/json

{"sql": "SELECT ..."}

Removes the pin with that SQL (also ?sql=<url-encoded SQL>). Returns {"deleted": true}. Its cached result stays until the cache needs the room, like any other. An unknown pin is a 404; a request without the SQL is a 400.

HTTP: Unpin it

Setup:

CREATE TABLE trades (id LONG PRIMARY KEY, symbol SYMBOL, qty LONG);
POST /api/pins
Content-Type: application/json

{"sql": "SELECT symbol, sum(qty) FROM trades GROUP BY symbol"}
###
DELETE /api/pins
Content-Type: application/json

{"sql": "SELECT symbol, sum(qty) FROM trades GROUP BY symbol"}

Response:

POST /api/pins

HTTP 200 (application/json)

{
  "database": "demo",
  "sql": "SELECT symbol, sum(qty) FROM trades GROUP BY symbol",
  "max_rows": 10000,
  "enabled": true,
  "created_ms": 1792647859798,
  "created_by": "",
  "open": true,
  "cached": false,
  "cached_bytes": 0,
  "last_run_ms": 1792647859799,
  "last_ok": true,
  "last_error": "",
  "error": ""
}

DELETE /api/pins

HTTP 200 (application/json)

{"deleted": true}