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)))
(defn meta-as-header [x]
  (kind/md (str "### " x "\n" (:doc (meta x)))))

Setup

Dependencies

{:deps 
{com.layerware/hugsql {:mvn/version "0.5.3"}
com.layerware/hugsql-adapter-next-jdbc {:mvn/version "0.5.3"}
org.scicloj/clay {:mvn/version "2-beta11"}
org.postgresql/postgresql {:mvn/version "42.7.3"}
seancorfield/next.jdbc {:mvn/version "1.2.659"}}}

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 db-from-uri
  (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 0x6f994d46 "hugsql.adapter.next_jdbc.HugsqlAdapterNextJdbc@6f994d46"]

Do a health check to ensure connectivity

(pgex/health-check db)
{:now #inst "2025-06-29T23:59:43.870451000-00:00",
 :version
 "PostgreSQL 16.9 (Debian 16.9-1.pgdg110+1) on aarch64-unknown-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit"}
(pgex/health-check db-from-uri)
{:now #inst "2025-06-29T23:59:43.878021000-00:00",
 :version
 "PostgreSQL 16.9 (Debian 16.9-1.pgdg110+1) on aarch64-unknown-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))
namesettingunitshort_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

{:name "checkpoint_completion_target",
 :setting "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

#’postgres-extras-clj.core/databases

List all databases

(show (pgex/databases db))
oiddb_namedb_sizetable_countsize_in_tablesextension_count
16384main161 MB72162 MB2

example row

(first (:databases dd))
{:oid 16384,
 :db_name "main",
 :db_size "161 MB",
 :table_count 72,
 :size_in_tables "162 MB",
 :extension_count 2}

#’postgres-extras-clj.core/schemas

List all shemas in current database

(show-public (pgex/schemas db))
size_prettydescriptionview_countsize_plus_indexestable_countfunction_countsize_plus_indexes_bytess_namesystem_objectownersize_bytes
132 MBstandard public schema2154 MB43161226752publicfalsepg_database_owner138706944

example row

(first (:schemas dd))
{:size_pretty "248 kB",
 :description nil,
 :view_count 65,
 :size_plus_indexes "248 kB",
 :table_count 4,
 :function_count 11,
 :size_plus_indexes_bytes 253952,
 :s_name "information_schema",
 :system_object true,
 :owner "postgres",
 :size_bytes 253952}

#’postgres-extras-clj.core/views

List all view objects in current database

(show-public (pgex/views db))
size_prettydescriptionowned_bysize_plus_indexesrowsoidv_namesize_plus_indexes_bytess_namesystem_objectview_typesize_bytes
0 bytespostgres0 bytes-116488pg_stat_statements_info0publicfalseview0
0 bytespostgres0 bytes-116499pg_stat_statements0publicfalseview0

example row

(first (:views dd))
{:size_pretty "0 bytes",
 :description nil,
 :owned_by "postgres",
 :size_plus_indexes "0 bytes",
 :rows -1,
 :oid 16488,
 :v_name "pg_stat_statements_info",
 :size_plus_indexes_bytes 0,
 :s_name "public",
 :system_object false,
 :view_type "view",
 :size_bytes 0}

#’postgres-extras-clj.core/indexes

List all index objects in current database

(show-public (pgex/indexes db))
total_columnsunique_indexrows_indexedoidindex_size_bytesi_namepartial_indexindex_sizekey_columnsvalid_indexs_namesystem_objectprimary_keyt_name
1true10.01644016384pgbench_branches_pkeyfalse16 kB1truepublicfalsetruepgbench_branches
1true100.01644216384pgbench_tellers_pkeyfalse16 kB1truepublicfalsetruepgbench_tellers
1true1000000.01644422487040pgbench_accounts_pkeyfalse21 MB1truepublicfalsetruepgbench_accounts

example row

(first (:indexes dd))
{:total_columns 1,
 :unique_index true,
 :rows_indexed 10.0,
 :oid 16440,
 :index_size_bytes 16384,
 :i_name "pgbench_branches_pkey",
 :partial_index false,
 :index_size "16 kB",
 :key_columns 1,
 :valid_index true,
 :s_name "public",
 :system_object false,
 :primary_key true,
 :t_name "pgbench_branches"}

#’postgres-extras-clj.core/columns

List all database column objects

(show-public (pgex/columns db))
descriptiongenerated_columnsource_typedata_typec_namepositions_namesystem_objectdefault_valuet_name
falsetableint4tid1publicfalsepgbench_history
falsetableint4bid2publicfalsepgbench_history
falsetableint4aid3publicfalsepgbench_history
falsetableint4delta4publicfalsepgbench_history
falsetabletimestampmtime5publicfalsepgbench_history
falsetablebpcharfiller6publicfalsepgbench_history
falsetableint4tid1publicfalsepgbench_tellers
falsetableint4bid2publicfalsepgbench_tellers
falsetableint4tbalance3publicfalsepgbench_tellers
falsetablebpcharfiller4publicfalsepgbench_tellers
falsetableint4aid1publicfalsepgbench_accounts
falsetableint4bid2publicfalsepgbench_accounts
falsetableint4abalance3publicfalsepgbench_accounts
falsetablebpcharfiller4publicfalsepgbench_accounts
falsetableint4bid1publicfalsepgbench_branches
falsetableint4bbalance2publicfalsepgbench_branches
falsetablebpcharfiller3publicfalsepgbench_branches
falseviewint8dealloc1publicfalsepg_stat_statements_info
falseviewtimestamptzstats_reset2publicfalsepg_stat_statements_info
falseviewoiduserid1publicfalsepg_stat_statements
falseviewoiddbid2publicfalsepg_stat_statements
falseviewbooltoplevel3publicfalsepg_stat_statements
falseviewint8queryid4publicfalsepg_stat_statements
falseviewtextquery5publicfalsepg_stat_statements
falseviewint8plans6publicfalsepg_stat_statements
falseviewfloat8total_plan_time7publicfalsepg_stat_statements
falseviewfloat8min_plan_time8publicfalsepg_stat_statements
falseviewfloat8max_plan_time9publicfalsepg_stat_statements
falseviewfloat8mean_plan_time10publicfalsepg_stat_statements
falseviewfloat8stddev_plan_time11publicfalsepg_stat_statements
falseviewint8calls12publicfalsepg_stat_statements
falseviewfloat8total_exec_time13publicfalsepg_stat_statements
falseviewfloat8min_exec_time14publicfalsepg_stat_statements
falseviewfloat8max_exec_time15publicfalsepg_stat_statements
falseviewfloat8mean_exec_time16publicfalsepg_stat_statements
falseviewfloat8stddev_exec_time17publicfalsepg_stat_statements
falseviewint8rows18publicfalsepg_stat_statements
falseviewint8shared_blks_hit19publicfalsepg_stat_statements
falseviewint8shared_blks_read20publicfalsepg_stat_statements
falseviewint8shared_blks_dirtied21publicfalsepg_stat_statements
falseviewint8shared_blks_written22publicfalsepg_stat_statements
falseviewint8local_blks_hit23publicfalsepg_stat_statements
falseviewint8local_blks_read24publicfalsepg_stat_statements
falseviewint8local_blks_dirtied25publicfalsepg_stat_statements
falseviewint8local_blks_written26publicfalsepg_stat_statements
falseviewint8temp_blks_read27publicfalsepg_stat_statements
falseviewint8temp_blks_written28publicfalsepg_stat_statements
falseviewfloat8blk_read_time29publicfalsepg_stat_statements
falseviewfloat8blk_write_time30publicfalsepg_stat_statements
falseviewfloat8temp_blk_read_time31publicfalsepg_stat_statements
falseviewfloat8temp_blk_write_time32publicfalsepg_stat_statements
falseviewint8wal_records33publicfalsepg_stat_statements
falseviewint8wal_fpi34publicfalsepg_stat_statements
falseviewnumericwal_bytes35publicfalsepg_stat_statements
falseviewint8jit_functions36publicfalsepg_stat_statements
falseviewfloat8jit_generation_time37publicfalsepg_stat_statements
falseviewint8jit_inlining_count38publicfalsepg_stat_statements
falseviewfloat8jit_inlining_time39publicfalsepg_stat_statements
falseviewint8jit_optimization_count40publicfalsepg_stat_statements
falseviewfloat8jit_optimization_time41publicfalsepg_stat_statements
falseviewint8jit_emission_count42publicfalsepg_stat_statements
falseviewfloat8jit_emission_time43publicfalsepg_stat_statements

example row

(first (:columns dd))
{:description nil,
 :generated_column false,
 :source_type "table",
 :data_type "oid",
 :c_name "oid",
 :position 1,
 :s_name "pg_catalog",
 :system_object true,
 :default_value nil,
 :t_name "pg_proc"}

#’postgres-extras-clj.core/tables

List all table objects in current database

(show-public (pgex/tables db))
size_prettydescriptionowned_bysize_plus_indexesrowsoiddata_typesize_plus_indexes_bytess_namesystem_objectt_namesize_bytesbytes_per_row
144 kBpostgres160 kB1016433table163840publicfalsepgbench_branches14745614746
240 kBpostgres256 kB10016427table262144publicfalsepgbench_tellers2457602458
130 MBpostgres152 MB100000016430table158924800publicfalsepgbench_accounts136437760136
1832 kBpostgres1832 kB3200016424table1875968publicfalsepgbench_history187596859

example row

(first (:tables dd))
{:size_pretty "144 kB",
 :description nil,
 :owned_by "postgres",
 :size_plus_indexes "160 kB",
 :rows 10,
 :oid 16433,
 :data_type "table",
 :size_plus_indexes_bytes 163840,
 :s_name "public",
 :system_object false,
 :t_name "pgbench_branches",
 :size_bytes 147456,
 :bytes_per_row 14746}

#’postgres-extras-clj.core/functions

List all function objects in current database

(show-public (pgex/functions db))
descriptionf_namesource_codeowned_byproc_securityresult_data_typesaccess_privilegesargument_data_typess_nameproc_languagesystem_object
pg_stat_statements_resetpg_stat_statements_reset_1_7postgresinvokervoidpostgres=X/postgresuserid oid DEFAULT 0, dbid oid DEFAULT 0, queryid bigint DEFAULT 0publiccfalse
pg_stat_statements_infopg_stat_statements_infopostgresinvokerrecordOUT dealloc bigint, OUT stats_reset timestamp with time zonepubliccfalse
pg_stat_statementspg_stat_statements_1_10postgresinvokerSETOF recordshowtext 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 precisionpubliccfalse

example row

(first (:functions dd))
{:description "I/O",
 :f_name "boolin",
 :source_code "boolin",
 :owned_by "postgres",
 :proc_security "invoker",
 :result_data_types "boolean",
 :access_privileges nil,
 :argument_data_types "cstring",
 :s_name "pg_catalog",
 :proc_language "internal",
 :system_object true}

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)

#’postgres-extras-clj.core/vacuum-stats

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

(show (pgex/vacuum-stats db))
schematablelast_vacuumlast_autovacuumrowcountdead_rowcountautovacuum_thresholdexpect_autovacuum
publicpgbench_branches2025-06-27 10:102025-06-27 10:1010.0050.2
publicpgbench_tellers2025-06-27 10:102025-06-27 10:10100.0052.0
publicpgbench_accounts2025-06-27 10:102025-06-27 10:101000000.0020050.0
publicpgbench_history2025-06-27 10:102025-06-27 10:1032000.00690.0

example row

(first (:vacuum-stats stats))
{:schema "public",
 :table "pgbench_branches",
 :last_vacuum "2025-06-27 10:10",
 :last_autovacuum "2025-06-27 10:10",
 :rowcount 10.0,
 :dead_rowcount 0,
 :autovacuum_threshold 50.2,
 :expect_autovacuum nil}

#’postgres-extras-clj.core/index-usage

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

(show (pgex/index-usage db))
schemanamepercent_of_times_index_usedrows_in_table
publicpgbench_accounts991000000
publicpgbench_history32000
publicpgbench_tellers49100
publicpgbench_branches4410

example row

(first (:index-usage stats))
{:schema "public",
 :name "pgbench_accounts",
 :percent_of_times_index_used 99,
 :rows_in_table 1000000}

#’postgres-extras-clj.core/total-index-size

Total size of all indexes in MB

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

example row

(first (:total-index-size stats))
{:size 22519808}

#’postgres-extras-clj.core/cache-hit

Index and table hit rate

(show (pgex/cache-hit db))
nameratio
index hit rate0.99304478510688361337
table hit rate0.98213610603614123501

example row

(first (:cache-hit stats))
{:name "index hit rate", :ratio 0.99304478510688361337M}

#’postgres-extras-clj.core/records-rank

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

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

example row

(first (:records-rank stats))
{:schema "public", :name "pgbench_accounts", :estimated_count 1000000}

#’postgres-extras-clj.core/index-cache-hit

Calculates your cache hit rate for reading indexes

(show (pgex/index-cache-hit db))
schemanamebuffer_hitsblock_readstotal_readratio
publicpgbench_history0000.0
publicpgbench_tellers320532320550.9999376072375604
publicpgbench_branches286262286280.9999301383261143
publicpgbench_accounts33181527453345600.991795193687231

example row

(first (:index-cache-hit stats))
{:schema "public",
 :name "pgbench_history",
 :buffer_hits 0,
 :block_reads 0,
 :total_read 0,
 :ratio 0.0}

#’postgres-extras-clj.core/outliers

Queries that have longest execution time in aggregate.

(show (pgex/outliers db {:limit 10}))
queryexec_time_msprop_exec_timecallssync_io_time_ms
UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $221821.0300.687812220162681320000.000
UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $228824.3410.24230609136281778320000.000
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)3137.1370.02637171958743119320000.000
UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $21084.1350.009113562034819042320000.000
WITH RECURSIVE Z ( IX, IY, CX, CY, X, Y, I ) AS ( SELECT IX, IY, X::float, Y::float, X::float, Y::float, $1 FROM ( SELECT $2 + $3 * i, i FROM generate_series($4, $5) AS i) AS xgen (x, ix), ( SELECT $6 + $7 * i, i FROM generate_series($8, $9) AS i) AS ygen (y, iy) UNION ALL SELECT IX, IY, CX, CY, X * X - Y * Y + CX AS X, Y * X * $10 + CY, I + $11 FROM Z WHERE X * X + Y * Y < $12::float AND I < $13 ) SELECT array_to_string(array_agg(SUBSTRING($14, LEAST (GREATEST (I, $15), $16), $17)), $18) AS art FROM ( SELECT IX, IY, MAX(I) AS I FROM Z GROUP BY IY, IX ORDER BY IY, IX) AS ZT GROUP BY IY ORDER BY IY613.6680.00515867734957243480.000
copy pgbench_accounts from stdin with (freeze on)426.0720.003581687946074551210.000
SELECT n.nspname::text AS s_name, p.proname::text AS f_name, pg_get_function_result(p.oid)::text AS result_data_types, pg_get_function_arguments(p.oid)::text AS argument_data_types, pg_get_userbyid(p.proowner)::text AS owned_by, CASE WHEN p.prosecdef THEN $1::text ELSE $2::text END AS proc_security, array_to_string(p.proacl, $3::text) AS access_privileges, l.lanname::text AS proc_language, p.prosrc::text AS source_code, obj_description(p.oid, $4::name)::text AS description, CASE WHEN n.nspname <> ALL (ARRAY[$5::name, $6::name]) THEN $7 ELSE $8 END AS system_object FROM pg_proc p LEFT JOIN pg_namespace n ON n.oid = p.pronamespace LEFT JOIN pg_language l ON l.oid = p.prolang416.8500.003504163473303969220.000
WITH RECURSIVE Z ( IX, IY, CX, CY, X, Y, I ) AS ( SELECT IX, IY, X::float, Y::float, X::float, Y::float, $1 FROM ( SELECT $2 + $3 * i, i FROM generate_series($4, $5) AS i) AS xgen (x, ix), ( SELECT $6 + $7 * i, i FROM generate_series($8, $9) AS i) AS ygen (y, iy) UNION ALL SELECT IX, IY, CX, CY, X * X - Y * Y + CX AS X, Y * X * $10 + CY, I + $11 FROM Z WHERE X * X + Y * Y < $12::float AND I < $13 ) SELECT array_to_string(array_agg(SUBSTRING($14, LEAST (GREATEST (I, $15), $16), $17)), $18) AS art FROM ( SELECT IX, IY, MAX(I) AS I FROM Z GROUP BY IY, IX ORDER BY IY, IX) AS ZT GROUP BY IY ORDER BY IY315.0330.002648266220919517740.000
SELECT n.nspname::text AS s_name, p.proname::text AS f_name, pg_get_function_result(p.oid)::text AS result_data_types, pg_get_function_arguments(p.oid)::text AS argument_data_types, pg_get_userbyid(p.proowner)::text AS owned_by, CASE WHEN p.prosecdef THEN $1::text ELSE $2::text END AS proc_security, array_to_string(p.proacl, $3::text) AS access_privileges, l.lanname::text AS proc_language, p.prosrc::text AS source_code, obj_description(p.oid, $4::name)::text AS description, CASE WHEN n.nspname <> ALL (ARRAY[$5::name, $6::name]) THEN $7 ELSE $8 END AS system_object FROM pg_proc p LEFT JOIN pg_namespace n ON n.oid = p.pronamespace LEFT JOIN pg_language l ON l.oid = p.prolang237.3870.0019955443496267496120.000
SELECT abalance FROM pgbench_accounts WHERE aid = $1205.8810.001730696812657487320000.000

