---
layout: article
title: Connection pooling
description: Configure the per-database connection pooler to serve many short-lived clients, with automatic read/write splitting when high availability is enabled.
---

PostgreSQL creates one backend process per connection, which makes each connection relatively expensive. Serverless functions, edge runtimes, and horizontally scaled application servers can easily exhaust the connection limit of your specification. The connection pooler sits in front of your database and multiplexes many client connections onto a small pool of server connections.

The pooler runs next to your database and is reachable on port `6432` on the same hostname. Your application connects to the pooler exactly like it would connect to PostgreSQL directly, same credentials, same TLS. The pooler runs on every PostgreSQL specification.

# Pool modes

| Mode | Behavior | Use for |
|---------------|-----------------------------------------------------------------------------|------------------------------------------------|
| `transaction` | A server connection is assigned for the duration of a transaction, then returned to the pool | Serverless and most applications (default) |
| `session` | A server connection is held for the entire client session | Session-level features: prepared statements, advisory locks, `LISTEN/NOTIFY`, temporary tables |

Transaction mode gives the highest connection multiplexing but does not support session-level state. If your framework prepares statements at the session level, either switch the driver to unnamed prepared statements or use session mode.

# Configure the pooler

Read the current pooler configuration with `getPooler`, and tune the pool mode and sizes with `updatePooler`. All parameters are optional; omitted values keep their current setting.

```server-nodejs
import { Client, Postgresql } from 'node-appwrite';

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const pooler = await postgresql.updatePooler({
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    defaultPoolSize: 25,
});
```
```server-deno
import { Client, Postgresql } from "npm:node-appwrite";

const client = new Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

const postgresql = new Postgresql(client);

const pooler = await postgresql.updatePooler({
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    defaultPoolSize: 25,
});
```
```server-php
<?php

use Appwrite\Client;
use Appwrite\Services\Postgresql;

$client = (new Client())
    ->setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    ->setProject('<PROJECT_ID>')
    ->setKey('<YOUR_API_KEY>');

$postgresql = new Postgresql($client);

$pooler = $postgresql->updatePooler(
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    defaultPoolSize: 25,
);
```
```server-python
from appwrite.client import Client
from appwrite.services.postgresql import Postgresql

client = Client()
client.set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
client.set_project('<PROJECT_ID>')
client.set_key('<YOUR_API_KEY>')

postgresql = Postgresql(client)

pooler = postgresql.update_pooler(
    database_id='<DATABASE_ID>',
    mode='transaction',
    default_pool_size=25,
)
```
```server-ruby
require 'appwrite'

include Appwrite

client = Client.new
    .set_endpoint('https://<REGION>.cloud.appwrite.io/v1')
    .set_project('<PROJECT_ID>')
    .set_key('<YOUR_API_KEY>')

postgresql = Postgresql.new(client)

pooler = postgresql.update_pooler(
    database_id: '<DATABASE_ID>',
    mode: 'transaction',
    default_pool_size: 25,
)
```
```server-dotnet
using Appwrite;
using Appwrite.Services;

Client client = new Client()
    .SetEndPoint("https://<REGION>.cloud.appwrite.io/v1")
    .SetProject("<PROJECT_ID>")
    .SetKey("<YOUR_API_KEY>");

Postgresql postgresql = new Postgresql(client);

var pooler = await postgresql.UpdatePooler(
    databaseId: "<DATABASE_ID>",
    mode: "transaction",
    defaultPoolSize: 25
);
```
```server-dart
import 'package:dart_appwrite/dart_appwrite.dart';

Client client = Client()
    .setEndpoint('https://<REGION>.cloud.appwrite.io/v1')
    .setProject('<PROJECT_ID>')
    .setKey('<YOUR_API_KEY>');

Postgresql postgresql = Postgresql(client);

final pooler = await postgresql.updatePooler(
    databaseId: '<DATABASE_ID>',
    mode: 'transaction',
    defaultPoolSize: 25,
);
```
```server-kotlin
import io.appwrite.Client
import io.appwrite.services.Postgresql

val client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

val postgresql = Postgresql(client)

val pooler = postgresql.updatePooler(
    databaseId = "<DATABASE_ID>",
    mode = "transaction",
    defaultPoolSize = 25,
)
```
```server-swift
import Appwrite

let client = Client()
    .setEndpoint("https://<REGION>.cloud.appwrite.io/v1")
    .setProject("<PROJECT_ID>")
    .setKey("<YOUR_API_KEY>")

let postgresql = Postgresql(client)

let pooler = try await postgresql.updatePooler(
    databaseId: "<DATABASE_ID>",
    mode: "transaction",
    defaultPoolSize: 25
)
```
```server-go
package main

import (
    "github.com/appwrite/sdk-for-go/appwrite"
    "github.com/appwrite/sdk-for-go/postgresql"
)

func main() {
    client := appwrite.NewClient(
        appwrite.WithEndpoint("https://<REGION>.cloud.appwrite.io/v1"),
        appwrite.WithProject("<PROJECT_ID>"),
        appwrite.WithKey("<YOUR_API_KEY>"),
    )

    service := appwrite.NewPostgresql(client)

    result, err := service.UpdatePooler(
        "<DATABASE_ID>",
        postgresql.WithUpdatePoolerMode("transaction"),
        postgresql.WithUpdatePoolerDefaultPoolSize(25),
    )
    if err != nil {
        panic(err)
    }
    _ = result
}
```
```server-rust
use appwrite::client::Client;
use appwrite::services::postgresql::Postgresql;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new()
        .set_endpoint("https://<REGION>.cloud.appwrite.io/v1")
        .set_project("<PROJECT_ID>")
        .set_key("<YOUR_API_KEY>");

    let postgresql = Postgresql::new(&client);

    let pooler = postgresql.update_pooler("<DATABASE_ID>", Some("transaction"), Some(25), None, None, None, None, None).await?;

    Ok(())
}
```
```bash
curl -X PATCH \
  -H "X-Appwrite-Project: <PROJECT_ID>" \
  -H "X-Appwrite-Key: <API_KEY>" \
  -H "Content-Type: application/json" \
  -d '{
      "mode": "transaction",
      "defaultPoolSize": 25
  }' \
  https://<REGION>.cloud.appwrite.io/v1/postgresql/<DATABASE_ID>/pooler
```

