From 30029633c4b9631d808c8aa7f2fdc0dc316e4ecd Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 15:10:47 +1200 Subject: [PATCH 01/15] docs: add dedicated databases launch content Add the docs, blog post, and changelog entry for the dedicated databases launch. Covers managed PostgreSQL, MySQL, MariaDB, and MongoDB engines on Appwrite Cloud, with high availability, point-in-time recovery, branching, extensions, a connection pooler, and a managed SQL API. * 11 docs pages under products/databases/dedicated (overview, specifications, connect, high-availability, backups, branches, extensions, pooler, sql-api, network, monitoring) wired into the databases sidebar. * Cross-link callout from the main databases overview to the new section. * Hide the "new TablesDB API" banner on the dedicated routes since it's scoped to the document store. * Announcement blog post with 8 FAQs. * Changelog entry for 2026-05-21. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../+page.markdoc | 127 ++++++++++++ .../changelog/(entries)/2026-05-21.markdoc | 18 ++ .../docs/products/databases/+layout.svelte | 62 +++++- .../docs/products/databases/+page.markdoc | 10 +- .../databases/dedicated/+page.markdoc | 117 +++++++++++ .../databases/dedicated/backups/+page.markdoc | 172 ++++++++++++++++ .../dedicated/branches/+page.markdoc | 128 ++++++++++++ .../databases/dedicated/connect/+page.markdoc | 183 ++++++++++++++++++ .../dedicated/extensions/+page.markdoc | 97 ++++++++++ .../dedicated/high-availability/+page.markdoc | 119 ++++++++++++ .../dedicated/monitoring/+page.markdoc | 139 +++++++++++++ .../databases/dedicated/network/+page.markdoc | 101 ++++++++++ .../databases/dedicated/pooler/+page.markdoc | 128 ++++++++++++ .../dedicated/specifications/+page.markdoc | 84 ++++++++ .../databases/dedicated/sql-api/+page.markdoc | 146 ++++++++++++++ 15 files changed, 1627 insertions(+), 4 deletions(-) create mode 100644 src/routes/blog/post/announcing-dedicated-databases/+page.markdoc create mode 100644 src/routes/changelog/(entries)/2026-05-21.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/backups/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/branches/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/connect/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/extensions/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/network/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/pooler/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/specifications/+page.markdoc create mode 100644 src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc diff --git a/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc b/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc new file mode 100644 index 00000000000..a2e4a3b6d0d --- /dev/null +++ b/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc @@ -0,0 +1,127 @@ +--- +layout: post +title: "Announcing Dedicated databases: managed PostgreSQL, MySQL, MariaDB, and MongoDB on Appwrite" +description: Run production database engines on Appwrite Cloud. PostgreSQL, MySQL, MariaDB, and MongoDB with high availability, point-in-time recovery, branching, extensions, and a connection pooler — billed by the hour from $15/mo. +date: 2026-05-21 +cover: /images/blog/announcing-dedicated-databases/cover.avif +timeToRead: 8 +author: jake-barnby +category: announcement +featured: true +callToAction: true +faqs: + - question: "How is this different from Appwrite Databases?" + answer: "Appwrite Databases is the document store — a curated TablesDB API with rows, columns, queries, and permissions, accessible through the Appwrite SDKs. Dedicated databases are real PostgreSQL, MySQL, MariaDB, or MongoDB engines that you connect to with their native drivers. The two products are independent — many teams will use both. Use the document store for app data that benefits from Appwrite's permission model; use a dedicated database when you need raw SQL, extensions like PostGIS or pgvector, or compatibility with an existing tool." + - question: "Which plans include dedicated databases?" + answer: "The Pro plan and higher. A payment method must be attached to the team. The Free specification (shared, scale-to-zero) is included with Pro at no extra cost; paid specifications are billed by the hour against the per-specification monthly price." + - question: "What engines and versions are supported?" + answer: "PostgreSQL 17 and 18 (default 18), MySQL 8.0 and 8.4 (default 8.4), MariaDB 10.11 and 11.4 (default 11.4), and MongoDB 7.0 and 8.0 (default 8.0). Version upgrades and resize operations are online — data streams to a new instance via engine-native logical replication and traffic cuts over once replication is caught up, with no read or write outages." + - question: "Can I bring my own backup storage bucket?" + answer: "Yes. The backup storage endpoint accepts S3-compatible credentials — AWS S3, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, Azure Blob via S3 interop. Backups go directly to your bucket and never enter Appwrite-managed storage. Credentials are encrypted at rest." + - question: "How does failover work?" + answer: "Appwrite continuously watches the primary. After two consecutive failed health checks (3-second timeout each), the platform promotes the replica with the lowest replication lag and emits a `failover.completed` event. A 60-second cooldown prevents flapping. You can also trigger a manual failover through the API, optionally targeting a specific replica." + - question: "Is read/write splitting automatic?" + answer: "When HA is enabled and the connection pooler is on, read/write splitting is on by default. The pooler inspects each query and routes writes plus transactions to the primary, ambient SELECTs to replicas. Turn it off if your workload requires every read to see the just-committed write." + - question: "Which region does my dedicated database live in?" + answer: "A dedicated database lives in the same region as the project that owns it — there's no per-database region selector. That means it sits next to your Functions, Sites, and Storage on the regional private network with no extra configuration." + - question: "How is billing pro-rated?" + answer: "Specification cost is pro-rated by the number of active days in the billing period — a database created mid-month is billed only from its creation timestamp to the end of the period, and a deleted database is billed only up to the deletion timestamp. Add-ons (HA, PITR, cross-region) follow the same pro-ration. Storage and bandwidth overage are computed against the included allowance for the specification." +--- + +Today we're announcing **Dedicated databases** on Appwrite Cloud. You can now run managed PostgreSQL, MySQL, MariaDB, and MongoDB engines next to your existing Appwrite Functions, Sites, and Storage, with the same per-region deployment, the same single payment relationship, and the same Console. + +Dedicated databases are a separate product from [Appwrite Databases](/docs/products/databases) — our document store with rows, columns, queries, and built-in permissions. The two products solve different problems and many teams will use both. Use the document store when you want app data managed through the Appwrite SDKs and the platform's permission model. Use a dedicated database when you need raw SQL, the PostgreSQL extension ecosystem, your existing ORM, or wire-compatibility with another tool. + +# What's in the box + +Each dedicated database is a real database engine running on its own dedicated compute, with its own storage, its own credentials, and its own public hostname. The engine is yours; Appwrite manages everything around it. + +**Four engines:** + +- **PostgreSQL 17 / 18**, with the standard contrib extensions plus a curated catalog of community extensions — PostGIS, pgvector, TimescaleDB, pg_stat_statements, pg_trgm, and more. +- **MySQL 8.0 / 8.4**, with semi-synchronous replication and connection pooling. +- **MariaDB 10.11 / 11.4**, with the same replication and pooling story as MySQL. +- **MongoDB 7.0 / 8.0**, with replica-set high availability via Raft. + +**Eight paid specifications** from `s-1vcpu-1gb` at $15/month to `s-8vcpu-64gb` at $860/month, plus a free shared specification included with the Pro plan. Specifications are dedicated CPU, not burstable; they're 10–20% below the equivalent tier on Supabase, RDS, and PlanetScale at the same memory level. Every paid specification gets dedicated compute that never spins down, with included storage (10 GB to 3 TB) and included bandwidth (50 GB to 10 TB) baked into the monthly price. + +**Six regions** available today — Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto — with Amsterdam, London, and Bangalore in the rollout queue. A dedicated database lives in the same region as the project that owns it, so it sits next to your Functions, Sites, and Storage on the regional private network with no extra configuration. + +# Production from day one + +The features that you'd normally bolt on after the fact — HA, PITR, branching, pooling — are all part of the product. + +## High availability + +Enable HA with one update call and the platform provisions the replicas, places them on different physical hosts from the primary, and turns on engine-native replication: WAL streaming for PostgreSQL, semi-synchronous binlog for MySQL/MariaDB, and Raft oplog tailing for MongoDB. Sync mode is `async`, `sync`, or `quorum` (PostgreSQL/MySQL) so you pick the durability/latency point that fits your workload. + +When the primary stays unhealthy across two 3-second probes, Appwrite takes a 15-second distributed lock, selects the lowest-lag replica, promotes it in place (no restart for SQL engines), runs a write-verification probe, drains connections from the old primary, and emits a `failover.completed` event to your webhooks, realtime channels, and functions. There's a 60-second cooldown so transient infrastructure events don't cause failover flapping. + +Up to five replicas per database. Replicas are billed at 50% of the underlying specification per replica. + +## Backups and point-in-time recovery + +Every database supports scheduled backups with cron syntax, retention from one to 365 days, and on-demand verification — Appwrite re-downloads the backup, decrypts it in an isolated environment, and runs a sanity check (`pg_restore --list`, `mongorestore --dryRun`, …) to confirm it would actually restore. A backup that's never been verified is a hope, not a backup. + +Backups are AES-256 encrypted before they leave the database. The encryption key is unique per database and stored in Appwrite's encrypted secret store scoped to the database, so the cloud storage provider only ever sees ciphertext. You can use Appwrite-managed storage or bring your own S3-compatible bucket — AWS, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, Azure Blob via S3 interop. + +For workloads that can't afford to lose the last hour of writes between scheduled backups, **point-in-time recovery** layers continuous WAL/binlog/oplog archiving on top. Enable PITR (20% of the specification price) and you can restore to any second within the PITR window (up to 35 days). The restore produces a new database; the source is untouched, so you can validate the recovery before swapping traffic to it. + +## Branching + +A **branch** is a near-instant ephemeral copy of a dedicated database, created from a storage snapshot of the parent. The parent is briefly quiesced (`CHECKPOINT` on PostgreSQL, `FLUSH TABLES WITH READ LOCK` on MySQL, `db.fsyncLock()` on MongoDB — under a second in practice), the snapshot is taken, the parent resumes, and the branch comes up with its own credentials and its own hostname. + +Branches are the right answer to "I want to test this migration without a maintenance window" or "I want a preview database per pull request" or "I want to throw a heavy `EXPLAIN ANALYZE` at production data without touching production". The parent and the branch share storage cost-free at the moment of branching (copy-on-write); cost accumulates only as the two databases diverge. + +Branches are billed like a standalone dedicated database against their own specification, so a small branch sitting next to a large parent is cheap. + +## Connection pooler + +Each dedicated database can run its own pooler — PostgreSQL on port 6432, MySQL/MariaDB on port 6033. The pooler is enabled by default on provision and is configurable per database — pool mode (`transaction` / `session` / `statement`), max client connections, default backend pool size. + +When HA is enabled, the pooler also handles **read/write splitting**: a query parser inspects each statement, pins transactions and writes to the primary, and routes ambient `SELECT`s to replicas. The application driver sees a single hostname and port; everything else is transparent. No SDK changes. + +MongoDB doesn't have a pooler equivalent in this catalog — its driver does connection management client-side, and `readPreference: 'secondary'` does the read routing. + +## SQL API + +For environments where opening a TCP connection isn't practical — Cloudflare Workers, Vercel Edge Functions, Deno Deploy, ad-hoc admin scripts — every dedicated database can also expose a **managed SQL API** over HTTPS. POST a parameterised statement with bindings, get back a JSON result. Schema-altering statements (`DROP`, `ALTER`, `GRANT`, …) are rejected unconditionally; DML statement types are gated by a per-database whitelist. Hard caps on rows, bytes, and execution time prevent runaway queries from taking down the engine. + +The SQL API is opt-in per database and disabled by default. + +# How connection works + +Each database gets a hostname of the form `db--..appwrite.network`. Resolve it with any DNS resolver, connect with `psql`, `mysql`, `mongosh`, or any driver in any language. TLS is enforced — `sslmode=require`, `useSSL=true`, `tls=true` — and the certificate is signed by a public CA, so no custom bundle is required. + +Behind the hostname sits Appwrite's edge proxy. It parses the engine's startup packet (PostgreSQL `StartupMessage`, MySQL handshake, MongoDB `OP_MSG hello`) to identify the database, applies your IP allowlist, and forwards the connection to the engine. For shared (free) databases, the proxy also detects the cold-start case: if the database has scaled to zero, the proxy triggers a start, waits for readiness, and forwards the handshake. The underlying storage is preserved across spin-downs, so no data is lost. + +# Pricing without surprises + +Three lines on the invoice cover the bulk of the cost: + +- **Specification cost** — pro-rated by active days in the billing period. A database created mid-month bills only from its creation timestamp. +- **Storage overage** — $0.125 per GB per month above the included allowance for the specification. +- **Bandwidth overage** — $0.08 per GB per month above the included allowance. + +Feature add-ons (HA replicas, cross-region replicas, cross-region standby, PITR) are billed as a percentage of the underlying specification price, pro-rated the same way. PostgreSQL extensions are free. + +# Get started + +Dedicated databases are available now on Appwrite Cloud for all Pro plan teams. The fastest path: + +1. Open your project in the Appwrite Console. +2. Open the **Databases** section in the sidebar. +3. Click **Create database** and choose the **Dedicated** type. +4. Pick an engine, version, and specification — the database is provisioned in your project's region. +5. Connect with `psql`, `mysql`, or `mongosh`. + +For the programmatic flow, the [Connect documentation](/docs/products/databases/dedicated/connect) shows credential retrieval and connection examples in Node, Python, PHP, and Go. The [Specifications page](/docs/products/databases/dedicated/specifications) has the full price list and resource limits. + +# Resources + +- [Dedicated databases documentation](/docs/products/databases/dedicated) +- [Specifications and pricing](/docs/products/databases/dedicated/specifications) +- [High availability](/docs/products/databases/dedicated/high-availability) +- [Backups and PITR](/docs/products/databases/dedicated/backups) +- [Branches](/docs/products/databases/dedicated/branches) +- [Join the Appwrite Discord community](https://appwrite.io/discord) diff --git a/src/routes/changelog/(entries)/2026-05-21.markdoc b/src/routes/changelog/(entries)/2026-05-21.markdoc new file mode 100644 index 00000000000..ab1ab4a058b --- /dev/null +++ b/src/routes/changelog/(entries)/2026-05-21.markdoc @@ -0,0 +1,18 @@ +--- +layout: changelog +title: "Announcing Dedicated databases for Appwrite Cloud" +date: 2026-05-21 +cover: /images/blog/announcing-dedicated-databases/cover.avif +--- + +Appwrite Cloud now supports [**Dedicated databases**](/docs/products/databases/dedicated) — managed PostgreSQL, MySQL, MariaDB, and MongoDB engines running next to your existing Functions, Sites, and Storage. Pick an engine (Postgres 17/18, MySQL 8.0/8.4, MariaDB 10.11/11.4, MongoDB 7.0/8.0), pick a region (Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto), pick a specification from `s-1vcpu-1gb` at $15/mo to `s-8vcpu-64gb` at $860/mo, and connect with `psql`, `mysql`, `mongosh`, or any standard driver. + +High availability, point-in-time recovery, branching, PostgreSQL extensions, connection pooling with read/write split, and a managed SQL API over HTTPS are all available out of the box. Dedicated databases are billed by the hour and pro-rated by active days; the Pro plan includes a free, scale-to-zero shared specification at no extra cost. + +{% arrow_link href="/blog/post/announcing-dedicated-databases" %} +Read the announcement +{% /arrow_link %} + +{% arrow_link href="/docs/products/databases/dedicated" %} +Dedicated databases in the docs +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/+layout.svelte b/src/routes/docs/products/databases/+layout.svelte index 87f1ffb5a32..ec407d28aa5 100644 --- a/src/routes/docs/products/databases/+layout.svelte +++ b/src/routes/docs/products/databases/+layout.svelte @@ -127,6 +127,66 @@ } ] }, + { + label: 'Dedicated databases', + items: [ + { + label: 'Overview', + href: '/docs/products/databases/dedicated', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'Specifications', + href: '/docs/products/databases/dedicated/specifications', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'Connect', + href: '/docs/products/databases/dedicated/connect', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'High availability', + href: '/docs/products/databases/dedicated/high-availability', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'Backups & PITR', + href: '/docs/products/databases/dedicated/backups', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'Branches', + href: '/docs/products/databases/dedicated/branches', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'Extensions', + href: '/docs/products/databases/dedicated/extensions', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'Connection pooler', + href: '/docs/products/databases/dedicated/pooler', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'SQL API', + href: '/docs/products/databases/dedicated/sql-api', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'Network', + href: '/docs/products/databases/dedicated/network', + new: isNewUntil('31 Aug 2026') + }, + { + label: 'Monitoring & insights', + href: '/docs/products/databases/dedicated/monitoring', + new: isNewUntil('31 Aug 2026') + } + ] + }, { label: 'References', items: [ @@ -150,7 +210,7 @@ .replace('tables', 'collections') ); - const hideSubtitleRoutes = ['offline', 'backups', 'csv-imports', 'csv-exports']; + const hideSubtitleRoutes = ['offline', 'backups', 'csv-imports', 'csv-exports', 'dedicated']; const shouldShowSubtitle = $derived( !hideSubtitleRoutes.some((segment) => page.route.id?.includes(segment)) && diff --git a/src/routes/docs/products/databases/+page.markdoc b/src/routes/docs/products/databases/+page.markdoc index 9217bde1e68..0d385e3ebfb 100644 --- a/src/routes/docs/products/databases/+page.markdoc +++ b/src/routes/docs/products/databases/+page.markdoc @@ -4,7 +4,7 @@ title: Databases description: Store and query structured data with Appwrite Databases. Databases provide performant and scalable storage for your application, business, and user data. --- -Appwrite Databases let you store and query structured data. +Appwrite Databases let you store and query structured data. Databases provide high-performance and scalable data storage for your key application, business, and user data. {% info title="Looking for file storage?" %} @@ -15,5 +15,9 @@ You can organize data into databases, tables, and rows. You can also paginate, o For complex business logic, Appwrite supports relationships to help you model your data. {% arrow_link href="/docs/products/databases/quick-start" %} -Quick start -{% /arrow_link %} \ No newline at end of file +Quick start +{% /arrow_link %} + +{% info title="Need a managed PostgreSQL, MySQL, MariaDB, or MongoDB?" %} +Appwrite also offers [Dedicated databases](/docs/products/databases/dedicated) — managed database engines you connect to with `psql`, `mysql`, `mongosh`, or any standard driver. They're a separate product from the document store above. +{% /info %} \ No newline at end of file diff --git a/src/routes/docs/products/databases/dedicated/+page.markdoc b/src/routes/docs/products/databases/dedicated/+page.markdoc new file mode 100644 index 00000000000..30355bd5851 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/+page.markdoc @@ -0,0 +1,117 @@ +--- +layout: article +title: Dedicated databases +description: Run managed PostgreSQL, MySQL, MariaDB, and MongoDB databases on Appwrite. Production-ready compute, storage, backups, high availability, and global regions, billed by the hour. +--- + +Appwrite **Dedicated databases** lets you run managed PostgreSQL, MySQL, MariaDB, and MongoDB instances on Appwrite Cloud. You pick the engine and the compute size; Appwrite provisions a real database in your project's region, with its own storage, networking, and credentials, and exposes it to your application over the public internet or your own private network. + +Dedicated databases are different from the [Appwrite Databases](/docs/products/databases) document store. Where the document store gives you a curated `TablesDB` API with rows, queries, and SDK helpers, dedicated databases give you the raw engine — you connect with `psql`, `mysql`, `mongosh`, or any driver of your choice and use the full feature set of the underlying database. + +{% info title="Looking for the document store?" %} +If you want to query data through Appwrite SDKs with rows, columns, and built-in permissions, you want [Appwrite Databases](/docs/products/databases). Dedicated databases are for teams who want a managed PostgreSQL, MySQL, MariaDB, or MongoDB engine they can talk to directly with their own ORM, migrations, and drivers. +{% /info %} + +# Supported engines {% #engines %} + +Appwrite manages the database container, storage, backups, and networking. You bring the application. + +| Engine | Default version | Other supported versions | Default port | +|------------|-----------------|--------------------------|--------------| +| PostgreSQL | 18 | 17 | 5432 | +| MySQL | 8.4 | 8.0 | 3306 | +| MariaDB | 11.4 | 10.11 | 3306 | +| MongoDB | 8.0 | 7.0 | 27017 | + +Version is selected on create and can be changed later via the upgrade endpoint. Upgrades run online — a second instance is provisioned on the new version, data is streamed over with engine-native logical replication, and traffic cuts over once replication is caught up. There are no read or write outages during the upgrade, and no manual dump-and-restore. + +# Database types {% #types %} + +A database is either **shared** or **dedicated**: + +| Type | Lifecycle | Resources | Use case | +|---------------|------------------------------------------|---------------------------------|---------------------------| +| **Shared** | Scales to zero after 15 minutes idle | 125m CPU, 128 MB RAM, 1 GB disk | Development, low traffic | +| **Dedicated** | Always-on, with optional HA replicas | Any specification | Production workloads | + +Shared databases are included with the Pro plan and use the **Free** specification. On the next connection after a spin-down, the database cold-starts in a few seconds — the underlying storage is preserved between spin-downs, so no data is lost. + +Dedicated databases are billed per hour against one of eight paid specifications and never spin down. They also unlock features that the shared specification does not have access to: high availability, point-in-time recovery, custom backup policies, storage autoscaling, IP allowlists, and connection pooling. + +# Regions {% #regions %} + +A dedicated database lives in the same region as the project that owns it — there's no per-database region selector. Dedicated databases are available in every Appwrite region: + +| Region | Code | Endpoint | +|---------------|------|----------------------------------------------| +| Frankfurt | FRA | `db--.fra.appwrite.network` | +| New York | NYC | `db--.nyc.appwrite.network` | +| San Francisco | SFO | `db--.sfo.appwrite.network` | +| Singapore | SGP | `db--.sgp.appwrite.network` | +| Sydney | SYD | `db--.syd.appwrite.network` | +| Toronto | TOR | `db--.tor.appwrite.network` | + +Each region is independent — data does not leave the region unless you explicitly opt into the cross-region failover feature. + +# Feature overview {% #features %} + +{% cards %} +{% cards_item href="/docs/products/databases/dedicated/specifications" title="Specifications" %} +Eight paid specifications from $15 to $860 per month, plus a free shared tier on Pro. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/connect" title="Connect" %} +Connect with `psql`, `mysql`, `mongosh`, or any driver. Credentials are rotatable from the API. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/high-availability" title="High availability" %} +Up to five synchronous, semi-synchronous, or asynchronous replicas with automatic failover. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/backups" title="Backups & PITR" %} +Scheduled encrypted backups with up to 365 days of retention and 35 days of point-in-time recovery. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/branches" title="Branches" %} +Spin up an ephemeral copy of your database in seconds from a storage snapshot. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/extensions" title="Extensions" %} +Install PostgreSQL extensions like PostGIS, pgvector, and TimescaleDB from the API. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/pooler" title="Connection pooler" %} +Per-database connection pooler with automatic read/write split when HA is enabled. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/sql-api" title="SQL API" %} +Run parameterised SQL over HTTP without opening a TCP connection. Useful from edge runtimes. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/network" title="Network" %} +TLS by default, optional IP allowlists, and a private hostname per database. +{% /cards_item %} +{% cards_item href="/docs/products/databases/dedicated/monitoring" title="Monitoring & insights" %} +CPU, memory, IOPS, slow queries, and index recommendations — pushed to the Console live. +{% /cards_item %} +{% /cards %} + +# Limits {% #limits %} + +The following per-database limits are enforced by the API: + +| Limit | Value | +|--------------------------------|-----------------------------------------------------------| +| Databases per project | 10 | +| CPU range | 125m – 16000m | +| Memory range | 128 MB – 64 GB | +| Storage range | 1 GB – 16 TB | +| High availability replicas | 0 – 5 | +| IP allowlist entries | 100 | +| Backup retention | 1 – 365 days | +| Point-in-time recovery window | 1 – 35 days | +| Database extensions installed | 50 (PostgreSQL only) | +| Max simultaneous connections | 10,000 (caps to specification quota at runtime) | +| Idle timeout (shared only) | 5 – 60 minutes | + +# Billing & plan requirements {% #plans %} + +Dedicated databases are available on the **Pro** plan and higher. A payment method must be attached to the team. Shared (free specification) databases are included with the Pro plan at no extra cost. + +Each dedicated database is billed by the hour against its specification price, with separate line items for storage and bandwidth overage and percentage-based add-ons for HA, cross-region replication, and point-in-time recovery. See the [specifications page](/docs/products/databases/dedicated/specifications) for the full price list. + +{% arrow_link href="/docs/products/databases/dedicated/specifications" %} +View specifications and pricing +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/dedicated/backups/+page.markdoc b/src/routes/docs/products/databases/dedicated/backups/+page.markdoc new file mode 100644 index 00000000000..e5493ac35b3 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/backups/+page.markdoc @@ -0,0 +1,172 @@ +--- +layout: article +title: Backups & PITR +description: Encrypted scheduled backups with up to 365 days of retention, on-demand manual backups, and point-in-time recovery to any second within the PITR window. +--- + +Every dedicated database supports automated backups out of the box. Backups are AES-256 encrypted at rest, uploaded to an object store, and verifiable on demand. Point-in-time recovery (PITR) layers continuous WAL/binlog/oplog archiving on top, so you can restore to any second within the retention window — not just to a backup checkpoint. + +# What gets backed up {% #scope %} + +A backup captures the full contents of the database: all schemas, tables/collections, indexes, extensions, and (optionally) user-defined roles. The backup is taken with the engine's native tool — `pg_dump` / `mysqldump` / `mongodump` — in a dedicated job that runs alongside (not on top of) the database, so production traffic is unaffected. + +# Schedule {% #schedule %} + +Scheduled backups run from a cron expression you configure on the database. The default is `0 3 * * *` (daily at 03:00 UTC), jittered to spread load across the platform. + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "backupEnabled": true, + "backupCron": "0 3 * * *", + "backupRetentionDays": 14 + }' \ + https://.cloud.appwrite.io/v1/compute/databases/ +``` + +Backup retention accepts 1–365 days. Backups older than the retention window are deleted automatically once a day. + +# Manual backups {% #manual %} + +To create an ad-hoc backup — before a destructive migration, for example — call the create endpoint. The response contains the backup ID; the actual dump runs asynchronously. + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//backups +``` + +Manual backups respect the same retention setting as scheduled ones. Pass `retentionDays` in the request body to override the default. + +# Encryption {% #encryption %} + +Backups are encrypted before they leave the database. A unique 32-byte AES-256 key is generated for the database on first backup and stored in Appwrite's encrypted secret store, scoped to the database. The dump is piped through `openssl enc -aes-256-cbc -pbkdf2` and uploaded to the configured storage target. + +Restore reverses the pipeline: the key is fetched from the secret store, the ciphertext is decrypted, and the engine restore tool consumes the plaintext stream. The key never leaves Appwrite-controlled infrastructure. + +# Storage targets {% #storage %} + +By default, backups are stored on Appwrite-managed S3-compatible storage. You can also bring your own bucket — S3, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, or Azure Blob via S3 interop. Bring-your-own-bucket backups never enter Appwrite-controlled storage. + +```bash +curl -X PUT \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "provider": "s3", + "bucket": "my-db-backups", + "region": "eu-central-1", + "prefix": "appwrite/", + "accessKey": "", + "secretKey": "" + }' \ + https://.cloud.appwrite.io/v1/compute/databases//backups/storage +``` + +Credentials are encrypted at rest. The `endpoint` field is supported for S3-compatible providers — leave empty for AWS S3. + +# Verify a backup {% #verify %} + +A backup that has never been verified is a hope, not a backup. Appwrite exposes a verification endpoint that downloads the backup, decrypts it, and runs an engine-native sanity check (`pg_restore --list`, `mysql --execute "..."`, `mongorestore --dryRun`) in an isolated environment, then tears the temporary resources down. + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//backups//verifications +``` + +The result is recorded on the backup document under `verifiedAt`. You can query the backup later to see verification history. + +# Restore from a backup {% #restore %} + +Restoring from a backup creates a new dedicated database, applies the dump, and waits for the engine to come up. The original database is untouched. + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "type": "backup", + "backupId": "" + }' \ + https://.cloud.appwrite.io/v1/compute/databases//restorations +``` + +The new database is provisioned with the same specification as the source unless you also pass overrides for `cpu`, `memory`, and `storage`. + +# Point-in-time recovery {% #pitr %} + +PITR layers continuous transaction-log archiving on top of scheduled backups. With PITR enabled, the database keeps shipping WAL segments (PostgreSQL), binlog files (MySQL/MariaDB), or oplog entries (MongoDB) to object storage every five minutes. To restore, the platform replays the most recent base backup plus the WAL up to the target timestamp. + +Enable PITR on create or update: + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ "backupPitr": true, "pitrRetentionDays": 7 }' \ + https://.cloud.appwrite.io/v1/compute/databases/ +``` + +`pitrRetentionDays` accepts 1–35 days. PITR is billed as a feature add-on at 20% of the underlying specification price. + +# Recovery windows {% #windows %} + +Each database with PITR enabled exposes its current recovery window. Use this to confirm the earliest and latest timestamps you can restore to before initiating a recovery: + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//pitr +``` + +Response: + +```json +{ + "earliestRecoveryPoint": "2026-05-14T00:00:00Z", + "latestRecoveryPoint": "2026-05-21T13:34:12Z", + "windows": [ + { "from": "2026-05-14T00:00:00Z", "to": "2026-05-21T13:34:12Z" } + ] +} +``` + +# Restore to a point in time {% #pitr-restore %} + +A PITR restore takes a target timestamp inside the active window and produces a new database that represents the state of the source at that exact moment: + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "type": "pitr", + "targetTime": "2026-05-21T13:30:00Z" + }' \ + https://.cloud.appwrite.io/v1/compute/databases//restorations +``` + +The new database is independent — recovering does not roll back the source. Once you've validated the recovery, you can swap your application traffic to it or copy specific tables/collections back to the source with `pg_dump` / `mysqldump` / `mongodump`. + +# Limits {% #limits %} + +| Limit | Value | +|----------------------------------|-------------------| +| Backup retention | 1 – 365 days | +| PITR retention | 1 – 35 days | +| Manual + scheduled backups | Unlimited (subject to storage) | +| Cron schedule resolution | 1 minute | +| WAL/binlog/oplog flush cadence | every 5 minutes | + +Backup storage usage rolls up into the dedicated database storage line on your invoice — there's no separate backup storage line item. diff --git a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc new file mode 100644 index 00000000000..0fbfb002940 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc @@ -0,0 +1,128 @@ +--- +layout: article +title: Branches +description: Spin up an ephemeral, isolated copy of a dedicated database in seconds from a storage snapshot. Use branches for previews, migrations, and testing. +--- + +A **branch** is a short-lived, isolated copy of a dedicated database. It has its own credentials, its own endpoint, and starts from a point-in-time snapshot of the parent's storage volume. Branches are not replicas — once created, they diverge from the parent and never sync back. + +Use cases: + +- **Preview environments** — one branch per pull request, destroyed when the PR closes +- **Test migrations** — apply a destructive `ALTER` against the branch first, observe behaviour, then run it against the source +- **Reproduce a bug** — branch off the most recent PITR window, attach a debugger, throw it away when done +- **Try an analytical query** — heavy `EXPLAIN ANALYZE` work against a branch can't slow down the primary + +# How it works {% #how %} + +Creating a branch: + +1. **Quiesce the parent** briefly. PostgreSQL takes a `CHECKPOINT`. MySQL/MariaDB issue `FLUSH TABLES WITH READ LOCK`. MongoDB runs `db.fsyncLock()`. +2. **Snapshot the parent's storage volume.** This is a near-instant copy-on-write operation. +3. **Release the parent.** Total parent quiesce time is typically under a second. +4. **Provision the branch** from the snapshot — its own isolated storage volume, its own database instance. +5. **Start the branch** on the same engine and version. Resources can be smaller than the parent. +6. **Generate branch credentials** and return the branch endpoint. + +The parent and the branch share storage cost-free at the moment of branching (copy-on-write). Storage cost grows as the two databases diverge. + +PostgreSQL supports crash-consistent snapshots, so the quiesce step is a fast checkpoint and PostgreSQL branches recover via WAL replay on first start. MySQL/MariaDB and MongoDB require an explicit write freeze before snapshot to produce a clean image. + +# Create a branch {% #create %} + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "branchId": "pr-1234", + "name": "PR 1234 preview", + "cpu": 1000, + "memory": 2048 + }' \ + https://.cloud.appwrite.io/v1/compute/databases//branches +``` + +Optional fields: + +| Field | Default | Purpose | +|------------|-------------------------|---------------------------------------------------------| +| `cpu` | parent's CPU | Branch can be smaller (or larger) than the parent | +| `memory` | parent's memory | Same | +| `storage` | parent's storage | Branches inherit storage from the snapshot | + +The branch endpoint is exposed as a separate hostname: + +``` +db---..appwrite.network +``` + +Connect to it with the engine's standard CLI, same as the parent. Credentials for the branch are retrieved through: + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//branches/ +``` + +# List branches {% #list %} + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//branches +``` + +# Delete a branch {% #delete %} + +Deleting a branch removes the branch's database instance, its storage volume, and the underlying snapshot. There's no soft-delete — once the branch is gone, the data is gone. + +```bash +curl -X DELETE \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//branches/ +``` + +# Billing {% #billing %} + +A branch is billed like a standalone dedicated database against its own specification (CPU, memory, storage). The snapshot itself is free at the moment of branching; storage cost accumulates as the branch's data diverges from the parent. + +There is no separate "branch" line item — branches roll into your regular dedicated database storage and compute totals. + +# Use case: per-PR preview database {% #ci-example %} + +A CI pipeline that branches on every pull request and tears down on merge or close: + +```yaml +# .github/workflows/preview.yml +on: + pull_request: + types: [opened, reopened, synchronize, closed] + +jobs: + preview: + runs-on: ubuntu-latest + steps: + - name: Create or update preview branch + if: github.event.action != 'closed' + run: | + curl -X POST \ + -H "X-Appwrite-Project: $PROJECT_ID" \ + -H "X-Appwrite-Key: $API_KEY" \ + -d '{ "branchId": "pr-${{ github.event.number }}", "name": "PR #${{ github.event.number }}" }' \ + https://fra.cloud.appwrite.io/v1/compute/databases/$DB_ID/branches || true + + - name: Tear down preview branch on close + if: github.event.action == 'closed' + run: | + curl -X DELETE \ + -H "X-Appwrite-Project: $PROJECT_ID" \ + -H "X-Appwrite-Key: $API_KEY" \ + https://fra.cloud.appwrite.io/v1/compute/databases/$DB_ID/branches/pr-${{ github.event.number }} +``` + +The `|| true` on `POST` makes the workflow idempotent — if the branch already exists, the call is a no-op. diff --git a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc new file mode 100644 index 00000000000..0f3a2c37ad2 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc @@ -0,0 +1,183 @@ +--- +layout: article +title: Connect +description: Connect to a dedicated database with psql, mysql, mongosh, or any standard driver. Retrieve credentials from the API, manage additional users, and rotate the primary password. +--- + +A dedicated database exposes a real engine endpoint over TLS. You connect to it the same way you would connect to any PostgreSQL, MySQL, MariaDB, or MongoDB server: with the engine's standard CLI or any ORM/driver in any language. + +# Get the connection details {% #credentials %} + +The connection details for a dedicated database are exposed through the [credentials endpoint](/docs/references/cloud/server-rest/compute#getDatabaseCredentials): + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//credentials +``` + +Response: + +```json +{ + "host": "db--..appwrite.network", + "port": 5432, + "username": "appwrite", + "password": "<32-char-hex>", + "database": "appwrite", + "engine": "postgres", + "ssl": true, + "connectionString": "postgres://appwrite:@db--..appwrite.network:5432/appwrite?sslmode=require" +} +``` + +The default database name and username on every engine are both `appwrite`. The primary password is generated on provision and stored encrypted in the platform database; you can rotate it from the API at any time without provisioning a new database. + +# Connect from a shell {% #shells %} + +PostgreSQL: + +```bash +psql "$(curl -s ... credentials | jq -r .connectionString)" +# or directly: +psql -h db--..appwrite.network -p 5432 -U appwrite -d appwrite +``` + +MySQL / MariaDB: + +```bash +mysql -h db--..appwrite.network -P 3306 -u appwrite -p appwrite +``` + +MongoDB: + +```bash +mongosh "mongodb://appwrite:@db--..appwrite.network:27017/appwrite?tls=true" +``` + +# Rotate the primary password {% #rotate %} + +The credentials endpoint also accepts `POST` to issue a new password for the primary user. The change is applied atomically in the underlying engine and the stored credential is updated in the same call. Existing sessions remain alive until the engine's idle timeout, then have to authenticate with the new password. + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//credentials +``` + +# Additional connection users {% #connections %} + +The primary `appwrite` user has full ownership of the default database. For applications that need narrower roles — a read-only reporting user, a write-only ingestion user, a per-tenant user — you can create additional connections from the API: + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "username": "analytics_ro", + "database": "appwrite", + "role": "readonly" + }' \ + https://.cloud.appwrite.io/v1/compute/databases//connections +``` + +The response contains the generated password for the new user. Appwrite stores it encrypted; if you lose it, delete the connection and create a new one. + +Available roles: + +| Role | PostgreSQL / MySQL / MariaDB | MongoDB | +|--------------|-------------------------------------------------------|-------------------------------| +| `readonly` | `SELECT` on all tables in the schema | `read` on the database | +| `readwrite` | `SELECT`, `INSERT`, `UPDATE`, `DELETE` (no schema DDL)| `readWrite` on the database | + +`readwrite` is the default. List, get, and delete connections through the same endpoints. The primary `appwrite` user cannot be deleted. + +# TLS {% #tls %} + +All dedicated database endpoints terminate TLS at the edge proxy and forward to the engine over Appwrite's internal regional network. Connection strings always include `sslmode=require` (PostgreSQL), `useSSL=true` (MySQL/MariaDB), or `tls=true` (MongoDB) by default — your driver should not need any extra configuration. + +To require mTLS, see the [network page](/docs/products/databases/dedicated/network). + +# Cold-start (shared databases) {% #cold-start %} + +If your database is on the **Free** specification, it spins down to zero compute after 15 minutes of inactivity. The next connection is intercepted by the edge proxy, which sends a start command to the platform and waits for the database to become ready before forwarding the handshake. + +The cold-start adds a few seconds to the first query after an idle period. Subsequent queries see no extra latency. The underlying storage is preserved across spin-downs, so no data is lost. + +# Connecting from an application {% #drivers %} + +There's nothing Appwrite-specific about the driver setup. A few example snippets: + +{% multicode %} +```nodejs +import { Client } from 'pg'; + +const client = new Client({ + host: 'db--..appwrite.network', + port: 5432, + user: 'appwrite', + password: process.env.DB_PASSWORD, + database: 'appwrite', + ssl: { rejectUnauthorized: true }, +}); + +await client.connect(); +const { rows } = await client.query('SELECT now()'); +console.log(rows); +``` +```python +import psycopg +import os + +with psycopg.connect( + host='db--..appwrite.network', + port=5432, + user='appwrite', + password=os.environ['DB_PASSWORD'], + dbname='appwrite', + sslmode='require', +) as conn: + with conn.cursor() as cur: + cur.execute('SELECT now()') + print(cur.fetchone()) +``` +```php +-..appwrite.network;port=5432;dbname=appwrite;sslmode=require', + 'appwrite', + getenv('DB_PASSWORD'), +); + +$rows = $pdo->query('SELECT now()')->fetchAll(); +print_r($rows); +``` +```go +package main + +import ( + "context" + "fmt" + "os" + + "github.com/jackc/pgx/v5" +) + +func main() { + conn, _ := pgx.Connect(context.Background(), + fmt.Sprintf("postgres://appwrite:%s@db--..appwrite.network:5432/appwrite?sslmode=require", + os.Getenv("DB_PASSWORD"))) + defer conn.Close(context.Background()) + + var now string + conn.QueryRow(context.Background(), "SELECT now()").Scan(&now) + fmt.Println(now) +} +``` +{% /multicode %} + +Once you can run a query, you can use any tool that talks the engine's wire protocol: pgAdmin, DataGrip, MySQL Workbench, MongoDB Compass, your ORM of choice, your migration tool of choice, your read-replica failover library of choice. Appwrite gets out of the way. diff --git a/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc b/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc new file mode 100644 index 00000000000..0fa05d18067 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc @@ -0,0 +1,97 @@ +--- +layout: article +title: Extensions +description: Install and manage PostgreSQL extensions like PostGIS, pgvector, and TimescaleDB on a dedicated database. Extensions are managed through the API and are free of charge. +--- + +PostgreSQL exposes a rich extension ecosystem — PostGIS for geospatial data, pgvector for embeddings, TimescaleDB for time-series, pg_stat_statements for query analytics, and many more. Dedicated PostgreSQL databases on Appwrite support managing extensions through the API; there is no separate cost. + +MySQL, MariaDB, and MongoDB do not have an equivalent extension system. The extensions endpoints are available on those engines but always return an empty list. + +# Install an extension {% #install %} + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "name": "pgvector", + "version": "0.7.4" + }' \ + https://.cloud.appwrite.io/v1/compute/databases//extensions +``` + +The request enqueues a worker that runs `CREATE EXTENSION` inside the database. The response returns immediately with status `creating`; the extension transitions to `ready` once the install completes (typically within a few seconds for small extensions). + +If the extension requires a non-default schema, pass the optional `schema` field. If it's a contrib extension shipped with PostgreSQL it's available out of the box; if it's a community extension, the platform installs the system package first. + +# List extensions {% #list %} + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//extensions +``` + +The response separates installed extensions from available extensions: + +```json +{ + "installed": [ + { "name": "pg_stat_statements", "version": "1.10", "schema": "public", "status": "ready" }, + { "name": "pgvector", "version": "0.7.4", "schema": "public", "status": "ready" } + ], + "available": [ + { "name": "postgis", "defaultVersion": "3.4", "category": "geospatial" }, + { "name": "timescaledb", "defaultVersion": "2.15", "category": "timeseries" }, + { "name": "pg_trgm", "defaultVersion": "1.6", "category": "text-search" } + ] +} +``` + +# Remove an extension {% #remove %} + +```bash +curl -X DELETE \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//extensions/pgvector +``` + +`DROP EXTENSION` is run with `RESTRICT` by default — if there are dependent objects (tables, indexes, columns), the call fails. Drop the dependents first, or call `DROP EXTENSION ... CASCADE` directly through SQL to acknowledge that you understand what gets removed. + +# Available extensions {% #available %} + +A non-exhaustive list of common extensions that are pre-packaged and installable through the API: + +| Category | Extensions | +|-------------|-------------------------------------------------------------------------------------------------------------| +| Geospatial | `postgis`, `postgis_raster`, `postgis_topology`, `pgrouting` | +| Vectors | `pgvector`, `pg_embedding` | +| Time-series | `timescaledb` | +| Analytics | `pg_stat_statements`, `auto_explain`, `pg_buffercache` | +| Text search | `pg_trgm`, `unaccent`, `fuzzystrmatch` | +| Crypto | `pgcrypto`, `uuid-ossp` | +| FDWs | `postgres_fdw`, `mysql_fdw`, `oracle_fdw` | +| HTTP | `pg_net`, `http` | +| Misc | `hstore`, `citext`, `tablefunc`, `intarray`, `ltree`, `cube`, `earthdistance` | + +If you need an extension that isn't on this list, [open a feature request](https://github.com/appwrite/appwrite/issues) — the catalog grows on demand. Custom or proprietary extensions require Enterprise support. + +# Version selection {% #versions %} + +Each extension has a list of versions tied to the PostgreSQL major version of the database. Newer PostgreSQL versions support newer extension versions; downgrading the engine version can require downgrading extensions first. Use the `version` field on the install request to pin to a specific version, or omit it to install the default version for the engine. + +To upgrade an extension version, drop it and reinstall it with the new version. The drop is rejected if there are dependent objects, which is the safe default for extensions that change schema (PostGIS, TimescaleDB). + +# Limits {% #limits %} + +| Limit | Value | +|---------------------------------------------|---------------| +| Maximum extensions per database | 50 | +| Extension install/uninstall billing | Free | +| Supported engines | PostgreSQL | + +The 50-extension cap is generous — most production deployments install fewer than ten. Extensions that pull in a lot of shared library code (PostGIS, TimescaleDB) count the same as small ones. diff --git a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc new file mode 100644 index 00000000000..b87517ec96f --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc @@ -0,0 +1,119 @@ +--- +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. +--- + +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. + +HA is opt-in per database and requires a paid specification — the free shared specification does not support replication. + +# How it works {% #how %} + +When you enable HA, Appwrite scales the underlying database to the configured replica count and turns on engine-native replication: + +| Engine | Replication mechanism | +|------------|---------------------------------------------------------------------------------------| +| PostgreSQL | Streaming replication over WAL | +| MySQL | Semi-synchronous binlog replication | +| MariaDB | Semi-synchronous 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. + +# Enable HA {% #enable %} + +Set `highAvailability`, `highAvailabilityReplicaCount`, and `highAvailabilitySyncMode` on the database. You can do this on create, or by updating the database later: + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "highAvailability": true, + "highAvailabilityReplicaCount": 2, + "highAvailabilitySyncMode": "sync" + }' \ + https://.cloud.appwrite.io/v1/compute/databases/ +``` + +`highAvailabilityReplicaCount` accepts 0–5. The primary is not counted — a value of 2 gives you one primary plus two replicas. + +Switching HA on or off is asynchronous: the platform queues a worker that provisions the new replicas, waits for them to come up, then runs an initial base sync. Watch the status with: + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//ha +``` + +The response contains per-replica state and current replication lag in seconds. + +# Automatic failover {% #failover %} + +Appwrite continuously watches the health of the primary. When the primary stays unhealthy across two consecutive 3-second health checks, the platform: + +1. Takes a 15-second distributed lock so only one controller acts. +2. Queries all replicas for their replication lag. +3. Selects the replica with the lowest lag as the failover target. +4. Promotes the target in place: `pg_ctl promote` (PostgreSQL), `STOP REPLICA; SET GLOBAL read_only=0;` (MySQL/MariaDB), or `rs.stepDown()` on the old primary followed by re-election (MongoDB). +5. Runs a write-verification probe on the new primary. +6. Drains live connections from the old primary so clients reconnect to the new one. + +A `failover.completed` event is emitted on the database, fanning out to webhooks, realtime subscriptions, and functions. There is a 60-second cooldown between failovers on the same database to prevent thrashing during infrastructure events. + +# Manual failover {% #manual-failover %} + +To trigger a failover yourself — for maintenance, testing, or a controlled swap — call the failover endpoint: + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ "targetReplicaId": "" }' \ + https://.cloud.appwrite.io/v1/compute/databases//ha/failovers +``` + +Omit `targetReplicaId` to let the platform pick the lowest-lag replica. + +# Read your writes {% #read-your-writes %} + +In `async` mode, a read that lands on a replica immediately after a write to the primary may not see the write yet. The application strategies: + +- **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. + +# Cross-region failover {% #cross-region-failover %} + +Beyond same-region HA, you can also configure a cross-region warm standby. The standby lives in another region and continuously replicates from the primary. On a planned or unplanned region-level failure you promote the standby and your DNS hostname starts resolving to the standby region. + +Cross-region replicas and standbys are billed as feature add-ons — see the [specifications page](/docs/products/databases/dedicated/specifications#add-ons) for the pricing. + +# Limits {% #limits %} + +| Limit | Value | +|------------------------------------------------|-------| +| 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 | + +Replicas are billed at 50% of the underlying specification price per replica. Cross-region replicas are billed at 75% per replica. diff --git a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc new file mode 100644 index 00000000000..2e2a41c5831 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc @@ -0,0 +1,139 @@ +--- +layout: article +title: Monitoring & insights +description: Live CPU, memory, IOPS, and connection metrics, slow query logging, query plan explanations, and automatic index recommendations for dedicated databases. +--- + +Every dedicated database collects its own resource telemetry, slow-query logs, and index analysis. The Appwrite Console renders the data in the Database view; the same data is available through the API so you can pipe it into your existing observability stack. + +# Live metrics {% #metrics %} + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + "https://.cloud.appwrite.io/v1/compute/databases//metrics?period=24h" +``` + +`period` accepts `1h`, `24h`, `7d`, or `30d`. + +The response includes: + +| Field | Unit | Notes | +|----------------------|---------------|----------------------------------------------------| +| `cpuPercent` | % | CPU utilisation over the period | +| `memoryPercent` | % | RSS as % of the specification's memory budget | +| `memoryUsedBytes` | bytes | Resident memory | +| `storageUsedBytes` | bytes | Engine on-disk size (data + indexes) | +| `connectionsActive` | count | Currently active backend connections | +| `connectionsMax` | count | Specification's connection cap | +| `iopsRead` | operations/s | Disk read IOPS | +| `iopsWrite` | operations/s | Disk write IOPS | +| `qps` | queries/s | Queries per second (across all sessions) | + +Metrics are sampled inside the database environment from OS-level resource counters and engine-native stats. Samples are pushed to the edge every 60 seconds and aggregated for the API. + +# Usage time series {% #usage %} + +For longer-range plots — invoice line items, capacity planning, anomaly detection — call the usage endpoint: + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + "https://.cloud.appwrite.io/v1/compute/databases//usage?range=30d" +``` + +`range` accepts `24h`, `7d`, `30d`, or `90d`. The response is a series of bucketed `(timestamp, value)` pairs for each metric. + +# Slow queries {% #slow-queries %} + +Each engine logs queries that run longer than the configured threshold to the slow-query log. The threshold defaults to 200 ms and is tunable via `metricsSlowQueryLogThresholdMs`. List slow queries through: + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//slow-queries +``` + +The response includes the query text, average duration, call count, user, and the matching database. The Appwrite Console deduplicates by query shape so you see the worst offenders first. + +# Query plan explanation {% #explanation %} + +To inspect the planner's choice for a specific query without running it against your production database, post the query to the explanation endpoint: + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "query": "SELECT * FROM users WHERE email = $1", + "analyze": false + }' \ + https://.cloud.appwrite.io/v1/compute/databases//explanation +``` + +| Engine | Underlying command | +|------------|---------------------------------------------------------------| +| PostgreSQL | `EXPLAIN (ANALYZE, BUFFERS, FORMAT JSON) ...` when `analyze` | +| MySQL | `EXPLAIN FORMAT=JSON ...` | +| MariaDB | `EXPLAIN FORMAT=JSON ...` | +| MongoDB | `db.collection.find(...).explain("executionStats")` | + +With `analyze: false`, the planner returns its estimated plan without touching the table. With `analyze: true`, the query is executed and you get actual timings, buffer usage, and row counts. The response includes both a parsed structured plan and the raw planner output. + +# Insights {% #insights %} + +In addition to metrics and slow queries, the platform runs a periodic index-analysis pass against each database (every ~24 hours, jittered) and submits the results as **insights**. Insights surface: + +- **Missing indexes** — tables with sequential scans on large row counts +- **Unused indexes** — indexes that have not been read since the last reset +- **Tuning recommendations** — engine config parameters that look out of range for the current specification + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + "https://.cloud.appwrite.io/v1/compute/databases//insights?period=24h" +``` + +`period` accepts `1h`, `24h`, or `7d`. + +Insights are recommendations, not auto-applied changes. The Console suggests the corresponding `CREATE INDEX` or `DROP INDEX` statement; you apply it (or not) through your normal migration process. + +# Audit logs {% #audit %} + +Database-level admin actions — credential rotation, IP allowlist changes, backup triggers, failover requests — are recorded in the standard project activity log: + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + "https://.cloud.appwrite.io/v1/compute/databases//logs" +``` + +The response includes the actor, the action, the source IP, and the previous/next state for fields that changed. + +# Webhooks & realtime {% #events %} + +Every database lifecycle transition emits an event that fans out through Appwrite's standard event system. Subscribe with webhooks, realtime channels, or function triggers to react in your own systems: + +| Event | When | +|------------------------------------------------|-----------------------------------------------| +| `databases..create` | Provisioning completed | +| `databases..update` | Any update applied (resize, HA, backups, etc.)| +| `databases..delete` | Database removed | +| `databases..backups..create` | A backup completed (scheduled or manual) | +| `databases..restore` | A restore (full or PITR) completed | + +Lifecycle events emitted by the edge — `spindown`, `storage.autoscaled`, `failover.completed` — also fan out through this same channel. + +# What's not exposed {% #out-of-band %} + +A few things deliberately stay inside the database environment and are not surfaced through the API: + +- **Engine error logs** beyond what's in slow-query and audit feeds. For deeper engine logs, ship your own logging extension (`pgaudit`, `log_min_duration_statement`, MongoDB profiler). +- **OS-level metrics** beyond CPU/memory/IOPS. For per-process inspection, use the engine's own introspection (`pg_stat_activity`, `SHOW PROCESSLIST`, `db.currentOp()`). +- **Backup file contents** — the platform never exposes raw dump bytes through the API, only metadata. diff --git a/src/routes/docs/products/databases/dedicated/network/+page.markdoc b/src/routes/docs/products/databases/dedicated/network/+page.markdoc new file mode 100644 index 00000000000..c0d058b6f2f --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/network/+page.markdoc @@ -0,0 +1,101 @@ +--- +layout: article +title: Network +description: Per-database hostname, mandatory TLS, optional IP allowlists, and connection limits for dedicated databases. +--- + +Every dedicated database gets a public hostname behind Appwrite's edge proxy. The proxy terminates TLS, applies your IP allowlist, and forwards the connection to the database engine over Appwrite's internal regional network. + +# Hostname {% #hostname %} + +On provision, a database hostname is generated in the form: + +``` +db--..appwrite.network +``` + +Example: `db-proj456-abc123.fra.appwrite.network`. + +The hostname is stable for the lifetime of the database. It resolves to the regional edge proxy via a wildcard `*.appwrite.network` DNS record, so you do not need to manage DNS yourself. + +# TLS {% #tls %} + +TLS is enabled on every dedicated database endpoint and is enforced by the edge proxy. Connection strings always include the engine's TLS flag: + +| Engine | Driver flag | +|------------|-------------------------------| +| PostgreSQL | `sslmode=require` | +| MySQL | `useSSL=true` / `tls=true` | +| MariaDB | `useSSL=true` / `tls=true` | +| MongoDB | `tls=true` | + +The certificate is signed by a well-known public CA — your driver does not need a custom CA bundle. + +To require mutual TLS (mTLS) where the client presents a certificate signed by your CA, contact support. mTLS is configured at the edge proxy and enforced before the connection is forwarded. + +# IP allowlist {% #allowlist %} + +By default, a dedicated database is reachable from any IP. To restrict access, set the `networkIPAllowlist` field with a list of CIDR ranges: + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "networkIPAllowlist": [ + "203.0.113.0/24", + "198.51.100.10/32" + ] + }' \ + https://.cloud.appwrite.io/v1/compute/databases/ +``` + +The allowlist is enforced at the edge proxy. A connection from a non-allowlisted IP is dropped at TCP handshake — no banner, no error message. Up to 100 CIDR entries are accepted per database. + +Send an empty array to remove all entries and restore unrestricted access. + +# Max connections {% #connections %} + +Each specification has a hard ceiling on the number of simultaneous connections (see the [specifications page](/docs/products/databases/dedicated/specifications)). Use `networkMaxConnections` to set a per-database limit at or below that ceiling: + +```bash +curl -X PATCH \ + ... \ + -d '{ "networkMaxConnections": 500 }' \ + ... +``` + +When the limit is reached, new connection attempts are rejected at the proxy with a clear error rather than being queued or held open. + +The pooler (if enabled) is counted as a single backend connection per pooled slot, not per client — turning on the pooler is the right way to push effective client concurrency well above this limit. + +# Connection idle timeout {% #idle-timeout %} + +Idle connections are closed by the edge proxy after `networkIdleTimeoutSeconds` of inactivity. The default is 900 (15 minutes). A higher value keeps connection-heavy clients alive longer; a lower value reclaims unused slots faster. + +```bash +curl -X PATCH \ + ... \ + -d '{ "networkIdleTimeoutSeconds": 600 }' \ + ... +``` + +# Locking down access {% #lockdown %} + +By default, a dedicated database is reachable over the public internet on the engine's standard port. To lock the surface down: + +1. **Restrict the allowlist** to your application server egress IPs only. +2. **Rotate the primary password** through the credentials endpoint after onboarding, so the initial generated password doesn't live in CI logs or chat history forever. +3. **Keep TLS on** — the platform enforces it at the proxy, so non-TLS clients can't connect even if they tried. +4. **Avoid embedding credentials in client-side code.** Connect from a server-side workload — an Appwrite Function, your application backend, or a CI job — and surface results through your own API. + +For workloads that should never be reachable from the public internet at all, contact support to discuss private endpoint options. + +# Limits {% #limits %} + +| Limit | Value | +|------------------------------------|--------------------------------| +| IP allowlist entries | up to 100 CIDR ranges | +| Max simultaneous connections (cap) | 10,000 (specification-bound) | +| Idle timeout range | 60 – 86,400 seconds (24 hours) | diff --git a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc new file mode 100644 index 00000000000..c57d50690b4 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc @@ -0,0 +1,128 @@ +--- +layout: article +title: Connection pooler +description: Optional per-database connection pooler that multiplexes many client connections onto a small backend pool. Includes pool modes and read/write splitting when HA is enabled. +--- + +The connection pooler is an opt-in component that runs alongside your database and multiplexes a large number of client connections onto a small number of backend connections. Use it to absorb traffic spikes, smooth out connection churn from serverless runtimes, and route read-only queries to HA replicas without changing your application code. + +Each dedicated database can run its own pooler. Pooler resources are billed as part of the underlying specification — there's no separate pooler line item. + +# Engine support {% #engines %} + +| Engine | Pooler | Port | Notes | +|------------|---------|-------|-------------------------------------------------------------| +| PostgreSQL | yes | 6432 | PgBouncer-compatible wire protocol, native read/write split | +| MySQL | yes | 6033 | Query-rule based routing of reads vs writes | +| MariaDB | yes | 6033 | Same as MySQL | +| MongoDB | — | — | Not applicable — use the driver's `readPreference` setting | + +The pooler exposes its own client-facing port on the database endpoint. Clients connect to the pooler port instead of the engine port — everything else is the same. + +# Pool modes {% #modes %} + +PostgreSQL and MySQL/MariaDB pools support three modes: + +| Mode | Backend connection lifetime | Best for | +|----------------|-------------------------------------------|---------------------------------------------------------------------| +| `transaction` | Held until `COMMIT` / `ROLLBACK` | Most workloads. The default. Massive client→backend amplification. | +| `session` | Held for the whole client session | Apps that rely on `SET LOCAL`, prepared statements, advisory locks | +| `statement` | Held until the statement completes | Read-only, single-statement workloads. Highest amplification. | + +If your application uses features that are tied to a single backend connection — server-side prepared statements (PostgreSQL), advisory locks, temporary tables, `SET LOCAL`, listen/notify — pick `session`. Otherwise, `transaction` is the right choice. + +# Enable the pooler {% #enable %} + +The pooler is enabled by default when a paid dedicated database is provisioned. To disable it or change its settings, use the pooler endpoint: + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "enabled": true, + "poolMode": "transaction", + "maxClientConn": 200, + "defaultPoolSize": 25 + }' \ + https://.cloud.appwrite.io/v1/compute/databases//pooler +``` + +Or to read the current configuration: + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + https://.cloud.appwrite.io/v1/compute/databases//pooler +``` + +Default settings: + +| Field | Default | Notes | +|----------------------|--------------------------|-----------------------------------------------------------------------| +| `enabled` | `true` on create | Disable to turn off the pooler entirely | +| `poolMode` | `transaction` | Pool mode at the pooler layer | +| `maxClientConn` | 100 | Max simultaneous client connections to the pooler | +| `defaultPoolSize` | 25 | Backend connections per (user, db) pair | +| `readWriteSplitting` | `true` if HA is enabled | Route ambient `SELECT`s to replicas | + +# Connect to the pooler {% #connect %} + +Same hostname, different port: + +```bash +psql -h db--..appwrite.network -p 6432 -U appwrite -d appwrite + +mysql -h db--..appwrite.network -P 6033 -u appwrite -p appwrite +``` + +The pooler accepts the same TLS, authentication, and parameter syntax as the engine. Your application driver doesn't know it's talking to a pooler. + +# Read/write splitting {% #read-write %} + +When the database has HA enabled and `readWriteSplitting` is on, the pooler routes queries based on type: + +**MySQL / MariaDB:** + +- `INSERT`, `UPDATE`, `DELETE`, DDL → primary +- `SELECT ... FOR UPDATE` / `FOR SHARE` → primary +- `SELECT` outside a transaction → replicas +- Everything inside a `BEGIN`…`COMMIT` block → pinned to the primary + +**PostgreSQL:** + +- The pooler's query parser inspects each statement. +- `INSERT`, `UPDATE`, `DELETE`, DDL, function calls → primary. +- `SELECT` outside a transaction → replicas. +- Anything inside a transaction → primary. + +This gives you read scale on aggregations and dashboard queries while keeping write-heavy transactions on the primary, with no SDK changes. + +Disable read/write splitting if your workload requires every read to see the most recent write — in async replication mode there's a small lag window where a read on a replica may not yet see a just-committed write: + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -d '{ "readWriteSplitting": false }' \ + https://.cloud.appwrite.io/v1/compute/databases//pooler +``` + +# Sizing {% #sizing %} + +A pooler is most useful when client connections outnumber backend connections by at least 4–5×. For a database that handles a small, stable number of long-lived application connections, the pooler adds latency without much benefit. + +Rule of thumb: + +- **`defaultPoolSize`** ≈ `min(specification.maxConnections, 4 × CPU cores)`. Going above 4×cores rarely improves throughput on PostgreSQL. +- **`maxClientConn`** = your peak client connection count. Serverless platforms can spike to thousands of clients; pick a generous value. + +# When not to use the pooler {% #anti-patterns %} + +- **Long-lived listen/notify subscribers** (PostgreSQL) — these need a session-bound connection. Use `session` mode or bypass the pooler. +- **MongoDB workloads** — MongoDB doesn't have a pooler equivalent in this catalog; rely on the driver's connection pool and `readPreference: 'secondary'` for replica reads. +- **Single-tenant, single-process apps** — the engine's own connection handling is enough; the pooler is overhead. + +For everything else — Lambda, Cloud Run, serverless GraphQL backends, edge runtimes that open a new connection per request — turn it on. diff --git a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc new file mode 100644 index 00000000000..c2efc2d14a8 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc @@ -0,0 +1,84 @@ +--- +layout: article +title: Specifications +description: Eight dedicated database specifications from 1 vCPU and 1 GB to 8 vCPU and 64 GB, plus a free shared tier. Per-specification CPU, memory, included storage, included bandwidth, and connection cap. +--- + +A dedicated database is provisioned against a **specification** — a fixed bundle of CPU, memory, included storage, included bandwidth, and a maximum connection cap. You pick the specification on create and can change it later by updating the database; the platform performs a blue-green resize with logical replication. + +All paid specifications are dedicated, always-on databases billed by the hour. The free specification is a shared, scale-to-zero database included with the Pro plan. + +# Paid specifications {% #paid %} + +| Specification | Name | vCPU | Memory | Max connections | Included storage | Included bandwidth | Price (USD/mo) | +|------------------|------------------|------|--------|-----------------|------------------|--------------------|----------------| +| `s-1vcpu-1gb` | Starter | 1 | 1 GB | 100 | 10 GB | 50 GB | $15 | +| `s-2vcpu-2gb` | Standard | 2 | 2 GB | 200 | 25 GB | 200 GB | $25 | +| `s-2vcpu-4gb` | Standard Plus | 2 | 4 GB | 500 | 50 GB | 400 GB | $60 | +| `s-4vcpu-8gb` | Professional | 4 | 8 GB | 1,000 | 200 GB | 1 TB | $100 | +| `s-4vcpu-16gb` | Business | 4 | 16 GB | 2,000 | 500 GB | 2 TB | $190 | +| `s-4vcpu-32gb` | Business Plus | 4 | 32 GB | 4,000 | 1 TB | 5 TB | $370 | +| `s-8vcpu-32gb` | Enterprise | 8 | 32 GB | 5,000 | 2 TB | 7.5 TB | $500 | +| `s-8vcpu-64gb` | Enterprise Plus | 8 | 64 GB | 10,000 | 3 TB | 10 TB | $860 | + +vCPU counts are dedicated, not burstable. Connection caps are enforced at the engine layer — going above the cap returns a connection refusal, not a slowdown. + +# Free (shared) specification {% #free %} + +The free specification is shared compute with strict resource caps. Idle databases scale to zero and the underlying disk is preserved between spin-downs, so you only ever pay for the storage you keep, never for the database container itself. + +| Resource | Limit | +|------------------|-------------| +| CPU | 125 mCPU | +| Memory | 128 MB | +| Storage | 1 GB | +| Max connections | 10 | +| Query timeout | 15 seconds | +| Idle timeout | 15 minutes | + +Free databases are included with the Pro plan and do not count toward storage or bandwidth overage. + +# Overage rates {% #overage %} + +When usage exceeds the included allowance for a specification, overage is billed at the following rates: + +| Resource | Rate | +|-----------|-----------------| +| Storage | $0.125 / GB / month | +| Bandwidth | $0.08 / GB / month | + +Storage overage is computed on the snapshot at the end of the billing period. Bandwidth overage sums inbound and outbound traffic across the billing period. + +# Feature add-ons {% #add-ons %} + +The following features are billed as a percentage of the underlying specification price, pro-rated by active days in the billing period: + +| Feature | Rate | Example on a $100/mo specification | +|-----------------------------|---------------------------------|------------------------------------| +| High availability replicas | 50% of specification per replica | $50/mo per replica | +| Cross-region replicas | 75% of specification per replica | $75/mo per replica | +| Cross-region standby | 50% of specification | $50/mo | +| Point-in-time recovery | 20% of specification | $20/mo | +| PostgreSQL extensions | Free | $0 | + +# Resize behaviour {% #resize %} + +Changing the specification on an existing database is a non-destructive, online operation. There are no read or write outages during the resize. + +1. A second database instance is provisioned with the new CPU and memory. +2. The schema and data are streamed from the old instance to the new one through engine-native logical replication — WAL on PostgreSQL, binlog on MySQL/MariaDB, and oplog tailing on MongoDB. +3. Once replication is caught up, the connection endpoint is switched to the new instance. +4. The old instance is decommissioned. + +Storage can only grow, never shrink — storage volumes can be expanded but not contracted. Storage expansion happens online without restarting the database. + +# Picking a specification {% #picking %} + +A few rules of thumb: + +- **OLTP workloads** (lots of small reads and writes) — pick a specification where the working set fits in memory. For PostgreSQL this is `shared_buffers + effective_cache_size` ≈ 75% of RAM. +- **Analytics workloads** (large scans, aggregations) — favour the higher CPU specifications. `s-8vcpu-32gb` and `s-8vcpu-64gb` are tuned for sustained CPU usage. +- **Storage-heavy workloads** — pick by included storage first. Going one specification up is usually cheaper than paying overage on a smaller specification. +- **Burst patterns** — keep autoscaling on for storage and use a specification one tier below your peak; resize up during peak windows and back down after. + +The next page explains how to [connect to a dedicated database](/docs/products/databases/dedicated/connect). diff --git a/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc b/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc new file mode 100644 index 00000000000..b357e90a198 --- /dev/null +++ b/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc @@ -0,0 +1,146 @@ +--- +layout: article +title: SQL API +description: Execute parameterised SQL against a dedicated database over HTTPS without opening a TCP connection. Useful from edge runtimes, serverless functions, and CI scripts. +--- + +The **SQL API** is a managed HTTP endpoint that executes one parameterised SQL statement against a dedicated database and returns the result as JSON. It runs alongside the database itself, so it sees the same latency as a local connection — but you call it from anywhere a TCP database connection isn't practical (edge runtimes, serverless functions on cold starts, CI scripts, internal admin tools). + +The SQL API is opt-in per database and supports all four engines. + +# Enable the SQL API {% #enable %} + +The SQL API is disabled by default. Enable it through the database update endpoint: + +```bash +curl -X PATCH \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "sqlApiEnabled": true, + "sqlApiAllowedStatements": ["SELECT"], + "sqlApiMaxRows": 10000, + "sqlApiMaxBytes": 10485760, + "sqlApiTimeoutSeconds": 30 + }' \ + https://.cloud.appwrite.io/v1/compute/databases/ +``` + +The defaults above are safe to start with: `SELECT`-only, 10k rows, 10 MB payload, 30-second timeout. + +# Configuration {% #config %} + +| Field | Default | Plan ceiling | Purpose | +|---------------------------|-----------------------|-------------------------------------|--------------------------------------------------------| +| `sqlApiEnabled` | `false` | n/a | Master switch | +| `sqlApiAllowedStatements` | `["SELECT"]` | `[SELECT, INSERT, UPDATE, DELETE]` | Statement types accepted | +| `sqlApiMaxRows` | 10,000 | 1,000,000 | Hard row cap; truncates beyond | +| `sqlApiMaxBytes` | 10 MB | 100 MB | Hard payload cap; truncates beyond | +| `sqlApiTimeoutSeconds` | 30 | 300 (5 min) | Server-side cancellation deadline | + +Only `SELECT`, `INSERT`, `UPDATE`, and `DELETE` are ever accepted. Schema-altering statements (`CREATE`, `DROP`, `TRUNCATE`, `ALTER`, `GRANT`, `REVOKE`) are rejected unconditionally — those must go through the dedicated provisioning APIs or a direct database connection. + +# Execute a statement {% #execute %} + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "sql": "SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2", + "bindings": ["2026-05-01T00:00:00Z", 50] + }' \ + https://.cloud.appwrite.io/v1/compute/databases//execution +``` + +Response: + +```json +{ + "rows": [ + { "id": "...", "email": "..." }, + ... + ], + "rowCount": 50, + "truncated": false, + "durationMs": 14, + "columns": [ + { "name": "id", "type": "uuid" }, + { "name": "email", "type": "text" } + ] +} +``` + +If the result exceeds `sqlApiMaxRows` or `sqlApiMaxBytes`, the response sets `truncated: true` and includes whatever fits. If the statement exceeds `sqlApiTimeoutSeconds`, the server cancels it and returns `dedicated_database_sql_api_timeout`. + +# Parameter binding {% #bindings %} + +Bindings are always sent through a separate field — Appwrite never interpolates them into the SQL string. Two styles are accepted: + +**Positional** — list of values, referenced as `$1`, `$2`, … in the SQL. + +```json +{ + "sql": "SELECT * FROM events WHERE user_id = $1 AND created_at > $2", + "bindings": ["usr_abc", "2026-05-01"] +} +``` + +**Named** — a `name => value` map, referenced as `:name` or `@name` depending on engine convention. + +```json +{ + "sql": "SELECT * FROM events WHERE user_id = :user AND created_at > :since", + "bindings": { "user": "usr_abc", "since": "2026-05-01" } +} +``` + +Pick one style per request — mixing positional and named bindings in the same statement is rejected. + +# Per-database whitelist {% #whitelist %} + +The platform always rejects DDL and privilege statements, but you can narrow the accepted DML further per database. For example, a read-only dashboard would set: + +```json +{ "sqlApiAllowedStatements": ["SELECT"] } +``` + +…and a writer-only ingestion endpoint: + +```json +{ "sqlApiAllowedStatements": ["INSERT"] } +``` + +The whitelist is enforced before the request leaves Appwrite, so a disallowed statement never touches the database. + +# Errors {% #errors %} + +| Code | When | +|-----------------------------------------------------|-------------------------------------------------------| +| `dedicated_database_sql_api_disabled` | API not enabled on the database | +| `dedicated_database_sql_api_statement_not_allowed` | Statement type not in the per-DB whitelist | +| `dedicated_database_sql_api_parse_failed` | SQL did not parse | +| `dedicated_database_sql_api_multiple_statements` | Submission contained more than one statement | +| `dedicated_database_sql_api_result_too_large` | Rows or bytes exceed the limit (returned as 200 with `truncated: true` if soft-truncating, 400 if rejecting outright) | +| `dedicated_database_sql_api_timeout` | Statement exceeded `sqlApiTimeoutSeconds` | +| `dedicated_database_sql_api_binding_invalid` | Binding count, name, or type mismatch | +| `dedicated_database_sql_api_sidecar_unavailable` | Per-database SQL API runtime is not ready | + +# When to use the SQL API {% #use-cases %} + +The SQL API trades raw throughput for portability. Use it when: + +- **Calling from an edge runtime** that can't open a long-lived TCP connection — Cloudflare Workers, Vercel Edge Functions, Deno Deploy. +- **Building an admin tool** that runs ad-hoc queries from a UI without shipping engine drivers to the browser. +- **Writing a one-off CI script** — easier to `curl` than to install `psql` and manage credentials in CI. + +Don't use it when: + +- **Throughput matters** — TCP + a connection pool is faster end-to-end. The SQL API adds an HTTP hop. +- **The query is transactional** — each SQL API call is its own statement. There's no `BEGIN`…`COMMIT` window across calls. + +# Audit {% #audit %} + +Every SQL API call produces an audit event of type `dedicatedDatabase.createExecution`, persisted with the user ID, project ID, database ID, statement keyword, duration, and result size. The full SQL text is **not** stored by default, but the statement type and binding count are. Enable verbose audit logging on the database settings if you need the full text in your audit log. From e0ab0ba1dc2fb78d77a77b48febf2c70a860dfd7 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 15:28:56 +1200 Subject: [PATCH 02/15] fix(docs): use server-* code fence languages in dedicated databases connect page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `nodejs` is not a registered highlight.js language alias — only `server-nodejs` (which maps to `js`) works. The SSR pass blew up with "Unknown language: nodejs" on every request, so the page 500'd and the sidebar link looked like it did nothing. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../products/databases/dedicated/connect/+page.markdoc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc index 0f3a2c37ad2..fdde7278172 100644 --- a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc @@ -112,7 +112,7 @@ The cold-start adds a few seconds to the first query after an idle period. Subse There's nothing Appwrite-specific about the driver setup. A few example snippets: {% multicode %} -```nodejs +```server-nodejs import { Client } from 'pg'; const client = new Client({ @@ -128,7 +128,7 @@ await client.connect(); const { rows } = await client.query('SELECT now()'); console.log(rows); ``` -```python +```server-python import psycopg import os @@ -144,7 +144,7 @@ with psycopg.connect( cur.execute('SELECT now()') print(cur.fetchone()) ``` -```php +```server-php query('SELECT now()')->fetchAll(); print_r($rows); ``` -```go +```server-go package main import ( From 3eed57da3bd7ae6e7b0901ea4229b2a5334d5e55 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 15:41:33 +1200 Subject: [PATCH 03/15] docs(dedicated): use vCPU instead of millicores in user-facing copy MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Switch '125m CPU' / '125 mCPU' / '125m – 16000m' to '0.125 vCPU' / '0.125 – 16 vCPU'. mCPU is a Kubernetes/internal unit; users think in fractional vCPUs. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/routes/docs/products/databases/dedicated/+page.markdoc | 4 ++-- .../products/databases/dedicated/specifications/+page.markdoc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/docs/products/databases/dedicated/+page.markdoc b/src/routes/docs/products/databases/dedicated/+page.markdoc index 30355bd5851..c0d75003c1e 100644 --- a/src/routes/docs/products/databases/dedicated/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/+page.markdoc @@ -31,7 +31,7 @@ A database is either **shared** or **dedicated**: | Type | Lifecycle | Resources | Use case | |---------------|------------------------------------------|---------------------------------|---------------------------| -| **Shared** | Scales to zero after 15 minutes idle | 125m CPU, 128 MB RAM, 1 GB disk | Development, low traffic | +| **Shared** | Scales to zero after 15 minutes idle | 0.125 CPU, 128 MB RAM, 1 GB disk | Development, low traffic | | **Dedicated** | Always-on, with optional HA replicas | Any specification | Production workloads | Shared databases are included with the Pro plan and use the **Free** specification. On the next connection after a spin-down, the database cold-starts in a few seconds — the underlying storage is preserved between spin-downs, so no data is lost. @@ -95,7 +95,7 @@ The following per-database limits are enforced by the API: | Limit | Value | |--------------------------------|-----------------------------------------------------------| | Databases per project | 10 | -| CPU range | 125m – 16000m | +| CPU range | 0.125 – 16 vCPU | | Memory range | 128 MB – 64 GB | | Storage range | 1 GB – 16 TB | | High availability replicas | 0 – 5 | diff --git a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc index c2efc2d14a8..026ed88d714 100644 --- a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc @@ -29,7 +29,7 @@ The free specification is shared compute with strict resource caps. Idle databas | Resource | Limit | |------------------|-------------| -| CPU | 125 mCPU | +| CPU | 0.125 vCPU | | Memory | 128 MB | | Storage | 1 GB | | Max connections | 10 | From 95fd9720955b876e82cbb253b246c4c70d9cb2d0 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 15:50:57 +1200 Subject: [PATCH 04/15] docs(dedicated): raise HA replica add-on rate to 75% MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 50% per replica is not sustainable at the higher specifications — at $860/mo the underlying infrastructure cost per replica is close to the full spec price. Raising to 75% matches the cross-region replica rate and preserves margin on Business+ tiers. Applied to cloud config and tests in a separate change. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../blog/post/announcing-dedicated-databases/+page.markdoc | 2 +- .../databases/dedicated/high-availability/+page.markdoc | 2 +- .../products/databases/dedicated/specifications/+page.markdoc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc b/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc index a2e4a3b6d0d..ceb57f985a6 100644 --- a/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc +++ b/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc @@ -57,7 +57,7 @@ Enable HA with one update call and the platform provisions the replicas, places When the primary stays unhealthy across two 3-second probes, Appwrite takes a 15-second distributed lock, selects the lowest-lag replica, promotes it in place (no restart for SQL engines), runs a write-verification probe, drains connections from the old primary, and emits a `failover.completed` event to your webhooks, realtime channels, and functions. There's a 60-second cooldown so transient infrastructure events don't cause failover flapping. -Up to five replicas per database. Replicas are billed at 50% of the underlying specification per replica. +Up to five replicas per database. Replicas are billed at 75% of the underlying specification per replica. ## Backups and point-in-time recovery diff --git a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc index b87517ec96f..abe28be9555 100644 --- a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc @@ -116,4 +116,4 @@ Cross-region replicas and standbys are billed as feature add-ons — see the [sp | Health-check timeout per attempt | 3 s | | Consecutive failed health checks before failover | 2 | -Replicas are billed at 50% of the underlying specification price per replica. Cross-region replicas are billed at 75% per replica. +Replicas are billed at 75% of the underlying specification price per replica. Cross-region replicas are billed at 75% per replica. diff --git a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc index 026ed88d714..2754cf753e2 100644 --- a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc @@ -55,7 +55,7 @@ The following features are billed as a percentage of the underlying specificatio | Feature | Rate | Example on a $100/mo specification | |-----------------------------|---------------------------------|------------------------------------| -| High availability replicas | 50% of specification per replica | $50/mo per replica | +| High availability replicas | 75% of specification per replica | $75/mo per replica | | Cross-region replicas | 75% of specification per replica | $75/mo per replica | | Cross-region standby | 50% of specification | $50/mo | | Point-in-time recovery | 20% of specification | $20/mo | From ebe545b92962fc1833d108f0b7ec0c742455e3eb Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 15:54:27 +1200 Subject: [PATCH 05/15] docs(dedicated): bill cross-region standby at the same 75% as replicas All replica-style add-ons (HA replicas, cross-region replicas, and the cross-region standby) now bill at 75% of the underlying specification. The standby is just a cross-region replica configured for automatic failover, so they price identically. Removes the separate "Cross-region standby" row from the add-on table and adds a one-liner explaining that the standby is a special-case cross-region replica. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dedicated/specifications/+page.markdoc | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc index 2754cf753e2..33d18ef2c7d 100644 --- a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc @@ -53,13 +53,14 @@ Storage overage is computed on the snapshot at the end of the billing period. Ba The following features are billed as a percentage of the underlying specification price, pro-rated by active days in the billing period: -| Feature | Rate | Example on a $100/mo specification | -|-----------------------------|---------------------------------|------------------------------------| -| High availability replicas | 75% of specification per replica | $75/mo per replica | -| Cross-region replicas | 75% of specification per replica | $75/mo per replica | -| Cross-region standby | 50% of specification | $50/mo | -| Point-in-time recovery | 20% of specification | $20/mo | -| PostgreSQL extensions | Free | $0 | +| Feature | Rate | Example on a $100/mo specification | +|-----------------------------|---------------------------------------|------------------------------------| +| High availability replicas | 75% of specification per replica | $75/mo per replica | +| Cross-region replicas | 75% of specification per replica | $75/mo per replica | +| Point-in-time recovery | 20% of specification | $20/mo | +| PostgreSQL extensions | Free | $0 | + +The cross-region **standby** that backs the [cross-region failover](/docs/products/databases/dedicated/high-availability) feature is a cross-region replica configured for automatic failover, so it bills the same way as any other cross-region replica. # Resize behaviour {% #resize %} From f54b0b634b56fa4049d26e0c817a305a05319d23 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 15:56:46 +1200 Subject: [PATCH 06/15] docs(dedicated): credential rotation is PATCH /credentials, not POST The endpoint mutates an existing resource (the primary user's password), which is what PATCH is for. POST was the wrong verb. Updated the docs example to use PATCH. The same change ships in cloud (endpoint, e2e tests, internal docs). Co-Authored-By: Claude Opus 4.7 (1M context) --- .../docs/products/databases/dedicated/connect/+page.markdoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc index fdde7278172..6cd786f9c5e 100644 --- a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc @@ -58,10 +58,10 @@ mongosh "mongodb://appwrite:@db--..appwrite.n # Rotate the primary password {% #rotate %} -The credentials endpoint also accepts `POST` to issue a new password for the primary user. The change is applied atomically in the underlying engine and the stored credential is updated in the same call. Existing sessions remain alive until the engine's idle timeout, then have to authenticate with the new password. +The credentials endpoint also accepts `PATCH` to issue a new password for the primary user. The change is applied atomically in the underlying engine and the stored credential is updated in the same call. Existing sessions remain alive until the engine's idle timeout, then have to authenticate with the new password. ```bash -curl -X POST \ +curl -X PATCH \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//credentials From f8e65c3e43772068ddf6099ab5e1303ab18c894b Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 15:59:35 +1200 Subject: [PATCH 07/15] docs(dedicated): show server-SDK examples for credentials get and rotate Replace the bare CURL examples in the Connect page with multicode blocks covering all server SDKs (Node.js, Deno, PHP, Python, Ruby, .NET, Dart, Kotlin, Swift, Go, Rust) plus CURL as the last entry, matching the pattern used elsewhere in the docs and blog posts. SDK calls go through the Compute service since the endpoint lives at /v1/compute/... Co-Authored-By: Claude Opus 4.7 (1M context) --- .../databases/dedicated/connect/+page.markdoc | 346 +++++++++++++++++- 1 file changed, 344 insertions(+), 2 deletions(-) diff --git a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc index 6cd786f9c5e..8e4a9d8db1b 100644 --- a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc @@ -8,14 +8,185 @@ A dedicated database exposes a real engine endpoint over TLS. You connect to it # Get the connection details {% #credentials %} -The connection details for a dedicated database are exposed through the [credentials endpoint](/docs/references/cloud/server-rest/compute#getDatabaseCredentials): +The connection details for a dedicated database are exposed through the [credentials endpoint](/docs/references/cloud/server-rest/compute#getDatabaseCredentials). Call it from any server SDK with an API key that has the `databases.read` scope. +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const credentials = await compute.getDatabaseCredentials({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const credentials = await compute.getDatabaseCredentials({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$credentials = $compute->getDatabaseCredentials(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +credentials = compute.get_database_credentials(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +credentials = compute.get_database_credentials(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var credentials = await compute.GetDatabaseCredentials(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final credentials = await compute.getDatabaseCredentials( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val credentials = compute.getDatabaseCredentials( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let credentials = try await compute.getDatabaseCredentials( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + credentials, err := compute.GetDatabaseCredentials("") + if err != nil { + panic(err) + } + _ = credentials +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let credentials = compute.get_database_credentials("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//credentials ``` +{% /multicode %} Response: @@ -58,14 +229,185 @@ mongosh "mongodb://appwrite:@db--..appwrite.n # Rotate the primary password {% #rotate %} -The credentials endpoint also accepts `PATCH` to issue a new password for the primary user. The change is applied atomically in the underlying engine and the stored credential is updated in the same call. Existing sessions remain alive until the engine's idle timeout, then have to authenticate with the new password. +The credentials endpoint also accepts `PATCH` to issue a new password for the primary user. The change is applied atomically in the underlying engine and the stored credential is updated in the same call. Existing sessions remain alive until the engine's idle timeout, then have to authenticate with the new password. The API key needs the `databases.write` scope. +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const credentials = await compute.updateDatabaseCredentials({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const credentials = await compute.updateDatabaseCredentials({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$credentials = $compute->updateDatabaseCredentials(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +credentials = compute.update_database_credentials(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +credentials = compute.update_database_credentials(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var credentials = await compute.UpdateDatabaseCredentials(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final credentials = await compute.updateDatabaseCredentials( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val credentials = compute.updateDatabaseCredentials( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let credentials = try await compute.updateDatabaseCredentials( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + credentials, err := compute.UpdateDatabaseCredentials("") + if err != nil { + panic(err) + } + _ = credentials +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let credentials = compute.update_database_credentials("").await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//credentials ``` +{% /multicode %} # Additional connection users {% #connections %} From cf27161d4c56b6550b3b00af091784eb47118b16 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 16:04:50 +1200 Subject: [PATCH 08/15] docs(dedicated): drop em-dashes, simplify network phrasing and HA failover steps - Replace every em-dash with a comma in the new docs and blog post (12 files). - 'Appwrite's internal regional network' becomes 'the Appwrite network'. - Reduce the automatic-failover numbered list to a two-sentence summary. The engine-specific promote commands and per-step accounting belonged in the internal docs, not the user-facing page. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../+page.markdoc | 42 +++++++++---------- .../changelog/(entries)/2026-05-21.markdoc | 2 +- .../databases/dedicated/+page.markdoc | 12 +++--- .../databases/dedicated/backups/+page.markdoc | 14 +++---- .../dedicated/branches/+page.markdoc | 18 ++++---- .../databases/dedicated/connect/+page.markdoc | 4 +- .../dedicated/extensions/+page.markdoc | 8 ++-- .../dedicated/high-availability/+page.markdoc | 27 +++++------- .../dedicated/monitoring/+page.markdoc | 14 +++---- .../databases/dedicated/network/+page.markdoc | 12 +++--- .../databases/dedicated/pooler/+page.markdoc | 18 ++++---- .../dedicated/specifications/+page.markdoc | 16 +++---- .../databases/dedicated/sql-api/+page.markdoc | 20 ++++----- 13 files changed, 100 insertions(+), 107 deletions(-) diff --git a/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc b/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc index ceb57f985a6..a61d25b4fcf 100644 --- a/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc +++ b/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc @@ -1,7 +1,7 @@ --- layout: post title: "Announcing Dedicated databases: managed PostgreSQL, MySQL, MariaDB, and MongoDB on Appwrite" -description: Run production database engines on Appwrite Cloud. PostgreSQL, MySQL, MariaDB, and MongoDB with high availability, point-in-time recovery, branching, extensions, and a connection pooler — billed by the hour from $15/mo. +description: Run production database engines on Appwrite Cloud. PostgreSQL, MySQL, MariaDB, and MongoDB with high availability, point-in-time recovery, branching, extensions, and a connection pooler, billed by the hour from $15/mo. date: 2026-05-21 cover: /images/blog/announcing-dedicated-databases/cover.avif timeToRead: 8 @@ -11,26 +11,26 @@ featured: true callToAction: true faqs: - question: "How is this different from Appwrite Databases?" - answer: "Appwrite Databases is the document store — a curated TablesDB API with rows, columns, queries, and permissions, accessible through the Appwrite SDKs. Dedicated databases are real PostgreSQL, MySQL, MariaDB, or MongoDB engines that you connect to with their native drivers. The two products are independent — many teams will use both. Use the document store for app data that benefits from Appwrite's permission model; use a dedicated database when you need raw SQL, extensions like PostGIS or pgvector, or compatibility with an existing tool." + answer: "Appwrite Databases is the document store, a curated TablesDB API with rows, columns, queries, and permissions, accessible through the Appwrite SDKs. Dedicated databases are real PostgreSQL, MySQL, MariaDB, or MongoDB engines that you connect to with their native drivers. The two products are independent, many teams will use both. Use the document store for app data that benefits from Appwrite's permission model; use a dedicated database when you need raw SQL, extensions like PostGIS or pgvector, or compatibility with an existing tool." - question: "Which plans include dedicated databases?" answer: "The Pro plan and higher. A payment method must be attached to the team. The Free specification (shared, scale-to-zero) is included with Pro at no extra cost; paid specifications are billed by the hour against the per-specification monthly price." - question: "What engines and versions are supported?" - answer: "PostgreSQL 17 and 18 (default 18), MySQL 8.0 and 8.4 (default 8.4), MariaDB 10.11 and 11.4 (default 11.4), and MongoDB 7.0 and 8.0 (default 8.0). Version upgrades and resize operations are online — data streams to a new instance via engine-native logical replication and traffic cuts over once replication is caught up, with no read or write outages." + answer: "PostgreSQL 17 and 18 (default 18), MySQL 8.0 and 8.4 (default 8.4), MariaDB 10.11 and 11.4 (default 11.4), and MongoDB 7.0 and 8.0 (default 8.0). Version upgrades and resize operations are online, data streams to a new instance via engine-native logical replication and traffic cuts over once replication is caught up, with no read or write outages." - question: "Can I bring my own backup storage bucket?" - answer: "Yes. The backup storage endpoint accepts S3-compatible credentials — AWS S3, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, Azure Blob via S3 interop. Backups go directly to your bucket and never enter Appwrite-managed storage. Credentials are encrypted at rest." + answer: "Yes. The backup storage endpoint accepts S3-compatible credentials, AWS S3, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, Azure Blob via S3 interop. Backups go directly to your bucket and never enter Appwrite-managed storage. Credentials are encrypted at rest." - question: "How does failover work?" answer: "Appwrite continuously watches the primary. After two consecutive failed health checks (3-second timeout each), the platform promotes the replica with the lowest replication lag and emits a `failover.completed` event. A 60-second cooldown prevents flapping. You can also trigger a manual failover through the API, optionally targeting a specific replica." - question: "Is read/write splitting automatic?" answer: "When HA is enabled and the connection pooler is on, read/write splitting is on by default. The pooler inspects each query and routes writes plus transactions to the primary, ambient SELECTs to replicas. Turn it off if your workload requires every read to see the just-committed write." - question: "Which region does my dedicated database live in?" - answer: "A dedicated database lives in the same region as the project that owns it — there's no per-database region selector. That means it sits next to your Functions, Sites, and Storage on the regional private network with no extra configuration." + answer: "A dedicated database lives in the same region as the project that owns it, there's no per-database region selector. That means it sits next to your Functions, Sites, and Storage on the regional private network with no extra configuration." - question: "How is billing pro-rated?" - answer: "Specification cost is pro-rated by the number of active days in the billing period — a database created mid-month is billed only from its creation timestamp to the end of the period, and a deleted database is billed only up to the deletion timestamp. Add-ons (HA, PITR, cross-region) follow the same pro-ration. Storage and bandwidth overage are computed against the included allowance for the specification." + answer: "Specification cost is pro-rated by the number of active days in the billing period, a database created mid-month is billed only from its creation timestamp to the end of the period, and a deleted database is billed only up to the deletion timestamp. Add-ons (HA, PITR, cross-region) follow the same pro-ration. Storage and bandwidth overage are computed against the included allowance for the specification." --- Today we're announcing **Dedicated databases** on Appwrite Cloud. You can now run managed PostgreSQL, MySQL, MariaDB, and MongoDB engines next to your existing Appwrite Functions, Sites, and Storage, with the same per-region deployment, the same single payment relationship, and the same Console. -Dedicated databases are a separate product from [Appwrite Databases](/docs/products/databases) — our document store with rows, columns, queries, and built-in permissions. The two products solve different problems and many teams will use both. Use the document store when you want app data managed through the Appwrite SDKs and the platform's permission model. Use a dedicated database when you need raw SQL, the PostgreSQL extension ecosystem, your existing ORM, or wire-compatibility with another tool. +Dedicated databases are a separate product from [Appwrite Databases](/docs/products/databases), our document store with rows, columns, queries, and built-in permissions. The two products solve different problems and many teams will use both. Use the document store when you want app data managed through the Appwrite SDKs and the platform's permission model. Use a dedicated database when you need raw SQL, the PostgreSQL extension ecosystem, your existing ORM, or wire-compatibility with another tool. # What's in the box @@ -38,18 +38,18 @@ Each dedicated database is a real database engine running on its own dedicated c **Four engines:** -- **PostgreSQL 17 / 18**, with the standard contrib extensions plus a curated catalog of community extensions — PostGIS, pgvector, TimescaleDB, pg_stat_statements, pg_trgm, and more. +- **PostgreSQL 17 / 18**, with the standard contrib extensions plus a curated catalog of community extensions, PostGIS, pgvector, TimescaleDB, pg_stat_statements, pg_trgm, and more. - **MySQL 8.0 / 8.4**, with semi-synchronous replication and connection pooling. - **MariaDB 10.11 / 11.4**, with the same replication and pooling story as MySQL. - **MongoDB 7.0 / 8.0**, with replica-set high availability via Raft. **Eight paid specifications** from `s-1vcpu-1gb` at $15/month to `s-8vcpu-64gb` at $860/month, plus a free shared specification included with the Pro plan. Specifications are dedicated CPU, not burstable; they're 10–20% below the equivalent tier on Supabase, RDS, and PlanetScale at the same memory level. Every paid specification gets dedicated compute that never spins down, with included storage (10 GB to 3 TB) and included bandwidth (50 GB to 10 TB) baked into the monthly price. -**Six regions** available today — Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto — with Amsterdam, London, and Bangalore in the rollout queue. A dedicated database lives in the same region as the project that owns it, so it sits next to your Functions, Sites, and Storage on the regional private network with no extra configuration. +**Six regions** available today, Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto, with Amsterdam, London, and Bangalore in the rollout queue. A dedicated database lives in the same region as the project that owns it, so it sits next to your Functions, Sites, and Storage on the regional private network with no extra configuration. # Production from day one -The features that you'd normally bolt on after the fact — HA, PITR, branching, pooling — are all part of the product. +The features that you'd normally bolt on after the fact, HA, PITR, branching, pooling, are all part of the product. ## High availability @@ -61,15 +61,15 @@ Up to five replicas per database. Replicas are billed at 75% of the underlying s ## Backups and point-in-time recovery -Every database supports scheduled backups with cron syntax, retention from one to 365 days, and on-demand verification — Appwrite re-downloads the backup, decrypts it in an isolated environment, and runs a sanity check (`pg_restore --list`, `mongorestore --dryRun`, …) to confirm it would actually restore. A backup that's never been verified is a hope, not a backup. +Every database supports scheduled backups with cron syntax, retention from one to 365 days, and on-demand verification, Appwrite re-downloads the backup, decrypts it in an isolated environment, and runs a sanity check (`pg_restore --list`, `mongorestore --dryRun`, …) to confirm it would actually restore. A backup that's never been verified is a hope, not a backup. -Backups are AES-256 encrypted before they leave the database. The encryption key is unique per database and stored in Appwrite's encrypted secret store scoped to the database, so the cloud storage provider only ever sees ciphertext. You can use Appwrite-managed storage or bring your own S3-compatible bucket — AWS, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, Azure Blob via S3 interop. +Backups are AES-256 encrypted before they leave the database. The encryption key is unique per database and stored in Appwrite's encrypted secret store scoped to the database, so the cloud storage provider only ever sees ciphertext. You can use Appwrite-managed storage or bring your own S3-compatible bucket, AWS, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, Azure Blob via S3 interop. For workloads that can't afford to lose the last hour of writes between scheduled backups, **point-in-time recovery** layers continuous WAL/binlog/oplog archiving on top. Enable PITR (20% of the specification price) and you can restore to any second within the PITR window (up to 35 days). The restore produces a new database; the source is untouched, so you can validate the recovery before swapping traffic to it. ## Branching -A **branch** is a near-instant ephemeral copy of a dedicated database, created from a storage snapshot of the parent. The parent is briefly quiesced (`CHECKPOINT` on PostgreSQL, `FLUSH TABLES WITH READ LOCK` on MySQL, `db.fsyncLock()` on MongoDB — under a second in practice), the snapshot is taken, the parent resumes, and the branch comes up with its own credentials and its own hostname. +A **branch** is a near-instant ephemeral copy of a dedicated database, created from a storage snapshot of the parent. The parent is briefly quiesced (`CHECKPOINT` on PostgreSQL, `FLUSH TABLES WITH READ LOCK` on MySQL, `db.fsyncLock()` on MongoDB, under a second in practice), the snapshot is taken, the parent resumes, and the branch comes up with its own credentials and its own hostname. Branches are the right answer to "I want to test this migration without a maintenance window" or "I want a preview database per pull request" or "I want to throw a heavy `EXPLAIN ANALYZE` at production data without touching production". The parent and the branch share storage cost-free at the moment of branching (copy-on-write); cost accumulates only as the two databases diverge. @@ -77,21 +77,21 @@ Branches are billed like a standalone dedicated database against their own speci ## Connection pooler -Each dedicated database can run its own pooler — PostgreSQL on port 6432, MySQL/MariaDB on port 6033. The pooler is enabled by default on provision and is configurable per database — pool mode (`transaction` / `session` / `statement`), max client connections, default backend pool size. +Each dedicated database can run its own pooler, PostgreSQL on port 6432, MySQL/MariaDB on port 6033. The pooler is enabled by default on provision and is configurable per database, pool mode (`transaction` / `session` / `statement`), max client connections, default backend pool size. When HA is enabled, the pooler also handles **read/write splitting**: a query parser inspects each statement, pins transactions and writes to the primary, and routes ambient `SELECT`s to replicas. The application driver sees a single hostname and port; everything else is transparent. No SDK changes. -MongoDB doesn't have a pooler equivalent in this catalog — its driver does connection management client-side, and `readPreference: 'secondary'` does the read routing. +MongoDB doesn't have a pooler equivalent in this catalog, its driver does connection management client-side, and `readPreference: 'secondary'` does the read routing. ## SQL API -For environments where opening a TCP connection isn't practical — Cloudflare Workers, Vercel Edge Functions, Deno Deploy, ad-hoc admin scripts — every dedicated database can also expose a **managed SQL API** over HTTPS. POST a parameterised statement with bindings, get back a JSON result. Schema-altering statements (`DROP`, `ALTER`, `GRANT`, …) are rejected unconditionally; DML statement types are gated by a per-database whitelist. Hard caps on rows, bytes, and execution time prevent runaway queries from taking down the engine. +For environments where opening a TCP connection isn't practical, Cloudflare Workers, Vercel Edge Functions, Deno Deploy, ad-hoc admin scripts, every dedicated database can also expose a **managed SQL API** over HTTPS. POST a parameterised statement with bindings, get back a JSON result. Schema-altering statements (`DROP`, `ALTER`, `GRANT`, …) are rejected unconditionally; DML statement types are gated by a per-database whitelist. Hard caps on rows, bytes, and execution time prevent runaway queries from taking down the engine. The SQL API is opt-in per database and disabled by default. # How connection works -Each database gets a hostname of the form `db--..appwrite.network`. Resolve it with any DNS resolver, connect with `psql`, `mysql`, `mongosh`, or any driver in any language. TLS is enforced — `sslmode=require`, `useSSL=true`, `tls=true` — and the certificate is signed by a public CA, so no custom bundle is required. +Each database gets a hostname of the form `db--..appwrite.network`. Resolve it with any DNS resolver, connect with `psql`, `mysql`, `mongosh`, or any driver in any language. TLS is enforced, `sslmode=require`, `useSSL=true`, `tls=true`, and the certificate is signed by a public CA, so no custom bundle is required. Behind the hostname sits Appwrite's edge proxy. It parses the engine's startup packet (PostgreSQL `StartupMessage`, MySQL handshake, MongoDB `OP_MSG hello`) to identify the database, applies your IP allowlist, and forwards the connection to the engine. For shared (free) databases, the proxy also detects the cold-start case: if the database has scaled to zero, the proxy triggers a start, waits for readiness, and forwards the handshake. The underlying storage is preserved across spin-downs, so no data is lost. @@ -99,9 +99,9 @@ Behind the hostname sits Appwrite's edge proxy. It parses the engine's startup p Three lines on the invoice cover the bulk of the cost: -- **Specification cost** — pro-rated by active days in the billing period. A database created mid-month bills only from its creation timestamp. -- **Storage overage** — $0.125 per GB per month above the included allowance for the specification. -- **Bandwidth overage** — $0.08 per GB per month above the included allowance. +- **Specification cost**, pro-rated by active days in the billing period. A database created mid-month bills only from its creation timestamp. +- **Storage overage**, $0.125 per GB per month above the included allowance for the specification. +- **Bandwidth overage**, $0.08 per GB per month above the included allowance. Feature add-ons (HA replicas, cross-region replicas, cross-region standby, PITR) are billed as a percentage of the underlying specification price, pro-rated the same way. PostgreSQL extensions are free. @@ -112,7 +112,7 @@ Dedicated databases are available now on Appwrite Cloud for all Pro plan teams. 1. Open your project in the Appwrite Console. 2. Open the **Databases** section in the sidebar. 3. Click **Create database** and choose the **Dedicated** type. -4. Pick an engine, version, and specification — the database is provisioned in your project's region. +4. Pick an engine, version, and specification, the database is provisioned in your project's region. 5. Connect with `psql`, `mysql`, or `mongosh`. For the programmatic flow, the [Connect documentation](/docs/products/databases/dedicated/connect) shows credential retrieval and connection examples in Node, Python, PHP, and Go. The [Specifications page](/docs/products/databases/dedicated/specifications) has the full price list and resource limits. diff --git a/src/routes/changelog/(entries)/2026-05-21.markdoc b/src/routes/changelog/(entries)/2026-05-21.markdoc index ab1ab4a058b..cfec3f9878b 100644 --- a/src/routes/changelog/(entries)/2026-05-21.markdoc +++ b/src/routes/changelog/(entries)/2026-05-21.markdoc @@ -5,7 +5,7 @@ date: 2026-05-21 cover: /images/blog/announcing-dedicated-databases/cover.avif --- -Appwrite Cloud now supports [**Dedicated databases**](/docs/products/databases/dedicated) — managed PostgreSQL, MySQL, MariaDB, and MongoDB engines running next to your existing Functions, Sites, and Storage. Pick an engine (Postgres 17/18, MySQL 8.0/8.4, MariaDB 10.11/11.4, MongoDB 7.0/8.0), pick a region (Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto), pick a specification from `s-1vcpu-1gb` at $15/mo to `s-8vcpu-64gb` at $860/mo, and connect with `psql`, `mysql`, `mongosh`, or any standard driver. +Appwrite Cloud now supports [**Dedicated databases**](/docs/products/databases/dedicated), managed PostgreSQL, MySQL, MariaDB, and MongoDB engines running next to your existing Functions, Sites, and Storage. Pick an engine (Postgres 17/18, MySQL 8.0/8.4, MariaDB 10.11/11.4, MongoDB 7.0/8.0), pick a region (Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto), pick a specification from `s-1vcpu-1gb` at $15/mo to `s-8vcpu-64gb` at $860/mo, and connect with `psql`, `mysql`, `mongosh`, or any standard driver. High availability, point-in-time recovery, branching, PostgreSQL extensions, connection pooling with read/write split, and a managed SQL API over HTTPS are all available out of the box. Dedicated databases are billed by the hour and pro-rated by active days; the Pro plan includes a free, scale-to-zero shared specification at no extra cost. diff --git a/src/routes/docs/products/databases/dedicated/+page.markdoc b/src/routes/docs/products/databases/dedicated/+page.markdoc index c0d75003c1e..58d05587b1d 100644 --- a/src/routes/docs/products/databases/dedicated/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/+page.markdoc @@ -6,7 +6,7 @@ description: Run managed PostgreSQL, MySQL, MariaDB, and MongoDB databases on Ap Appwrite **Dedicated databases** lets you run managed PostgreSQL, MySQL, MariaDB, and MongoDB instances on Appwrite Cloud. You pick the engine and the compute size; Appwrite provisions a real database in your project's region, with its own storage, networking, and credentials, and exposes it to your application over the public internet or your own private network. -Dedicated databases are different from the [Appwrite Databases](/docs/products/databases) document store. Where the document store gives you a curated `TablesDB` API with rows, queries, and SDK helpers, dedicated databases give you the raw engine — you connect with `psql`, `mysql`, `mongosh`, or any driver of your choice and use the full feature set of the underlying database. +Dedicated databases are different from the [Appwrite Databases](/docs/products/databases) document store. Where the document store gives you a curated `TablesDB` API with rows, queries, and SDK helpers, dedicated databases give you the raw engine, you connect with `psql`, `mysql`, `mongosh`, or any driver of your choice and use the full feature set of the underlying database. {% info title="Looking for the document store?" %} If you want to query data through Appwrite SDKs with rows, columns, and built-in permissions, you want [Appwrite Databases](/docs/products/databases). Dedicated databases are for teams who want a managed PostgreSQL, MySQL, MariaDB, or MongoDB engine they can talk to directly with their own ORM, migrations, and drivers. @@ -23,7 +23,7 @@ Appwrite manages the database container, storage, backups, and networking. You b | MariaDB | 11.4 | 10.11 | 3306 | | MongoDB | 8.0 | 7.0 | 27017 | -Version is selected on create and can be changed later via the upgrade endpoint. Upgrades run online — a second instance is provisioned on the new version, data is streamed over with engine-native logical replication, and traffic cuts over once replication is caught up. There are no read or write outages during the upgrade, and no manual dump-and-restore. +Version is selected on create and can be changed later via the upgrade endpoint. Upgrades run online, a second instance is provisioned on the new version, data is streamed over with engine-native logical replication, and traffic cuts over once replication is caught up. There are no read or write outages during the upgrade, and no manual dump-and-restore. # Database types {% #types %} @@ -34,13 +34,13 @@ A database is either **shared** or **dedicated**: | **Shared** | Scales to zero after 15 minutes idle | 0.125 CPU, 128 MB RAM, 1 GB disk | Development, low traffic | | **Dedicated** | Always-on, with optional HA replicas | Any specification | Production workloads | -Shared databases are included with the Pro plan and use the **Free** specification. On the next connection after a spin-down, the database cold-starts in a few seconds — the underlying storage is preserved between spin-downs, so no data is lost. +Shared databases are included with the Pro plan and use the **Free** specification. On the next connection after a spin-down, the database cold-starts in a few seconds, the underlying storage is preserved between spin-downs, so no data is lost. Dedicated databases are billed per hour against one of eight paid specifications and never spin down. They also unlock features that the shared specification does not have access to: high availability, point-in-time recovery, custom backup policies, storage autoscaling, IP allowlists, and connection pooling. # Regions {% #regions %} -A dedicated database lives in the same region as the project that owns it — there's no per-database region selector. Dedicated databases are available in every Appwrite region: +A dedicated database lives in the same region as the project that owns it, there's no per-database region selector. Dedicated databases are available in every Appwrite region: | Region | Code | Endpoint | |---------------|------|----------------------------------------------| @@ -51,7 +51,7 @@ A dedicated database lives in the same region as the project that owns it — th | Sydney | SYD | `db--.syd.appwrite.network` | | Toronto | TOR | `db--.tor.appwrite.network` | -Each region is independent — data does not leave the region unless you explicitly opt into the cross-region failover feature. +Each region is independent, data does not leave the region unless you explicitly opt into the cross-region failover feature. # Feature overview {% #features %} @@ -84,7 +84,7 @@ Run parameterised SQL over HTTP without opening a TCP connection. Useful from ed TLS by default, optional IP allowlists, and a private hostname per database. {% /cards_item %} {% cards_item href="/docs/products/databases/dedicated/monitoring" title="Monitoring & insights" %} -CPU, memory, IOPS, slow queries, and index recommendations — pushed to the Console live. +CPU, memory, IOPS, slow queries, and index recommendations, pushed to the Console live. {% /cards_item %} {% /cards %} diff --git a/src/routes/docs/products/databases/dedicated/backups/+page.markdoc b/src/routes/docs/products/databases/dedicated/backups/+page.markdoc index e5493ac35b3..e2152d51128 100644 --- a/src/routes/docs/products/databases/dedicated/backups/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/backups/+page.markdoc @@ -4,11 +4,11 @@ title: Backups & PITR description: Encrypted scheduled backups with up to 365 days of retention, on-demand manual backups, and point-in-time recovery to any second within the PITR window. --- -Every dedicated database supports automated backups out of the box. Backups are AES-256 encrypted at rest, uploaded to an object store, and verifiable on demand. Point-in-time recovery (PITR) layers continuous WAL/binlog/oplog archiving on top, so you can restore to any second within the retention window — not just to a backup checkpoint. +Every dedicated database supports automated backups out of the box. Backups are AES-256 encrypted at rest, uploaded to an object store, and verifiable on demand. Point-in-time recovery (PITR) layers continuous WAL/binlog/oplog archiving on top, so you can restore to any second within the retention window, not just to a backup checkpoint. # What gets backed up {% #scope %} -A backup captures the full contents of the database: all schemas, tables/collections, indexes, extensions, and (optionally) user-defined roles. The backup is taken with the engine's native tool — `pg_dump` / `mysqldump` / `mongodump` — in a dedicated job that runs alongside (not on top of) the database, so production traffic is unaffected. +A backup captures the full contents of the database: all schemas, tables/collections, indexes, extensions, and (optionally) user-defined roles. The backup is taken with the engine's native tool, `pg_dump` / `mysqldump` / `mongodump`, in a dedicated job that runs alongside (not on top of) the database, so production traffic is unaffected. # Schedule {% #schedule %} @@ -31,7 +31,7 @@ Backup retention accepts 1–365 days. Backups older than the retention window a # Manual backups {% #manual %} -To create an ad-hoc backup — before a destructive migration, for example — call the create endpoint. The response contains the backup ID; the actual dump runs asynchronously. +To create an ad-hoc backup, before a destructive migration, for example, call the create endpoint. The response contains the backup ID; the actual dump runs asynchronously. ```bash curl -X POST \ @@ -50,7 +50,7 @@ Restore reverses the pipeline: the key is fetched from the secret store, the cip # Storage targets {% #storage %} -By default, backups are stored on Appwrite-managed S3-compatible storage. You can also bring your own bucket — S3, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, or Azure Blob via S3 interop. Bring-your-own-bucket backups never enter Appwrite-controlled storage. +By default, backups are stored on Appwrite-managed S3-compatible storage. You can also bring your own bucket, S3, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, or Azure Blob via S3 interop. Bring-your-own-bucket backups never enter Appwrite-controlled storage. ```bash curl -X PUT \ @@ -68,7 +68,7 @@ curl -X PUT \ https://.cloud.appwrite.io/v1/compute/databases//backups/storage ``` -Credentials are encrypted at rest. The `endpoint` field is supported for S3-compatible providers — leave empty for AWS S3. +Credentials are encrypted at rest. The `endpoint` field is supported for S3-compatible providers, leave empty for AWS S3. # Verify a backup {% #verify %} @@ -157,7 +157,7 @@ curl -X POST \ https://.cloud.appwrite.io/v1/compute/databases//restorations ``` -The new database is independent — recovering does not roll back the source. Once you've validated the recovery, you can swap your application traffic to it or copy specific tables/collections back to the source with `pg_dump` / `mysqldump` / `mongodump`. +The new database is independent, recovering does not roll back the source. Once you've validated the recovery, you can swap your application traffic to it or copy specific tables/collections back to the source with `pg_dump` / `mysqldump` / `mongodump`. # Limits {% #limits %} @@ -169,4 +169,4 @@ The new database is independent — recovering does not roll back the source. On | Cron schedule resolution | 1 minute | | WAL/binlog/oplog flush cadence | every 5 minutes | -Backup storage usage rolls up into the dedicated database storage line on your invoice — there's no separate backup storage line item. +Backup storage usage rolls up into the dedicated database storage line on your invoice, there's no separate backup storage line item. diff --git a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc index 0fbfb002940..3e38d6da52b 100644 --- a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc @@ -4,14 +4,14 @@ title: Branches description: Spin up an ephemeral, isolated copy of a dedicated database in seconds from a storage snapshot. Use branches for previews, migrations, and testing. --- -A **branch** is a short-lived, isolated copy of a dedicated database. It has its own credentials, its own endpoint, and starts from a point-in-time snapshot of the parent's storage volume. Branches are not replicas — once created, they diverge from the parent and never sync back. +A **branch** is a short-lived, isolated copy of a dedicated database. It has its own credentials, its own endpoint, and starts from a point-in-time snapshot of the parent's storage volume. Branches are not replicas, once created, they diverge from the parent and never sync back. Use cases: -- **Preview environments** — one branch per pull request, destroyed when the PR closes -- **Test migrations** — apply a destructive `ALTER` against the branch first, observe behaviour, then run it against the source -- **Reproduce a bug** — branch off the most recent PITR window, attach a debugger, throw it away when done -- **Try an analytical query** — heavy `EXPLAIN ANALYZE` work against a branch can't slow down the primary +- **Preview environments**, one branch per pull request, destroyed when the PR closes +- **Test migrations**, apply a destructive `ALTER` against the branch first, observe behaviour, then run it against the source +- **Reproduce a bug**, branch off the most recent PITR window, attach a debugger, throw it away when done +- **Try an analytical query**, heavy `EXPLAIN ANALYZE` work against a branch can't slow down the primary # How it works {% #how %} @@ -20,7 +20,7 @@ Creating a branch: 1. **Quiesce the parent** briefly. PostgreSQL takes a `CHECKPOINT`. MySQL/MariaDB issue `FLUSH TABLES WITH READ LOCK`. MongoDB runs `db.fsyncLock()`. 2. **Snapshot the parent's storage volume.** This is a near-instant copy-on-write operation. 3. **Release the parent.** Total parent quiesce time is typically under a second. -4. **Provision the branch** from the snapshot — its own isolated storage volume, its own database instance. +4. **Provision the branch** from the snapshot, its own isolated storage volume, its own database instance. 5. **Start the branch** on the same engine and version. Resources can be smaller than the parent. 6. **Generate branch credentials** and return the branch endpoint. @@ -78,7 +78,7 @@ curl -X GET \ # Delete a branch {% #delete %} -Deleting a branch removes the branch's database instance, its storage volume, and the underlying snapshot. There's no soft-delete — once the branch is gone, the data is gone. +Deleting a branch removes the branch's database instance, its storage volume, and the underlying snapshot. There's no soft-delete, once the branch is gone, the data is gone. ```bash curl -X DELETE \ @@ -91,7 +91,7 @@ curl -X DELETE \ A branch is billed like a standalone dedicated database against its own specification (CPU, memory, storage). The snapshot itself is free at the moment of branching; storage cost accumulates as the branch's data diverges from the parent. -There is no separate "branch" line item — branches roll into your regular dedicated database storage and compute totals. +There is no separate "branch" line item, branches roll into your regular dedicated database storage and compute totals. # Use case: per-PR preview database {% #ci-example %} @@ -125,4 +125,4 @@ jobs: https://fra.cloud.appwrite.io/v1/compute/databases/$DB_ID/branches/pr-${{ github.event.number }} ``` -The `|| true` on `POST` makes the workflow idempotent — if the branch already exists, the call is a no-op. +The `|| true` on `POST` makes the workflow idempotent, if the branch already exists, the call is a no-op. diff --git a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc index 8e4a9d8db1b..70b11fd6d08 100644 --- a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc @@ -411,7 +411,7 @@ curl -X PATCH \ # Additional connection users {% #connections %} -The primary `appwrite` user has full ownership of the default database. For applications that need narrower roles — a read-only reporting user, a write-only ingestion user, a per-tenant user — you can create additional connections from the API: +The primary `appwrite` user has full ownership of the default database. For applications that need narrower roles, a read-only reporting user, a write-only ingestion user, a per-tenant user, you can create additional connections from the API: ```bash curl -X POST \ @@ -439,7 +439,7 @@ Available roles: # TLS {% #tls %} -All dedicated database endpoints terminate TLS at the edge proxy and forward to the engine over Appwrite's internal regional network. Connection strings always include `sslmode=require` (PostgreSQL), `useSSL=true` (MySQL/MariaDB), or `tls=true` (MongoDB) by default — your driver should not need any extra configuration. +All dedicated database endpoints terminate TLS at the edge proxy and forward to the engine over the Appwrite network. Connection strings always include `sslmode=require` (PostgreSQL), `useSSL=true` (MySQL/MariaDB), or `tls=true` (MongoDB) by default, so your driver should not need any extra configuration. To require mTLS, see the [network page](/docs/products/databases/dedicated/network). diff --git a/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc b/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc index 0fa05d18067..0321b3d0ff5 100644 --- a/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc @@ -4,7 +4,7 @@ title: Extensions description: Install and manage PostgreSQL extensions like PostGIS, pgvector, and TimescaleDB on a dedicated database. Extensions are managed through the API and are free of charge. --- -PostgreSQL exposes a rich extension ecosystem — PostGIS for geospatial data, pgvector for embeddings, TimescaleDB for time-series, pg_stat_statements for query analytics, and many more. Dedicated PostgreSQL databases on Appwrite support managing extensions through the API; there is no separate cost. +PostgreSQL exposes a rich extension ecosystem, PostGIS for geospatial data, pgvector for embeddings, TimescaleDB for time-series, pg_stat_statements for query analytics, and many more. Dedicated PostgreSQL databases on Appwrite support managing extensions through the API; there is no separate cost. MySQL, MariaDB, and MongoDB do not have an equivalent extension system. The extensions endpoints are available on those engines but always return an empty list. @@ -60,7 +60,7 @@ curl -X DELETE \ https://.cloud.appwrite.io/v1/compute/databases//extensions/pgvector ``` -`DROP EXTENSION` is run with `RESTRICT` by default — if there are dependent objects (tables, indexes, columns), the call fails. Drop the dependents first, or call `DROP EXTENSION ... CASCADE` directly through SQL to acknowledge that you understand what gets removed. +`DROP EXTENSION` is run with `RESTRICT` by default, if there are dependent objects (tables, indexes, columns), the call fails. Drop the dependents first, or call `DROP EXTENSION ... CASCADE` directly through SQL to acknowledge that you understand what gets removed. # Available extensions {% #available %} @@ -78,7 +78,7 @@ A non-exhaustive list of common extensions that are pre-packaged and installable | HTTP | `pg_net`, `http` | | Misc | `hstore`, `citext`, `tablefunc`, `intarray`, `ltree`, `cube`, `earthdistance` | -If you need an extension that isn't on this list, [open a feature request](https://github.com/appwrite/appwrite/issues) — the catalog grows on demand. Custom or proprietary extensions require Enterprise support. +If you need an extension that isn't on this list, [open a feature request](https://github.com/appwrite/appwrite/issues), the catalog grows on demand. Custom or proprietary extensions require Enterprise support. # Version selection {% #versions %} @@ -94,4 +94,4 @@ To upgrade an extension version, drop it and reinstall it with the new version. | Extension install/uninstall billing | Free | | Supported engines | PostgreSQL | -The 50-extension cap is generous — most production deployments install fewer than ten. Extensions that pull in a lot of shared library code (PostGIS, TimescaleDB) count the same as small ones. +The 50-extension cap is generous, most production deployments install fewer than ten. Extensions that pull in a lot of shared library code (PostGIS, TimescaleDB) count the same as small ones. diff --git a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc index abe28be9555..79050646429 100644 --- a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc @@ -6,7 +6,7 @@ description: Run up to five replicas of a dedicated database with synchronous, s 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. -HA is opt-in per database and requires a paid specification — the free shared specification does not support replication. +HA is opt-in per database and requires a paid specification, the free shared specification does not support replication. # How it works {% #how %} @@ -31,7 +31,7 @@ PostgreSQL and MySQL/MariaDB expose three durability modes. MongoDB uses its own | `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. +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. # Enable HA {% #enable %} @@ -50,7 +50,7 @@ curl -X PATCH \ https://.cloud.appwrite.io/v1/compute/databases/ ``` -`highAvailabilityReplicaCount` accepts 0–5. The primary is not counted — a value of 2 gives you one primary plus two replicas. +`highAvailabilityReplicaCount` accepts 0–5. The primary is not counted, a value of 2 gives you one primary plus two replicas. Switching HA on or off is asynchronous: the platform queues a worker that provisions the new replicas, waits for them to come up, then runs an initial base sync. Watch the status with: @@ -65,20 +65,13 @@ The response contains per-replica state and current replication lag in seconds. # Automatic failover {% #failover %} -Appwrite continuously watches the health of the primary. When the primary stays unhealthy across two consecutive 3-second health checks, the platform: +Appwrite continuously watches the health of the primary. When the primary stays unhealthy across two consecutive health checks, the platform promotes the replica with the lowest replication lag and shifts the connection endpoint to it. Clients reconnect transparently. -1. Takes a 15-second distributed lock so only one controller acts. -2. Queries all replicas for their replication lag. -3. Selects the replica with the lowest lag as the failover target. -4. Promotes the target in place: `pg_ctl promote` (PostgreSQL), `STOP REPLICA; SET GLOBAL read_only=0;` (MySQL/MariaDB), or `rs.stepDown()` on the old primary followed by re-election (MongoDB). -5. Runs a write-verification probe on the new primary. -6. Drains live connections from the old primary so clients reconnect to the new one. - -A `failover.completed` event is emitted on the database, fanning out to webhooks, realtime subscriptions, and functions. There is a 60-second cooldown between failovers on the same database to prevent thrashing during infrastructure events. +A `failover.completed` event is emitted on the database and fans out to webhooks, realtime subscriptions, and functions. A 60-second cooldown between failovers prevents thrashing during transient infrastructure events. # Manual failover {% #manual-failover %} -To trigger a failover yourself — for maintenance, testing, or a controlled swap — call the failover endpoint: +To trigger a failover yourself, for maintenance, testing, or a controlled swap, call the failover endpoint: ```bash curl -X POST \ @@ -95,15 +88,15 @@ Omit `targetReplicaId` to let the platform pick the lowest-lag replica. In `async` mode, a read that lands on a replica immediately after a write to the primary may not see the write yet. The application strategies: -- **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. +- **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. # Cross-region failover {% #cross-region-failover %} Beyond same-region HA, you can also configure a cross-region warm standby. The standby lives in another region and continuously replicates from the primary. On a planned or unplanned region-level failure you promote the standby and your DNS hostname starts resolving to the standby region. -Cross-region replicas and standbys are billed as feature add-ons — see the [specifications page](/docs/products/databases/dedicated/specifications#add-ons) for the pricing. +Cross-region replicas and standbys are billed as feature add-ons, see the [specifications page](/docs/products/databases/dedicated/specifications#add-ons) for the pricing. # Limits {% #limits %} diff --git a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc index 2e2a41c5831..b6d2fb1cfc6 100644 --- a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc @@ -35,7 +35,7 @@ Metrics are sampled inside the database environment from OS-level resource count # Usage time series {% #usage %} -For longer-range plots — invoice line items, capacity planning, anomaly detection — call the usage endpoint: +For longer-range plots, invoice line items, capacity planning, anomaly detection, call the usage endpoint: ```bash curl -X GET \ @@ -88,9 +88,9 @@ With `analyze: false`, the planner returns its estimated plan without touching t In addition to metrics and slow queries, the platform runs a periodic index-analysis pass against each database (every ~24 hours, jittered) and submits the results as **insights**. Insights surface: -- **Missing indexes** — tables with sequential scans on large row counts -- **Unused indexes** — indexes that have not been read since the last reset -- **Tuning recommendations** — engine config parameters that look out of range for the current specification +- **Missing indexes**, tables with sequential scans on large row counts +- **Unused indexes**, indexes that have not been read since the last reset +- **Tuning recommendations**, engine config parameters that look out of range for the current specification ```bash curl -X GET \ @@ -105,7 +105,7 @@ Insights are recommendations, not auto-applied changes. The Console suggests the # Audit logs {% #audit %} -Database-level admin actions — credential rotation, IP allowlist changes, backup triggers, failover requests — are recorded in the standard project activity log: +Database-level admin actions, credential rotation, IP allowlist changes, backup triggers, failover requests, are recorded in the standard project activity log: ```bash curl -X GET \ @@ -128,7 +128,7 @@ Every database lifecycle transition emits an event that fans out through Appwrit | `databases..backups..create` | A backup completed (scheduled or manual) | | `databases..restore` | A restore (full or PITR) completed | -Lifecycle events emitted by the edge — `spindown`, `storage.autoscaled`, `failover.completed` — also fan out through this same channel. +Lifecycle events emitted by the edge, `spindown`, `storage.autoscaled`, `failover.completed`, also fan out through this same channel. # What's not exposed {% #out-of-band %} @@ -136,4 +136,4 @@ A few things deliberately stay inside the database environment and are not surfa - **Engine error logs** beyond what's in slow-query and audit feeds. For deeper engine logs, ship your own logging extension (`pgaudit`, `log_min_duration_statement`, MongoDB profiler). - **OS-level metrics** beyond CPU/memory/IOPS. For per-process inspection, use the engine's own introspection (`pg_stat_activity`, `SHOW PROCESSLIST`, `db.currentOp()`). -- **Backup file contents** — the platform never exposes raw dump bytes through the API, only metadata. +- **Backup file contents**, the platform never exposes raw dump bytes through the API, only metadata. diff --git a/src/routes/docs/products/databases/dedicated/network/+page.markdoc b/src/routes/docs/products/databases/dedicated/network/+page.markdoc index c0d058b6f2f..c06f40edd76 100644 --- a/src/routes/docs/products/databases/dedicated/network/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/network/+page.markdoc @@ -4,7 +4,7 @@ title: Network description: Per-database hostname, mandatory TLS, optional IP allowlists, and connection limits for dedicated databases. --- -Every dedicated database gets a public hostname behind Appwrite's edge proxy. The proxy terminates TLS, applies your IP allowlist, and forwards the connection to the database engine over Appwrite's internal regional network. +Every dedicated database gets a public hostname behind Appwrite's edge proxy. The proxy terminates TLS, applies your IP allowlist, and forwards the connection to the database engine over the Appwrite network. # Hostname {% #hostname %} @@ -29,7 +29,7 @@ TLS is enabled on every dedicated database endpoint and is enforced by the edge | MariaDB | `useSSL=true` / `tls=true` | | MongoDB | `tls=true` | -The certificate is signed by a well-known public CA — your driver does not need a custom CA bundle. +The certificate is signed by a well-known public CA, your driver does not need a custom CA bundle. To require mutual TLS (mTLS) where the client presents a certificate signed by your CA, contact support. mTLS is configured at the edge proxy and enforced before the connection is forwarded. @@ -51,7 +51,7 @@ curl -X PATCH \ https://.cloud.appwrite.io/v1/compute/databases/ ``` -The allowlist is enforced at the edge proxy. A connection from a non-allowlisted IP is dropped at TCP handshake — no banner, no error message. Up to 100 CIDR entries are accepted per database. +The allowlist is enforced at the edge proxy. A connection from a non-allowlisted IP is dropped at TCP handshake, no banner, no error message. Up to 100 CIDR entries are accepted per database. Send an empty array to remove all entries and restore unrestricted access. @@ -68,7 +68,7 @@ curl -X PATCH \ When the limit is reached, new connection attempts are rejected at the proxy with a clear error rather than being queued or held open. -The pooler (if enabled) is counted as a single backend connection per pooled slot, not per client — turning on the pooler is the right way to push effective client concurrency well above this limit. +The pooler (if enabled) is counted as a single backend connection per pooled slot, not per client, turning on the pooler is the right way to push effective client concurrency well above this limit. # Connection idle timeout {% #idle-timeout %} @@ -87,8 +87,8 @@ By default, a dedicated database is reachable over the public internet on the en 1. **Restrict the allowlist** to your application server egress IPs only. 2. **Rotate the primary password** through the credentials endpoint after onboarding, so the initial generated password doesn't live in CI logs or chat history forever. -3. **Keep TLS on** — the platform enforces it at the proxy, so non-TLS clients can't connect even if they tried. -4. **Avoid embedding credentials in client-side code.** Connect from a server-side workload — an Appwrite Function, your application backend, or a CI job — and surface results through your own API. +3. **Keep TLS on**, the platform enforces it at the proxy, so non-TLS clients can't connect even if they tried. +4. **Avoid embedding credentials in client-side code.** Connect from a server-side workload, an Appwrite Function, your application backend, or a CI job, and surface results through your own API. For workloads that should never be reachable from the public internet at all, contact support to discuss private endpoint options. diff --git a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc index c57d50690b4..24541ce6f82 100644 --- a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc @@ -6,7 +6,7 @@ description: Optional per-database connection pooler that multiplexes many clien The connection pooler is an opt-in component that runs alongside your database and multiplexes a large number of client connections onto a small number of backend connections. Use it to absorb traffic spikes, smooth out connection churn from serverless runtimes, and route read-only queries to HA replicas without changing your application code. -Each dedicated database can run its own pooler. Pooler resources are billed as part of the underlying specification — there's no separate pooler line item. +Each dedicated database can run its own pooler. Pooler resources are billed as part of the underlying specification, there's no separate pooler line item. # Engine support {% #engines %} @@ -15,9 +15,9 @@ Each dedicated database can run its own pooler. Pooler resources are billed as p | PostgreSQL | yes | 6432 | PgBouncer-compatible wire protocol, native read/write split | | MySQL | yes | 6033 | Query-rule based routing of reads vs writes | | MariaDB | yes | 6033 | Same as MySQL | -| MongoDB | — | — | Not applicable — use the driver's `readPreference` setting | +| MongoDB |, |, | Not applicable, use the driver's `readPreference` setting | -The pooler exposes its own client-facing port on the database endpoint. Clients connect to the pooler port instead of the engine port — everything else is the same. +The pooler exposes its own client-facing port on the database endpoint. Clients connect to the pooler port instead of the engine port, everything else is the same. # Pool modes {% #modes %} @@ -29,7 +29,7 @@ PostgreSQL and MySQL/MariaDB pools support three modes: | `session` | Held for the whole client session | Apps that rely on `SET LOCAL`, prepared statements, advisory locks | | `statement` | Held until the statement completes | Read-only, single-statement workloads. Highest amplification. | -If your application uses features that are tied to a single backend connection — server-side prepared statements (PostgreSQL), advisory locks, temporary tables, `SET LOCAL`, listen/notify — pick `session`. Otherwise, `transaction` is the right choice. +If your application uses features that are tied to a single backend connection, server-side prepared statements (PostgreSQL), advisory locks, temporary tables, `SET LOCAL`, listen/notify, pick `session`. Otherwise, `transaction` is the right choice. # Enable the pooler {% #enable %} @@ -100,7 +100,7 @@ When the database has HA enabled and `readWriteSplitting` is on, the pooler rout This gives you read scale on aggregations and dashboard queries while keeping write-heavy transactions on the primary, with no SDK changes. -Disable read/write splitting if your workload requires every read to see the most recent write — in async replication mode there's a small lag window where a read on a replica may not yet see a just-committed write: +Disable read/write splitting if your workload requires every read to see the most recent write, in async replication mode there's a small lag window where a read on a replica may not yet see a just-committed write: ```bash curl -X PATCH \ @@ -121,8 +121,8 @@ Rule of thumb: # When not to use the pooler {% #anti-patterns %} -- **Long-lived listen/notify subscribers** (PostgreSQL) — these need a session-bound connection. Use `session` mode or bypass the pooler. -- **MongoDB workloads** — MongoDB doesn't have a pooler equivalent in this catalog; rely on the driver's connection pool and `readPreference: 'secondary'` for replica reads. -- **Single-tenant, single-process apps** — the engine's own connection handling is enough; the pooler is overhead. +- **Long-lived listen/notify subscribers** (PostgreSQL), these need a session-bound connection. Use `session` mode or bypass the pooler. +- **MongoDB workloads**, MongoDB doesn't have a pooler equivalent in this catalog; rely on the driver's connection pool and `readPreference: 'secondary'` for replica reads. +- **Single-tenant, single-process apps**, the engine's own connection handling is enough; the pooler is overhead. -For everything else — Lambda, Cloud Run, serverless GraphQL backends, edge runtimes that open a new connection per request — turn it on. +For everything else, Lambda, Cloud Run, serverless GraphQL backends, edge runtimes that open a new connection per request, turn it on. diff --git a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc index 33d18ef2c7d..7e3c15fbdd4 100644 --- a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc @@ -4,7 +4,7 @@ title: Specifications description: Eight dedicated database specifications from 1 vCPU and 1 GB to 8 vCPU and 64 GB, plus a free shared tier. Per-specification CPU, memory, included storage, included bandwidth, and connection cap. --- -A dedicated database is provisioned against a **specification** — a fixed bundle of CPU, memory, included storage, included bandwidth, and a maximum connection cap. You pick the specification on create and can change it later by updating the database; the platform performs a blue-green resize with logical replication. +A dedicated database is provisioned against a **specification**, a fixed bundle of CPU, memory, included storage, included bandwidth, and a maximum connection cap. You pick the specification on create and can change it later by updating the database; the platform performs a blue-green resize with logical replication. All paid specifications are dedicated, always-on databases billed by the hour. The free specification is a shared, scale-to-zero database included with the Pro plan. @@ -21,7 +21,7 @@ All paid specifications are dedicated, always-on databases billed by the hour. T | `s-8vcpu-32gb` | Enterprise | 8 | 32 GB | 5,000 | 2 TB | 7.5 TB | $500 | | `s-8vcpu-64gb` | Enterprise Plus | 8 | 64 GB | 10,000 | 3 TB | 10 TB | $860 | -vCPU counts are dedicated, not burstable. Connection caps are enforced at the engine layer — going above the cap returns a connection refusal, not a slowdown. +vCPU counts are dedicated, not burstable. Connection caps are enforced at the engine layer, going above the cap returns a connection refusal, not a slowdown. # Free (shared) specification {% #free %} @@ -67,19 +67,19 @@ The cross-region **standby** that backs the [cross-region failover](/docs/produc Changing the specification on an existing database is a non-destructive, online operation. There are no read or write outages during the resize. 1. A second database instance is provisioned with the new CPU and memory. -2. The schema and data are streamed from the old instance to the new one through engine-native logical replication — WAL on PostgreSQL, binlog on MySQL/MariaDB, and oplog tailing on MongoDB. +2. The schema and data are streamed from the old instance to the new one through engine-native logical replication, WAL on PostgreSQL, binlog on MySQL/MariaDB, and oplog tailing on MongoDB. 3. Once replication is caught up, the connection endpoint is switched to the new instance. 4. The old instance is decommissioned. -Storage can only grow, never shrink — storage volumes can be expanded but not contracted. Storage expansion happens online without restarting the database. +Storage can only grow, never shrink, storage volumes can be expanded but not contracted. Storage expansion happens online without restarting the database. # Picking a specification {% #picking %} A few rules of thumb: -- **OLTP workloads** (lots of small reads and writes) — pick a specification where the working set fits in memory. For PostgreSQL this is `shared_buffers + effective_cache_size` ≈ 75% of RAM. -- **Analytics workloads** (large scans, aggregations) — favour the higher CPU specifications. `s-8vcpu-32gb` and `s-8vcpu-64gb` are tuned for sustained CPU usage. -- **Storage-heavy workloads** — pick by included storage first. Going one specification up is usually cheaper than paying overage on a smaller specification. -- **Burst patterns** — keep autoscaling on for storage and use a specification one tier below your peak; resize up during peak windows and back down after. +- **OLTP workloads** (lots of small reads and writes), pick a specification where the working set fits in memory. For PostgreSQL this is `shared_buffers + effective_cache_size` ≈ 75% of RAM. +- **Analytics workloads** (large scans, aggregations), favour the higher CPU specifications. `s-8vcpu-32gb` and `s-8vcpu-64gb` are tuned for sustained CPU usage. +- **Storage-heavy workloads**, pick by included storage first. Going one specification up is usually cheaper than paying overage on a smaller specification. +- **Burst patterns**, keep autoscaling on for storage and use a specification one tier below your peak; resize up during peak windows and back down after. The next page explains how to [connect to a dedicated database](/docs/products/databases/dedicated/connect). diff --git a/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc b/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc index b357e90a198..91dbedc65ab 100644 --- a/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc @@ -4,7 +4,7 @@ title: SQL API description: Execute parameterised SQL against a dedicated database over HTTPS without opening a TCP connection. Useful from edge runtimes, serverless functions, and CI scripts. --- -The **SQL API** is a managed HTTP endpoint that executes one parameterised SQL statement against a dedicated database and returns the result as JSON. It runs alongside the database itself, so it sees the same latency as a local connection — but you call it from anywhere a TCP database connection isn't practical (edge runtimes, serverless functions on cold starts, CI scripts, internal admin tools). +The **SQL API** is a managed HTTP endpoint that executes one parameterised SQL statement against a dedicated database and returns the result as JSON. It runs alongside the database itself, so it sees the same latency as a local connection, but you call it from anywhere a TCP database connection isn't practical (edge runtimes, serverless functions on cold starts, CI scripts, internal admin tools). The SQL API is opt-in per database and supports all four engines. @@ -39,7 +39,7 @@ The defaults above are safe to start with: `SELECT`-only, 10k rows, 10 MB payloa | `sqlApiMaxBytes` | 10 MB | 100 MB | Hard payload cap; truncates beyond | | `sqlApiTimeoutSeconds` | 30 | 300 (5 min) | Server-side cancellation deadline | -Only `SELECT`, `INSERT`, `UPDATE`, and `DELETE` are ever accepted. Schema-altering statements (`CREATE`, `DROP`, `TRUNCATE`, `ALTER`, `GRANT`, `REVOKE`) are rejected unconditionally — those must go through the dedicated provisioning APIs or a direct database connection. +Only `SELECT`, `INSERT`, `UPDATE`, and `DELETE` are ever accepted. Schema-altering statements (`CREATE`, `DROP`, `TRUNCATE`, `ALTER`, `GRANT`, `REVOKE`) are rejected unconditionally, those must go through the dedicated provisioning APIs or a direct database connection. # Execute a statement {% #execute %} @@ -77,9 +77,9 @@ If the result exceeds `sqlApiMaxRows` or `sqlApiMaxBytes`, the response sets `tr # Parameter binding {% #bindings %} -Bindings are always sent through a separate field — Appwrite never interpolates them into the SQL string. Two styles are accepted: +Bindings are always sent through a separate field, Appwrite never interpolates them into the SQL string. Two styles are accepted: -**Positional** — list of values, referenced as `$1`, `$2`, … in the SQL. +**Positional**, list of values, referenced as `$1`, `$2`, … in the SQL. ```json { @@ -88,7 +88,7 @@ Bindings are always sent through a separate field — Appwrite never interpolate } ``` -**Named** — a `name => value` map, referenced as `:name` or `@name` depending on engine convention. +**Named**, a `name => value` map, referenced as `:name` or `@name` depending on engine convention. ```json { @@ -97,7 +97,7 @@ Bindings are always sent through a separate field — Appwrite never interpolate } ``` -Pick one style per request — mixing positional and named bindings in the same statement is rejected. +Pick one style per request, mixing positional and named bindings in the same statement is rejected. # Per-database whitelist {% #whitelist %} @@ -132,14 +132,14 @@ The whitelist is enforced before the request leaves Appwrite, so a disallowed st The SQL API trades raw throughput for portability. Use it when: -- **Calling from an edge runtime** that can't open a long-lived TCP connection — Cloudflare Workers, Vercel Edge Functions, Deno Deploy. +- **Calling from an edge runtime** that can't open a long-lived TCP connection, Cloudflare Workers, Vercel Edge Functions, Deno Deploy. - **Building an admin tool** that runs ad-hoc queries from a UI without shipping engine drivers to the browser. -- **Writing a one-off CI script** — easier to `curl` than to install `psql` and manage credentials in CI. +- **Writing a one-off CI script**, easier to `curl` than to install `psql` and manage credentials in CI. Don't use it when: -- **Throughput matters** — TCP + a connection pool is faster end-to-end. The SQL API adds an HTTP hop. -- **The query is transactional** — each SQL API call is its own statement. There's no `BEGIN`…`COMMIT` window across calls. +- **Throughput matters**, TCP + a connection pool is faster end-to-end. The SQL API adds an HTTP hop. +- **The query is transactional**, each SQL API call is its own statement. There's no `BEGIN`…`COMMIT` window across calls. # Audit {% #audit %} From 2271ca82dafe885480f5561e5c8606252b0ba339 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 16:17:10 +1200 Subject: [PATCH 09/15] docs(dedicated): use server SDKs as default, curl as fallback Convert every API curl example in the dedicated databases docs to a {% multicode %} block. Server SDKs (Node.js, Deno, PHP, Python, Ruby, .NET, Dart, Kotlin, Swift, Go, Rust) come first, with curl as the last tab. ~32 endpoints converted across high-availability, backups, branches, extensions, pooler, sql-api, network, and monitoring pages. Mark cross-region failover and cross-region replicas as 'coming soon' in the HA and specifications pages. Replace 'quiesce' on the branches page with 'pause writes', since the term reads as jargon to anyone who isn't a DB engineer. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../databases/dedicated/backups/+page.markdoc | 1412 +++++++++++++++++ .../dedicated/branches/+page.markdoc | 766 ++++++++- .../dedicated/extensions/+page.markdoc | 563 +++++++ .../dedicated/high-availability/+page.markdoc | 583 ++++++- .../dedicated/monitoring/+page.markdoc | 1125 +++++++++++++ .../databases/dedicated/network/+page.markdoc | 621 +++++++- .../databases/dedicated/pooler/+page.markdoc | 588 +++++++ .../dedicated/specifications/+page.markdoc | 18 +- .../databases/dedicated/sql-api/+page.markdoc | 440 +++++ 9 files changed, 6098 insertions(+), 18 deletions(-) diff --git a/src/routes/docs/products/databases/dedicated/backups/+page.markdoc b/src/routes/docs/products/databases/dedicated/backups/+page.markdoc index e2152d51128..756aa1bc909 100644 --- a/src/routes/docs/products/databases/dedicated/backups/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/backups/+page.markdoc @@ -14,6 +14,219 @@ A backup captures the full contents of the database: all schemas, tables/collect Scheduled backups run from a cron expression you configure on the database. The default is `0 3 * * *` (daily at 03:00 UTC), jittered to spread load across the platform. +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + backupEnabled: true, + backupCron: '0 3 * * *', + backupRetentionDays: 14, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + backupEnabled: true, + backupCron: '0 3 * * *', + backupRetentionDays: 14, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabase( + databaseId: '', + backupEnabled: true, + backupCron: '0 3 * * *', + backupRetentionDays: 14, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database( + database_id='', + backup_enabled=True, + backup_cron='0 3 * * *', + backup_retention_days=14, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database( + database_id: '', + backup_enabled: true, + backup_cron: '0 3 * * *', + backup_retention_days: 14, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabase( + databaseId: "", + backupEnabled: true, + backupCron: "0 3 * * *", + backupRetentionDays: 14 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabase( + databaseId: '', + backupEnabled: true, + backupCron: '0 3 * * *', + backupRetentionDays: 14, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabase( + databaseId = "", + backupEnabled = true, + backupCron = "0 3 * * *", + backupRetentionDays = 14, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabase( + databaseId: "", + backupEnabled: true, + backupCron: "0 3 * * *", + backupRetentionDays: 14 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabase("", + compute.WithUpdateDatabaseBackupEnabled(true), + compute.WithUpdateDatabaseBackupCron("0 3 * * *"), + compute.WithUpdateDatabaseBackupRetentionDays(14), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database("") + .backup_enabled(true) + .backup_cron("0 3 * * *") + .backup_retention_days(14) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ -H "X-Appwrite-Project: " \ @@ -26,6 +239,7 @@ curl -X PATCH \ }' \ https://.cloud.appwrite.io/v1/compute/databases/ ``` +{% /multicode %} Backup retention accepts 1–365 days. Backups older than the retention window are deleted automatically once a day. @@ -33,12 +247,183 @@ Backup retention accepts 1–365 days. Backups older than the retention window a To create an ad-hoc backup, before a destructive migration, for example, call the create endpoint. The response contains the backup ID; the actual dump runs asynchronously. +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const backup = await compute.createDatabaseBackup({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const backup = await compute.createDatabaseBackup({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$backup = $compute->createDatabaseBackup(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +backup = compute.create_database_backup(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +backup = compute.create_database_backup(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var backup = await compute.CreateDatabaseBackup(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final backup = await compute.createDatabaseBackup( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val backup = compute.createDatabaseBackup( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let backup = try await compute.createDatabaseBackup( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + backup, err := compute.CreateDatabaseBackup("") + if err != nil { + panic(err) + } + _ = backup +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let backup = compute.create_database_backup("").await?; + + Ok(()) +} +``` ```bash curl -X POST \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//backups ``` +{% /multicode %} Manual backups respect the same retention setting as scheduled ones. Pass `retentionDays` in the request body to override the default. @@ -52,6 +437,252 @@ Restore reverses the pipeline: the key is fetched from the secret store, the cip By default, backups are stored on Appwrite-managed S3-compatible storage. You can also bring your own bucket, S3, Backblaze, DigitalOcean Spaces, Linode, Wasabi, GCS via S3 interop, or Azure Blob via S3 interop. Bring-your-own-bucket backups never enter Appwrite-controlled storage. +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabaseBackupStorage({ + databaseId: '', + provider: 's3', + bucket: 'my-db-backups', + region: 'eu-central-1', + prefix: 'appwrite/', + accessKey: '', + secretKey: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabaseBackupStorage({ + databaseId: '', + provider: 's3', + bucket: 'my-db-backups', + region: 'eu-central-1', + prefix: 'appwrite/', + accessKey: '', + secretKey: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabaseBackupStorage( + databaseId: '', + provider: 's3', + bucket: 'my-db-backups', + region: 'eu-central-1', + prefix: 'appwrite/', + accessKey: '', + secretKey: '', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database_backup_storage( + database_id='', + provider='s3', + bucket='my-db-backups', + region='eu-central-1', + prefix='appwrite/', + access_key='', + secret_key='', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database_backup_storage( + database_id: '', + provider: 's3', + bucket: 'my-db-backups', + region: 'eu-central-1', + prefix: 'appwrite/', + access_key: '', + secret_key: '', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabaseBackupStorage( + databaseId: "", + provider: "s3", + bucket: "my-db-backups", + region: "eu-central-1", + prefix: "appwrite/", + accessKey: "", + secretKey: "" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabaseBackupStorage( + databaseId: '', + provider: 's3', + bucket: 'my-db-backups', + region: 'eu-central-1', + prefix: 'appwrite/', + accessKey: '', + secretKey: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabaseBackupStorage( + databaseId = "", + provider = "s3", + bucket = "my-db-backups", + region = "eu-central-1", + prefix = "appwrite/", + accessKey = "", + secretKey = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabaseBackupStorage( + databaseId: "", + provider: "s3", + bucket: "my-db-backups", + region: "eu-central-1", + prefix: "appwrite/", + accessKey: "", + secretKey: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabaseBackupStorage("", + compute.WithUpdateDatabaseBackupStorageProvider("s3"), + compute.WithUpdateDatabaseBackupStorageBucket("my-db-backups"), + compute.WithUpdateDatabaseBackupStorageRegion("eu-central-1"), + compute.WithUpdateDatabaseBackupStoragePrefix("appwrite/"), + compute.WithUpdateDatabaseBackupStorageAccessKey(""), + compute.WithUpdateDatabaseBackupStorageSecretKey(""), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database_backup_storage("") + .provider("s3") + .bucket("my-db-backups") + .region("eu-central-1") + .prefix("appwrite/") + .access_key("") + .secret_key("") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PUT \ -H "X-Appwrite-Project: " \ @@ -67,6 +698,7 @@ curl -X PUT \ }' \ https://.cloud.appwrite.io/v1/compute/databases//backups/storage ``` +{% /multicode %} Credentials are encrypted at rest. The `endpoint` field is supported for S3-compatible providers, leave empty for AWS S3. @@ -87,6 +719,208 @@ The result is recorded on the backup document under `verifiedAt`. You can query Restoring from a backup creates a new dedicated database, applies the dump, and waits for the engine to come up. The original database is untouched. +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseRestoration({ + databaseId: '', + type: 'backup', + backupId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseRestoration({ + databaseId: '', + type: 'backup', + backupId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->createDatabaseRestoration( + databaseId: '', + type: 'backup', + backupId: '', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.create_database_restoration( + database_id='', + type='backup', + backup_id='', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.create_database_restoration( + database_id: '', + type: 'backup', + backup_id: '', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.CreateDatabaseRestoration( + databaseId: "", + type: "backup", + backupId: "" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.createDatabaseRestoration( + databaseId: '', + type: 'backup', + backupId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.createDatabaseRestoration( + databaseId = "", + type = "backup", + backupId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.createDatabaseRestoration( + databaseId: "", + type: "backup", + backupId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.CreateDatabaseRestoration("", + compute.WithCreateDatabaseRestorationType("backup"), + compute.WithCreateDatabaseRestorationBackupId(""), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.create_database_restoration("") + .r#type("backup") + .backup_id("") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X POST \ -H "X-Appwrite-Project: " \ @@ -98,6 +932,7 @@ curl -X POST \ }' \ https://.cloud.appwrite.io/v1/compute/databases//restorations ``` +{% /multicode %} The new database is provisioned with the same specification as the source unless you also pass overrides for `cpu`, `memory`, and `storage`. @@ -107,6 +942,208 @@ PITR layers continuous transaction-log archiving on top of scheduled backups. Wi Enable PITR on create or update: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + backupPitr: true, + pitrRetentionDays: 7, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + backupPitr: true, + pitrRetentionDays: 7, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabase( + databaseId: '', + backupPitr: true, + pitrRetentionDays: 7, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database( + database_id='', + backup_pitr=True, + pitr_retention_days=7, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database( + database_id: '', + backup_pitr: true, + pitr_retention_days: 7, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabase( + databaseId: "", + backupPitr: true, + pitrRetentionDays: 7 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabase( + databaseId: '', + backupPitr: true, + pitrRetentionDays: 7, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabase( + databaseId = "", + backupPitr = true, + pitrRetentionDays = 7, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabase( + databaseId: "", + backupPitr: true, + pitrRetentionDays: 7 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabase("", + compute.WithUpdateDatabaseBackupPitr(true), + compute.WithUpdateDatabasePitrRetentionDays(7), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database("") + .backup_pitr(true) + .pitr_retention_days(7) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ -H "X-Appwrite-Project: " \ @@ -115,6 +1152,7 @@ curl -X PATCH \ -d '{ "backupPitr": true, "pitrRetentionDays": 7 }' \ https://.cloud.appwrite.io/v1/compute/databases/ ``` +{% /multicode %} `pitrRetentionDays` accepts 1–35 days. PITR is billed as a feature add-on at 20% of the underlying specification price. @@ -122,12 +1160,183 @@ curl -X PATCH \ Each database with PITR enabled exposes its current recovery window. Use this to confirm the earliest and latest timestamps you can restore to before initiating a recovery: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const windows = await compute.getDatabasePITRWindows({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const windows = await compute.getDatabasePITRWindows({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$windows = $compute->getDatabasePITRWindows(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +windows = compute.get_database_pitr_windows(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +windows = compute.get_database_pitr_windows(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var windows = await compute.GetDatabasePITRWindows(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final windows = await compute.getDatabasePITRWindows( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val windows = compute.getDatabasePITRWindows( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let windows = try await compute.getDatabasePITRWindows( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + windows, err := compute.GetDatabasePITRWindows("") + if err != nil { + panic(err) + } + _ = windows +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let windows = compute.get_database_pitr_windows("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//pitr ``` +{% /multicode %} Response: @@ -145,6 +1354,208 @@ Response: A PITR restore takes a target timestamp inside the active window and produces a new database that represents the state of the source at that exact moment: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseRestoration({ + databaseId: '', + type: 'pitr', + targetTime: '2026-05-21T13:30:00Z', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseRestoration({ + databaseId: '', + type: 'pitr', + targetTime: '2026-05-21T13:30:00Z', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->createDatabaseRestoration( + databaseId: '', + type: 'pitr', + targetTime: '2026-05-21T13:30:00Z', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.create_database_restoration( + database_id='', + type='pitr', + target_time='2026-05-21T13:30:00Z', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.create_database_restoration( + database_id: '', + type: 'pitr', + target_time: '2026-05-21T13:30:00Z', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.CreateDatabaseRestoration( + databaseId: "", + type: "pitr", + targetTime: "2026-05-21T13:30:00Z" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.createDatabaseRestoration( + databaseId: '', + type: 'pitr', + targetTime: '2026-05-21T13:30:00Z', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.createDatabaseRestoration( + databaseId = "", + type = "pitr", + targetTime = "2026-05-21T13:30:00Z", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.createDatabaseRestoration( + databaseId: "", + type: "pitr", + targetTime: "2026-05-21T13:30:00Z" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.CreateDatabaseRestoration("", + compute.WithCreateDatabaseRestorationType("pitr"), + compute.WithCreateDatabaseRestorationTargetTime("2026-05-21T13:30:00Z"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.create_database_restoration("") + .r#type("pitr") + .target_time("2026-05-21T13:30:00Z") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X POST \ -H "X-Appwrite-Project: " \ @@ -156,6 +1567,7 @@ curl -X POST \ }' \ https://.cloud.appwrite.io/v1/compute/databases//restorations ``` +{% /multicode %} The new database is independent, recovering does not roll back the source. Once you've validated the recovery, you can swap your application traffic to it or copy specific tables/collections back to the source with `pg_dump` / `mysqldump` / `mongodump`. diff --git a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc index 3e38d6da52b..1c04c3f25aa 100644 --- a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc @@ -17,19 +17,243 @@ Use cases: Creating a branch: -1. **Quiesce the parent** briefly. PostgreSQL takes a `CHECKPOINT`. MySQL/MariaDB issue `FLUSH TABLES WITH READ LOCK`. MongoDB runs `db.fsyncLock()`. +1. **Briefly pause writes on the parent.** PostgreSQL takes a `CHECKPOINT`. MySQL/MariaDB issue `FLUSH TABLES WITH READ LOCK`. MongoDB runs `db.fsyncLock()`. 2. **Snapshot the parent's storage volume.** This is a near-instant copy-on-write operation. -3. **Release the parent.** Total parent quiesce time is typically under a second. +3. **Resume writes on the parent.** The parent is paused for under a second. 4. **Provision the branch** from the snapshot, its own isolated storage volume, its own database instance. 5. **Start the branch** on the same engine and version. Resources can be smaller than the parent. 6. **Generate branch credentials** and return the branch endpoint. The parent and the branch share storage cost-free at the moment of branching (copy-on-write). Storage cost grows as the two databases diverge. -PostgreSQL supports crash-consistent snapshots, so the quiesce step is a fast checkpoint and PostgreSQL branches recover via WAL replay on first start. MySQL/MariaDB and MongoDB require an explicit write freeze before snapshot to produce a clean image. +PostgreSQL supports crash-consistent snapshots, so the pause is a fast checkpoint and PostgreSQL branches recover via WAL replay on first start. MySQL/MariaDB and MongoDB require an explicit write freeze before snapshot to produce a clean image. # Create a branch {% #create %} +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseBranch({ + databaseId: '', + branchId: 'pr-1234', + name: 'PR 1234 preview', + cpu: 1000, + memory: 2048, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseBranch({ + databaseId: '', + branchId: 'pr-1234', + name: 'PR 1234 preview', + cpu: 1000, + memory: 2048, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->createDatabaseBranch( + databaseId: '', + branchId: 'pr-1234', + name: 'PR 1234 preview', + cpu: 1000, + memory: 2048, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.create_database_branch( + database_id='', + branch_id='pr-1234', + name='PR 1234 preview', + cpu=1000, + memory=2048, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.create_database_branch( + database_id: '', + branch_id: 'pr-1234', + name: 'PR 1234 preview', + cpu: 1000, + memory: 2048, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.CreateDatabaseBranch( + databaseId: "", + branchId: "pr-1234", + name: "PR 1234 preview", + cpu: 1000, + memory: 2048 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.createDatabaseBranch( + databaseId: '', + branchId: 'pr-1234', + name: 'PR 1234 preview', + cpu: 1000, + memory: 2048, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.createDatabaseBranch( + databaseId = "", + branchId = "pr-1234", + name = "PR 1234 preview", + cpu = 1000, + memory = 2048, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.createDatabaseBranch( + databaseId: "", + branchId: "pr-1234", + name: "PR 1234 preview", + cpu: 1000, + memory: 2048 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.CreateDatabaseBranch("", + compute.WithCreateDatabaseBranchBranchId("pr-1234"), + compute.WithCreateDatabaseBranchName("PR 1234 preview"), + compute.WithCreateDatabaseBranchCpu(1000), + compute.WithCreateDatabaseBranchMemory(2048), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.create_database_branch("") + .branch_id("pr-1234") + .name("PR 1234 preview") + .cpu(1000) + .memory(2048) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X POST \ -H "X-Appwrite-Project: " \ @@ -43,6 +267,7 @@ curl -X POST \ }' \ https://.cloud.appwrite.io/v1/compute/databases//branches ``` +{% /multicode %} Optional fields: @@ -58,34 +283,565 @@ The branch endpoint is exposed as a separate hostname: db---..appwrite.network ``` -Connect to it with the engine's standard CLI, same as the parent. Credentials for the branch are retrieved through: +Connect to it with the engine's standard CLI, same as the parent. Credentials for the branch are retrieved by listing branches and locating yours by `branchId`: + +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const branches = await compute.listDatabaseBranches({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const branches = await compute.listDatabaseBranches({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$branches = $compute->listDatabaseBranches(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +branches = compute.list_database_branches(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +branches = compute.list_database_branches(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var branches = await compute.ListDatabaseBranches(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final branches = await compute.listDatabaseBranches( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val branches = compute.listDatabaseBranches( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let branches = try await compute.listDatabaseBranches( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + branches, err := compute.ListDatabaseBranches("") + if err != nil { + panic(err) + } + _ = branches +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let branches = compute.list_database_branches("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ - https://.cloud.appwrite.io/v1/compute/databases//branches/ + https://.cloud.appwrite.io/v1/compute/databases//branches ``` +{% /multicode %} # List branches {% #list %} +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const branches = await compute.listDatabaseBranches({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const branches = await compute.listDatabaseBranches({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$branches = $compute->listDatabaseBranches(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +branches = compute.list_database_branches(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +branches = compute.list_database_branches(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var branches = await compute.ListDatabaseBranches(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final branches = await compute.listDatabaseBranches( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val branches = compute.listDatabaseBranches( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let branches = try await compute.listDatabaseBranches( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + branches, err := compute.ListDatabaseBranches("") + if err != nil { + panic(err) + } + _ = branches +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let branches = compute.list_database_branches("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//branches ``` +{% /multicode %} # Delete a branch {% #delete %} Deleting a branch removes the branch's database instance, its storage volume, and the underlying snapshot. There's no soft-delete, once the branch is gone, the data is gone. +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.deleteDatabaseBranch({ + databaseId: '', + branchId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.deleteDatabaseBranch({ + databaseId: '', + branchId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->deleteDatabaseBranch( + databaseId: '', + branchId: '', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.delete_database_branch( + database_id='', + branch_id='', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.delete_database_branch( + database_id: '', + branch_id: '', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.DeleteDatabaseBranch( + databaseId: "", + branchId: "" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.deleteDatabaseBranch( + databaseId: '', + branchId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.deleteDatabaseBranch( + databaseId = "", + branchId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.deleteDatabaseBranch( + databaseId: "", + branchId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.DeleteDatabaseBranch("", "") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.delete_database_branch("", "") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X DELETE \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//branches/ ``` +{% /multicode %} # Billing {% #billing %} diff --git a/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc b/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc index 0321b3d0ff5..05c0642df51 100644 --- a/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/extensions/+page.markdoc @@ -10,6 +10,208 @@ MySQL, MariaDB, and MongoDB do not have an equivalent extension system. The exte # Install an extension {% #install %} +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseExtension({ + databaseId: '', + name: 'pgvector', + version: '0.7.4', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseExtension({ + databaseId: '', + name: 'pgvector', + version: '0.7.4', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->createDatabaseExtension( + databaseId: '', + name: 'pgvector', + version: '0.7.4', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.create_database_extension( + database_id='', + name='pgvector', + version='0.7.4', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.create_database_extension( + database_id: '', + name: 'pgvector', + version: '0.7.4', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.CreateDatabaseExtension( + databaseId: "", + name: "pgvector", + version: "0.7.4" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.createDatabaseExtension( + databaseId: '', + name: 'pgvector', + version: '0.7.4', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.createDatabaseExtension( + databaseId = "", + name = "pgvector", + version = "0.7.4", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.createDatabaseExtension( + databaseId: "", + name: "pgvector", + version: "0.7.4" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.CreateDatabaseExtension("", + compute.WithCreateDatabaseExtensionName("pgvector"), + compute.WithCreateDatabaseExtensionVersion("0.7.4"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.create_database_extension("") + .name("pgvector") + .version("0.7.4") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X POST \ -H "X-Appwrite-Project: " \ @@ -21,6 +223,7 @@ curl -X POST \ }' \ https://.cloud.appwrite.io/v1/compute/databases//extensions ``` +{% /multicode %} The request enqueues a worker that runs `CREATE EXTENSION` inside the database. The response returns immediately with status `creating`; the extension transitions to `ready` once the install completes (typically within a few seconds for small extensions). @@ -28,12 +231,183 @@ If the extension requires a non-default schema, pass the optional `schema` field # List extensions {% #list %} +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const extensions = await compute.listDatabaseExtensions({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const extensions = await compute.listDatabaseExtensions({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$extensions = $compute->listDatabaseExtensions(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +extensions = compute.list_database_extensions(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +extensions = compute.list_database_extensions(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var extensions = await compute.ListDatabaseExtensions(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final extensions = await compute.listDatabaseExtensions( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val extensions = compute.listDatabaseExtensions( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let extensions = try await compute.listDatabaseExtensions( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + extensions, err := compute.ListDatabaseExtensions("") + if err != nil { + panic(err) + } + _ = extensions +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let extensions = compute.list_database_extensions("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//extensions ``` +{% /multicode %} The response separates installed extensions from available extensions: @@ -53,12 +427,201 @@ The response separates installed extensions from available extensions: # Remove an extension {% #remove %} +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.deleteDatabaseExtension({ + databaseId: '', + extensionName: 'pgvector', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.deleteDatabaseExtension({ + databaseId: '', + extensionName: 'pgvector', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->deleteDatabaseExtension( + databaseId: '', + extensionName: 'pgvector', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.delete_database_extension( + database_id='', + extension_name='pgvector', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.delete_database_extension( + database_id: '', + extension_name: 'pgvector', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.DeleteDatabaseExtension( + databaseId: "", + extensionName: "pgvector" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.deleteDatabaseExtension( + databaseId: '', + extensionName: 'pgvector', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.deleteDatabaseExtension( + databaseId = "", + extensionName = "pgvector", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.deleteDatabaseExtension( + databaseId: "", + extensionName: "pgvector" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.DeleteDatabaseExtension("", "pgvector") + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.delete_database_extension("", "pgvector") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X DELETE \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//extensions/pgvector ``` +{% /multicode %} `DROP EXTENSION` is run with `RESTRICT` by default, if there are dependent objects (tables, indexes, columns), the call fails. Drop the dependents first, or call `DROP EXTENSION ... CASCADE` directly through SQL to acknowledge that you understand what gets removed. diff --git a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc index 79050646429..d9b8447b877 100644 --- a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc @@ -37,6 +37,219 @@ A safe default for production is `sync` with two replicas, a single replica fail Set `highAvailability`, `highAvailabilityReplicaCount`, and `highAvailabilitySyncMode` on the database. You can do this on create, or by updating the database later: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + highAvailability: true, + highAvailabilityReplicaCount: 2, + highAvailabilitySyncMode: 'sync', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + highAvailability: true, + highAvailabilityReplicaCount: 2, + highAvailabilitySyncMode: 'sync', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabase( + databaseId: '', + highAvailability: true, + highAvailabilityReplicaCount: 2, + highAvailabilitySyncMode: 'sync', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database( + database_id='', + high_availability=True, + high_availability_replica_count=2, + high_availability_sync_mode='sync', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database( + database_id: '', + high_availability: true, + high_availability_replica_count: 2, + high_availability_sync_mode: 'sync', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabase( + databaseId: "", + highAvailability: true, + highAvailabilityReplicaCount: 2, + highAvailabilitySyncMode: "sync" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabase( + databaseId: '', + highAvailability: true, + highAvailabilityReplicaCount: 2, + highAvailabilitySyncMode: 'sync', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabase( + databaseId = "", + highAvailability = true, + highAvailabilityReplicaCount = 2, + highAvailabilitySyncMode = "sync", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabase( + databaseId: "", + highAvailability: true, + highAvailabilityReplicaCount: 2, + highAvailabilitySyncMode: "sync" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabase("", + compute.WithUpdateDatabaseHighAvailability(true), + compute.WithUpdateDatabaseHighAvailabilityReplicaCount(2), + compute.WithUpdateDatabaseHighAvailabilitySyncMode("sync"), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database("") + .high_availability(true) + .high_availability_replica_count(2) + .high_availability_sync_mode("sync") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ -H "X-Appwrite-Project: " \ @@ -49,17 +262,189 @@ curl -X PATCH \ }' \ https://.cloud.appwrite.io/v1/compute/databases/ ``` +{% /multicode %} `highAvailabilityReplicaCount` accepts 0–5. The primary is not counted, a value of 2 gives you one primary plus two replicas. Switching HA on or off is asynchronous: the platform queues a worker that provisions the new replicas, waits for them to come up, then runs an initial base sync. Watch the status with: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const status = await compute.getDatabaseHAStatus({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const status = await compute.getDatabaseHAStatus({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$status = $compute->getDatabaseHAStatus(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +status = compute.get_database_ha_status(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +status = compute.get_database_ha_status(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var status = await compute.GetDatabaseHAStatus(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final status = await compute.getDatabaseHAStatus( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val status = compute.getDatabaseHAStatus( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let status = try await compute.getDatabaseHAStatus( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + status, err := compute.GetDatabaseHAStatus("") + if err != nil { + panic(err) + } + _ = status +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let status = compute.get_database_ha_status("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//ha ``` +{% /multicode %} The response contains per-replica state and current replication lag in seconds. @@ -73,14 +458,206 @@ A `failover.completed` event is emitted on the database and fans out to webhooks To trigger a failover yourself, for maintenance, testing, or a controlled swap, call the failover endpoint: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseFailover({ + databaseId: '', + targetReplicaId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.createDatabaseFailover({ + databaseId: '', + targetReplicaId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->createDatabaseFailover( + databaseId: '', + targetReplicaId: '', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.create_database_failover( + database_id='', + target_replica_id='', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.create_database_failover( + database_id: '', + target_replica_id: '', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.CreateDatabaseFailover( + databaseId: "", + targetReplicaId: "" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.createDatabaseFailover( + databaseId: '', + targetReplicaId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.createDatabaseFailover( + databaseId = "", + targetReplicaId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.createDatabaseFailover( + databaseId: "", + targetReplicaId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.CreateDatabaseFailover("", + compute.WithCreateDatabaseFailoverTargetReplicaId(""), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.create_database_failover("") + .target_replica_id("") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X POST \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ -H "Content-Type: application/json" \ - -d '{ "targetReplicaId": "" }' \ + -d '{ "targetReplicaId": "" }' \ https://.cloud.appwrite.io/v1/compute/databases//ha/failovers ``` +{% /multicode %} Omit `targetReplicaId` to let the platform pick the lowest-lag replica. @@ -94,6 +671,10 @@ In `async` mode, a read that lands on a replica immediately after a write to the # Cross-region failover {% #cross-region-failover %} +{% info title="Coming soon" %} +Cross-region failover is not yet available. The API surface, pricing, and behaviour described below are previewed for context; the feature will ship in a future release. +{% /info %} + Beyond same-region HA, you can also configure a cross-region warm standby. The standby lives in another region and continuously replicates from the primary. On a planned or unplanned region-level failure you promote the standby and your DNS hostname starts resolving to the standby region. Cross-region replicas and standbys are billed as feature add-ons, see the [specifications page](/docs/products/databases/dedicated/specifications#add-ons) for the pricing. diff --git a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc index b6d2fb1cfc6..9a1d8df35ee 100644 --- a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc @@ -8,12 +8,205 @@ Every dedicated database collects its own resource telemetry, slow-query logs, a # Live metrics {% #metrics %} +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const metrics = await compute.getDatabaseMetrics({ + databaseId: '', + period: '24h', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const metrics = await compute.getDatabaseMetrics({ + databaseId: '', + period: '24h', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$metrics = $compute->getDatabaseMetrics( + databaseId: '', + period: '24h', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +metrics = compute.get_database_metrics( + database_id='', + period='24h', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +metrics = compute.get_database_metrics( + database_id: '', + period: '24h', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var metrics = await compute.GetDatabaseMetrics( + databaseId: "", + period: "24h" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final metrics = await compute.getDatabaseMetrics( + databaseId: '', + period: '24h', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val metrics = compute.getDatabaseMetrics( + databaseId = "", + period = "24h", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let metrics = try await compute.getDatabaseMetrics( + databaseId: "", + period: "24h" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + metrics, err := compute.GetDatabaseMetrics("", + compute.WithGetDatabaseMetricsPeriod("24h"), + ) + if err != nil { + panic(err) + } + _ = metrics +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let metrics = compute.get_database_metrics("") + .period("24h") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ "https://.cloud.appwrite.io/v1/compute/databases//metrics?period=24h" ``` +{% /multicode %} `period` accepts `1h`, `24h`, `7d`, or `30d`. @@ -37,12 +230,205 @@ Metrics are sampled inside the database environment from OS-level resource count For longer-range plots, invoice line items, capacity planning, anomaly detection, call the usage endpoint: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const usage = await compute.getDatabaseUsage({ + databaseId: '', + range: '30d', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const usage = await compute.getDatabaseUsage({ + databaseId: '', + range: '30d', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$usage = $compute->getDatabaseUsage( + databaseId: '', + range: '30d', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +usage = compute.get_database_usage( + database_id='', + range='30d', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +usage = compute.get_database_usage( + database_id: '', + range: '30d', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var usage = await compute.GetDatabaseUsage( + databaseId: "", + range: "30d" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final usage = await compute.getDatabaseUsage( + databaseId: '', + range: '30d', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val usage = compute.getDatabaseUsage( + databaseId = "", + range = "30d", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let usage = try await compute.getDatabaseUsage( + databaseId: "", + range: "30d" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + usage, err := compute.GetDatabaseUsage("", + compute.WithGetDatabaseUsageRange("30d"), + ) + if err != nil { + panic(err) + } + _ = usage +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let usage = compute.get_database_usage("") + .range("30d") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ "https://.cloud.appwrite.io/v1/compute/databases//usage?range=30d" ``` +{% /multicode %} `range` accepts `24h`, `7d`, `30d`, or `90d`. The response is a series of bucketed `(timestamp, value)` pairs for each metric. @@ -50,12 +436,183 @@ curl -X GET \ Each engine logs queries that run longer than the configured threshold to the slow-query log. The threshold defaults to 200 ms and is tunable via `metricsSlowQueryLogThresholdMs`. List slow queries through: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const queries = await compute.listDatabaseQueries({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const queries = await compute.listDatabaseQueries({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$queries = $compute->listDatabaseQueries(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +queries = compute.list_database_queries(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +queries = compute.list_database_queries(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var queries = await compute.ListDatabaseQueries(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final queries = await compute.listDatabaseQueries( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val queries = compute.listDatabaseQueries( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let queries = try await compute.listDatabaseQueries( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + queries, err := compute.ListDatabaseQueries("") + if err != nil { + panic(err) + } + _ = queries +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let queries = compute.list_database_queries("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//slow-queries ``` +{% /multicode %} The response includes the query text, average duration, call count, user, and the matching database. The Appwrite Console deduplicates by query shape so you see the worst offenders first. @@ -63,6 +620,209 @@ The response includes the query text, average duration, call count, user, and th To inspect the planner's choice for a specific query without running it against your production database, post the query to the explanation endpoint: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const explanation = await compute.createDatabaseQueryExplanation({ + databaseId: '', + query: 'SELECT * FROM users WHERE email = $1', + analyze: false, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const explanation = await compute.createDatabaseQueryExplanation({ + databaseId: '', + query: 'SELECT * FROM users WHERE email = $1', + analyze: false, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$explanation = $compute->createDatabaseQueryExplanation( + databaseId: '', + query: 'SELECT * FROM users WHERE email = $1', + analyze: false, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +explanation = compute.create_database_query_explanation( + database_id='', + query='SELECT * FROM users WHERE email = $1', + analyze=False, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +explanation = compute.create_database_query_explanation( + database_id: '', + query: 'SELECT * FROM users WHERE email = $1', + analyze: false, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var explanation = await compute.CreateDatabaseQueryExplanation( + databaseId: "", + query: "SELECT * FROM users WHERE email = $1", + analyze: false +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final explanation = await compute.createDatabaseQueryExplanation( + databaseId: '', + query: 'SELECT * FROM users WHERE email = \$1', + analyze: false, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val explanation = compute.createDatabaseQueryExplanation( + databaseId = "", + query = "SELECT * FROM users WHERE email = \$1", + analyze = false, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let explanation = try await compute.createDatabaseQueryExplanation( + databaseId: "", + query: "SELECT * FROM users WHERE email = $1", + analyze: false +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + explanation, err := compute.CreateDatabaseQueryExplanation("", + compute.WithCreateDatabaseQueryExplanationQuery("SELECT * FROM users WHERE email = $1"), + compute.WithCreateDatabaseQueryExplanationAnalyze(false), + ) + if err != nil { + panic(err) + } + _ = explanation +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let explanation = compute.create_database_query_explanation("") + .query("SELECT * FROM users WHERE email = $1") + .analyze(false) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X POST \ -H "X-Appwrite-Project: " \ @@ -74,6 +834,7 @@ curl -X POST \ }' \ https://.cloud.appwrite.io/v1/compute/databases//explanation ``` +{% /multicode %} | Engine | Underlying command | |------------|---------------------------------------------------------------| @@ -92,12 +853,205 @@ In addition to metrics and slow queries, the platform runs a periodic index-anal - **Unused indexes**, indexes that have not been read since the last reset - **Tuning recommendations**, engine config parameters that look out of range for the current specification +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const insights = await compute.getDatabaseInsights({ + databaseId: '', + period: '24h', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const insights = await compute.getDatabaseInsights({ + databaseId: '', + period: '24h', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$insights = $compute->getDatabaseInsights( + databaseId: '', + period: '24h', +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +insights = compute.get_database_insights( + database_id='', + period='24h', +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +insights = compute.get_database_insights( + database_id: '', + period: '24h', +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var insights = await compute.GetDatabaseInsights( + databaseId: "", + period: "24h" +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final insights = await compute.getDatabaseInsights( + databaseId: '', + period: '24h', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val insights = compute.getDatabaseInsights( + databaseId = "", + period = "24h", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let insights = try await compute.getDatabaseInsights( + databaseId: "", + period: "24h" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + insights, err := compute.GetDatabaseInsights("", + compute.WithGetDatabaseInsightsPeriod("24h"), + ) + if err != nil { + panic(err) + } + _ = insights +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let insights = compute.get_database_insights("") + .period("24h") + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ "https://.cloud.appwrite.io/v1/compute/databases//insights?period=24h" ``` +{% /multicode %} `period` accepts `1h`, `24h`, or `7d`. @@ -107,12 +1061,183 @@ Insights are recommendations, not auto-applied changes. The Console suggests the Database-level admin actions, credential rotation, IP allowlist changes, backup triggers, failover requests, are recorded in the standard project activity log: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const logs = await compute.listDatabaseLogs({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const logs = await compute.listDatabaseLogs({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$logs = $compute->listDatabaseLogs(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +logs = compute.list_database_logs(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +logs = compute.list_database_logs(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var logs = await compute.ListDatabaseLogs(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final logs = await compute.listDatabaseLogs( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val logs = compute.listDatabaseLogs( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let logs = try await compute.listDatabaseLogs( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + logs, err := compute.ListDatabaseLogs("") + if err != nil { + panic(err) + } + _ = logs +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let logs = compute.list_database_logs("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ "https://.cloud.appwrite.io/v1/compute/databases//logs" ``` +{% /multicode %} The response includes the actor, the action, the source IP, and the previous/next state for fields that changed. diff --git a/src/routes/docs/products/databases/dedicated/network/+page.markdoc b/src/routes/docs/products/databases/dedicated/network/+page.markdoc index c06f40edd76..da81edaf85d 100644 --- a/src/routes/docs/products/databases/dedicated/network/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/network/+page.markdoc @@ -37,6 +37,230 @@ To require mutual TLS (mTLS) where the client presents a certificate signed by y By default, a dedicated database is reachable from any IP. To restrict access, set the `networkIPAllowlist` field with a list of CIDR ranges: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + networkIPAllowlist: [ + '203.0.113.0/24', + '198.51.100.10/32', + ], +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + networkIPAllowlist: [ + '203.0.113.0/24', + '198.51.100.10/32', + ], +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabase( + databaseId: '', + networkIPAllowlist: [ + '203.0.113.0/24', + '198.51.100.10/32', + ], +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database( + database_id='', + network_ip_allowlist=[ + '203.0.113.0/24', + '198.51.100.10/32', + ], +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database( + database_id: '', + network_ip_allowlist: [ + '203.0.113.0/24', + '198.51.100.10/32', + ], +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabase( + databaseId: "", + networkIPAllowlist: new List { + "203.0.113.0/24", + "198.51.100.10/32" + } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabase( + databaseId: '', + networkIPAllowlist: [ + '203.0.113.0/24', + '198.51.100.10/32', + ], +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabase( + databaseId = "", + networkIPAllowlist = listOf( + "203.0.113.0/24", + "198.51.100.10/32", + ), +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabase( + databaseId: "", + networkIPAllowlist: [ + "203.0.113.0/24", + "198.51.100.10/32", + ] +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabase("", + compute.WithUpdateDatabaseNetworkIPAllowlist([]string{ + "203.0.113.0/24", + "198.51.100.10/32", + }), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database("") + .network_ip_allowlist(vec![ + "203.0.113.0/24".to_string(), + "198.51.100.10/32".to_string(), + ]) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ -H "X-Appwrite-Project: " \ @@ -50,6 +274,7 @@ curl -X PATCH \ }' \ https://.cloud.appwrite.io/v1/compute/databases/ ``` +{% /multicode %} The allowlist is enforced at the edge proxy. A connection from a non-allowlisted IP is dropped at TCP handshake, no banner, no error message. Up to 100 CIDR entries are accepted per database. @@ -59,12 +284,206 @@ Send an empty array to remove all entries and restore unrestricted access. Each specification has a hard ceiling on the number of simultaneous connections (see the [specifications page](/docs/products/databases/dedicated/specifications)). Use `networkMaxConnections` to set a per-database limit at or below that ceiling: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + networkMaxConnections: 500, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + networkMaxConnections: 500, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabase( + databaseId: '', + networkMaxConnections: 500, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database( + database_id='', + network_max_connections=500, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database( + database_id: '', + network_max_connections: 500, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabase( + databaseId: "", + networkMaxConnections: 500 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabase( + databaseId: '', + networkMaxConnections: 500, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabase( + databaseId = "", + networkMaxConnections = 500, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabase( + databaseId: "", + networkMaxConnections: 500 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabase("", + compute.WithUpdateDatabaseNetworkMaxConnections(500), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database("") + .network_max_connections(500) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ - ... \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ -d '{ "networkMaxConnections": 500 }' \ - ... + https://.cloud.appwrite.io/v1/compute/databases/ ``` +{% /multicode %} When the limit is reached, new connection attempts are rejected at the proxy with a clear error rather than being queued or held open. @@ -74,12 +493,206 @@ The pooler (if enabled) is counted as a single backend connection per pooled slo Idle connections are closed by the edge proxy after `networkIdleTimeoutSeconds` of inactivity. The default is 900 (15 minutes). A higher value keeps connection-heavy clients alive longer; a lower value reclaims unused slots faster. +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + networkIdleTimeoutSeconds: 600, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + networkIdleTimeoutSeconds: 600, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabase( + databaseId: '', + networkIdleTimeoutSeconds: 600, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database( + database_id='', + network_idle_timeout_seconds=600, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database( + database_id: '', + network_idle_timeout_seconds: 600, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabase( + databaseId: "", + networkIdleTimeoutSeconds: 600 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabase( + databaseId: '', + networkIdleTimeoutSeconds: 600, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabase( + databaseId = "", + networkIdleTimeoutSeconds = 600, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabase( + databaseId: "", + networkIdleTimeoutSeconds: 600 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabase("", + compute.WithUpdateDatabaseNetworkIdleTimeoutSeconds(600), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database("") + .network_idle_timeout_seconds(600) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ - ... \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ -d '{ "networkIdleTimeoutSeconds": 600 }' \ - ... + https://.cloud.appwrite.io/v1/compute/databases/ ``` +{% /multicode %} # Locking down access {% #lockdown %} diff --git a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc index 24541ce6f82..497f33684cd 100644 --- a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc @@ -35,6 +35,230 @@ If your application uses features that are tied to a single backend connection, The pooler is enabled by default when a paid dedicated database is provisioned. To disable it or change its settings, use the pooler endpoint: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabasePooler({ + databaseId: '', + enabled: true, + poolMode: 'transaction', + maxClientConn: 200, + defaultPoolSize: 25, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabasePooler({ + databaseId: '', + enabled: true, + poolMode: 'transaction', + maxClientConn: 200, + defaultPoolSize: 25, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabasePooler( + databaseId: '', + enabled: true, + poolMode: 'transaction', + maxClientConn: 200, + defaultPoolSize: 25, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database_pooler( + database_id='', + enabled=True, + pool_mode='transaction', + max_client_conn=200, + default_pool_size=25, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database_pooler( + database_id: '', + enabled: true, + pool_mode: 'transaction', + max_client_conn: 200, + default_pool_size: 25, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabasePooler( + databaseId: "", + enabled: true, + poolMode: "transaction", + maxClientConn: 200, + defaultPoolSize: 25 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabasePooler( + databaseId: '', + enabled: true, + poolMode: 'transaction', + maxClientConn: 200, + defaultPoolSize: 25, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabasePooler( + databaseId = "", + enabled = true, + poolMode = "transaction", + maxClientConn = 200, + defaultPoolSize = 25, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabasePooler( + databaseId: "", + enabled: true, + poolMode: "transaction", + maxClientConn: 200, + defaultPoolSize: 25 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabasePooler("", + compute.WithUpdateDatabasePoolerEnabled(true), + compute.WithUpdateDatabasePoolerPoolMode("transaction"), + compute.WithUpdateDatabasePoolerMaxClientConn(200), + compute.WithUpdateDatabasePoolerDefaultPoolSize(25), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database_pooler("") + .enabled(true) + .pool_mode("transaction") + .max_client_conn(200) + .default_pool_size(25) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ -H "X-Appwrite-Project: " \ @@ -48,15 +272,187 @@ curl -X PATCH \ }' \ https://.cloud.appwrite.io/v1/compute/databases//pooler ``` +{% /multicode %} Or to read the current configuration: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const pooler = await compute.getDatabasePooler({ + databaseId: '', +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const pooler = await compute.getDatabasePooler({ + databaseId: '', +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$pooler = $compute->getDatabasePooler(databaseId: ''); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +pooler = compute.get_database_pooler(database_id='') +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +pooler = compute.get_database_pooler(database_id: '') +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var pooler = await compute.GetDatabasePooler(databaseId: ""); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final pooler = await compute.getDatabasePooler( + databaseId: '', +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val pooler = compute.getDatabasePooler( + databaseId = "", +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let pooler = try await compute.getDatabasePooler( + databaseId: "" +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + pooler, err := compute.GetDatabasePooler("") + if err != nil { + panic(err) + } + _ = pooler +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + let pooler = compute.get_database_pooler("").await?; + + Ok(()) +} +``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ https://.cloud.appwrite.io/v1/compute/databases//pooler ``` +{% /multicode %} Default settings: @@ -102,6 +498,197 @@ This gives you read scale on aggregations and dashboard queries while keeping wr Disable read/write splitting if your workload requires every read to see the most recent write, in async replication mode there's a small lag window where a read on a replica may not yet see a just-committed write: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabasePooler({ + databaseId: '', + readWriteSplitting: false, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabasePooler({ + databaseId: '', + readWriteSplitting: false, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabasePooler( + databaseId: '', + readWriteSplitting: false, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database_pooler( + database_id='', + read_write_splitting=False, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database_pooler( + database_id: '', + read_write_splitting: false, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabasePooler( + databaseId: "", + readWriteSplitting: false +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabasePooler( + databaseId: '', + readWriteSplitting: false, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabasePooler( + databaseId = "", + readWriteSplitting = false, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabasePooler( + databaseId: "", + readWriteSplitting: false +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabasePooler("", + compute.WithUpdateDatabasePoolerReadWriteSplitting(false), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database_pooler("") + .read_write_splitting(false) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ -H "X-Appwrite-Project: " \ @@ -109,6 +696,7 @@ curl -X PATCH \ -d '{ "readWriteSplitting": false }' \ https://.cloud.appwrite.io/v1/compute/databases//pooler ``` +{% /multicode %} # Sizing {% #sizing %} diff --git a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc index 7e3c15fbdd4..edddf9037cf 100644 --- a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc @@ -53,14 +53,16 @@ Storage overage is computed on the snapshot at the end of the billing period. Ba The following features are billed as a percentage of the underlying specification price, pro-rated by active days in the billing period: -| Feature | Rate | Example on a $100/mo specification | -|-----------------------------|---------------------------------------|------------------------------------| -| High availability replicas | 75% of specification per replica | $75/mo per replica | -| Cross-region replicas | 75% of specification per replica | $75/mo per replica | -| Point-in-time recovery | 20% of specification | $20/mo | -| PostgreSQL extensions | Free | $0 | - -The cross-region **standby** that backs the [cross-region failover](/docs/products/databases/dedicated/high-availability) feature is a cross-region replica configured for automatic failover, so it bills the same way as any other cross-region replica. +| Feature | Rate | Example on a $100/mo specification | +|----------------------------------------|---------------------------------------|------------------------------------| +| High availability replicas | 75% of specification per replica | $75/mo per replica | +| Cross-region replicas *(coming soon)* | 75% of specification per replica | $75/mo per replica | +| Point-in-time recovery | 20% of specification | $20/mo | +| PostgreSQL extensions | Free | $0 | + +{% info title="Cross-region replication is coming soon" %} +The cross-region replica and cross-region standby that back the [cross-region failover](/docs/products/databases/dedicated/high-availability#cross-region-failover) feature are previewed here for context; they will ship in a future release. The standby is a cross-region replica configured for automatic failover and will bill the same way as any other cross-region replica. +{% /info %} # Resize behaviour {% #resize %} diff --git a/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc b/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc index 91dbedc65ab..8e10991dbb1 100644 --- a/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/sql-api/+page.markdoc @@ -12,6 +12,241 @@ The SQL API is opt-in per database and supports all four engines. The SQL API is disabled by default. Enable it through the database update endpoint: +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + sqlApiEnabled: true, + sqlApiAllowedStatements: ['SELECT'], + sqlApiMaxRows: 10000, + sqlApiMaxBytes: 10485760, + sqlApiTimeoutSeconds: 30, +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +await compute.updateDatabase({ + databaseId: '', + sqlApiEnabled: true, + sqlApiAllowedStatements: ['SELECT'], + sqlApiMaxRows: 10000, + sqlApiMaxBytes: 10485760, + sqlApiTimeoutSeconds: 30, +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$compute->updateDatabase( + databaseId: '', + sqlApiEnabled: true, + sqlApiAllowedStatements: ['SELECT'], + sqlApiMaxRows: 10000, + sqlApiMaxBytes: 10485760, + sqlApiTimeoutSeconds: 30, +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +compute.update_database( + database_id='', + sql_api_enabled=True, + sql_api_allowed_statements=['SELECT'], + sql_api_max_rows=10000, + sql_api_max_bytes=10485760, + sql_api_timeout_seconds=30, +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +compute.update_database( + database_id: '', + sql_api_enabled: true, + sql_api_allowed_statements: ['SELECT'], + sql_api_max_rows: 10000, + sql_api_max_bytes: 10485760, + sql_api_timeout_seconds: 30, +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +await compute.UpdateDatabase( + databaseId: "", + sqlApiEnabled: true, + sqlApiAllowedStatements: new List { "SELECT" }, + sqlApiMaxRows: 10000, + sqlApiMaxBytes: 10485760, + sqlApiTimeoutSeconds: 30 +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +await compute.updateDatabase( + databaseId: '', + sqlApiEnabled: true, + sqlApiAllowedStatements: ['SELECT'], + sqlApiMaxRows: 10000, + sqlApiMaxBytes: 10485760, + sqlApiTimeoutSeconds: 30, +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +compute.updateDatabase( + databaseId = "", + sqlApiEnabled = true, + sqlApiAllowedStatements = listOf("SELECT"), + sqlApiMaxRows = 10000, + sqlApiMaxBytes = 10485760, + sqlApiTimeoutSeconds = 30, +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +_ = try await compute.updateDatabase( + databaseId: "", + sqlApiEnabled: true, + sqlApiAllowedStatements: ["SELECT"], + sqlApiMaxRows: 10000, + sqlApiMaxBytes: 10485760, + sqlApiTimeoutSeconds: 30 +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + _, err := compute.UpdateDatabase("", + compute.WithUpdateDatabaseSqlApiEnabled(true), + compute.WithUpdateDatabaseSqlApiAllowedStatements([]string{"SELECT"}), + compute.WithUpdateDatabaseSqlApiMaxRows(10000), + compute.WithUpdateDatabaseSqlApiMaxBytes(10485760), + compute.WithUpdateDatabaseSqlApiTimeoutSeconds(30), + ) + if err != nil { + panic(err) + } +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.update_database("") + .sql_api_enabled(true) + .sql_api_allowed_statements(vec!["SELECT".to_string()]) + .sql_api_max_rows(10000) + .sql_api_max_bytes(10485760) + .sql_api_timeout_seconds(30) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X PATCH \ -H "X-Appwrite-Project: " \ @@ -26,6 +261,7 @@ curl -X PATCH \ }' \ https://.cloud.appwrite.io/v1/compute/databases/ ``` +{% /multicode %} The defaults above are safe to start with: `SELECT`-only, 10k rows, 10 MB payload, 30-second timeout. @@ -43,6 +279,209 @@ Only `SELECT`, `INSERT`, `UPDATE`, and `DELETE` are ever accepted. Schema-alteri # Execute a statement {% #execute %} +{% multicode %} +```server-nodejs +import { Client, Compute } from 'node-appwrite'; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const execution = await compute.createDatabaseExecution({ + databaseId: '', + sql: 'SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2', + bindings: ['2026-05-01T00:00:00Z', 50], +}); +``` +```server-deno +import { Client, Compute } from "npm:node-appwrite"; + +const client = new Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +const compute = new Compute(client); + +const execution = await compute.createDatabaseExecution({ + databaseId: '', + sql: 'SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2', + bindings: ['2026-05-01T00:00:00Z', 50], +}); +``` +```server-php +setEndpoint('https://.cloud.appwrite.io/v1') + ->setProject('') + ->setKey(''); + +$compute = new Compute($client); + +$execution = $compute->createDatabaseExecution( + databaseId: '', + sql: 'SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2', + bindings: ['2026-05-01T00:00:00Z', 50], +); +``` +```server-python +from appwrite.client import Client +from appwrite.services.compute import Compute + +client = Client() +client.set_endpoint('https://.cloud.appwrite.io/v1') +client.set_project('') +client.set_key('') + +compute = Compute(client) + +execution = compute.create_database_execution( + database_id='', + sql='SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2', + bindings=['2026-05-01T00:00:00Z', 50], +) +``` +```server-ruby +require 'appwrite' + +include Appwrite + +client = Client.new + .set_endpoint('https://.cloud.appwrite.io/v1') + .set_project('') + .set_key('') + +compute = Compute.new(client) + +execution = compute.create_database_execution( + database_id: '', + sql: 'SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2', + bindings: ['2026-05-01T00:00:00Z', 50], +) +``` +```server-dotnet +using Appwrite; +using Appwrite.Services; + +Client client = new Client() + .SetEndPoint("https://.cloud.appwrite.io/v1") + .SetProject("") + .SetKey(""); + +Compute compute = new Compute(client); + +var execution = await compute.CreateDatabaseExecution( + databaseId: "", + sql: "SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2", + bindings: new List { "2026-05-01T00:00:00Z", 50 } +); +``` +```server-dart +import 'package:dart_appwrite/dart_appwrite.dart'; + +Client client = Client() + .setEndpoint('https://.cloud.appwrite.io/v1') + .setProject('') + .setKey(''); + +Compute compute = Compute(client); + +final execution = await compute.createDatabaseExecution( + databaseId: '', + sql: 'SELECT id, email FROM users WHERE created_at > \$1 ORDER BY created_at DESC LIMIT \$2', + bindings: ['2026-05-01T00:00:00Z', 50], +); +``` +```server-kotlin +import io.appwrite.Client +import io.appwrite.services.Compute + +val client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +val compute = Compute(client) + +val execution = compute.createDatabaseExecution( + databaseId = "", + sql = "SELECT id, email FROM users WHERE created_at > \$1 ORDER BY created_at DESC LIMIT \$2", + bindings = listOf("2026-05-01T00:00:00Z", 50), +) +``` +```server-swift +import Appwrite + +let client = Client() + .setEndpoint("https://.cloud.appwrite.io/v1") + .setProject("") + .setKey("") + +let compute = Compute(client) + +let execution = try await compute.createDatabaseExecution( + databaseId: "", + sql: "SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2", + bindings: ["2026-05-01T00:00:00Z", 50] +) +``` +```server-go +package main + +import ( + "github.com/appwrite/sdk-for-go/appwrite" +) + +func main() { + client := appwrite.NewClient( + appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), + appwrite.WithProject(""), + appwrite.WithKey(""), + ) + + compute := appwrite.NewCompute(client) + + execution, err := compute.CreateDatabaseExecution("", + compute.WithCreateDatabaseExecutionSql("SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2"), + compute.WithCreateDatabaseExecutionBindings([]interface{}{"2026-05-01T00:00:00Z", 50}), + ) + if err != nil { + panic(err) + } + _ = execution +} +``` +```server-rust +use appwrite::Client; +use appwrite::services::compute::Compute; + +#[tokio::main] +async fn main() -> Result<(), Box> { + let client = Client::new() + .set_endpoint("https://.cloud.appwrite.io/v1") + .set_project("") + .set_key(""); + + let compute = Compute::new(&client); + + compute.create_database_execution("") + .sql("SELECT id, email FROM users WHERE created_at > $1 ORDER BY created_at DESC LIMIT $2") + .bindings(serde_json::json!(["2026-05-01T00:00:00Z", 50])) + .send() + .await?; + + Ok(()) +} +``` ```bash curl -X POST \ -H "X-Appwrite-Project: " \ @@ -54,6 +493,7 @@ curl -X POST \ }' \ https://.cloud.appwrite.io/v1/compute/databases//execution ``` +{% /multicode %} Response: From 820548c28a434c431c4606bba816ac652776904e Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 16:23:05 +1200 Subject: [PATCH 10/15] docs(dedicated): remove duplicate list-branches multicode block The branches page had two adjacent identical {% multicode %} blocks calling listDatabaseBranches. Drop the one in the 'Create a branch' section in favour of a sentence that links to the canonical 'List branches' section below. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../dedicated/branches/+page.markdoc | 180 +----------------- 1 file changed, 1 insertion(+), 179 deletions(-) diff --git a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc index 1c04c3f25aa..bfecd565141 100644 --- a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc @@ -283,185 +283,7 @@ The branch endpoint is exposed as a separate hostname: db---..appwrite.network ``` -Connect to it with the engine's standard CLI, same as the parent. Credentials for the branch are retrieved by listing branches and locating yours by `branchId`: - -{% multicode %} -```server-nodejs -import { Client, Compute } from 'node-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -const compute = new Compute(client); - -const branches = await compute.listDatabaseBranches({ - databaseId: '', -}); -``` -```server-deno -import { Client, Compute } from "npm:node-appwrite"; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -const compute = new Compute(client); - -const branches = await compute.listDatabaseBranches({ - databaseId: '', -}); -``` -```server-php -setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setKey(''); - -$compute = new Compute($client); - -$branches = $compute->listDatabaseBranches(databaseId: ''); -``` -```server-python -from appwrite.client import Client -from appwrite.services.compute import Compute - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_key('') - -compute = Compute(client) - -branches = compute.list_database_branches(database_id='') -``` -```server-ruby -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_key('') - -compute = Compute.new(client) - -branches = compute.list_database_branches(database_id: '') -``` -```server-dotnet -using Appwrite; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetKey(""); - -Compute compute = new Compute(client); - -var branches = await compute.ListDatabaseBranches(databaseId: ""); -``` -```server-dart -import 'package:dart_appwrite/dart_appwrite.dart'; - -Client client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -Compute compute = Compute(client); - -final branches = await compute.listDatabaseBranches( - databaseId: '', -); -``` -```server-kotlin -import io.appwrite.Client -import io.appwrite.services.Compute - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") - -val compute = Compute(client) - -val branches = compute.listDatabaseBranches( - databaseId = "", -) -``` -```server-swift -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") - -let compute = Compute(client) - -let branches = try await compute.listDatabaseBranches( - databaseId: "" -) -``` -```server-go -package main - -import ( - "github.com/appwrite/sdk-for-go/appwrite" -) - -func main() { - client := appwrite.NewClient( - appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), - appwrite.WithProject(""), - appwrite.WithKey(""), - ) - - compute := appwrite.NewCompute(client) - - branches, err := compute.ListDatabaseBranches("") - if err != nil { - panic(err) - } - _ = branches -} -``` -```server-rust -use appwrite::Client; -use appwrite::services::compute::Compute; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let client = Client::new() - .set_endpoint("https://.cloud.appwrite.io/v1") - .set_project("") - .set_key(""); - - let compute = Compute::new(&client); - - let branches = compute.list_database_branches("").await?; - - Ok(()) -} -``` -```bash -curl -X GET \ - -H "X-Appwrite-Project: " \ - -H "X-Appwrite-Key: " \ - https://.cloud.appwrite.io/v1/compute/databases//branches -``` -{% /multicode %} +Connect to it with the engine's standard CLI, same as the parent. The branch endpoint and credentials are returned alongside every entry of the [list branches](#list) response, indexed by `branchId`. # List branches {% #list %} From e03ba65310537c2b4b217c2ad7151785d065f814 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 16:56:39 +1200 Subject: [PATCH 11/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/routes/docs/products/databases/dedicated/+page.markdoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/routes/docs/products/databases/dedicated/+page.markdoc b/src/routes/docs/products/databases/dedicated/+page.markdoc index 58d05587b1d..6fe71a438c4 100644 --- a/src/routes/docs/products/databases/dedicated/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/+page.markdoc @@ -81,7 +81,7 @@ Per-database connection pooler with automatic read/write split when HA is enable Run parameterised SQL over HTTP without opening a TCP connection. Useful from edge runtimes. {% /cards_item %} {% cards_item href="/docs/products/databases/dedicated/network" title="Network" %} -TLS by default, optional IP allowlists, and a private hostname per database. +TLS by default, optional IP allowlists, and a dedicated hostname per database. {% /cards_item %} {% cards_item href="/docs/products/databases/dedicated/monitoring" title="Monitoring & insights" %} CPU, memory, IOPS, slow queries, and index recommendations, pushed to the Console live. From de945e19a9575e44dadcca4ad8014ef290db7bba Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 16:57:05 +1200 Subject: [PATCH 12/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../docs/products/databases/dedicated/branches/+page.markdoc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc index bfecd565141..9a0eda4a6ab 100644 --- a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc @@ -691,6 +691,7 @@ jobs: curl -X POST \ -H "X-Appwrite-Project: $PROJECT_ID" \ -H "X-Appwrite-Key: $API_KEY" \ + -H "Content-Type: application/json" \ -d '{ "branchId": "pr-${{ github.event.number }}", "name": "PR #${{ github.event.number }}" }' \ https://fra.cloud.appwrite.io/v1/compute/databases/$DB_ID/branches || true From 7614f030fb3632bcd3592e666693ce608c9573c4 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Thu, 21 May 2026 16:57:14 +1200 Subject: [PATCH 13/15] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../docs/products/databases/dedicated/pooler/+page.markdoc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc index 497f33684cd..d87609b9737 100644 --- a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc @@ -693,6 +693,7 @@ async fn main() -> Result<(), Box> { curl -X PATCH \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ -d '{ "readWriteSplitting": false }' \ https://.cloud.appwrite.io/v1/compute/databases//pooler ``` From ba7e52ca989791085cd87f793415e135f57b0aa5 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 22 May 2026 15:41:12 +1200 Subject: [PATCH 14/15] docs: update dedicated database rollout docs --- .../+page.markdoc | 14 +- .../changelog/(entries)/2026-05-21.markdoc | 4 +- .../docs/products/databases/+page.markdoc | 31 +- .../databases/dedicated/+page.markdoc | 20 +- .../databases/dedicated/backups/+page.markdoc | 41 +- .../dedicated/branches/+page.markdoc | 4 + .../databases/dedicated/connect/+page.markdoc | 14 +- .../dedicated/high-availability/+page.markdoc | 2 +- .../dedicated/monitoring/+page.markdoc | 425 ++---------------- .../databases/dedicated/pooler/+page.markdoc | 5 +- .../dedicated/specifications/+page.markdoc | 56 ++- 11 files changed, 184 insertions(+), 432 deletions(-) diff --git a/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc b/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc index a61d25b4fcf..425d11140a3 100644 --- a/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc +++ b/src/routes/blog/post/announcing-dedicated-databases/+page.markdoc @@ -1,7 +1,7 @@ --- layout: post title: "Announcing Dedicated databases: managed PostgreSQL, MySQL, MariaDB, and MongoDB on Appwrite" -description: Run production database engines on Appwrite Cloud. PostgreSQL, MySQL, MariaDB, and MongoDB with high availability, point-in-time recovery, branching, extensions, and a connection pooler, billed by the hour from $15/mo. +description: Run production database engines on Appwrite Cloud. PostgreSQL, MySQL, MariaDB, and MongoDB with high availability, point-in-time recovery, branching, extensions, and a connection pooler, billed by the hour from $13/mo. date: 2026-05-21 cover: /images/blog/announcing-dedicated-databases/cover.avif timeToRead: 8 @@ -11,7 +11,7 @@ featured: true callToAction: true faqs: - question: "How is this different from Appwrite Databases?" - answer: "Appwrite Databases is the document store, a curated TablesDB API with rows, columns, queries, and permissions, accessible through the Appwrite SDKs. Dedicated databases are real PostgreSQL, MySQL, MariaDB, or MongoDB engines that you connect to with their native drivers. The two products are independent, many teams will use both. Use the document store for app data that benefits from Appwrite's permission model; use a dedicated database when you need raw SQL, extensions like PostGIS or pgvector, or compatibility with an existing tool." + answer: "Appwrite Databases is the serverless TablesDB API with rows, columns, queries, and permissions, accessible through the Appwrite SDKs. Dedicated databases are raw PostgreSQL, MySQL, MariaDB, or MongoDB engines that you connect to with their native drivers. The products are independent, many teams will use both. Use TablesDB for app data that benefits from Appwrite's permission model; use a dedicated database when you need raw SQL, extensions like PostGIS or pgvector, or compatibility with an existing tool." - question: "Which plans include dedicated databases?" answer: "The Pro plan and higher. A payment method must be attached to the team. The Free specification (shared, scale-to-zero) is included with Pro at no extra cost; paid specifications are billed by the hour against the per-specification monthly price." - question: "What engines and versions are supported?" @@ -30,11 +30,11 @@ faqs: Today we're announcing **Dedicated databases** on Appwrite Cloud. You can now run managed PostgreSQL, MySQL, MariaDB, and MongoDB engines next to your existing Appwrite Functions, Sites, and Storage, with the same per-region deployment, the same single payment relationship, and the same Console. -Dedicated databases are a separate product from [Appwrite Databases](/docs/products/databases), our document store with rows, columns, queries, and built-in permissions. The two products solve different problems and many teams will use both. Use the document store when you want app data managed through the Appwrite SDKs and the platform's permission model. Use a dedicated database when you need raw SQL, the PostgreSQL extension ecosystem, your existing ORM, or wire-compatibility with another tool. +Dedicated databases are a separate product from Appwrite's serverless data APIs, including [TablesDB](/docs/products/databases), DocumentsDB, and VectorsDB. Those products give you SDK-first APIs, platform permissions, and shared/serverless scaling. Dedicated databases give you raw engines for SQL, the PostgreSQL extension ecosystem, your existing ORM, or wire-compatibility with another tool. # What's in the box -Each dedicated database is a real database engine running on its own dedicated compute, with its own storage, its own credentials, and its own public hostname. The engine is yours; Appwrite manages everything around it. +Each paid dedicated database is a real database engine running on its own dedicated compute, with its own storage, its own credentials, and its own public hostname. The free specification uses shared compute that scales to zero. In both cases, Appwrite manages the surrounding platform. **Four engines:** @@ -43,7 +43,7 @@ Each dedicated database is a real database engine running on its own dedicated c - **MariaDB 10.11 / 11.4**, with the same replication and pooling story as MySQL. - **MongoDB 7.0 / 8.0**, with replica-set high availability via Raft. -**Eight paid specifications** from `s-1vcpu-1gb` at $15/month to `s-8vcpu-64gb` at $860/month, plus a free shared specification included with the Pro plan. Specifications are dedicated CPU, not burstable; they're 10–20% below the equivalent tier on Supabase, RDS, and PlanetScale at the same memory level. Every paid specification gets dedicated compute that never spins down, with included storage (10 GB to 3 TB) and included bandwidth (50 GB to 10 TB) baked into the monthly price. +**Eight paid specifications** from `s-1vcpu-1gb` at $13/month to `s-8vcpu-64gb` at $699/month, plus a free shared, scale-to-zero specification included with the Pro plan. Every paid specification gets dedicated CPU that never spins down, with included storage (10 GB to 3 TB) and included bandwidth (50 GB to 10 TB) baked into the monthly price. **Six regions** available today, Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto, with Amsterdam, London, and Bangalore in the rollout queue. A dedicated database lives in the same region as the project that owns it, so it sits next to your Functions, Sites, and Storage on the regional private network with no extra configuration. @@ -57,7 +57,7 @@ Enable HA with one update call and the platform provisions the replicas, places When the primary stays unhealthy across two 3-second probes, Appwrite takes a 15-second distributed lock, selects the lowest-lag replica, promotes it in place (no restart for SQL engines), runs a write-verification probe, drains connections from the old primary, and emits a `failover.completed` event to your webhooks, realtime channels, and functions. There's a 60-second cooldown so transient infrastructure events don't cause failover flapping. -Up to five replicas per database. Replicas are billed at 75% of the underlying specification per replica. +Up to five replicas per database. Regional HA replicas are billed at 50% of the underlying specification per replica. ## Backups and point-in-time recovery @@ -77,7 +77,7 @@ Branches are billed like a standalone dedicated database against their own speci ## Connection pooler -Each dedicated database can run its own pooler, PostgreSQL on port 6432, MySQL/MariaDB on port 6033. The pooler is enabled by default on provision and is configurable per database, pool mode (`transaction` / `session` / `statement`), max client connections, default backend pool size. +Each dedicated database can run its own pooler, PostgreSQL on port 6432, MySQL/MariaDB on port 6033. The pooler is enabled by default on provision and is configurable per database, pool mode (`transaction` / `session`), max client connections, default backend pool size. When HA is enabled, the pooler also handles **read/write splitting**: a query parser inspects each statement, pins transactions and writes to the primary, and routes ambient `SELECT`s to replicas. The application driver sees a single hostname and port; everything else is transparent. No SDK changes. diff --git a/src/routes/changelog/(entries)/2026-05-21.markdoc b/src/routes/changelog/(entries)/2026-05-21.markdoc index cfec3f9878b..5edd5619b5f 100644 --- a/src/routes/changelog/(entries)/2026-05-21.markdoc +++ b/src/routes/changelog/(entries)/2026-05-21.markdoc @@ -5,9 +5,9 @@ date: 2026-05-21 cover: /images/blog/announcing-dedicated-databases/cover.avif --- -Appwrite Cloud now supports [**Dedicated databases**](/docs/products/databases/dedicated), managed PostgreSQL, MySQL, MariaDB, and MongoDB engines running next to your existing Functions, Sites, and Storage. Pick an engine (Postgres 17/18, MySQL 8.0/8.4, MariaDB 10.11/11.4, MongoDB 7.0/8.0), pick a region (Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto), pick a specification from `s-1vcpu-1gb` at $15/mo to `s-8vcpu-64gb` at $860/mo, and connect with `psql`, `mysql`, `mongosh`, or any standard driver. +Appwrite Cloud now supports [**Dedicated databases**](/docs/products/databases/dedicated), managed PostgreSQL, MySQL, MariaDB, and MongoDB engines running next to your existing Functions, Sites, and Storage. Pick an engine (Postgres 17/18, MySQL 8.0/8.4, MariaDB 10.11/11.4, MongoDB 7.0/8.0), pick a region (Frankfurt, New York, San Francisco, Singapore, Sydney, Toronto), pick a specification from `s-1vcpu-1gb` at $13/mo to `s-8vcpu-64gb` at $699/mo, and connect with `psql`, `mysql`, `mongosh`, or any standard driver. -High availability, point-in-time recovery, branching, PostgreSQL extensions, connection pooling with read/write split, and a managed SQL API over HTTPS are all available out of the box. Dedicated databases are billed by the hour and pro-rated by active days; the Pro plan includes a free, scale-to-zero shared specification at no extra cost. +High availability, point-in-time recovery, branching, PostgreSQL extensions, connection pooling with read/write split, and a managed SQL API over HTTPS are all available out of the box. Paid specifications are billed by the hour and pro-rated by active days; the Pro plan includes a free shared specification that scales to zero at no extra cost. {% arrow_link href="/blog/post/announcing-dedicated-databases" %} Read the announcement diff --git a/src/routes/docs/products/databases/+page.markdoc b/src/routes/docs/products/databases/+page.markdoc index 0d385e3ebfb..f4288a23295 100644 --- a/src/routes/docs/products/databases/+page.markdoc +++ b/src/routes/docs/products/databases/+page.markdoc @@ -1,23 +1,36 @@ --- layout: article title: Databases -description: Store and query structured data with Appwrite Databases. Databases provide performant and scalable storage for your application, business, and user data. +description: Choose the right Appwrite database product for structured app data, documents, vectors, or raw PostgreSQL, MySQL, MariaDB, and MongoDB engines. --- -Appwrite Databases let you store and query structured data. -Databases provide high-performance and scalable data storage for your key application, business, and user data. +Appwrite gives you several ways to store and query data. Use the serverless Appwrite data products when you want SDK-first APIs, permissions, and platform-managed scaling. Use [Dedicated databases](/docs/products/databases/dedicated) when you need a raw PostgreSQL, MySQL, MariaDB, or MongoDB engine with native drivers. {% info title="Looking for file storage?" %} Databases store data, if you need to store files like images, PDFs or videos, use [Appwrite Storage](/docs/products/storage). {% /info %} -You can organize data into databases, tables, and rows. You can also paginate, order, and query rows. -For complex business logic, Appwrite supports relationships to help you model your data. +# Choose a database product {% #choose %} + +| Product | Best for | Data model | Access pattern | Infrastructure | +|---------|----------|------------|----------------|----------------| +| **TablesDB** | Application records, relational-style app data, permissions, transactions | Databases, tables, rows, columns, relationships | Appwrite SDKs and REST APIs | Serverless shared Appwrite platform | +| **DocumentsDB** | Flexible JSON-like documents and document workflows | Documents and collections | Appwrite SDKs and REST APIs | Serverless shared Appwrite platform | +| **VectorsDB** | Embeddings, semantic search, and AI retrieval | Vectors with metadata | Appwrite SDKs and REST APIs | Serverless shared Appwrite platform | +| **Dedicated databases** | Existing database tools, raw SQL, native MongoDB, ORMs, migrations, extensions | PostgreSQL, MySQL, MariaDB, MongoDB | Native drivers like `psql`, `mysql`, `mongosh`, ORMs, and engine tools | Dedicated raw engine, with a free shared scale-to-zero specification on Pro | + +# TablesDB {% #tablesdb %} + +The main Databases documentation covers **TablesDB**, Appwrite's serverless structured data API. You can organize data into databases, tables, and rows. You can paginate, order, query rows, define relationships, and use Appwrite permissions to control access. {% arrow_link href="/docs/products/databases/quick-start" %} -Quick start +Start with TablesDB {% /arrow_link %} -{% info title="Need a managed PostgreSQL, MySQL, MariaDB, or MongoDB?" %} -Appwrite also offers [Dedicated databases](/docs/products/databases/dedicated) — managed database engines you connect to with `psql`, `mysql`, `mongosh`, or any standard driver. They're a separate product from the document store above. -{% /info %} \ No newline at end of file +# Raw database engines {% #dedicated %} + +Dedicated databases are separate from TablesDB, DocumentsDB, and VectorsDB. They give you managed PostgreSQL, MySQL, MariaDB, or MongoDB engines that you connect to with native tools and drivers. + +{% arrow_link href="/docs/products/databases/dedicated" %} +Explore Dedicated databases +{% /arrow_link %} diff --git a/src/routes/docs/products/databases/dedicated/+page.markdoc b/src/routes/docs/products/databases/dedicated/+page.markdoc index 6fe71a438c4..e3a016b29bc 100644 --- a/src/routes/docs/products/databases/dedicated/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/+page.markdoc @@ -4,12 +4,12 @@ title: Dedicated databases description: Run managed PostgreSQL, MySQL, MariaDB, and MongoDB databases on Appwrite. Production-ready compute, storage, backups, high availability, and global regions, billed by the hour. --- -Appwrite **Dedicated databases** lets you run managed PostgreSQL, MySQL, MariaDB, and MongoDB instances on Appwrite Cloud. You pick the engine and the compute size; Appwrite provisions a real database in your project's region, with its own storage, networking, and credentials, and exposes it to your application over the public internet or your own private network. +Appwrite **Dedicated databases** lets you run managed PostgreSQL, MySQL, MariaDB, and MongoDB instances on Appwrite Cloud. You pick the engine and compute size; Appwrite provisions a raw database engine in your project's region, with its own storage, networking, and credentials, and exposes it to your application over the public internet or your own private network. -Dedicated databases are different from the [Appwrite Databases](/docs/products/databases) document store. Where the document store gives you a curated `TablesDB` API with rows, queries, and SDK helpers, dedicated databases give you the raw engine, you connect with `psql`, `mysql`, `mongosh`, or any driver of your choice and use the full feature set of the underlying database. +Dedicated databases are different from Appwrite's serverless/shared data products: TablesDB for tables and rows, DocumentsDB for document workflows, and VectorsDB for embeddings and semantic search. Those products are accessed through Appwrite SDKs and platform APIs. Dedicated databases give you the raw engine; you connect with `psql`, `mysql`, `mongosh`, or any driver of your choice and use the feature set of the underlying database. -{% info title="Looking for the document store?" %} -If you want to query data through Appwrite SDKs with rows, columns, and built-in permissions, you want [Appwrite Databases](/docs/products/databases). Dedicated databases are for teams who want a managed PostgreSQL, MySQL, MariaDB, or MongoDB engine they can talk to directly with their own ORM, migrations, and drivers. +{% info title="Looking for serverless Appwrite data APIs?" %} +If you want Appwrite SDKs, platform permissions, and serverless/shared scaling for app data, start with [Appwrite Databases](/docs/products/databases). Dedicated databases are for teams who want a managed PostgreSQL, MySQL, MariaDB, or MongoDB engine they can talk to directly with their own ORM, migrations, and drivers. {% /info %} # Supported engines {% #engines %} @@ -31,12 +31,12 @@ A database is either **shared** or **dedicated**: | Type | Lifecycle | Resources | Use case | |---------------|------------------------------------------|---------------------------------|---------------------------| -| **Shared** | Scales to zero after 15 minutes idle | 0.125 CPU, 128 MB RAM, 1 GB disk | Development, low traffic | -| **Dedicated** | Always-on, with optional HA replicas | Any specification | Production workloads | +| **Shared** | Scales to zero after 15 minutes idle | 0.125 CPU, 128 MB RAM, 1 GB disk | Development, previews, low traffic | +| **Dedicated** | Always-on, with optional HA replicas | Any paid specification | Production workloads | -Shared databases are included with the Pro plan and use the **Free** specification. On the next connection after a spin-down, the database cold-starts in a few seconds, the underlying storage is preserved between spin-downs, so no data is lost. +Shared databases use the **Free** specification included with the Pro plan. They are not always-on dedicated compute: the engine runs on shared capacity, scales to zero after an idle period, and cold-starts on the next connection. The underlying storage is preserved between spin-downs, so no data is lost. -Dedicated databases are billed per hour against one of eight paid specifications and never spin down. They also unlock features that the shared specification does not have access to: high availability, point-in-time recovery, custom backup policies, storage autoscaling, IP allowlists, and connection pooling. +Paid dedicated databases are billed per hour against one of eight paid specifications and never spin down. They also unlock features that the shared specification does not have access to: high availability, point-in-time recovery, configurable backup schedules, storage autoscaling, IP allowlists, and connection pooling. # Regions {% #regions %} @@ -57,7 +57,7 @@ Each region is independent, data does not leave the region unless you explicitly {% cards %} {% cards_item href="/docs/products/databases/dedicated/specifications" title="Specifications" %} -Eight paid specifications from $15 to $860 per month, plus a free shared tier on Pro. +Eight paid specifications from $13 to $699 per month, plus a free shared specification on Pro. {% /cards_item %} {% cards_item href="/docs/products/databases/dedicated/connect" title="Connect" %} Connect with `psql`, `mysql`, `mongosh`, or any driver. Credentials are rotatable from the API. @@ -103,7 +103,7 @@ The following per-database limits are enforced by the API: | Backup retention | 1 – 365 days | | Point-in-time recovery window | 1 – 35 days | | Database extensions installed | 50 (PostgreSQL only) | -| Max simultaneous connections | 10,000 (caps to specification quota at runtime) | +| Max simultaneous connections | 10,000 platform cap, lower on smaller specifications | | Idle timeout (shared only) | 5 – 60 minutes | # Billing & plan requirements {% #plans %} diff --git a/src/routes/docs/products/databases/dedicated/backups/+page.markdoc b/src/routes/docs/products/databases/dedicated/backups/+page.markdoc index 756aa1bc909..7daeaa2ba26 100644 --- a/src/routes/docs/products/databases/dedicated/backups/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/backups/+page.markdoc @@ -6,13 +6,48 @@ description: Encrypted scheduled backups with up to 365 days of retention, on-de Every dedicated database supports automated backups out of the box. Backups are AES-256 encrypted at rest, uploaded to an object store, and verifiable on demand. Point-in-time recovery (PITR) layers continuous WAL/binlog/oplog archiving on top, so you can restore to any second within the retention window, not just to a backup checkpoint. +# Backup policy {% #policy %} + +Backup settings are modeled as policies. A database can have multiple policies, so you can keep short-lived hourly backups, longer-lived daily backups, and monthly checkpoints without forcing every schedule into one retention window. + +| Field | Purpose | +|-------|---------| +| `enabled` | Turns the policy schedule on or off | +| `schedule` | Cron expression for the policy | +| `retention` | Number of days backups created by the policy are retained | +| `resourceId` | Database the policy applies to | +| `resourceType` | `dedicatedDatabases` | +| `type` | Backup type, `full` or `incremental` | + +The Console can present presets such as hourly, daily, weekly, or monthly, plus a custom cron flow. Internally these policies reuse the same project-level backup policy model and scheduler as Appwrite project backups, so scheduled runs and plan limits stay consistent. + +Create a policy through the dedicated database backup policy endpoint: + +```bash +curl -X POST \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + -H "Content-Type: application/json" \ + -d '{ + "policyId": "unique()", + "name": "Hourly backups", + "schedule": "0 * * * *", + "retention": 3, + "type": "full", + "enabled": true + }' \ + https://.cloud.appwrite.io/v1/compute/databases//backups/policies +``` + +Point-in-time recovery is database-level configuration. Use `backupPitr` and `pitrRetentionDays` on the database when PITR should be enabled alongside scheduled policies. + # What gets backed up {% #scope %} A backup captures the full contents of the database: all schemas, tables/collections, indexes, extensions, and (optionally) user-defined roles. The backup is taken with the engine's native tool, `pg_dump` / `mysqldump` / `mongodump`, in a dedicated job that runs alongside (not on top of) the database, so production traffic is unaffected. -# Schedule {% #schedule %} +# Schedules {% #schedule %} -Scheduled backups run from a cron expression you configure on the database. The default is `0 3 * * *` (daily at 03:00 UTC), jittered to spread load across the platform. +Each policy has its own cron expression and retention window. The default database cron is `0 3 * * *` (daily at 03:00 UTC), jittered to spread load across the platform. The examples below configure the database-level backup defaults used for compatibility and PITR. {% multicode %} ```server-nodejs @@ -425,7 +460,7 @@ curl -X POST \ ``` {% /multicode %} -Manual backups respect the same retention setting as scheduled ones. Pass `retentionDays` in the request body to override the default. +Manual backups use the database's default backup retention. Scheduled backups use the retention window on the policy that created them. # Encryption {% #encryption %} diff --git a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc index 9a0eda4a6ab..258e4b4fec6 100644 --- a/src/routes/docs/products/databases/dedicated/branches/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/branches/+page.markdoc @@ -6,6 +6,10 @@ description: Spin up an ephemeral, isolated copy of a dedicated database in seco A **branch** is a short-lived, isolated copy of a dedicated database. It has its own credentials, its own endpoint, and starts from a point-in-time snapshot of the parent's storage volume. Branches are not replicas, once created, they diverge from the parent and never sync back. +{% info title="Branches do not merge back" %} +There is no branch merge operation. Use a branch to validate a migration, data repair, or application change, then intentionally cut application traffic over to the validated database or copy the data you want back with engine-native tools. Appwrite does not reconcile two diverged database histories for you. +{% /info %} + Use cases: - **Preview environments**, one branch per pull request, destroyed when the PR closes diff --git a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc index 70b11fd6d08..c4f44511ec9 100644 --- a/src/routes/docs/products/databases/dedicated/connect/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/connect/+page.markdoc @@ -6,7 +6,19 @@ description: Connect to a dedicated database with psql, mysql, mongosh, or any s A dedicated database exposes a real engine endpoint over TLS. You connect to it the same way you would connect to any PostgreSQL, MySQL, MariaDB, or MongoDB server: with the engine's standard CLI or any ORM/driver in any language. -# Get the connection details {% #credentials %} +# Get connection details in Console {% #console %} + +The fastest way to connect is through the Appwrite Console: + +1. Open your project in the Appwrite Console. +2. Open **Databases** and select your dedicated database. +3. Open the **Connect** or **Credentials** view. +4. Copy the connection string for your engine. +5. Paste it into `psql`, `mysql`, `mongosh`, your ORM, or your database client. + +Use the API or SDK flow below when you need to fetch credentials from automation, rotate credentials from a script, or inject the connection string into your deployment pipeline. + +# Get credentials with the API {% #credentials %} The connection details for a dedicated database are exposed through the [credentials endpoint](/docs/references/cloud/server-rest/compute#getDatabaseCredentials). Call it from any server SDK with an API key that has the `databases.read` scope. diff --git a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc index d9b8447b877..1d057d4fde7 100644 --- a/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/high-availability/+page.markdoc @@ -690,4 +690,4 @@ Cross-region replicas and standbys are billed as feature add-ons, see the [speci | Health-check timeout per attempt | 3 s | | Consecutive failed health checks before failover | 2 | -Replicas are billed at 75% of the underlying specification price per replica. Cross-region replicas are billed at 75% per replica. +Regional HA replicas are billed at 50% of the underlying specification price per replica. Cross-region replicas are billed at 75% per replica. diff --git a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc index 9a1d8df35ee..e6fb4602bf4 100644 --- a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc @@ -4,9 +4,43 @@ title: Monitoring & insights description: Live CPU, memory, IOPS, and connection metrics, slow query logging, query plan explanations, and automatic index recommendations for dedicated databases. --- -Every dedicated database collects its own resource telemetry, slow-query logs, and index analysis. The Appwrite Console renders the data in the Database view; the same data is available through the API so you can pipe it into your existing observability stack. +Every dedicated database collects its own resource telemetry, slow-query logs, and index analysis. The Appwrite Console renders charts from the platform usage API. Platform activity is available through the activity events API, and lower-level engine diagnostics stay on database-specific surfaces. -# Live metrics {% #metrics %} +# Usage time series {% #usage %} + +For Console charts, longer-range capacity planning, anomaly detection, and billing-adjacent views, use the platform usage endpoints. Gauge queries support `metric` and `time` filters. Dedicated database gauges scope the database inside the metric name, such as `dedicatedDatabases..cpu`. + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + --get \ + --data-urlencode 'queries[]=equal("metric", ["dedicatedDatabases..cpu"])' \ + --data-urlencode 'queries[]=greaterThanEqual("time", ["2026-05-01T00:00:00.000Z"])' \ + https://.cloud.appwrite.io/v1/usage/gauges +``` + +Gauge metrics include `storage`, `connections`, `cpu`, `memory`, `qps`, `iopsRead`, and `iopsWrite`. Counter metrics include `compute`, `inbound`, `outbound`, and `coldStarts`. + +Use `/v1/usage/events` when you need discrete usage events, such as storage autoscaling, failover, or lifecycle transitions: + +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + --get \ + --data-urlencode 'queries[]=equal("resource", ["dedicatedDatabases"])' \ + --data-urlencode 'queries[]=equal("resourceId", [""])' \ + --data-urlencode 'queries[]=equal("metric", ["dedicatedDatabases..coldStarts"])' \ + --data-urlencode 'queries[]=greaterThanEqual("time", ["2026-05-01T00:00:00.000Z"])' \ + https://.cloud.appwrite.io/v1/usage/events +``` + +Console charts should be built from these usage streams. For a per-database rollup in SDKs, the dedicated `getDatabaseUsage` endpoint remains available. + +# Live operational snapshot {% #metrics %} + +Use the dedicated metrics endpoint only when you need a short, live operational snapshot for the database view or for direct troubleshooting. {% multicode %} ```server-nodejs @@ -226,212 +260,6 @@ The response includes: Metrics are sampled inside the database environment from OS-level resource counters and engine-native stats. Samples are pushed to the edge every 60 seconds and aggregated for the API. -# Usage time series {% #usage %} - -For longer-range plots, invoice line items, capacity planning, anomaly detection, call the usage endpoint: - -{% multicode %} -```server-nodejs -import { Client, Compute } from 'node-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -const compute = new Compute(client); - -const usage = await compute.getDatabaseUsage({ - databaseId: '', - range: '30d', -}); -``` -```server-deno -import { Client, Compute } from "npm:node-appwrite"; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -const compute = new Compute(client); - -const usage = await compute.getDatabaseUsage({ - databaseId: '', - range: '30d', -}); -``` -```server-php -setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setKey(''); - -$compute = new Compute($client); - -$usage = $compute->getDatabaseUsage( - databaseId: '', - range: '30d', -); -``` -```server-python -from appwrite.client import Client -from appwrite.services.compute import Compute - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_key('') - -compute = Compute(client) - -usage = compute.get_database_usage( - database_id='', - range='30d', -) -``` -```server-ruby -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_key('') - -compute = Compute.new(client) - -usage = compute.get_database_usage( - database_id: '', - range: '30d', -) -``` -```server-dotnet -using Appwrite; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetKey(""); - -Compute compute = new Compute(client); - -var usage = await compute.GetDatabaseUsage( - databaseId: "", - range: "30d" -); -``` -```server-dart -import 'package:dart_appwrite/dart_appwrite.dart'; - -Client client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -Compute compute = Compute(client); - -final usage = await compute.getDatabaseUsage( - databaseId: '', - range: '30d', -); -``` -```server-kotlin -import io.appwrite.Client -import io.appwrite.services.Compute - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") - -val compute = Compute(client) - -val usage = compute.getDatabaseUsage( - databaseId = "", - range = "30d", -) -``` -```server-swift -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") - -let compute = Compute(client) - -let usage = try await compute.getDatabaseUsage( - databaseId: "", - range: "30d" -) -``` -```server-go -package main - -import ( - "github.com/appwrite/sdk-for-go/appwrite" -) - -func main() { - client := appwrite.NewClient( - appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), - appwrite.WithProject(""), - appwrite.WithKey(""), - ) - - compute := appwrite.NewCompute(client) - - usage, err := compute.GetDatabaseUsage("", - compute.WithGetDatabaseUsageRange("30d"), - ) - if err != nil { - panic(err) - } - _ = usage -} -``` -```server-rust -use appwrite::Client; -use appwrite::services::compute::Compute; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let client = Client::new() - .set_endpoint("https://.cloud.appwrite.io/v1") - .set_project("") - .set_key(""); - - let compute = Compute::new(&client); - - let usage = compute.get_database_usage("") - .range("30d") - .send() - .await?; - - Ok(()) -} -``` -```bash -curl -X GET \ - -H "X-Appwrite-Project: " \ - -H "X-Appwrite-Key: " \ - "https://.cloud.appwrite.io/v1/compute/databases//usage?range=30d" -``` -{% /multicode %} - -`range` accepts `24h`, `7d`, `30d`, or `90d`. The response is a series of bucketed `(timestamp, value)` pairs for each metric. - # Slow queries {% #slow-queries %} Each engine logs queries that run longer than the configured threshold to the slow-query log. The threshold defaults to 200 ms and is tunable via `metricsSlowQueryLogThresholdMs`. List slow queries through: @@ -1059,187 +887,28 @@ Insights are recommendations, not auto-applied changes. The Console suggests the # Audit logs {% #audit %} -Database-level admin actions, credential rotation, IP allowlist changes, backup triggers, failover requests, are recorded in the standard project activity log: - -{% multicode %} -```server-nodejs -import { Client, Compute } from 'node-appwrite'; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -const compute = new Compute(client); - -const logs = await compute.listDatabaseLogs({ - databaseId: '', -}); -``` -```server-deno -import { Client, Compute } from "npm:node-appwrite"; - -const client = new Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -const compute = new Compute(client); - -const logs = await compute.listDatabaseLogs({ - databaseId: '', -}); -``` -```server-php -setEndpoint('https://.cloud.appwrite.io/v1') - ->setProject('') - ->setKey(''); - -$compute = new Compute($client); - -$logs = $compute->listDatabaseLogs(databaseId: ''); -``` -```server-python -from appwrite.client import Client -from appwrite.services.compute import Compute - -client = Client() -client.set_endpoint('https://.cloud.appwrite.io/v1') -client.set_project('') -client.set_key('') - -compute = Compute(client) - -logs = compute.list_database_logs(database_id='') -``` -```server-ruby -require 'appwrite' - -include Appwrite - -client = Client.new - .set_endpoint('https://.cloud.appwrite.io/v1') - .set_project('') - .set_key('') - -compute = Compute.new(client) - -logs = compute.list_database_logs(database_id: '') -``` -```server-dotnet -using Appwrite; -using Appwrite.Services; - -Client client = new Client() - .SetEndPoint("https://.cloud.appwrite.io/v1") - .SetProject("") - .SetKey(""); - -Compute compute = new Compute(client); - -var logs = await compute.ListDatabaseLogs(databaseId: ""); -``` -```server-dart -import 'package:dart_appwrite/dart_appwrite.dart'; - -Client client = Client() - .setEndpoint('https://.cloud.appwrite.io/v1') - .setProject('') - .setKey(''); - -Compute compute = Compute(client); - -final logs = await compute.listDatabaseLogs( - databaseId: '', -); -``` -```server-kotlin -import io.appwrite.Client -import io.appwrite.services.Compute - -val client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") - -val compute = Compute(client) - -val logs = compute.listDatabaseLogs( - databaseId = "", -) -``` -```server-swift -import Appwrite - -let client = Client() - .setEndpoint("https://.cloud.appwrite.io/v1") - .setProject("") - .setKey("") - -let compute = Compute(client) - -let logs = try await compute.listDatabaseLogs( - databaseId: "" -) -``` -```server-go -package main - -import ( - "github.com/appwrite/sdk-for-go/appwrite" -) - -func main() { - client := appwrite.NewClient( - appwrite.WithEndpoint("https://.cloud.appwrite.io/v1"), - appwrite.WithProject(""), - appwrite.WithKey(""), - ) - - compute := appwrite.NewCompute(client) - - logs, err := compute.ListDatabaseLogs("") - if err != nil { - panic(err) - } - _ = logs -} +```bash +curl -X GET \ + -H "X-Appwrite-Project: " \ + -H "X-Appwrite-Key: " \ + --get \ + --data-urlencode 'queries[]=equal("resourceType", ["dedicatedDatabase"])' \ + --data-urlencode 'queries[]=equal("resourceId", [""])' \ + https://.cloud.appwrite.io/v1/activities/events ``` -```server-rust -use appwrite::Client; -use appwrite::services::compute::Compute; - -#[tokio::main] -async fn main() -> Result<(), Box> { - let client = Client::new() - .set_endpoint("https://.cloud.appwrite.io/v1") - .set_project("") - .set_key(""); - let compute = Compute::new(&client); +These activity events describe Appwrite platform actions. Engine audit logs remain on the dedicated database logs endpoint: - let logs = compute.list_database_logs("").await?; - - Ok(()) -} -``` ```bash curl -X GET \ -H "X-Appwrite-Project: " \ -H "X-Appwrite-Key: " \ "https://.cloud.appwrite.io/v1/compute/databases//logs" ``` -{% /multicode %} -The response includes the actor, the action, the source IP, and the previous/next state for fields that changed. +Activity events are not a replacement for engine audit logs, engine error logs, or slow-query logs. Engine and slow logs remain lower-level database surfaces exposed through the engine's own logging and profiling tools, plus the dedicated slow-query view documented above. # Webhooks & realtime {% #events %} diff --git a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc index d87609b9737..83089c1d487 100644 --- a/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/pooler/+page.markdoc @@ -15,19 +15,18 @@ Each dedicated database can run its own pooler. Pooler resources are billed as p | PostgreSQL | yes | 6432 | PgBouncer-compatible wire protocol, native read/write split | | MySQL | yes | 6033 | Query-rule based routing of reads vs writes | | MariaDB | yes | 6033 | Same as MySQL | -| MongoDB |, |, | Not applicable, use the driver's `readPreference` setting | +| MongoDB | no | - | Not applicable, use the driver's `readPreference` setting | The pooler exposes its own client-facing port on the database endpoint. Clients connect to the pooler port instead of the engine port, everything else is the same. # Pool modes {% #modes %} -PostgreSQL and MySQL/MariaDB pools support three modes: +PostgreSQL and MySQL/MariaDB pools support two modes: | Mode | Backend connection lifetime | Best for | |----------------|-------------------------------------------|---------------------------------------------------------------------| | `transaction` | Held until `COMMIT` / `ROLLBACK` | Most workloads. The default. Massive client→backend amplification. | | `session` | Held for the whole client session | Apps that rely on `SET LOCAL`, prepared statements, advisory locks | -| `statement` | Held until the statement completes | Read-only, single-statement workloads. Highest amplification. | If your application uses features that are tied to a single backend connection, server-side prepared statements (PostgreSQL), advisory locks, temporary tables, `SET LOCAL`, listen/notify, pick `session`. Otherwise, `transaction` is the right choice. diff --git a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc index edddf9037cf..9e0f3d92a84 100644 --- a/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/specifications/+page.markdoc @@ -1,27 +1,47 @@ --- layout: article title: Specifications -description: Eight dedicated database specifications from 1 vCPU and 1 GB to 8 vCPU and 64 GB, plus a free shared tier. Per-specification CPU, memory, included storage, included bandwidth, and connection cap. +description: Eight dedicated database specifications from 1 vCPU and 1 GB to 8 vCPU and 64 GB, plus a free shared tier. Per-specification CPU, memory, included storage, included bandwidth, connection limits, and pricing. --- -A dedicated database is provisioned against a **specification**, a fixed bundle of CPU, memory, included storage, included bandwidth, and a maximum connection cap. You pick the specification on create and can change it later by updating the database; the platform performs a blue-green resize with logical replication. +A dedicated database is provisioned against a **specification**, a fixed bundle of CPU, memory, included storage, included bandwidth, and a connection limit. You pick the specification on create and can change it later by updating the database; the platform performs a blue-green resize with logical replication. All paid specifications are dedicated, always-on databases billed by the hour. The free specification is a shared, scale-to-zero database included with the Pro plan. # Paid specifications {% #paid %} -| Specification | Name | vCPU | Memory | Max connections | Included storage | Included bandwidth | Price (USD/mo) | -|------------------|------------------|------|--------|-----------------|------------------|--------------------|----------------| -| `s-1vcpu-1gb` | Starter | 1 | 1 GB | 100 | 10 GB | 50 GB | $15 | -| `s-2vcpu-2gb` | Standard | 2 | 2 GB | 200 | 25 GB | 200 GB | $25 | -| `s-2vcpu-4gb` | Standard Plus | 2 | 4 GB | 500 | 50 GB | 400 GB | $60 | -| `s-4vcpu-8gb` | Professional | 4 | 8 GB | 1,000 | 200 GB | 1 TB | $100 | -| `s-4vcpu-16gb` | Business | 4 | 16 GB | 2,000 | 500 GB | 2 TB | $190 | -| `s-4vcpu-32gb` | Business Plus | 4 | 32 GB | 4,000 | 1 TB | 5 TB | $370 | -| `s-8vcpu-32gb` | Enterprise | 8 | 32 GB | 5,000 | 2 TB | 7.5 TB | $500 | -| `s-8vcpu-64gb` | Enterprise Plus | 8 | 64 GB | 10,000 | 3 TB | 10 TB | $860 | - -vCPU counts are dedicated, not burstable. Connection caps are enforced at the engine layer, going above the cap returns a connection refusal, not a slowdown. +| Specification | Name | vCPU | Memory | Included storage | Included bandwidth | Price (USD/mo) | +|------------------|------------------|------|--------|------------------|--------------------|----------------| +| `s-1vcpu-1gb` | Starter | 1 | 1 GB | 10 GB | 50 GB | $13 | +| `s-2vcpu-2gb` | Standard | 2 | 2 GB | 25 GB | 200 GB | $23 | +| `s-2vcpu-4gb` | Standard Plus | 2 | 4 GB | 50 GB | 400 GB | $49 | +| `s-4vcpu-8gb` | Professional | 4 | 8 GB | 200 GB | 1 TB | $85 | +| `s-4vcpu-16gb` | Business | 4 | 16 GB | 500 GB | 2 TB | $160 | +| `s-4vcpu-32gb` | Business Plus | 4 | 32 GB | 1 TB | 5 TB | $299 | +| `s-8vcpu-32gb` | Enterprise | 8 | 32 GB | 2 TB | 7.5 TB | $425 | +| `s-8vcpu-64gb` | Enterprise Plus | 8 | 64 GB | 3 TB | 10 TB | $699 | + +vCPU counts are dedicated, not burstable. Paid specifications are always-on and billed by the hour against the monthly price. + +# Connection limits {% #connections %} + +Connection caps are enforced at the engine layer. Going above the cap returns a connection refusal, not a slowdown. + +| Specification | Max connections | +|------------------|-----------------| +| Free shared | 10 | +| `s-1vcpu-1gb` | 100 | +| `s-2vcpu-2gb` | 200 | +| `s-2vcpu-4gb` | 500 | +| `s-4vcpu-8gb` | 1,000 | +| `s-4vcpu-16gb` | 2,000 | +| `s-4vcpu-32gb` | 4,000 | +| `s-8vcpu-32gb` | 5,000 | +| `s-8vcpu-64gb` | 10,000 | + +{% arrow_link href="/contact-us/enterprise" %} +Contact sales for custom connection limits +{% /arrow_link %} # Free (shared) specification {% #free %} @@ -53,11 +73,11 @@ Storage overage is computed on the snapshot at the end of the billing period. Ba The following features are billed as a percentage of the underlying specification price, pro-rated by active days in the billing period: -| Feature | Rate | Example on a $100/mo specification | +| Feature | Rate | Example on an $85/mo specification | |----------------------------------------|---------------------------------------|------------------------------------| -| High availability replicas | 75% of specification per replica | $75/mo per replica | -| Cross-region replicas *(coming soon)* | 75% of specification per replica | $75/mo per replica | -| Point-in-time recovery | 20% of specification | $20/mo | +| High availability replicas | 50% of specification per replica | $42.50/mo per replica | +| Cross-region replicas *(coming soon)* | 75% of specification per replica | $63.75/mo per replica | +| Point-in-time recovery | 20% of specification | $17/mo | | PostgreSQL extensions | Free | $0 | {% info title="Cross-region replication is coming soon" %} From 50fa3618cc9079abe03b645b562f83b1139be73f Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 22 May 2026 16:59:19 +1200 Subject: [PATCH 15/15] docs: remove dedicated database legacy usage routes --- .../databases/dedicated/monitoring/+page.markdoc | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc index e6fb4602bf4..efbc603e282 100644 --- a/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc +++ b/src/routes/docs/products/databases/dedicated/monitoring/+page.markdoc @@ -20,7 +20,7 @@ curl -X GET \ https://.cloud.appwrite.io/v1/usage/gauges ``` -Gauge metrics include `storage`, `connections`, `cpu`, `memory`, `qps`, `iopsRead`, and `iopsWrite`. Counter metrics include `compute`, `inbound`, `outbound`, and `coldStarts`. +Gauge metrics include `storage`, `connections`, `cpu`, `memory`, `qps`, `iopsRead`, and `iopsWrite`. Gauge rows also include `resourceType` and `resourceId` when they are ingested through the dedicated database metrics pipeline. Counter metrics include `compute`, `inbound`, `outbound`, and `coldStarts`. Use `/v1/usage/events` when you need discrete usage events, such as storage autoscaling, failover, or lifecycle transitions: @@ -36,7 +36,7 @@ curl -X GET \ https://.cloud.appwrite.io/v1/usage/events ``` -Console charts should be built from these usage streams. For a per-database rollup in SDKs, the dedicated `getDatabaseUsage` endpoint remains available. +Console charts and per-database rollups should be built from these usage streams. # Live operational snapshot {% #metrics %} @@ -899,16 +899,7 @@ curl -X GET \ https://.cloud.appwrite.io/v1/activities/events ``` -These activity events describe Appwrite platform actions. Engine audit logs remain on the dedicated database logs endpoint: - -```bash -curl -X GET \ - -H "X-Appwrite-Project: " \ - -H "X-Appwrite-Key: " \ - "https://.cloud.appwrite.io/v1/compute/databases//logs" -``` - -Activity events are not a replacement for engine audit logs, engine error logs, or slow-query logs. Engine and slow logs remain lower-level database surfaces exposed through the engine's own logging and profiling tools, plus the dedicated slow-query view documented above. +These activity events describe Appwrite platform actions. Activity events are not a replacement for engine audit logs, engine error logs, or slow-query logs. Engine logs remain lower-level database surfaces exposed through the engine's own logging and profiling tools, plus the dedicated slow-query view documented above. # Webhooks & realtime {% #events %}