PostgreSQL β The World's Most Advanced Open-Source Database
Overview
PostgreSQL (also known as Postgres or PG) is a powerful, open-source object-relational database system with over 40 years of active development. Born at UC Berkeley in 1985 as a successor to the Ingres database, it has earned a reputation for reliability, feature robustness, and extensibility. PostgreSQL supports both relational (SQL) and non-relational (JSON) querying, making it a versatile choice for applications of any scale.
PostgreSQL uses Multi-Version Concurrency Control (MVCC) β every transaction gets a snapshot of the data, allowing extremely efficient concurrent reads without blocking writes.
[!TIP] PostgreSQL is often called "the Toyota of databases" β it's battle-tested, fast, and powers most major platforms. It has consistently ranked as the most loved database in developer surveys.
Installation
Linux (Debian / Ubuntu)
# Install PostgreSQL
sudo apt update
sudo apt install postgresql postgresql-contrib
# Start the service
sudo systemctl start postgresql
sudo systemctl enable postgresql
# Verify installation
psql --version
Linux (RHEL / Fedora / Rocky)
# Install PostgreSQL
sudo dnf install postgresql-server postgresql-contrib
# Initialize and start
sudo postgresql-setup --initdb
sudo systemctl start postgresql
sudo systemctl enable postgresql
macOS (Homebrew)
Docker
docker run --name pg -e POSTGRES_PASSWORD=mysecretpassword -p 5432:5432 -d postgres:16
psql -h localhost -U postgres -d postgres
[!NOTE] The default port is
5432. The default database created on init is namedpostgres.
Connect to PostgreSQL
# Full explicit form:
psql -h localhost -p 5432 -U <username> -d postgres
# Shortcut (same as above with default user):
psql postgres
CLI Tools Comparison
| Tool | Description |
|---|---|
psql |
Official PostgreSQL CLI, minimal but powerful |
pgcli |
Third-party CLI with syntax highlighting and auto-completion |
Install pgcli:
Useful psql Meta-Commands
| Command | Description |
|---|---|
\l |
List all databases |
\dt |
List all tables in current database |
\d <table> |
Describe table schema |
\du |
List roles/users |
\c <dbname> |
Connect to another database |
\q |
Quit |
\! <cmd> |
Execute shell command (e.g., \! clear) |
Core SQL Features
Creating a Database and Table
CREATE DATABASE myapp;
\c myapp
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT NOW()
);
INSERT INTO users (name, email) VALUES
('Alice', 'alice@example.com'),
('Bob', 'bob@example.com');
SELECT * FROM users;
Composite Types
PostgreSQL supports custom composite types β essentially user-defined structured data types stored in a single column.
CREATE TYPE address AS (
street TEXT,
city TEXT
);
CREATE TABLE customers (
name TEXT,
shipping_to address
);
-- Insert with ROW constructor
INSERT INTO customers VALUES ('Homer Simpson', ROW('742 Evergreen Terrace', 'Springfield'));
-- Insert without ROW (implicit)
INSERT INTO customers VALUES ('Marge Simpson', ('742 Evergreen Terrace', 'Springfield'));
-- Query nested fields
SELECT name, (shipping_to).city FROM customers;
-- Filter by nested field
SELECT * FROM customers WHERE (shipping_to).city = 'Springfield';
JSON / JSONB β Unstructured Data
PostgreSQL can store and query JSON documents, rivaling NoSQL databases.
JSONβ stores an exact copy of the input text (preserves formatting)JSONBβ stores a decomposed binary format (slower to insert, but indexable and queryable)
CREATE TABLE users_json (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
profile JSONB
);
INSERT INTO users_json (name, profile) VALUES
('Homer Simpson', '{"city": "Springfield", "age": 39, "occupation": "Safety Inspector"}');
-- Query JSON fields with -> and ->> operators
SELECT name, profile->>'city' AS city FROM users_json;
SELECT * FROM users_json WHERE profile->>'city' = 'Springfield';
-- Check if a key exists (? operator)
SELECT * FROM users_json WHERE profile ? 'occupation';
-- Update a JSON field
UPDATE users_json
SET profile = JSONB_SET(profile, '{age}', '20'::JSONB)
WHERE name = 'Homer Simpson';
| Operator | Description | Example |
|---|---|---|
-> |
Get JSON field as JSON | profile->'city' |
->> |
Get JSON field as text | profile->>'city' |
? |
Does key exist? | profile ? 'occupation' |
@> |
Does JSON contain? | profile @> '{"city":"Springfield"}' |
Transactions and MVCC
PostgreSQL's MVCC model gives each transaction a consistent snapshot of data. Writes do not block reads, but concurrent writes block each other.
-- Session 1
BEGIN;
SELECT * FROM users; -- sees snapshot
UPDATE users SET name = 'Lisa' WHERE id = 1;
-- Don't commit yet
-- Session 2 (concurrent)
BEGIN;
SELECT * FROM users; -- sees original data (not Session 1's uncommitted change)
COMMIT;
-- Back in Session 1
COMMIT; -- now Session 2 sees the change on next read
-- or
ROLLBACK; -- discard changes
[!TIP] Use
BEGIN/COMMIT/ROLLBACKto safely test data manipulations on a snapshot before making them permanent.
Major Extensions
Extensions are PostgreSQL's superpower. They let you turn PG into a full backend stack: cache server, message queue, job scheduler, geospatial database, vector database, time-series database, and more.
Installing Extensions
# Most extensions come with postgresql-contrib
# For community extensions, use PGXN:
sudo apt install postgresql-16-pgxn # Debian/Ubuntu
pgxn install <extension_name>
Once installed at the OS level, enable it inside the database:
To see all available and installed extensions:
1. pg_stat_statements β Query Performance Monitoring
Purpose: Tracks execution statistics of all SQL statements for performance tuning.
CREATE EXTENSION pg_stat_statements;
-- Find the top 5 slowest queries
SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 5;
Installation:
2. uuid-ossp β UUID Generation
Purpose: Generate universally unique identifiers.
3. hstore β Key-Value Store
Purpose: Store sets of key-value pairs within a single column.
CREATE EXTENSION hstore;
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT,
attributes hstore
);
INSERT INTO products (name, attributes) VALUES
('Laptop', 'color => silver, weight => 1.5kg, ram => 16GB');
-- Query keys
SELECT name, attributes->'color' AS color FROM products;
SELECT * FROM products WHERE attributes ? 'ram';
SELECT * FROM products WHERE attributes @> 'color => silver';
4. PostGIS β Geospatial Data
Purpose: Store, index, and query geographic objects. The most advanced open-source geospatial database extension.
CREATE EXTENSION postgis;
CREATE TABLE coffee_shops (
id SERIAL PRIMARY KEY,
name TEXT,
location GEOGRAPHY(Point, 4326)
);
-- Create spatial index
CREATE INDEX idx_coffee_location ON coffee_shops USING GIST (location);
-- Insert locations (longitude, latitude)
INSERT INTO coffee_shops (name, location) VALUES
('Central Perk', ST_SetSRID(ST_MakePoint(-73.9857, 40.7484), 4326)),
('Blue Bottle', ST_SetSRID(ST_MakePoint(-73.9860, 40.7490), 4326));
-- Find nearest coffee shop (distance in meters)
SELECT name, ST_Distance(
location,
ST_SetSRID(ST_MakePoint(-73.9855, 40.7480), 4326)::GEOGRAPHY
) AS distance_meters
FROM coffee_shops
ORDER BY location <-> ST_SetSRID(ST_MakePoint(-73.9855, 40.7480), 4326)::GEOGRAPHY
LIMIT 1;
[!NOTE] PostGIS is production-grade β used by NASA, the US Census Bureau, and millions of mapping applications worldwide.
5. pgvector β Vector / Embedding Search
Purpose: Store and query vector embeddings for AI/ML similarity search (semantic search, RAG, recommendations).
# Installation
cd /tmp
git clone --branch v0.7.0 https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make install
CREATE EXTENSION vector;
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT,
embedding VECTOR(384) -- 384-dimensional embeddings
);
-- Create an index for fast approximate nearest neighbor search
CREATE INDEX ON documents USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
-- Insert embeddings
INSERT INTO documents (content, embedding) VALUES
('PostgreSQL is a powerful database', '[0.1, 0.2, ...]'::VECTOR(384)),
('Vector search enables semantic lookup', '[0.3, 0.1, ...]'::VECTOR(384));
-- Similarity search (nearest neighbors by cosine distance)
SELECT content, 1 - (embedding <=> '[0.15, 0.18, ...]'::VECTOR(384)) AS similarity
FROM documents
ORDER BY embedding <=> '[0.15, 0.18, ...]'::VECTOR(384)
LIMIT 5;
| Operator | Distance Type |
|---|---|
<-> |
Euclidean (L2) |
<=> |
Cosine |
<#> |
Inner product (dot) |
6. pg_cron β Job Scheduling
Purpose: Run scheduled maintenance and automation jobs inside the database itself.
[!WARNING]
pg_cronrequires adding it toshared_preload_librariesinpostgresql.confand a server restart.
-- Enable (requires config change + restart first)
CREATE EXTENSION pg_cron;
-- Schedule a job that runs every hour
SELECT cron.schedule('hourly-cache-cleanup', '0 * * * *', $$CALL expire_rows(60)$$);
-- View scheduled jobs
SELECT * FROM cron.job;
-- Remove a scheduled job
SELECT cron.unschedule('hourly-cache-cleanup');
Configuration (postgresql.conf):
[!NOTE] Find your config file location with:
SHOW config_file;
7. pgmq β Message Queue
Purpose: Lightweight message queue with API parity to AWS SQS.
CREATE EXTENSION pgmq;
-- Create a queue
SELECT pgmq.create('my_queue');
-- Send a message
SELECT pgmq.send('my_queue', '{"task": "process_order", "order_id": 123}');
-- Read a message (with visibility timeout β invisible for N seconds)
SELECT pgmq.read('my_queue', 30, 1);
-- Pop a message (read + delete immediately)
SELECT pgmq.pop('my_queue');
-- Archive a processed message
SELECT pgmq.archive('my_queue', 1);
-- Delete a message after processing
SELECT pgmq.delete('my_queue', 1);
[!TIP] Use
pgmq.read()with a visibility timeout for reliable processing β if the worker crashes, the message reappears after the timeout. Usepgmq.pop()only when you're sure processing will succeed.
8. TimescaleDB β Time-Series Data
Purpose: Turn PostgreSQL into a time-series database, comparable to InfluxDB. Auto-partitions (chunks) data by time.
# Installation
sudo apt install timescaledb-2-postgresql-16
# Then add to shared_preload_libraries and restart
CREATE EXTENSION timescaledb;
CREATE TABLE sensor_data (
time TIMESTAMPTZ NOT NULL,
sensor_id INTEGER,
temperature FLOAT,
humidity FLOAT
);
-- Convert to hypertable (auto-partitions by time)
SELECT create_hypertable('sensor_data', 'time');
-- Time-bucketed aggregation
SELECT time_bucket('1 hour', time) AS hour,
sensor_id,
AVG(temperature) AS avg_temp
FROM sensor_data
WHERE time > NOW() - INTERVAL '7 days'
GROUP BY hour, sensor_id
ORDER BY hour DESC;
9. pg_trgm β Fuzzy Text Search
Purpose: Fast fuzzy string matching using trigrams. Replaces basic full-text search needs.
CREATE EXTENSION pg_trgm;
-- Find similar strings
SELECT similarity('postgresql', 'postgres');
-- GIN index for fast fuzzy lookups
CREATE INDEX ON articles USING GIN (title gin_trgm_ops);
-- Fuzzy search
SELECT * FROM articles WHERE title % 'postgress';
10. unaccent β Accent-Insensitive Search
Purpose: Remove diacritics from text for accent-insensitive search.
CREATE EXTENSION unaccent;
SELECT unaccent('PostgreSQL est gΓ©nial'); -- 'PostgreSQL est genial'
-- Create a deterministic collation with unaccent
CREATE INDEX ON users (unaccent(name));
SELECT * FROM users WHERE unaccent(name) ILIKE unaccent('%genial%');
Advanced: Cache with Unlogged Tables
Unlogged tables skip the Write-Ahead Log (WAL), making them significantly faster for cache-like use cases. They are automatically truncated on crash (acceptable for cache).
-- Create an unlogged cache table
CREATE UNLOGGED TABLE cache (
key TEXT PRIMARY KEY,
value JSONB,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_cache_created ON cache (created_at);
-- Procedure to expire old entries
CREATE OR REPLACE PROCEDURE expire_rows(retention_minutes INT)
LANGUAGE SQL AS
$$
DELETE FROM cache
WHERE created_at < NOW() - (retention_minutes || ' minutes')::INTERVAL;
$$;
-- Schedule with pg_cron (runs every hour)
SELECT cron.schedule('cache-expiry', '0 * * * *', $$CALL expire_rows(60)$$);
Advanced: Pub/Sub with LISTEN / NOTIFY
PostgreSQL can act as a lightweight message broker using built-in LISTEN/NOTIFY.
-- Session 1 (listener)
LISTEN new_jobs;
-- Session 2 (notifier)
CREATE TABLE jobs (
id SERIAL PRIMARY KEY,
task TEXT,
processed BOOLEAN DEFAULT FALSE
);
INSERT INTO jobs (task) VALUES ('Process order 123');
NOTIFY new_jobs, 'New job created: 1';
-- Back in Session 1 β a notification is received on the channel
[!NOTE] For production-grade queues, use
pgmqinstead of rawLISTEN/NOTIFY. The extension handles delivery guarantees, visibility timeouts, and dead-letter queues.
Configuration
Finding the Config File
Performance Tuning
Key parameters to tweak (adjust based on available memory):
# ~25% of total RAM
shared_buffers = 2GB
# ~75% of total RAM for sorts/hash
work_mem = 128MB
# WAL size
wal_buffers = 64MB
# Max parallel workers (one per CPU core)
max_parallel_workers_per_gather = 4
[!TIP] Use PGTune to generate optimized configurations for your server specs.
Best Practices
- Always use
JSONBoverJSONβ JSONB is indexable and queryable; JSON is just text. - Use
SERIALorIDENTITYfor auto-incrementing primary keys. - Create indexes on columns used in
WHERE,JOIN, andORDER BY. - Use
EXPLAIN ANALYZEto understand query plans before optimizing. - For time-series data, use TimescaleDB hypertables.
- For geospatial data, use PostGIS β not manual lat/lng columns.
- Use
pg_stat_statementsto identify slow queries in production. - Backup regularly with
pg_dumporpg_basebackup. - Monitor connection limits β each connection consumes ~10MB of RAM.
- Use connection poolers (PgBouncer, Pgpool-II) in production.