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)))

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 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 0x3f341f89 "hugsql.adapter.next_jdbc.HugsqlAdapterNextJdbc@3f341f89"]

Do a health check to ensure connectivity

(pgex/health-check db)
{:now #inst "2024-07-11T15:18:45.956844000-00:00",
 :version
 "PostgreSQL 16.2 (Debian 16.2-1.pgdg110+2) 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))
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_connections400Sets 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_buffers2631688kBSets 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

List all databases

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

example row

{:oid 19699,
 :db_name "main",
 :db_size "161 MB",
 :table_count 72,
 :size_in_tables "162 MB",
 :extension_count 2}

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

{: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}

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-119715pg_stat_statements_info0publicfalseview0
0 bytespostgres0 bytes-119726pg_stat_statements0publicfalseview0

example row

{:size_pretty "0 bytes",
 :description nil,
 :owned_by "postgres",
 :size_plus_indexes "0 bytes",
 :rows -1,
 :oid 19715,
 :v_name "pg_stat_statements_info",
 :size_plus_indexes_bytes 0,
 :s_name "public",
 :system_object false,
 :view_type "view",
 :size_bytes 0}

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.01978216384pgbench_branches_pkeyfalse16 kB1truepublicfalsetruepgbench_branches
1true100.01978416384pgbench_tellers_pkeyfalse16 kB1truepublicfalsetruepgbench_tellers
1true1000000.01978622487040pgbench_accounts_pkeyfalse21 MB1truepublicfalsetruepgbench_accounts

example row

{:total_columns 3,
 :unique_index true,
 :rows_indexed 0.0,
 :oid 2696,
 :index_size_bytes 32768,
 :i_name "pg_statistic_relid_att_inh_index",
 :partial_index false,
 :index_size "32 kB",
 :key_columns 3,
 :valid_index true,
 :s_name "pg_catalog",
 :system_object true,
 :primary_key true,
 :t_name "pg_statistic"}

List all database column objects

(show-public (pgex/columns db))
descriptiongenerated_columnsource_typedata_typec_namepositions_namesystem_objectdefault_valuet_name
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
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

example row

{: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"}

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 kB1019775table163840publicfalsepgbench_branches14745614746
256 kBpostgres272 kB10019769table278528publicfalsepgbench_tellers2621442621
130 MBpostgres152 MB100000019772table158941184publicfalsepgbench_accounts136454144136
1800 kBpostgres1800 kB3200019766table1843200publicfalsepgbench_history184320058

example row

{:size_pretty "280 kB",
 :description nil,
 :owned_by "postgres",
 :size_plus_indexes "312 kB",
 :rows 409,
 :oid 2619,
 :data_type "table",
 :size_plus_indexes_bytes 319488,
 :s_name "pg_catalog",
 :system_object true,
 :t_name "pg_statistic",
 :size_bytes 286720,
 :bytes_per_row 701}

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

{: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)

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

(show (pgex/vacuum-stats db))
schematablelast_vacuumlast_autovacuumrowcountdead_rowcountautovacuum_thresholdexpect_autovacuum
publicpgbench_branches2024-07-11 09:082024-07-11 09:0910.0050.2
publicpgbench_tellers2024-07-11 09:082024-07-11 09:09100.0052.0
publicpgbench_accounts2024-07-11 09:082024-07-11 09:091000000.0020050.0
publicpgbench_history2024-07-11 09:082024-07-11 09:0932000.00690.0

example row

{:schema "public",
 :table "pgbench_branches",
 :last_vacuum "2024-07-11 09:08",
 :last_autovacuum "2024-07-11 09:09",
 :rowcount 10.0,
 :dead_rowcount 0,
 :autovacuum_threshold 50.2,
 :expect_autovacuum nil}

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_tellers57100
publicpgbench_branches4710

example row

{:schema "public",
 :name "pgbench_accounts",
 :percent_of_times_index_used 99,
 :rows_in_table 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))
nameratio
index hit rate0.99319155048877683195
table hit rate0.98325516438754727960

example row

{:name "index hit rate", :ratio 0.99319155048877683195M}

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

{:schema "public", :name "pgbench_accounts", :estimated_count 1000000}

Calculates your cache hit rate for reading indexes

(show (pgex/index-cache-hit db))
schemanamebuffer_hitsblock_readstotal_readratio
publicpgbench_history0000.0
publicpgbench_tellers373662373680.9999464782701777
publicpgbench_branches311132311150.9999357223204243
publicpgbench_accounts33253527453352800.9918128131710809

example row

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

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 = $212927.2140.6589330487430934320000.000
UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $256300.9720.279089357936559320000.000
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)4857.4330.02407876287821654320000.000
UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $21595.3930.007908518366362697320000.000
copy pgbench_accounts from stdin with (freeze on)861.3530.004269812410065524510.000
CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder801.8510.00397485152279127210.000
CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder754.7690.003741460819489370710.000
CREATE EXTENSION IF NOT EXISTS postgis383.2330.001899725030644277210.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.prolang381.9870.001893545385083702360.000
CREATE EXTENSION IF NOT EXISTS postgis375.0060.001858940304852908710.000

