Operate the GizDB server
Available Runs today, as shown. Roadmap
This guide installs, secures and operates GizDB on a Linux machine. The server speaks the PostgreSQL wire protocol. Use GizDB owns SQL, embedded APIs, search and streams; this guide owns server setup, drivers, backup, recovery and operational limits.
Every command block on this page is a tested step. A scenario test reads this page and runs the blocks in order (refer to How this page is tested).
Before you start
You need:
- Linux on x86-64, with systemd. The steps use a systemd user service. They do not need root.
curl, to install the server. You do not need the Gizlang toolchain.- The PostgreSQL client programs
psqlandpg_isready(this guide used version 16). - For TLS:
openssl.
The steps use these shell variables. Set them first, in the shell where you do the steps:
export GIZDB_HOME="$HOME/gizdb" # the binary, the data, the archive and the backups
export GIZDB_UNIT=gizdb # the name of the systemd unit
export PGHOST=127.0.0.1 PGPORT=5432 PGUSER=postgres PGDATABASE=postgres
export PGPASSWORD='change-me' # the superuser password: choose your ownpsql, pg_isready and the other PostgreSQL clients read PGHOST, PGPORT, PGUSER,
PGDATABASE and PGPASSWORD.
Install the server
The server is the program gizdb. Every Gizlang release ships it prebuilt and signed, with the
compiler: the server compiles the filters, aggregates and joins of its queries to machine code
while it runs, with that compiler. The installer's --only gizdb installs the release into a
folder of its own, and its bin/gizdb runs the server with the release's compiler.
Install it into $GIZDB_HOME:
curl -fsSL https://gizstack.com/install | GIZDB_INSTALL="$GIZDB_HOME" sh -s -- --only gizdb --no-modify-path > /dev/null
test -x "$GIZDB_HOME/bin/gizdb"- The installer checks the SHA-256 of the installer it downloads, the release signature (Ed25519) and the SHA-256 of every file before it activates the version. It needs no root.
- The license notices of the third-party code in the binary are in
$GIZDB_HOME/versions/*/share/licensesand$GIZDB_HOME/versions/*/licenses. - A toolchain you installed for development has the same
gizdbnext togiz. - To install the server on a different machine, run the same command there.
At run time the shipped binary needs only the C library (OpenSSL, SQLite and Brotli are linked in):
ldd "$GIZDB_HOME/bin/gizdb" | awk '{print $1}' | grep -v -e vdso -e ld-linux | LC_ALL=C sortlibc.so.6
libm.so.6Managed databases on GizStack (giz db create, connect and backup) are coming. Until
then, you run the server as this page tells.
Start the server as a service
The server keeps its data in one folder (--dir). It writes each commit to its log before it
replies. Thus a stop of the process at any time loses no confirmed commit.
Make the folders, and the environment file of the service. The file holds the superuser password (
GIZDB_PASSWORD) and the server options (GIZDB_OPTIONS). Only you can read it.mkdir -p "$GIZDB_HOME/data" "$GIZDB_HOME/archive" "$GIZDB_HOME/backups" umask 077 cat > "$GIZDB_HOME/gizdb.env" <<EOF GIZDB_PASSWORD=$PGPASSWORD GIZDB_OPTIONS=--port $PGPORT --archive $GIZDB_HOME/archive --backups $GIZDB_HOME/backups --max-connections 20 --statement-timeout 30s EOFWrite the unit file, and start the service:
mkdir -p ~/.config/systemd/user cat > ~/.config/systemd/user/$GIZDB_UNIT.service <<EOF [Unit] Description=GizDB server (PostgreSQL protocol) [Service] EnvironmentFile=$GIZDB_HOME/gizdb.env ExecStart=$GIZDB_HOME/bin/gizdb --dir $GIZDB_HOME/data \$GIZDB_OPTIONS \$GIZDB_TLS Restart=on-failure [Install] WantedBy=default.target EOF systemctl --user daemon-reload systemctl --user enable --now -q "$GIZDB_UNIT" timeout 60 sh -c 'until pg_isready -q; do sleep 1; done' psql -At -c "SELECT version()"PostgreSQL 16.0 (GizDB)
$GIZDB_OPTIONSand$GIZDB_TLScome from the environment file. systemd splits them into separate options.$GIZDB_TLSis empty until you enable TLS.- The server listens on 127.0.0.1 by default. To listen on all addresses, add
--bind 0.0.0.0toGIZDB_OPTIONS. Before you do this, enable TLS. - A user service stops when you log out. To keep it running, run
loginctl enable-lingerone time.
Server options
gizdb --help lists every option.
| Option | Meaning |
|---|---|
--dir DIR | The database folder. The default is data. |
--port N | The TCP port. The default is 5432. |
--bind ADDRESS | The listen address. The default is 127.0.0.1. |
--password SECRET | The superuser password. GIZDB_PASSWORD is the same, and keeps the password out of the process list. Without a password, the server serves only this host: on another --bind address it does not start, unless --trust-all yes (every client is accepted; the server prints a warning). |
--archive DIR | Copy the log to DIR, for restore to a point in time. |
--archive-every SECONDS | How often the server copies the log to the archive. The default is 60. |
--backups DIR | The folder of BACKUP TO 'name': the backup goes to DIR/name. Without it, BACKUP TO is refused. |
--tls-cert FILE, --tls-key FILE | The certificate chain and the key, in PEM. Refer to Enable TLS. |
--max-connections N | The maximum number of client sessions at one time. Refer to Limits. |
--statement-timeout DURATION | The default statement_timeout of each session, for example 30s. |
Other server settings
gizdb --help is the reference for the installed binary. Beyond the settings above:
| Option | Purpose |
|---|---|
--threads N or auto | Set SQL worker count; auto uses available CPUs. |
--quota 10G | Restrict database file bytes; reads continue after writes reach the limit. |
--synchronous-commit off | Answer a commit before syncing the log; a host crash can lose acknowledged writes. Leave on for durable OLTP. |
--socket DIR, --bind none | Use a Unix-domain socket without TCP exposure. |
--tls-require yes | Reject connections that do not use TLS. |
--kv-port N, --kv-tenant NAME, --kv-shards N | Redis-compatible commands with tenant separation and optional KV writers. |
--kafka-port N | Table-backed Kafka-compatible interface. |
--otlp-port N | OTLP/HTTP receiver for telemetry. |
--files DIR | Gate SQL access to local CSV/Parquet files. |
--primary HOST:PORT | Follow a primary in read-only standby mode. |
Authenticate and firewall each enabled protocol listener. Do not expose --trust-all yes
on a network: it bypasses password authentication.
Users, passwords and TLS
Superuser and roles
- With a server password, a client logs in with SCRAM-SHA-256.
- A user name that is not a role logs in as the superuser, with the server password. The server password grants full administrator access regardless of user name. Never hand it to an application; create a limited role instead.
- A role logs in with its own password.
Make a table, and a role app that can read and add rows only:
psql -q -c "CREATE TABLE notes (id int PRIMARY KEY, title text NOT NULL)"
psql -q -c "INSERT INTO notes VALUES (1, 'Buy milk'), (2, 'Write guide')"
psql -q -c "CREATE ROLE app LOGIN PASSWORD 'app-secret'"
psql -q -c "GRANT SELECT, INSERT ON notes TO app"
export APP_PASSWORD='app-secret'Log in as app:
PGUSER=app PGPASSWORD="$APP_PASSWORD" psql -At -c "SELECT current_user, count(*) FROM notes GROUP BY 1"
PGUSER=app PGPASSWORD="$APP_PASSWORD" psql -At -c "DELETE FROM notes" 2>&1 || true
PGUSER=app PGPASSWORD=wrong psql -At -c "SELECT 1" 2>&1 | grep -o 'password authentication failed for user "app"'app|2
ERROR: permission denied for table notes
password authentication failed for user "app"To change the password of a role, use ALTER ROLE:
psql -q -c "ALTER ROLE app PASSWORD 'app-secret-2'"
PGUSER=app PGPASSWORD='app-secret-2' psql -At -c "SELECT 'new password works'"
export APP_PASSWORD='app-secret-2'new password worksTo change the superuser password, change GIZDB_PASSWORD in the environment file, and restart
the service.
To let a role see only some rows, use row level security. A policy filters each read of the role. The superuser and the table owner are not filtered. A read through a parent table uses the policies of the parent. Foreign key checks see all rows.
psql -q -c "CREATE TABLE tasks (id int PRIMARY KEY, owner text NOT NULL)"
psql -q -c "INSERT INTO tasks VALUES (1, 'app'), (2, 'ana')"
psql -q -c "GRANT SELECT ON tasks TO app"
psql -q -c "ALTER TABLE tasks ENABLE ROW LEVEL SECURITY"
psql -q -c "CREATE POLICY own ON tasks USING (owner = current_user)"
PGUSER=app PGPASSWORD="$APP_PASSWORD" psql -At -c "SELECT id, owner FROM tasks"
psql -At -c "SELECT count(*) FROM tasks"1|app
2Enable TLS
The server reads the certificate chain and the key in PEM. This example makes a self-signed
certificate for localhost. For a server that other machines use, get a certificate from your
certificate authority for the name that clients connect to.
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 365 \
-subj /CN=localhost -addext subjectAltName=DNS:localhost,IP:127.0.0.1 \
-keyout "$GIZDB_HOME/server.key" -out "$GIZDB_HOME/server.pem" 2>/dev/null
chmod 600 "$GIZDB_HOME/server.key"
echo "GIZDB_TLS=--tls-cert $GIZDB_HOME/server.pem --tls-key $GIZDB_HOME/server.key" >> "$GIZDB_HOME/gizdb.env"
systemctl --user restart "$GIZDB_UNIT"
timeout 60 sh -c 'until pg_isready -q; do sleep 1; done'
psql "host=localhost sslmode=verify-full sslrootcert=$GIZDB_HOME/server.pem" -c '\conninfo' | grep -o 'SSL connection (protocol: TLSv1.3'SSL connection (protocol: TLSv1.3sslmode=verify-fullmakes psql check the certificate and the host name.- By default the server also accepts connections without TLS.
--tls-require yesrejects unencrypted clients.gizlang:postgressupports TLS. The shell examples here usepsqlfor easy protocol inspection.
To replace the certificate (for example, before it expires), replace the two files. New sessions use the new files. You do not restart the server.
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 365 \
-subj /CN=localhost -addext subjectAltName=DNS:localhost,IP:127.0.0.1 \
-keyout "$GIZDB_HOME/new.key" -out "$GIZDB_HOME/new.pem" 2>/dev/null
mv "$GIZDB_HOME/new.key" "$GIZDB_HOME/server.key"
mv "$GIZDB_HOME/new.pem" "$GIZDB_HOME/server.pem"
psql "host=localhost sslmode=verify-full sslrootcert=$GIZDB_HOME/server.pem" -At -c "SELECT 'new certificate works'"new certificate worksConnect from clients
Each example connects as app and reads the note with id 1, with a parameter.
psql
PGUSER=app PGPASSWORD="$APP_PASSWORD" psql -At -c "SELECT title FROM notes WHERE id = 1"Buy milkNode.js (node-postgres)
Install the driver in your project with npm install pg. connect.mjs:
import pg from "pg";
const client = new pg.Client({ host: "127.0.0.1", port: Number(process.env.PGPORT),
user: "app", password: process.env.APP_PASSWORD, database: "postgres" });
await client.connect();
const result = await client.query("SELECT title FROM notes WHERE id = $1", [1]);
console.log(result.rows[0].title);
await client.end();node connect.mjsBuy milkPython (psycopg 3)
Install the driver with pip install "psycopg[binary]". connect.py:
import os, psycopg
with psycopg.connect(host="127.0.0.1", port=os.environ["PGPORT"], user="app",
password=os.environ["APP_PASSWORD"], dbname="postgres") as conn:
print(conn.execute("SELECT title FROM notes WHERE id = %s", (1,)).fetchone()[0])python3 connect.pyBuy milkJava (JDBC)
Put the PostgreSQL JDBC driver (postgresql-42.x.jar) in CLASSPATH. Connect.java:
import java.sql.*;
public class Connect {
public static void main(String[] args) throws Exception {
String url = "jdbc:postgresql://127.0.0.1:" + System.getenv("PGPORT") + "/postgres";
try (Connection c = DriverManager.getConnection(url, "app", System.getenv("APP_PASSWORD"));
PreparedStatement s = c.prepareStatement("SELECT title FROM notes WHERE id = ?")) {
s.setInt(1, 1);
try (ResultSet r = s.executeQuery()) { r.next(); System.out.println(r.getString(1)); }
}
}
}java Connect.javaBuy milkGizlang
gizlang:postgres is the PostgreSQL client of the Gizlang SDK. It supports
certificate-checked TLS with sslmode=require, verify-ca and verify-full; see
external PostgreSQL configuration.
This local example uses the loopback interface without TLS. notes.giz:
import {System} from "gizlang:system";
import {Option} from "gizlang:core";
import {parseI64, IntegerParse} from "gizlang:text";
import * as Pg from "gizlang:postgres";
function main(sys: System): void {
let port = 5432;
match (parseI64(sys.environment("PGPORT") ?? "5432")) {
IntegerParse.ok(n) => { port = n; }
IntegerParse.invalid(at) => {}
IntegerParse.overflow(at) => {}
}
const password = sys.environment("APP_PASSWORD") ?? "";
const c = Pg.connect(sys, { host: "127.0.0.1", port: port, user: "app", password: password, database: "postgres" })!;
const result = c.query("SELECT title FROM notes WHERE id = $1", [Option.some("1")])!;
print(result.rows[0][0] ?? "NULL");
c.close();
}giz notes.gizBuy milkStandby replication and failover
A standby uses an empty folder, follows the primary's WAL and serves reads while catching up. Start this example in a separate terminal or service after the primary is ready:
GIZDB_PASSWORD="$PGPASSWORD" "$GIZDB_HOME/bin/gizdb" serve \
--dir "$GIZDB_HOME/standby" --port 5433 --primary 127.0.0.1:5432A standby is read-only; SELECT pg_is_in_recovery() returns t. Use SELECT pg_promote()
to promote it. Before rerouting writes, isolate the old primary to avoid split brain. Test
failover, lag and reconnection with your actual clients before relying on this in production.
Serve many databases
gizdb cluster --root ROOT --port PORT serves separate databases on one port, each in its
own process. It also takes --bind, --socket, --password and --idle. This is not a
distributed transaction coordinator: an ACID transaction across independent databases is
not provided by the cluster mode.
Migrate and check data
gizdb migrate copy|verify|compare|status --source starrocks://USER@HOST:8030/DB ...
provides separate steps for copying and validating migrated data. Check the source-specific
arguments with gizdb --help. Coordinate writes while comparing, and keep the source as a
rollback path. For flat files, see CSV import/export.
Verify or salvage a stopped database
After stopping the service, run:
"$GIZDB_HOME/bin/gizdb" verify --dir "$GIZDB_HOME/data"verify checks log replay, stray files and constraints without changing the stopped data.
gizdb salvage --dir DIR --keep COPY copies the damaged database and truncates the log at
the first bad frame. Salvage can discard transactions. Preserve the original, backups
and archived WAL before starting; compare the recovered state. Both commands refuse a
folder still held by an active server.
Back up and restore to a point in time
A restore to a point in time needs two things:
- A base backup.
BACKUP TO 'folder'makes one while the server runs. The folder is inside the--backupsfolder (here$GIZDB_HOME/backups), and it must exist and be empty. Only a superuser can run it. - The log after the backup. The server copies it to the
--archivefolder every--archive-everyseconds. The newest part is still in the database folder.
Make a base backup:
mkdir "$GIZDB_HOME/backups/base-1" psql -q -c "BACKUP TO 'base-1'" ls "$GIZDB_HOME/backups/base-1" | grep -x BACKUP_LABELBACKUP_LABELThe file
BACKUP_LABELshows that the backup is complete.Work continues. Then a mistake deletes every note:
PGUSER=app PGPASSWORD="$APP_PASSWORD" psql -q -c "INSERT INTO notes VALUES (3, 'Call Ana')" sleep 1 export GOOD_TIME="$(date -u '+%Y-%m-%d %H:%M:%S+00')" sleep 1 psql -q -c "DELETE FROM notes"Stop the server. Restore the backup and the log up to the time before the mistake, into a new folder. Give the archive and the database folder to
--logs.systemctl --user stop "$GIZDB_UNIT" mkdir "$GIZDB_HOME/restored" "$GIZDB_HOME/bin/gizdb" restore --base "$GIZDB_HOME/backups/base-1" \ --logs "$GIZDB_HOME/archive,$GIZDB_HOME/data" --to "$GIZDB_HOME/restored" --until "$GOOD_TIME"Without
--until, the restore replays all of the log.Keep the damaged folder and the old archive. Start the server on the restored folder, with a new archive:
mv "$GIZDB_HOME/data" "$GIZDB_HOME/data.before-restore" mv "$GIZDB_HOME/restored" "$GIZDB_HOME/data" mv "$GIZDB_HOME/archive" "$GIZDB_HOME/archive.before-restore" mkdir "$GIZDB_HOME/archive" systemctl --user start "$GIZDB_UNIT" timeout 60 sh -c 'until pg_isready -q; do sleep 1; done' psql -At -c "SELECT id, title FROM notes ORDER BY id"1|Buy milk 2|Write guide 3|Call AnaMake a new base backup now. A restore of the new archive starts from it.
mkdir "$GIZDB_HOME/backups/base-2" psql -q -c "BACKUP TO 'base-2'"
Roles and grants are in the database. Thus the restore also restores them.
Upgrade the server
- An upgrade installs the new version with the same command. The database folder stays.
- GizDB does not yet promise that a new version opens the database folder of an older version.
Thus make a base backup before each upgrade. To go back, install the old version
(
--version VERSION), and restore that backup, as in Back up and restore. - The installer replaces
$GIZDB_HOME/bin/gizdbin one step. The running server keeps the old binary until the restart.
mkdir "$GIZDB_HOME/backups/before-upgrade"
psql -q -c "BACKUP TO 'before-upgrade'"
curl -fsSL https://gizstack.com/install | GIZDB_INSTALL="$GIZDB_HOME" sh -s -- --only gizdb --no-modify-path > /dev/null
systemctl --user restart "$GIZDB_UNIT"
timeout 60 sh -c 'until pg_isready -q; do sleep 1; done'
psql -At -c "SELECT count(*) FROM notes"3Monitor the server
These checks work today:
systemctl --user is-active "$GIZDB_UNIT"
pg_isready -q && echo accepting connections
psql -At -c "SELECT pg_is_in_recovery()"
psql -At -c "SHOW statement_timeout"active
accepting connections
f
30s| Check | What it tells |
|---|---|
systemctl --user is-active | The process runs. systemd restarts it after a failure (Restart=on-failure). |
pg_isready | The server accepts connections. |
SELECT pg_is_in_recovery() | t on a standby, f on a primary. |
journalctl --user -u $GIZDB_UNIT | What the server printed. The server prints only errors. |
du -s $GIZDB_HOME/data $GIZDB_HOME/archive | The disk space of the database and of the archive. |
There is no general HTTP metrics endpoint. The current source implements pg_stat_gizdb
and pg_stat_activity, pg_stat_database and pg_stat_user_tables compatible views;
availability and columns vary by the installed release. Test these on the running
server before configuring dashboards:
SELECT vector_searches FROM pg_stat_gizdb;
SELECT pid, usename, state FROM pg_stat_activity;In an installation using @gizai/cli 2.2.1, pg_stat_activity returned no rows despite
another client being connected, and pg_stat_gizdb.commit_sequence was missing although
it appears in the current source. Check binary/source version drift when monitoring differs.
Limits
| Limit | Value today | What happens at the limit |
|---|---|---|
| Client sessions | --max-connections (20 in this guide) | The next client gets FATAL: sorry, too many clients already. |
| Statement time | --statement-timeout (30 s in this guide) is the default statement_timeout of each session. SHOW statement_timeout shows it. | This guide did not measure it: GizDB has no pg_sleep to make a slow statement. |
| Data written by one transaction | No 64 KiB limit: one statement writes a 1 MB value. | The example below writes it. |
for i in $(seq 20); do sleep 4 | psql -q & done
sleep 2
psql -At -c "SELECT 1" 2>&1 | grep -m1 -o 'sorry, too many clients already'
wait
psql -q -c "CREATE TABLE blobs (id int PRIMARY KEY, body text)"
psql -At -c "INSERT INTO blobs VALUES (1, repeat('x', 60000))"
psql -At -c "INSERT INTO blobs VALUES (2, repeat('x', 1000000))"sorry, too many clients already
INSERT 0 1
INSERT 0 1Export the data
Export each table as CSV with \copy. The CSV has a header line:
psql -q -c "\copy (SELECT * FROM notes ORDER BY id) TO '$GIZDB_HOME/notes.csv' WITH (FORMAT csv, HEADER)"
cat "$GIZDB_HOME/notes.csv"id,title
1,Buy milk
2,Write guide
3,Call AnaClient and export compatibility
pg_dump is not a supported backup method. A checked installation failed
pg_dump --schema-only with schema with OID 13258 does not exist; another build may fail
at a different catalog query. Use BACKUP TO and the WAL archive for recovery and \copy
for portable CSV data. PostgreSQL wire support does not imply full catalog/extension support.
Parquet data export is implemented as COPY ... TO STDOUT (FORMAT parquet) and
COPY ... TO FILE (FORMAT parquet) for supported schemas. See
file queries and CSV import/export.
Parquet files do not replace a full backup of data, schema and roles.
Troubleshooting
| Symptom | Check | Response |
|---|---|---|
| Clients cannot connect | systemctl --user status gizdb, pg_isready, listen port and journalctl --user -u gizdb | Fix bind, port, permissions or service settings. |
| Password is rejected | GIZDB_PASSWORD, PGUSER, role verifier | Test the correct role and password, not an unknown name with an admin password. |
| Permission denied | GRANT, table owner and RLS policies | Grant only the app's required operations. |
| Too many clients | --max-connections, connection pool sizes | Close idle connections; tune tested concurrency limits. |
| Slow or timed-out SQL | SHOW statement_timeout, EXPLAIN, index usage | Fix the plan or change the timeout deliberately. |
pg_dump or an ORM fails | Unsupported catalogs and SQL functions | Use GizDB backup; validate ORM-generated migrations. |
| Missing or empty statistics | View columns, executable and source revision | Align the deployed binary before relying on these views. |
Change feed returns GZ001 | WAL position no longer retained | Resnapshot and resubscribe from a current position. |
| TLS is unavailable | Certificate pair, --tls-require, client driver | Enable TLS on the server and configure certificate verification in the client. See Data access. |
| Restore/verify refuses data | The database folder is locked | Stop its owning process, never force a second engine into the directory. |
Run gizdb --help, SELECT version() and a small relevant query against the installed
server binary before assuming behavior from source HEAD or documentation.
Remove the service
systemctl --user disable --now "$GIZDB_UNIT" 2>/dev/null || true
rm -f ~/.config/systemd/user/$GIZDB_UNIT.service
systemctl --user daemon-reloadThe data stays in $GIZDB_HOME. Remove that folder when you do not need the data.
How this page is tested
A scenario test reads this page and runs its command blocks in order.
- It runs each block marked
sh opsin order, with/bin/sh, in one scratch folder. A step fails when a command in it fails. When anoutputblock follows, the step must print exactly that text (standard output and standard error). - It writes each block marked
file=NAMEto the file NAME, as you do. - It does not run the settings block. It uses its own values:
GIZDB_HOMEin the scratch folder, a freePGPORT, a randomPGPASSWORD, and a randomGIZDB_UNIT. - The install command (
curl -fsSL https://gizstack.com/install | ...) runs as written whenGIZDB_OPS_RELEASE=publishedis set: that is the check of a published release on a clean host. Otherwise the suite builds, in its place, the program that the release ships (gizdb) from the source tree of the test into$GIZDB_HOME/bin. - A block marked
requires=runs only when that software is on the machine. The suite prints each block that it did not run, and why. It skips all steps whenpsql,pg_isreadyor a systemd user manager is not available. - It always runs the block of Remove the service at the end.