> ## Documentation Index
> Fetch the complete documentation index at: https://artie.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# PlanetScale for Postgres

> Learn how to configure PlanetScale for Postgres as a source in Artie, including service account creation, heartbeats, publication setup, enabling logical replication, and failover-safe replication slots.

> [!NOTE]
>
> This page covers **PlanetScale for Postgres**. For PlanetScale on Vitess (MySQL), use the [PlanetScale source connector](https://artie.com/docs/sources/planetscale.md).

This page covers only what differs on PlanetScale. For partitioned tables, tables without primary keys, and other settings, see the [PostgreSQL overview](https://artie.com/docs/sources/postgresql.md).

## Create a service account

Connect to the primary as the branch's default `postgres` role on port `5432` and run:

```sql setup.sql theme={null}
CREATE USER artie_transfer WITH REPLICATION PASSWORD 'password';

GRANT USAGE ON SCHEMA public TO artie_transfer;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO artie_transfer;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO artie_transfer;
```

* Connect as `artie_transfer.<branch_id>`. The branch ID is the part after the `.` in the default username (e.g. `postgres.nk35mx55qq`).
* SQL-created roles don't appear on the PlanetScale **Roles** page. A managed role can only get **WITH REPLICATION** by inheriting `postgres` (admin), so this role is the least-privileged option.

## Create the heartbeat table

Heartbeats keep the slot advancing on a quiet branch, which matters on PlanetScale because `max_slot_wal_keep_size` defaults to no limit. As `postgres`, run:

```sql heartbeats.sql theme={null}
CREATE TABLE test_heartbeat_table (id text PRIMARY KEY, ts timestamp);
GRANT SELECT, UPDATE ON TABLE test_heartbeat_table TO artie_transfer;
INSERT INTO test_heartbeat_table (id, ts) VALUES ('1', NOW());
```

Grant to `artie_transfer` without the branch suffix. The table must be in the publication, which the next step covers. Then turn on **Enable heartbeats** under **Postgres settings**.

## Create a publication

Artie can't create publications on PlanetScale, so create one as `postgres`. Without it, deployment fails with `publication "dbz_publication" does not exist`.

Once a table is in a publication, PostgreSQL rejects `UPDATE` and `DELETE` on it unless it has a primary key or replica identity. First, list tables that would break:

```sql check.sql theme={null}
SELECT n.nspname AS table_schema, c.relname AS table_name
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
  AND c.relpersistence = 'p'
  AND n.nspname NOT IN ('pg_catalog', 'information_schema')
  AND n.nspname NOT LIKE 'pg_toast%'
  AND (
    c.relreplident = 'n'
    OR (c.relreplident = 'd' AND NOT EXISTS (
      SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisprimary))
    OR (c.relreplident = 'i' AND NOT EXISTS (
      SELECT 1 FROM pg_index i WHERE i.indrelid = c.oid AND i.indisreplident))
  )
ORDER BY 1, 2;
```

If it returns tables, set `REPLICA IDENTITY FULL` on each ([details](https://artie.com/docs/sources/postgresql.md#can-i-replicate-tables-without-primary-keys)) or publish only specific tables (below). To replicate a table without a primary key, also set **Override primary keys** in Artie to an existing unique index, or Artie fails with `partition key is not set`. Then create the publication and leave the pipeline's publication mode on **All tables**:

```sql publication.sql theme={null}
CREATE PUBLICATION dbz_publication FOR ALL TABLES WITH (publish_via_partition_root = true);
```

This includes tables created later, so give every new table a primary key or replica identity.

> **Publish only specific tables**
>
> Create the publication with a table list instead, and include the heartbeat table if you use heartbeats:
>
> ```sql theme={null}
> CREATE PUBLICATION dbz_publication
>   FOR TABLE public.orders, public.customers, test_heartbeat_table
>   WITH (publish_via_partition_root = true);
> ```
>
> Set the pipeline's publication mode to **Filtered**. If you already created the `FOR ALL TABLES` publication, drop it first (`DROP PUBLICATION dbz_publication;`), or deployment fails with `please set the publication mode to 'all tables'`. Add new tables before adding them to the pipeline:
>
> ```sql theme={null}
> ALTER PUBLICATION dbz_publication ADD TABLE public.refunds;
> ```

## Enable logical replication

In the PlanetScale console, go to your branch's **Clusters → Parameters** and set:

| Parameter | Value |
| - | - |
| `wal_level` | `logical` (requires a restart, which PlanetScale does with a brief switchover) |
| `max_replication_slots` | At least 2× the number of replicas and CDC consumers |
| `max_wal_senders` | At least `max_replication_slots` |
| `max_slot_wal_keep_size` | Sized for your change rate and free disk (PlanetScale suggests over `4GB`) |
| `sync_replication_slots` | `on` (keeps the slot through a switchover or failover) |
| `hot_standby_feedback` | `on` (same) |

## Create a failover-safe replication slot

A slot only survives a PlanetScale switchover or failover if it has `failover = true`. If PlanetScale finds a logical slot without it, it notifies you and resumes planned failovers after three days.

Before you deploy, enable **Use PostgreSQL 17 failover replication slot** under **Postgres settings**. Artie then creates its slot with failover. The setting doesn't change an existing slot.

> **Create the slot yourself**
>
> As `artie_transfer`, before deploying:
>
> ```sql slot.sql theme={null}
> SELECT pg_create_logical_replication_slot('artie', 'pgoutput', false, false, true); -- last arg: failover
> ```
>
> Use your slot name override instead of `artie` if you set one. Keep the failover setting on so Artie recreates the slot with failover if it's ever dropped.

After deploying, check that `failover` is `t`:

```sql theme={null}
SELECT slot_name, active, failover FROM pg_replication_slots WHERE slot_type = 'logical';
```

If it's `f`, see **Repairing a slot created without failover** below.

## Connect Artie to PlanetScale

| Field | Value |
| - | - |
| Host | From the PlanetScale **Connect** page (e.g. `nk35mx55qq-useast1-1.horizon.psdb.cloud`) |
| Port | `5432` |
| Username | `artie_transfer.<branch_id>` |
| Password | The password you set for `artie_transfer` |
| Database | The Postgres database your tables are in (`postgres` by default) |

Artie enables TLS automatically, which PlanetScale requires. Under **Postgres settings**, turn on **Publish partitioned table changes via partition root** to match the publication, or deployment fails validation.

> [!WARNING]
>
> Connect directly to the **primary** on port `5432`. Don't use the PgBouncer port (`6432`) or append `|replica` to the username.

## Advanced

> **Repairing a slot created without failover**
>
> You can enable failover on an existing slot without dropping it ([PlanetScale's procedure](https://planetscale.com/docs/postgres/integrations/logical-cdc#enabling-failover-on-an-existing-replication-slot)). `sync_replication_slots` and `hot_standby_feedback` must be `on` first.
>
> **1. Pause the pipeline**
>
> Choose **Pause reading & delivery** so the reader releases the slot (the default, **Pause delivery only**, stays connected). For a shared source reader, contact Artie support to pause it.
>
> **2. Open a replication connection as the default role**
>
> ```bash theme={null}
> psql "host=nk35mx55qq-useast1-1.horizon.psdb.cloud port=5432 dbname=my_database user=postgres.nk35mx55qq replication=database sslmode=verify-full sslrootcert=system"
> ```
>
> **3. Alter the slot**
>
> ```sql theme={null}
> ALTER_REPLICATION_SLOT artie (FAILOVER true);
> ```
>
> If it hangs, a session is still attached. From a normal (non-replication) `postgres` connection, run:
>
> ```sql theme={null}
> SELECT pg_terminate_backend(active_pid) FROM pg_replication_slots WHERE slot_name = 'artie' AND active_pid IS NOT NULL;
> ```
>
> **4. Verify and resume**
>
> Confirm `failover` is `t` in `pg_replication_slots`, then resume the pipeline. Artie continues from its last acknowledged position.

> **What happens during a switchover**
>
> PlanetScale switches over for parameter changes, resizes, and upgrades, and fails over if the primary goes down. With the settings above, the slot moves to the new primary and Artie resumes from its last acknowledged position after a brief gap.

> **Replicating another branch**
>
> Each branch is its own cluster and doesn't inherit parameters, SQL-created roles, publications, or slots. Repeat these steps on that branch and create a separate Artie source with its credentials.

> **Private connectivity**
>
> PlanetScale supports [AWS PrivateLink](https://artie.com/docs/connection-options/privatelink.md). Otherwise, Artie connects from its [fixed IP addresses](https://artie.com/docs/connection-options.md#fixed-ip-addresses).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.