KDB knowledge

Limits

The sizes and counts KDB allows, what happens at each limit, and the features that are not there yet.

These are the limits of KDB 0.2.0-dev (September 2026). Most of them are there to protect memory and to give a clear error instead of a crash. When a limit is reached, the statement fails with an error that names the limit, and nothing of that statement is saved.

The full list of missing features is in known limitations. Please check again after you update KDB.

Names

Syntax:

database names   1 to 64 characters: letters, digits, _ and -
user names       1 to 63 characters: letters, digits, _ and -
passwords        12 characters to 1024 bytes
NameLimit
Database1 to 64 characters, letters, digits, _ and -. Upper and lower case are the same. Not _trash, _system, or Windows device names (CON, NUL, COM1, ...).
Server user1 to 63 characters, letters, digits, _ and -
Password of a server userat least 12 characters (not the user name), at most 1024 bytes
Tables, columns, views, functionsno fixed length. Reserved words (like order, select) need double quotes.
Columns in one index16

Example: A database name that is too long (shows an error)

CREATE DATABASE stock_market_data_from_2015_to_2026_all_symbols_minute_candles_copy;

Result:

Error: line 1, column 17: bad database name 'stock_market_data_from_2015_to_2026_all_symbols_minute_candles_copy': the name is longer than 64 characters

Values

Syntax:

DECIMAL(scale)       scale 0 to 18, 18 digits
NUMERIC(p, scale)    up to 38 digits
STRING, JSON, BYTES  at most 16 MB per value
TIMESTAMP            milliseconds, UTC
ValueLimit
INT-2,147,483,647 to 2,147,483,647
LONGabout ±9.2 × 10^18
DECIMAL(scale)scale 0 to 18; 18 digits in all (8 bytes). Exact.
NUMERIC(p, s), DECIMAL(p, s)up to 38 digits (16 bytes when p is over 18), scale 0 to 38. Exact; a result past 38 digits is an error (22003).
STRING, JSON, BYTES16 MB per value. Keep PDF files and images outside the database; store a path and a hash.
JSON nesting256 levels
TIMESTAMPmilliseconds. Microseconds and nanoseconds are cut.
repeat result16 MB
lpad, rpad length10,000,000 characters
greatest, least64 values
Regular expression4,096 bytes; {n,m} up to 1,000; 32 groups; 200 million steps of work per match, then needs too much work for this text (a 4 MB value can take 2 to 3 seconds before that)
Password hash rounds (password_hash(text, n))1,000 to 10,000,000

Example: DECIMAL scale (shows an error)

SELECT CAST(12.5 AS DECIMAL(19));

Result:

Error: line 1, column 29: expected a scale from 0 to 18 in DECIMAL(scale) but found '19'

Example: A value too big for INT (sample data: sample; shows an error)

INSERT INTO brokers VALUES (3000000000, 'Too Big Securities');

Result:

Error: column 'id' (INT): 3000000000 is out of range (-2147483647 to 2147483647)

Example: A text result over 16 MB (shows an error)

SELECT length(repeat('x', 20000000));

Result:

Error: line 1, column 15: repeat: the result would be larger than 16 MB

Nesting

Syntax:

expressions                  about 100 levels of ( ... )
subqueries and views         16 levels
saved functions, CALL, triggers   16 levels each
tables in one query          63
WhatLimit
Nesting of expressions (brackets, CASE in CASE, ...)about 100 levels
Nesting of subqueries and views16 levels
Saved functions calling saved functions16 levels
CALL inside procedures, triggers that fire triggers16 levels each
Tables in one query (FROM and JOINs)63
Rounds of one WHILE loop10 million
A recursive WITH query100,000 rounds or 10 million rows

Example: Too many brackets (shows an error)

SELECT (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((1)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))));

Result:

Error: line 1, column 107: expression is nested too deeply; expected at most 100 levels of nesting

Rows

Syntax:

