
If you have spent any time around backend development, data engineering, or even just dabbling with databases, you have probably seen the name PostgreSQL pop up everywhere. Companies like Apple, Instagram, Reddit, and Spotify all run PostgreSQL at serious scale. But for someone coming in fresh, the name alone looks intimidating, the setup can feel like a wall, and figuring out what it actually does versus other databases is harder than it should be.
This guide strips all of that away. We are going to cover what PostgreSQL is and why it exists, how it differs from other databases you might know, how to get it running on your machine, how to connect it to Python, what happens inside when you run queries, and a few real problems developers run into that nobody talks about honestly.
![]() |
1. What Is PostgreSQL? The Real Answer
PostgreSQL (often written as Postgres) is a free, open-source relational database management system. It was born out of a research project at UC Berkeley in 1986 called POSTGRES, led by professor Michael Stonebraker. The name PostgreSQL came later in 1996 when SQL support was formally added to the project.
It is maintained by the PostgreSQL Global Development Group, which is a community of volunteers and companies, not a single corporation. That is an important detail we will get back to in the business model section.
What makes PostgreSQL stand out from most databases is that it is object-relational. That means it supports standard relational database features like tables, foreign keys, and joins, but it also supports object-oriented concepts like table inheritance, user-defined types, and custom functions written in multiple languages.
What PostgreSQL Actually Does
At its core, PostgreSQL stores structured data and lets you query it using SQL. But beyond the basics, it handles:
- Full ACID compliance (Atomicity, Consistency, Isolation, Durability) even for complex multi-step operations
- Advanced data types like JSON, JSONB, arrays, hstore (key-value), ranges, UUID, and geometric types
- Full-text search built into the engine
- Triggers and stored procedures written in PL/pgSQL, Python, Perl, JavaScript (via extensions), and more
- Table partitioning for managing massive datasets
- Row-level security for fine-grained access control
- Logical replication and streaming replication for high availability setups
| Quick context: When people say PostgreSQL is “advanced”, they usually mean it handles edge cases that simpler databases skip. Things like concurrent writes, complex joins, JSON storage alongside relational data, and custom data types all work reliably without workarounds. |
A Brief Timeline Worth Knowing
| Year | What Happened |
| 1986 | POSTGRES research project starts at UC Berkeley |
| 1994 | SQL interpreter added by Jolly Chen and Andrew Yu |
| 1996 | Renamed to PostgreSQL, first public release (6.0) |
| 2005 | Version 8.0 – native Windows support added |
| 2010 | Version 9.0 – streaming replication introduced |
| 2017 | Version 10 – logical replication, better partitioning |
| 2023 | Version 16 released with performance and security improvements |
| 2024 | Version 17 released with major I/O and memory improvements |
Want to understand how databases fit into your broader web stack? Our guide on API integration in web development covers how backend services like PostgreSQL connect to front-end applications through APIs.
2. Is PostgreSQL a Relational Database?
Yes, PostgreSQL is a relational database. Specifically, it calls itself an object-relational database management system (ORDBMS). The relational part means data is stored in tables with rows and columns, relationships between tables are defined using keys, and you query data using SQL (Structured Query Language).
The object part means it extends the pure relational model with features borrowed from object-oriented programming. You can define custom types, inherit table structures, and write functions that behave like methods.
What Makes It Relational vs NoSQL
| Feature | Relational (PostgreSQL) | NoSQL (MongoDB, Redis, etc.) |
| Data structure | Tables with fixed schema | Documents, key-value, graphs |
| Query language | SQL (standardized) | Varies by database |
| Relationships | Foreign keys, joins | Usually embedded or denormalized |
| ACID compliance | Full by default | Varies, often eventual consistency |
| Schema | Defined upfront (strict) | Flexible or schema-less |
| Best for | Complex queries, reporting, transactions | High-speed reads, unstructured data |
PostgreSQL sits in an interesting middle ground. You can store JSON documents (using the JSONB type) alongside normalized relational data in the same database. This makes it one of the few databases where you genuinely get relational power and document flexibility without needing two separate systems.
If you are building a data-driven website and unsure which database suits you, our CMS development guide walks through how data layer choices affect the full stack.
3. Is PostgreSQL Free? What the Business Model Actually Looks Like
PostgreSQL is completely free. It is released under the PostgreSQL License, which is a permissive open-source license similar to the BSD or MIT license. This means you can download it, use it in commercial products, modify the source code, and distribute it, all without paying anything to anyone.
There are no paid tiers, no enterprise license walls, and no feature gates. The version you download for free is the same version running inside companies worth billions of dollars.
So Who Pays for PostgreSQL Development?
This is where the business model gets interesting. PostgreSQL itself has no company behind it, but a large ecosystem of companies contributes to and profits from it:
| Model | How It Works | Example Companies |
| Support contracts | Companies pay for professional support SLAs | EnterpriseDB (EDB), Crunchy Data |
| Managed hosting | Cloud providers run PostgreSQL for you and charge for infrastructure | AWS RDS, Google Cloud SQL, Supabase, Neon |
| Extended distributions | Companies ship PostgreSQL with extra proprietary features | EnterpriseDB Postgres Advanced Server |
| Consulting & training | Experts charge for implementation, tuning, and courses | Independent consultants worldwide |
| SaaS built on Postgres | Products built using Postgres as the backend | Timescale, Supabase, Neon |
| Key point: PostgreSQL the database is free forever. What costs money is support, hosting, and extra tooling built around it. You are never paying for the database engine itself. |
What About Cloud Managed PostgreSQL?
If you use AWS RDS for PostgreSQL, Google Cloud SQL, or Azure Database for PostgreSQL, you are paying for the cloud infrastructure and management layer, not the database itself. The PostgreSQL engine running underneath those services is the same open-source version you could install yourself.
Newer serverless options like Supabase and Neon have generous free tiers and offer PostgreSQL without server management. These are popular for startups and side projects.
4. PostgreSQL vs MySQL vs SQLite: Picking the Right Tool
Developers often get stuck choosing between PostgreSQL and MySQL, or wondering whether SQLite is good enough. Here is an honest side-by-side comparison based on real usage patterns:
| Criteria | PostgreSQL | MySQL | SQLite |
| License | PostgreSQL License (free) | GPL / Commercial dual | Public Domain (free) |
| Best use case | Complex applications, analytics, APIs | Web apps, CMS, high read volume | Embedded apps, local storage, testing |
| ACID compliance | Full | Full (InnoDB engine) | Full |
| JSON support | Excellent (JSONB) | Good (JSON type) | Basic |
| Full-text search | Built-in | Limited | Limited |
| Extensions | Large ecosystem (PostGIS, etc.) | Fewer | Very few |
| Concurrency | Excellent (MVCC) | Good | Poor (write lock) |
| Replication | Native (logical + streaming) | Native | Not supported |
| Setup complexity | Medium | Easy | None (file-based) |
| Community size | Very large | Very large | Large |
When to Actually Use PostgreSQL
- You need complex queries with multiple joins and subqueries
- Your application writes data often and needs strong consistency
- You want to store JSON and relational data in the same system
- You need geographic data (PostGIS extension is the industry standard)
- You are building something that will need to scale and you want to avoid migrating databases later
When PostgreSQL Is Overkill
- You are building a quick prototype that may never see production
- Your app is mostly read-heavy and MySQL already powers your stack
- You need an embedded database inside a mobile or desktop app (SQLite is better here)
5. How to Install PostgreSQL
PostgreSQL runs on Windows, macOS, and Linux. The installation steps differ by platform but the end result is the same: a running PostgreSQL server you can connect to.

