Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
layout: article
title: High availability
description: Run up to five replicas of a dedicated database with synchronous, semi-synchronous, or asynchronous replication and automatic failover on primary failure.
description: Run up to five replicas of a dedicated database with asynchronous replication and automatic failover on primary failure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Overview Still Promises Sync

This page now says HA is async-only, but the dedicated databases overview still advertises synchronous and semi-synchronous replicas for the same feature. A reader can enter through that overview, try sync, and hit the API rejection described here.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc
Line: 4

Comment:
**Overview Still Promises Sync**

This page now says HA is async-only, but the dedicated databases overview still advertises synchronous and semi-synchronous replicas for the same feature. A reader can enter through that overview, try `sync`, and hit the API rejection described here.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex

---

High availability adds replicas to a dedicated database and enables automatic failover when the primary becomes unhealthy. With HA on, a primary outage is recovered in seconds by promoting the most up-to-date replica, without manual intervention.
Expand All @@ -14,24 +14,16 @@ When you enable HA, Appwrite scales the underlying database to the configured re

| Engine | Replication mechanism |
|------------|---------------------------------------------------------------------------------------|
| PostgreSQL | Streaming replication over WAL |
| MySQL | Semi-synchronous binlog replication |
| MariaDB | Semi-synchronous binlog replication |
| PostgreSQL | Asynchronous streaming replication over WAL |
| MySQL | Asynchronous binlog replication |
| MariaDB | Asynchronous binlog replication |
| MongoDB | Replica-set oplog tailing with Raft-based primary election |

Replicas are placed on different physical hosts from the primary, so a single host failure does not take down both the primary and a replica.

# Sync modes {% #sync-modes %}

PostgreSQL and MySQL/MariaDB expose three durability modes. MongoDB uses its own write-concern model and is not configurable through this setting.

| Mode | Commit acknowledged when… | Durability | Write latency |
|--------------|------------------------------------------------------|----------------|---------------------|
| `async` | The primary persists the write to local WAL/binlog | Low | Lowest (default) |
| `sync` | At least one replica acknowledges the write | High | +network round-trip |
| `quorum` | A majority of replicas acknowledge the write | Highest | +network round-trip |

A safe default for production is `sync` with two replicas, a single replica failure does not block writes, and you still cannot lose a committed write to a primary-only crash.
Replication is asynchronous: a commit is acknowledged as soon as the primary persists the write to its local WAL/binlog, and replicas apply it moments later. `syncMode` accepts only `async` — synchronous and quorum modes are not yet available, and the API rejects them. On a primary crash, writes committed after the promoted replica's applied position are lost; failover discloses those coordinates. MongoDB uses its own write-concern model and is not configurable through this setting.

# Enable HA {% #enable %}