kdb sql / kdb shell     1,000 rows per result in table format (--max-rows, .maxrows)
HTTP /exec              10,000 rows by default, at most 1,000,000 (limit=)
HTTP /exp               1,000,000 rows by default, any number with limit= (streamed)
one transaction         10 million changed rows
one query (kdb serve)   1 GB of rows in memory, 5,000,000 groups
WhatLimit
Rows, tables, partitions, databasesno fixed limit (the disk is the limit)
Rows shown by kdb sql and kdb shell in table format1,000 (change with --max-rows N / .maxrows N; 0 = all). CSV and JSON: no limit.
Rows returned by HTTP /exec10,000 unless you pass limit=, at most 1,000,000. A cut answer has "truncated": true: check it, or you read (and time) a short answer.
Rows exported by HTTP /exp1,000,000 unless you pass limit=, then any number: a plain read of one table is streamed, never held whole. X-KDB-Truncated: true says rows were cut (GET /exp).
Rows changed in one transaction (BEGIN...COMMIT, CALL, a statement with triggers)10 million (the old rows are kept in memory until the end)
Rows one query keeps in memory under kdb serve1 GB (--max-result-memory, 0 = no limit). A plain INSERT ... SELECT writes its rows while it reads them and is not stopped by it (INSERT ... SELECT), and neither is a plain read over the Postgres port, which sends its rows while it reads them (big answers stream). A sort (ORDER BY without LIMIT) cannot use disk: its rows must fit.
Groups of one GROUP BY or SAMPLE BY under kdb serve5,000,000 (--max-groups, 0 = no limit). Counted as the groups are made, also those kept on disk when memory is short, so the query stops as soon as it passes the limit.
Rows made by FILL in SAMPLE BY, and by generate_series10 million
Text shown per cell in kdb sql table format48 characters (then ...); use --format csv

Command line: kdb sql shows at most --max-rows rows (sample data: sample)

kdb sql market "SELECT time, symbol, close FROM ochl_minutes" --max-rows 2

Output:

time                     | symbol | close
-------------------------+--------+------
2026-09-09T05:15:00.000Z | CITY   | 512.1
2026-09-09T05:16:00.000Z | CITY   | 511.9
(2 rows, more not shown, 0.3 ms)

HTTP: HTTP: limit=2, and truncated is true (sample data: sample)

GET /exec?query=SELECT%20time,%20symbol,%20close%20FROM%20ochl_minutes&limit=2

Response:

HTTP 200 (application/json)

{
  "query": "SELECT time, symbol, close FROM ochl_minutes",
  "results": [
    {
      "statement": "SELECT time, symbol, close FROM ochl_minutes",
      "columns": [
        {"name": "time", "type": "TIMESTAMP"},
        {"name": "symbol", "type": "SYMBOL"},
        {"name": "close", "type": "DOUBLE"}
      ],
      "dataset": [
        ["2026-09-09T05:15:00.000Z", "CITY", 512.1],
        ["2026-09-09T05:16:00.000Z", "CITY", 511.9]
      ],
      "count": 2,
      "truncated": true,
      "rows_affected": 0,
      "message": "",
      "timings": {"parse_us": 30, "execute_us": 287}
    }
  ],
  "columns": [
    {"name": "time", "type": "TIMESTAMP"},
    {"name": "symbol", "type": "SYMBOL"},
    {"name": "close", "type": "DOUBLE"}
  ],
  "dataset": [
    ["2026-09-09T05:15:00.000Z", "CITY", 512.1],
    ["2026-09-09T05:16:00.000Z", "CITY", 511.9]
  ],
  "count": 2,
  "timings": {"parse_us": 30, "execute_us": 287}
}

Writers, readers and transactions

Syntax:

writers                 one per table; writers of different tables run at the same time
waiting for a table     5 seconds unless set (--lock-timeout, SET lock_timeout), then "table 'x' is busy"
idle transaction        rolled back after 30 seconds without a statement (--idle-in-transaction-timeout, SET idle_in_transaction_session_timeout)
open cursors            16 per database, 10 million rows each, closed after 10 unused minutes
WhatLimit
Writers at the same timeone per table. Writers of different tables run at the same time. A writer of a table that another writer or an open BEGIN ... COMMIT holds waits at most 5 seconds, then gets table 'x' is busy: .... Change the wait for the server (kdb serve --lock-timeout 20s, KDB_LOCK_TIMEOUT, lock_timeout_ms in kdb.conf) or for one session (SET lock_timeout = '20s'); 0 = no limit (lock timeout).
Two transactions that would wait for each otherone of them gets deadlock: ... at once
Schema changes (CREATE, ALTER, DROP)CREATE, ALTER and DROP TABLE, CREATE and DROP INDEX and CREATE, ALTER and DROP TRIGGER wait only for writers of their table (table 'x' is busy: ...), and for writers of other tables only until those pause between two statements. So do TRUNCATE, CREATE TABLE ... AS (the tables its query reads) and materialized views (the view and its table). Views, procedures and functions lock no table, but a procedure or function change waits for a running writer that runs a procedure, function or plpgsql trigger (a long CALL), and a view change for a running writer that reads views. Sequences, BACKUP and a schema change sent in one request with statements it affects (CREATE OR REPLACE PROCEDURE p ...; CALL p()) wait for every table of the database (database is busy: ...). A load into a table waits while a schema change is queued for it, unless the schema change itself still waits for a writer that was there first. A statement with nothing to do (CREATE TABLE IF NOT EXISTS of a table that is there, ADD COLUMN IF NOT EXISTS of a column that is there, DROP ... IF EXISTS of something that is not there) waits for nothing.
Readersany number, and a read never waits for another read. A read does not wait for an open BEGIN ... COMMIT: it sees the committed rows.
Readers during a long CALL, trigger, write request or checkpointa read of other tables never waits. A read of a table the writer holds waits until the running statement ends: the writer lets it in between its statements (reader yield, on by default). With --reader-yield off it waits for the whole request. See readers during a long writer.
An open transaction with no statementrolled back after 30 seconds. Change it for the server (--idle-in-transaction-timeout, KDB_IDLE_IN_TRANSACTION_TIMEOUT, idle_in_transaction_timeout_ms in kdb.conf) or one session (SET idle_in_transaction_session_timeout = '5min'); 0 = never (idle transactions).
Statement timeout of kdb serve60 s (--statement-timeout, 0 = none). A trigger's time counts toward the statement that fired it, also for POST /write and POST /imp.
How soon a timeout stops a statementthe row path checks every 1,024 rows; the column engine once per batch. One row is never stopped in the middle (a 16 MB value with a slow regular expression runs to its end).
Statements of one user at the same time under kdb serve32 (--max-queries-per-user, 0 = no limit)
Open cursors16 per database. Each keeps at most 10 million rows in memory. An unused cursor is closed after 10 minutes.
Queued DEFERRED trigger jobsno cap yet
Advisory locks (pg_advisory_lock)wait at most 30 seconds
Programs with the database folder openone (the database is open in another program)
Isolation levelsSERIALIZABLE (the default) and READ COMMITTED (isolation); REPEATABLE READ runs as SERIALIZABLE
READ ONLY transactionsaccepted, not enforced

See cursors and transactions.

Example: The 17th open cursor (shows an error)

DECLARE c1 CURSOR FOR SELECT 1 AS x;
DECLARE c2 CURSOR FOR SELECT 2 AS x;
DECLARE c3 CURSOR FOR SELECT 3 AS x;
DECLARE c4 CURSOR FOR SELECT 4 AS x;
DECLARE c5 CURSOR FOR SELECT 5 AS x;
DECLARE c6 CURSOR FOR SELECT 6 AS x;
DECLARE c7 CURSOR FOR SELECT 7 AS x;
DECLARE c8 CURSOR FOR SELECT 8 AS x;
DECLARE c9 CURSOR FOR SELECT 9 AS x;
DECLARE c10 CURSOR FOR SELECT 10 AS x;
DECLARE c11 CURSOR FOR SELECT 11 AS x;
DECLARE c12 CURSOR FOR SELECT 12 AS x;
DECLARE c13 CURSOR FOR SELECT 13 AS x;
DECLARE c14 CURSOR FOR SELECT 14 AS x;
DECLARE c15 CURSOR FOR SELECT 15 AS x;
DECLARE c16 CURSOR FOR SELECT 16 AS x;
DECLARE c17 CURSOR FOR SELECT 17 AS x;

Result:

DECLARE c1 CURSOR FOR SELECT 1 AS x

cursor 'c1' declared: 1 row(s)

DECLARE c2 CURSOR FOR SELECT 2 AS x

cursor 'c2' declared: 1 row(s)

DECLARE c3 CURSOR FOR SELECT 3 AS x