example row

(first (:outliers stats))
{:query
 "UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2",
 :exec_time_ms 21821.030M,
 :prop_exec_time 0.6878500635089461,
 :calls 32000,
 :sync_io_time_ms 0.000M}

#’postgres-extras-clj.core/extensions

Available and installed extensions

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

example row

(first (:extensions stats))
{:name "plpgsql",
 :default_version "1.0",
 :installed_version "1.0",
 :comment "PL/pgSQL procedural language"}

#’postgres-extras-clj.core/total-table-size

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

(show (pgex/total-table-size db))
schemanamesize
publicpgbench_accounts158924800
publicpgbench_history1875968
publicpgbench_tellers262144
publicpgbench_branches163840

example row

(first (:total-table-size stats))
{:schema "public", :name "pgbench_accounts", :size 158924800}

#’postgres-extras-clj.core/bloat

Table and index bloat in your database ordered by most wasteful

(show (pgex/bloat db))
typeschemanameobject_namebloatwaste
tablepublicpgbench_accounts1.18052736
tablepublicpgbench_tellers26.0204800
tablepublicpgbench_branches14.0106496
indexpg_catalogpg_ts_config_map::pg_ts_config_map_index2.016384
indexpg_catalogpg_amproc::pg_amproc_fam_proc_index1.716384
indexpg_catalogpg_depend::pg_depend_depender_index1.216384
tablepg_catalogpg_class1.216384
indexpg_catalogpg_auth_members::pg_auth_members_member_role_index2.08192
indexpg_catalogpg_auth_members::pg_auth_members_oid_index2.08192
indexpg_catalogpg_auth_members::pg_auth_members_role_member_index2.08192
indexpg_catalogpg_authid::pg_authid_oid_index2.08192
indexpg_catalogpg_authid::pg_authid_rolname_index2.08192
indexpg_catalogpg_cast::pg_cast_oid_index2.08192
indexpg_catalogpg_cast::pg_cast_source_target_index2.08192
indexpg_catalogpg_namespace::pg_namespace_oid_index2.08192
indexpg_catalogpg_tablespace::pg_tablespace_spcname_index2.08192
indexpg_catalogpg_tablespace::pg_tablespace_oid_index2.08192
indexpg_catalogpg_shdescription::pg_shdescription_o_c_index2.08192
indexpg_catalogpg_range::pg_range_rngtypid_index2.08192
indexpg_catalogpg_range::pg_range_rngmultitypid_index2.08192
indexpg_catalogpg_auth_members::pg_auth_members_grantor_index2.08192
indexpg_catalogpg_ts_config::pg_ts_config_oid_index2.08192
indexpg_catalogpg_language::pg_language_name_index2.08192
indexpg_catalogpg_language::pg_language_oid_index2.08192
indexpg_catalogpg_namespace::pg_namespace_nspname_index2.08192
indexpg_catalogpg_ts_config::pg_ts_config_cfgname_index2.08192
indexpublicpgbench_tellers::pgbench_tellers_pkey2.08192
indexpublicpgbench_branches::pgbench_branches_pkey2.08192
indexpg_catalogpg_ts_template::pg_ts_template_tmplname_index2.08192
indexpg_catalogpg_ts_template::pg_ts_template_oid_index2.08192
indexpg_catalogpg_ts_parser::pg_ts_parser_prsname_index2.08192
indexpg_catalogpg_ts_parser::pg_ts_parser_oid_index2.08192
indexpg_catalogpg_am::pg_am_oid_index2.08192
indexpg_catalogpg_am::pg_am_name_index2.08192
indexpg_catalogpg_database::pg_database_datname_index2.08192
indexpg_catalogpg_database::pg_database_oid_index2.08192
indexpg_catalogpg_ts_dict::pg_ts_dict_oid_index2.08192
indexpg_catalogpg_ts_dict::pg_ts_dict_dictname_index2.08192
indexpg_catalogpg_extension::pg_extension_name_index2.08192
indexpg_catalogpg_extension::pg_extension_oid_index2.08192
indexpg_catalogpg_amproc::pg_amproc_oid_index1.38192
indexpg_catalogpg_amop::pg_amop_opr_fam_index1.28192
indexpg_catalogpg_amop::pg_amop_fam_strat_index1.28192
tablepg_catalogpg_depend1.18192
tablepg_catalogpg_description1.08192
indexpg_catalogpg_conversion::pg_conversion_default_index1.00
indexpg_catalogpg_amop::pg_amop_oid_index1.00
indexpg_catalogpg_aggregate::pg_aggregate_fnoid_index1.00
indexpg_catalogpg_conversion::pg_conversion_name_nsp_index1.00
indexpg_catalogpg_conversion::pg_conversion_oid_index1.00
indexpg_catalogpg_init_privs::pg_init_privs_o_c_o_index1.00
indexpg_catalogpg_opfamily::pg_opfamily_am_name_nsp_index1.00
indexpg_catalogpg_opfamily::pg_opfamily_oid_index1.00
tablepg_catalogpg_am1.00
tablepg_catalogpg_amop1.00
tablepg_catalogpg_amproc1.00
tablepg_catalogpg_attribute1.00
tablepg_catalogpg_auth_members1.00
tablepg_catalogpg_authid1.00
tablepg_catalogpg_cast1.00
tablepg_catalogpg_constraint1.00
tablepg_catalogpg_conversion1.00
tablepg_catalogpg_database1.00
tablepg_catalogpg_extension1.00
tablepg_catalogpg_index1.00
tablepg_catalogpg_init_privs1.00
tablepg_catalogpg_language1.00
tablepg_catalogpg_namespace1.00
tablepg_catalogpg_opclass1.00
tablepg_catalogpg_operator1.00
tablepg_catalogpg_opfamily1.00
tablepg_catalogpg_proc1.00
tablepg_catalogpg_range1.00
tablepg_catalogpg_rewrite1.00
tablepg_catalogpg_shdescription1.00
tablepg_catalogpg_tablespace1.00
tablepg_catalogpg_ts_config1.00
tablepg_catalogpg_ts_config_map1.00
tablepg_catalogpg_ts_dict1.00
tablepg_catalogpg_ts_parser1.00
tablepg_catalogpg_ts_template1.00
indexpg_catalogpg_description::pg_description_o_c_o_index0.90
tablepg_catalogpg_type0.90
tablepg_catalogpg_collation0.90
indexpg_catalogpg_depend::pg_depend_reference_index0.90
tablepublicpgbench_history0.80
indexpg_catalogpg_constraint::pg_constraint_oid_index0.70
indexpg_catalogpg_opclass::pg_opclass_oid_index0.70
indexpg_catalogpg_opclass::pg_opclass_am_name_nsp_index0.70
indexpg_catalogpg_index::pg_index_indrelid_index0.70
indexpg_catalogpg_index::pg_index_indexrelid_index0.70
indexpg_catalogpg_constraint::pg_constraint_contypid_index0.70
indexpg_catalogpg_constraint::pg_constraint_conrelid_contypid_conname_index0.70
indexpg_catalogpg_constraint::pg_constraint_conparentid_index0.70
indexpg_catalogpg_constraint::pg_constraint_conname_nsp_index0.70
tablepg_catalogpg_aggregate0.70
indexpg_catalogpg_class::pg_class_relname_nsp_index0.60
indexpg_catalogpg_collation::pg_collation_name_enc_nsp_index0.50
indexpg_catalogpg_operator::pg_operator_oprname_l_r_n_index0.50
indexpg_catalogpg_collation::pg_collation_oid_index0.50
indexpg_catalogpg_proc::pg_proc_proname_args_nsp_index0.40
indexpg_catalogpg_class::pg_class_oid_index0.40
indexpg_catalogpg_type::pg_type_typname_nsp_index0.40
indexpg_catalogpg_operator::pg_operator_oid_index0.40
indexpg_catalogpg_attribute::pg_attribute_relid_attnam_index0.40
indexpg_catalogpg_type::pg_type_oid_index0.30
indexpg_catalogpg_class::pg_class_tblspc_relfilenode_index0.20
indexpg_catalogpg_rewrite::pg_rewrite_oid_index0.20
indexpg_catalogpg_attribute::pg_attribute_relid_attnum_index0.20
indexpublicpgbench_accounts::pgbench_accounts_pkey0.20
indexpg_catalogpg_rewrite::pg_rewrite_rel_rulename_index0.20
indexpg_catalogpg_proc::pg_proc_oid_index0.10

