KDB knowledge

Troubleshooting a running server

What to look at, and what to change, when the server is slow, readers wait, memory or disk grows, the start is slow, a timer or a backup does not run, or data must come back.

Each part below is one symptom: what you see, where to check, and the fix. Start with the console's Monitor: it shows memory, the statements running now and the slow statements on one page.

The checks use three routes. Send the token as for any route: Authorization: Bearer <token>.

RouteShows
GET /api/statsmemory, running statements, waits, snapshot reads, databases that are still opening
GET /metricsthe same counters for Prometheus or Grafana
GET /api/slow-queriesthe statements that took longer than --slow-query-ms (1 second unless set)

The server is slow

CheckWhat it tells you
Monitor, Statements running nowone long statement that holds everything up. Cancel stops it.
GET /api/slow-queries?limit=20which statements are slow, who sent them, how many rows they gave
kdb_slow_statements_total in /metricswhether slow statements are new or have always been there
kdb_gate_waits_total{mode}, kdb_gate_wait_seconds_total{mode}time spent waiting for a lock, not working: see readers wait
kdb_partition_loads_total rising fastdata is read from disk again and again: --memory is too small for what is queried
EXPLAIN <the slow query>whether the query reads every partition (scan table t (120 of 120 partitions ...))

HTTP: The newest slow statements

GET /api/slow-queries?limit=5

Response:

HTTP 200 (application/json)

{
  "threshold_ms": 1000,
  "recorded": 0,
  "queries": []
}

Example: How much of the table a query reads

Setup:

CREATE TABLE ticks (time TIMESTAMP, symbol SYMBOL, price DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO ticks VALUES ('2026-09-23T10:00:00Z', 'CITY', 510), ('2026-09-24T10:00:00Z', 'CITY', 512);
EXPLAIN SELECT max(price) FROM ticks WHERE time IN '2026-09-24';

Result:

plan (STRING)
scan table ticks (1 of 2 partitions, about 2 rows)
answer from saved stats of table ticks: 1 of 1 partitions from the partition list and their files' stats (0 in memory, 0 partitions loaded, 1 file headers read); 0 partitions scanned
build 2 of 3 stored column(s) per row: time, price
filter while scanning: 1 condition(s)
group by 0 key(s), 1 aggregate(s)
engine: column

(6 rows)

Readers wait for a writer

A SELECT that is quick at night takes seconds by day, while a load, a trigger or a long CALL writes the same table.

CheckWhat it tells you
The start-up lines, readers ...whether reader yield is on
kdb_gate_wait_seconds_total{mode="read"}how long readers waited in total
snapshot_reads in /api/statswhether snapshot reads are on and used

Errors 55P03 and 40001: the table is busy

table 'x' is busy: another transaction has held it for N ms means a writer waited the lock timeout (5 seconds unless set) for another writer of the same table. Nothing of the statement was done.

CodeWhenFix
55P03 (HTTP 503)a statement outside BEGIN ... COMMITsend it again
40001 (HTTP 503)inside BEGIN ... COMMITROLLBACK and run the whole transaction again; drivers retry this code by themselves
40P01two transactions that would wait for each otherrun it again
CheckWhat it tells you
Monitor, Statements running nowthe transaction that holds the table, and for how long
idle_in_transaction_session_timeouta client that sent BEGIN and then nothing keeps the table until it is rolled back (30 s unless set, see idle transactions)

Example: Let this session wait longer for a busy table, then go back to the server's wait

SET lock_timeout = '20s';
SET lock_timeout = DEFAULT;

Result:

SET lock_timeout = '20s'

lock_timeout is 20 s for this session

SET lock_timeout = 'DEFAULT'

lock_timeout is the server's default again

Memory is high

CheckWhat it tells you
Monitor, Memory in RAM and Most it ever heldwhat the process holds now, and at its peak
kdb_memory_bytes{kind} in /metricswhich part is big: table_data, key_maps, indexes, pending, result_cache, allocator ...
kdb_memory_level0 green, 1 yellow, 2 red: near the memory limit
errors memory full: ... (HTTP 503)the limit stopped new work instead of the process being killed
SettingWhat it limits
--memory <MB> (KDB_MEMORY)table data kept loaded. Data over it is read from disk again.
--memory-limit <size> (KDB_MEMORY_LIMIT)everything together. Keep --memory at about half of it.
--max-result-memoryone query's result (protection)
GLIBC_TUNABLES=glibc.malloc.arena_max=8:glibc.malloc.trim_threshold=67108864Linux and Docker: how much freed memory the C library keeps; without it the process can hold far more than it uses

The disk keeps growing

CheckWhat it tells you
GET /api/disk, or the console's Keep data pagethe size of the data, of older file versions, of the trash, and of the log
kdb_database_disk_bytes{database,kind} in /metricsthe same, over time
Big partWhyFix
previous_bytesevery checkpoint keeps older versions of each file for repairs (keep_versions, 2 unless set)PURGE VERSIONS KEEP 1, or a lower keep_versions in kdb.conf (versions)
trash_bytesreplaced files wait trash_keep_hours (1 hour), dropped databases wait for everPURGE TRASH, or PURGE TRASH ALL for dropped databases (trash)
dropped tablesDROP TABLE keeps a table for --trash-days (7) so UNDROP TABLE worksSHOW DROPPED TABLES, then DROP TABLE _dropped_... (the trash)
wal_archive_bytesthe old log is kept for point-in-time restoresa shorter WAL_ARCHIVE (databases)
data_bytesthe rows themselvesa TTL or DROP PARTITION for old days

HTTP: What a database takes on disk

Setup:

CREATE TABLE ticks (time TIMESTAMP, price DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO ticks VALUES ('2026-09-24T10:00:00Z', 1.5);
GET /api/disk

Response:

HTTP 200 (application/json)

{
  "database": "demo",
  "data_files": 1,
  "data_bytes": 236,
  "tail_files": 0,
  "tail_bytes": 0,
  "delta_files": 0,
  "delta_bytes": 0,
  "previous_files": 0,
  "previous_bytes": 0,
  "trash_folders": 0,
  "trash_bytes": 0,
  "wal_bytes": 1048640,
  "wal_archive_bytes": 0,
  "total_bytes": 1048876,
  "trash_purged_bytes": 0
}

Example: Free older file versions and the trash

Setup:

CREATE TABLE ticks (time TIMESTAMP, price DOUBLE) TIMESERIES(time) PARTITION BY DAY;
INSERT INTO ticks VALUES ('2026-09-24T10:00:00Z', 1.5);
PURGE VERSIONS KEEP 1;
PURGE TRASH;

Result:

PURGE VERSIONS KEEP 1

0 older file version(s) removed (0 bytes), 1 kept per file; freed 0 bytes from the trash

PURGE TRASH

removed 0 folder(s) from the trash, freed 0 bytes

The start is slow, or says "last shutdown: not clean"

last shutdown: not clean (crash or kill) in the start-up lines means the server was killed, not stopped. That is not damage: KDB replays the log, and every confirmed change comes back. Replaying a long log takes time.

CheckWhat it tells you
the recovery start-up lineclean or not, and how many log records were replayed
opening_databases in /api/statsdatabases still opening, and for how long

A timer does not run

CheckWhat it tells you
GET /api/timersenabled, next_run_ms (0 = off), last_run_ms, last_ok, last_error
kdb_timer_failures_total, kdb_timer_late_seconds_maxfailed runs, and how late runs started
CauseFix
the timer is offturn it on (Timers page, or POST /api/timers/<name>/enable)
it runs at the wrong hourtimes are the server's clock; a container runs on UTC. Set --timezone Asia/Kathmandu
last_error says whyfix the SQL, or the rights of the user who saved it last (it runs with them)
the run before is still goinga run is skipped when the one before has not finished. Make the SQL faster; a run stops after 10 minutes
its database was droppeddelete the timer

HTTP: Every timer, with its last run and error

GET /api/timers

Response:

HTTP 200 (application/json)

{
  "now_ms": 1792647875758,
  "timers": []
}

A backup failed

CheckWhat it tells you
backup_schedule in GET /api/infolast_run_ok, warning (the last good backup of 'x' is N hours old), and per database error
backup_command in /api/infothe copy command's exit code and the end of what it printed
kdb backups <dir>the backups there, and whether each is whole
CauseFix
disk fullfree space, or keep fewer backups (backup_keep_daily, backup_keep_weekly) (when the disk is full)
the folder is not there, or not writableinside Docker it must be under a mounted volume
the copy command failedrun the command by hand with the same folder

Then run one backup now to see that it works: POST /api/backup, or Back up now in the console.

Getting data back

What happenedSteps
a table was droppedUNDROP TABLE t within --trash-days (the trash)
a bad UPDATE or DELETErestore while the server runs: POST /api/restore with "until" one second before the mistake into a new name, check it, then POST /api/databases/swap
damaged files (failed its checksum, DAMAGED)stop, copy the folder, kdb check, kdb repair --backups <dir> (after damage)
the whole folder is lostkdb restore <newest backup> <new folder>, kdb check <new folder>, then start the server on it (kdb restore)

Command line: Check a database, then repair it from the backups (sample data: sample)

kdb check market
kdb repair market --backups backups

Output:

> kdb check market

checked 14 files (76.1 KB) and 1 log segments
OK: no damage found

> kdb repair market --backups backups

manifest copy: a, last shutdown: clean
replayed 0 transactions after LSN 30, last LSN 30

Nothing to repair.