cursor 'c3' declared: 1 row(s)

DECLARE c4 CURSOR FOR SELECT 4 AS x

cursor 'c4' declared: 1 row(s)

DECLARE c5 CURSOR FOR SELECT 5 AS x

cursor 'c5' declared: 1 row(s)

DECLARE c6 CURSOR FOR SELECT 6 AS x

cursor 'c6' declared: 1 row(s)

DECLARE c7 CURSOR FOR SELECT 7 AS x

cursor 'c7' declared: 1 row(s)

DECLARE c8 CURSOR FOR SELECT 8 AS x

cursor 'c8' declared: 1 row(s)

DECLARE c9 CURSOR FOR SELECT 9 AS x

cursor 'c9' declared: 1 row(s)

DECLARE c10 CURSOR FOR SELECT 10 AS x

cursor 'c10' declared: 1 row(s)

DECLARE c11 CURSOR FOR SELECT 11 AS x

cursor 'c11' declared: 1 row(s)

DECLARE c12 CURSOR FOR SELECT 12 AS x

cursor 'c12' declared: 1 row(s)

DECLARE c13 CURSOR FOR SELECT 13 AS x

cursor 'c13' declared: 1 row(s)

DECLARE c14 CURSOR FOR SELECT 14 AS x

cursor 'c14' declared: 1 row(s)

DECLARE c15 CURSOR FOR SELECT 15 AS x

cursor 'c15' declared: 1 row(s)

DECLARE c16 CURSOR FOR SELECT 16 AS x

cursor 'c16' declared: 1 row(s)

Error: too many open cursors (16); CLOSE the ones you do not need (an unused cursor also closes after 10 minutes)

Example: DDL inside a transaction is refused (shows an error)

BEGIN;
CREATE TABLE notes (id LONG PRIMARY KEY, body STRING);

Result:

transaction started

Error: CREATE TABLE cannot be used inside a transaction; COMMIT or ROLLBACK first (the transaction was rolled back)

Server and HTTP

Syntax:

request body     64 MB (also after gzip or deflate is unpacked)
request headers  64 KB
idle connection  closed after 30 seconds
worker threads   one per CPU thread, at least 8 (--http-threads)
pg connections   500 (--max-connections)
WhatLimit
HTTP request body (SQL text, line protocol, CSV)64 MB. A body sent with Content-Encoding: gzip or deflate is unpacked first, and the unpacked body is also at most 64 MB. Send big loads in several requests.
HTTP repliesnot compressed
HTTP request headers64 KB
Requests handled at the same timeone worker thread per CPU thread, at least 8 and at most 64 by default. Set it with kdb serve --http-threads N (1 to 1024), KDB_HTTP_THREADS or http_threads in kdb.conf. Fast and slow requests share the workers: more slow statements than workers make other requests wait for a free one (in a test with 8 workers, 12 slow scans made a SELECT 1 wait 18.6 s).
PostgreSQL connections500 logged-in connections for the whole server, all databases together. Set it with kdb serve --max-connections N (0 = no limit), KDB_MAX_CONNECTIONS or max_connections in kdb.conf. A closed connection's place is free again at once. One more gets 53300.
A keep-alive connection with no new requestclosed after 35 seconds (answers advertise Keep-Alive: timeout=30; the 5 extra seconds keep a request sent right at 30 seconds from meeting the close)
A started request must arrive within30 seconds
Failed loginsafter 5 failures for one user from one address (or 20 from one address), waits of 1 s, 2 s, 4 s ... up to 15 minutes
Web console login12 hours
HTTPS--tls-cert (TLS), or a reverse proxy

See server security and HTTP exec.

Command line: The server's defaults are in its banner (sample data: sample)

kdb serve ./market

Output:

KDB 0.2.0-dev
  console   http://127.0.0.1:9900
  data      ./market
  backups   ./_backups/market (from default)
  access    no token
  capacity  32 HTTP threads (default), 500 PostgreSQL connections (default), lock wait 5 s (default)
  lanes     reads using 100 ms of CPU (three times in a row) wait for one of 16 heavy slots, at most 16 HTTP in line (--lanes on)
  reads     32 reads run at once (the CPUs, at least 2; follows the CPU limit), the rest wait in line (--read-slots)
  commits   synced to disk before they are confirmed (--sync-commits always)
  time zone Asia/Katmandu (the computer's); also timers and backup schedules
  readers   read while a long trigger or CALL writes (--reader-yield on); idle open transaction rolled back after 30 s (default)
  memory    26.95 GB for the whole process (85% of the machine's memory); table data 512.0 MB of it (--memory)
  cpu       32 CPUs (the machine's); 16 query threads (half of the CPUs, follows the limit), limits checked every 10 s
  limits    statement timeout 1 min, result memory 1.00 GB, 5000000 groups, 32 statements per user
  safety    writes allowed; DROP TABLE kept 7 days (UNDROP TABLE); audit log off
  logs      slow queries >= 1000 ms to ./market/_logs/slow_queries.jsonl; pg_stat_statements on; metrics at /metrics
  recovery  manifest copy: a, last shutdown: clean
  open      opened in 2 ms: manifest 1 ms, 1 files read 0 ms (8 table files left for their first use), log check 1 ms, replay 0 ms (0 transactions), clean-up 0 ms

Press Ctrl+C to stop.

Disk and memory

Syntax:

free disk space     writes stop below 64 MB free
disk full           HTTP 507 {"code":"disk full"}, PostgreSQL SQLSTATE 53100
old file versions   2 per file, then _trash for 1 hour
log archive         1 GB per database by default; SET WAL_ARCHIVE changes it
memory for data     --memory, default 512 MB for all open databases together
WhatLimit
Free disk spacewrites (and checkpoints) are refused when less than 64 MB is free; they work again when there is space
A full diskthe write fails with HTTP 507 {"code":"disk full", ...} (PostgreSQL port: SQLSTATE 53100). Rows already committed stay, and nothing is half written.
A full disk with the default log modekdb serve commits with a group flush (--sync-commits always). If that flush hits a full disk, the writes in it get 507 with The rows of this write are applied in memory and reach the log once disk space is freed. Reads go on, later writes get 507 until there is room, and then everything works again with no restart. Those refused rows are lost only if the server stops before space is freed. Rarely (the disk refuses the flush after it took the data), the log stops instead: reads go on, writes say reopen the database to recover.
A full disk with --sync-commits batchreads go on, and writes work again when there is space. But if the disk fills at the moment the log is flushed, the writes confirmed since the last flush (every 10 ms) are lost, and the database must be reopened.
An INSERT that got 507can still be committed: the checkpoint that runs after a commit can fail on its own and report the full disk. Count the rows before you send them again.
Previous versions of each data file2 (for the repair ladder; keep_versions in kdb.conf or KDB_KEEP_VERSIONS, 0 to 10); fewer once the old log they need is gone
Replaced files in _trashremoved after 1 hour (trash_keep_hours in kdb.conf or KDB_TRASH_KEEP_HOURS), or sooner beyond trash_max_mb (a quarter of the data, at least 256 MB, unless set)
A dropped tablekept 7 days for UNDROP TABLE (--trash-days)
Old log files (wal_archive)1 GB per database by default; the oldest are deleted. Change it per database with ALTER DATABASE ... SET WAL_ARCHIVE, or for all with wal_archive_mb on the Settings page. OFF keeps none.
One log file (wal/*.log)64 MB, then a new file starts
One log record256 MB (big changes are split into several records)
Memory for loaded table data--memory, default 512 MB for all open databases of kdb serve together
Memory for one plain or full-text index2 GB (index_memory_bytes); a bigger index is not built and queries read the table
Memory for repeated query results--cache-mb, default 64 MB for all open databases together. One result may use an eighth of it, a pinned query half.
Temporary files of one big GROUP BY or SAMPLE BY16 GB (max_spill_bytes), in _tmp/ of the database folder; used when the groups need more than half of --max-result-memory, removed when the query ends
Temporary files of one INSERT ... SELECT ... ORDER BYthe same 16 GB (max_spill_bytes), in _tmp/; the rows are sorted in batches of up to 64 MB, removed when the statement ends
Full-text search: one word256 bytes (a longer word is cut)
Zone mapsskip only partitions that are in memory. A partition not loaded since the open is read, unless a whole-table count, sum, min or max is answered from saved stats.

Command line: Sizes and log positions of the files (sample data: sample)

kdb info market

Output:

database        market
manifest        copy a, sequence 9
last shutdown   clean
log position    30 (saved in files up to 30)
newest change   2026-10-22 11:28:27
features        batched_log_sync, block_stats, compressed_log, partition_format_2
manifest keys   26

FILE                                      VERSION       SIZE          LSN  STATUS
catalog                                         6     2.1 KB           30  ok
t/1/all                                         7      777 B           30  ok
t/2/all                                         8      556 B           30  ok
t/3/2025                                        9     1.7 KB           30  ok
t/3/2026                                       10     3.6 KB           30  ok
t/4/2026-09                                    11    57.0 KB           30  ok
t/5/2026-09-09                                 12     1.4 KB           30  ok
t/5/2026-09-10                                 13     2.4 KB           30  ok
t/6/2026-09-10                                 14     2.7 KB           30  ok

9 files, 72.1 KB, 0 marked damaged
(STATUS is what the manifest records. To read every block and find new damage, run: kdb check)

Switches for measuring

These environment variables turn one speed-up off, so you can time a query with and without it. The answers are the same either way. Set them for kdb serve only while you measure.

VariableTurns off
KDB_NO_ZONE_MAPS=1skipping partitions by their min and max, and answers from saved stats
KDB_NO_COLUMN_MASK=1building only the columns a query reads (column mask)
KDB_NO_UNION_PUSHDOWN=1grouping a GROUP BY over a UNION ALL part by part

For one query, /exec?cache=0 skips the result cache (see performance tips).

Not supported yet

Syntax:

NATURAL JOIN   DDL in a transaction

Short list. The details, and the ways around each one, are in known limitations.

AreaNot there yet
StatementsCREATE/DROP/ALTER inside a transaction or procedure; SELECT ... INTO
Changing a tablePARTITION BY and SORT BY of an existing table; the type of the time column or of a SORT BY / DEDUP KEY column; dropping the PRIMARY KEY
QueriesNATURAL JOIN (write JOIN ... USING (columns)); ASOF JOIN ... TOLERANCE; a series function inside another (lag(sma(x, 3))); GROUPS frames, named windows, IGNORE NULLS
Procedures and triggersexception handlers, LOOP, OUT parameters, functions that return tables, INSTEAD OF triggers; row triggers (FOR EACH ROW) only as a PostgreSQL BEFORE UPDATE trigger
TransactionsOnly SELECT ... FOR UPDATE on one table with a primary key locks rows; a change (UPDATE, DELETE, INSERT) still locks its whole table until COMMIT, so SKIP LOCKED inside BEGIN returns no rows (and NOWAIT fails) while another transaction has changed that table, unless kdb serve --skip-locked-beside on. FOR SHARE, FOR NO KEY UPDATE and FOR KEY SHARE take the same lock as FOR UPDATE: two sessions cannot share a row lock, the second one waits (stricter than PostgreSQL, never looser; see FOR UPDATE).
Text and JSONregular expression back references and look-around, JSON path wildcards ($.items[*]), json_each, the jsonb operator ?
Timesummer time in ROUND ... TIME ZONE, time IN ... TIME ZONE and the zone argument of date_trunc / to_char (fixed offsets only there)
ServerCORS, compressed replies, a Windows service mode; TLS certificates are read at start (restart after renewing one)
PostgreSQL portpgAdmin's own catalog queries (psql \d and DBeaver work); API tokens as passwords
Usersrights per column or row (rights are per database, and per table with GRANT ... ON TABLE); kdb sql and kdb shell never ask for a login
Other databaseswriting into another database (INSERT INTO otherdb.t is refused; reading it works, see queries across databases)

Example: NATURAL JOIN is not there yet (sample data: sample; shows an error)

SELECT * FROM brokers b NATURAL JOIN brokers c;

Result:

Error: line 1, column 25: expected JOIN, LEFT JOIN, RIGHT JOIN, FULL JOIN, ASOF JOIN, LT JOIN, SPLICE JOIN, CROSS JOIN, WHERE, LATEST ON, SAMPLE BY, GROUP BY, UNION ALL, ORDER BY, LIMIT, ';' or the end of the input but found 'NATURAL'