diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 23ebc11..cdced7f 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -1878,6 +1878,13 @@ include 'filename' results in most cases. + + When you see pg_stat_walwriter.dirty_write, which means number + of buffer flushing at buffer full, is continuously increasing + in your running server, you may need to enlarge this buffer + size. + + diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index 4ec6981..15d9202 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -278,6 +278,14 @@ postgres: user database host + pg_stat_walwriterpg_stat_walwriter + One row only, showing statistics about the wal writer + process's activity. See + for details. + + + + pg_stat_databasepg_stat_database One row per database, showing database-wide statistics. See for details. @@ -735,6 +743,39 @@ postgres: user database host + + <structname>pg_stat_walwriter</structname> View + + + + + Column + Type + Description + + + + + + dirty_writes + bigint + Number of dirty writes, which means flushing wal buffers + because of its full. + + + stats_reset + timestamp with time zone + Time at which these statistics were last reset + + + +
+ + + The pg_stat_walwriter view will always have a + single row, containing global data for the cluster. + + <structname>pg_stat_database</structname> View diff --git a/src/backend/access/transam/xlog.c b/src/backend/access/transam/xlog.c index dc47c47..d0e85c9 100644 --- a/src/backend/access/transam/xlog.c +++ b/src/backend/access/transam/xlog.c @@ -2467,6 +2467,7 @@ AdvanceXLInsertBuffer(XLogRecPtr upto, bool opportunistic) WriteRqst.Write = OldPageRqstPtr; WriteRqst.Flush = 0; XLogWrite(WriteRqst, false); + WalWriterStats.m_xlog_dirty_writes++; LWLockRelease(WALWriteLock); TRACE_POSTGRESQL_WAL_BUFFER_WRITE_DIRTY_DONE(); } diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 575a40f..12a2ed0 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -686,6 +686,11 @@ CREATE VIEW pg_stat_bgwriter AS pg_stat_get_buf_alloc() AS buffers_alloc, pg_stat_get_bgwriter_stat_reset_time() AS stats_reset; +CREATE VIEW pg_stat_walwriter AS + SELECT + pg_stat_get_xlog_dirty_writes() AS dirty_writes, + pg_stat_get_wal_stat_reset_time() AS stats_reset; + CREATE VIEW pg_user_mappings AS SELECT U.oid AS umid, diff --git a/src/backend/postmaster/pgstat.c b/src/backend/postmaster/pgstat.c index b5ce2f6..8c56af5 100644 --- a/src/backend/postmaster/pgstat.c +++ b/src/backend/postmaster/pgstat.c @@ -129,6 +129,14 @@ char *pgstat_stat_tmpname = NULL; */ PgStat_MsgBgWriter BgWriterStats; +/* + * WalWriter statistics counter. + * This counter is incremented by each XLogWrite call, + * both in the wal writer process and each backend. + * And then, sent to the stat collector process. + */ +PgStat_MsgWalWriter WalWriterStats; + /* ---------- * Local data * ---------- @@ -293,6 +301,7 @@ static void pgstat_recv_autovac(PgStat_MsgAutovacStart *msg, int len); static void pgstat_recv_vacuum(PgStat_MsgVacuum *msg, int len); static void pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len); static void pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len); +static void pgstat_recv_walwriter(PgStat_MsgWalWriter *msg, int len); static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len); static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len); static void pgstat_recv_recoveryconflict(PgStat_MsgRecoveryConflict *msg, int len); @@ -820,6 +829,9 @@ pgstat_report_stat(bool force) /* Now, send function statistics */ pgstat_send_funcstats(); + + /* Now, send wal buffer flush statistics */ + pgstat_send_walwriter(); } /* @@ -1249,11 +1261,13 @@ pgstat_reset_shared_counters(const char *target) if (strcmp(target, "bgwriter") == 0) msg.m_resettarget = RESET_BGWRITER; + else if (strcmp(target, "walwriter") == 0) + msg.m_resettarget = RESET_WALWRITER; else ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized reset target: \"%s\"", target), - errhint("Target must be \"bgwriter\"."))); + errhint("Target must be \"bgwriter\" or \"walwriter\"."))); pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER); pgstat_send(&msg, sizeof(msg)); @@ -3055,6 +3069,38 @@ pgstat_send_bgwriter(void) MemSet(&BgWriterStats, 0, sizeof(BgWriterStats)); } +/* ---------- + * pgstat_send_walwriter() - + * + * Send walwriter statistics to the collector + * ---------- + */ +void +pgstat_send_walwriter(void) +{ + /* We assume this initializes to zeroes */ + static const PgStat_MsgBgWriter all_zeroes; + + /* + * This function can be called even if nothing at all has happened. In + * this case, avoid sending a completely empty message to the stats + * collector. + */ + if (memcmp(&WalWriterStats, &all_zeroes, sizeof(PgStat_MsgWalWriter)) == 0) + return; + + /* + * Prepare and send the message + */ + pgstat_setheader(&WalWriterStats.m_hdr, PGSTAT_MTYPE_WALWRITER); + pgstat_send(&WalWriterStats, sizeof(WalWriterStats)); + + /* + * Clear out the statistics buffer, so it can be re-used. + */ + MemSet(&WalWriterStats, 0, sizeof(WalWriterStats)); +} + /* ---------- * PgstatCollectorMain() - @@ -3270,6 +3316,10 @@ PgstatCollectorMain(int argc, char *argv[]) pgstat_recv_bgwriter((PgStat_MsgBgWriter *) &msg, len); break; + case PGSTAT_MTYPE_WALWRITER: + pgstat_recv_walwriter((PgStat_MsgWalWriter *) &msg, len); + break; + case PGSTAT_MTYPE_FUNCSTAT: pgstat_recv_funcstat((PgStat_MsgFuncstat *) &msg, len); break; @@ -3825,7 +3875,8 @@ pgstat_read_statsfiles(Oid onlydb, bool permanent, bool deep) * Set the current timestamp (will be kept only in case we can't load an * existing statsfile). */ - globalStats.stat_reset_timestamp = GetCurrentTimestamp(); + globalStats.bgWriterGlobalStats.stat_reset_timestamp = GetCurrentTimestamp(); + globalStats.walWriterGlobalStats.stat_reset_timestamp = GetCurrentTimestamp(); /* * Try to open the stats file. If it doesn't exist, the backends simply @@ -4723,8 +4774,23 @@ pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len) if (msg->m_resettarget == RESET_BGWRITER) { /* Reset the global background writer statistics for the cluster. */ - memset(&globalStats, 0, sizeof(globalStats)); - globalStats.stat_reset_timestamp = GetCurrentTimestamp(); + globalStats.bgWriterGlobalStats.timed_checkpoints = 0; + globalStats.bgWriterGlobalStats.requested_checkpoints = 0; + globalStats.bgWriterGlobalStats.checkpoint_write_time = 0; + globalStats.bgWriterGlobalStats.checkpoint_sync_time = 0; + globalStats.bgWriterGlobalStats.buf_written_checkpoints = 0; + globalStats.bgWriterGlobalStats.buf_written_clean = 0; + globalStats.bgWriterGlobalStats.maxwritten_clean = 0; + globalStats.bgWriterGlobalStats.buf_written_backend = 0; + globalStats.bgWriterGlobalStats.buf_fsync_backend = 0; + globalStats.bgWriterGlobalStats.buf_alloc = 0; + globalStats.bgWriterGlobalStats.stat_reset_timestamp = GetCurrentTimestamp(); + } + else if (msg->m_resettarget == RESET_WALWRITER) + { + /* Reset the global walwriter statistics for the cluster. */ + globalStats.walWriterGlobalStats.xlog_dirty_writes = 0; + globalStats.walWriterGlobalStats.stat_reset_timestamp = GetCurrentTimestamp(); } /* @@ -4865,16 +4931,28 @@ pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len) static void pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len) { - globalStats.timed_checkpoints += msg->m_timed_checkpoints; - globalStats.requested_checkpoints += msg->m_requested_checkpoints; - globalStats.checkpoint_write_time += msg->m_checkpoint_write_time; - globalStats.checkpoint_sync_time += msg->m_checkpoint_sync_time; - globalStats.buf_written_checkpoints += msg->m_buf_written_checkpoints; - globalStats.buf_written_clean += msg->m_buf_written_clean; - globalStats.maxwritten_clean += msg->m_maxwritten_clean; - globalStats.buf_written_backend += msg->m_buf_written_backend; - globalStats.buf_fsync_backend += msg->m_buf_fsync_backend; - globalStats.buf_alloc += msg->m_buf_alloc; + globalStats.bgWriterGlobalStats.timed_checkpoints += msg->m_timed_checkpoints; + globalStats.bgWriterGlobalStats.requested_checkpoints += msg->m_requested_checkpoints; + globalStats.bgWriterGlobalStats.checkpoint_write_time += msg->m_checkpoint_write_time; + globalStats.bgWriterGlobalStats.checkpoint_sync_time += msg->m_checkpoint_sync_time; + globalStats.bgWriterGlobalStats.buf_written_checkpoints += msg->m_buf_written_checkpoints; + globalStats.bgWriterGlobalStats.buf_written_clean += msg->m_buf_written_clean; + globalStats.bgWriterGlobalStats.maxwritten_clean += msg->m_maxwritten_clean; + globalStats.bgWriterGlobalStats.buf_written_backend += msg->m_buf_written_backend; + globalStats.bgWriterGlobalStats.buf_fsync_backend += msg->m_buf_fsync_backend; + globalStats.bgWriterGlobalStats.buf_alloc += msg->m_buf_alloc; +} + +/* ---------- + * pgstat_recv_walwriter() - + * + * Process a WALWRITER message. + * ---------- + */ +static void +pgstat_recv_walwriter(PgStat_MsgWalWriter *msg, int len) +{ + globalStats.walWriterGlobalStats.xlog_dirty_writes += msg->m_xlog_dirty_writes; } /* ---------- diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c index 8359da6..a6cdee2 100644 --- a/src/backend/postmaster/walwriter.c +++ b/src/backend/postmaster/walwriter.c @@ -49,6 +49,7 @@ #include "access/xlog.h" #include "libpq/pqsignal.h" #include "miscadmin.h" +#include "pgstat.h" #include "postmaster/walwriter.h" #include "storage/bufmgr.h" #include "storage/fd.h" @@ -290,6 +291,8 @@ WalWriterMain(void) else if (left_till_hibernate > 0) left_till_hibernate--; + pgstat_send_walwriter(); + /* * Sleep until we are signaled or WalWriterDelay has elapsed. If we * haven't done anything useful for quite some time, lengthen the diff --git a/src/backend/utils/adt/pgstatfuncs.c b/src/backend/utils/adt/pgstatfuncs.c index 0533cd6..df9f1d8 100644 --- a/src/backend/utils/adt/pgstatfuncs.c +++ b/src/backend/utils/adt/pgstatfuncs.c @@ -98,6 +98,7 @@ extern Datum pg_stat_get_bgwriter_stat_reset_time(PG_FUNCTION_ARGS); extern Datum pg_stat_get_buf_written_backend(PG_FUNCTION_ARGS); extern Datum pg_stat_get_buf_fsync_backend(PG_FUNCTION_ARGS); extern Datum pg_stat_get_buf_alloc(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_wal_stat_reset_time(PG_FUNCTION_ARGS); extern Datum pg_stat_get_xact_numscans(PG_FUNCTION_ARGS); extern Datum pg_stat_get_xact_tuples_returned(PG_FUNCTION_ARGS); @@ -119,6 +120,8 @@ extern Datum pg_stat_reset_shared(PG_FUNCTION_ARGS); extern Datum pg_stat_reset_single_table_counters(PG_FUNCTION_ARGS); extern Datum pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS); +extern Datum pg_stat_get_xlog_dirty_writes(PG_FUNCTION_ARGS); + /* Global bgwriter statistics, from bgwriter.c */ extern PgStat_MsgBgWriter bgwriterStats; @@ -1409,69 +1412,75 @@ pg_stat_get_db_blk_write_time(PG_FUNCTION_ARGS) Datum pg_stat_get_bgwriter_timed_checkpoints(PG_FUNCTION_ARGS) { - PG_RETURN_INT64(pgstat_fetch_global()->timed_checkpoints); + PG_RETURN_INT64(pgstat_fetch_global()->bgWriterGlobalStats.timed_checkpoints); } Datum pg_stat_get_bgwriter_requested_checkpoints(PG_FUNCTION_ARGS) { - PG_RETURN_INT64(pgstat_fetch_global()->requested_checkpoints); + PG_RETURN_INT64(pgstat_fetch_global()->bgWriterGlobalStats.requested_checkpoints); } Datum pg_stat_get_bgwriter_buf_written_checkpoints(PG_FUNCTION_ARGS) { - PG_RETURN_INT64(pgstat_fetch_global()->buf_written_checkpoints); + PG_RETURN_INT64(pgstat_fetch_global()->bgWriterGlobalStats.buf_written_checkpoints); } Datum pg_stat_get_bgwriter_buf_written_clean(PG_FUNCTION_ARGS) { - PG_RETURN_INT64(pgstat_fetch_global()->buf_written_clean); + PG_RETURN_INT64(pgstat_fetch_global()->bgWriterGlobalStats.buf_written_clean); } Datum pg_stat_get_bgwriter_maxwritten_clean(PG_FUNCTION_ARGS) { - PG_RETURN_INT64(pgstat_fetch_global()->maxwritten_clean); + PG_RETURN_INT64(pgstat_fetch_global()->bgWriterGlobalStats.maxwritten_clean); } Datum pg_stat_get_checkpoint_write_time(PG_FUNCTION_ARGS) { /* time is already in msec, just convert to double for presentation */ - PG_RETURN_FLOAT8((double) pgstat_fetch_global()->checkpoint_write_time); + PG_RETURN_FLOAT8((double) pgstat_fetch_global()->bgWriterGlobalStats.checkpoint_write_time); } Datum pg_stat_get_checkpoint_sync_time(PG_FUNCTION_ARGS) { /* time is already in msec, just convert to double for presentation */ - PG_RETURN_FLOAT8((double) pgstat_fetch_global()->checkpoint_sync_time); + PG_RETURN_FLOAT8((double) pgstat_fetch_global()->bgWriterGlobalStats.checkpoint_sync_time); } Datum pg_stat_get_bgwriter_stat_reset_time(PG_FUNCTION_ARGS) { - PG_RETURN_TIMESTAMPTZ(pgstat_fetch_global()->stat_reset_timestamp); + PG_RETURN_TIMESTAMPTZ(pgstat_fetch_global()->bgWriterGlobalStats.stat_reset_timestamp); } Datum pg_stat_get_buf_written_backend(PG_FUNCTION_ARGS) { - PG_RETURN_INT64(pgstat_fetch_global()->buf_written_backend); + PG_RETURN_INT64(pgstat_fetch_global()->bgWriterGlobalStats.buf_written_backend); } Datum pg_stat_get_buf_fsync_backend(PG_FUNCTION_ARGS) { - PG_RETURN_INT64(pgstat_fetch_global()->buf_fsync_backend); + PG_RETURN_INT64(pgstat_fetch_global()->bgWriterGlobalStats.buf_fsync_backend); } Datum pg_stat_get_buf_alloc(PG_FUNCTION_ARGS) { - PG_RETURN_INT64(pgstat_fetch_global()->buf_alloc); + PG_RETURN_INT64(pgstat_fetch_global()->bgWriterGlobalStats.buf_alloc); +} + +Datum +pg_stat_get_wal_stat_reset_time(PG_FUNCTION_ARGS) +{ + PG_RETURN_TIMESTAMPTZ(pgstat_fetch_global()->walWriterGlobalStats.stat_reset_timestamp); } Datum @@ -1711,3 +1720,9 @@ pg_stat_reset_single_function_counters(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } + +Datum +pg_stat_get_xlog_dirty_writes(PG_FUNCTION_ARGS) +{ + PG_RETURN_INT64(pgstat_fetch_global()->walWriterGlobalStats.xlog_dirty_writes); +} diff --git a/src/include/catalog/pg_proc.h b/src/include/catalog/pg_proc.h index f03dd0b..b1f9c54 100644 --- a/src/include/catalog/pg_proc.h +++ b/src/include/catalog/pg_proc.h @@ -2709,6 +2709,8 @@ DATA(insert OID = 3063 ( pg_stat_get_buf_fsync_backend PGNSP PGUID 12 1 0 0 0 f DESCR("statistics: number of backend buffer writes that did their own fsync"); DATA(insert OID = 2859 ( pg_stat_get_buf_alloc PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 20 "" _null_ _null_ _null_ _null_ pg_stat_get_buf_alloc _null_ _null_ _null_ )); DESCR("statistics: number of buffer allocations"); +DATA(insert OID = 3178 ( pg_stat_get_wal_stat_reset_time PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 1184 "" _null_ _null_ _null_ _null_ pg_stat_get_wal_stat_reset_time _null_ _null_ _null_ )); +DESCR("statistics: last reset for the wal"); DATA(insert OID = 2978 ( pg_stat_get_function_calls PGNSP PGUID 12 1 0 0 0 f f f f t f s 1 0 20 "26" _null_ _null_ _null_ _null_ pg_stat_get_function_calls _null_ _null_ _null_ )); DESCR("statistics: number of function calls"); @@ -2753,6 +2755,9 @@ DESCR("statistics: reset collected statistics for a single table or index in the DATA(insert OID = 3777 ( pg_stat_reset_single_function_counters PGNSP PGUID 12 1 0 0 0 f f f f f f v 1 0 2278 "26" _null_ _null_ _null_ _null_ pg_stat_reset_single_function_counters _null_ _null_ _null_ )); DESCR("statistics: reset collected statistics for a single function in the current database"); +DATA(insert OID = 3179 ( pg_stat_get_xlog_dirty_writes PGNSP PGUID 12 1 0 0 0 f f f f f f v 0 0 20 "" _null_ _null_ _null_ _null_ pg_stat_get_xlog_dirty_writes _null_ _null_ _null_ )); +DESCR("statistics: get xlog dirty buffer write statistics"); + DATA(insert OID = 3163 ( pg_trigger_depth PGNSP PGUID 12 1 0 0 0 f f f f t f s 0 0 23 "" _null_ _null_ _null_ _null_ pg_trigger_depth _null_ _null_ _null_ )); DESCR("current trigger depth"); diff --git a/src/include/pgstat.h b/src/include/pgstat.h index fb242e4..1213964 100644 --- a/src/include/pgstat.h +++ b/src/include/pgstat.h @@ -45,6 +45,7 @@ typedef enum StatMsgType PGSTAT_MTYPE_VACUUM, PGSTAT_MTYPE_ANALYZE, PGSTAT_MTYPE_BGWRITER, + PGSTAT_MTYPE_WALWRITER, PGSTAT_MTYPE_FUNCSTAT, PGSTAT_MTYPE_FUNCPURGE, PGSTAT_MTYPE_RECOVERYCONFLICT, @@ -102,7 +103,8 @@ typedef struct PgStat_TableCounts /* Possible targets for resetting cluster-wide shared values */ typedef enum PgStat_Shared_Reset_Target { - RESET_BGWRITER + RESET_BGWRITER, + RESET_WALWRITER } PgStat_Shared_Reset_Target; /* Possible object types for resetting single counters */ @@ -373,6 +375,17 @@ typedef struct PgStat_MsgBgWriter } PgStat_MsgBgWriter; /* ---------- + * PgStat_MsgWalWriter Sent by the walwriter to update statistics. + * ---------- + */ +typedef struct PgStat_MsgWalWriter +{ + PgStat_MsgHdr m_hdr; + + PgStat_Counter m_xlog_dirty_writes; +} PgStat_MsgWalWriter; + +/* ---------- * PgStat_MsgRecoveryConflict Sent by the backend upon recovery conflict * ---------- */ @@ -500,6 +513,7 @@ typedef union PgStat_Msg PgStat_MsgVacuum msg_vacuum; PgStat_MsgAnalyze msg_analyze; PgStat_MsgBgWriter msg_bgwriter; + PgStat_MsgWalWriter msg_walwriter; PgStat_MsgFuncstat msg_funcstat; PgStat_MsgFuncpurge msg_funcpurge; PgStat_MsgRecoveryConflict msg_recoveryconflict; @@ -608,12 +622,8 @@ typedef struct PgStat_StatFuncEntry } PgStat_StatFuncEntry; -/* - * Global statistics kept in the stats collector - */ -typedef struct PgStat_GlobalStats +typedef struct PgStat_BgWriterGlobalStats { - TimestampTz stats_timestamp; /* time of stats file update */ PgStat_Counter timed_checkpoints; PgStat_Counter requested_checkpoints; PgStat_Counter checkpoint_write_time; /* times in milliseconds */ @@ -625,6 +635,22 @@ typedef struct PgStat_GlobalStats PgStat_Counter buf_fsync_backend; PgStat_Counter buf_alloc; TimestampTz stat_reset_timestamp; +} PgStat_BgWriterGlobalStats; + +typedef struct PgStat_WalWriterGlobalStats +{ + PgStat_Counter xlog_dirty_writes; + TimestampTz stat_reset_timestamp; +} PgStat_WalWriterGlobalStats; + +/* + * Global statistics kept in the stats collector + */ +typedef struct PgStat_GlobalStats +{ + TimestampTz stats_timestamp; /* time of stats file update */ + PgStat_BgWriterGlobalStats bgWriterGlobalStats; + PgStat_WalWriterGlobalStats walWriterGlobalStats; } PgStat_GlobalStats; @@ -733,6 +759,8 @@ extern char *pgstat_stat_filename; */ extern PgStat_MsgBgWriter BgWriterStats; +extern PgStat_MsgWalWriter WalWriterStats; + /* * Updated by pgstat_count_buffer_*_time macros */ @@ -861,6 +889,7 @@ extern void pgstat_twophase_postabort(TransactionId xid, uint16 info, void *recdata, uint32 len); extern void pgstat_send_bgwriter(void); +extern void pgstat_send_walwriter(void); /* ---------- * Support functions for the SQL-callable functions to diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 8f24c51..4074c61 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -1775,6 +1775,8 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem | pg_stat_all_tables.autoanalyze_count + | FROM pg_stat_all_tables + | WHERE ((pg_stat_all_tables.schemaname <> ALL (ARRAY['pg_catalog'::name, 'information_schema'::name])) AND (pg_stat_all_tables.schemaname !~ '^pg_toast'::text)); + pg_stat_walwriter | SELECT pg_stat_get_xlog_dirty_writes() AS dirty_writes, + + | pg_stat_get_wal_stat_reset_time() AS stats_reset; pg_stat_xact_all_tables | SELECT c.oid AS relid, + | n.nspname AS schemaname, + | c.relname, + @@ -2142,7 +2144,7 @@ SELECT viewname, definition FROM pg_views WHERE schemaname <> 'information_schem | FROM tv; tvvmv | SELECT tvvm.grandtot + | FROM tvvm; -(64 rows) +(65 rows) SELECT tablename, rulename, definition FROM pg_rules ORDER BY tablename, rulename;