]> begriffs open source - pg_scribe/blob - logical-replication-backup-design.md
Fact check against pg docs
[pg_scribe] / logical-replication-backup-design.md
1 # Incremental SQL Backup System Using PostgreSQL Logical Replication
2
3 **PostgreSQL Version**: This design is based on PostgreSQL 18.0 documentation. While most features (logical replication, event triggers, pg_recvlogical) are available in earlier versions (PostgreSQL 10+), verify specific parameter availability (e.g., `max_slot_wal_keep_size` requires PostgreSQL 13+) for your target version.
4
5 ## Executive Summary
6
7 This document details the design for a PostgreSQL backup system that produces human-readable, plain SQL incremental backups using logical replication. The system creates backups that remain readable and restorable for 10+ years while supporting online operation and crash safety.
8
9 **Design Decision**: Use `pg_recvlogical` with the `decoder_raw` plugin for DML capture, combined with event triggers for DDL tracking and periodic `pg_dumpall --globals-only` for shared objects.
10
11 **Why This Works**:
12 - **Built-in tooling handles complexity**: `pg_recvlogical` provides streaming infrastructure, crash recovery, and position tracking
13 - **No transformation layer needed**: `decoder_raw` produces production-ready SQL directly
14 - **Complete coverage**: Event triggers + `pg_dumpall --globals-only` captures all DDL
15 - **Long-term readability**: Plain SQL format that can be executed years later
16
17 **Key Requirements**:
18 1. **DDL tracking** - Event triggers capture schema changes; `pg_dumpall --globals-only` handles shared objects
19 2. **Replica identity configuration** - All tables need proper configuration for UPDATE/DELETE
20 3. **Aggressive monitoring** - Replication slots must be monitored to prevent operational issues
21 4. **decoder_raw installation** - Third-party plugin must be compiled and installed
22
23 ## Architecture Overview
24
25 ### High-Level Design
26
27 ```
28 ┌─────────────────────────────────────────────────────────────┐
29 │                    PostgreSQL Database                       │
30 │                                                              │
31 │  ┌────────────────┐         ┌──────────────────┐           │
32 │  │  Regular Tables│────────▶│  WAL (Write-Ahead│           │
33 │  │  (DML Changes) │         │       Log)       │           │
34 │  └────────────────┘         └──────────────────┘           │
35 │                                      │                       │
36 │                                      ▼                       │
37 │                         ┌─────────────────────────┐         │
38 │                         │ Logical Decoding Process│         │
39 │                         │  (decoder_raw plugin)   │         │
40 │                         └─────────────────────────┘         │
41 │                                      │                       │
42 └──────────────────────────────────────┼───────────────────────┘
43                                        │
44                                        ▼
45                         ┌─────────────────────────────┐
46                         │  Replication Slot           │
47                         │  (Tracks position, durable) │
48                         └─────────────────────────────┘
49                                        │
50                                        ▼
51                         ┌─────────────────────────────┐
52                         │    pg_recvlogical Tool     │
53                         │ (Built-in PostgreSQL util)  │
54                         └─────────────────────────────┘
55                                        │
56                         ┌──────────────┴──────────────┐
57                         ▼                             ▼
58           ┌─────────────────────┐      ┌─────────────────────┐
59           │  Incremental Files  │      │  Full pg_dump       │
60           │  (SQL Changes)      │      │  (Periodic)         │
61           │  - 2024-01-01.sql   │      │  - base-2024-01.sql │
62           │  - 2024-01-02.sql   │      │  - base-2024-02.sql │
63           │  - ...              │      │  - ...              │
64           └─────────────────────┘      └─────────────────────┘
65 ```
66
67 ### Core Components
68
69 1. **Logical Replication Slot**: Durable position tracker in PostgreSQL
70 2. **decoder_raw Plugin**: Transforms binary WAL to executable SQL
71 3. **pg_recvlogical**: Built-in PostgreSQL tool that streams logical decoding output
72 4. **Base Backup System**: Regular full `pg_dump` backups with `--snapshot` for consistency
73 5. **Schema Tracking System**: Event triggers + `pg_dumpall --globals-only` for DDL changes
74
75 ## How It Works
76
77 ### DML Capture via Logical Replication
78
79 PostgreSQL's logical replication decodes the Write-Ahead Log (WAL) into logical changes. The `decoder_raw` plugin outputs these directly as executable SQL:
80
81 ```sql
82 BEGIN;
83 INSERT INTO public.users (id, name, email) VALUES (1, 'Alice', 'alice@example.com');
84 UPDATE public.users SET name = 'Alice Smith' WHERE id = 1;
85 DELETE FROM public.orders WHERE id = 42;
86 COMMIT;
87 ```
88
89 **Key Properties**:
90 - **Crash-safe**: Replication slots persist position across crashes (positions persisted at checkpoint intervals; after crash, slot may return to earlier LSN causing recent changes to be replayed)
91 - **Consistent**: Transaction boundaries are preserved
92 - **Online**: Runs without blocking database operations
93 - **Idempotent positioning**: Can restart from last known position (clients responsible for handling duplicate messages)
94
95 ### DDL Capture via Event Triggers
96
97 Logical replication does **not** capture DDL (CREATE TABLE, ALTER TABLE, etc.). We solve this with event triggers:
98
99 ```sql
100 -- Create a table to log DDL changes
101 CREATE TABLE ddl_history (
102     id SERIAL PRIMARY KEY,
103     executed_at TIMESTAMP DEFAULT now(),
104     ddl_command TEXT,
105     object_type TEXT,
106     object_identity TEXT,
107     query TEXT
108 );
109
110 -- Create event trigger function
111 CREATE OR REPLACE FUNCTION log_ddl_changes()
112 RETURNS event_trigger AS $$
113 DECLARE
114     obj RECORD;
115 BEGIN
116     FOR obj IN SELECT * FROM pg_event_trigger_ddl_commands()
117     LOOP
118         INSERT INTO ddl_history (ddl_command, object_type, object_identity, query)
119         VALUES (obj.command_tag, obj.object_type, obj.object_identity, current_query());
120     END LOOP;
121 END;
122 $$ LANGUAGE plpgsql;
123
124 -- Register the event trigger
125 CREATE EVENT TRIGGER capture_ddl ON ddl_command_end
126     EXECUTE FUNCTION log_ddl_changes();
127 ```
128
129 **How it works**:
130 1. Event trigger captures all DDL commands
131 2. Stores actual query text in `ddl_history` table with timestamps
132 3. The `ddl_history` table is replicated via logical replication
133 4. During restore, apply DDL changes in chronological order
134
135 **Limitations**:
136 - Event triggers don't fire for shared objects: databases, roles (role definitions and role memberships), tablespaces, parameter privileges, and ALTER SYSTEM commands
137 - Solution: Use periodic `pg_dumpall --globals-only` to capture shared objects
138
139 ## Implementation Components
140
141 ### 1. Initial Setup Script
142
143 **Purpose**: Bootstrap the backup system
144
145 **Tasks**:
146 - Install decoder_raw plugin (compile from source)
147 - Create logical replication slot: `pg_recvlogical --create-slot --plugin=decoder_raw`
148 - Export the snapshot from the slot for consistency
149 - Take initial base backup: `pg_dump --snapshot=<exported_snapshot>`
150 - Set up event triggers for DDL capture
151 - Create initial `pg_dumpall --globals-only` backup
152 - Configure `REPLICA IDENTITY` on tables without primary keys
153 - Document PostgreSQL version and installed extensions
154
155 **Critical Detail - Initial Snapshot Consistency**:
156
157 From PostgreSQL documentation (Section 47.2.5):
158 > When a new replication slot is created using the streaming replication interface, a snapshot is exported which will show exactly the state of the database after which all changes will be included in the change stream.
159
160 This ensures the base backup and incremental stream are perfectly aligned with no gaps or overlaps.
161
162 ### 2. Incremental Backup Collection
163
164 **Tool**: Built-in `pg_recvlogical` utility with `decoder_raw` plugin
165
166 **Example Usage**:
167 ```bash
168 pg_recvlogical \
169     --dbname=mydb \
170     --slot=backup_slot \
171     --plugin=decoder_raw \
172     --start \
173     --file=/backups/incremental/changes.sql \
174     --option=include_transaction=on \
175     --fsync-interval=10
176 ```
177
178 **What pg_recvlogical provides**:
179 - Streams decoded changes continuously from the replication slot
180 - Handles connection failures and automatic reconnection
181 - Tracks LSN positions with status updates to the server
182 - Supports file rotation via SIGHUP signal
183 - Configurable fsync intervals for crash safety
184
185 **What decoder_raw provides**:
186 - ✅ Schema-qualified table names: `INSERT INTO public.users (...)`
187 - ✅ Proper column name quoting with `quote_identifier()`
188 - ✅ Transaction boundaries via `include_transaction=on` option (BEGIN/COMMIT)
189 - ✅ Intelligent replica identity handling (DEFAULT, INDEX, FULL, NOTHING)
190 - ✅ Comprehensive data type support (booleans, NaN, Infinity, NULL, strings, bit strings)
191 - ✅ TOAST optimization (skips unchanged TOAST columns in UPDATEs)
192 - ✅ Production-quality memory management
193
194 **Custom wrapper script tasks**:
195 - File rotation and timestamping
196 - Coordinate with monitoring system
197 - Metadata file generation (PostgreSQL version, extensions, encoding, collation)
198
199 ### 3. Periodic Full Backup Script
200
201 **Purpose**: Take regular full backups as restore points
202
203 **Tasks**:
204 - Execute `pg_dump` to create full database backup
205 - Execute `pg_dumpall --globals-only` to capture shared objects (databases, roles, tablespaces)
206 - Compress old backups to save space
207 - Implement retention policy (delete old backups)
208
209 ### 4. Restore Script
210
211 **Purpose**: Restore database from base + incremental backups
212
213 **Tasks**:
214 1. Locate most recent full backup
215 2. Find all incremental backups since that full backup
216 3. Recreate target database
217 4. Restore `pg_dumpall --globals-only` (shared objects)
218 5. Restore full `pg_dump` backup
219 6. Apply DDL changes from `ddl_history` in chronological order
220 7. Apply incremental SQL backups in chronological order
221 8. Synchronize sequence values: `SELECT setval('seq_name', (SELECT MAX(id) FROM table))`
222 9. Verify data integrity (checksums, row counts)
223
224 ### 5. Monitoring and Health Check Script
225
226 **Purpose**: Prevent operational issues from inactive replication slots
227
228 **Critical Metrics**:
229 ```sql
230 -- Check replication slot health
231 SELECT
232     slot_name,
233     slot_type,
234     database,
235     active,
236     pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)) as replication_lag,
237     pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) as confirmed_lag
238 FROM pg_replication_slots
239 WHERE slot_type = 'logical';
240 ```
241
242 **Alerting**:
243 - **Critical alert** when `restart_lsn` falls more than 1GB behind
244 - **Emergency alert** when slot lag exceeds 10GB or age exceeds 24 hours
245 - **Emergency procedure** documented to drop slot if it threatens database availability
246
247 **Available Tools**:
248 - **Prometheus + postgres_exporter + Grafana**: Open-source monitoring stack
249 - **pgDash** (https://pgdash.io/): Commercial PostgreSQL monitoring
250 - **check_postgres**: Nagios/Icinga/Zabbix integration
251 - **Built-in views**: `pg_replication_slots`, `pg_stat_replication_slots`
252
253 ## decoder_raw Plugin Details
254
255 **Source**: https://github.com/michaelpq/pg_plugins/tree/main/decoder_raw
256 **License**: PostgreSQL License (permissive, production-ready)
257 **Compatibility**: PostgreSQL 9.4+
258
259 **Installation**:
260 ```bash
261 # Install PostgreSQL development headers
262 apt-get install postgresql-server-dev-XX  # Debian/Ubuntu
263 yum install postgresql-devel              # RHEL/CentOS
264
265 # Clone and compile
266 git clone https://github.com/michaelpq/pg_plugins.git
267 cd pg_plugins/decoder_raw
268 make
269 sudo make install
270
271 # Verify installation
272 ls $(pg_config --pkglibdir)/decoder_raw.so
273 ```
274
275 **Why decoder_raw is essential**:
276 - Eliminates the entire SQL transformation layer
277 - Handles all data type escaping correctly (strings, NULL, NaN, Infinity, booleans)
278 - Produces production-ready SQL that can be executed with `psql -f changes.sql`
279 - Mature codebase with comprehensive test suite
280
281 **Known Limitation of decoder_raw Plugin**:
282 - ⚠️ **TRUNCATE not captured by decoder_raw**: While PostgreSQL logical replication supports TRUNCATE replication (documented in Section 29.8), the decoder_raw plugin does not implement the `truncate_cb` callback to output TRUNCATE statements
283 - **Workarounds** (all viable):
284   1. Event triggers CAN and WILL capture TRUNCATE as DDL (stored in `ddl_history` table) - **recommended approach**
285   2. Use `DELETE FROM table` instead of TRUNCATE (slower but captured by decoder_raw)
286   3. Contribute `truncate_cb` callback implementation to the decoder_raw project upstream
287 - **Important**: TRUNCATE operations will be captured and replicated via the event trigger mechanism, so this limitation has a complete workaround within this design
288
289 ## Key Challenges and Solutions
290
291 ### 1. Replica Identity Required for UPDATE/DELETE
292
293 **Problem**: Tables need replica identity for UPDATE/DELETE operations.
294
295 From PostgreSQL documentation (Section 29.1.1):
296 > Tables with a replica identity defined as `NOTHING`, `DEFAULT` without a primary key, or `USING INDEX` with a dropped index, **cannot support UPDATE or DELETE operations**. **Attempting such operations will result in an error on the publisher.**
297
298 This means UPDATE/DELETE will **fail on the source database**, not just during restore!
299
300 **Solution**: Ensure all tables have one of:
301 - A primary key (automatic replica identity)
302 - A unique index configured via `REPLICA IDENTITY USING INDEX index_name`
303 - Explicit `REPLICA IDENTITY FULL` setting (inefficient, last resort)
304
305 **Example**:
306 ```sql
307 -- Table without primary key will error on UPDATE/DELETE
308 CREATE TABLE logs (timestamp TIMESTAMPTZ, message TEXT);
309
310 -- Fix: Set replica identity to FULL
311 ALTER TABLE logs REPLICA IDENTITY FULL;
312 ```
313
314 ### 2. Replication Slots Prevent WAL Cleanup
315
316 **Problem**: Inactive replication slots prevent WAL cleanup, leading to:
317 1. Disk fills up (WAL files not cleaned)
318 2. Table bloat (VACUUM cannot clean old row versions)
319 3. **Database shutdown** (transaction ID wraparound)
320
321 From PostgreSQL documentation (Section 47.2.2):
322 > In extreme cases this could cause the database to shut down to prevent transaction ID wraparound.
323
324 **Solution**:
325 - **Monitor slot lag aggressively** (see monitoring section)
326 - Set `max_slot_wal_keep_size` parameter (PostgreSQL 13+) to limit WAL retention
327 - Have documented emergency procedure to drop slot if needed
328 - Consider `pg_replication_slot_advance()` to skip ahead (loses backup coverage)
329
330 ### 3. Sequences Are Not Replicated
331
332 **Problem**: Sequence values are not captured in logical replication.
333
334 **Solution**:
335 - Use `pg_dump --sequence-data` (enabled by default) in periodic full dumps
336 - After restore, synchronize sequences:
337   ```sql
338   SELECT setval('users_id_seq', (SELECT MAX(id) FROM users));
339   ```
340
341 ### 4. Large Objects Are Not Replicated
342
343 **Problem**: PostgreSQL large objects are not captured in logical replication.
344
345 **Solution**:
346 - **Preferred**: Use `BYTEA` columns instead (these ARE replicated)
347 - **Alternative**: Use `pg_dump --large-objects` in periodic full backups
348   - Note: Incremental changes to large objects NOT captured between full backups
349
350 ### 5. Crash Recovery and Duplicate Handling
351
352 **Problem**: After database crash, slot position may roll back, causing duplicate changes.
353
354 From PostgreSQL documentation (Section 47.2.2):
355 > The current position of each slot is persisted only at checkpoint, so in the case of a crash the slot might return to an earlier LSN, which will then cause recent changes to be sent again when the server restarts.
356
357 **Solution**: `pg_recvlogical` handles position tracking through status reporting and `--startpos` parameter. However, per PostgreSQL documentation (Section 47.2.2): "Logical decoding clients are responsible for avoiding ill effects from handling the same message more than once."
358
359 **Recommendations**:
360 - The restore process should be idempotent (safe to replay duplicate transactions)
361 - For exact-once semantics, consider tracking last applied LSN in metadata and using `--startpos` during restore
362 - Test crash scenarios to verify duplicate handling behaves acceptably
363
364 ### 6. Long-Term Readability
365
366 **Challenges**:
367 - PostgreSQL syntax may change between major versions (rare)
368 - Extension dependencies may not exist in future systems
369 - Encoding/collation definitions may change
370
371 **Solution**: Include metadata file with each backup:
372 - PostgreSQL version (full version string)
373 - All installed extension names and versions
374 - Database encoding
375 - Locale and collation settings
376 - Custom data types and enums
377
378 Periodically test restoring old backups on current PostgreSQL versions.
379
380 ## Prerequisites and Configuration
381
382 ### PostgreSQL Configuration
383
384 ```ini
385 # postgresql.conf
386
387 # Required: Set WAL level to logical
388 wal_level = logical
389
390 # Required: Allow at least one replication slot
391 max_replication_slots = 10
392
393 # Recommended: Allow replication connections
394 max_wal_senders = 10
395
396 # Recommended: Keep more WAL for safety
397 wal_keep_size = 1GB
398
399 # Recommended: Limit WAL retention for safety (PostgreSQL 13+)
400 max_slot_wal_keep_size = 10GB
401
402 # Optional: Tune checkpoint frequency to persist slot positions more often
403 checkpoint_timeout = 5min
404 ```
405
406 ### Client Requirements
407
408 - PostgreSQL client utilities installed (`pg_recvlogical`, `pg_dump`, `pg_dumpall`)
409 - Superuser or role with `REPLICATION` privilege
410 - Permission to create replication slots
411 - decoder_raw plugin compiled and installed
412
413 ## Operational Procedures
414
415 ### Backup Schedule
416
417 **Recommended**:
418 - **Incremental**: Continuously streaming via `pg_recvlogical`
419 - **Full backup**: Daily at 2 AM
420 - **Globals backup**: Daily (`pg_dumpall --globals-only`)
421 - **Metadata export**: Daily (PostgreSQL version, extensions, encoding)
422
423 ### Retention Policy
424
425 - **Incremental backups**: Keep 7 days
426 - **Full backups**: Keep 30 days, then one per month for 1 year
427 - **Monitor disk space**: Alert if backup directory exceeds 80% capacity
428
429 ### Disaster Recovery Runbook
430
431 1. **Stop application** to prevent new writes during restore
432 2. **Create new database** (don't overwrite production)
433 3. **Restore shared objects**: `psql -f globals-YYYY-MM-DD.sql`
434 4. **Restore full backup**: `psql dbname < base-YYYY-MM-DD.sql`
435 5. **Apply DDL changes**: Extract from `ddl_history` table, apply in chronological order
436 6. **Apply incremental backups**: `psql dbname < incremental-YYYY-MM-DD.sql` in order
437 7. **Sync sequences**: Run `setval()` for all sequences
438 8. **Verify data integrity**: Check row counts, checksums
439 9. **Test application** against restored database
440 10. **Switch over** application to restored database
441
442 ## Testing Strategy
443
444 ### 1. Basic Functionality Test
445
446 ```sql
447 -- Create test database and setup
448 CREATE DATABASE backup_test;
449 \c backup_test
450
451 -- Create test table
452 CREATE TABLE test_users (id SERIAL PRIMARY KEY, name TEXT, created_at TIMESTAMP DEFAULT now());
453
454 -- Generate data and schema changes
455 INSERT INTO test_users (name) VALUES ('Alice'), ('Bob'), ('Charlie');
456 UPDATE test_users SET name = 'Alice Smith' WHERE id = 1;
457 DELETE FROM test_users WHERE id = 3;
458 ALTER TABLE test_users ADD COLUMN email TEXT;
459 UPDATE test_users SET email = 'alice@example.com' WHERE id = 1;
460
461 -- Restore and verify
462 ```
463
464 ### 2. Crash Recovery Test
465
466 ```bash
467 # Start collecting incrementals
468 # Generate load with pgbench
469 # Simulate crash: pg_ctl stop -m immediate
470 # Restart PostgreSQL
471 # Verify no data loss and duplicates handled correctly
472 # Restore and verify
473 ```
474
475 ### 3. Long-Term Storage Test
476
477 ```bash
478 # Create backup
479 # Store backup files
480 # Wait (or simulate) years passing
481 # Restore on modern PostgreSQL version
482 # Verify SQL is still readable and executable
483 ```
484
485 ### 4. Replica Identity Test
486
487 ```sql
488 -- Create table without primary key
489 CREATE TABLE test_no_pk (col1 TEXT, col2 INT);
490
491 -- Attempt UPDATE (should fail with replica identity error)
492 UPDATE test_no_pk SET col2 = 5 WHERE col1 = 'test';
493
494 -- Fix with REPLICA IDENTITY FULL
495 ALTER TABLE test_no_pk REPLICA IDENTITY FULL;
496
497 -- Retry UPDATE (should succeed)
498 ```
499
500 ### 5. TRUNCATE Handling Test
501
502 ```sql
503 -- Create test table
504 CREATE TABLE test_truncate (id INT);
505 INSERT INTO test_truncate VALUES (1), (2), (3);
506
507 -- Perform TRUNCATE
508 TRUNCATE test_truncate;
509
510 -- Verify: Check if decoder_raw incremental backup captured TRUNCATE
511 -- Expected: NOT captured by decoder_raw (known plugin limitation)
512 -- Verify: Check if event trigger captured TRUNCATE as DDL in ddl_history table
513 -- Expected: SHOULD be captured by event trigger (this is the workaround)
514 SELECT * FROM ddl_history WHERE ddl_command = 'TRUNCATE TABLE' ORDER BY executed_at DESC LIMIT 1;
515 ```
516
517 ## Performance Considerations
518
519 **Write Amplification**:
520 - WAL must be written (normal)
521 - WAL must be decoded into logical format (additional CPU)
522 - Event triggers fire on every DDL operation (minimal overhead)
523
524 **Disk I/O**:
525 - Additional WAL volume retained by replication slots
526 - More frequent checkpoint I/O if checkpoint_timeout is tuned
527
528 **Recommendations**:
529 - Benchmark overhead on test system with production-like workload
530 - Monitor CPU usage of WAL sender processes
531 - Monitor disk usage for WAL and backup directories
532
533 ## Next Steps for Proof of Concept
534
535 1. **Install decoder_raw**
536    - Clone pg_plugins repository
537    - Install PostgreSQL development headers
538    - Compile and install decoder_raw
539    - Verify installation
540
541 2. **Initial Setup**
542    - Create replication slot with decoder_raw
543    - Set up event triggers for DDL capture
544    - Take initial synchronized base backup
545
546 3. **Streaming Collection**
547    - Test `pg_recvlogical` with decoder_raw
548    - Verify output is immediately executable SQL
549    - Test with various data types and operations
550
551 4. **DDL Handling**
552    - Test event trigger captures DDL correctly
553    - Test `pg_dumpall --globals-only` captures shared objects
554    - Verify coordination during restore
555
556 5. **Monitoring Setup**
557    - Configure replication slot monitoring
558    - Set up critical alerting
559    - Document emergency procedures
560
561 6. **Restore Process**
562    - Build restore scripts
563    - Test point-in-time recovery
564    - Verify sequence synchronization
565
566 7. **Crash Recovery**
567    - Test duplicate handling
568    - Verify LSN tracking
569
570 8. **Performance Testing**
571    - Measure storage overhead
572    - Measure CPU overhead
573    - Benchmark restore time
574
575 ## References
576
577 ### PostgreSQL Documentation
578 - PostgreSQL Documentation: Chapter 25 - Backup and Restore
579 - PostgreSQL Documentation: Chapter 29 - Logical Replication
580 - PostgreSQL Documentation: Chapter 47 - Logical Decoding
581 - PostgreSQL Documentation: Section 29.8 - Logical Replication Restrictions
582 - PostgreSQL Documentation: `pg_recvlogical` man page
583 - PostgreSQL Documentation: `pg_dump` man page
584 - PostgreSQL Documentation: `pg_dumpall` man page
585
586 ### Essential Tools
587 - **decoder_raw**: SQL output plugin for logical decoding
588   - Source: https://github.com/michaelpq/pg_plugins/tree/main/decoder_raw
589   - **CRITICAL COMPONENT**: Eliminates output transformation layer
590   - License: PostgreSQL License (production-ready)
591   - Compatibility: PostgreSQL 9.4+
592
593 ### Monitoring Tools
594 - **Prometheus + postgres_exporter + Grafana**: Open-source monitoring stack
595 - **pgDash**: PostgreSQL monitoring - https://pgdash.io/
596 - **check_postgres**: Nagios/Icinga/Zabbix integration
597 - **pg_stat_replication_slots**: Built-in PostgreSQL monitoring view