example row

(first (:bloat stats))
{:type "table",
 :schemaname "public",
 :object_name "pgbench_accounts",
 :bloat 1.1M,
 :waste 8052736}

#’postgres-extras-clj.core/calls

Queries that have the highest frequency of execution

(show (pgex/calls db {:limit 10}))
queryexec_time_msexec_time_ratiocallssync_io_time_ms
commit7.5686.361382463554805E-5320010.000
begin8.8947.475989311512681E-5320010.000
UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $228824.3410.24229609449873538320000.000
SELECT abalance FROM pgbench_accounts WHERE aid = $1205.8810.0017306254090014475320000.000
UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $21084.1350.00911318603502285320000.000
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)3137.1370.026370631564860844320000.000
UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $221821.0300.6877838429756259320000.000
SET application_name = 'PostgreSQL JDBC Driver'2.5542.1468025602660974E-512690.000
SET extra_float_digits = 34.4543.7444256311153375E-512690.000
SET application_name = 'PostgreSQL JDBC Driver'1.3911.169264044194772E-57020.000

example row

(first (:calls stats))
{:query "commit",
 :exec_time_ms 7.568M,
 :exec_time_ratio 6.361719558622419E-5,
 :calls 32001,
 :sync_io_time_ms 0.000M}

#’postgres-extras-clj.core/table-size

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

