How to Understand the Purpose of Commit Logs in PostgreSQL

PostgreSQL uses several internal logging mechanisms to maintain consistency, durability, and crash recovery. Two of the most important mechanisms are Write-Ahead Logging (WAL) and the Commit Log, also known as CLOG. While WAL is widely discussed, the commit log often remains less understood even though it plays a critical role in PostgreSQL’s transaction management.

This article explores what commit logs are, where they are stored, how PostgreSQL tracks transaction states internally, and how commit logs differ from WAL.

What Is a Commit Log in PostgreSQL?

In PostgreSQL, the commit log records the final status of each transaction. It tells PostgreSQL whether a transaction:

  • committed successfully
  • Aborted
  • is still in progress
  • or was sub-committed

This information allows PostgreSQL to determine the visibility of rows for other transactions.

Internally, PostgreSQL calls this system CLOG (Commit Log). In modern PostgreSQL versions, the files storing this information are located in the pg_xact directory inside the data directory.

For example, in a PostgreSQL installation, you may see something like:

cybrosys@cybrosys:~$ pg_lsclusters 
Ver Cluster Port Status Owner    Data directory              Log file
18  main    5432 online postgres /var/lib/postgresql/18/main /var/log/postgresql/postgresql-18-main.log

The actual database storage directory can then be inspected.

sudo su postgres
cd /var/lib/postgresql/18/main
ls

Among the many internal directories, you will find:

postgres@cybrosys:~/18/main$ ls
base pg_commit_ts  pg_logical    pg_notify pg_serial     pg_stat     pg_subtrans  pg_twophase  pg_wal   postgresql.auto.conf
global pg_dynshmem   pg_multixact  pg_replslot  pg_snapshots  pg_stat_tmp  pg_tblspc PG_VERSION   pg_xact 

The pg_xact directory contains the commit logs.

cd pg_xact
ls
0000
0001

Each file in this directory stores transaction status bits for a range of transaction IDs.

How PostgreSQL Tracks Transaction Status

Every transaction in PostgreSQL receives a Transaction ID (XID). PostgreSQL keeps track of the status of these XIDs inside the commit log.

For example, you can check the current transaction ID with:

SELECT pg_current_xact_id();

Example result:

pg_current_xact_id 
--------------------
             109662

This means the current transaction is using transaction ID 109662.

PostgreSQL will record the final state of this transaction in the commit log when the transaction ends.

Transaction Status Values in PostgreSQL

Inside the PostgreSQL source code, transaction states are defined in clog.h. Each transaction is stored using two bits representing its status.

#define TRANSACTION_STATUS_IN_PROGRESS      0x00
#define TRANSACTION_STATUS_COMMITTED        0x01
#define TRANSACTION_STATUS_ABORTED          0x02
#define TRANSACTION_STATUS_SUB_COMMITTED    0x03

These states represent the lifecycle of a transaction.

IN_PROGRESS

The transaction has started but has not yet been completed. Other transactions may need to wait or check visibility rules.

COMMITTED

The transaction completed successfully. Any rows written by this transaction become visible to other transactions depending on their isolation level.

ABORTED

The transaction failed or was rolled back. Any changes made by this transaction must be ignored.

SUB_COMMITTED

This state occurs when a subtransaction commits but the parent transaction has not yet finished.

Because each transaction only requires two bits of storage, PostgreSQL can efficiently track millions of transaction statuses with minimal space.

Structure of the pg_xact Directory

The pg_xact directory contains files that store transaction status bits sequentially.

Each file represents a range of transaction IDs. Instead of storing one record per file, PostgreSQL packs many transaction statuses into a single page. This design significantly reduces storage overhead.

For example:

pg_xact/
0000
0001
0002

Each file contains multiple pages, and each page stores the status of many transactions.

When PostgreSQL needs to determine whether a transaction is committed or aborted, it checks the appropriate location in the pg_xact files.

Why Commit Logs Are Important

Commit logs are essential for PostgreSQL’s MVCC (Multi-Version Concurrency Control) mechanism.

When a row is inserted or updated, PostgreSQL stores the transaction ID in the tuple header. Later, when another transaction reads that row, PostgreSQL must determine whether the inserting transaction was committed.

To do this, PostgreSQL checks the commit log.

If the inserting transaction is marked committed, the row may be visible.

If it is aborted, the row must be ignored.

Without commit logs, PostgreSQL would not be able to determine tuple visibility efficiently.

WAL vs Commit Log

Although both WAL and commit logs are related to transactions, they serve completely different purposes.

WAL (Write-Ahead Log)

WAL records every change made to the database. This includes:

  • tuple inserts
  • Updates
  • Deletes
  • page modifications
  • commit records

The main purpose of WAL is crash recovery and durability.

Before modifying actual data files, PostgreSQL writes the change to WAL. If the server crashes, PostgreSQL can replay WAL records to restore the database to a consistent state.

WAL files are stored in the pg_wal directory.

Commit Log (CLOG)

The commit log does not store detailed changes. Instead, it only stores the final outcome of transactions.

Its primary role is transaction visibility management.

When PostgreSQL checks whether a row should be visible to a transaction, it reads the transaction status from the commit log.

The commit log is stored in the pg_xact directory.

Relationship Between WAL and Commit Log

When a transaction commits, PostgreSQL performs several steps internally.

First, it writes a commit record to WAL. This ensures the commit is durable and can be recovered if a crash occurs.

Next, PostgreSQL updates the commit log to mark the transaction as committed.

This means WAL ensures durability, while the commit log ensures correct visibility for other transactions.

Internal Workflow of a Commit

A simplified sequence of events during a transaction commit looks like this:

  1. Transaction performs inserts or updates.
  2. WAL records are generated for these changes.
  3. A commit record is written to WAL.
  4. PostgreSQL flushes WAL to disk.
  5. The commit log is updated to mark the transaction as committed.
  6. Other transactions can now see the committed data.

This coordination ensures both durability and consistency.

Why PostgreSQL Uses a Separate Commit Log

Instead of scanning WAL to determine transaction states, PostgreSQL uses a dedicated commit log for efficiency.

Reading WAL for visibility checks would be expensive and slow. By storing transaction states in a compact structure, PostgreSQL can quickly determine whether a transaction is committed or aborted.

This design is especially important for high-concurrency systems where thousands of transactions may be active simultaneously.

Commit logs are a fundamental part of PostgreSQL’s transaction system. Stored in the pg_xact directory, they track the final state of every transaction using compact status bits.

While WAL records the actual changes made to the database and ensures durability, the commit log records whether each transaction was committed or aborted. PostgreSQL relies on this information to implement MVCC and maintain correct row visibility.

Understanding the relationship between WAL and commit logs helps developers gain deeper insight into PostgreSQL internals, particularly how transactions are managed and how PostgreSQL maintains consistency under heavy workloads.

whatsapp_icon
location

Calicut

Cybrosys Technologies Pvt. Ltd.
Neospace, KINFRA Techno Park
Kakkanchery, Calicut
Kerala, India - 673635

location

Kochi

Cybrosys Technologies Pvt. Ltd.
1st Floor, Thapasya Building,
Infopark, Kakkanad,
Kochi, India - 682030.

location

Bangalore

Cybrosys Techno Solutions
The Estate, 8th Floor,
Dickenson Road,
Bangalore, India - 560042

Send Us A Message