| Parameter | Range | Description |
|----------------------|----------------|--------------------------------------------------------------------------------|
| `mode` | `transaction`, `session` | How long a server connection stays assigned to a client |
| `maxConnections` | read-only | Reports the database's advertised connection limit. The PostgreSQL pooler does not cap client connections, so set `networkMaxConnections` on the database instead |
| `defaultPoolSize` | 1 - 1,000 | Server connections per user in the pool |
| `readWriteSplitting` | boolean | Route `SELECT`s to replicas, writes and locked reads to the primary. Defaults to on when high availability is enabled |

Pooler configuration is API-only. The Console shows the pooled host and port in the **Credentials** dialog but has no page for changing these settings.

Switching to session mode is refused while the database has high availability replicas. Set `replicas` to 0 first, or stay on transaction mode.

# Connect through the pooler

Take your normal connection string and change the port to `6432`:

```bash
postgresql://admin:<password>@db-<hash>.<region>.appwrite.center:6432/db-<hash>?sslmode=require
```

Point your application's runtime traffic at the pooler port. Keep migrations and long-lived administrative sessions on the direct port `5432`, schema changes and tools like `pg_dump` expect session semantics and can misbehave in transaction mode.

# Read/write splitting

When [high availability](/docs/products/databases/postgresql/high-availability) is enabled, the pooler can route read-only statements to replicas and everything else to the primary. `SELECT ... FOR UPDATE` and statements inside explicit transactions go to the primary. Replicas replicate asynchronously by default, so a read that immediately follows a write can be stale. Synchronous replication does not fix this. It waits for the replica to flush the write to disk, not to replay it, so a replica can still answer a read from before the write. When a read must observe the write that preceded it, send it to the primary on the direct port.

# Sizing guidance

Appwrite already sizes the pool for you when the database is provisioned, from your specification's CPU count and connection limit. If you tune it by hand, `4 x CPU cores` is a useful starting point. Watch the connection metrics in the [Monitor tab](/docs/products/databases/postgresql/monitoring) and increase the pool only when clients queue for a connection.

# When not to use the pooler

The pooler adds a network hop, and transaction mode trades session-level features for connection multiplexing. Connect to the direct port `5432` instead when any of the following applies:

- **A small, fixed fleet.** A few long-lived application servers that each maintain a driver-level pool, with a combined connection count that fits in your specification's limit, gain nothing from an extra hop.
- **Session state.** Workloads that rely on advisory locks, `LISTEN/NOTIFY`, session-level prepared statements, or temporary tables break in transaction mode. Use session mode or the direct port.
- **Migrations and administration.** Schema changes and tools like `pg_dump` expect one session for the whole run. Always run them against the direct port.
- **Single latency-sensitive queries.** A workload of few, fast queries on an idle database pays the extra hop on every round trip without ever hitting the connection limit the pooler protects against.