(show (pgex/table-size db))
schemanamesize
publicpgbench_accounts136437760
publicpgbench_history1875968
publicpgbench_tellers245760
publicpgbench_branches147456

example row

(first (:table-size stats))
{:schema "public", :name "pgbench_accounts", :size 136437760}

#’postgres-extras-clj.core/connections

Returns the list of all active database connections

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

example row

(first (:connections stats))
{:username "postgres",
 :client_address "172.18.0.1/32",
 :application_name "PostgreSQL JDBC Driver"}

#’postgres-extras-clj.core/table-cache-hit

Calculates your cache hit rate for reading tables

(show (pgex/table-cache-hit db))
schemanamebuffer_hitsblock_readstotal_readratio
publicpgbench_branches57190505719051.0
publicpgbench_tellers48195004819501.0
publicpgbench_history12912601291261.0
publicpgbench_accounts399090287764278660.9327452987617618

example row

(first (:table-cache-hit stats))
{:schema "public",
 :name "pgbench_branches",
 :buffer_hits 571905,
 :block_reads 0,
 :total_read 571905,
 :ratio 1.0}

#’postgres-extras-clj.core/table-indexes-size

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

(first (:table-indexes-size stats))
{:schema "public", :table "pgbench_accounts", :index_size 22487040}

#’postgres-extras-clj.core/seq-scans

