# Backup, check, repair and restore > How KDB keeps your data safe, how to make and test backups, and how to get the data back after damage or a mistake. - Category: Backup, check, repair, restore - Keywords: backup, backups, CHECKPOINT, checkpoint now, settle after a load, restore, point-in-time restore, PITR, point-in-time window, restore drill, monthly restore, test restore, restore while running, online restore, /api/restore, swap database, /api/databases/swap, wal\_archive\_mb, undo, bad import, UPDATE without WHERE, verify, verified backup, hard links, retention, keep daily, keep weekly, prune, daily backup, Task Scheduler, cron, damage, disk failure, safety, --hard-links, --keep-daily, --keep-weekly, --logs-from, --until, backup.kdb, /api/backup, /api/check, Backup now, Check now, disk full, 507, 53100, no space, out of memory, ENOMEM, Cannot allocate memory, 53200, try again, compressed log, compressed\_log, log compression, WAL compression, KDB\_WAL\_COMPRESS, packed log records, kdb\_wal\_packed\_input\_bytes\_total - See also: [kdb info, check, repair, dump-file](cli-maintenance.md), [How KDB stores data](data-formats.md), [kdb serve](cli-serve.md), [Transactions](transactions.md), [Health, info, check and backup over HTTP](http-server-info.md) KDB is built so that a crash, a power cut or a bad disk block never gives you wrong data without telling you. But only **backups on another disk** protect you from a dead disk or a big mistake (a wrong `UPDATE`, a bad import). This page shows how to make backups, check them, and restore from them. Most commands here need the database **closed** (no `kdb serve`, no `kdb shell` on it). With a running server, use `BACKUP DATABASE`, the console's **Backup now**, `POST /api/backup` and `POST /api/check`, and [restore while the server runs](#restore-online). ## How KDB protects your data | Danger | What KDB does | |---|---| | Power cut or crash while writing | Every change goes into the **log** (WAL, write-ahead log) first. The log is on the disk before KDB says "done". At the next start KDB replays the log. | | A file only half written | Data files are **never changed**. KDB writes a new version, checks it, and then switches to it. | | A bad bit on the disk | Every block of every file has a **checksum** (CRC32C, a short number made from the bytes). KDB notices one wrong bit and names the file and block. | | A damaged data file | The **repair ladder**: the previous version of the file plus the log, then a copy from a backup plus the log. See [kdb repair](cli-maintenance.md#kdb-repair). | | A damaged list of files | The list (the **manifest**) is saved **twice**. One good copy is always left. | | A mistake (wrong UPDATE, bad import) | **Point-in-time restore**: a backup plus the log, up to the second before the mistake. See [restore while running](#restore-online) and [kdb restore](#kdb-restore). | | The whole disk dies | Only backups on another disk help. | | The disk is full | KDB stops accepting writes when less than 64 MB is free, so nothing is left half written. See [when the disk is full](#disk-full). | More details: [data formats](data-formats.md) and [chapter 9](../tutorial/09-backup-and-safety.md). ## The log is compressed A commit has to wait until its log records are on the disk. For a big commit (a whole-history rewrite of one symbol: `DELETE` 240,000 rows and `INSERT ... SELECT` them again) that is tens of MB, and the wait for the disk was most of the commit's time. Since 2026-10-02 KDB packs such records before it writes them. | What | How | |---|---| | which commits | those whose log records hold **64 KB or more**; smaller commits are written exactly as before, so they cost nothing extra | | how much smaller | about **a third** of the bytes for candle rows and deletes (measured: 44 MB of log became 14 MB) | | how fast | packing runs on up to 8 threads (at most half the CPUs); measured on that rewrite, its `COMMIT` took 41 ms instead of 89 ms | | never bigger | a group of records that does not get smaller (random bytes) is written as before | | safe | every record inside is checked again (its checksum) when it is read; a torn end after a power cut is found as before | | encrypted databases | the records are packed first, then [encrypted](encryption.md) | | everything that reads the log | recovery, `kdb check`, backups and restore, [standby copies](standby.md), [change streams](change-stream.md) and `AS OF` read packed records like any others | | watch it | `/metrics`: `kdb_wal_packed_input_bytes_total` (what the records would have taken) and `kdb_wal_packed_output_bytes_total` (what was written) | | turn it off | `KDB_WAL_COMPRESS=off` in the environment of `kdb serve` (or any `kdb` command) | Packed records are a stored-format feature (`compressed_log`, see [versions](versions.md)): a KDB from before 2026-10-02 refuses them as "needs a newer KDB", never as damage. [kdb downgrade](versions.md#downgrade) to such a KDB turns packing off by itself while it works, so it leaves no packed record that the older KDB would have to read. ## When the disk is full Syntax: ```sql HTTP: 507 Insufficient Storage (/exec: {"error": ...}; POST /write: {"code": "disk full", "message": ..., ...}) PostgreSQL: SQLSTATE 53100 (disk_full) ``` KDB looks at the free space of the data disk before it writes, at most once a second. Below **64 MB** free it refuses the write with `only N MB free on disk; KDB pauses writes below 64 MB so nothing is half-written`. A write the disk itself refuses (it filled up in between) gets the same HTTP code and SQLSTATE, with the system's message. | What | What happens | |---|---| | the refused write | nothing of it is saved; the whole request or transaction is rolled back | | reads | go on as usual | | the database | is not damaged and needs no repair | | later writes | work again as soon as there is room; no restart is needed | - Free room: delete old backups on that disk, empty the [trash](trash.md), drop old partitions, or move the data folder to a bigger disk (the [Settings page](console-settings.md)). - A loader should treat 507 like 503: wait, then send the same request again. It was not applied. - One exception: when the disk fills while the log is being flushed, the 507 message says `The rows of this write are applied in memory and reach the log once disk space is freed`. Those rows are already there (reads show them) and are saved once space is back. For a table with a key, sending them again is harmless; for a table without one, do not send them again. ## When the system is short of memory Syntax: ```sql HTTP: 503 Service Unavailable, Retry-After: 1 (not applied: send it again) HTTP: 500, no Retry-After ("... The rows of this write are applied in memory ...": do not send it again) PostgreSQL: SQLSTATE 53200 (out_of_memory) ``` Sometimes the operating system refuses a write to a file for a moment: `Cannot allocate memory` (ENOMEM). A Docker VM whose memory is full of file cache does this. It is not a broken disk, and since 2026-10-02 KDB treats it so: | What | What happens | |---|---| | a read, write or truncate of a file | tried again after a short pause, for about 2 s, before it counts as failed | | a write to the log | undone and tried again, for about 3 s more | | a write that still fails | gets 503 with `Retry-After` (it was not applied: send it again in a moment); the database does **not** stop and needs no reopen | | reads, `/health` | go on as usual | | later writes | work again by themselves when memory is back | - One exception, as with a full disk: when the shortage hits the shared flush of the log, the rows are already in the table. That answer is **500 without `Retry-After`**, and its message says `The rows of this write are applied in memory`. Reads show those rows, and KDB writes them to the log as soon as it can. Do not send them again to a table without a key: it would hold them twice. - `kdb_io_retries_total` in [/metrics](http-server-info.md#metrics) counts the calls that were tried again. ## BACKUP DATABASE Syntax: ```sql BACKUP DATABASE ``` Makes a backup of the current database **while it stays open**, then reads the whole backup again to check it. It works in `kdb serve` (console, `POST /exec`), in `kdb shell` and in `kdb sql`, when there is a backup folder. `kdb serve` always has one unless it is started with `--backups off` (`_backups` in the data folder, `_backups/` next to a folder served by its path, or `--backups `; see [where the settings come from](backup-schedule.md#backup-settings)). `kdb shell` and `kdb sql` need `--backups `. | Result column | Meaning | |---|---| | `backup` | The new backup folder. Its name is the **UTC** time: `20260911-081906-660`. | | `files` | Data files in the backup | | `bytes` | Size of the backup | | `log_lsn` | The backup holds every change up to this log position | - **Deletes old backups:** after a good backup it keeps the newest backup of each of the last 7 days and of the last 4 weeks, and deletes the rest of that folder. - In a server with a data folder, a database backs up into `/`. - Needs the `admin` role when the server has users. - Without a backup folder (`kdb sql` without `--backups`, or a server started with `--backups off`): `no backup folder is set`. - A server with a [copy command](backup-schedule.md#backup-command) copies each new backup off the server. **Command line: In kdb shell, started with a backup folder** (sample data: sample) ```bash kdb shell market --backups backups > BACKUP DATABASE; ``` Output: ```text KDB 0.2.0-dev shell on market End statements with ';'. Type .help for commands, .exit to quit. kdb> backup | files | bytes | log_lsn ----------------------------+-------+--------+-------- backups/20261022-054329-910 | 9 | 194519 | 30 (1 row, 25.8 ms) backup created and verified kdb> ``` **Example: No backup folder was given** (sample data: sample; shows an error) ```sql BACKUP DATABASE; ``` Result: Error: `no backup folder is set; start KDB with a backup folder (for example --backups D:/kdb_backups)` ## CHECK TABLE Syntax: ```sql CHECK TABLE ``` Reads every block of every data file and every log record, and checks the checksums. It changes nothing. - **Result:** a column `problem` with one row per problem (no rows when all is well), and the message `OK: no damage found in N files` or `N problem(s) found`. - **It always checks the whole database**, whatever table you name. The name is not even checked. - The same as `.check` in `kdb shell`, **Check now** in the console, `POST /api/check`, and [kdb check](cli-maintenance.md#kdb-check) for a closed database. - Needs the `admin` role when the server has users. **Example: Check the database** (sample data: sample) ```sql CHECK TABLE ochl_day; ``` Result: | problem (STRING) | |---| (0 rows) ```text OK: no damage found in 14 files ``` ## CHECKPOINT Syntax: ```sql CHECKPOINT ``` Writes every change of the current database that waits in memory (and in the log) to its data files, and answers only when that is done. It is PostgreSQL's `CHECKPOINT`. A benchmark or a loader sends it after a bulk load, so that what comes next does not run beside the background writing of that load. | What | How | |---|---| | a checkpoint already running | it waits for that one to end first, then writes what is still waiting | | nothing waiting | answers at once: `nothing to write: every change is in the files already` (no file is written) | | readers | go on while the files are written; writers wait until it is done | | the answer | no rows; the message `checkpoint done: every change is in the files`. Over the PostgreSQL port the command tag is `CHECKPOINT` | | where | HTTP `POST /exec`, the PostgreSQL port, the console, `kdb shell` | | rights | the `ddl` role (or `admin`) when the server has users, as for `COMPRESS TABLE` | - Send it **alone**: `CHECKPOINT; SELECT 1` is refused with `CHECKPOINT runs alone`. - Not inside a transaction block: after `BEGIN` it is refused (SQLSTATE `25001`); `COMMIT` or `ROLLBACK` first. - Your changes are safe without it: every commit is in the log on the disk before KDB answers. `CHECKPOINT` only moves the work of writing the data files to a moment you choose. See [data formats](data-formats.md) for when KDB checkpoints by itself. - On a read-only database there is nothing to write, and it answers at once. **Example: Write out what a load left waiting** (sample data: sample) ```sql CHECKPOINT; ``` Result: ```text checkpoint done: every change is in the files ``` ## kdb backup Syntax: ```sql kdb backup [--hard-links] [--keep-daily N] [--keep-weekly N] [--data-dir ] ``` Makes a backup of a **closed** database into a new folder inside ``, then reads the whole backup again. Only then it prints `backup created and verified`. | Option | Meaning | Default | |---|---|---| | `` | A database name in the data folder, or a folder path | required | | `` | The folder for backups. It is created when missing. For a database **name**, the backup goes to `/`. | required | | `--hard-links` | Hard links instead of copies for the data files: very fast, no extra space, but **no protection** from disk damage (see below). The log files are always copied. | copies | | `--keep-daily N` | After the backup, keep the newest backup of each of the last `N` days | 7 (when only `--keep-weekly` is given) | | `--keep-weekly N` | After the backup, keep the newest backup of each of the last `N` weeks (weeks start on Sunday, UTC) | 4 (when only `--keep-daily` is given) | - **Exit code:** `0` = the backup is made and verified. `1` = error (for example `the database is open in another program`). `2` = damage (the backup failed its check). - **Without `--keep-daily` and `--keep-weekly` nothing is deleted.** With one of them, backups that no rule keeps are deleted, and each one prints `removed old backup ...`. The newest backup is always kept. Days count only days that have a backup. - **Backup folder names are UTC time**: `20260911-081906-660` = 2026-09-11 08:19:06.660 UTC = 14:04 in Nepal (+05:45). - Inside: `backup.kdb` (the list, written last), `files/` (the data files) and `wal/` (the log needed). A folder whose name ends in `.partial` is an unfinished backup; it is never used. - **Put the backups on another disk** (`E:\kdb_backups`). One broken disk must not destroy both. - **Hard links** share the same disk sectors as the database. If the disk goes bad, both are lost. Use them only as an extra undo point before a big import, never as the only backup. - A running server has the database open, so `kdb backup` fails with `the database is open in another program. Stop it first, or run BACKUP DATABASE in that server's console or API.` - **Watch the path:** `kdb backup` on a folder that does not exist makes a new empty database there and backs it up (`0 files`) with exit code `0`. Check the output of a scheduled backup. **Command line: A daily backup that keeps 7 daily and 4 weekly backups** (sample data: sample) ```bash kdb backup market backups --keep-daily 7 --keep-weekly 4 kdb backups backups ``` Output: `> kdb backup market backups --keep-daily 7 --keep-weekly 4` ```text backup created and verified: backups/20261022-054330-059 9 files, 190.0 KB, log up to 30 ``` `> kdb backups backups` ```text BACKUP CREATED FILES SIZE LOG LSN 20261022-054330-059 2026-10-22 11:28:30 9 190.0 KB 30 1 backups ``` **Command line: Three backups, but keep only the newest of today** (sample data: sample) ```bash kdb backup market backups kdb backup market backups kdb backup market backups --keep-daily 1 --keep-weekly 1 kdb backups backups ``` Output: `> kdb backup market backups` ```text backup created and verified: backups/20261022-054330-082 9 files, 190.0 KB, log up to 30 ``` `> kdb backup market backups` ```text backup created and verified: backups/20261022-054330-142 9 files, 190.0 KB, log up to 30 ``` `> kdb backup market backups --keep-daily 1 --keep-weekly 1` ```text backup created and verified: backups/20261022-054330-217 9 files, 190.0 KB, log up to 30 removed old backup backups/20261022-054330-142 removed old backup backups/20261022-054330-082 ``` `> kdb backups backups` ```text BACKUP CREATED FILES SIZE LOG LSN 20261022-054330-217 2026-10-22 11:28:30 9 190.0 KB 30 1 backups ``` **Command line: Hard links: a fast undo point before a big import** (sample data: sample) ```bash kdb backup market before-import --hard-links ``` Output: ```text backup created and verified: before-import/20261022-054330-104 9 files, 190.0 KB, log up to 30 ``` **Command line: A database name in the data folder backs up into a sub-folder** ```bash kdb shell > CREATE DATABASE reports; > USE reports; > CREATE TABLE daily (day DATE PRIMARY KEY, trades LONG); kdb backup reports backups kdb backups backups/reports ``` Output: `> kdb shell` ```text KDB 0.2.0-dev shell on default (data folder ./kdb-data) End statements with ';'. Type .help for commands, .exit to quit. kdb> database 'reports' created kdb> using database 'reports' kdb> table 'daily' created kdb> ``` `> kdb backup reports backups` ```text backup created and verified: backups/reports/20261022-054330-184 1 files, 427 B, log up to 2 ``` `> kdb backups backups/reports` ```text BACKUP CREATED FILES SIZE LOG LSN 20261022-054330-184 2026-10-22 11:28:30 1 427 B 2 1 backups ``` ## kdb backups Syntax: ```sql kdb backups ``` Lists the finished backups in a folder, **newest first**. | Column | Meaning | |---|---| | `BACKUP` | Folder name (UTC time) | | `CREATED` | When it was made, in the **local time** of this computer | | `FILES`, `SIZE` | Data files and total size (data and log) | | `LOG LSN` | The backup holds every change up to this log position | - **Exit code:** `0`; `1` when the folder does not exist. - Unfinished (`.partial`) and unreadable folders are not listed. It does **not** check the backups: use [kdb verify-backup](#kdb-verify-backup). - For a database name in the data folder, list `/`. **Command line** (sample data: sample) ```bash kdb backup market backups kdb backups backups ``` Output: `> kdb backup market backups` ```text backup created and verified: backups/20261022-054330-179 9 files, 190.0 KB, log up to 30 ``` `> kdb backups backups` ```text BACKUP CREATED FILES SIZE LOG LSN 20261022-054330-179 2026-10-22 11:28:30 9 190.0 KB 30 1 backups ``` **Command line: A folder that does not exist** (sample data: sample) ```bash kdb backups no-such-folder ``` Output: ```text error: Not found: list 'no-such-folder': Windows error 3 ``` exit code 1 ## kdb verify-backup Syntax: ```sql kdb verify-backup ``` Reads **one** whole backup again: every data file and block against its checksum, every log file, and that the log reaches the end the backup promises. - **Exit code:** `0` = `OK: backup is intact`. `2` = problems, one `PROBLEM:` line each. A folder that is not a backup also gives `2`. - `` is one backup folder, like `E:\kdb_backups\20260911-081906-660`, not the folder of all backups. - A backup you never tested is not a backup. Verify old backups now and then, and do a test restore once a month. The help build cannot know the name of a new backup folder, so this is real output from [chapter 9](../tutorial/09-backup-and-safety.md): ```powershell $newest = Get-ChildItem backups -Directory | Sort-Object Name | Select-Object -Last 1 kdb verify-backup "backups\$($newest.Name)" "exit code: $LASTEXITCODE" ``` ```text OK: backup is intact exit code: 0 ``` **Command line: The folder of all backups is not one backup** (sample data: sample) ```bash kdb backup market backups kdb verify-backup backups ``` Output: `> kdb backup market backups` ```text backup created and verified: backups/20261022-054330-268 9 files, 190.0 KB, log up to 30 ``` `> kdb verify-backup backups` ```text PROBLEM: backup.kdb: open 'backups/backup.kdb': Windows error 2 error: Corruption: backup.kdb: open 'backups/backup.kdb': Windows error 2 ``` exit code 2 ## kdb restore Syntax: ```sql kdb restore [--logs-from ] [--until "YYYY-MM-DD HH:MM[:SS]"] ``` Makes a **new** database folder from a backup. The old database is not touched. | Option | Meaning | Default | |---|---|---| | `` | One backup folder (`backups\20260911-081906-660`) | required | | `` | The new database folder. It must not exist, or be empty. | required | | `--logs-from ` | Also take the **newer log** from the old database, so changes made **after** the backup come back too | only the backup | | `--until "YYYY-MM-DD HH:MM[:SS]"` | Replay the log only up to this time, in the **local time** of this computer. Changes made in that second are included. | the end of the log | - **Exit code:** `0` = restored. `1` = error (the target is not empty, a wrong `--until`). `2` = the backup is damaged (`pick another one`). A wrong backup path gives the same message and `2`. - **First it verifies the backup.** Every copied file is checked again. - `--until` earlier than the backup itself is refused: `this backup already contains changes made after the requested time; pick an older backup`. - `--logs-from` only uses a log of the **same** database and history; another database's log is ignored with a note. - The log is replayed the first time the new database is opened. Run `kdb check ` before you use it. - A restored database has no older file versions yet; they appear again as data changes. - `` is a folder path. To use it as a database in the data folder, restore into `\`. **A story: an UPDATE without WHERE.** At about 14:05 someone ran `UPDATE ochl_day SET close = 0`. The newest backup was from 14:04. Restore the backup, add the newer log from the old database, and stop one second before the mistake. Real commands and output from [chapter 9](../tutorial/09-backup-and-safety.md) (the help build cannot run this, because it cannot know the backup's folder name): ```powershell $backup = Get-ChildItem backups -Directory | Sort-Object Name | Select-Object -Last 1 kdb restore "backups\$($backup.Name)" market-restored --logs-from market --until "2026-09-11 14:05:10" kdb sql market-restored "SELECT time, symbol, close FROM ochl_day WHERE time >= '2026-09-10' ORDER BY symbol, time" ``` ```text stopped at the requested time: changes after LSN 36 are not restored restored 9 data files from backups\20260911-142715-556 plus 1 newer log segments; the log (up to LSN 36) is replayed on first open restored into market-restored time | symbol | close -------------------------+--------+------ 2026-09-10T00:00:00.000Z | CITY | 504.9 2026-09-13T00:00:00.000Z | CITY | 508.2 2026-09-14T00:00:00.000Z | CITY | 511 2026-09-10T00:00:00.000Z | COAST | 376.1 2026-09-10T00:00:00.000Z | VOLT | 191.2 (5 rows, 0.2 ms) ``` The prices are back. The row of 2026-09-14 was added **after** the backup but **before** the mistake, and it is there too. Then switch over: stop the server, rename `market` to `market-broken` and `market-restored` to `market`, start the server. Keep `market-broken` until you are sure. With a running server, [restore while running](#restore-online) does all of this without a stop. A target folder that is not empty (real output from chapter 9): ```text error: Already exists: restore target is not empty: market-restored ``` **Command line: --until must be a local time like 2026-09-11 14:30** (sample data: sample) ```bash kdb restore backups/20260911-081906-660 market-restored --until "11.09.2026 14:30" ``` Output: ```text error: --until must look like "2026-09-11 14:30" or "2026-09-11 14:30:15" ``` exit code 1 **Command line: Not a backup folder** (sample data: sample) ```bash kdb restore no-such-backup market-restored ``` Output: ```text error: Corruption: the backup is damaged; pick another one backup.kdb: open 'no-such-backup/backup.kdb': Windows error 3 ``` exit code 2 ## Restore while the server runs Syntax: ```sql POST /api/restore {"database": "market", "as": "market_check", "until": "2026-09-24 14:05:09"} POST /api/databases/swap {"database": "market", "with": "market_check"} ``` The same as `kdb restore`, without stopping anything: 1. **Restore** a backup of `market` into a **new** database `market_check`. `market` keeps running and is not touched. 2. **Check** `market_check`: run a few queries, `CHECK TABLE`. 3. **Swap**: `market` gets the restored data, `market_check` the data `market` had. Users, rights and settings (read-only, dialect, old log, timers, pins) stay with the name `market`. 4. Drop `market_check` when you are sure. | `until` | What comes back | |---|---| | not given | the backup as it was | | a time (`2026-09-24 14:05:09`, UTC unless it ends in `Z` or `+05:45`), or `until_ms` | the backup, then `market`'s own log up to that time: **point in time** | | `latest` | the backup, then all of `market`'s log: a copy as of now | - The newest backup made before `until` is used, or name one with `"backup": "20260924-120000-000"`. - In the console: **Health** > **Restore a backup**, then **Swap in**. - The swap needs both databases unused: an open PostgreSQL connection or session on either gives `in use`; close it and try again. - Details: [POST /api/restore](http-server-info.md#api-restore) and [POST /api/databases/swap](http-server-info.md#api-swap). ### How far back a point in time can go A restore to a time starts from a backup made **before** it and replays the database's log from there. The log older than the data files is kept in `wal_archive`, up to its size limit (`wal_archive_mb`, 1 GB per database by default; [Settings](console-settings.md#wal-archive)). When the oldest kept log starts after a backup ended, that backup can only be restored as it was. `GET /api/backups` says how far back you can go now: Syntax: ```sql "point_in_time": {"possible": true, "from": "2026-09-23T10:00:00.000Z", "from_backup": "20260923-100000-000", ...} ``` - `from` is the earliest time you can restore to; anything from there to now works. - `possible: false` with `why`: the kept log does not reach the newest backup. Make a backup now, and raise `wal_archive_mb` or back up more often. - Rule of thumb: `wal_archive_mb` must hold the log written between **two** backups. A database that writes 300 MB of log a day and backs up daily needs at least 300 MB; 100 MB gives a point in time only for the last few hours. ## A monthly restore drill A backup you never restored is a hope, not a backup. Once a month, with the server running: | Step | How | Pass when | |---|---|---| | 1. restore the newest backup into a new name | `POST /api/restore {"database":"market","as":"drill_2026_09"}` (or **Health** > **Restore a backup**) | `"opened": true` | | 2. check it | `POST /api/check?db=drill_2026_09` | `"ok": true` | | 3. compare | `SELECT count(*)` of the main tables in both, and the newest row time | the counts match the backup's time | | 4. a point in time | the same with `"until"` an hour ago | it opens, rows up to that hour | | 5. the copy off the server | copy one backup back from the remote (rclone, S3) into the backup folder and restore it | it restores | | 6. clean up | `DROP DATABASE drill_2026_09` | | | 7. write it down | date, backup used, time taken | | Time step 1: that is how long a real restore takes. If `point_in_time` says `possible: false`, raise `wal_archive_mb`. **HTTP: Step 1 and 6 of a drill: restore the newest backup, then drop the copy** (sample data: sample) ```http POST /api/backup ### POST /api/restore Content-Type: application/json {"as": "drill_check"} ### POST /exec?db=drill_check Content-Type: text/plain SELECT count(*) FROM ochl_day ### DELETE /api/databases/drill_check ``` Response: `POST /api/backup` HTTP 200 (application/json) ```text { "name": "20261022-054330-492", "dir": "./backups/demo/20261022-054330-492", "created_ms": 1792647810492, "created": "2026-10-22T05:43:30.492Z", "end_lsn": 30, "checkpoint_lsn": 30, "files": 9, "bytes": 194519 } ``` `POST /api/restore` HTTP 200 (application/json) ```text { "database": "drill_check", "source": "demo", "backup": { "name": "20261022-054330-492", "dir": "./backups/demo/20261022-054330-492", "created_ms": 1792647810492, "created": "2026-10-22T05:43:30.492Z", "end_lsn": 30, "checkpoint_lsn": 30, "files": 9, "bytes": 194519 }, "until_ms": null, "used_log": false, "opened": true, "open_error": "", "notes": [ "restored 9 data files from ./backups/demo/20261022-054330-492; the log (up to LSN 30) is replayed on first open" ], "took_ms": 31, "next": "check 'drill_check', then POST /api/databases/swap {\"database\":\"demo\",\"with\":\"drill_check\"} to put it in place, or DROP DATABASE drill_check" } ``` `POST /exec?db=drill_check` HTTP 200 (application/json) ```text { "query": "SELECT count(*) FROM ochl_day", "results": [ { "statement": "SELECT count(*) FROM ochl_day", "columns": [ {"name": "count(*)", "type": "LONG"} ], "dataset": [ [57] ], "count": 1, "truncated": false, "rows_affected": 0, "message": "", "timings": {"parse_us": 38, "execute_us": 175} } ], "columns": [ {"name": "count(*)", "type": "LONG"} ], "dataset": [ [57] ], "count": 1, "timings": {"parse_us": 38, "execute_us": 175} } ``` `DELETE /api/databases/drill_check` HTTP 200 (application/json) ```text { "name": "drill_check", "dropped": true, "moved_to": "kdb-data/_trash/1792647810562-drill_check" } ``` ## A daily backup plan Syntax: ```sql after the market closes: kdb backup --keep-daily 7 --keep-weekly 4 right after: kdb check once a week: copy the newest backup to a third place once a month: a restore drill (below), then a few queries ``` The plan below is for market data that stops arriving at 15:00 local time (the examples use Nepal time and a Sunday-to-Thursday trading week, as on some exchanges). For logs, sensor readings or an app, pick the quietest hour of your own day. Do this every working day: | When (local time) | What | How | |---|---|---| | after 15:00, when imports are done | Backup to **another disk** | `kdb backup` (server stopped) or `POST /api/backup` (server running). Keep 7 daily + 4 weekly. | | right after the backup | Check the database | `kdb check` or `POST /api/check`. Exit code `2` or `"ok":false`: act now. | | every day | Read the log of the backup script | | | after each backup | Copy it to a third place (another PC, NAS, cloud) | [--backup-command](backup-schedule.md#backup-command), or `robocopy`, `rsync` or your backup tool | | once a month | **Test restore** and run a few queries | the [restore drill](#restore-drill), or `kdb restore D:\restore-test`, then `kdb sql D:\restore-test "SELECT count(*) FROM ochl_day"` | | before a big import or a schema change | An extra backup | `kdb backup ... --hard-links` is fine here | **The script** (Windows PowerShell; save as `D:\kdb-work\daily-backup.ps1`; not run here): ```powershell # daily-backup.ps1 - backup and check after the market closes. Use full paths in a scheduled task. $Kdb = "D:\KDB\build\bin\kdb.exe" $Data = "D:\kdb\market" $Backups = "E:\kdb_backups" $Log = "D:\kdb-work\backup-log.txt" Add-Content $Log "---- $(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')" $out = & $Kdb backup $Data $Backups --keep-daily 7 --keep-weekly 4 2>&1 | ForEach-Object { "$_" } $code = $LASTEXITCODE Add-Content $Log $out if ($code -ne 0) { Add-Content $Log "BACKUP FAILED (exit code $code)"; exit $code } $out = & $Kdb check $Data 2>&1 | ForEach-Object { "$_" } $code = $LASTEXITCODE Add-Content $Log $out if ($code -ne 0) { Add-Content $Log "CHECK FOUND A PROBLEM (exit code $code)"; exit $code } ``` **Windows Task Scheduler**, Sunday to Thursday at 15:30 (run PowerShell as Administrator; not run here): ```powershell $action = New-ScheduledTaskAction -Execute "powershell.exe" ` -Argument "-NoProfile -ExecutionPolicy Bypass -File D:\kdb-work\daily-backup.ps1" ` -WorkingDirectory "D:\kdb-work" $trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Sunday,Monday,Tuesday,Wednesday,Thursday -At 15:30 Register-ScheduledTask -TaskName "KDB daily backup" -Action $action -Trigger $trigger Start-ScheduledTask -TaskName "KDB daily backup" # test it once, then read the log ``` **Linux cron** (`crontab -e`; Sunday=0 to Thursday=4, computer time; not run here): ```bash 30 15 * * 0-4 /usr/local/bin/kdb backup /srv/kdb/data/market /mnt/backup/kdb --keep-daily 7 --keep-weekly 4 >> /var/log/kdb-backup.log 2>&1 && /usr/local/bin/kdb check /srv/kdb/data/market >> /var/log/kdb-backup.log 2>&1 ``` **If `kdb serve` runs all day**, let the server do it: `--backups E:\kdb_backups --backup-schedule "daily 15:30" --backup-copy F:\kdb_copy` makes, checks and copies a backup of every database each day, with no script (see [backups on a schedule](backup-schedule.md)). To do it from a script instead, ask the server. It keeps 7 daily and 4 weekly backups by itself (not run here): ```powershell $r = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:9900/api/backup?db=market" $c = Invoke-RestMethod -Method Post -Uri "http://127.0.0.1:9900/api/check?db=market" if (-not $c.ok) { "CHECK FOUND A PROBLEM: $($c.problems -join '; ')"; exit 2 } ``` With `--token` or users, add `-Headers @{ Authorization = "Bearer " }` (see [HTTP login](http-login.md)). ## After damage: what to do Syntax: ```sql 1. stop 2. copy the folder 3. kdb check 4. kdb repair --backups 5. kdb check 6. if still damaged: kdb restore --logs-from ``` Messages like `failed its checksum`, `DAMAGED`, `the end of the log is damaged`, or exit code `2`: 1. **Stop** the server and every program that uses the database. 2. **Copy** the whole database folder to a safe place (`Copy-Item D:\kdb\market E:\damaged-copy -Recurse`). Then nothing you try can make it worse. 3. `kdb check ` to see what is damaged. 4. `kdb repair --backups `. It uses the previous file version, then the backups, and replays the log. See [kdb repair](cli-maintenance.md#kdb-repair). 5. `kdb check ` again. When it says `OK: no damage found`, start the server again. 6. If the repair says `Some damage could not be repaired`: `kdb restore --logs-from `, check the new folder, and switch folders. 7. If needed log records are damaged and no backup has them: `kdb restore ... --logs-from` keeps everything up to the damage. `kdb repair --accept-log-loss` is the **last** choice: it opens the database and the changes in the damaged part are lost. 8. Find out **why**: disk errors in the Windows Event Viewer, a failing SSD (`smartctl`). Replace bad hardware. - After a crash or a killed server, `last shutdown: not clean` is normal: KDB replays the log, and confirmed changes are not lost. That is not damage. - A write that the crash cut in half at the very end of the log is dropped by itself, as if the power had gone a moment earlier. **Command line: Steps 3 to 5 on a healthy database** (sample data: sample) ```bash kdb check market kdb repair market --backups backups kdb check market ``` Output: `> kdb check market` ```text checked 14 files (76.1 KB) and 1 log segments OK: no damage found ``` `> kdb repair market --backups backups` ```text manifest copy: a, last shutdown: clean replayed 0 transactions after LSN 30, last LSN 30 Nothing to repair. ``` `> kdb check market` ```text checked 14 files (76.1 KB) and 1 log segments OK: no damage found ```