Expand All @@ -52,7 +44,7 @@ await compute.updateDatabase({
databaseId: '<DATABASE_ID>',
highAvailability: true,
highAvailabilityReplicaCount: 2,
highAvailabilitySyncMode: 'sync',
highAvailabilitySyncMode: 'async',
});
```
```server-deno
Expand All @@ -69,7 +61,7 @@ await compute.updateDatabase({
databaseId: '<DATABASE_ID>',
highAvailability: true,
highAvailabilityReplicaCount: 2,
highAvailabilitySyncMode: 'sync',
highAvailabilitySyncMode: 'async',
});
```
```server-php
Expand All @@ -91,7 +83,7 @@ $compute->updateDatabase(
databaseId: '<DATABASE_ID>',
highAvailability: true,
highAvailabilityReplicaCount: 2,
highAvailabilitySyncMode: 'sync',
highAvailabilitySyncMode: 'async',
);
```
```server-python
Expand All @@ -109,7 +101,7 @@ compute.update_database(
database_id='<DATABASE_ID>',
high_availability=True,
high_availability_replica_count=2,
high_availability_sync_mode='sync',
high_availability_sync_mode='async',
)
```
```server-ruby
Expand All @@ -128,7 +120,7 @@ compute.update_database(
database_id: '<DATABASE_ID>',
high_availability: true,
high_availability_replica_count: 2,
high_availability_sync_mode: 'sync',
high_availability_sync_mode: 'async',
)
```
```server-dotnet
Expand All @@ -146,7 +138,7 @@ await compute.UpdateDatabase(
databaseId: "<DATABASE_ID>",
highAvailability: true,
highAvailabilityReplicaCount: 2,
highAvailabilitySyncMode: "sync"
highAvailabilitySyncMode: "async"
);
```
```server-dart
Expand All @@ -163,7 +155,7 @@ await compute.updateDatabase(
databaseId: '<DATABASE_ID>',
highAvailability: true,
highAvailabilityReplicaCount: 2,
highAvailabilitySyncMode: 'sync',
highAvailabilitySyncMode: 'async',
);
```
```server-kotlin
Expand All @@ -181,7 +173,7 @@ compute.updateDatabase(
databaseId = "<DATABASE_ID>",
highAvailability = true,
highAvailabilityReplicaCount = 2,
highAvailabilitySyncMode = "sync",
highAvailabilitySyncMode = "async",
)
```
```server-swift
Expand All @@ -198,7 +190,7 @@ _ = try await compute.updateDatabase(
databaseId: "<DATABASE_ID>",
highAvailability: true,
highAvailabilityReplicaCount: 2,
highAvailabilitySyncMode: "sync"
highAvailabilitySyncMode: "async"
)
```
```server-go
Expand All @@ -220,7 +212,7 @@ func main() {
_, err := compute.UpdateDatabase("<DATABASE_ID>",
compute.WithUpdateDatabaseHighAvailability(true),
compute.WithUpdateDatabaseHighAvailabilityReplicaCount(2),
compute.WithUpdateDatabaseHighAvailabilitySyncMode("sync"),
compute.WithUpdateDatabaseHighAvailabilitySyncMode("async"),
)
if err != nil {
panic(err)
Expand All @@ -243,7 +235,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
compute.update_database("<DATABASE_ID>")
.high_availability(true)
.high_availability_replica_count(2)
.high_availability_sync_mode("sync")
.high_availability_sync_mode("async")
.send()
.await?;

Expand All @@ -258,7 +250,7 @@ curl -X PATCH \
-d '{
"highAvailability": true,
"highAvailabilityReplicaCount": 2,
"highAvailabilitySyncMode": "sync"
"highAvailabilitySyncMode": "async"
}' \
https://<REGION>.cloud.appwrite.io/v1/compute/databases/<DATABASE_ID>
```
Expand Down Expand Up @@ -667,7 +659,7 @@ In `async` mode, a read that lands on a replica immediately after a write to the

- **Always read from the primary**, the safest default. The pooler routes all queries to the primary if read/write splitting is off.
- **Use read replicas, with primary-affinity for writes**, turn on the [connection pooler with read/write split](/docs/products/databases/dedicated/pooler). The pooler pins transactions and writes to the primary and routes ambient `SELECT`s to replicas.
- **Use a stronger sync mode**, in `sync` or `quorum`, any reader on an in-sync replica sees the write as soon as `COMMIT` returns.
- **Pin critical reads to the primary**, run the read inside a transaction or use `SELECT ... FOR UPDATE` — the [pooler routes both to the primary](/docs/products/databases/dedicated/pooler#read-write) even with read/write splitting on.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 PostgreSQL Reads May Replicate

The SELECT ... FOR UPDATE guidance is broader than the documented pooler behavior. The pooler docs only guarantee that PostgreSQL statements inside a transaction go to the primary, so a bare PostgreSQL SELECT ... FOR UPDATE can still be treated as a replica read and miss the read-your-writes guarantee this bullet promises.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc
Line: 662

Comment:
**PostgreSQL Reads May Replicate**

The `SELECT ... FOR UPDATE` guidance is broader than the documented pooler behavior. The pooler docs only guarantee that PostgreSQL statements inside a transaction go to the primary, so a bare PostgreSQL `SELECT ... FOR UPDATE` can still be treated as a replica read and miss the read-your-writes guarantee this bullet promises.

How can I resolve this? If you propose a fix, please make it concise.

Fix in Claude Code Fix in Codex


# Cross-region failover {% #cross-region-failover %}

Expand All @@ -685,7 +677,6 @@ Cross-region replicas and standbys are billed as feature add-ons, see the [speci
|------------------------------------------------|-------|
| Maximum replicas per database | 5 |
| Replica count change (online) | yes |
| Sync mode change without recreating replicas | yes |
| Failover cooldown after auto-promote | 60 s |
| Health-check timeout per attempt | 3 s |
| Consecutive failed health checks before failover | 2 |
Expand Down
Loading