Count of sequential scans by table descending by order

(show (pgex/seq-scans db))
schemanamecount
publicpgbench_branches35607
publicpgbench_tellers32324
publicpgbench_accounts4
publicpgbench_history3

example row

(first (:seq-scans stats))
{:schema "public", :name "pgbench_branches", :count 35607}

#’postgres-extras-clj.core/index-size

The size of indexes, descending by size

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

example row

(first (:index-size stats))
{:schema "public", :name "pgbench_accounts_pkey", :size 22487040}

These show no results for the pgbench example but can be valuable in other scenarios.

#’postgres-extras-clj.core/partition-children

List all child partitions in current database

(show (pgex/partition-children db))
()

#’postgres-extras-clj.core/partition-parents

List all parent partitions in current database

(show (pgex/partition-parents db))
()

#’postgres-extras-clj.core/duplicate-indexes

Multiple indexes that have the same set of columns, same opclass, expression and predicate

(show (pgex/duplicate-indexes db))
()

#’postgres-extras-clj.core/locks

Queries with active exclusive locks

(show (pgex/locks db))
()

#’postgres-extras-clj.core/null-indexes

Find indexes with a high ratio of NULL values

(show (pgex/null-indexes db))
()

#’postgres-extras-clj.core/all-locks

Queries with active locks

