Appearance
Backup, restore and import
kd-tool is the command-line tool for three jobs:
kd-tool backupwrites a logical dump of a database to a directory.kd-tool restorewrites a dump back into a server.kd-tool importwrites a MongoDBmongodumpdirectory into a server.
A logical dump holds rows, documents and files, not disk blocks. So a dump taken with one build of kinedb restores into a later build, into a different cluster, or into a single server.
Get kd-tool
Every archive of the binaries holds kd-tool next to kinedb. See Download the binaries for the files of each platform:
sh
base=https://git.kinedb.com/api/packages/kinedb/generic/kinedb/latest
curl -fLO "$base/kinedb-x86_64-unknown-linux-musl.tar.gz"
tar xzf kinedb-x86_64-unknown-linux-musl.tar.gz
./kinedb-x86_64-unknown-linux-musl/kd-tool --helpThe container image also ships the tool, at /app/kd-tool. The entrypoint of the image is the node, so a bare docker run <image> /app/kd-tool hands those words to the node. Name the tool as the entrypoint:
sh
docker run --rm --entrypoint /app/kd-tool git.kinedb.com/kinedb/kinedb:latest --helpA build from the source repository gives the same tool:
sh
cargo build --release -p kinedb-tool
./target/release/kd-tool --helpkd-tool logs in with the same protocol as the SDKs. Use the kd-tool of the same build as the server: a kd-tool of another build can fail to log in. For an upgrade through a dump, run kd-tool backup of the old build and kd-tool restore of the new build (see Upgrade kinedb).
Connection options
Every subcommand takes the same connection options:
| option | meaning |
|---|---|
--host <host> | default 127.0.0.1 |
--port <port> | default 4820 |
--user <name> | the user to log in as |
--pass-file <path> | read the password from this file |
--password-env <VAR> | read the password from the environment variable VAR |
--pass <password> | the password on the command line; ps shows it to every user of the machine |
--api-secret-file <path> | log in with the API secret in this file |
--api-secret-env <VAR> | log in with the API secret in the environment variable VAR |
--key-file <path> | log in with the P-256 private key in this PKCS#8 file (see a key pair) |
--database <name> | default: the server's default database |
--branch <name> | default: the server's default branch |
The user needs the rights of the job. A backup creates a branch and reads every table; a restore or an import inserts into the target. A superuser can do all of it.
The password
Give the password in a file or in an environment variable. Do not use --pass on a shared machine: the command line of a process is visible in ps while the tool runs.
--pass-file <path>reads the file. The tool removes one line end (\nor\r\n) at the end of the file. An empty file or a file that the tool cannot read stops the tool before it connects. Give the file the mode600.--password-env <VAR>reads the variableVAR. An unset or empty variable stops the tool before it connects.- Without one of the three options, the tool reads
KINEDB_ROOT_PASSWORD, the same variable as the SQL shell. When it is unset or empty, the tool logs in with an empty password.
Give only one of --pass, --pass-file, --password-env, --api-secret-file, --api-secret-env and --key-file; the tool refuses two of them. With --pass, the tool writes a warning to stderr.
A key pair
--key-file <path> logs in with a P-256 key pair. The server sends a challenge, and the tool signs it with the private key. The private key does not go to the server. Add the public key to the user first:
sh
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out kd-key.pem
openssl pkey -in kd-key.pem -pubout # the text for ADD PUBLIC KEYsql
ALTER USER backup ADD PUBLIC KEY '-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----' NAME 'kd-tool';The file is PKCS#8, DER or PEM (-----BEGIN PRIVATE KEY-----), without a password, and it holds the public key. An openssl genpkey file and a WebCrypto pkcs8 export hold it. A SEC1 file (-----BEGIN EC PRIVATE KEY-----), an encrypted file (-----BEGIN ENCRYPTED PRIVATE KEY-----) or a file without the public key stops the tool before it connects. Convert such a file with openssl ec -in <old file> | openssl pkcs8 -topk8 -nocrypt -out <new file>: openssl ec writes the public key back into the file (openssl pkcs8 alone does not add it). Add -inform DER after openssl ec for a DER file. The key has no environment variable and no command-line form. Give the file the mode 600.
A daily backup, with the password in a file that only its owner can read:
sh
install -m 600 /dev/null ~/.kinedb-backup-pass
$EDITOR ~/.kinedb-backup-pass # write the password on one line
kd-tool backup --out "./dump-$(date +%F)" --user backup --pass-file ~/.kinedb-backup-passThe client name in the audit trail
kd-tool backup and kd-tool restore send a client name when they log in: kd-tool/<version> backup and kd-tool/<version> restore, for example kd-tool/0.0.2 backup. The server writes this name in the client_name column of the audit trail. To find the logins of the backups:
sql
SHOW AUDIT WHERE action = 'login' AND actor = 'backup';Look for kd-tool/ at the start of the client_name column. kd-tool import sends no client name.
Backup
sh
kd-tool backup --out ./dump-2026-09-14 --user root --pass-file ~/.kinedb-pass
KINEDB_ROOT_PASSWORD=... kd-tool backup --out ./dump-users --table users --table orders --user root| option | meaning |
|---|---|
--out <dir> | the dump directory to write (required). A directory that already holds a manifest is refused. |
--table <name> | dump only this table or collection; repeat for more; none = every table and every collection |
What a backup does:
- It creates a temporary branch,
kdtool_backup_<unix_ms>, and reads every table from that branch. So every table in the dump comes from ONE commit: the dump is consistent across tables, and writes that arrive during the backup do not enter it. - For every table it records the column header and the indexes in the manifest, then streams
SELECT * FROM <table>into<table>.kdt(SHOW CONFLICT STATE OF <table>for a table with a clock, see below). A document collection is dumped as its documents. The node sends the rows while it scans, so the first frame arrives at once and the node does not hold the whole table in memory. A typed table is scanned two times: the first pass finds the column types. If you stop the tool, the node stops the read at the next frame. - For every table it lists the files with
SELECT FILE FROM <table>, on the same branch. It writes the manifest of each file tofiles.kdf, and each block of a file toblocks.kdb. See Files below. - It drops the temporary branch and writes
manifest.kdmlast. A dump without a manifest is incomplete, andrestorerefuses it. The branch is dropped on every exit path, an error included.
The dump directory:
text
<dir>/manifest.kdm the manifest: format version, source, every table's header and indexes, the functions and the triggers
<dir>/<table>.kdt the table's rows, in LZ4 frames of typed cells
<dir>/files.kdf the manifest of each file (only in a dump with files)
<dir>/blocks.kdb each block of every file, once (only in a dump with files)
<dir>/principals.kdp the users, the roles and the table policies (only in a dump by a superuser)
<dir>/providers.kdp the OIDC providers, with no client secret (only when the server has one)
<dir>/apps.kdp the stored apps and the DEFAULT list of return addresses, with no client secretThe manifest names the file of each table. Two table names can differ only in case, for example Users and users. On a disk that folds case, Users.kdt and users.kdt are one file. So the second table gets the file <table>~<n>.kdt, for example users~1.kdt. The backup never overwrites a file in the dump directory. restore reads the file name from the manifest, so you do not rename a file.
The format is binary and versioned. A reader refuses a manifest of a newer version and a column type it does not know; it never coerces a value.
The manifest also records the declared conflict policy of each table, as SHOW COLLECTIONS prints it. A dump with a declared policy is format version 2. A dump with no declared policy stays version 1, so an older kd-tool still reads it. An older kd-tool refuses a version 2 dump, so it never drops the policy in silence.
The manifest also records the column constraints of each table: NOT NULL, the DEFAULT and UNIQUE (see Column constraints in the SQL reference). A restore with --create writes them into the CREATE TABLE of the target, so the UNIQUE index comes back under the name of the target table. A dump with a constraint is format version 5, and an older kd-tool refuses it, so it never drops a constraint in silence. A dump made by a kd-tool before version 5 records no constraint: restore it into a table that you create yourself.
The manifest also records each other secondary index: its name, its columns and the UNIQUE flag. A restore with --create re-creates the indexes after the CREATE TABLE (or CREATE COLLECTION) and before the rows, under their own names:
- a UNIQUE index (
CREATE UNIQUE INDEX), - an index on two or more columns,
- an index on one column that is not TEXT,
- every index of a document collection, also an index on a field path such as
address.city.
A non-unique index of a table on ONE TEXT column is NOT re-created. SHOW INDEXES does not tell an ordinary index from a FULLTEXT index, so the dump does not record the kind. The restore prints one warning that names each such index and its column. Re-create it by hand, with CREATE INDEX or CREATE FULLTEXT INDEX. A restore without --create creates no index.
An index name is unique inside its database. Before the first change, a restore with --create refuses a dump whose index name the target database already holds, and names the index. Restore into another database, or drop that index first.
A dump with such an index is format version 7, and an older kd-tool refuses it, so it never drops an index in silence. A dump made by a kd-tool before version 7 records no index definition: a restore with --create re-creates no index of it except the UNIQUE column constraints, and prints one warning that names the indexes that it leaves out.
The manifest also records the kd functions of the server, under functions. Functions are global, so every backup records every function, also a backup with --table. For each function the manifest holds the statement of SHOW CREATE FUNCTION and the INVOKE grants of SHOW GRANTS ON FUNCTION (ANYONE, AUTHENTICATED, ROLE <name>). A grant to a role that was dropped names nobody, and the backup does not record it. A dump with a function is format version 3, and an older kd-tool refuses it. A dump with no function keeps version 1 or 2.
The statement holds the typed signature: the type of each parameter and the RETURNS clause (the signature). Before the first write, the restore parses the statement of every function of the dump. It refuses a function without types, and the error names the function. To restore such a dump, give the function its types on the source with CREATE OR REPLACE FUNCTION, then make the backup again (a function without types).
Only a superuser can read the grants of a function, and only a superuser or the run-as user can read its definition. When the backup user cannot read a function that a dumped table names as its conflict function, the backup stops and names the function. Any other function that the backup user cannot read is not dumped: the manifest lists it under skipped with the reason, and the backup prints a line for it.
Triggers
The manifest records each trigger of a dumped table, under triggers: its name, its table, its timing, its events and its function, as SHOW TRIGGERS gives them (Triggers). The function of each trigger is in the dump too. When the backup user cannot read the function of a dumped trigger, the backup stops and names the function. A dump with a trigger is format version 8, and an older kd-tool refuses it, so it never drops a trigger in silence. A dump with no trigger keeps its old version.
A restore with --create creates each trigger after the rows of every table, with CREATE TRIGGER … ON <table> CALL <function>. So the restore loads the rows as they were dumped, and no trigger runs on them. --into gives the trigger the new table name. A restored trigger belongs to the user who runs the restore. Before the first change, the restore refuses these dumps, and the error names each trigger:
- a trigger whose function or table is not in the dump,
- a trigger whose name a database of the target already holds. A trigger name is global, so the restore reads
SHOW TRIGGERSin each database ofSHOW DATABASES. A restore never replaces a trigger. The restore cannot read a database that the restore user cannot see: a name in such a database refuses theCREATE TRIGGERafter the rows. Run the restore as a superuser to check every database.
SHOW TRIGGERS lists only the triggers of a table that the backup user can see. So a backup by a user who cannot see a table also leaves out the triggers of that table. Run the backup as a superuser to keep every trigger.
A trigger of a table that --table leaves out is not restored, and the restore prints a line for it. A restore without --create creates no trigger.
Users, roles and grants
The dump also holds the access model of the server, in the principals section (principals.kdp):
- every role, with its member users, its member roles (a role inside a role) and its principal id (the column
principalofSHOW ROLES). A restore creates the role with the same id (CREATE ROLE <name> WITH PRINCIPAL '<id>', superuser only). So aMATCHrow that names the role still grants after the restore. A dump from an olderkd-toolhas no principal id: the restore then creates the role with a new id, and such a row grants nothing. A role that the target already holds keeps its own id, and the restore says so; - the members of the built-in roles
global_adminsandadmins_<db>, but not the roles. The server makes these roles itself:global_adminsalways exists, andCREATE DATABASE <db>makesadmins_<db>. A restore never creates a built-in role. It grants each member withGRANT ROLE, and it never grants one toroot. A dump that grantsadmins_<db>refuses a restore into a target with no database<db>, before any change: runCREATE DATABASE <db>on the target first. A built-in role that grants nothing (an entry of an older dump) is skipped with a warning line. A dump of a server with no member added to a built-in role holds no role entry for it; - the kind of each role, from the columns
built_inandadmin_ofofSHOW ROLES. The backup and the restore use this marker, never the name. A role with a built-in name and no marker (for example a roleadmins_xthat a user made before this release) is a plain role. Its members never become the admins of a databasex: a target whereadmins_xis the admin role ofxrefuses the restore before any change. A target that does not hold the plain role refuses too, because the server refusesCREATE ROLEof that name. Give the members another role on the source, and take the dump again. A dump of an olderkd-toolholds no marker: itsadmins_…roles that grant something refuse the restore, and the error tells you to take the dump again with thiskd-tool; - the value of
cluster.auth.max_role_depth, when the dump holds a role inside a role and the value is not the default 3. A restore refuses a dump with a value above 64, the upper bound of the server; - every user except
root, as the statement ofSHOW CREATE USER:CREATE USER '<name>' PASSWORD HASH '<hash>'. When the user has values of the password options, the statement ends withWITH CHANGE_OWN_PASSWORD = ON, SET_OWN_PASSWORD = OFF, KEEP_SESSIONS_ON_PASSWORD_CHANGE = ON(only the options that the user has). A restore sets these values when it creates the user. It does not change a user that the target already holds. The other cluster values (cluster.auth.*) are not in the dump; - the key pair of each user, as the clause
KEY '<hex>'at the end of the statement: the private half, locked by the cluster key (users, roles and permissions). The dump never holds the cluster key. A restore installs the same key pair, so the data that the key protects stays readable. This works only into a cluster with the same cluster key. The restore compares the fingerprint in each KEY withSHOW CLUSTER KEYof the target. Another fingerprint, or a target with no cluster key pair, refuses the restore before any change. A user that the target holds with another key pair refuses the restore. A dump of a user that was dropped holds no key of that user. A dump without the clause (an older dump) restores as before: the target makes a new key pair; - the home of each user, as the clause
DEFAULT DATABASE <db>at the end of the statement (a user ofdefaulthas no clause). A restore never creates a database: a home that the target does not hold refuses the restore before any change, and the error names every missing database (run CREATE DATABASE sales, shop on the target first). A user that the target holds with another home refuses the restore; - the rules on each user (
GRANT <rights> ON USER <u> TO <holder>) and the rules on the users of each home (GRANT <rights> ON USERS IN DATABASE <db> TO <holder>), from the rows of kinduserandusersofSHOW GRANTS(rights on a role and on a user).CREATE USERgives each new userCONTROLon himself. A user who does not hold it on the source comes back without it: the restore runsREVOKE CONTROL ON USER <u> FROM USER <u>right after the create. A user withrights_version0 inSHOW USERS(the upgrade did not give him the rule yet) is not a user who does not hold it: he comes back with it. The restore writes the rules after every role and user, so each holder exists. A holder that neither the dump nor the target holds refuses the restore. A user that the target holds with other rules refuses the restore. A rule on the users of a home that the target holds with the same or more rights is kept. A dump with a home or with these rules needs a target of this release, and akd-toolbefore this release refuses such a dump; - the credentials of each user (public keys and API secrets), as the later rows of
SHOW CREATE USER:ALTER USER '<name>' ADD PUBLIC KEY '<hex>'orALTER USER '<name>' ADD API SECRET HASH '<hash>', with theNAMEand theEXPIRE AT(credentials). The dump holds the hash of an API secret, never the secret. A restore adds the credentials when it creates the user, with the sameid,nameandexpire_at; the target sets a newcreated_at. A credential that expired before the backup is not in the dump. A credential that expires between the backup and the restore makes the target refuse itsALTER USER, and the restore stops. A user that the target already holds with other credentials refuses the restore before any change. A dump with a credential cannot be read by akd-toolbefore this release: that reader refuses the dump, so it never drops a credential in silence; - the grants of each dumped table, as its collection policy (the rows of
SHOW GRANTS ON <table>). A rule with aWHEREfilter keeps its filter.
The dump holds the password hash of each user, never a password. Keep a dump as a secret file: a person who reads it can try to guess the passwords offline. root is never in the dump: the target keeps its own root and its own password.
The backup reads the users, the roles and the policies at the time of the backup, not from its snapshot branch. A change to them while the backup runs can be in the dump or not.
A grant to a role that was dropped names nobody, and the backup does not record it. A role member that is not a user of the dump stops the backup, and the error names it.
The backup reads the member users of each role from the column members_json of SHOW ROLES. A user name with a comma is thus backed up and restored whole. A role name with a comma in a member list stops the backup before it writes, and the error names the role. Rename the role, then run the backup again. A restore whose target holds such a role name is refused before any change for the same reason.
Use a kd-tool and a server of the same build.
A dump that holds a role inside a role cannot be read by a kd-tool before this release: that reader refuses the dump, so it never drops the role in silence. Back up a server of this release with a kd-tool of this release or newer. An older kd-tool reads only the member users of each role. Its dump holds no member roles and no value of cluster.auth.max_role_depth, and it gives no warning about them.
Only a superuser reads all the users, the roles and the grants. Another user reads only a filtered SHOW USERS and SHOW ROLES, so the backup runs SHOW GRANTS to find out if the backup user is a superuser. Today a backup needs a superuser anyway, because it creates a branch. When the backup user is not a superuser, the dump holds no principals section: the manifest lists principals under skipped with the reason, and the backup prints a line for it. A reader that does not know the principals section refuses the dump, so it never drops the users in silence.
OIDC providers
The dump holds each live OIDC provider of the server, in the providers section (providers.kdp). The backup reads SHOW PROVIDERS for the names and SHOW CREATE PROVIDER <name> STORED for each provider. A dropped provider is not in the dump. A server with no provider gives a dump with no providers section.
Each provider keeps its kind, its endpoints, its stored keys, its algorithms, its lists, its role options and its switches. A DISCOVERY provider keeps the keys and the time of its first fetch: the restore makes no network request.
The client secret is never in the dump, not even in its sealed form. A restored provider has no client secret, and SHOW PROVIDERS shows none. For each provider that had a secret, the restore prints a line:
text
restore provider corp: the client secret is not in the dump; run ALTER PROVIDER corp CLIENT SECRET '…'Run that statement with the secret of the identity provider after the restore. The provider then uses its old token auth method again.
The backup needs a server that knows SHOW CREATE PROVIDER … STORED. An older server refuses it, and the backup stops with that error: upgrade the server. A reader that does not know the providers section refuses the dump, so it never drops a provider in silence.
Federation links
A federation link of a user is one more row of that user in the principals section, the row of SHOW CREATE USER:
text
ALTER USER '<u>' ADD FEDERATION PROVIDER '<p>' SUBJECT '<s>' [NAME '<n>']The dump holds the subject: a restore needs it to link the same account. A user with no password (CREATE USER … NO PASSWORD) has the row CREATE USER '<u>' NO PASSWORD [WITH …] and no password hash. restore --create writes the providers BEFORE the users, because a link row names its provider. Before any change, it refuses a link row whose provider is not in the providers section of the dump:
text
restore user <u>: a federation link names the provider <p>, which is not in the dump; take the dump again, or restore that provider firstThe refusal names no subject. A provider of the same name on the target is not enough: it can be another identity provider. A kd-tool older than B3854 refuses a dump with a link row or a NO PASSWORD row by name, so it never drops a link in silence. An error of kd-tool shows a user or provider statement as a fixed label, never its text.
Apps and the DEFAULT list of return addresses
The dump holds each stored app of the server and the DEFAULT list of return addresses, in the apps section (apps.kdp). The backup reads SHOW APPS for the names, SHOW CREATE APP <name> for each app with stored = true, and the row cluster.auth.default_return_addresses of SHOW SETTINGS. The built-in app kinedb-admin is in the dump only after an ALTER APP 'kinedb-admin' stored it. A dropped app is not in the dump. A server with no stored app and an empty DEFAULT list gives a dump with no apps section.
The client secret is never in the dump, and its hash is not in the dump either. A restored app has no client secret, and SHOW APPS shows none. For each app that had a secret, the restore prints a line:
text
restore app shop: the client secret is not in the dump; run ALTER APP shop NEW CLIENT SECRETRun that statement after the restore, and give the new secret to the application.
The restore reads each statement with the parser of the server and each return address pattern with the pattern check of the server, before the first change. A refused pattern stops the restore, and the error names the frame and the position of the pattern, not its text. A stored app of a dumped name on the target (also a stored kinedb-admin), more than 64 apps in all, or another non-empty DEFAULT list on the target refuses the restore before any change. A target DEFAULT list that holds the same patterns is kept.
The backup needs a server that knows SHOW APPS. An older server refuses it, and the backup stops with that error: upgrade the server. A kd-tool that does not know the apps section refuses the dump by name, so it never drops an app in silence.
The issuer and the ID token signing keys
The dump holds the issuer of the identity provider and the number of signing keys, in the idp section (idp.kdp). The backup reads cluster.auth.issuer of SHOW SETTINGS and counts the rows of SHOW ID TOKEN KEYS. A server with no issuer and no key gives a dump with no idp section.
A key is never in the dump. The dump holds no kid, no public key, no private key and no sealed key. It holds the count only. The restore runs the statement of the issuer. It makes no key. When the dump had keys, the restore prints a line:
text
restore: the ID token signing keys are not in the dump; the first ID token makes a new key; apps read it from the key document by themselvesThe target cluster needs its cluster key before it makes that first key. No action is needed in the apps: they read the new key from the key document. A restore without --create writes no IdP state and prints one line that says so.
A restore never replaces an issuer. When the target already holds a different cluster.auth.issuer, restore --create refuses before any change. When the target holds the same issuer, the restore keeps it.
The backup needs a server that knows SHOW ID TOKEN KEYS. An older server refuses it, and the backup stops with that error: upgrade the server. A kd-tool that does not know the idp section refuses the dump by name.
The conflict state of a table with a clock
A table WITH CONFLICT POLICY LWW or LPW keeps a clock on each row (and, for LPW, on each cell), and a delete in such a table keeps the clock of the delete. A merge and a sync push decide a conflict by these clocks. The backup keeps them:
- It reads such a table with
SHOW CONFLICT STATE OF <table>in place ofSELECT *. The statement gives each row and the stored conflict header of the row, and each deleted row with the clock of its delete. - The table file holds three more frame kinds: the conflict header of each row, and the deleted keys with their clocks. The tool does not decode the headers. It reads only the tag and the version, and it refuses a version that it does not know.
- The manifest records the stored slot of each column (the
stored_slotcolumn ofSHOW COLUMNS). A restored table can have other slots, for example after aDROP COLUMN. The server moves each cell clock to the slot of the same column. - A dump with conflict state is format version 4, and an older
kd-toolrefuses it.
The backup user needs the SELECT right on the whole table: a row-level grant does not show the deleted keys. A server without SHOW CONFLICT STATE cannot give the clocks, so a backup of such a table from it stops and writes no dump.
Files
A file of INSERT … FILE, put_file or putFile is a manifest and its content blocks (see Files in the SQL reference). The row of a table does not hold the bytes, so the backup dumps the files on their own:
files.kdfholds one frame for each file: the table, the file name, the size, the blake3 of the whole file, and the blake3 of each block in file order.blocks.kdbholds each block that a dumped file names: its blake3 and its bytes. A block that two files share is written once. The backup checks the blake3 of each block that it reads.- The manifest lists the two files under
sections, with the frame count and the byte length of each. A dump with a section is format version 6. An olderkd-toolrefuses it, so it never restores the rows without their files in silence. A dump with no file keeps its older version. - A reader refuses a section kind that it does not know, and the error names the kind. A dump that a newer
kd-toolwrote with a new kind of section is refused, never read in part.
A document collection lists its plain documents in SELECT FILE too. Such a document names no block, so its row in <table>.kdt holds all of it, and the backup does not dump it as a file.
A table with a BLOB column is still refused: a BLOB cell carries only block hashes over the client wire (B1889). The files of a table are a different thing, and the backup dumps them.
A dump of a kd-tool before format version 6 holds no file. A restore of such a dump gives the rows of a file table without the files.
A dump does not hold branches, their base commits or the merge ancestor rows, and it does not hold the client side of a sync (the op-log, the base rows). A restored database has no branches.
Restore
sh
kd-tool restore --in ./dump-2026-09-14 --create --user root --pass-file ~/.kinedb-pass
kd-tool restore --in ./dump-2026-09-14 --table users --into users_copy --create --user root --pass-file ~/.kinedb-pass| option | meaning |
|---|---|
--in <dir> | the dump directory to read (required) |
--table <name> | restore only this table; repeat for more |
--into <name> | write into this table instead of the dumped name; needs exactly one --table |
--create | run CREATE TABLE from the manifest before writing. Refused before the connection opens when the manifest holds a compound primary key or a column type the SQL surface cannot spell. A column that is NULL in every row of the dump takes the type that SHOW COLUMNS gave at backup time (the manifest key declared_types, B1868). A dump from an older kd-tool has no such key, and --create refuses that column by name. A declared conflict policy is declared again (WITH CONFLICT POLICY). Before the first table, every kd function of the dump is created with its INVOKE grants. A function that the target already holds with the same definition is kept, and its grants are given again. A function that the target holds with a different definition refuses the restore before any change, and the error names each such function: restore never replaces a function. The restore creates a function with a plain CREATE FUNCTION, so a function of the same name that another client creates during the restore stops it. Before the functions, the restore creates the roles, the users and the role members of the principals section, and after each table it sets the policy of that table (ALTER COLLECTION … SET POLICY). A restored user logs in with the password it had on the source, because the dump holds its hash. A user that the target holds with another password, and a table that the target already holds, refuse the restore before any change, and the error names each one. A user that the target holds with the same password but with other values of the password options also refuses the restore, also when the dump user has no value: the restore never changes a user of the target. The error names the values of the dump and of the target. Align them (ALTER USER … SET <option> = ON | OFF | DEFAULT), then run the restore again. A role that the target already holds keeps its members and gets the missing ones. The member roles come after every role and every user. When the dump holds a value of cluster.auth.max_role_depth that is larger than the value of the target, the restore sets it before the member roles; it never makes the value of the target smaller. A member role that makes a cycle with the roles of the target, or a chain of roles that is longer than that value, refuses the restore before any change. A hash with fewer iterations than the minimum of the target server is refused. Before the roles and the users (B3854: a federation link row names its provider), the restore creates each OIDC provider of the providers section, with no client secret. A link row whose provider is not in the dump refuses the restore before any change. A provider of a dumped name that the target already holds, or more than 64 providers in all, refuses the restore before any change. After the roles and the users, the restore creates each app of the apps section with no client secret, and sets the DEFAULT list of return addresses when the target list is empty; the apps section names the refusals. Without --create, restore creates no function, no user, no role, no policy, no provider and no app. |
--append | write into a table that already holds rows |
--in-flight <n> | batches on the wire at once; default 2, 1 = one at a time |
A restore writes into an EMPTY table. The server checks the target on every request, and a table that already holds rows is refused:
text
bulk load into non-empty table t: 5 rows; pass append to add to them. Nothing was written.--append lifts that check. A table with 0 rows in the dump also sends one request with no rows, so the same check runs on its target. The server answers it with 0 rows written, on a single server and on a --calvin cluster, and the restore continues with the next table. A request of 0 documents with --append into a name that does not exist is a first write: it creates an empty DOCUMENT collection, on a single server and on a --calvin cluster. Without --append, it answers table not found. A restore never truncates a table and never drops one. The rows travel in the same typed form the dump holds, one frame per request, so a large table is never built in memory.
The files after a restore
A restore writes the files of a table BEFORE its rows. For each file it sends each block with put_file_chunk, checks the size and the blake3 of the whole file against the dump, and then sends the manifest with put_file. The rows follow, and they add to the rows that the files made, so each cell of a file row keeps its dumped value. The restore checks the blake3 of each block that it reads from blocks.kdb, and it refuses a section file whose length or frame count is not the one that the manifest records.
A shard split or a node that is not ready can refuse a block or a manifest with a hold (write_hold, not_ready). The refused request wrote nothing, so the restore sends that ONE request again, on the server's retry hint, for at most the hold budget of the client. It does not send the whole file again. Every other error stops the restore, as before.
The first request of a table is then a file, not a row. So, without --create and without --append, the restore counts the rows of the target before it writes a file, and it refuses a target that holds rows:
text
restore into non-empty table t: 5 rows; pass --append to add to them. Nothing was written.The clocks after a restore
A restore into a table WITH CONFLICT POLICY LWW or LPW never gives a row the clock of the restore:
- A dump with conflict state (format 4) restores each row with its dumped clock, and each cell clock with the clock of the same column. Each dumped delete restores as a deleted row with the clock of the delete. A dumped delete never removes a row that the target holds: the server refuses the request.
- A dump without conflict state (format 3 or older, or a table that had no clock) restores each row with clock 0, UNKNOWN. A merge or a sync push then refuses a conflict on such a row, in place of a silent win of the restored row.
The restore refuses before any change when the dump holds conflict state and the target table keeps no clock. Restore into a table with LWW or LPW, or use --create. The server refuses a dumped clock more than 5 minutes after its own clock. A restore with clocks needs the INSERT right on the whole target table.
Import from MongoDB
kd-tool import reads the directory that mongodump writes (<collection>.bson.gz files) and writes every collection into the server.
sh
mongodump --db shop --gzip --out ./mongo
kd-tool import ./mongo/shop --user root --pass-file ~/.kinedb-pass
kd-tool import ./mongo/shop --collection orders --skip-gridfs --dry-run| option | meaning |
|---|---|
<dump-dir> | a mongodump directory (required) |
--collection <name> | import only this collection; repeat for more |
--skip-gridfs | skip the GridFS collections (fs.files, fs.chunks) |
--skip-history | skip the *_hist collections |
--skip-system | skip the system.* collections |
--dry-run | read, convert and count; write nothing and open no connection |
--verbose | print one line per batch |
--append | write into a collection that already holds documents; an existing _id is replaced |
--skip-existing | write only the documents whose _id is absent; a present document is skipped and never changed |
--in-flight <n> | batches on the wire at once; default 2, 1 = one at a time |
What an import does:
- It creates each collection as a document collection of the SAME name (
CREATE COLLECTION [name] TYPE DOCUMENT). A name with a dot, likefs.chunks, is kept. A collection that already exists is used. - It refuses a collection that already holds documents, unless
--appendor--skip-existingis given. - It converts every BSON document to JSON and writes it with its
_idas the document key. Nothing is dropped and nothing is flattened: a BSON type without a JSON form becomes its MongoDB Extended JSON v2 wrapper, the form every MongoDB driver reads and writes. The top-level_idis the one exception: an ObjectId there becomes its 24 hex digits as a plain string, which is MongoDB's own string form of an id. A nested ObjectId keeps{"$oid": "..."}. ADecimal128becomes{"$numberDecimalBytes": "<hex>"}. - A date lands as
{"$date": {"$numberLong": "<ms>"}}, andWHEREandORDER BYread the wrapped value. See the SQL reference under documents. - Every index the collection has is maintained during the import, so an index created before the import is complete when the import ends.
Continue an import that stopped
An import that stops part way (the tool was killed, the node restarted) leaves the documents it wrote. Run the same command again with --append:
sh
kd-tool import ./mongo/shop --collection orders --append --user root --pass-file ~/.kinedb-passEvery document of the dump is written again. A document whose _id already exists is replaced by the copy from the dump, whole body, so the collection ends with exactly the documents of the dump and no duplicate _id. The indexes and the grants of the collection stay as they are.
A replace costs more than a first write when the collection has indexes: every replaced document retracts and re-adds its index entries. Without an index the second run costs the same as the first.
To leave the present documents alone, run the same command with --skip-existing instead:
sh
kd-tool import ./mongo/shop --collection orders --skip-existing --user root --pass-file ~/.kinedb-passThe server checks every document of the batch on the shard that owns its _id, at the moment the batch applies: a document whose _id is absent is written, a document whose _id is present is skipped, and a skipped document keeps its body and its index entries untouched. The collection ends with exactly the documents of the dump and no duplicate _id. The summary line counts the skipped documents:
text
import orders: 1640124 docs, 1490421 skipped, 318 batches, …, writtenA skipped document costs one read and no write, so on an indexed collection the skip mode is the cheaper way to continue. --skip-existing needs a server that checks the _id per document; the server says so at login, and against an older server the tool refuses before it sends anything, so nothing is replaced. If a batch reply carries no skipped count, the tool stops with a server reply without the skipped count and sends no more batches; the batches before it are applied, so run the same command again to continue.
What the tool prints
One summary line per collection goes to stdout:
text
import orders: 1640124 docs, 318 batches, 52.1 s, 31500 docs/s, 42.6 MB/s, read 37.2 s, wait 14.9 s, in-flight max 2, writtenread is the time spent in gzip, BSON and the JSON conversion; wait is the time the tool stood still because the server was still writing. A dry run ends with dry-run instead of written. When stderr is a terminal, a live progress line (bar, count, rate, batch, elapsed, ETA) repaints on stderr; a piped run gets no live line.
A refusal the server declared transient (a shard split, a leadership transfer) is re-sent by the tool, the same batch under the same request id, for up to about 27 seconds per batch. The run then says how many re-sends it made. Any other refusal ends the run with the server's own words.