How to Install PostgreSQL on Windows
- Go to https://www.postgresql.org/download/windows/ and download the installer from EnterpriseDB (EDB). This is the official interactive installer.
- Run the installer as Administrator. It will ask you to choose components: PostgreSQL Server, pgAdmin 4 (GUI tool), Stack Builder, and Command Line Tools. Install all of them for a full setup.
- Set a password for the default postgres superuser during installation. Write this down. You will need it.
- The default port is 5432. Keep it unless you have a conflict.
- After installation completes, PostgreSQL runs as a Windows service automatically.
- Open pgAdmin 4 from the Start menu to verify the server is running and accessible.
How to Install PostgreSQL on Mac
There are two main methods on macOS. The Homebrew method is preferred by developers because it integrates cleanly with the terminal:
Method 1: Homebrew (Recommended)
brew install postgresql@16
After installation:
brew services start postgresql@16
Connect to verify:
psql postgres
Method 2: Postgres.app
Download Postgres.app from https://postgresapp.com. This is a macOS-native application that runs PostgreSQL in your menu bar. It is the simplest way to get PostgreSQL running on a Mac without using the terminal at all. Move it to your Applications folder, open it, and click Initialize.
How to Install PostgreSQL on Linux (Ubuntu/Debian)
Ubuntu’s default repositories include PostgreSQL, but they often lag behind the latest version. Use the official PostgreSQL apt repository for the current version:
sudo apt install -y postgresql postgresql-contrib
Start and enable the service:
sudo systemctl start postgresql sudo systemctl enable postgresql
Switch to the postgres user and open the shell:
sudo -i -u postgres psql
| After any installation, verify your setup with: SELECT version(); inside the psql shell. It should return the PostgreSQL version string. |
6. How to Use PostgreSQL: Core Concepts You Cannot Skip
Getting PostgreSQL installed is step one. Understanding how it actually organizes data and how you interact with it is step two. Here are the fundamentals, explained plainly.
The psql Command Line Tool
psql is the official command-line interface for PostgreSQL. It ships with every installation. You do not need a GUI to use PostgreSQL, though one helps when you are exploring data visually.
| Command | What It Does |
| \l | List all databases |
| \c dbname | Connect to a specific database |
| \dt | List all tables in current database |
| \d tablename | Show table structure (columns, types, constraints) |
| \du | List all users/roles |
| \q | Quit psql |
| \? | Show all psql commands |
| \timing | Toggle query execution time display |
Databases, Schemas, and Tables
PostgreSQL organizes data in a three-level hierarchy that confuses beginners:
- A PostgreSQL server can host multiple databases. Each database is completely isolated from others.
- Inside a database, you have schemas. A schema is like a namespace. The default schema is called ‘public’. You can create separate schemas to organize tables logically, for example, separating ‘billing’ from ‘inventory’ within the same database.
- Inside a schema, you have tables. This is where your actual rows of data live.
Essential SQL Operations in PostgreSQL
Creating a database and table:
CREATE DATABASE myapp; \c myapp CREATE TABLE users ( id SERIAL PRIMARY KEY, email VARCHAR(255) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT NOW() );
Inserting, querying, updating, and deleting:
INSERT INTO users (email) VALUES (‘ali@example.com’); SELECT * FROM users WHERE email LIKE ‘%@example.com’; UPDATE users SET email = ‘ali@newdomain.com’ WHERE id = 1; DELETE FROM users WHERE id = 1;
Understanding Transactions
PostgreSQL wraps every statement in a transaction automatically. If you want to group multiple statements into one atomic operation:
BEGIN; UPDATE accounts SET balance = balance – 500 WHERE id = 1; UPDATE accounts SET balance = balance + 500 WHERE id = 2; COMMIT;
If something goes wrong between BEGIN and COMMIT, you can call ROLLBACK to cancel everything. This is the foundation of safe financial and transactional data operations.
If you are building a searchable data-driven website with PostgreSQL as the backend, our article on how to create a searchable database in WordPress gives practical context on how the data layer connects to the front end.
7. How to Connect Python to a PostgreSQL Database
Python and PostgreSQL work together in a lot of production systems. The most direct way to connect them is using the psycopg2 library, which is the most widely adopted PostgreSQL adapter for Python.