(show (pgex/all-locks db))
()

#’postgres-extras-clj.core/blocking

Queries holding locks other queries are waiting to be released

(show (pgex/blocking db))
()

#’postgres-extras-clj.core/unused-indexes

Unused and almost unused indexes

(show (pgex/unused-indexes db {:min_scans 50}))
()

#’postgres-extras-clj.core/long-running-queries

All queries longer than the threshold by descending duration

(show (pgex/long-running-queries db))
()

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.

(comment
  (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"
 "! Warning :bloat public.pgbench_tellers Bloated tables\n{:type \"table\", :schemaname \"public\", :object_name \"pgbench_tellers\", :bloat 26.0M, :waste 204800}"
 "! Warning :bloat public.pgbench_branches Bloated tables\n{:type \"table\", :schemaname \"public\", :object_name \"pgbench_branches\", :bloat 14.0M, :waste 106496}"
 "✓  Passed :bloat pg_catalog.pg_ts_config_map::pg_ts_config_map_index"
 "✓  Passed :bloat pg_catalog.pg_amproc::pg_amproc_fam_proc_index"
 "✓  Passed :bloat pg_catalog.pg_depend::pg_depend_depender_index"
 "✓  Passed :bloat pg_catalog.pg_class"
 "✓  Passed :bloat pg_catalog.pg_auth_members::pg_auth_members_member_role_index"
 "✓  Passed :bloat pg_catalog.pg_auth_members::pg_auth_members_oid_index"
 "✓  Passed :bloat pg_catalog.pg_auth_members::pg_auth_members_role_member_index"
 "✓  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 21821.030M, :prop_exec_time 0.6877539481806065, :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 WITH RECURSIVE Z ( IX, IY, CX, CY, X, Y, I ) AS ( SELECT IX, IY, X::float, Y::float, X::float, Y::float, $1"
 "✓  Passed :outliers copy pgbench_accounts from stdin with (freeze on)"
 "✓  Passed :outliers SELECT n.nspname::text AS s_name, p.proname::text AS f_name, pg_get_function_result(p.oid)::text AS result_data_types, pg_get_function_arguments(p.oid)::text AS argument_data_types,"
 "✓  Passed :outliers WITH RECURSIVE Z ( IX, IY, CX, CY, X, Y, I ) AS ( SELECT IX, IY, X::float, Y::float, X::float, Y::float, $1"
 "✓  Passed :outliers SELECT n.nspname::text AS s_name, p.proname::text AS f_name, pg_get_function_result(p.oid)::text AS result_data_types, pg_get_function_arguments(p.oid)::text AS argument_data_types,"
 "✓  Passed :outliers SELECT abalance FROM pgbench_accounts WHERE aid = $1"
 "✓  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\", :name \"pgbench_accounts\", :buffer_hits 399090, :block_reads 28776, :total_read 427866, :ratio 0.9327452987617618}")

Or just the warnings

(for [w (pgex/diagnose-warnings (pgex/read-stats db))]
  (:message w))
("! Warning :bloat public.pgbench_tellers Bloated tables\n{:type \"table\", :schemaname \"public\", :object_name \"pgbench_tellers\", :bloat 26.0M, :waste 204800}"
 "! Warning :bloat public.pgbench_branches Bloated tables\n{:type \"table\", :schemaname \"public\", :object_name \"pgbench_branches\", :bloat 14.0M, :waste 106496}"
 "! 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 21821.030M, :prop_exec_time 0.6877050407085642, :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\", :name \"pgbench_accounts\", :buffer_hits 399090, :block_reads 28776, :total_read 427866, :ratio 0.9327452987617618}")

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 :name}})
(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\", :name \"pgbench_accounts\", :buffer_hits 399090, :block_reads 28776, :total_read 427866, :ratio 0.9327452987617618}")

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