This is the multi-page printable view of this section. .
Documentation
- 1: About kbdiag
- 2: Capabilities
- 3: Your first check
- 3.1: Read a real result
- 4: Troubleshooting scenarios
- 5: User manual
- 5.1: sessions: list sessions
- 5.2: session: one session
- 5.3: locks: lock waits
- 5.4: txn: transactions
- 5.5: waits: wait events
- 5.6: status: instance overview
- 5.7: slots: replication slots
- 5.8: space: disk, WAL, databases
- 5.9: freeze: wraparound distance
- 5.10: vacuum: dead tuples and autovacuum
- 5.11: archive: WAL archiving
- 5.12: params: changed parameters
- 5.13: repl: replication from this node
- 5.14: cluster: repmgr's view
- 5.15: top-objects: largest tables and indexes
- 5.16: table: one table
- 5.17: top: cumulative top SQL
- 5.18: progress: long operations
- 5.19: checkpoint: checkpoint activity
- 5.20: wal: WAL position and what keeps it
- 5.21: seq: sequences
Start here
- About kbdiag: who it serves, where it runs, and what it will not do.
- Capabilities: the question each command answers.
- First check: build, install, connect, and read exit codes.
- User manual: every command, flag and output field.
Start with status and sessions. If you already have a problem in front of you, pick a command from the capabilities page or follow a troubleshooting scenario.
1 - About kbdiag
What problem does it solve?
When something feels wrong with a database, the first questions are usually the same: who is connected, who is waiting for a lock and who blocks them, which transaction has been open too long, whether a standby’s slot is piling up WAL. Answering them means remembering system views and joining them by hand.
kbdiag packages those questions into commands. Each command runs one read-only look at a live instance and prints a verdict, the evidence it is based on, and the next command to run.
Who is it for?
| Reader | Start with | What you get |
|---|---|---|
| Operations | status, sessions | Instance facts, connection pressure, sessions left idle in transaction |
| DBAs | locks, session <pid>, txn | Who blocks whom, what a session holds, old and prepared transactions |
| Monitoring scripts | any command, --json | A verdict in the exit code and a stable JSON report |
Look, query, diagnose
- Look: one command gives one fact, such as the instance’s role or its connection usage (
status). - Query: a focused command looks at one dimension in depth, such as lock waits (
locks) or one session (session <pid>). - Diagnose: correlating several dimensions into a root cause is planned for later versions. This version does look and query; each finding’s
verifyline tells you which query to run next.
Runtime and requirements
- KingbaseES V8R6 (tested on V008R006C009B0014), standalone or repmgr primary/standby.
- One static Linux binary (amd64 or arm64). It speaks the wire protocol itself: no
ksql, no runtime, no service. - Run it on the database host as the
kingbaseOS user over the local socket for the full picture. Other accounts and TCP work too; what they cannot see is reported, not hidden. - repmgr is optional.
Operating boundaries
- Read-only. Every connection is a read-only transaction with a
lock_timeout. Suggested fixes, such asROLLBACK PREPARED, are printed for you to review and never run. - No pretend OK. When something the verdict depends on could not be collected or seen, and nothing visible is WARN or FAIL, the verdict is UNKNOWN (exit 3), not OK.
- One look, not monitoring. Each run is a single sample. A clean result says nothing about a moment ago or a moment later.
kbdiag 2.0 is a Go rewrite. The shell toolkit (v1.x) is frozen; its other commands (health check, reports, performance and advisors) are available in release v1.0.0.
Next
2 - Capabilities
Start with the question you need to answer. Examples assume the binary is at ~/kbdiag; see the user manual for every flag and output field.
Instance
What is this instance, and do its basics hold?
Version, role (primary / standby), uptime, connections, which standbys it sends WAL to or where a standby receives WAL from, database sizes, and the disk holding the data directory. FAIL when every connection ordinary users may open is taken (max_connections less superuser_reserved_connections); WARN when a standby is not receiving WAL.
→ status
Sessions
Who holds the connections, which sessions are doing something, and what is one session doing?
sessions counts the connections by user, database, application and client, then lists the client sessions that are not idle, longest transaction first. It warns about sessions idle in transaction for more than 300 seconds. session <pid> shows one session in full: who it is, its whole SQL, the lock it waits for and who blocks it, whom it blocks and what it holds.
Locks
Who is waiting for a lock, and which session blocks them?
The blockers come first, the one blocking the most sessions at the top, then every waiting session, longest wait first. Each wait over 10 seconds is a WARN naming its direct blocker, and the verify line points to the blocker’s session. When a chain is several sessions long, follow it one hop at a time.
→ locks · Scenario: lock waits
Transactions
Who holds back the vacuum horizon: a transaction open too long, or a forgotten prepared (2PC) transaction?
The oldest transaction id first, with the sessions or prepared transaction holding it, then the open transactions and prepared ones. Open transactions WARN at 300 seconds and prepared transactions at 900, with a ROLLBACK PREPARED / COMMIT PREPARED suggestion printed for you to decide. Prepared transactions live on the primary; on a standby they are reported as not applicable.
→ txn
Waits
What are sessions waiting on right now?
The sessions doing something, grouped by wait event and state with their PIDs, biggest pile first. Idle sessions and background processes idling in their main loop are only counted. It summarizes without thresholds; use locks for how long and on whom.
→ waits · Scenario: long-running SQL
Replication slots
Is a slot keeping WAL for a standby that is gone?
Each slot with its consumer, the WAL it keeps and its xmin. An inactive slot keeps WAL and, with an xmin, holds back vacuum, so it is a WARN at once. The verify line sends you to status on the slot’s downstream node to check whether it still receives WAL. kbdiag never drops a slot.
→ slots
Script integration
Every command sets its exit code from the verdict and supports --json:
UNKNOWN means something could not be collected or seen, so kbdiag will not claim OK. 69 only means the connection failed; a database that accepts the connection but cannot answer is UNKNOWN. See exit codes.
Not in this version
Replication lag, repmgr cluster checks, slow-SQL history, vacuum and bloat, deadlock history and correlated diagnosis are not in kbdiag 2.0 yet. The frozen shell toolkit v1.0.0 still has health checks and reports if you need them.
3 - Your first check
Build kbdiag once, copy it to the KingbaseES host, and run status and sessions. Then decide what needs a closer look.
1. Prepare
- A running KingbaseES V8R6 instance (tested on V008R006C009B0014).
- Access to the
kingbaseOS user on the database host. - A machine with Go (the version in the repository’s
go.mod) and git, to build the binary. It can be your laptop; the database host needs nothing but the binary.
2. Build and install
On the build machine:
Use GOARCH=arm64 for ARM hosts. The result is one static file with no runtime dependencies, so an offline host only needs the file copied over.
On the database host:
3. Connect
By default kbdiag connects over the local socket /tmp/.s.KINGBASE.54321 to database test as user system, the way ksql test system does. Change the target with flags:
| Flag | Default |
|---|---|
--host | /tmp (socket directory); a host name or IP for TCP |
-p, --port | 54321 |
-d, --dbname | test |
-U, --user | system |
--timeout | 10s per query |
The password comes from PGPASSWORD or ~/.pgpass. If kbdiag cannot connect, it prints the error and exits with 69; check the port, socket directory and sys_hba.conf before anything else.
An account without a monitoring role cannot see other sessions’ state, timings or SQL. kbdiag lists those fields under redacted and answers UNKNOWN rather than OK; grant sys_monitor or use system for the full picture.
4. Run your first checks
Read $? right after the command; the next command replaces it. status tells you what the instance is and whether its basics hold: connections, replication, disk. sessions shows who holds the connections and which sessions are doing something, and warns about sessions left idle in transaction.
5. Read the verdict and exit code
The first line of every report carries the verdict, and the exit code says the same thing:
| Exit code | Verdict | Next step |
|---|---|---|
0 | OK | Nothing crossed a threshold in this sample |
1 | WARN | Nothing is broken yet, but it will be if left alone: read each finding and run its verify command |
2 | FAIL | Same, now: the application is already affected |
3 | UNKNOWN | Something could not be collected or seen; check privileges and the redacted list |
64 | Usage error | Check the command and flags |
69 | Cannot connect | Check the connection first; nothing was looked at |
A WARN or FAIL finding wins over UNKNOWN: if kbdiag found a problem in what it could see, it says so.
Read a real result line by line
6. Follow the evidence
| You see | Next command | Look for |
|---|---|---|
| A lock wait | ~/kbdiag session <blocker pid> | What the blocker is doing and how long its transaction has been open |
| Idle in transaction | ~/kbdiag session <pid> | The locks it holds and whether it blocks anyone |
| An old or prepared transaction | ~/kbdiag txn | The oldest xid and who holds it, transaction age, the prepared transaction’s gid and owner |
| Many sessions waiting | ~/kbdiag waits | Which wait event they share |
| Connections used up | ~/kbdiag sessions | The summary at the top: a pile-up by user, database, application or client |
| An inactive slot | ~/kbdiag status on the slot’s downstream node | Whether it answers, whether inst.upstream is streaming, and whether last_msg keeps growing |
Each finding already prints the right one of these as its verify line.
Done
You have built and installed kbdiag, connected to the right instance, run status and sessions, and know what their exit codes mean.
3.1 - Read a real result
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. PIDs, counts and timings belong to this capture only. The idle-in-transaction session was created by a fault-injection script. The warning threshold was lowered to 5 seconds with --idle-in-txn-warn 5 so the example did not have to wait the default 300.
Command and output
Line by line
- First line: the command (
sessions), the verdict (WARN) and the context: server version, role (primary),user@location(localis the socket) and when the data was collected. - Finding:
[WARN], then a stable id (session.idle_in_txn), then the symptom: session 1049818 has been idle in transaction for 9 seconds. Scripts should rely on the id and the JSON fields; the wording of a symptom may change. verify:the next command to run and why. Here: look at session 1049818 to see which locks it holds and whether it blocks anyone. Afix:line, when present, is a statement for you to review; kbdiag never runs it.- Data: laid out for reading.
connectedcounts the client sessions by user, database, application and client.not idlelists the ones doing something or holding a transaction open, longest transaction first.-is null;?would be a value this account may not see. - The
esreprow is repmgr’s own query, caught while it ran. Idle sessions and background processes are only counted in the headings;--alllists them. - Exit code 1: WARN, meaning nothing is broken yet but it will be if left alone. A FAIL (the application is already affected) would be 2, UNKNOWN 3.
The same report in JSON
--json carries the same content with stable field names. This is the findings array of ~/kbdiag sessions --idle-in-txn-warn 5 --json, taken a moment later:
The full report also has:
command,verdictandcontext.data: each probe with itsstatus,columns,rowsandtruncated. JSON keeps raw values (seconds, bytes, full SQL) and always carries every session, idle ones and background processes included. Here that is 12 rows, where the text lists 2.redacted: the fields the account was not allowed to see.
The exit code is the same as in text mode.
What happened next
The injected session was released and a follow-up check found no test sessions left. In a real case, run the verify command and decide with the application owner whether the transaction should be committed, rolled back or the connection closed.
4 - Troubleshooting scenarios
Confirm you are on the right instance before following a scenario. The examples come from fault-injection scripts on a lab cluster; you do not need to reproduce them in production.
- Long-running SQL: find a statement that has been running for a while and what it waits on.
- Lock waits: find who waits, follow the verify line to the blocker, and confirm the waits are gone.
- Read a real result: a WARN in text and JSON, line by line.
4.1 - Find long-running SQL
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. PIDs, counts and timings belong to this capture only. A fault-injection script started select pg_sleep(3600);, a statement that only sleeps. It tests detection, not CPU or disk pressure.
kbdiag 2.0 has no slow-SQL threshold yet: a long statement is listed and sorted, but not a WARN on its own. It becomes a finding when it holds a transaction open too long (txn) or makes others wait for a lock (locks).
1. What is running?
connectedcounts the client sessions by user, database, application and client; walsenders and background processes are only counted (--alllists them).not idlelists the sessions doing something or holding a transaction open, longest transaction first. 1049328 has runselect pg_sleep(3600);for 13 seconds.queryis how long the current statement has been running;waitis what it waits on.- The
sqlcolumn is cut at 60 characters;session <pid>shows the full text.
2. What is it waiting on?
Timeout:PgSleep says the statement is sleeping on purpose. It waits for nobody, blocks nobody and holds no lock worth showing (every transaction’s own virtualxid lock is left out). For a real statement you would see IO (reading data), Lock (waiting for another session), or no wait event at all (running on CPU).
3. Is it one statement or many?
waits groups the sessions that are doing something by wait event and state. One Timeout:PgSleep session means a single statement, not a pile-up; the idle sessions and idle background processes are only counted. When dozens of sessions share one wait event, that event is where to look.
4. Decide
Here the sleep is deliberate; there is nothing to tune. For a real statement, check its plan and the data it touches with the application owner. kbdiag does not cancel statements; if you decide to, use select pg_cancel_backend(<pid>); in ksql. The injected session was released afterwards and a follow-up check found none left.
4.2 - Investigate lock waits
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. PIDs, counts and timings belong to this capture only. A fault-injection script opened a transaction that took an ACCESS EXCLUSIVE lock on a test table and then sat idle, and a second session tried to read the table.
1. Who is waiting?
- Session 1048829 has waited 13 seconds for
AccessShareLockonpublic.kbdiag_inj_lock; the direct blocker is 1048819. Waits over 10 seconds are a WARN (--lock-wait-warnchanges this). blockerscomes first, because the first question is who is in the way. It shows 1048819 blocks one session and holdsAccessExclusiveLockon the table. When one blocker holds up dozens of sessions, it is the top row.waitinglists each waiting session, longest wait first. The wait is measured from the waiter’s last state change, so it can overstate the wait a little, never understate it.
2. What is the blocker doing?
Follow the verify line:
- The blocker is
idle in transaction: its last statement waslock table kbdiag_inj_lock in access exclusive mode;and it has been waiting for the client for 14 seconds with the transaction still open. Nothing is running; the lock is held because nobody committed or rolled back. blockingshows who it holds up;holdsshows the lock they want.- If
waiting foris not empty, the blocker is itself waiting: runsessionon its blocker in turn until you reach one that is not waiting. kbdiag shows one hop at a time. - If a blocker is shown as
2PC, it is a prepared (two-phase) transaction with no session; find it withkbdiag txnon the primary.
3. Decide and act
kbdiag does not end sessions. Confirm with the application owner what the transaction was for. Usually the application should commit or roll back; if it cannot, end the connection yourself, for example with select pg_terminate_backend(1048819); in ksql, after checking the PID still belongs to the same user and application. Ending the blocker rolls back its transaction.
In this lab run the injection script released the holder, which ended the transaction.
4. Confirm
No waits, OK, exit 0. One clean sample only says the waits are gone now; if they come back, look for the application path that leaves transactions open.
5 - User manual
Snapshot of the kbdiag README at generation time.
Command pages:status · sessions · session · locks · txn · waits · slots · space · freeze · vacuum · archive · params · repl · cluster · top-objects · table · top · progress · checkpoint · wal · seq
KingbaseES command-line diagnostics. One static binary that talks the wire protocol directly: no ksql, no interactive screens. Each command runs a single read-only look at a live instance and prints a verdict, the evidence, and the next command to run. Works on single nodes and on repmgr primary/standby clusters.
This is kbdiag 2.0, rewritten in Go; v2.0.0-alpha.1 is its first release. The shell toolkit (v1.x) is frozen; use release v1.0.0 if you need it.
Install
Build a static Linux binary (Go from go.mod), then copy it to the database host:
Use GOARCH=arm64 for ARM hosts. The binary has no runtime dependencies.
Quick start
Commands
| Command | What it shows | Flags |
|---|---|---|
status | Version, data directory, port, role; each standby (on a primary) or the WAL upstream (on a standby); uptime, connections used / usable, database sizes, disk of the data directory (local runs only). FAIL when ordinary users can no longer connect; WARN when a standby is not receiving WAL | |
sessions | Who holds the connections (counted by user, database, application, client), then the client sessions that are not idle, longest transaction first; WARN on long idle in transaction. JSON always carries every session | --all, --limit N, --idle-in-txn-warn S |
session <pid> | One session: who and what it is, its full SQL, the lock it waits for and who blocks it, whom it blocks, what it holds | --lock-wait-warn S, --idle-in-txn-warn S |
locks | Who blocks the most (and what it holds), then every lock wait, longest first, with its direct blockers; WARN on long waits | --limit N, --lock-wait-warn S |
txn | The oldest xid holding back vacuum and who holds it, open transactions, prepared (2PC) ones; WARN on old ones | --limit N, --xact-warn S, --prepared-warn S |
waits | What the sessions doing something wait on, grouped by wait event and state, biggest pile first; idle sessions and background processes only counted | |
slots | Replication slots, inactive ones and the ones keeping the most WAL first; WARN on inactive ones | |
space | Where the space goes: the filesystems holding the data directory, WAL and tablespaces (local runs only), WAL size against max_wal_size and wal_keep_segments, database and tablespace sizes. FAIL when the filesystem holding the data directory or WAL has less free space than one WAL segment; everything else is shown only | |
freeze | How far each database is from transaction ID wraparound, and the oldest tables of this one; WARN past autovacuum_freeze_max_age, FAIL at the stop limit where new transaction IDs are refused | --limit N |
vacuum | The tables with the most dead tuples and where autovacuum’s threshold is for each, what is vacuuming now; WARN when nothing will clean them (autovacuum or track_counts off, or a table past its threshold with autovacuum off for it). Primary only | --limit N |
archive | Whether WAL archiving works: the settings, the last success and failure, WAL waiting to be archived; WARN when the last attempt failed | |
params | The parameters someone set (not at their default) and where: file and line; WARN for each change that waits for a restart | |
repl | Replication from this node’s side: on a primary the synchronous settings and how far each standby is behind; on a standby its upstream, receive and replay. WARN when fewer synchronous standbys stream than asked for, replay is paused, or a standby receives no WAL. Lag is shown, not judged | |
cluster | repmgr’s view (nodes, roles, upstreams, latest events) checked against this node; WARN on two primaries, inactive nodes, a role repmgr has wrong, a standby not attached. Connects to database esrep unless -d is given | |
top-objects | The largest tables (heap, indexes, TOAST) and indexes of this database. Shows only | --limit N |
table <name> | One table: size, rows, vacuum and analyze, freeze age, access, indexes; judged with the freeze and vacuum rules. The name follows SQL rules (unquoted folds to lower case); not found is UNKNOWN | |
top | Cumulative top SQL from sys_stat_statements (since the last reset), with each statement’s share of all execution time; says so when statements are not being collected. Shows only | --limit N, --by time/mean/calls/io/temp |
progress | How far running VACUUM, CREATE INDEX, CLUSTER / VACUUM FULL and CHECKPOINT have got. Shows only | |
checkpoint | The last checkpoint, timed vs requested checkpoints, who writes dirty buffers (checkpointer, bgwriter, backends), and the settings. Shows only | |
wal | Where WAL is, how much sys_wal holds, and what keeps it: max_wal_size, wal_keep_segments, each slot, files waiting to be archived. Shows only | |
seq | Sequences of this database by how much of their range is used; FAIL when one cannot hand out another value | --limit N |
Defaults: idle in transaction 300 s, lock wait 10 s, transaction 300 s, prepared transaction 900 s, all WARN. --limit only trims what is shown; findings always cover every row.
Connection
| Flag | Default |
|---|---|
--host | /tmp (local socket /tmp/.s.KINGBASE.54321); a host name or IP for TCP |
-p, --port | 54321 |
-d, --dbname | test |
-U, --user | system |
--timeout | 10s per query |
--json | print the report as JSON |
The password comes from PGPASSWORD or ~/.pgpass. Every connection is a read-only transaction with a lock_timeout; kbdiag never changes anything. Suggested fixes (such as ROLLBACK PREPARED) are printed, never run.
Output
- First line: command, verdict, and context (version, role, user@location, collection time).
- Findings: an id, a symptom, and a
verifyorfixnext step. - Data: laid out for reading, sizes and durations in readable units;
-is null,?is hidden from this account.--jsongives every probe’s raw table (bytes, seconds) with stable field names.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN |
| 2 | FAIL |
| 3 | UNKNOWN: something could not be collected or seen, so kbdiag will not claim OK |
| 64 | Usage error |
| 69 | Cannot connect |
Privileges
Run as system over the local socket for the full picture. An account without a monitoring role cannot see other sessions’ state, timings or SQL; kbdiag lists those fields under redacted and answers UNKNOWN instead of OK, unless what it can see is already WARN or FAIL. Granting sys_monitor lifts this. On a standby, prepared transactions are not_applicable (run txn on the primary); this does not change the verdict.
Requirements
- KingbaseES V8R6 (tested on V008R006C009B0014)
- Linux amd64 or arm64
- repmgr is optional
5.1 - sessions: list sessions
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. The application sessions named kbdiag_inj_* were created by fault-injection scripts. PIDs, counts and timings belong to this capture only.
Usage
| Flag | Effect |
|---|---|
--all | List every session, including idle ones and background processes |
--limit N | Show at most N rows, default 50; 0 shows all |
--idle-in-txn-warn SECONDS | WARN when a session is idle in transaction longer than this, default 300 |
--json | Print the report as JSON |
Connection flags --host, -p, -d, -U and --timeout are global. Defaults: local socket in /tmp, port 54321, database test, user system. The password comes from PGPASSWORD or ~/.pgpass. The connection runs read-only transactions and sets lock_timeout.
--all and --limit only change what is displayed. The summary and the findings always cover every session, so a session that is not shown can still raise a WARN.
Default output
Several injected sessions are open: a table lock held idle in transaction and a reader waiting for it, a second idle-in-transaction session, a session waiting on a prepared transaction, and a pg_sleep(3600).
connected:answers “who holds the connections”. Client sessions are counted by user, database, application and client, leaving out kbdiag’s own connection. Walsenders and background processes are only counted in the heading.localis the Unix socket.not idle:lists the client sessions that are doing something or hold a transaction open:active,idle in transactionand the like. Idle sessions and background processes are not listed;--alllists them.- Rows are sorted by transaction age, longest first. Sessions without a transaction come last, then by PID.
xactis the transaction age andquerythe age of the current (or last) statement.waitis the wait event astype:event.sqlis cut to 60 characters;session <pid>shows it in full.-means null.- The two idle-in-transaction sessions have been so for about 30 seconds, below the default 300, so the verdict is OK.
A WARN
Only the idle-in-transaction session is left, and the threshold is lowered to 5 seconds so the example does not have to wait 300:
- One finding per session over the threshold. The
verifyline gives the next command to run. - The
esreprow is repmgr’s own query, caught while it ran. - Exit code 1 means at least one WARN.
Idle in transaction for 5 minutes is a fault whatever the application: a pool that leaked a connection, or a missing commit. 300 seconds filters out noise; it is not a capacity line. Raise it if DBAs routinely hold transactions open by hand. txn reports the same session as a long transaction at the same default. The two findings answer different questions (“it sits idle” and “its transaction is long”), so both stay.
Who holds the connections
When status reports that ordinary users can no longer connect, its verify line points here. A fault-injection script filled every connection ordinary users may open:
The summary shows the pile-up at once: 93 idle sessions of kbdiag_ro from 127.0.0.1, application kbdiag_inj_conn. They are idle, so not idle is empty.
Fewer rows
--limit cuts the not idle list (and the rows in --json); the last line says how many are not shown. The summary and the findings still cover every session.
Every session
--all lists idle sessions and background processes too, and adds a type column (backend_type). The walsender is the replication connection to the standby.
Insufficient privilege
Connect as kbdiag_ro, an account without a monitoring role, while the injected sessions are still there:
- This account cannot see the state, type, client address, times, wait event or SQL of other users’ sessions. It cannot tell whether a session is working, idle or a background process, so none is called idle: all 15 are
hidden, and they are still counted in the summary by user, database and application. ?is a value this account may not see;-is null.- The sessions cannot be judged, so the verdict is UNKNOWN with exit code 3, not OK. The
redactedline names the hidden columns and the fix: grantsys_monitor. - A session that turns off
track_activitiesfor itself (SETorALTER ROLE ... SET) shows the statedisabled. It is still listed, but its times show?: the server keeps old values. It also makes the verdict UNKNOWN, with the reasontrack_activities_off.
JSON
--json always carries every session, idle ones and background processes included, with raw values: times in seconds and the full SQL. See Read a real result for its shape.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: a session idle in transaction too long |
| 3 | UNKNOWN: some sessions not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.2 - session: one session
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. The sessions in the examples were created by fault-injection scripts: 1045715 holds an exclusive lock on table kbdiag_inj_lock idle in transaction, 1045724 waits for it, and 1045809 is a second session idle in transaction. PIDs, counts and timings belong to this capture only.
Usage
| Option | Effect |
|---|---|
<pid> | PID of the session to show; required |
--lock-wait-warn SECONDS | WARN when waiting for a lock longer than this; default 10 |
--idle-in-txn-warn SECONDS | WARN when idle in transaction longer than this; default 300 |
--json | Print JSON |
Connection options are the same as for sessions. The thresholds and findings are the same as those of sessions and locks.
A session waiting for a lock
- The block at the top says who the session is (user, database, application, client, type) and what it does: its state and how long it has been in it, the transaction and statement ages, the wait event, and its transaction id and snapshot xmin.
sqlis the statement in full, with its line breaks. This is the only command that shows the whole SQL. For an idle session the heading readslast sql, since that statement has finished.waiting foris the lock it wants, how long it has waited and who blocks it directly.- It has waited 35 seconds, over the default 10, so there is a
lock.waitingWARN and the exit code is 1. Theverifyline points at the blocker. waitedis measured from the session’s last state change, so it can overstate the wait a little, never understate it.
The blocking session
blockinglists the sessions waiting for locks this session holds.holdslists its locks.- Every transaction holds its own
virtualxidandtransactionidlocks; they are left out ofholdsunless someone waits for a lock of that type. The xid is in the block at the top. - A row lock is labelled with its type, for example
public.t (tuple), so it is not read as a table lock. - The WARN is the waiter’s
lock.waiting: it says this session has been blocking another for 34 seconds, which is this session’s problem. It has noverifyline, since it would point back here. - The session has been idle in transaction for 34 seconds: it took the lock and never committed. Its own
session.idle_in_txnwould come at 300 seconds.
Idle in transaction
With the threshold lowered to 10 seconds:
The session has a transaction id but holds no lock others want, so holds is empty. What it holds back is the vacuum horizon; see txn.
Insufficient privilege
Connect as kbdiag_ro, which has no monitoring role, and look at the blocker:
- User, database and application are visible, and so are the transaction id and xmin. State, times, client, type, wait and SQL show
?. - Locks and who blocks whom are visible; how long the other session has waited is not.
- The session cannot be judged, so the verdict is UNKNOWN with exit code 3. Grant
sys_monitorto see everything.
No such session
When the PID is not found (it may have ended), a note goes to standard error, the verdict is UNKNOWN and the exit code 3. Omitting the PID is a usage error, exit code 64.
JSON
--json carries the activity row and the lock rows the text is built from, with raw values: times in seconds and every lock row, including virtualxid. The lock.waiting finding of the waiter above, with the fields scripts can use:
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: a lock wait or idle in transaction over its threshold |
| 3 | UNKNOWN: no such session, or data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.3 - locks: lock waits
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. The lock waits were created by fault-injection scripts. On the primary, 1045715 holds an exclusive lock on table kbdiag_inj_lock idle in transaction and 1045724 waits for it. A prepared (two-phase) transaction holds a lock on kbdiag_inj_2pc, and 1046007 waits for that. PIDs, counts and timings belong to this capture only.
Usage
| Option | Effect |
|---|---|
--limit N | Show at most N waiting sessions; default 50, 0 for all |
--lock-wait-warn SECONDS | WARN when waiting for a lock longer than this; default 10 |
--json | Print JSON |
Connection options are the same as for sessions. --limit only cuts the waiting list; the findings always cover every wait.
Sessions waiting for locks
blockerscomes first, because the first question in an incident is who is blocking. One blocker often holds up dozens of sessions, so they are sorted by how many sessions they block.holdsis what the blocker holds on the objects its waiters want.- A prepared transaction belongs to no session, so it is shown as
2PC. Its finding says so and points atkbdiag txn, which gives its gid and age. - A blocker that is itself waiting is marked
(queued ahead for ...): the server counts a conflicting session queued earlier as a blocker too. waitinglists every session waiting for a lock, longest wait first.blocked bynames the direct blockers only. For a longer chain, runsessionon the blocker in turn.- Each waiting session over
--lock-wait-warngets onelock.waitingfinding. Shorter waits are listed but not flagged. waitedis measured from the waiter’s last state change: V8R6 has no lock-wait start time. It can overstate the wait a little, never understate it.
A 10-second lock wait is already an incident for OLTP but can be normal for a batch job, so it is a WARN, not a FAIL. 10 seconds filters out passing row-lock contention. Batch databases may want it higher.
With no waits:
On a standby
A session-level advisory lock, taken on the standby:
A lock with no table, such as an advisory lock, shows its lock type as the object.
Insufficient privilege
Connect as kbdiag_ro, which has no monitoring role, while the waits on the primary are still there:
- The locks and who blocks whom are visible. This account cannot see other sessions’ timestamps, so the waits show
?and the threshold cannot be checked. The verdict is UNKNOWN, exit code 3. - Grant
sys_monitorto see everything.
JSON
--json lists the lock rows of the waiting sessions, plus the blockers’ locks on the same objects: pid, locktype, relation, mode, granted, wait_s and blocked_by. blocked_by keeps the server’s raw list: a prepared transaction appears as 0.
Limits
- Parallel query workers have their own PIDs and lock rows, so one waiting parallel query may count as several blocked sessions.
- Several advisory locks of one blocker are listed together in its
holds, since only the lock type is compared.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: a session waited for a lock too long |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.4 - txn: transactions
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. The transactions in the examples were created by fault-injection scripts: two sessions idle in transaction (one holding a table lock), sessions waiting for locks, a pg_sleep(3600), and kbdiag_inj_2pc, a prepared transaction that was neither committed nor rolled back. PIDs, counts and timings belong to this capture only.
Usage
| Option | Effect |
|---|---|
--limit N | Show at most N transactions; default 50, 0 for all |
--xact-warn SECONDS | WARN when a transaction is older than this; default 300 |
--prepared-warn SECONDS | WARN when a prepared transaction is older than this; default 900 |
--json | Print JSON |
Connection options are the same as for sessions. --limit only affects what is shown; the findings always cover every transaction.
Default output
oldest xidis the oldest transaction id any session or prepared transaction holds, and who holds it. Here 1045715 has transaction id 6343, and three others have snapshots (xmin) taken while 6343 was running. VACUUM cannot remove row versions newer than this, so this line answers “who holds back the vacuum horizon”. Ids are compared modulo 2^32, as the server does.open transactionslists sessions inside a transaction, meaning with a transaction start time, a transaction id or a snapshot xmin. The oldest transaction comes first.xidis assigned at the first write; a read-only transaction has onlyxmin.preparedlists every prepared transaction: gid, owner, database, age and xid. They belong to no session, sosessionscannot show them.- Nothing is past its default threshold, so the verdict is OK.
WARN
With both thresholds lowered to 10 seconds:
txn.long: one per transaction older than--xact-warn. Theverifyline leads to the session.txn.prepared: a prepared transaction older than--prepared-warn. It keeps its locks and holds back the vacuum horizon until someone commits or rolls it back. Thefixline gives the statement; kbdiag only prints it. Its note says to confirm with the application whether to commit (COMMIT PREPARED) or roll back, and to run it connected to the transaction’s database, outside a transaction block.- If a gid contains control characters, the terminal shows it escaped and a copied statement would not match. In that case there is no
fixline; theverifyline says to take the raw gid from--json. - Both are WARN, never FAIL. Their harm, table bloat and locks held for a long time, is still to come. The sessions they block now are reported by
locks. - 300 and 900 seconds have no server-side counterpart. 300 can catch a normal batch job, so raise them on batch databases.
- At default thresholds a session idle in transaction for over 300 seconds is also reported by
sessionsassession.idle_in_txn. The two answer different questions.
On a standby
A standby cannot see the primary’s prepared transactions, so txn.prepared is not_applicable. That does not mean “none”, and it does not affect the verdict. Run txn on the primary.
Insufficient privilege
Connect as kbdiag_ro, which has no monitoring role:
- KingbaseES shows other sessions’ transaction id and xmin to any account, so
oldest xidand the open transactions are still found. Their state, age and SQL show?. - Hidden sessions with neither an xid nor an xmin are only counted (
10 sessions hidden). They are most likely idle, and they are not called transactions. - The verdict is UNKNOWN, exit code 3. Grant
sys_monitorto see everything. - When session activity or the prepared transactions could not be collected,
oldest xidsays an older one may exist, or showsunknownif nothing was found.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: a long transaction or a long-pending prepared transaction |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.5 - waits: wait events
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. The sessions in the examples were created by fault-injection scripts: two sessions idle in transaction, two waiting for table locks, and a pg_sleep(3600). PIDs, counts and timings belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions.
Default output
not idlegroups the sessions that are doing something by wait event and state.sessionscounts them andpidslists them. The biggest group comes first, since a pile-up is what to look for; on a tieactivecomes first.Lock:relation: two sessions wait for table locks; who blocks them and for how long is inlocks.Client:ClientReadwithidle in transaction: the sessions wait for their client to send the next statement while holding a transaction open.Timeout:PgSleepis the injectedpg_sleep. An active session with no wait event shows(running): on CPU, or in code without a wait event.pidslists up to 10 PIDs, then... (+N).--jsonhas them all.not showncounts what is left out.idlesessions do not answer “where is it stuck”. Nor do background processes idling in their main loop (anyActivitywait, such as a walsender with nothing to send or the checkpointer between checkpoints). A background process stuck on a real wait, for example the checkpointer onIO:DataFileSync, is listed with the state(background).
There are no thresholds and no options. Ten sessions waiting on IO can be an incident on one database and routine on another, so waits only reports. A lock wait that is too long is judged by locks.
A quiet primary:
On a busy primary a walsender catching up may appear with a wait such as IO:WALRead; that is normal.
On a standby
Insufficient privilege
Connect as kbdiag_ro, which has no monitoring role:
- Other users’ sessions have no visible wait event or state. They may be idle or not, so they are counted as
hidden (state unknown), not as idle. Sessions that turned offtrack_activitiesare counted asuntracked (state unknown)for the same reason. - With hidden sessions kbdiag cannot claim nothing is waiting, so the verdict is UNKNOWN, exit code 3. Grant
sys_monitorto see everything.
JSON
--json has every group, idle and background ones included, with the full PID lists: wait_event_type, wait_event, state, sessions and pids.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.6 - status: instance overview
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. The FAIL example was created by a fault-injection script that fills every connection ordinary users may open. Values belong to this capture only.
Usage
status has no options of its own. Connection options are the same as for sessions.
Only two things are judged:
- FAIL when every connection ordinary users may open is taken (
max_connectionslesssuperuser_reserved_connections). At that point the application can no longer connect. - WARN on a standby that is not receiving WAL, meaning it has no WAL receiver or its status is not
streaming. Queries still work, but if the primary fails now there is no up-to-date standby to take over.
There is no “almost full” warning. How many connections is too many depends on the application, so status shows the count and leaves that call to you.
On the primary
- The role in the first line (primary / standby) comes from
sys_is_in_recovery(). inst.info:connectionscounts only backends connected to a database, not background processes. It compares directly with the 97 connections ordinary users may open.inst.downstreams: one row per standby this instance sends WAL to, fromsys_stat_replication.syncis shown as the server reports it. With repmgr it readsquorumhere, notsync/async.inst.upstreamonly applies to a standby.inst.databases: non-template databases, largest first. Sizes use 1024-based units, aspg_size_prettydoes.inst.disk: the filesystem holdingdata_directory, asdfreports it. Used plus free is less than the total by the blocks reserved for root. It is shown, not judged.
On the standby
- On a standby the upstream comes first. It comes from
sys_stat_wal_receiver: where the standby gets WAL from, through which slot, and when the last message arrived. last_msgis shown, not judged. A quiet primary only sends a message everywal_receiver_status_interval(10 seconds by default), so a few seconds is normal.- A paused WAL receiver (for example one stopped with SIGSTOP) keeps the status
streaming, so status does not report it. Iflast_msgkeeps growing, check the standby’s WAL receiver process yourself. Theslotspage shows a real capture of this.
All connections taken
- 98 is above 97 because superusers still get in through the reserve, including kbdiag’s own
systemconnection. - The
verifyline points tosessions, whose summary counts connections by user, database, application and client. In this capture it shows 93 idle sessions ofkbdiag_rofrom127.0.0.1.
Standby not receiving WAL
This case has not been reproduced in the lab yet. The finding below is quoted from the source code, not captured. On a standby with no WAL receiver, status reports:
If the receiver exists but its status is not streaming, the finding names that status instead. The exit code is 1.
Over TCP
kbdiag only reads inst.disk when it is sure it runs on the database host. That means one of:
- the Unix socket;
--hostset to localhost / 127.0.0.1 / ::1, withdata_directorypresent locally. The directory check is needed because a forwarded port could lead to another machine.
Otherwise inst.disk is not_applicable. Here kbdiag connects to the primary’s own network address as kbdiag_ro:
not_applicable does not affect the verdict.
Without privileges
kbdiag_ro has no monitoring role. On KingbaseES V8R6 it still sees sys_stat_replication, sys_stat_wal_receiver and data_directory in full, so status gives it the same result as system. Only database sizes can be hidden. Without CONNECT on a database, that database’s size is empty and recorded in redacted. Sizes are not judged, so the verdict stays OK rather than UNKNOWN.
JSON
--json keeps raw values: sizes in bytes, times in seconds, and the version as the short number. inst.info also carries usable_connections. inst.disk carries total_bytes, used_bytes and avail_bytes. A probe that does not apply has "status": "not_applicable" and a reason, for example "primary" or "remote connection".
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: a standby is not receiving WAL |
| 2 | FAIL: all connections ordinary users may open are taken |
| 3 | UNKNOWN: data not collected |
| 64 | Usage error |
| 69 | Cannot connect |
5.7 - slots: replication slots
Captured on 2026-09-27 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 93bf65d (static linux/amd64 binary). This is lab evidence, not production validation. The WARN example was created by a fault-injection script. It pauses the standby’s WAL receiver (SIGSTOP), so after wal_sender_timeout (30 seconds here) the primary’s slot turns inactive, as it does when a standby dies. Values belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions.
Healthy
activesays whether something consumes the slot, and which process (here the walsender to the standby).retainedis the WAL this slot makes the instance keep, in 1024-based units aspg_size_prettyuses. It is measured from the current WAL position on a primary and from the replay position on a standby.xminis the oldest transaction id the slot asks the instance to keep. For this physical slot it comes from the standby’shot_standby_feedback.xmin ageis how far it lags behind the current transaction id.- A logical slot can also carry a
catalog_xmin, which holds back vacuum of the system catalogs. That column appears only when some slot has one. (The lab runswal_level=replica, so it has no logical slots.)
On a standby with no slots of its own:
An inactive slot
- Nothing consumes an inactive slot, yet it keeps WAL, and one with an
xminstops VACUUM from removing old row versions. Being inactive is the line itself, so there is no time threshold and no option. - It is a WARN, not a FAIL: the application keeps working. The harm (WAL filling the disk, table bloat, no standby keeping up) comes later.
- Right after the injection almost no WAL is retained. In a real failure it keeps growing.
- A standby restarted by repmgr also makes its slot inactive for a few seconds, and that is reported too.
- Drop the slot only when its downstream is really gone. kbdiag never drops it.
Following the verify line
The verify line says to run kbdiag status on the slot’s downstream node. That is usually a standby, though a standby can have downstreams of its own. On the paused standby:
The WAL receiver still shows streaming, because a paused process does not change its status. Run it again after 10–20 seconds, as the note says:
last_msg grew from 44 seconds to 1 minute: the receiver is stuck. status does not judge last_msg, because a quiet primary only sends a message every wal_receiver_status_interval (10 seconds by default). If the node cannot be reached at all, it is down. If inst.upstream has no receiver or is not streaming, status reports that as a WARN.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: an inactive slot |
| 3 | UNKNOWN: data not collected |
| 64 | Usage error |
| 69 | Cannot connect |
5.8 - space: disk, WAL, databases
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. Everything here comes from the healthy lab and from two accounts that cannot see everything: kbdiag_ro (no monitoring role) over 127.0.0.1, and kbdiag_ro over the node’s network address. No fault was injected for this command. The one FAIL it can report was not produced in the lab (see the last section). Values belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions.
Healthy primary
disklists the filesystems that hold the data directory, the WAL directory and the tablespaces, with directories on the same filesystem merged into one row (by device number, so asys_walthat is a symlink to another disk shows up as its own row).freeis what a non-root user such askingbasecan use. It reads the disk with statfs, so it works only when kbdiag runs on the database host (see “Remote connection” below).walis the size ofsys_wal, set besidemax_wal_sizeandwal_keep_segments(shown as the size it keeps: 512 x 16 MB). kbdiag does not judge it:max_wal_sizeis a soft limit, and PG12 normally keeps about the sum of both plus the WAL written since the last checkpoint, so exceeding only one of them is normal. Whensys_walis above that sum, the text points you tokbdiag slotsandkbdiag archive. Here 3248 MB is below 1024 + 8192 MB.databasesreuses the same probe asstatus, so the numbers matchstatus.tablespaceslists each one with its location (in data_directoryfor the default ones).
Standby
A standby is judged the same way. Its WAL directory is its own, so the file count and size differ from the primary’s.
Without privileges
kbdiag_ro has no monitoring role. It connects over 127.0.0.1, which is TCP (hence kbdiag_ro@remote in the first line), but the database is on this host, so the disk can still be read:
wal: skipped:sys_ls_waldiris not allowed for this account, so the WAL size is not collected.spaceis a display command, so any probe that is not collected makes the verdict UNKNOWN instead of OK: OK would only mean “everything was collected”, and here it was not.?in the tablespace table means this account cannot see that size (pg_tablespace_size(sys_global)is denied). kbdiag wraps the call in a CASE, so only those cells are NULL and the rest of the statement still runs; the cells are listed inredacted. Grantingsys_monitorlifts both.
Remote connection
Over the node’s network address the data directory may be on another machine than the one you are looking at, so kbdiag does not read a disk:
disk: not_applicable (remote connection). kbdiag reads the disk only when it is certain to run on the database host: a Unix socket, or a host oflocalhost,127.0.0.1or::1with thedata_directorypresent locally. A forwarded port could lead to another machine, so the host name alone is not enough.not_applicableis not a failure, but with the privilege gaps above the verdict is UNKNOWN here too.
The FAIL that was not produced
Not produced in the lab, taken from the source: space.disk_full (FAIL). It reports a filesystem holding the data directory or the WAL directory whose free space is less than one WAL segment (16 MB here). Making it appear needs the data disk to be really filled, which the lab does not do.
- On the WAL disk no new segment can be created, so the server stops once no old segment is left to reuse. On the data directory’s disk, tables, transaction status files and temp files are about to fail to grow (these are ERRORs; the instance does not stop). The finding says which applies.
- Strictly it is “about to be affected”, and kbdiag reports it as FAIL because there is one segment of room left and no margin to keep watching.
- Free space on other disks (a tablespace) or “low but more than a segment” depends on the database, so it is only shown.
- One finding per filesystem. The segment size comes from the
walprobe, so when it is not collected (as forkbdiag_ro) this rule does not judge.
The status command’s inst.disk is a shorter, display-only view of the data directory’s disk.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 2 | FAIL: a filesystem has less free space than one WAL segment (not produced in the lab) |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.9 - freeze: wraparound distance
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. Both nodes were healthy and nothing was injected, so every example is OK. The WARN and FAIL cases were not produced in the lab (see the last section). Values belong to this capture only.
Usage
| Option | Effect |
|---|---|
--limit N | Show at most N tables; default 20, 0 for all |
--json | Print JSON |
Connection options are the same as for sessions. --limit only trims the table list; the findings always cover every database and every table.
Healthy primary
limitsshows the two lines kbdiag compares against, both taken from the server:autovacuum_freeze_max_age(WARN line: autovacuum must start freezing by force here) and the stop limit (FAIL line: the server refuses to hand out new transaction IDs, so write transactions fail). The stop limit is a constant of the PG12 kernel, 1 million xids before the wraparound point (2^31 - 1); for multixacts it is 100. kbdiag does not invent a threshold of its own.databasesis one row per database:xid ageandmxid ageare the age of its oldest unfrozen xid and multixact,of freeze maxis the xid age as a percentage ofautovacuum_freeze_max_age, andxids leftis how many more transaction IDs can be handed out before the stop limit.- The verdict is judged per database. A database is as old as its oldest table, so reporting every table would repeat one problem dozens of times.
tablesis shown only for the database you are connected to, oldest first; usekbdiag -d <database> freezefor another one. heap (est.)isrelpages x block_size, an estimate. The exact size functions take a lock on every table, and during a rescue (VACUUM FULL, TRUNCATE are common then) they would make the whole probe wait untillock_timeoutand fail.- Tables whose
relfrozenxidis 0 (such as_kingbase_loginfo) are left out:age(0)reads as 2147483647 and would raise a false FAIL. - On the standby the ages are replicated and equal the primary’s (the captured output on kes-node2 shows the same 5672 for every database). A VACUUM to fix a finding must still be run on the primary.
Options as the binary prints them:
WARN and FAIL: not produced in the lab
Neither finding was produced, because it would take pushing the transaction counter to hundreds of millions of xids. Taken from the source, not captured, both with the id freeze.database_age (and freeze.table_age on kbdiag table):
- WARN: a database’s xid age is at or above
autovacuum_freeze_max_age, or its multixact age is at or aboveautovacuum_multixact_freeze_max_age. The anti-wraparound autovacuum is due or cannot finish. - FAIL: the xid age is within 1 million of wraparound (or the multixact age within 100). The server refuses new transaction IDs, so writes fail.
- Next steps point to
kbdiag txnandkbdiag slots(what holds the horizon back), tokbdiag -d <database> freezefor the other databases, and toVACUUM (FREEZE)run on the primary. - If the limits could not be read, a FAIL can still be judged (the stop limit is a constant); below it the verdict is UNKNOWN.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: past autovacuum_freeze_max_age (not produced in the lab) |
| 2 | FAIL: at the stop limit (not produced in the lab) |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.10 - vacuum: dead tuples and autovacuum
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. The WARN example was created by an injection script: it made table public.kbdiag_inj_dead with autovacuum_enabled=off, inserted 10000 rows and deleted 5000, so the table holds 5000 dead tuples that nothing will clean. Values belong to this capture only.
Usage
| Option | Effect |
|---|---|
--limit N | Show at most N tables; default 20, 0 for all |
--json | Print JSON |
Connection options are the same as for sessions. --limit only trims the table list; the findings cover every table. vacuum looks at the database you are connected to (default test).
Healthy primary
settingsshows whetherautovacuumandtrack_countsare on, the workers and naptime, and the trigger formula (threshold+ scale factor xreltuples).runninglists VACUUMs in progress, with how far each has got (seeprogress). None here.tablesis ordered by dead tuples.thresholdis the autovacuum trigger line for that table, computed the way the server does it (float4, and per-tablereloptionsoverride the global values).duesays whether the dead tuples are past that line.autovacuumis the table’s own switch.- The list is sorted by dead tuples, and the top rows have 0, so no table is due.
last autovacuum neveron every table only says autovacuum never had to run on these tables since the statistics reset.
Standby
Table statistics are local to each node and stay 0 on a standby, and autovacuum does not run there, so running and tables are not_applicable. The settings are still shown. Run vacuum on the primary.
A table that nobody will clean
vacuum.table_disabledis the finding: the table has 5000 dead tuples, past its trigger line 2050, but its ownautovacuum_enabled=off, so autovacuum will never clean it. Theduecolumn saysyesand theautovacuumcolumn saysoff.- It is a WARN: the dead tuples cause bloat later but nothing is broken now. The
fixline isVACUUMon that table (names are quoted likequote_identwhere needed); if the dead tuples are still there afterwards, something holds the horizon back, which is what the twoverifylines (kbdiag txn,kbdiag slots) are for. - A table that is past its line while autovacuum is on is not reported. That is the normal autovacuum queue, handled once every naptime, and “waiting too long” would need a time threshold that has no objective value. It shows as
duein the table only. - The other finding of this command,
vacuum.disabled(WARN), was not produced in the lab: it meansautovacuumortrack_countsis off (taken from the source), so no table is vacuumed automatically any more except for the anti-wraparound vacuum. ThefixisALTER SYSTEM SET <the switch> = onfollowed by a reload. - When
track_countsis off the table probe isskipped: the counters are frozen, so old values are not shown as current.
For the same table seen from the table command, see its page.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.11 - archive: WAL archiving
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. No fault was injected. The primary’s WAL archiving was already failing in the lab (its archive_command runs sys_rman archive-push, and the last success was 21 days earlier), so the WARN below is what the lab really looks like; why it fails was not investigated for this page, because kbdiag does not read the server log. Values belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions.
Primary: archiving is failing
archive.failing(WARN) fires when the last attempt failed: the last failure is later than the last success, or there has never been a success. WAL that is not archived stays insys_waland is missing from the backups, but the business keeps running, so it is a WARN.settingsshowsarchive_mode,archive_commandandarchive_timeout. kbdiag first excludes what is switched off on purpose:archive_mode=offis never judged, andarchive_mode=onon a standby is not judged either, because a standby does not archive unless the mode isalways.archiveris the server’s archiver statistics, counted since the statistics reset time: how many succeeded and failed, and the last WAL file and how long ago for each.waitingcounts.readyfiles (WAL waiting to be archived) and.donefiles, and how long the oldest.readyhas waited. It is display only: it must not change the verdict.- The
verifyline points tokbdiag space(how bigsys_walhas grown). The reason the command fails is only in the server log (log_directory). - An empty
archive_commandis not judged: the PostgreSQL documentation says WAL then stays until a command is set, but this was not checked on KingbaseES, so the text only writes(empty).
Standby
The standby runs with archive_mode=always, so it archives too. It counts since its own start, and here it succeeds and has nothing waiting, so it is OK.
Without privileges
kbdiag_ro cannot call sys_ls_archive_statusdir, so waiting is skipped. That probe is display only and is not allowed to change the verdict. The finding itself comes from the statistics view, which this account can read.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: the last archive attempt failed |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.12 - params: changed parameters
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. The WARN example was created by an injection script: it changed max_files_per_process (a parameter that needs a restart) with ALTER SYSTEM, reloaded the configuration, and did not restart, so the running value differs from the file. Values belong to this capture only.
Usage
No options of its own, and no pattern argument: to see one parameter use ksql -c 'show x', or --json with jq. Connection options are the same as for sessions.
Healthy primary
pending restartlists the parameters whose file value differs from what is running (the server’s ownpending_restartflag). None here.changedlists only what someone set: sources other thandefault,override(fixed at compile or initdb time) and this connection’s ownclient/session(those are kbdiag’s own settings and would be noise).fileis the file and line that set it.- The line under
changednames the two limits of what can be seen. kbdiag setsapplication_name,default_transaction_read_only,lock_timeoutandstatement_timeoutitself on its connection, so their configured values are not shown. Per-role and per-database settings show only for this role and database. - Long values such as
archive_commandare cut with...;--jsonhas them in full. - The standby shows a similar list, with
primary_conninfo,primary_slot_nameandsynchronous_standby_namesdiffering (captured on kes-node2; 63 parameters changed there too).
Without privileges
kbdiag_ro (no monitoring role, over 127.0.0.1):
fileis?: this account cannot seesourcefileandsourceline. They are recorded underredacted.- The list is shorter (59 instead of 63) because superuser-only parameters are not visible to this account at all, and the text says so on its last line. Their
pending_restartflag is invisible too, so with no finding the verdict is UNKNOWN: kbdiag will not say OK about something it cannot see.
A change waiting for a restart
params.pending_restartis a WARN, one per parameter. The line is the server’s ownpending_restartflag. The business is not affected now: the instance still runs the old value. But the next restart, including one after a failover, will suddenly switch to the new value.pending restartshows the running value; the value waiting in the file is shown as-here. Theverifyline gives a query for it (sys_file_settings; it needs a superuser).- The
fixline writes the running value back withALTER SYSTEM SET x = '<running value>'and thenSELECT sys_reload_conf(). In PostgreSQL 12 a reload clears the pending flag only when the file’s value equals the running value, soALTER SYSTEM RESETalone would leave the flag until the next restart. If the change was meant, plan the restart instead. max_files_per_processis listed inchangedwith sourcedefault: a parameter that waits for a restart is listed whatever its source, because after the change its source still readsdefault.- Config file errors (
sys_file_settings.error) are not checked:kbdiag_rocannot read that view.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: a parameter change waits for a restart |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.13 - repl: replication from this node
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. The WARN example was created by a fault-injection script that pauses the standby’s WAL receiver (SIGSTOP), the same one used on the slots page. Values belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions.
Primary
syncshowssynchronous_standby_namesand what it means (any 1 of node2 must confirm),synchronous_commit, and how many standbys the server currently counts as synchronous.quorumin thesynccolumn is the server’s ownsync_state; repmgr reportsquorumhere rather thansyncorasync, so kbdiag does not translate it.downstreamsis one row per standby: the position it has been sent, flushed and replayed, as bytes behind this node’s current WAL position, the replay lag in time (empty when the standby is caught up and idle), and how long ago it last replied.- Lag is shown, not judged. There is no objective line on the server for it.
Standby
upstreamis the same WAL receiver view askbdiag status(inst.upstream), withlast_msgthe time since the last message from the primary.replaycompares what was received with what was replayed.caught up with what was receivedmeans the two positions are equal.last replayed transaction 1m 0s agois not a delay: on an idle primary it can read hours while the positions are identical, so kbdiag does not use it as a lag.pausedis whether replay was paused withsys_wal_replay_pause().
A stuck WAL receiver
With the standby’s receiver paused, the standby reports:
- The finding is
inst.upstream, the same rulekbdiag statususes: the receiver saysstreaming, butlast_msgis older thanwal_receiver_timeout(30 s in the lab). A working receiver asks the primary for a reply at half that time and reconnects when it is full, so only a stuck one gets past it. Without a receiver, or in a state other thanstreaming, the same finding fires. - The
receivedandreplayedpositions stay equal: nothing arrived, so there is nothing to replay. It is the age of the last message that shows it. - Known edge: the receiver sets its receive time at connection start, so a slow connection (more than half the timeout) or a timeline switch can raise this finding once for a moment. There is no margin on purpose, because no objective value for one exists.
The primary at the same moment:
- The standby is gone from
downstreams(0), andsynchronous_standby_namesnow reads(none: asynchronous only); it wasANY 1( node2)before the injection. Sorepl.sync_shortdid not fire. kbdiag shows what the server has; the the setting changed during the injection because repmgr changed it: withsynchronous='quorum'in repmgr.conf, repmgrd clearssynchronous_standby_namesabout 2 seconds after the standby disconnects (falling back to asynchronous commits) and restores it when the standby reconnects (seen in the lab’s hamgr.log, 2026-09-28). It is repmgr, not KingbaseES itself. So on this kind of clusterrepl.sync_shortshows only in that short window; the usual state after a standby is lost is “already asynchronous”, whichreplshows as(none: asynchronous only). - Use
slotson the primary to see the slot turned inactive, andclusterfor repmgr’s view.
WARN findings not produced in the lab
Taken from the source, not captured:
repl.replay_paused(WARN): replay is paused on a standby (sys_wal_replay_pause()). Queries still run, but it falls further behind and a failover would have to replay everything received first. ThefixisSELECT sys_wal_replay_resume()unless it was paused on purpose. It has no injection in the lab: pausing replay there blocks every commit on the primary (synchronous_commitisremote_apply).repl.sync_short(WARN):synchronous_standby_namesneeds more standbys than the server counts as synchronous candidates (syncorquorum). The text says commits either wait or the server has stopped waiting, so the synchronous copy is not guaranteed. It is a WARN rather than FAIL because in the lab, commits went through after the standby disconnected (repmgr had switched to asynchronous, see above). kbdiag counts the server’s ownsync_stateinstead of redoing the candidate rules, and it judges only whensynchronous_commitmakes commits wait for a standby (this connection’s value; per-role or per-database settings are not visible).
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: no WAL being received, replay paused, or too few synchronous standbys |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.14 - cluster: repmgr's view
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. The cluster is managed by repmgr, with its metadata in database esrep. The WARN example was created by the fault-injection script that pauses the standby’s WAL receiver (SIGSTOP) until the standby is no longer attached to the primary, the same one used on the slots page. Values belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions, with one difference: without -d, cluster connects to database esrep, where repmgr keeps its metadata. kbdiag holds only one connection, so it does not open a second one for it. If there is no esrep database it falls back to the default database (not exit code 69, which only means the instance cannot be reached). It does not run the repmgr binaries, so the per-node Status column of repmgr cluster show (which connects to each node) is not available.
Primary
nodesis repmgr’s node table: type, upstream, active, priority, location and replication slot. The text marks which node this is, using repmgr’sget_local_node_id()first and theprimary_slot_name(repmgr_slot_N) otherwise. If it cannot tell which node it is, it does not compare roles and the verdict is not OK.- The line
this node is node1: repmgr says primary, the database is a primaryis the comparison between repmgr’s metadata and what the database itself says.attached here: node2is the standbys repmgr says follow this node that really have a walsender here. eventsis the latest 20 repmgr events. The disconnect and reconnect events in the list are from earlier test runs in this lab.- A node that is up or not, and whether
repmgrdis running, are not visible from one database; runkbdiag statuson each node. - The conninfo is not collected: it may contain a password.
Standby
The standby’s repmgr metadata is replicated from the primary, so the node table and the events are the same. Only the comparison line is its own. cluster.detached is judged on the primary only.
An explicit -d
When you give -d yourself, kbdiag trusts it. Database test has no repmgr schema, so both probes are skipped, which makes the verdict UNKNOWN (without -d the same absence would be not_applicable: not a repmgr cluster, or the metadata is somewhere else). The text says where repmgr keeps its data by default.
A standby that is not attached
cluster.detached(WARN): repmgr still listsnode2as an active standby of this node, but no walsender here has that application name, so it is not attached. repmgr would report it so too, unless the standby’s conninfo sets anotherapplication_name. The text linenot attached here: node2is the same fact.- The newest event,
child_node_disconnectat 17:22:20, is repmgr’s own record of the same thing. - It is a WARN: the business is not hit yet. Whether it is really down or only stuck shows only on the node itself, so the
verifylines say to runkbdiag statusonnode2andkbdiag slotshere.
WARN findings not produced in the lab
Taken from the source, not captured. All are WARN, because repmgr acts on its metadata and a mismatch is a risk, but the business is not affected yet; whether it is a real split brain shows only on each node.
cluster.primaries: repmgr lists more than one active primary. Its metadata says split brain; runkbdiag statuson each to see which one is out of recovery.cluster.inactive: repmgr marks a node inactive, counted as failed or removed, so the cluster has one node less to fail over to.cluster.role_mismatch: repmgr says this node is a primary (or standby), but the database is the other.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.15 - top-objects: largest tables and indexes
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. Nothing was injected. Values belong to this capture only.
Usage
| Option | Effect |
|---|---|
--limit N | Show at most N rows per list; default 20, 0 for all |
--json | Print JSON |
Connection options are the same as for sessions and it looks at the database you are connected to. This command only shows; it has no findings.
Primary
tablesordered by total size.totalis the heap, indexes and TOAST added together:total = heap + indexes + toast. The split answers “is it big because of data, indexes or large columns”, and the remedies differ.heapispg_table_sizeminus TOAST, so it includes the free space and visibility maps. A TOAST table and its index are not listed on their own; they count under the parent table.- Sizes are exact, not
relpagesestimates (the opposite offreeze): this command’s whole point is “who takes space now”, and after a bulk load without ANALYZErelpagesis stale. The cost is that the size functions take anAccessShareLock, so when a table is held by something like VACUUM FULL the whole probe isskippedatlock_timeoutwith the reason written out, instead of showing partial results. A table dropped while the query runs reads NULL and is filtered out, so an ETL that creates and drops temporary tables does not fail the probe. rows (est.)isreltuples.indexeslists the largest indexes with the table they belong to. Size is the whole point, so the usage count is onkbdiag table <name>.- Partitions are not summed up to their parent.
- No judgment: how big is too big has no objective line. So the verdict is OK only when everything was collected; an uncollected probe makes it UNKNOWN.
- The lab shows the bulk of the space in tables created for tests (
orders,flight_psg_new_info,kbdiag_test).
Standby and without privileges
The standby and a kbdiag_ro account (no monitoring role, over 127.0.0.1) return the same rows as the primary in the lab; only the first line differs (role, user, time). Sizes need no privilege, so nothing is hidden. The standby capture is from kes-node2, the kbdiag_ro one from kes-node1.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.16 - table: one table
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. Two WARN/OK examples were created by injection scripts: public.kbdiag_inj_dead (table with autovacuum_enabled=off, 10000 rows inserted and 5000 deleted) and public.kbdiag_inj_tbl (20000 rows inserted, 5000 deleted, with a primary key and a second index). Values belong to this capture only.
Usage
<name> follows SQL rules: it is resolved with to_regclass (as a bound parameter), so an unquoted name folds to lower case and is found through the search_path, the same as in ksql. Quote it as in SQL for upper case: '"Name"'. Connection options are the same as for sessions and it looks at the database you are connected to (use -d for another).
An empty name is a usage error (exit code 64). A name that cannot be found is UNKNOWN (3), not a usage error, because the table may live in another database.
A table on the primary
sizeis exact and split into heap, indexes and TOAST. It uses the same definition astop-objects, so the same table shows the same numbers on both. Resolving the name takes no lock, and the size is its own probe: if someone holds the table with VACUUM FULL, only the size is lost (afterlock_timeout), the rest still shows, and the text points you tokbdiag locks.rowshas the estimate (reltuples) beside the live and dead tuples from the statistics.xid ageis the freeze age againstautovacuum_freeze_max_age.vacuum and analyze: dead tuples against the autovacuum trigger line, the last manual and automatic vacuum and analyze with their counts, and the changes since the last analyze.access since the statistics reset: scans, rows read and written, and block cache hits, one decimal place so that a few disk reads are not rounded to 100%.indexeshas size, scan count, whether it is the primary key, and the definition.- The judgment is the same as in the other commands, not a new rule: the age against the same two lines as
freeze(finding idfreeze.table_age), and a table with autovacuum off that is past its line asvacuum.table_disabled. Neither fires here, so it is OK.
The same table on the standby
Table statistics are local to each node, so statistics is not_applicable and the index scan counts are - (NULL, not 0). The size, row estimate and age are replicated and still shown, and the age is still judged.
A table with autovacuum off
- The finding is the same one
kbdiag vacuumraises for this table:vacuum.table_disabled, with the samefix.reloptionsshows the cause,autovacuum_enabled=off, and the text saysdue (autovacuum is off for this table). modified since analyzeis 5000, matching the 5000 deleted rows.
A table past its line with autovacuum on
5000 dead tuples against a threshold of 4050, so it says due, but autovacuum is on for this table, so the next autovacuum round will take it: no finding, verdict OK.
A table that does not exist
The first line is stderr. It says that unquoted names fold to lower case and that -d selects another database. Exit code 3 (UNKNOWN), and the report body only has table: not found.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 1 | WARN: freeze.table_age past autovacuum_freeze_max_age, or vacuum.table_disabled (autovacuum off and past its line) |
| 2 | FAIL: freeze.table_age at the stop limit (not produced in the lab) |
| 3 | UNKNOWN: data not collected, or the table was not found |
| 64 | Usage error, including an empty name |
| 69 | Cannot connect |
5.17 - top: cumulative top SQL
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. Nothing was injected. In the lab sys_stat_statements.track is none on both nodes, so statements are not being collected and top has no rows to show. This page shows that case, which is what the lab really looks like. The case with statistics on was not captured (see the last section).
Usage
| Option | Effect |
|---|---|
--limit N | Show at most N statements; default 20, 0 for all |
--by ORDER | Sort by time (total execution time, the default), mean (time per call), calls, io (blocks read) or temp (temp blocks written). It is a sort, not a filter |
--json | Print JSON |
Anything else for --by is a usage error (exit code 64). Connection options are the same as for sessions.
Statements not being collected
skippedwith the reason written out, and the verdict is UNKNOWN: an empty table with an OK would claim “no expensive SQL” when nothing was measured. The standby (kes-node2) prints the same.- The switch to turn it on is
sys_stat_statements.track(toporall). Two other reasons get their own text: the library is not inshared_preload_libraries(needs a restart), or the extension is not installed in this database (CREATE EXTENSION sys_stat_statements, or use-d). - The view is read from the schema where
sys_extensionsays the extension lives, not through thesearch_path: a view of the same name inpubliccould otherwise feed kbdiag false numbers. - This connection’s
track=nonedoes not by itself mean no data (a role or database can set it, andsave=onkeeps old data), so kbdiag reads the view anyway and writesskippedonly when there is no row at all.
With statistics on: not captured
Taken from the source, not captured: with statements collected, the output is a table with the columns total, share, calls, mean, rows, read, temp, user, database and query, under a header that says the numbers are cumulative since the last reset.
shareis the statement’s part of all execution time, the direct answer to “where does the database’s time go”. Which SQL is too expensive has no objective line, sotoponly shows and has no findings.- The numbers are cumulative since the last statistics reset. This version has no
sys_stat_statements_info, so the reset time cannot be read, and the header says so.readandtempare blocks. - With
track=all, statements run inside functions count again inside their callers, so the shares add up to more than 100%, and the text says so. Withtrack=noneand rows present, it says they were collected earlier, or for roles and databases that settrackthemselves. - Times use the unit that fits the size (0.09 ms, 503 ms, 1.75 s). A query that this account may not see is
?. top --interval(the last N seconds) is not part of this version.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK: statements collected |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error, including an invalid --by |
| 69 | Cannot connect |
5.18 - progress: long operations
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. The running example was created by an injection script that ran CREATE INDEX on public.orders (1,000,000 rows) and was read three times while it ran, once by kbdiag_ro. PIDs and timings belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions.
Nothing running
The text says which operations it looks at. It also says what it cannot: ANALYZE and base backups have no progress view in this version. The standby (kes-node2) printed the same line.
- kbdiag merges three PostgreSQL progress views (VACUUM, CREATE INDEX, CLUSTER / VACUUM FULL) into one probe, since the reader asks “how far is the long operation” and does not care which view it is in. KingbaseES’s own checkpoint view is a separate probe, because only its column names were observed and a failure there must not take the other three with it.
CREATE INDEX in progress
A few seconds later the same build:
phaseis the operation’s own phase, andprogressis done / total with its unit and a percentage. The counter is chosen by phase: block counts stop at the end of the scan, and the following phases (vacuum’s cleanup, the index’s sort and load) count something else, otherwise the display would sit at 100% for a long time. 100% in the second capture is the end of the table scan, not of the index build.runningis how long the operation has been running.- It only shows. How long is slow has no objective line, and whether a vacuum should run is judged by
kbdiag vacuum. When aCREATE INDEX CONCURRENTLYis still waiting for other transactions, the phase is followed by how many are left and the text points tokbdiag locks, which is the usual reason it looks stuck.
Without privileges
kbdiag_ro (no monitoring role, over 127.0.0.1) reads the same build:
The operation is visible, but phase is hidden (?), so progress, running time and relation are not shown, and the label reads CREATE INDEX or REINDEX. The hidden cells are recorded under redacted. Because something could not be seen, the verdict is UNKNOWN even though the operation was found. Granting sys_monitor lifts it.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.19 - checkpoint: checkpoint activity
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. Nothing was injected. Values belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions.
Primary
last checkpointis the time and theredoposition (with its WAL file) of the latest checkpoint.checkpointscounts timed and requested checkpoints since the statistics reset. “Requested” is not only WAL volume: a manualCHECKPOINT, a base backup (including a repmgr clone), a promotion, and creating or dropping a database count too. kbdiag gives the share but draws no conclusion. The server’s own “checkpoints too frequent” warning looks at the interval between two checkpoints (checkpoint_warning), which cumulative counters cannot give.buffers writtensplits dirty-buffer writes into three: the checkpointer, the background writer, and “backends and others”. The third is not “the checkpointer cannot keep up”: in PG12buffers_backendalso counts relation extension, the ring buffers of VACUUM and COPY, and the standby’s startup process, so an idle node can show 60-70% there (65% and 70% were measured in this lab).backend fsyncsabove 0 is shown, not reported as a WARN. It means an fsync request could not be handed to the checkpointer, which happens when the queue is full or the checkpointer was not running at that moment (9 was measured earlier on this lab’s node2, which kbha had restarted).settingsis the relevant parameters.- Display only, so any probe that is not collected makes the verdict UNKNOWN; OK means “all collected”, not “good numbers”.
Standby
- On a standby these are not restartpoint counts. In PG12 the checkpointer adds one for every attempt, and with no new checkpoint record it tries every 15 seconds (over four days that was 15917 attempts on this lab’s node2), so the text says
restartpoint attemptsand gives no ratio. - The header says the checkpoint record the last restartpoint started from was written by the primary: the time is the primary’s.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.20 - wal: WAL position and what keeps it
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. Nothing was injected. Values belong to this capture only.
Usage
No options of its own. Connection options are the same as for sessions.
Primary
positionis the current write position and its WAL file.sys_walis the same size as onspace: same probe, same columns.what keeps WAL hereputs on one screen what you would otherwise assemble from three commands: the parameters (max_wal_size,wal_keep_segmentsas its size), each replication slot with the WAL it retains and whether it is active, and the archive backlog. Here the slot retains 0 bytes and is active, but 38.readyfiles wait for archiving (the lab’s archiving is failing, seearchive).- It shows and does not repeat the judgment. An inactive slot is reported by
kbdiag slotsand a failing archive bykbdiag archive; this screen only points there. The same problem reported in three commands would look like three problems. - The WAL generation rate needs two samples over time; a single query cannot give it, so this version does not.
Standby
On a standby position is the replay position (there is no write position, and no WAL file name). This standby has no slots of its own, and nothing waits for archiving.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |
5.21 - seq: sequences
Captured on 2026-10-07 (UTC+08) on a local KingbaseES V008R006C009B0014 primary and standby, database test, using ~/kbdiag built from Go source commit 96c874f (static linux/amd64 binary). This is lab evidence, not production validation. The FAIL example was created by an injection script: it created public.kbdiag_inj_seq with maxvalue 3 and used it up with nextval. Values belong to this capture only.
Usage
| Option | Effect |
|---|---|
--limit N | Show at most N sequences; default 20, 0 for all |
--json | Print JSON |
Connection options are the same as for sessions and it looks at the database you are connected to. --limit only trims the list; the finding covers every sequence.
Primary
- The sequences are ordered by how much of their range is used.
lastis the last value handed out,limitthe end of the range it counts toward (its ownMAXVALUE, orMINVALUEfor a descending one),usedthe share,lefthow many values remain. The arithmetic is exact (math/big), since a bigint sequence spans the whole int64 range and plain subtraction would overflow. - A sequence that has never been called, or was reset with
setval(..., false), has no last value: it is shown asno value yet, with-. - Only one thing is judged:
seq.exhausted(FAIL), whennextvalalready fails. A sequence that merely runs low is shown, not reported: how low is too low has no objective line. A sequence that cycles does not count as used up; it would be written ascycles(not captured in the lab).
Standby
A standby’s sequence value is the copy in the WAL, and the primary writes 32 values ahead each time, so a standby can read “at the end” while the primary still has values. The text says so (up to 32 values ahead of the primary), and the standby is not judged: run seq on the primary. The last values above are indeed larger than the primary’s.
Without privileges
kbdiag_ro (no monitoring role, over 127.0.0.1):
?is alast_valuethis account may not read (no SELECT on the sequence), sousedandleftcannot be computed. The SQL checks the privilege by oid, so a sequence dropped meanwhile does not fail the whole probe. The 14 hidden rows are inredacted, with the advice to grantSELECTon the sequences:sys_monitordoes not cover them.- The two rows with
-are sequences never called (no value yet): that is not the same NULL as a privilege gap, and kbdiag keeps the two apart. - The verdict is UNKNOWN, because it could not see the values.
An exhausted sequence
seq.exhaustedis a FAIL:nextvalfails, so inserts that use it already fail. The line is the sequence’s own limit, so there is no option for it.- The
fixdepends on what blocks it. Here the sequence stops at its ownMAXVALUE3, short of whatintegerallows, so it raises that. When the sequence is at the limit of its type, an int or smallint becomes bigint (change the column first; this rewrites the table), and a bigint has no remedy but a new key. A sequence with a mismatched bigint sequence on an int column, where the column overflows first, is not detected.
The same database on the standby:
Same 100.00% row, and the standby still says OK because it does not judge sequences.
Exit codes
| Code | Meaning |
|---|---|
| 0 | OK |
| 2 | FAIL: a sequence cannot hand out another value |
| 3 | UNKNOWN: data not collected or not visible |
| 64 | Usage error |
| 69 | Cannot connect |