KDB knowledge

Backups on a schedule

kdb serve makes a verified backup of every database at set times, checks a second copy on another disk, runs your command to copy each backup off the server, and warns when the last good backup is old.

You do not need a scheduled task outside KDB: kdb serve makes the backups itself.

A backup does not stop the database. KDB waits for an open transaction to end, writes a checkpoint, and then copies the files while reads and writes go on.

Where the settings come from

Each backup setting is read when the server starts. The first of these wins: a flag, kdb.conf (the Settings page saves it there), the environment, the default.

SettingFlagkdb.confEnvironmentDefault
backup folder--backups <dir>backup_dirKDB_BACKUP_DIR_backups in the data folder (one folder served by its path: _backups/<folder name> next to it)
schedule--backup-schedulebackup_scheduleKDB_BACKUP_SCHEDULEoff
daily backups kept--keep-dailybackup_keep_dailyKDB_BACKUP_KEEP_DAILY7
weekly backups kept--keep-weeklybackup_keep_weeklyKDB_BACKUP_KEEP_WEEKLY4
copy command--backup-commandbackup_commandKDB_BACKUP_COMMANDnone

Command line: The default backup folder in the start-up lines

kdb serve

Output:

KDB 0.2.0-dev
  console   http://127.0.0.1:9900
  data      ./kdb-data (from KDB_DATA_DIR; one folder per database)
  default   default (used when a request names no database)
  backups   ./kdb-data/_backups/<database name> (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 on (SHOW AUDIT)
  logs      slow queries >= 1000 ms to ./kdb-data/_logs/slow_queries.jsonl; pg_stat_statements on; metrics at /metrics
  recovery  the default database is created when it is first used

Press Ctrl+C to stop.

Set a daily backup

Backups are on by default, but nothing makes one by itself until there is a schedule. Three steps:

  1. Open Settings > Backups in the console, type daily 16:30 as the schedule and press Save. Or send it with the token:

Syntax:

POST /api/settings   {"backup_schedule": "daily 16:30"}   ->  {"saved": true, "restart_required": true, ...}
  1. The schedule is saved in kdb.conf and used from the next start. Restart the server (the Restart button on the Settings page, or restart the service or container).
  2. Check it: GET /api/info shows "backup_schedule": {"schedule": "daily 16:30", "next_run_ms": ...}, and the Settings page shows the schedule from kdb.conf.

--backup-schedule

Syntax:

kdb serve [--backups <dir>] --backup-schedule "daily 16:00" [--keep-daily 7] [--keep-weekly 4]
ScheduleMeaning
daily 16:00every day at 16:00
daily 02:00,14:00several times a day
hourly, hourly :30every hour, at minute 0 (or 30)
every 6h, every 30mcounted from midnight, 5 minutes to 24 hours
offno scheduled backups (the default)

Times are the local time of the computer, or the zone of kdb serve --timezone when it is set (in a zone without daylight saving, such as Nepal +05:45, daily 16:00 is always the same hour; pick a time after your data for the day is in).

Start errorWhy
a backup schedule needs a backup folder: add --backups <dir> (or KDB_BACKUP_DIR)a schedule with the folder off
--backup-copy needs a backup schedule (the copy is made after each scheduled backup)a copy without a schedule
the backup schedule (--backup-schedule): the backup interval must be from 5 minutes to 24 hoursevery 1m and the like

These stop the start only when the flags themselves clash. When the folder off comes from kdb.conf or KDB_BACKUP_DIR, the server starts anyway with warning: backups are off ... starting without scheduled backups, so the console stays reachable to fix it. The Settings page refuses to save such a clash: send {"backup_dir": "off", "backup_schedule": "off"} to turn both off.

Command line: Backups every day at 16:00 local time

kdb serve --backups backups --backup-schedule "daily 16:00"

Output:

KDB 0.2.0-dev
  console   http://127.0.0.1:9900
  data      ./kdb-data (from KDB_DATA_DIR; one folder per database)
  default   default (used when a request names no database)
  backups   backups/<database name> (from --backups)
  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 on (SHOW AUDIT)
  logs      slow queries >= 1000 ms to ./kdb-data/_logs/slow_queries.jsonl; pg_stat_statements on; metrics at /metrics
  schedule  backups daily 16:00, next 2026-10-22 16:00:00
  recovery  the default database is created when it is first used

Press Ctrl+C to stop.

Command line: A schedule with backups turned off is refused

kdb serve --backups off --backup-schedule "daily 16:00"

Output:

error: a backup schedule needs a backup folder: add --backups <dir> (or KDB_BACKUP_DIR)

exit code 1

--backup-copy

Syntax:

kdb serve --backups <dir> --backup-schedule "..." --backup-copy <dir on another disk>

After each scheduled backup, KDB copies it to a second folder (another disk, a USB disk, a mounted network drive) and checks the copy there: every block checksum and every log checksum is read back from the copy. A backup on the same disk is lost with the disk.

--backup-command

Syntax:

kdb serve --backup-command "<command with {dir}, {database}, {name}>"

After every backup the server makes (the schedule, BACKUP DATABASE, Backup now, POST /api/backup), it runs this command to copy the backup off the server. KDB has no cloud code of its own: the command is any tool you have installed and logged in (rclone, the AWS CLI, scp, robocopy).

In the commandBecomes
{dir}the new backup folder, full path
{database}the database's name
{name}the backup folder's own name, like 20260924-160000-123

Syntax:

rclone:   --backup-command "rclone copy {dir} remote:kdb/{database}/{name}"
AWS S3:   --backup-command "aws s3 cp --recursive {dir} s3://my-bucket/kdb/{database}/{name}"
scp:      --backup-command "scp -r {dir} backup@10.0.0.9:/srv/kdb/{database}/"
Windows:  --backup-command "robocopy {dir} \\nas\kdb\{name} /E"

A copy made with --backup-command is a plain folder: to use it, copy it back and restore it (kdb restore, or put it in the backup folder and use POST /api/restore).

Command line: A command after each backup: here it only prints the folder

kdb serve --backup-command "echo copy {database} {name}"

Output:

KDB 0.2.0-dev
  console   http://127.0.0.1:9900
  data      ./kdb-data (from KDB_DATA_DIR; one folder per database)
  default   default (used when a request names no database)
  backups   ./kdb-data/_backups/<database name> (from default)
  copy      after each backup: echo copy {database} {name} (from --backup-command)
  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 on (SHOW AUDIT)
  logs      slow queries >= 1000 ms to ./kdb-data/_logs/slow_queries.jsonl; pg_stat_statements on; metrics at /metrics
  recovery  the default database is created when it is first used

Press Ctrl+C to stop.

--backup-warn-hours

Syntax:

kdb serve ... --backup-schedule "..." --backup-warn-hours 26        (0 = no warning)

Each database is checked on its own. When a database's newest good backup is older than this, the log says so once an hour, naming the database: WARNING: the last good backup of 'sales' is 30 hours old .... /api/info and the console show the same warning.

Backup status in /api/info

Syntax:

GET /api/info  ->  "backup_schedule": {"schedule", "copy_to", "next_run_ms", "last_run_ms", "last_run_ok",
                                       "last_ok_ms", "running", "warning", "databases": [...]}

Syntax:

GET /api/info  ->  "backup_command": {"set", "runs", "failed", "dropped", "queued", "running",
                                      "last": {"database", "dir", "ok", "exit_code", "output", "started_ms", "took_ms"}}