Skip to content

Commit e19a073

Browse files
committed
feat: 2pc crash recovery docs
1 parent beb63f3 commit e19a073

14 files changed

Lines changed: 48 additions & 43 deletions

File tree

docs/configuration/pgdog.toml/general.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -431,19 +431,19 @@ Default: **`false`** (disabled)
431431

432432
### `two_phase_commit`
433433

434-
Enable [two-phase commit](../../features/sharding/2pc.md) for write, cross-shard transactions.
434+
Enable [two-phase commit](../../features/sharding/2pc/index.md) for write, cross-shard transactions.
435435

436436
Default: **`false`** (disabled)
437437

438438
### `two_phase_commit_auto`
439439

440-
Enable automatic conversion of single-statement write transactions to use [two-phase commit](../../features/sharding/2pc.md).
440+
Enable automatic conversion of single-statement write transactions to use [two-phase commit](../../features/sharding/2pc/index.md).
441441

442442
Default: **`true`** (enabled)
443443

444444
### `two_phase_commit_wal_dir`
445445

446-
Directory where the [two-phase commit](../../features/sharding/2pc.md) write-ahead log is stored.
446+
Directory where the [two-phase commit](../../features/sharding/2pc/index.md) write-ahead log is stored.
447447

448448
!!! note "Requires restart"
449449
This setting cannot be changed at runtime.

docs/configuration/pgdog.toml/rewrite.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ The `rewrite` section controls PgDog's automatic SQL rewrites for sharded databa
3131
| `primary_key` | Behavior when an `INSERT` is missing a `BIGINT` primary key: `error` rejects the statement,<br>`rewrite` auto-injects `pgdog.unique_id()` for missing keys,<br>`ignore` allows the INSERT without modification. | `"ignore"` |
3232

3333
!!! note "Two-phase commit"
34-
Consider enabling [two-phase commit](../../features/sharding/2pc.md) when either feature is set to `rewrite`. Without it, rewrites are committed shard-by-shard and can leave partial changes if a transaction fails.
34+
Consider enabling [two-phase commit](../../features/sharding/2pc/index.md) when either feature is set to `rewrite`. Without it, rewrites are committed shard-by-shard and can leave partial changes if a transaction fails.
3535

3636
## Runtime overrides
3737

Lines changed: 26 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
1+
---
2+
icon: material/backup-restore
3+
---
4+
15
# Crash recovery
26

37
!!! note "New feature"
48
This feature is new and experimental. Please make sure to test it before
59
deploying to production and let us know if you run into any issues.
610

7-
[Two-phase commit](index.md) requires different state to be stored on each shard during the commit phase. If PgDog were to crash during this step, the state in which each transaction is on each shard would be impossible to determine.
11+
[Two-phase commit](index.md) requires different state to be stored on each shard during the commit phase. If PgDog were to crash during this step, it would be impossible to determine the state of each transaction on each shard.
812

9-
To avoid this, PgDog can write the commit state of each transaction into its own write-ahead log (WAL). When its restarted, PgDog reads from the log and restores the 2pc state to what it was, allowing it to finish any in-flight transactions.
13+
To avoid this, PgDog can write the commit state of each two-phase transaction into its own write-ahead log (WAL). When it's restarted, PgDog reads from the log and restores the 2PC state to what it was, allowing it to finish any in-flight transactions.
1014

1115
## How it works
1216

13-
The WAL requires PgDog to run on a machine with a durable storage medium. If you're using our [Helm chart](../../installation.md#kubernetes), you can enable this with configuration:
17+
The WAL requires PgDog to run on a machine with a durable storage medium. If you're using our [Helm chart](../../../installation.md#kubernetes), you can enable this directly in the chart:
1418

1519
```yaml title="values.yaml"
1620
statefulSet:
@@ -19,7 +23,7 @@ statefulSet:
1923
enabled: true
2024
```
2125
22-
The `walPvc` flag will provision a Persistent Volume Claim (PVC) and attach the volume to each replica in the deployment. It will also configure the necessary [`pgdog.toml`](../../configuration/pgdog.toml/general.md) settings:
26+
The `walPvc` flag will provision a Persistent Volume Claim (PVC) and attach its own volume to each replica in the deployment. It will also configure the necessary [`pgdog.toml`](../../../configuration/pgdog.toml/general.md) settings:
2327

2428
=== "pgdog.toml"
2529
```toml
@@ -28,42 +32,42 @@ The `walPvc` flag will provision a Persistent Volume Claim (PVC) and attach the
2832
```
2933
=== "Helm chart"
3034
```yaml
31-
# These are configured automatically.
35+
# This is configured automatically.
3236
twoPhaseCommitWalDir: "/var/lib/pgdog/wal"
3337
```
3438

3539
Our WAL has no Kubernetes dependency, so you can use it if you're deploying PgDog anywhere else, as long as you have a durable disk, e.g., EBS volume, SSD, etc.
3640

3741
### Configuration
3842

39-
The WAL has a couple settings that allows you to tweak its performance:
43+
The WAL has a couple of settings that allow you to tweak its performance:
4044

4145
| Setting | Description |
4246
|-|-|
43-
| `two_phase_commit_fsync_interval` | Wait this long (in ms, **0** by default) before calling `fsync` on a group of WAL records written to the log. |
44-
| `two_phase_commit_checkpoint_interval` | How often the checkpointer runs (in ms, **15 seconds** by default) to remove unnecessary WAL segments. |
47+
| `two_phase_commit_wal_fsync_interval` | Wait this long (in ms, **0** by default) before calling `fsync` on a group of WAL records written to the log. |
48+
| `two_phase_commit_wal_checkpoint_interval` | How often the checkpointer runs (in ms, **15 seconds** by default) to remove unnecessary WAL segments. |
4549
| `two_phase_commit_wal_segment_size` | The size of each WAL segment, in bytes. The default, **16MB**, is usually good enough. |
4650

47-
The default settings there are usually optimal for most deployments. The fsync interval lever allows you to optimize WAL disk performance in highly concurrenct scenarios, e.g., thousands of clients
48-
executing 2pc transactions concurrently.
51+
The default settings are usually optimal for most deployments. The fsync interval allows you to optimize WAL disk performance in highly concurrent scenarios, e.g., thousands of clients
52+
executing 2PC transactions concurrently.
4953

5054
## Architecture
5155

52-
PgDog's write-ahead log architecture is loosly based on Postgres, with some minor differences. Like Postgres, PgDog implements it using three independent components:
56+
PgDog's write-ahead log architecture is loosely based on Postgres, with some differences. Like Postgres, PgDog implements it using three independent components:
5357

5458
| Component | Description |
5559
|-|-|
56-
| [WAL writer](#wal-writer) | Background thread responsible for writing records to WAL segments (files). |
57-
| [Checkpointer](#checkpointer) | Background thread responsible for removing old WAL segments no longer necessary for recovery. |
58-
| [Recovery](#recovery) | Foreground thread that runs on PgDog startup and replays the WAL to restore its in-memory state to a consistent point. |
60+
| [WAL writer](#wal-writer) | Background task responsible for writing records to WAL segments (files). |
61+
| [Checkpointer](#checkpointer) | Background task responsible for removing old WAL segments no longer necessary for recovery. |
62+
| [Recovery](#recovery) | Foreground task that runs on PgDog startup and replays the WAL to restore its in-memory state to a consistent point. |
5963

6064
### WAL writer
6165

6266
The WAL writer is a background task (we are using Tokio under the hood), which is responsible for writing data into the write-ahead log. It receives records from clients via a synchronization primitive (a queue) and writes them to disk, in batches.
6367

6468
Once written, the WAL writer sends a signal back to each client notifying them that their transaction state is safe on disk, and they can proceed. The clients then execute the transaction control statements against the Postgres shards themselves (e.g., `PREPARE TRANSACTION`, `COMMIT PREPARED`).
6569

66-
If PgDog were to crash at any time, the state of each transaction can be restored from disk and the transaction control statements replayed against the Postgres shards, either rolling back or comitting a two-phase transaction.
70+
If PgDog were to crash at any time, the state of each transaction can be restored from disk and the transaction control statements replayed against the Postgres shards, either rolling back or committing a two-phase transaction.
6771

6872
#### WAL segments
6973

@@ -83,30 +87,30 @@ By default, PgDog's segment size is **16MB**, just like Postgres. This ensures t
8387

8488
##### Segment size
8589

86-
The segments are rotated by a background task asynchronously. This is one key difference between PgDog's implementation and Postgres: the segment size isn't guaranteed. While the rotation takes place, in-flight transactions state is written to the previous segment. This removes the need for us to _lock_ the WAL during a write, i.e., implementing our own version of `WALWriteLock`.
90+
The segments are rotated asynchronously by a background task. This is one key difference between PgDog's implementation and Postgres: the segment size isn't guaranteed. While the rotation takes place, in-flight transaction state is written to the previous segment. This removes the need for us to _lock_ the WAL during a write, i.e., implementing our own version of `WALWriteLock`.
8791

8892
#### Partial records
8993

9094
All of our WAL segments contain complete records. This makes [recovery](#recovery) easier, but also contributes to the variable [segment size](#segment-size).
9195

9296
### Checkpointer
9397

94-
The checkpointer is a background task that runs on a loop and removes any WAL segments that are no longer needed for [recovery](#recovery). These segments contain 2pc stages for transactions that have been already been fully committed to Postgres.
98+
The checkpointer is a background task that runs in a loop and removes any WAL segments that are no longer needed for [recovery](#recovery). These segments contain 2PC stages for transactions that have already been fully committed to Postgres.
9599

96-
Unlike the Postgres checkpointer which needs to update data files to do its job, PgDog's checkpointer only needs to delete unused WAL segments. This makes it very fast. It runs on a regular interval, configurable in [`pgdog.toml`](../../configuration/pgdog.toml/general.md):
100+
Unlike the Postgres checkpointer which needs to update data files to do its job, PgDog's checkpointer only needs to delete unused WAL segments. This makes it very fast. It runs on a regular interval, configurable in [`pgdog.toml`](../../../configuration/pgdog.toml/general.md):
97101

98102
=== "pgdog.toml"
99103
```toml
100104
[general]
101-
two_phase_commit_checkpoint_interval = 15_000
105+
two_phase_commit_wal_checkpoint_interval = 15_000
102106
```
103107
=== "Helm chart"
104108
```yaml
105-
twoPhaseCommitCheckpointInterval: 15_000
109+
twoPhaseCommitWalCheckpointInterval: 15_000
106110
```
107111

108112
### Recovery
109113

110-
The recovery process's job is to read all available WAL segments and replay their data into the in-memory state of the [2pc](index.md#error-handling) transaction manager. Once the replay is complete, the manager state should be restored to what it was prior to the crash.
114+
The recovery process's job is to read all available WAL segments and replay their data into the in-memory state of the [2PC](index.md#error-handling) transaction manager. Once the replay is complete, the manager state should be restored to what it was prior to the crash.
111115

112-
The recovery process runs on PgDog startup and will block it until its complete. Clients will not be able to connect until recovery is complete. Just like the [checkpointer](#checkpointer), it only needs to read WAL segments into memory and doesn't perform any writes to disk, so it's very quick.
116+
The recovery process runs on PgDog startup and will block it until it's complete. Clients will not be able to connect until recovery is complete. Just like the [checkpointer](#checkpointer), it only needs to read WAL segments into memory and doesn't perform any writes to disk, so it's very quick.

docs/features/sharding/2pc/index.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Alternatively, if you're running on managed Postgres (e.g., AWS RDS), this param
3131
!!! note
3232
Changes to this parameter require a server restart to take effect.
3333

34-
Once prepared transactions are enabled in Postgres, two-phase commit can be enabled in [`pgdog.toml`](../../configuration/pgdog.toml/general.md):
34+
Once prepared transactions are enabled in Postgres, two-phase commit can be enabled in [`pgdog.toml`](../../../configuration/pgdog.toml/general.md):
3535

3636
=== "pgdog.toml"
3737
```toml
@@ -93,7 +93,7 @@ This feature allows for easier migrations to sharded databases, without requirin
9393

9494
While it's often desirable to ensure cross-shard writes are atomic, rewriting single-statement transactions to use 4 statements has some performance overhead. For this reason, this feature is **disabled** by default.
9595

96-
If your writes are idempotent and can be safely retried, or your application doesn't have consistency requirements, you don't need to use this. Otherwise, you can enable it in [`pgdog.toml`](../../configuration/pgdog.toml/general.md):
96+
If your writes are idempotent and can be safely retried, or your application doesn't have consistency requirements, you don't need to use this. Otherwise, you can enable it in [`pgdog.toml`](../../../configuration/pgdog.toml/general.md):
9797

9898
=== "pgdog.toml"
9999
```toml
@@ -112,6 +112,7 @@ Two-phase commit is used for writes only. Read transactions are finished using n
112112
## Read more
113113

114114
{{ next_steps_links([
115-
("Omnisharded tables", "omnishards.md", "Tables replicated to every shard for fast local joins."),
116-
("Cross-shard queries", "cross-shard-queries/index.md", "Run queries that span multiple shards transparently."),
115+
("Crash recovery", "crash-recovery.md", "Recover in-flight two-phase transactions if PgDog crashes."),
116+
("Omnisharded tables", "../omnishards.md", "Tables replicated to every shard for fast local joins."),
117+
("Cross-shard queries", "../cross-shard-queries/index.md", "Run queries that span multiple shards transparently."),
117118
]) }}

docs/features/sharding/cross-shard-queries/copy.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,6 @@ If the query fetches rows from more than one shard, PgDog will also ignore any `
5151
## Read more
5252

5353
{{ next_steps_links([
54-
("Two-phase commit", "../2pc.md", "Atomic transactions spanning multiple shards."),
54+
("Two-phase commit", "../2pc/index.md", "Atomic transactions spanning multiple shards."),
5555
("Omnisharded tables", "../omnishards.md", "Tables replicated to every shard for fast local joins."),
5656
]) }}

docs/features/sharding/cross-shard-queries/ddl.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ icon: material/table-cog
88

99
## Atomicity
1010

11-
DDL statements should be atomic across all shards. This is to protect against a single shard failing to create a table or index, which could result in an inconsistent schema. PgDog can use [two-phase commit](../2pc.md) to ensure this is the case, however that means that all DDL statements must be executed inside a transaction, for example:
11+
DDL statements should be atomic across all shards. This is to protect against a single shard failing to create a table or index, which could result in an inconsistent schema. PgDog can use [two-phase commit](../2pc/index.md) to ensure this is the case, however that means that all DDL statements must be executed inside a transaction, for example:
1212

1313
```postgresql
1414
BEGIN;

docs/features/sharding/cross-shard-queries/insert.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ This is a common pattern for tables that don't have a sharding key, or tables th
3131

3232
### Omnisharded consistency
3333

34-
Unless [two-phase commit](../2pc.md) is enabled, inserts into omnisharded tables are not guaranteed to be atomic. It is possible for the statement to succeed on some of the shards and not others.
34+
Unless [two-phase commit](../2pc/index.md) is enabled, inserts into omnisharded tables are not guaranteed to be atomic. It is possible for the statement to succeed on some of the shards and not others.
3535

3636
If you don't want to or can't enable two-phase commit on your database shards, consider sending cross-shard inserts inside a transaction or writing idempotent statements, for example:
3737

@@ -47,7 +47,7 @@ COMMIT;
4747
This gives you a much higher chance of writing rows on all shards, since you will know if your statement violated a constraint (e.g., unique index or `NOT NULL` check) before committing the transaction.
4848

4949
!!! warning "Two-phase commit"
50-
Enabling [two-phase commit](../2pc.md) is highly recommended. It's been tested and works well in production.
50+
Enabling [two-phase commit](../2pc/index.md) is highly recommended. It's been tested and works well in production.
5151

5252
### Primary keys in omnisharded tables
5353

@@ -167,7 +167,7 @@ RETURNING id;
167167
The `id` column will be generated by the database (and not PgDog), globally unique, and matched to the shard it's generated on, as guaranteed by the [sharded sequence](../sequences.md) implementation.
168168

169169
!!! warning "Sharded tables only"
170-
Make sure to **never** use sharded sequences with **omnisharded** tables. They are not guaranteed to generate the same value on all shards, even with [two-phase commit](../2pc.md), and could cause primary key drift across shards.
170+
Make sure to **never** use sharded sequences with **omnisharded** tables. They are not guaranteed to generate the same value on all shards, even with [two-phase commit](../2pc/index.md), and could cause primary key drift across shards.
171171

172172
## Composite primary keys
173173

@@ -238,4 +238,4 @@ Requiring transactions ensures that if one of the `INSERT` statements fails, e.g
238238

239239
!!! warning "Two-phase commit"
240240

241-
Much like [omnisharded](#omnisharded-tables) table inserts, it's best to enable [two-phase commit](../2pc.md) before attempting cross-shard multi-tuple inserts. This feature increases the likelihood that cross-shard transactions are atomic.
241+
Much like [omnisharded](#omnisharded-tables) table inserts, it's best to enable [two-phase commit](../2pc/index.md) before attempting cross-shard multi-tuple inserts. This feature increases the likelihood that cross-shard transactions are atomic.

docs/features/sharding/cross-shard-queries/update.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ UPDATE users SET is_admin = true WHERE email LIKE '%@pgdog.dev';
1414

1515
## Consistency
1616

17-
Much like cross-shard [`INSERT`](insert.md) statements, any updates to multiple rows on multiple databases outside a [two-phase](../2pc.md) transaction are not guaranteed to be atomic. It's always best to send updates inside a transaction, like so:
17+
Much like cross-shard [`INSERT`](insert.md) statements, any updates to multiple rows on multiple databases outside a [two-phase](../2pc/index.md) transaction are not guaranteed to be atomic. It's always best to send updates inside a transaction, like so:
1818

1919
```postgresql
2020
BEGIN;

docs/features/sharding/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ PgDog is a query router. It can extract sharding hints directly from the SQL que
3434

3535
### Data consistency
3636

37-
To make sure data is atomically written in cross-shard transactions, PgDog supports PostgreSQL's prepared transactions and [two-phase commit](2pc.md).
37+
To make sure data is atomically written in cross-shard transactions, PgDog supports PostgreSQL's prepared transactions and [two-phase commit](2pc/index.md).
3838

3939
## Managing data
4040

docs/features/sharding/omnishards.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ WHERE users.id = $1;
5252

5353
### Consistency
5454

55-
Writing data to omnisharded tables is atomic if you enable [two-phase commit](2pc.md).
55+
Writing data to omnisharded tables is atomic if you enable [two-phase commit](2pc/index.md).
5656

5757
If you can't or choose not to use 2pc, make sure writes to omnisharded tables can be repeated in case of failure. This can be achieved by using unique indexes and `INSERT ... ON CONFLICT ... DO UPDATE` queries.
5858

0 commit comments

Comments
 (0)