Step 1: Install psycopg2
pip install psycopg2-binary
The -binary version includes pre-built C extensions so you do not need to compile anything. For production deployments where you want more control, install psycopg2 without the -binary suffix, but that requires a C compiler and libpq development headers.
Step 2: Establish a Connection
import psycopg2 conn = psycopg2.connect( host=”localhost”, database=”myapp”, user=”postgres”, password=”your_password”, port=”5432″ ) print(“Connection successful”)
Step 3: Run Queries With a Cursor
cursor = conn.cursor() # Insert with parameterized query (prevents SQL injection) cursor.execute( “INSERT INTO users (email) VALUES (%s)”, (“user@example.com”,) ) conn.commit() # Fetch data cursor.execute(“SELECT id, email FROM users”) rows = cursor.fetchall() for row in rows: print(row) cursor.close() conn.close()
| Always use parameterized queries (%s placeholders) instead of string formatting to build SQL. String formatting opens your database to SQL injection attacks. This is not optional. |
Using a Context Manager (Cleaner Pattern)
import psycopg2 from contextlib import contextmanager @contextmanager def get_db_connection(): conn = psycopg2.connect( host=”localhost”, database=”myapp”, user=”postgres”, password=”your_password” ) try: yield conn conn.commit() except Exception: conn.rollback() raise finally: conn.close() with get_db_connection() as conn: with conn.cursor() as cur: cur.execute(“SELECT COUNT(*) FROM users”) count = cur.fetchone()[0] print(f”Total users: {count}”)
Connection Pooling for Production
Opening a new connection for every database operation is expensive. In production, you should use connection pooling. psycopg2 works with psycopg2.pool or with third-party poolers like pgBouncer:
from psycopg2 import pool connection_pool = pool.SimpleConnectionPool( minconn=1, maxconn=10, host=”localhost”, database=”myapp”, user=”postgres”, password=”your_password” ) conn = connection_pool.getconn() # use conn connection_pool.putconn(conn)
Alternative: SQLAlchemy ORM
If you prefer not to write raw SQL, SQLAlchemy is the most popular Python ORM that works with PostgreSQL. It abstracts the SQL layer into Python objects:
pip install sqlalchemy psycopg2-binary
from sqlalchemy import create_engine engine = create_engine( “postgresql://postgres:your_password@localhost:5432/myapp” ) with engine.connect() as conn: result = conn.execute(“SELECT * FROM users”) for row in result: print(row)
For a deeper look at how Python applications communicate with web infrastructure, see our guide on what is JSON response in WordPress which covers how data gets passed between backend databases and front-end layers.
8. Does a COMMIT in PostgreSQL Release Memory?
This is a question that comes up when developers notice high memory usage in long-running transactions and wonder if calling COMMIT will fix it. The short answer is: COMMIT does not directly release memory from the database server’s perspective, but it has indirect effects that matter.
What COMMIT Actually Does
When you run COMMIT in PostgreSQL:
- The transaction’s changes are durably written to disk (via the WAL – Write Ahead Log)
- Locks held by that transaction are released, allowing other transactions to proceed
- The transaction ID (XID) is marked as committed in the commit log
- The transaction’s snapshot is removed from the running transactions list
Memory and COMMIT: What Is Actually Happening
PostgreSQL uses a concept called MVCC (Multi-Version Concurrency Control). When you update or delete rows, PostgreSQL does not overwrite the old data. It keeps dead row versions (called dead tuples) on disk until they are removed by a process called AUTOVACUUM.
COMMIT does not trigger AUTOVACUUM or immediately reclaim disk space from dead tuples. What it does do:
- Releases the memory used to track the transaction’s state in shared_buffers
- Frees the lock table entries for that transaction
- Allows autovacuum to eventually clean up dead tuples from tables modified in that transaction
The Real Memory Concern: Long Transactions
The actual memory issue with long transactions is this: PostgreSQL cannot run AUTOVACUUM to clean up old row versions while a transaction is still open and might need to see those old versions. A transaction that stays open for hours will cause table bloat, not just memory pressure.
| If you are seeing memory pressure in PostgreSQL, the most common causes are: work_mem set too high for the number of parallel queries, large result sets being held in application memory, or connection pooling not being used (each idle connection holds memory). |
9. Does PostgreSQL Return SQL Error Codes for Duplicate Keys in Golang?
Yes, PostgreSQL does return specific SQL state error codes when a duplicate key violation occurs. This is important for Golang developers who need to detect this condition and handle it gracefully rather than treating all errors the same way.
The Error Code to Know
PostgreSQL uses SQLSTATE error codes. For a unique constraint violation (which is what a duplicate key triggers), the SQLSTATE code is 23505. This code is part of the SQL standard class 23, which covers integrity constraint violations.
Detecting It in Golang
In Go, you use the lib/pq driver (or pgx, the more modern alternative). Here is how to detect a duplicate key error:
import ( “database/sql” “github.com/lib/pq” ) _, err := db.Exec(“INSERT INTO users (email) VALUES ($1)”, email) if err != nil { if pqErr, ok := err.(*pq.Error); ok { if pqErr.Code == “23505” { // Duplicate key – handle this case specifically return fmt.Errorf(“email already exists”) } } // Other database errors return err }
Using pgx (Modern Alternative to lib/pq)
import ( “github.com/jackc/pgx/v5/pgconn” ) _, err := conn.Exec(ctx, “INSERT INTO users (email) VALUES ($1)”, email) if err != nil { var pgErr *pgconn.PgError if errors.As(err, &pgErr) && pgErr.Code == “23505” { return fmt.Errorf(“duplicate email: %w”, err) } return err }
Other PostgreSQL Error Codes Worth Knowing
| SQLSTATE Code | Condition | When It Triggers |
| 23505 | unique_violation | Duplicate key on UNIQUE or PRIMARY KEY constraint |
| 23503 | foreign_key_violation | Inserting/updating a FK that references a non-existent row |
| 23502 | not_null_violation | Inserting NULL into a NOT NULL column |
| 23514 | check_violation | A CHECK constraint fails |
| 40001 | serialization_failure | Transaction conflict in SERIALIZABLE isolation |
| 42P01 | undefined_table | Query references a table that does not exist |
| 28P01 | invalid_password | Authentication failure |
10. Common Mistakes Developers Make With PostgreSQL
These are real issues that appear in production systems, not textbook problems.
Mistake 1: Ignoring EXPLAIN ANALYZE
Most slow query problems in PostgreSQL come from missing indexes or the query planner making a bad choice. Running EXPLAIN ANALYZE before your query shows you exactly what PostgreSQL will do and how long each step takes. Developers who skip this spend hours guessing when the answer is right there.
Mistake 2: Using SERIAL Instead of IDENTITY
SERIAL (and BIGSERIAL) are older PostgreSQL-specific shortcuts for auto-incrementing primary keys. The modern standard, introduced in PostgreSQL 10, is GENERATED ALWAYS AS IDENTITY or GENERATED BY DEFAULT AS IDENTITY. These are SQL standard compliant and behave more predictably with replication and schema migrations.
Mistake 3: Skipping Connection Pooling
PostgreSQL spawns a new process for each connection. Spawning 200 processes for 200 concurrent users is expensive. Applications without connection pooling hit performance walls fast. Use pgBouncer at the infrastructure level or a pooler in your application framework.
Mistake 4: Storing Passwords in Plain Text
This is a general web development mistake, but it shows up in PostgreSQL-backed apps regularly. Always hash passwords before storing them. PostgreSQL itself provides pgcrypto for hashing, but your application layer (bcrypt, Argon2) should handle this before the data ever reaches the database.
Mistake 5: Not Tuning postgresql.conf
Fresh installations of PostgreSQL use conservative default settings because they have to work on any hardware. On a production server with 16GB RAM, the default shared_buffers (128MB) will leave 90% of your RAM unused for caching. At minimum, tune: shared_buffers, work_mem, effective_cache_size, and max_connections.
For performance optimization thinking that applies to your whole stack, see our post on how to speed up WordPress which covers caching, database optimization, and server tuning in one place.
If you’re building database-backed web applications and need a complete technology stack, our web development guide explains how the front-end, back-end, and database layers fit together.
Quick Reference Card: PostgreSQL Commands You Will Actually Use
| Task | Command |
| Connect to database | psql -U postgres -d mydb |
| List databases | \l |
| Create database | CREATE DATABASE mydb; |
| Create user | CREATE USER myuser WITH PASSWORD ‘pass’; |
| Grant permissions | GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser; |
| Create table | CREATE TABLE items (id SERIAL PRIMARY KEY, name TEXT NOT NULL); |
| Add index | CREATE INDEX idx_items_name ON items (name); |
| Check running queries | SELECT pid, query, state FROM pg_stat_activity; |
| Kill a query | SELECT pg_terminate_backend(pid); |
| Vacuum a table | VACUUM ANALYZE items; |
| Backup database | pg_dump mydb > backup.sql |
| Restore database | psql mydb < backup.sql |
Sources & Further Reading
Everything in this article is based on official documentation and established technical sources:
- PostgreSQL Official Documentation: https://www.postgresql.org/docs/
- PostgreSQL License Details: https://www.postgresql.org/about/licence/
- psycopg2 Documentation: https://www.psycopg.org/docs/
- PostgreSQL Error Codes Reference: https://www.postgresql.org/docs/current/errcodes-appendix.html
- pgx Driver for Golang: https://github.com/jackc/pgx
- MVCC in PostgreSQL: https://www.postgresql.org/docs/current/mvcc.html
- PostgreSQL Wiki: https://wiki.postgresql.org/wiki/Main_Page
We use strategic solutions and industry-leading technology to create websites that reflect your brand’s unique identity and grow your customer base and revenue.