example row

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

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
ltree1.2data type for hierarchical tree-like structures
autoinc1.0functions for autoincrementing fields
lo1.1Large Object maintenance
tcn1.0Triggered change notifications
pgrowlocks1.2show row-level locking information
hstore1.8data type for storing sets of (key, value) pairs
file_fdw1.0foreign-data wrapper for flat file access
tsm_system_time1.0TABLESAMPLE method which accepts time in milliseconds as a limit
citext1.6data type for case-insensitive character strings
pgstattuple1.5show tuple-level statistics
pg_surgery1.0extension to perform surgery on a damaged relation
tablefunc1.0functions that manipulate whole tables, including crosstab
bloom1.0bloom access method - signature file based index
dblink1.2connect to other PostgreSQL databases from within a database
sslinfo1.2information about SSL certificates
refint1.0functions for implementing referential integrity (obsolete)
pg_walinspect1.1functions to inspect contents of PostgreSQL Write-Ahead Log
amcheck1.3functions for verifying relation integrity
adminpack2.1administrative functions for PostgreSQL
earthdistance1.1calculate great-circle distances on the surface of the Earth
pg_trgm1.6text similarity measurement and index searching based on trigrams
uuid-ossp1.1generate universally unique identifiers (UUIDs)
unaccent1.1text search dictionary that removes accents
seg1.4data type for representing line segments or floating-point intervals
moddatetime1.0functions for tracking last modification time
intagg1.1integer aggregator and enumerator (obsolete)
dict_xsyn1.0text search dictionary template for extended synonym processing
postgres_fdw1.1foreign-data wrapper for remote PostgreSQL servers
pg_visibility1.2examine the visibility map (VM) and page-level visibility info
btree_gist1.7support for indexing common datatypes in GiST
old_snapshot1.0utilities in support of old_snapshot_threshold
pg_prewarm1.2prewarm relation data
cube1.5data type for multidimensional cubes
isn1.2data types for international product numbering standards
xml21.1XPath querying and XSLT
pg_freespacemap1.2examine the free space map (FSM)
intarray1.5functions, operators, and index support for 1-D arrays of integers
dict_int1.0text search dictionary template for integers
tsm_system_rows1.0TABLESAMPLE method which accepts number of rows as a limit
fuzzystrmatch1.2determine similarities and distance between strings
btree_gin1.3support for indexing common datatypes in GIN
pgcrypto1.3cryptographic functions
pageinspect1.12inspect the contents of database pages at a low level
postgis_topology-33.4.2PostGIS topology spatial types and functions
postgis_raster3.4.2PostGIS raster types and functions
address_standardizer3.4.2Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.
address_standardizer-33.4.2Used to parse an address into constituent elements. Generally used to support geocoding address normalization step.
postgis_sfcgal3.4.2PostGIS SFCGAL functions
postgis3.4.2PostGIS geometry and geography spatial types and functions
postgis_tiger_geocoder3.4.2PostGIS tiger geocoder and reverse geocoder
postgis_raster-33.4.2PostGIS raster types and functions
address_standardizer_data_us-33.4.2Address Standardizer US dataset example
postgis_tiger_geocoder-33.4.2PostGIS tiger geocoder and reverse geocoder
postgis_sfcgal-33.4.2PostGIS SFCGAL functions
postgis-33.4.2PostGIS geometry and geography spatial types and functions
postgis_topology3.4.2PostGIS topology spatial types and functions
pg_buffercache1.4examine the shared buffer cache
address_standardizer_data_us3.4.2Address Standardizer US dataset example
insert_username1.0functions for tracking who changed a table

example row

{:name "plpgsql",
 :default_version "1.0",
 :installed_version "1.0",
 :comment "PL/pgSQL procedural language"}

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

(show (pgex/total-table-size db))
schemanamesize
publicpgbench_accounts158941184
publicpgbench_history1843200
publicpgbench_tellers278528
publicpgbench_branches163840

example row

{:schema "public", :name "pgbench_accounts", :size 158941184}

Table and index bloat in your database ordered by most wasteful

