Postgres
Kubernetes
max_prepared_transactions
configuration
database optimization

Recommended way to configure max_prepared_transactions in Postgres on Kubernetes

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

max_prepared_transactions is one of those PostgreSQL settings you should leave alone unless you know you need two-phase commit. On Kubernetes, the right way to configure it is not a one-off shell edit inside a pod, but a durable server-start configuration managed by your chart, operator, or mounted config file.

First Decide Whether You Need It At All

Prepared transactions are used for two-phase commit through PREPARE TRANSACTION. Many Postgres deployments never use that feature, and PostgreSQL defaults max_prepared_transactions to 0, which disables it. That is usually the correct setting.

Keep it at 0 unless your application stack explicitly uses prepared transactions through:

  • XA or JTA-style transaction coordination
  • a distributed transaction manager
  • application code that issues PREPARE TRANSACTION

If you are not using those patterns, enabling prepared transactions only increases the risk of orphaned prepared work and operational confusion.

If You Do Need It, Size It Intentionally

If your workload genuinely uses prepared transactions, PostgreSQL documentation recommends sizing max_prepared_transactions at least as high as max_connections so each session can have one prepared transaction pending if necessary.

This setting:

  • defaults to 0
  • is a server-start parameter
  • must be the same or higher on standby servers than on the primary

That means a normal ALTER SYSTEM habit or a psql session change is not the right mental model here. Plan a restart and make the value part of the declared deployment configuration.

Configure It Through the Kubernetes Source of Truth

The exact mechanism depends on how Postgres is deployed, but the rule is the same: change the configuration in the thing that creates the pod.

For a plain StatefulSet with a mounted config file, that can look like this:

yaml
1apiVersion: v1
2kind: ConfigMap
3metadata:
4  name: postgres-config
5data:
6  postgresql.conf: |
7    max_connections = 100
8    max_prepared_transactions = 100

And in the pod spec:

yaml
1apiVersion: apps/v1
2kind: StatefulSet
3metadata:
4  name: postgres
5spec:
6  template:
7    spec:
8      containers:
9        - name: postgres
10          image: postgres:16
11          args:
12            - "-c"
13            - "config_file=/etc/postgresql/postgresql.conf"
14          volumeMounts:
15            - name: config
16              mountPath: /etc/postgresql
17      volumes:
18        - name: config
19          configMap:
20            name: postgres-config

If you are using Helm, Patroni, Crunchy, CloudNativePG, or another operator, set the parameter in that product’s values or custom resource instead. Do not patch the running container manually, because the next restart will discard the change.

Verify Both the Setting and Its Usage

After rollout, verify the effective value:

sql
SHOW max_prepared_transactions;

Also inspect whether prepared transactions are actually accumulating:

sql
SELECT gid, prepared, owner, database
FROM pg_prepared_xacts;

That second query matters operationally. A nonzero setting is only safe if your application reliably commits or rolls back prepared transactions. Otherwise, locks and resources can remain stuck until an operator intervenes.

A Practical Recommendation

For most Kubernetes teams, the best recommendation is:

  • leave max_prepared_transactions = 0
  • only enable it when the application architecture truly requires two-phase commit
  • manage it in versioned deployment config
  • roll it out with a planned restart
  • monitor pg_prepared_xacts

That approach is boring, and boring is exactly what you want in database operations.

Common Pitfalls

The biggest pitfall is enabling the setting because a framework mentions "transactions" without confirming it uses prepared transactions specifically. Ordinary Postgres transactions do not require this parameter.

Another mistake is changing the file inside a running pod. In Kubernetes, the durable configuration lives in manifests, chart values, or operator resources, not in interactive container edits.

Teams also forget replicas. Standby nodes must have the same or higher value than the primary, or read queries on the standby can fail.

Finally, do not enable prepared transactions without a cleanup story. An application bug that leaves prepared transactions open can block work long after the original request path is gone.

Summary

  • Leave max_prepared_transactions at 0 unless you truly use two-phase commit.
  • If enabled, size it deliberately, often at least as high as max_connections.
  • Configure it through your Helm chart, operator, or mounted config, not by editing pods.
  • Restart PostgreSQL to apply the change because it is a server-start parameter.
  • Monitor pg_prepared_xacts so prepared transactions do not accumulate silently.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.