postgres-extras-clj example with pgbench

postgres-extras-clj is a Clojure toolbox for inspecting and diagnosing PostgreSQL databases.

In this notebook, we’ll cover the majority of the high-level API, demonstrating some useful fns based on the postgres system catalogs.

This notebook is written in a Clojure namespace and converted to HTML using Clay.

(ns pgbench-tutorial
  "This is a demonstration of postgres-extras-clj using the next.jdbc adapter
  to access a pgbench database."
  (:require
   [hugsql.adapter.next-jdbc :as next-adapter]
   [hugsql.core :as hugsql]
   [next.jdbc :as jdbc]
   [postgres-extras-clj.core :as pgex]
   [scicloj.kindly.v4.kind :as kind]))

Utility functions

(defn show
  "Given a sequence of maps (records),
  Kindly show me a table with some nice UI elements."
  [f]
  (if (empty? f)
    '()
    (let [ncol (count (first f))
          nrow (count f)]
      (kind/table f
                  {:use-datatables true
                   :datatables     {:paging     (< 12 nrow)
                                    :scrollY    false
                                    :scrollX    (< 6 ncol)
                                    :pageLength 12}}))))
(defn show-public
  "Given a sequence of maps (records),
  filter for non-system objects, then show it."
  [f]
  (show
   (filter #(not (:system_object %)) f)))

Database benchmark

We’ll use pgbench to get a database populated with interesting tables:

Pgbench is a simple program for running benchmark tests on PostgreSQL. It runs the same sequence of SQL commands over and over, possibly in multiple concurrent database sessions, and then calculates the average transaction rate (transactions per second) . By default, pgbench tests a scenario that is loosely based on TPC-B, involving five SELECT, UPDATE, and INSERT commands per transaction.

To initialize

 PGPASSWORD=password pgbench --host localhost --port 5432 --username postgres -s 10 -F 100 -i -I dtgvpf main

Then run the benchmarks

 PGPASSWORD=password pgbench --host localhost --port 5432 --username postgres --client 32 --transactions 1000 --jobs 8 main

Driver Setup

We define a JDBC datasource from a map

(def db
  (jdbc/get-datasource
   {:dbtype "postgresql" :dbname "main" :host "localhost" :port 5432 :user "postgres" :password "password"}))

or from a JDBC URI.

(def db2
  (jdbc/get-datasource
   "jdbc:postgresql://localhost:5432/main?user=postgres&password=password"))

Independently, we need to tell hugsql to expect next-jdbc.

(hugsql/set-adapter! (next-adapter/hugsql-adapter-next-jdbc))
#object[hugsql.adapter.next_jdbc.HugsqlAdapterNextJdbc 0x237c078c "hugsql.adapter.next_jdbc.HugsqlAdapterNextJdbc@237c078c"]

Do a health check to ensure connectivity

(pgex/health-check db)
{:now #inst "2026-10-26T01:20:31.990603000-00:00",
 :version
 "PostgreSQL 16.9 (Debian 16.9-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit"}

Settings at a glance

These settings are a subset of the total available via postgresql.conf, but these particular settings are worth paying attention to.

How to tune each of these parameters is beyond the scope of this document. There are plenty of guides online, my recommnedation being PGTune. It provides a simple, web-based tool to generate “optimal” settings depending on your hardware and application.

The default settings that ship with postgres are almost never optimal, they are far too conservative and make poor use of modern hardware. Here’s my configuration tuned for test/dev on my laptop.

(show (pgex/db-settings db))
settingvalueunitshort_desc
checkpoint_completion_target0.75Time spent flushing dirty buffers during checkpoint, as fraction of checkpoint interval.
default_statistics_target100Sets the default statistics target.
effective_cache_size5242888kBSets the planner's assumption about the total size of the data caches.
effective_io_concurrency1Number of simultaneous requests that can be handled efficiently by the disk subsystem.
maintenance_work_mem655360kBSets the maximum memory to be used for maintenance operations.
max_connections40Sets the maximum number of concurrent connections.
max_wal_size1024MBSets the WAL size that triggers a checkpoint.
min_wal_size80MBSets the minimum size to shrink the WAL to.
random_page_cost4Sets the planner's estimate of the cost of a nonsequentially fetched disk page.
shared_buffers1310728kBSets the number of shared memory buffers used by the server.
wal_buffers20488kBSets the number of disk-page buffers in shared memory for WAL.
work_mem40960kBSets the maximum memory to be used for query workspaces.

example row

{:setting "checkpoint_completion_target",
 :value "0.75",
 :unit nil,
 :short_desc
 "Time spent flushing dirty buffers during checkpoint, as fraction of checkpoint interval."}

Full map of database objects, the “data dictionary”

(def dd (pgex/read-data-dictionary db))
(keys dd)
(:partition-children
 :columns
 :functions
 :tables
 :databases
 :schemas
 :partition-parents
 :indexes
 :views)

Or one object, one item at time

List all databases

(show (pgex/databases db))
oiddatabasedb_size_bytesdb_size_prettytable_countsize_in_tables_bytessize_in_tables_prettyextension_count
19699main169038307161 MB72169435136162 MB2

example row

{:oid 19699,
 :database "main",
 :db_size_bytes 169038307,
 :db_size_pretty "161 MB",
 :table_count 72,
 :size_in_tables_bytes 169435136M,
 :size_in_tables_pretty "162 MB",
 :extension_count 2}

List all shemas in current database

(show-public (pgex/schemas db))
size_prettydescriptionview_countschematable_countfunction_countsize_plus_indexes_bytessize_plus_indexes_prettysystem_objectownersize_bytes
132 MBstandard public schema2public43161259520154 MBfalsepg_database_owner138739712

example row

{:size_pretty "132 MB",
 :description "standard public schema",
 :view_count 2,
 :schema "public",
 :table_count 4,
 :function_count 3,
 :size_plus_indexes_bytes 161259520,
 :size_plus_indexes_pretty "154 MB",
 :system_object false,
 :owner "pg_database_owner",
 :size_bytes 138739712}

List all view objects in current database

(show-public (pgex/views db))
size_prettydescriptionschemarowsoidsize_plus_indexes_bytessize_plus_indexes_prettysystem_objectownerview_typeviewsize_bytes
0 bytespublic-16635100 bytesfalsepostgresviewpg_stat_statements_info0
0 bytespublic-16636200 bytesfalsepostgresviewpg_stat_statements0

example row

{:size_pretty "0 bytes",
 :description nil,
 :schema "public",
 :rows -1,
 :oid 66351,
 :size_plus_indexes_bytes 0,
 :size_plus_indexes_pretty "0 bytes",
 :system_object false,
 :owner "postgres",
 :view_type "view",
 :view "pg_stat_statements_info",
 :size_bytes 0}

List all index objects in current database

(show-public (pgex/indexes db))
schematableindextotal_columnsunique_indexindex_size_prettyrows_indexedoidindex_size_bytespartial_indexkey_columnsvalid_indexsystem_objectprimary_key
publicpgbench_branchespgbench_branches_pkey1true16 kB10.06638316384false1truefalsetrue
publicpgbench_tellerspgbench_tellers_pkey1true16 kB100.06638516384false1truefalsetrue
publicpgbench_accountspgbench_accounts_pkey1true21 MB1000000.06638722487040false1truefalsetrue

example row

{:schema "public",
 :table "pgbench_branches",
 :index "pgbench_branches_pkey",
 :total_columns 1,
 :unique_index true,
 :index_size_pretty "16 kB",
 :rows_indexed 10.0,
 :oid 66383,
 :index_size_bytes 16384,
 :partial_index false,
 :key_columns 1,
 :valid_index true,
 :system_object false,
 :primary_key true}

List all database column objects

(show-public (pgex/columns db))
descriptionschematablegenerated_columnsource_typecolumndata_typepositionsystem_objectdefault_value
publicpg_stat_statements_infofalseviewdeallocint81false
publicpg_stat_statements_infofalseviewstats_resettimestamptz2false
publicpg_stat_statementsfalseviewuseridoid1false
publicpg_stat_statementsfalseviewdbidoid2false
publicpg_stat_statementsfalseviewtoplevelbool3false
publicpg_stat_statementsfalseviewqueryidint84false
publicpg_stat_statementsfalseviewquerytext5false
publicpg_stat_statementsfalseviewplansint86false
publicpg_stat_statementsfalseviewtotal_plan_timefloat87false
publicpg_stat_statementsfalseviewmin_plan_timefloat88false
publicpg_stat_statementsfalseviewmax_plan_timefloat89false
publicpg_stat_statementsfalseviewmean_plan_timefloat810false
publicpg_stat_statementsfalseviewstddev_plan_timefloat811false
publicpg_stat_statementsfalseviewcallsint812false
publicpg_stat_statementsfalseviewtotal_exec_timefloat813false
publicpg_stat_statementsfalseviewmin_exec_timefloat814false
publicpg_stat_statementsfalseviewmax_exec_timefloat815false
publicpg_stat_statementsfalseviewmean_exec_timefloat816false
publicpg_stat_statementsfalseviewstddev_exec_timefloat817false
publicpg_stat_statementsfalseviewrowsint818false
publicpg_stat_statementsfalseviewshared_blks_hitint819false
publicpg_stat_statementsfalseviewshared_blks_readint820false
publicpg_stat_statementsfalseviewshared_blks_dirtiedint821false
publicpg_stat_statementsfalseviewshared_blks_writtenint822false
publicpg_stat_statementsfalseviewlocal_blks_hitint823false
publicpg_stat_statementsfalseviewlocal_blks_readint824false
publicpg_stat_statementsfalseviewlocal_blks_dirtiedint825false
publicpg_stat_statementsfalseviewlocal_blks_writtenint826false
publicpg_stat_statementsfalseviewtemp_blks_readint827false
publicpg_stat_statementsfalseviewtemp_blks_writtenint828false
publicpg_stat_statementsfalseviewblk_read_timefloat829false
publicpg_stat_statementsfalseviewblk_write_timefloat830false
publicpg_stat_statementsfalseviewtemp_blk_read_timefloat831false
publicpg_stat_statementsfalseviewtemp_blk_write_timefloat832false
publicpg_stat_statementsfalseviewwal_recordsint833false
publicpg_stat_statementsfalseviewwal_fpiint834false
publicpg_stat_statementsfalseviewwal_bytesnumeric35false
publicpg_stat_statementsfalseviewjit_functionsint836false
publicpg_stat_statementsfalseviewjit_generation_timefloat837false
publicpg_stat_statementsfalseviewjit_inlining_countint838false
publicpg_stat_statementsfalseviewjit_inlining_timefloat839false
publicpg_stat_statementsfalseviewjit_optimization_countint840false
publicpg_stat_statementsfalseviewjit_optimization_timefloat841false
publicpg_stat_statementsfalseviewjit_emission_countint842false
publicpg_stat_statementsfalseviewjit_emission_timefloat843false
publicpgbench_historyfalsetabletidint41false
publicpgbench_historyfalsetablebidint42false
publicpgbench_historyfalsetableaidint43false
publicpgbench_historyfalsetabledeltaint44false
publicpgbench_historyfalsetablemtimetimestamp5false
publicpgbench_historyfalsetablefillerbpchar6false
publicpgbench_tellersfalsetabletidint41false
publicpgbench_tellersfalsetablebidint42false
publicpgbench_tellersfalsetabletbalanceint43false
publicpgbench_tellersfalsetablefillerbpchar4false
publicpgbench_accountsfalsetableaidint41false
publicpgbench_accountsfalsetablebidint42false
publicpgbench_accountsfalsetableabalanceint43false
publicpgbench_accountsfalsetablefillerbpchar4false
publicpgbench_branchesfalsetablebidint41false
publicpgbench_branchesfalsetablebbalanceint42false
publicpgbench_branchesfalsetablefillerbpchar3false

example row

{:description nil,
 :schema "public",
 :table "pg_stat_statements_info",
 :generated_column false,
 :source_type "view",
 :column "dealloc",
 :data_type "int8",
 :position 1,
 :system_object false,
 :default_value nil}

List all table objects in current database

(show-public (pgex/tables db))
size_prettydescriptionschematablerowsoiddata_typesize_plus_indexes_bytessize_plus_indexes_prettysystem_objectownersize_bytesbytes_per_row
192 kBpublicpgbench_branches1066376table212992208 kBfalsepostgres19660819661
216 kBpublicpgbench_tellers10066370table237568232 kBfalsepostgres2211842212
130 MBpublicpgbench_accounts100000066373table158932992152 MBfalsepostgres136445952136
1832 kBpublicpgbench_history-166367table18759681832 kBfalsepostgres1875968

example row

{:size_pretty "192 kB",
 :description nil,
 :schema "public",
 :table "pgbench_branches",
 :rows 10,
 :oid 66376,
 :data_type "table",
 :size_plus_indexes_bytes 212992,
 :size_plus_indexes_pretty "208 kB",
 :system_object false,
 :owner "postgres",
 :size_bytes 196608,
 :bytes_per_row 19661}

List all function objects in current database

(show-public (pgex/functions db))
descriptionschemasource_codeproc_securityresult_data_typesfunctionaccess_privilegesargument_data_typesproc_languagesystem_objectowner
publicpg_stat_statements_reset_1_7invokervoidpg_stat_statements_resetpostgres=X/postgresuserid oid DEFAULT 0, dbid oid DEFAULT 0, queryid bigint DEFAULT 0cfalsepostgres
publicpg_stat_statements_infoinvokerrecordpg_stat_statements_infoOUT dealloc bigint, OUT stats_reset timestamp with time zonecfalsepostgres
publicpg_stat_statements_1_10invokerSETOF recordpg_stat_statementsshowtext boolean, OUT userid oid, OUT dbid oid, OUT toplevel boolean, OUT queryid bigint, OUT query text, OUT plans bigint, OUT total_plan_time double precision, OUT min_plan_time double precision, OUT max_plan_time double precision, OUT mean_plan_time double precision, OUT stddev_plan_time double precision, OUT calls bigint, OUT total_exec_time double precision, OUT min_exec_time double precision, OUT max_exec_time double precision, OUT mean_exec_time double precision, OUT stddev_exec_time double precision, OUT rows bigint, OUT shared_blks_hit bigint, OUT shared_blks_read bigint, OUT shared_blks_dirtied bigint, OUT shared_blks_written bigint, OUT local_blks_hit bigint, OUT local_blks_read bigint, OUT local_blks_dirtied bigint, OUT local_blks_written bigint, OUT temp_blks_read bigint, OUT temp_blks_written bigint, OUT blk_read_time double precision, OUT blk_write_time double precision, OUT temp_blk_read_time double precision, OUT temp_blk_write_time double precision, OUT wal_records bigint, OUT wal_fpi bigint, OUT wal_bytes numeric, OUT jit_functions bigint, OUT jit_generation_time double precision, OUT jit_inlining_count bigint, OUT jit_inlining_time double precision, OUT jit_optimization_count bigint, OUT jit_optimization_time double precision, OUT jit_emission_count bigint, OUT jit_emission_time double precisioncfalsepostgres

example row

{:description nil,
 :schema "public",
 :source_code "pg_stat_statements_reset_1_7",
 :proc_security "invoker",
 :result_data_types "void",
 :function "pg_stat_statements_reset",
 :access_privileges "postgres=X/postgres",
 :argument_data_types
 "userid oid DEFAULT 0, dbid oid DEFAULT 0, queryid bigint DEFAULT 0",
 :proc_language "c",
 :system_object false,
 :owner "postgres"}

Full map of diagnostic statistics

(def stats (pgex/read-stats db {:limit 100}))
(keys stats)
(:duplicate-indexes
 :db-settings
 :locks
 :vacuum-stats
 :index-usage
 :total-index-size
 :cache-hit
 :records-rank
 :null-indexes
 :index-cache-hit
 :all-locks
 :outliers
 :long-running-queries
 :extensions
 :total-table-size
 :unused-indexes
 :bloat
 :calls
 :table-size
 :connections
 :table-cache-hit
 :table-indexes-size
 :blocking
 :seq-scans
 :index-size)

Dead rows and whether an automatic vacuum is expected to be triggered

(show (pgex/vacuum-stats db))
schematablelast_vacuumlast_autovacuumrowsdead_rowsautovacuum_thresholdexpect_autovacuum
publicpgbench_branches2026-10-25 19:2010.0134450.2yes
publicpgbench_tellers2026-10-25 19:20100.0206852.0yes
publicpgbench_accounts2026-10-25 19:201000000.02404820050.0yes
publicpgbench_history2026-10-25 19:20-1.0049.98

example row

{:schema "public",
 :table "pgbench_branches",
 :last_vacuum "2026-10-25 19:20",
 :last_autovacuum nil,
 :rows 10.0,
 :dead_rows 1344,
 :autovacuum_threshold 50.2,
 :expect_autovacuum "yes"}

Index hit rate (effective databases are at 99% and up)

(show (pgex/index-usage db))
schematableindex_usage_ratiorows
publicpgbench_accounts0.99995833506937221000000
publicpgbench_history32000
publicpgbench_tellers0.9974375800756227100
publicpgbench_branches0.484564429446319210

example row

{:schema "public",
 :table "pgbench_accounts",
 :index_usage_ratio 0.9999583350693722,
 :rows 1000000}

Total size of all indexes in MB

(pgex/total-index-size db)
[{:size 22519808}]

example row

{:size 22519808}

Index and table hit rate

(show (pgex/cache-hit db))
metricratio
index hit rate0.9935828152975972
table hit rate0.9790910081743869

example row

{:metric "index hit rate", :ratio 0.9935828152975972}

All tables and the number of rows in each ordered by number of rows descending

(show (pgex/records-rank db))
schematablerows
publicpgbench_accounts1000000
publicpgbench_history32000
publicpgbench_tellers100
publicpgbench_branches10

example row

{:schema "public", :table "pgbench_accounts", :rows 1000000}

Calculates your cache hit rate for reading indexes

(show (pgex/index-cache-hit db))
schemaindexbuffer_hitsblock_readstotal_readratio
publicpgbench_history0000.0
publicpgbench_tellers644382644400.9999689633767846
publicpgbench_branches314302314320.9999363705777552
publicpgbench_accounts32976427453325090.9917445843571151

example row

{:schema "public",
 :index "pgbench_history",
 :buffer_hits 0,
 :block_reads 0,
 :total_read 0,
 :ratio 0.0}

Queries that have longest execution time in aggregate.

Dispatches to a version-appropriate query because PostgreSQL 17 renamed pg_stat_statements.blk_read_time/blk_write_time into shared/local/temp read/write time columns.

(show (pgex/outliers db {:limit 10}))
queryexec_time_msprop_exec_timecallssync_io_time_ms
UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $249473.1950.690894794065738320000.000
UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $236610.3320.29087308131532197320000.000
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)2424.6260.007300031640973953320000.000
UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $21168.2370.0035173112436317922320000.000
copy pgbench_accounts from stdin with (freeze on)506.1780.001523992506812821610.000
COPY adsb_message (icao24, callsign, altitude_ft, speed_kt, heading_deg, vertical_rate_fpm, latitude, longitude, rssi_db, on_ground, emergency, wake_vortex, pressure_setting_mbar, selected_heading_deg) FROM STDIN (FORMAT binary)382.7520.00115238470326850968330.000
COPY adsb_message (icao24, timestamp, callsign, altitude_ft, speed_kt, heading_deg, vertical_rate_fpm, latitude, longitude, rssi_db, on_ground, emergency, wake_vortex, pressure_setting_mbar, selected_heading_deg) FROM STDIN (FORMAT binary)311.3249.373307548520495E-46300.000
SELECT abalance FROM pgbench_accounts WHERE aid = $1265.8668.00466901411818E-4320000.000
alter table pgbench_accounts add primary key (aid)161.2794.8557520983859477E-410.000
alter table pgbench_accounts add constraint pgbench_accounts_bid_fkey foreign key (bid) references pgbench_branches88.8462.674967818775456E-410.000

example row

{:query
 "UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2",
 :exec_time_ms 49473.195M,
 :prop_exec_time 0.6909331621733533,
 :calls 32000,
 :sync_io_time_ms 0.000M}

Available and installed extensions

(show (pgex/extensions db))
extensiondefault_versioninstalled_versiondescription
plpgsql1.01.0PL/pgSQL procedural language
pg_stat_statements1.101.10track planning and execution statistics of all SQL statements executed
insert_username1.0functions for tracking who changed a table
xml21.1XPath querying and XSLT
intarray1.5functions, operators, and index support for 1-D arrays of integers
uuid-ossp1.1generate universally unique identifiers (UUIDs)
dict_int1.0text search dictionary template for integers
old_snapshot1.0utilities in support of old_snapshot_threshold
lo1.1Large Object maintenance
unaccent1.1text search dictionary that removes accents
pageinspect1.12inspect the contents of database pages at a low level
bloom1.0bloom access method - signature file based index
pg_walinspect1.1functions to inspect contents of PostgreSQL Write-Ahead Log
adminpack2.1administrative functions for PostgreSQL
cube1.5data type for multidimensional cubes
tcn1.0Triggered change notifications
pg_trgm1.6text similarity measurement and index searching based on trigrams
tsm_system_time1.0TABLESAMPLE method which accepts time in milliseconds as a limit
hstore1.8data type for storing sets of (key, value) pairs
btree_gist1.7support for indexing common datatypes in GiST
dict_xsyn1.0text search dictionary template for extended synonym processing
tablefunc1.0functions that manipulate whole tables, including crosstab
pgrowlocks1.2show row-level locking information
amcheck1.3functions for verifying relation integrity
isn1.2data types for international product numbering standards
btree_gin1.3support for indexing common datatypes in GIN
autoinc1.0functions for autoincrementing fields
pg_prewarm1.2prewarm relation data
fuzzystrmatch1.2determine similarities and distance between strings
citext1.6data type for case-insensitive character strings
postgres_fdw1.1foreign-data wrapper for remote PostgreSQL servers
pgstattuple1.5show tuple-level statistics
dblink1.2connect to other PostgreSQL databases from within a database
file_fdw1.0foreign-data wrapper for flat file access
moddatetime1.0functions for tracking last modification time
ltree1.2data type for hierarchical tree-like structures
pg_freespacemap1.2examine the free space map (FSM)
pg_visibility1.2examine the visibility map (VM) and page-level visibility info
pg_surgery1.0extension to perform surgery on a damaged relation
pgcrypto1.3cryptographic functions
sslinfo1.2information about SSL certificates
earthdistance1.2calculate great-circle distances on the surface of the Earth
refint1.0functions for implementing referential integrity (obsolete)
tsm_system_rows1.0TABLESAMPLE method which accepts number of rows as a limit
pg_buffercache1.4examine the shared buffer cache
intagg1.1integer aggregator and enumerator (obsolete)
seg1.4data type for representing line segments or floating-point intervals

example row

{:extension "plpgsql",
 :default_version "1.0",
 :installed_version "1.0",
 :description "PL/pgSQL procedural language"}

Size of the tables (including indexes), descending by size

(show (pgex/total-table-size db))
schematablesize
publicpgbench_accounts158932992
publicpgbench_history1875968
publicpgbench_tellers237568
publicpgbench_branches212992

example row

{:schema "public", :table "pgbench_accounts", :size 158932992}

Table and index bloat in your database ordered by most wasteful

(show (pgex/bloat db))
typeschemaobject_namebloatwaste
tablepublicpgbench_accounts1.05980160
indexpublicpgbench_branches::pgbench_branches_pkey2.08192
indexpublicpgbench_tellers::pgbench_tellers_pkey2.08192
tablepublicpgbench_branches1.00
tablepublicpgbench_tellers1.00
indexpublicpgbench_accounts::pgbench_accounts_pkey0.20

example row

{:type "table",
 :schema "public",
 :object_name "pgbench_accounts",
 :bloat 1.0M,
 :waste 5980160}

Queries that have the highest frequency of execution.

Dispatches to a version-appropriate query because PostgreSQL 17 renamed pg_stat_statements.blk_read_time/blk_write_time into shared/local/temp read/write time columns.

(show (pgex/calls db {:limit 10}))
queryexec_time_msexec_time_ratiocallssync_io_time_ms
commit11.6973.521543540271427E-5320010.000
begin11.5433.475103532381449E-5320010.000
SELECT abalance FROM pgbench_accounts WHERE aid = $1265.8668.004432245336049E-4320000.000
UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $249473.1950.6908743582027831320000.000
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)2424.6260.007299815714544254320000.000
UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $236610.3320.2908644776285117320000.000
UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $21168.2370.003517207205663694320000.000
START TRANSACTION2.6618.012453375541591E-614660.000
COMMIT0.7462.2464589852287076E-614640.000
COPY adsb_message (icao24, callsign, altitude_ft, speed_kt, heading_deg, vertical_rate_fpm, latitude, longitude, rssi_db, on_ground, emergency, wake_vortex, pressure_setting_mbar, selected_heading_deg) FROM STDIN (FORMAT binary)382.7520.00115235061707178428330.000

example row

{:query "commit",
 :exec_time_ms 11.697M,
 :exec_time_ratio 3.521675353629051E-5,
 :calls 32001,
 :sync_io_time_ms 0.000M}

Size of the tables (excluding indexes), descending by size

(show (pgex/table-size db))
schematablesize
publicpgbench_accounts136445952
publicpgbench_history1875968
publicpgbench_tellers221184
publicpgbench_branches196608

example row

{:schema "public", :table "pgbench_accounts", :size 136445952}

Returns the list of all active database connections

(show (pgex/connections db))
usernameclient_addressapplication_name
postgres172.18.0.1/32PostgreSQL JDBC Driver

example row

{:username "postgres",
 :client_address "172.18.0.1/32",
 :application_name "PostgreSQL JDBC Driver"}

Calculates your cache hit rate for reading tables

(show (pgex/table-cache-hit db))
schematablebuffer_hitsblock_readstotal_readratio
publicpgbench_branches64849306484931.0
publicpgbench_tellers23427002342701.0
publicpgbench_history12862901286291.0
publicpgbench_accounts336082287763648580.9211309605380723

example row

{:schema "public",
 :table "pgbench_branches",
 :buffer_hits 648493,
 :block_reads 0,
 :total_read 648493,
 :ratio 1.0}

Total size of all the indexes on each table, descending by size

(show (pgex/table-indexes-size db))
schematableindex_size
publicpgbench_accounts22487040
publicpgbench_branches16384
publicpgbench_tellers16384
publicpgbench_history0

example row

{:schema "public", :table "pgbench_accounts", :index_size 22487040}

Count of sequential scans by table descending by order

(show (pgex/seq-scans db))
schematablecount
publicpgbench_branches32992
publicpgbench_tellers164
publicpgbench_accounts4
publicpgbench_history4

example row

{:schema "public", :table "pgbench_branches", :count 32992}

The size of indexes, descending by size

(show (pgex/index-size db))
schemaindexsize
publicpgbench_accounts_pkey22487040
publicpgbench_tellers_pkey16384
publicpgbench_branches_pkey16384

example row

{:schema "public", :index "pgbench_accounts_pkey", :size 22487040}

The Kill Switch

The only mutating function in the entire library is a kill switch for closing all connections, vital if you need to take a heavily-used database down for maintenance or emergency.

(pgex/kill-all! db)  ;; use with caution

Diagnostics

Query and and print all default diagnostics

(for [d (pgex/diagnose (pgex/read-stats db))]
  (:message d))
("✓  Passed :bloat public.pgbench_accounts"
 "✓  Passed :bloat public.pgbench_branches::pgbench_branches_pkey"
 "✓  Passed :bloat public.pgbench_tellers::pgbench_tellers_pkey"
 "✓  Passed :bloat public.pgbench_branches"
 "✓  Passed :bloat public.pgbench_tellers"
 "✓  Passed :bloat public.pgbench_accounts::pgbench_accounts_pkey"
 "✓  Passed :index-cache-hit public.pgbench_history"
 "✓  Passed :index-cache-hit public.pgbench_tellers"
 "✓  Passed :index-cache-hit public.pgbench_branches"
 "✓  Passed :index-cache-hit public.pgbench_accounts"
 "! Warning :outliers UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2 Detected slow query\n{:query \"UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2\", :exec_time_ms 49473.195M, :prop_exec_time 0.6908607150115953, :calls 32000, :sync_io_time_ms 0.000M}"
 "✓  Passed :outliers UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2"
 "✓  Passed :outliers INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)"
 "✓  Passed :outliers UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2"
 "✓  Passed :outliers copy pgbench_accounts from stdin with (freeze on)"
 "✓  Passed :outliers COPY adsb_message (icao24, callsign, altitude_ft, speed_kt, heading_deg, vertical_rate_fpm, latitude, longitude, rssi_db, on_ground, emergency, wake_vortex, pressure_setting_mbar, selected_heading_deg"
 "✓  Passed :outliers COPY adsb_message (icao24, timestamp, callsign, altitude_ft, speed_kt, heading_deg, vertical_rate_fpm, latitude, longitude, rssi_db, on_ground, emergency, wake_vortex, pressure_setting_mbar, selected_"
 "✓  Passed :outliers SELECT abalance FROM pgbench_accounts WHERE aid = $1"
 "✓  Passed :outliers alter table pgbench_accounts add primary key (aid)"
 "✓  Passed :outliers alter table pgbench_accounts add constraint pgbench_accounts_bid_fkey foreign key (bid) references pgbench_branches"
 "✓  Passed :table-cache-hit public.pgbench_branches"
 "✓  Passed :table-cache-hit public.pgbench_tellers"
 "✓  Passed :table-cache-hit public.pgbench_history"
 "! Warning :table-cache-hit public.pgbench_accounts Table sees too much block IO relative to buffer cache hit\n{:schema \"public\", :table \"pgbench_accounts\", :buffer_hits 336082, :block_reads 28776, :total_read 364858, :ratio 0.9211309605380723}")

Or just the warnings

(for [w (pgex/diagnose-warnings (pgex/read-stats db))]
  (:message w))
("! Warning :outliers UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2 Detected slow query\n{:query \"UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2\", :exec_time_ms 49473.195M, :prop_exec_time 0.6908266095854058, :calls 32000, :sync_io_time_ms 0.000M}"
 "! Warning :table-cache-hit public.pgbench_accounts Table sees too much block IO relative to buffer cache hit\n{:schema \"public\", :table \"pgbench_accounts\", :buffer_hits 336082, :block_reads 28776, :total_read 364858, :ratio 0.9211309605380723}")

Create your own diagnostics

(def unrealistic-expectations
  {:table-cache-hit
   {:pred    #(> (:ratio %) 0.999)
    :onfalse "The cache hit ratio is not as insanely high as I'd like."
    :idfn    :table}})
(for [w (pgex/diagnose-warnings
         (pgex/read-stats db)
         :diagnostic-fns unrealistic-expectations)]
  (:message w))
("! Warning :table-cache-hit pgbench_accounts The cache hit ratio is not as insanely high as I'd like.\n{:schema \"public\", :table \"pgbench_accounts\", :buffer_hits 336082, :block_reads 28776, :total_read 364858, :ratio 0.9211309605380723}")

Just for fun

(for [m (pgex/mandelbrot db)]
  (:art m))
("             ....................................................................................     "
 "            .......................................................................................   "
 "           .........................................................................................  "
 "          ........................................................................................... "
 "        ....................................................,,,,,,,,,................................."
 "       ................................................,,,,,,,,,,,,,,,,,,............................."
 "      ..............................................,,,,,,,,,,,,,,,,,,,,,,,,.........................."
 "     ............................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,........................"
 "     ..........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,......................"
 "    .........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,...................."
 "   ........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..................."
 "  .......................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,................."
 " .......................................,,,,,,,,,,,,,,,,,,,,,,,,--,,,,,,,,,,,,,,,,,,,,................"
 "......................................,,,,,,,,,,,,,,,,,,,,,,,,,,-+--,,,,,,,,,,,,,,,,,,,..............."
 "....................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----,,,,,,,,,,,,,,,,,,,.............."
 "...................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,--- -----,,,,,,,,,,,,,,,,,............."
 ".................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---++--++,,,,,,,,,,,,,,,,,,............"
 "................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----%++---,,,,,,,,,,,,,,,,,............"
 "..............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----+%----,,,,,,,,,,,,,,,,,,..........."
 ".............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----- %%+----,,,,,,,,,,,,,,,,,,.........."
 "...........................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---%-+%   ----,,,,,,,,,,,,,,,,,,,........."
 "..........................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---+ +##  %+%---,,,,,,,,,,,,,,,,,,........."
 "........................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----#      # +---,,,,,,,,,,,,,,,,,,........"
 ".......................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-------%       %-----,,,,,,,,,,,,,,,,,........"
 ".....................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---------+         ------,,,,,,,,,,,,,,,,,......."
 "....................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----------+@       +-----------,,,,,,,,,,,,......."
 "..................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----@-------++       ++-----------,,,,,,,,,,,,......"
 ".................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--+@% ---+ +@%%@     %%+@+@%------+-,,,,,,,,,,,......"
 "................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----  # ++%               % @-----++--,,,,,,,,,,,....."
 "..............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----+    %                  %%++ %+%@-,,,,,,,,,,,....."
 ".............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----+#                       #%    ++-,,,,,,,,,,,,...."
 "............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------+                             @---,,,,,,,,,,,,...."
 "..........,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-------++%                             ---,,,,,,,,,,,,...."
 ".........,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------+ +                             %+---,,,,,,,,,,,,,..."
 "........,,,,,,,,,,,,,,,,,,,,,--------------------@                                +----,,,,,,,,,,,,..."
 ".......,,,,,,,,,,,,,,,,,,,,,,- +-----------------+                                 ----,,,,,,,,,,,,..."
 ".......,,,,,,,,,,,,,,,,,,,,,--++------+---------+%                                 +++--,,,,,,,,,,,,.."
 "......,,,,,,,,,,,,,,,,,,,,,,--%+-----++---------                                     #+-,,,,,,,,,,,,.."
 ".....,,,,,,,,,,,,,,,,,,,,,,----#%++--+@ -+-----+%                                     --,,,,,,,,,,,,.."
 ".....,,,,,,,,,,,,,,,,,,,,,,-----+## ++@ + +----%                                    +--,,,,,,,,,,,,,.."
 "....,,,,,,,,,,,,,,,,,,,,,,------+@  @     @@++++#                                   +--,,,,,,,,,,,,,.."
 "....,,,,,,,,,,,,,,,,,,,,,-------%           #++%                                      -,,,,,,,,,,,,,.."
 "...,,,,,,,,,,,,,,,,,,,,,------++%#           %%@                                     %-,,,,,,,,,,,,,,."
 "...,,,,,,,,,,,,,,,,,,,--------+               %                                     +--,,,,,,,,,,,,,,."
 "...,,,,,,,,,,,,,,,,,,-----+--++@              #                                      --,,,,,,,,,,,,,,."
 "..,,,,,,,,,,,,,,,,,-------%+++%                                                    @--,,,,,,,,,,,,,,,."
 "..,,,,,,,,,,,-------------+ @#@                                                    ---,,,,,,,,,,,,,,,."
 "..,,,,,,,,,---@--------@-+%                                                       +---,,,,,,,,,,,,,,,."
 "..,,,,,------- +-++++-+%%%                                                       +----,,,,,,,,,,,,,,,."
 "..,,,,,,------%--------++%                                                       +----,,,,,,,,,,,,,,,."
 "..,,,,,,,,,,--+----------++#                                                       ---,,,,,,,,,,,,,,,."
 "..,,,,,,,,,,,,------------+@@@%                                                    +--,,,,,,,,,,,,,,,."
 "..,,,,,,,,,,,,,,,,,------- +++%                                                    %--,,,,,,,,,,,,,,,."
 "...,,,,,,,,,,,,,,,,,,---------+@              @                                      --,,,,,,,,,,,,,,."
 "...,,,,,,,,,,,,,,,,,,,,------- #              %@                                    +--,,,,,,,,,,,,,,."
 "...,,,,,,,,,,,,,,,,,,,,,-------++@           %+                                      %-,,,,,,,,,,,,,,."
 "....,,,,,,,,,,,,,,,,,,,,,-------            %++%                                     %-,,,,,,,,,,,,,.."
 "....,,,,,,,,,,,,,,,,,,,,,,------+#  %#   #@ ++++                                    +--,,,,,,,,,,,,,.."
 ".....,,,,,,,,,,,,,,,,,,,,,,-----+ %%++% +@+----+                                    +--,,,,,,,,,,,,,.."
 ".....,,,,,,,,,,,,,,,,,,,,,,,---%+++--+#+--------%                                    #--,,,,,,,,,,,,.."
 "......,,,,,,,,,,,,,,,,,,,,,,--++-----%%---------                                    @#--,,,,,,,,,,,,.."
 ".......,,,,,,,,,,,,,,,,,,,,,---------------------+@                                +-++,,,,,,,,,,,,..."
 "........,,,,,,,,,,,,,,,,,,,,,--------------------+                                 ----,,,,,,,,,,,,..."
 ".........,,,,,,,,,,,,,,,,,,,,----,,,-------------                                #+----,,,,,,,,,,,,..."
 "..........,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-------+ +                              +---,,,,,,,,,,,,,..."
 "...........,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------+%#                           #---,,,,,,,,,,,,...."
 "............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------+#                        @   @---,,,,,,,,,,,,...."
 ".............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----+#                        +    @--,,,,,,,,,,,,...."
 "..............,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---+%   %+@                 %+-+ +++%-,,,,,,,,,,,....."
 "................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----% %@++              # %  -----++-,,,,,,,,,,,,....."
 ".................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-- ++ ---+ + +%@     %++++++------%-,,,,,,,,,,,......"
 "...................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---- -------++       +------------,,,,,,,,,,,,......"
 "....................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----------+%       +--------,,,,,,,,,,,,,,,......."
 "......................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------+#        -----,,,,,,,,,,,,,,,,,,......."
 ".......................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-------+       #----,,,,,,,,,,,,,,,,,,........"
 ".........................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----+%      %#---,,,,,,,,,,,,,,,,,,,........"
 "..........................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---+%+%@  %+%%--,,,,,,,,,,,,,,,,,,........."
 "............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---+-+%  %----,,,,,,,,,,,,,,,,,,.........."
 ".............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----+%@+---,,,,,,,,,,,,,,,,,,,.........."
 "...............................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----+%----,,,,,,,,,,,,,,,,,,..........."
 "................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,-----%+ +--,,,,,,,,,,,,,,,,,............"
 "..................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,---++----,,,,,,,,,,,,,,,,,............."
 "...................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,---@-----,,,,,,,,,,,,,,,,,............."
 ".....................................,,,,,,,,,,,,,,,,,,,,,,,,,,,-----,,,,,,,,,,,,,,,,,,,.............."
 " .....................................,,,,,,,,,,,,,,,,,,,,,,,,,,--%,,,,,,,,,,,,,,,,,,,,..............."
 " .......................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,................."
 "  ........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.................."
 "   ........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,..................."
 "    .........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,...................."
 "     ..........................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,......................"
 "      ............................................,,,,,,,,,,,,,,,,,,,,,,,,,,,,........................"
 "       .............................................,,,,,,,,,,,,,,,,,,,,,,,,.........................."
 "        ................................................,,,,,,,,,,,,,,,,,............................."
 "         .....................................................,,,,...................................."
 "          ........................................................................................... "
 "           .........................................................................................  "
 "            ......................................................................................    "
 "             ....................................................................................     "
 "               .................................................................................      "
 "                ..............................................................................        "
 "                  ...........................................................................         "
 "                   ........................................................................           ")

To recreate this notebook

The contents of this namespace are periodically rendered and checked into main. Why not as part of the test suite? running a database to make a meaningful tutorial, with commentary about the results, is not trivial to automate.

(comment
  (require '[scicloj.clay.v2.api :as clay])
  (clay/browse!)
  (clay/make! {:source-path      "examples/pgbench_tutorial.clj"
               :format           [:quarto :html]
               :base-target-path "target/clay"}))
source: examples/pgbench_tutorial.clj