(show (pgex/bloat db))
typeschemanameobject_namebloatwaste
tablepublicpgbench_accounts1.18069120
tablepublicpgbench_tellers28.0221184
tablepublicpgbench_branches14.0106496
indexpg_catalogpg_ts_config_map::pg_ts_config_map_index2.016384
indexpg_catalogpg_amproc::pg_amproc_fam_proc_index1.716384
tablepg_catalogpg_class1.216384
indexpg_catalogpg_range::pg_range_rngmultitypid_index2.08192
indexpg_catalogpg_auth_members::pg_auth_members_grantor_index2.08192
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_ts_config::pg_ts_config_cfgname_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_extension::pg_extension_oid_index2.08192
indexpg_catalogpg_ts_dict::pg_ts_dict_dictname_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_oid_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_extension::pg_extension_name_index2.08192
indexpg_catalogpg_amproc::pg_amproc_oid_index1.38192
indexpg_catalogpg_amop::pg_amop_fam_strat_index1.28192
indexpg_catalogpg_amop::pg_amop_opr_fam_index1.28192
tablepg_catalogpg_depend1.18192
indexpg_catalogpg_depend::pg_depend_depender_index1.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_depend::pg_depend_reference_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_collation0.90
tablepg_catalogpg_type0.90
tablepublicpgbench_history0.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_oid_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_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_class::pg_class_oid_index0.40
indexpg_catalogpg_type::pg_type_oid_index0.30
indexpg_catalogpg_attribute::pg_attribute_relid_attnum_index0.20
indexpg_catalogpg_rewrite::pg_rewrite_oid_index0.20
indexpg_catalogpg_rewrite::pg_rewrite_rel_rulename_index0.20
indexpg_catalogpg_class::pg_class_tblspc_relfilenode_index0.20
indexpublicpgbench_accounts::pgbench_accounts_pkey0.20
indexpg_catalogpg_proc::pg_proc_oid_index0.10

example row

{:type "table",
 :schemaname "public",
 :object_name "pgbench_accounts",
 :bloat 1.1M,
 :waste 8069120}

Queries that have the highest frequency of execution

(show (pgex/calls db {:limit 10}))
queryexec_time_msexec_time_ratiocallssync_io_time_ms
begin18.7909.313107695188783E-5320010.000
commit16.9098.381033978988401E-5320010.000
UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $256300.9720.27905307309743405320000.000
UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $212927.2140.6588473798381789320000.000
UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $21595.3930.007907490167656872320000.000
INSERT INTO pgbench_history (tid, bid, aid, delta, mtime) VALUES ($1, $2, $3, $4, CURRENT_TIMESTAMP)4857.4330.024075632361009357320000.000
SELECT abalance FROM pgbench_accounts WHERE aid = $1357.0880.0017698895506576837320000.000
SET extra_float_digits = 33.3981.6843645212852338E-53300.000
SET application_name = 'PostgreSQL JDBC Driver'1.7798.817865438357988E-63300.000
insert into pgbench_tellers(tid,bid,tbalance) values ($1,$2,$3)0.3841.9028265937977505E-61000.000

example row

{:query "begin",
 :exec_time_ms 18.790M,
 :exec_time_ratio 9.314608859977958E-5,
 :calls 32001,
 :sync_io_time_ms 0.000M}

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

(show (pgex/table-size db))
schemanamesize
publicpgbench_accounts136454144
publicpgbench_history1843200
publicpgbench_tellers262144
publicpgbench_branches147456

example row

{:schema "public", :name "pgbench_accounts", :size 136454144}

Returns the list of all active database connections

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

example row

{:username "postgres",
 :client_address "172.20.0.1/32",
 :application_name "psql"}

Calculates your cache hit rate for reading tables

(show (pgex/table-cache-hit db))
schemanamebuffer_hitsblock_readstotal_readratio
publicpgbench_branches67131806713181.0
publicpgbench_tellers48877104887711.0
publicpgbench_history12942401294241.0
publicpgbench_accounts400211287764289870.9329210442274475

example row

{:schema "public",
 :name "pgbench_branches",
 :buffer_hits 671318,
 :block_reads 0,
 :total_read 671318,
 :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))
schemanamecount
publicpgbench_branches33352
publicpgbench_tellers27304
publicpgbench_accounts4
publicpgbench_history3

example row

{:schema "public", :name "pgbench_branches", :count 33352}

The size of indexes, descending by size

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

example row

{:schema "public", :name "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"
 "! Warning :bloat public.pgbench_tellers Bloated tables\n{:type \"table\", :schemaname \"public\", :object_name \"pgbench_tellers\", :bloat 28.0M, :waste 221184}"
 "! 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_class"
 "✓  Passed :bloat pg_catalog.pg_range::pg_range_rngmultitypid_index"
 "✓  Passed :bloat pg_catalog.pg_auth_members::pg_auth_members_grantor_index"
 "✓  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 :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 12927.214M, :prop_exec_time 0.6587952573944417, :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 CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder"
 "✓  Passed :outliers CREATE EXTENSION IF NOT EXISTS postgis_tiger_geocoder"
 "✓  Passed :outliers CREATE EXTENSION IF NOT EXISTS postgis"
 "✓  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 CREATE EXTENSION IF NOT EXISTS postgis"
 "✓  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 400211, :block_reads 28776, :total_read 428987, :ratio 0.9329210442274475}")

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 28.0M, :waste 221184}"
 "! 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 12927.214M, :prop_exec_time 0.6586800877968829, :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 400211, :block_reads 28776, :total_read 428987, :ratio 0.9329210442274475}")

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 400211, :block_reads 28776, :total_read 428987, :ratio 0.9329210442274475}")

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