quality: completeness: 50 accuracy: 70 reviewed: false reviewer: 'KI (Gemini)' reviewDate: null
PostgreSQL/PostGIS
PostgreSQL with the PostGIS extension is the central geodatabase for p2d2.
Installation
PostgreSQL 15
# On Debian/Ubuntu
apt install postgresql-15 postgresql-client-15
# Start service
systemctl enable postgresql
systemctl start postgresqlPostGIS 3.4
# Install PostGIS package
apt install postgresql-15-postgis-3
# Enable in database
sudo -u postgres psql -d p2d2 -c "CREATE EXTENSION postgis;"
sudo -u postgres psql -d p2d2 -c "CREATE EXTENSION postgis_topology;"Database Setup
Create Database and User
-- As postgres user
CREATE USER p2d2 WITH PASSWORD 'secure_password';
CREATE DATABASE p2d2 OWNER p2d2;
-- In p2d2 database
\c p2d2
CREATE EXTENSION postgis;
CREATE EXTENSION postgis_topology;
CREATE EXTENSION fuzzystrmatch;
CREATE EXTENSION postgis_tiger_geocoder;Create Schema
-- Main schema for features
CREATE SCHEMA features;
GRANT ALL ON SCHEMA features TO p2d2;
-- Schema for metadata
CREATE SCHEMA metadata;
GRANT ALL ON SCHEMA metadata TO p2d2;
-- Schema for versioning
CREATE SCHEMA history;
GRANT ALL ON SCHEMA history TO p2d2;Create Tables
Feature Table
CREATE TABLE features.friedhoefe (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
kategorie VARCHAR(50),
adresse TEXT,
telefon VARCHAR(50),
email VARCHAR(100),
webseite VARCHAR(255),
oeffnungszeiten TEXT,
beschreibung TEXT,
geom GEOMETRY(MultiPolygon, 4326),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
created_by VARCHAR(100),
updated_by VARCHAR(100),
status VARCHAR(20) DEFAULT 'draft',
qc_status VARCHAR(20),
qc_by VARCHAR(100),
qc_at TIMESTAMP
);
-- Spatial index
CREATE INDEX idx_friedhoefe_geom ON features.friedhoefe USING GIST(geom);
-- Attribute indices
CREATE INDEX idx_friedhoefe_name ON features.friedhoefe(name);
CREATE INDEX idx_friedhoefe_status ON features.friedhoefe(status);Versioning Table
CREATE TABLE history.friedhoefe_history (
history_id SERIAL PRIMARY KEY,
feature_id INTEGER,
operation VARCHAR(10),
old_data JSONB,
new_data JSONB,
changed_by VARCHAR(100),
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);Trigger for Versioning
-- Trigger function
CREATE OR REPLACE FUNCTION history.log_changes()
RETURNS TRIGGER AS $$
BEGIN
IF (TG_OP = 'UPDATE') THEN
INSERT INTO history.friedhoefe_history (feature_id, operation, old_data, new_data, changed_by)
VALUES (OLD.id, 'UPDATE', row_to_json(OLD), row_to_json(NEW), NEW.updated_by);
RETURN NEW;
ELSIF (TG_OP = 'DELETE') THEN
INSERT INTO history.friedhoefe_history (feature_id, operation, old_data, changed_by)
VALUES (OLD.id, 'DELETE', row_to_json(OLD), OLD.updated_by);
RETURN OLD;
ELSIF (TG_OP = 'INSERT') THEN
INSERT INTO history.friedhoefe_history (feature_id, operation, new_data, changed_by)
VALUES (NEW.id, 'INSERT', row_to_json(NEW), NEW.created_by);
RETURN NEW;
END IF;
END;
$$ LANGUAGE plpgsql;
-- Create trigger
CREATE TRIGGER friedhoefe_audit
AFTER INSERT OR UPDATE OR DELETE ON features.friedhoefe
FOR EACH ROW EXECUTE FUNCTION history.log_changes();Performance Tuning
postgresql.conf
# Shared Buffers (25% of RAM)
shared_buffers = 8GB
# Effective Cache (50-75% of RAM)
effective_cache_size = 24GB
# Work Memory
work_mem = 256MB
# Maintenance Work Memory
maintenance_work_mem = 2GB
# Max Parallel Workers
max_parallel_workers_per_gather = 4
max_parallel_workers = 8
# Random Page Cost (SSD)
random_page_cost = 1.1
# Logging
log_min_duration_statement = 1000
log_line_prefix = '%t [%p]: [%l-1] user=%u,db=%d,app=%a,client=%h 'Connection Pooling with PgBouncer
# Install PgBouncer
apt install pgbouncer
# /etc/pgbouncer/pgbouncer.ini
[databases]
p2d2 = host=localhost port=5432 dbname=p2d2
[pgbouncer]
listen_addr = 0.0.0.0
listen_port = 6432
auth_type = md5
auth_file = /etc/pgbouncer/userlist.txt
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 100Backup Strategy
Logical Backup (pg_dump)
# Full dump
pg_dump -U p2d2 -F c -b -v -f p2d2_$(date +%Y%m%d).backup p2d2
# Schema only
pg_dump -U p2d2 -s -f p2d2_schema.sql p2d2
# Data only of one table
pg_dump -U p2d2 -t features.friedhoefe -f friedhoefe_data.sql p2d2Physical Backup (pg_basebackup)
# Base backup for PITR
pg_basebackup -U replication -D /backup/pg_base -Fp -Xs -P
# Enable WAL archiving
# postgresql.conf:
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /backup/wal/%f && cp %p /backup/wal/%f'Automatic Backup
# /usr/local/bin/backup-p2d2.sh
#!/bin/bash
BACKUP_DIR="/backup/postgresql"
DATE=$(date +%Y%m%d_%H%M%S)
pg_dump -U p2d2 -F c -f $BACKUP_DIR/p2d2_$DATE.backup p2d2
# Delete old backups (older than 30 days)
find $BACKUP_DIR -name "p2d2_*.backup" -mtime +30 -delete
# Cronjob: daily at 2 AM
# 0 2 * * * /usr/local/bin/backup-p2d2.shMonitoring
pg_stat_statements
-- Enable extension
CREATE EXTENSION pg_stat_statements;
-- Slowest queries
SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 10;Prometheus Exporter
# Install postgres_exporter
docker run -d \
--name postgres-exporter \
-p 9187:9187 \
-e DATA_SOURCE_NAME="postgresql://p2d2:password@localhost:5432/p2d2?sslmode=disable" \
prometheuscommunity/postgres-exporterMaintenance
VACUUM and ANALYZE
# Automatic VACUUM
# postgresql.conf:
autovacuum = on
autovacuum_naptime = 1min
# Manual VACUUM
VACUUM ANALYZE features.friedhoefe;
# VACUUM FULL (locks table!)
VACUUM FULL features.friedhoefe;Reindex
-- Rebuild index
REINDEX INDEX idx_friedhoefe_geom;
-- All indices of a table
REINDEX TABLE features.friedhoefe;VACUUM FULL
VACUUM FULL locks the table exclusively and can take a long time! Only perform during maintenance windows.