This is the multi-page printable view of this section. .
User manual
- 1: sessions: list sessions
- 2: session: one session
- 3: locks: lock waits
- 4: txn: transactions
- 5: waits: wait events
- 6: status: instance overview
- 7: slots: replication slots
- 8: space: disk, WAL, databases
- 9: freeze: wraparound distance
- 10: vacuum: dead tuples and autovacuum
- 11: archive: WAL archiving
- 12: params: changed parameters
- 13: repl: replication from this node
- 14: cluster: repmgr's view
- 15: top-objects: largest tables and indexes
- 16: table: one table
- 17: top: cumulative top SQL
- 18: progress: long operations
- 19: checkpoint: checkpoint activity
- 20: wal: WAL position and what keeps it
- 21: seq: sequences
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
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 |
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 |
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 |
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 - 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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |