Configuration¶
LokaDB is one binary, lokadb, with subcommands: proxy, relay, agent, issuer, and the administrative commands (check, enable, move-org, move-back, repoint, disable, org-profile, org-credentials, org-add, routing, schema-diff, schema-sync, refs-diff, refs-sync, move-cleanup, token, revoke, agents, org-endpoint, certs). lokadb <command> -h lists the flags of each.
The spec file¶
The spec names the tables whose rows move with an organization, and how each table finds its organization.
{
"tables": [
{"name": "documents", "tenant_column": "organization_id"},
{"name": "document_versions", "parent_table": "documents", "parent_column": "document_id"},
{"name": "app.invoices", "tenant_column": "account_id"}
],
"routing": {
"relations": [{"from": "messages.conversation_id", "to": "conversations.id"}],
"cloud_tables": [{"table": "projects", "tenant_column": "account_id"}],
"keys": [{"table": "documents", "column": "slug"}]
}
}
tenant_column— the table has its own organization column (integer oruuid).parent_table/parent_column— the table gets its organization from its parent through a foreign key;enableadds the organization column to it (filled from the parent).- A table outside
publicis writtenschema.name. The order of tables does not matter. routingdeclares what the schema does not say: relations without a foreign key, cloud tables that know the organization, columns unique across the table. See routing.md.
lokadb check -org-table organizations -write-spec spec.json proposes a spec from the schema: it follows the foreign keys to the organizations table, whatever the organization column of each table is called (tenant_id, owner_company_id), and says why every other table stays in the cloud (check.md). -tenant-column organization_id does the same for a schema where all tables share one column name. Which data leaves the cloud is a human decision: review it.
Proxy: lokadb.json¶
{
"listen": ":6432",
"upstream": "host=cloud port=5432 sslmode=require",
"fanout": {
"*": {"mode": "reject"},
"reports": {"mode": "allow"},
"app": {"mode": "reject", "allow": ["SELECT * FROM documents WHERE slug = $1 LIMIT $2"]}
},
"customers_app": {"app": {"1": "host=relay port=15001 dbname=cust user=lokadb_dml sslmode=verify-full sslrootcert=/certs/cust1.crt"}},
"tls": {"cert": "/certs/proxy.crt", "key": "/certs/proxy.key", "require": true},
"tenant": {"local_only": true}
}
| Key | Default | Meaning |
|---|---|---|
listen |
— | address the application connects to |
upstream |
— | libpq connection string of the cloud Postgres (host, port, TLS); user, password and database come from the client |
fanout |
reject for every database |
policy for statements that name no organization, per database ("*" for the rest): reject, reject-writes, log, allow, plus an allow list of reviewed statements. See routing.md |
customers |
— | per database and organization, the address of the organization's database for DDL replay, when the proxy reaches it at a different address than the cloud does. Normally not needed: the proxy reads it from the cloud |
customers_app |
— | per database and organization, the DSN of the direct-path role (DML only) for fdw+direct / direct |
control_plane, credentials_key, credentials_key_old |
— | read organization credentials from the control-plane database, encrypted, instead of customers / customers_app (see Credentials) |
tls |
off | TLS between the application and the proxy: cert, key, require (refuse clients without TLS). Certificate files are re-read without a restart |
tenant |
absent | cooperative mode: setting (an existing application setting such as app.current_tenant), local_only (default true: only per-transaction values), mismatch (reject or log), all_roles (roles allowed to use '*') |
upstream_pool |
on, for all databases | reuse cloud connections between clients (DISCARD ALL between them). false turns it off, {} turns it on everywhere; max_idle (10 per key), max_idle_total (200), idle_ttl (60s), max_age (5m) |
direct_pool |
off | reuse direct connections to organization databases ({} to turn on; same settings) |
upstream_pooling |
— | "session" when a pooler in session mode sits between the proxy and Postgres. Transaction mode there is not supported |
meta_cache |
on | shared cache of LokaDB metadata per database and role; {"off": true} disables it, {"verify": "1s"} lets new connections skip the freshness check for up to that long |
cache_bytes |
8 MiB | per-session cache of statement analyses |
cache_bytes_total |
512 MiB | all sessions together |
org_cache_bytes |
64 MiB | organizations of row ids, shared by all sessions |
route_log |
off | one route log line per statement on a LokaDB table (costs throughput: leave off when measuring) |
no_route_log |
off | suppress route lines even at debug level |
record |
— | path of a workload recording (same as -record) |
max_connections |
1000 | client connections |
max_message_bytes |
256 MB | largest protocol message |
SIGHUP drops every pooled connection (cloud and organization databases) and the shared metadata cache: send it after ALTER ROLE … PASSWORD, NOLOGIN, REVOKE CONNECT or a pg_hba.conf change, otherwise pooled connections stay usable with old credentials for up to max_age.
Relay: relay.json¶
{
"listen": ":7443",
"enroll_listen": ":7444",
"health_listen": ":7446",
"cert": "/certs/relay.crt", "key": "/certs/relay.key",
"ca": "/certs/ca.crt", "ca_key": "/certs/ca.key",
"cert_ttl": "24h",
"control_plane": "host=cloud dbname=controlplane user=relay sslmode=require",
"orgs": {"1": ":15001"},
"org_allow": ["proxy.internal", "cloud-db.internal"],
"cloud": "cloud:5432",
"cloud_access": "window",
"org_tls": "require"
}
| Key | Default | Meaning |
|---|---|---|
listen |
— | port agents connect to (mutual TLS) |
enroll_listen |
— | enrolment and renewal (token → certificate) |
health_listen |
— | GET /healthz (200 / 503 while draining), GET /sessions. Internal only |
cert, key, ca, ca_key |
— | relay certificate, CA bundle, CA key (not needed with issuer_url). Re-read when the files change |
issuer_url |
— | sign agent certificates through a separate issuing service instead of holding the CA key |
cert_ttl |
— | lifetime of agent certificates; agents renew at 2/3 of it |
renew_expired |
24h |
still renew a certificate that expired at most this long ago ("0" disables) |
control_plane |
— | the registry database: tokens, certificates, revocations, endpoints, cloud windows |
orgs |
— | organization → listen address inside the cloud. More can be added at run time with lokadb org-endpoint |
org_allow |
required | addresses allowed to connect to organization endpoints: the proxy, the cloud Postgres (postgres_fdw), the machines that run check and move-org |
org_tls |
off | require: refuse organization connections that do not start TLS |
cloud, cloud_access |
—, window |
where the agent's connection to the cloud goes (the subscription during a logical move), and when it is allowed (window: only while move-org opens it) |
registry_grace |
off | admit an agent whose certificate the registry confirmed within this period while the registry is down (a certificate revoked in that period also gets in) |
drain_delay, drain_timeout |
5s, 1m | graceful stop on SIGTERM |
max_agents_per_org |
4 | concurrent agents per organization |
max_org_conns |
1000 | concurrent connections per organization endpoint |
max_streams |
2048 | connections through the relay in total (memory: up to about max_streams × 4 MiB) |
max_cloud_streams |
64 | connections to the cloud per agent session |
max_handshakes, max_enroll_conns |
256, 256 | agents in TLS handshake; connections to the enrolment port |
enroll_fail_per_min |
10 | failed enrolment/renewal attempts per client address (-1: unlimited) |
Agent flags: -relay host:port[,host:port] (or one name resolving to every relay), -ca, -state (key and certificate, mode 0600), -token-file, -db host:port (the only address it forwards to), -cloud-listen and -cloud-allow (the subscription path during a logical move), -sessions N (1–8 TCP connections per relay), -max-conns (200).
Issuing service: issuer.json¶
{"listen": ":7445", "cert": "/certs/issuer.crt", "key": "/certs/issuer.key",
"ca": "/certs/ca.crt", "ca_key": "/certs/ca.key", "cert_ttl": "1h", "relays": ["relay"]}
The only process that holds the CA key; it signs agent client certificates for the relays listed in relays. See tunnel.md.
Passwords on the command line¶
A DSN with a password in an argument is visible in ps and in shell history. Every DSN flag (-db, -customer, -customer-app, -publisher, -cp, -dsn, …) accepts instead:
-flag -— read the DSN from stdin (one flag per command);-flag env:NAME— read it from the environment variableNAME;- a DSN without a password, with the password in
PGPASSFILE(~/.pgpass) orPGPASSWORD, as libpq does.
A password written in the argument still works, with a warning. Commands never print passwords.
Credentials in the control plane¶
The proxy needs credentials of every organization database it reaches directly (the direct-path role, and the owner role for DDL replay). Instead of keeping them in lokadb.json, store them encrypted in the control-plane database:
lokadb org-credentials -db "$CLOUD" -control-plane "$CP" -key /keys/lokadb.key -org 1 -kind direct -dsn - # DSN from stdin
lokadb org-credentials -db "$CLOUD" -control-plane "$CP" -key /keys/lokadb.key -org 1 -kind owner -dsn -
- The command connects with the credentials first (the direct role must have DML only), then encrypts them with AES-256-GCM. The organization, role, database and the cloud cluster's identifier are bound into the ciphertext: a row cannot be replayed for another organization or another cloud.
- The proxy reads them at start and every 30 s with
control_planeandcredentials_key(a 32-byte file or 64 hex digits, mode 0600). They take precedence overcustomers/customers_app. - Key rotation: put the new key in
credentials_keyand the old one incredentials_key_old, runorg-credentials -reseal -key NEW -key-old OLD, then drop the old key.-listshows what is stored without decrypting. - The owner role may not be a superuser or have
BYPASSRLSorREPLICATION.