Limits
Branch caps, storage quotas, compute accounting, extension mechanisms, and monitoring recommendations.
Limits
To ensure fair usage, prevent runaway costs, and keep our clusters running smoothly, AxiomDB sets a few limits on database resources. In this guide, we'll explain how these limits are measured, how we enforce them, and how you can increase them when your application grows.
Database Branch Limits
Here is a quick look at the limits on database branches:
| Limit | Value | Scope | How we enforce it |
|---|---|---|---|
| Active branches | 10 | Per project | A hard cap checked when you create a new branch |
main branch | 1 | Per project | Automatically created, and cannot be deleted |
| Max branch slug length | 48 characters | Per branch | Validated when you create the branch |
| Min branch slug length | 3 characters | Per branch | Validated when you create the branch |
| Max database name length | 63 characters | Per branch | A standard PostgreSQL system limit |
| Concurrent branch creations | 3 | Per project | Queued and throttled automatically |
How we check active branch counts
When you ask to create a new branch, our gateway runs this check in the background:
SELECT COUNT(*)
FROM branches
WHERE project_id = :project_id
AND status IN ('creating', 'active', 'expired');If your project already has 10 or more active branches, the request is declined with this message:
{
"error": "BRANCH_LIMIT_REACHED",
"message": "Project has reached the maximum of 10 active branches.",
"limit": 10,
"current": 10,
"retry_after": null
}How database name lengths are calculated
We automatically generate database names using this structure:
sq_<app_key>_<env>_br_<slug>Let's break down the maximum lengths:
app_key: 48 charactersenv: 7 characters (usingstagingas the longest label)slug: 48 characters- System prefixes and separators: 8 characters (
sq_,_,_br_)
In the absolute worst-case scenario, this adds up to 111 characters. However, because PostgreSQL limits database names to 63 characters, our control plane checks the derived name length first and will politely reject the request if it would exceed the 63-character limit.
sq_payments_staging_br_fix-checkout-bug-very-long-name
│ │ │
│ 48 max │ 7 max │ 48 max
│ │ │
└──────── Total: 63 chars max ──────────────────────┘Storage Limits
| Limit | Value | Scope | How we enforce it |
|---|---|---|---|
| Default storage quota | 0.25 GiB | Per project (shared across branches) | A warning email followed by database write blocks |
| Max storage quota | 5 GiB | Per project | Maximum available self-serve quota |
| Measurement interval | 60 seconds | Per branch | We check database sizes via pg_database_size() every minute |
| Warning threshold | 80% | Per project | We send an alert email |
| Write block threshold | 100% | Per project | We temporarily disable data writes |
How we measure storage
We measure storage at the project level by adding up the disk space used by all your database branches:
-- Run on the Postgres cluster for each branch database
SELECT pg_database_size('sq_payments_prod') AS main_bytes;
SELECT pg_database_size('sq_payments_prod_br_fix-checkout-bug') AS branch_bytes;Your total project storage usage:
project_storage = Σ pg_database_size(branch_database) for all active branchesWhat happens when you hit a quota?
0% 80% 100%
│ │ │
Normal operation │ Warning emitted │ Writes blocked │
◄─────────────────┼──────────────────────┼──────────────────────►
│ │ │
│ ┌───────────────┐ │ ┌───────────────┐ │
│ │ ALERT_EMAIL │ │ │ WRITE_BLOCKED │ │
│ │ sent to team │ │ │ error on │ │
│ │ │ │ │ INSERT/UPDATE │ │
│ └───────────────┘ │ └───────────────┘ │As soon as your storage exceeds 80% of your quota, we log an event and send your team an email:
{
"event": "STORAGE_WARNING",
"project_id": "proj_a1b2c3d4",
"storage_used_gib": 0.21,
"storage_quota_gib": 0.25,
"usage_percent": 84.0,
"timestamp": "2025-03-20T14:00:00Z"
}If storage reaches 100% of your quota, we need to protect the database from running out of disk space. The control plane automatically sets the non-owner roles to read-only:
-- Applied automatically by the control plane
ALTER ROLE payments_prod_rw SET default_transaction_read_only = on;
ALTER ROLE payments_prod_ro SET default_transaction_read_only = on;Don't worry—your database owner role still has write access so you can delete old data, or you can simply increase your storage quota.
How to extend your storage quota
You can easily request more storage space by sending a request:
curl -X PATCH https://gateway.axiomdb.io/v1/projects/proj_a1b2c3d4 \
-H "Authorization: Bearer paseto_v4_..." \
-H "Content-Type: application/json" \
-d '{ "storage_quota_gib": 2.0 }'Self-serve storage increases are capped at 5 GiB. If your project needs more than that, just reach out to our support team and we'll be happy to help!
Compute Accounting
To help you track performance and plan resource capacity, AxiomDB measures database compute metrics for each branch.
What we measure
We keep track of these metrics for your branches:
| Metric | Unit | Description |
|---|---|---|
cpu_seconds | Seconds | The total amount of CPU time consumed by queries. |
active_connections | Count | The current number of active connections to the database. |
total_connections | Count | The total connections established since the branch was created. |
transactions_committed | Count | The number of successfully committed transactions. |
transactions_rolled_back | Count | The number of rolled-back transactions. |
rows_fetched | Count | The number of database rows returned by queries. |
rows_inserted | Count | The number of database rows inserted. |
rows_updated | Count | The number of database rows updated. |
rows_deleted | Count | The number of database rows deleted. |
How we query CPU usage
-- CPU time per database
SELECT
datname,
sum(total_time) AS cpu_seconds,
sum(calls) AS total_calls
FROM pg_stat_statements
WHERE datname = current_database()
GROUP BY datname;Checking active connections
-- Active connections
SELECT COUNT(*)
FROM pg_stat_activity
WHERE datname = current_database()
AND state = 'active';Requesting Limit Increases
Storage Upgrades
Need more disk space? You can upgrade your storage cap at any time:
| Current Quota | Max Upgrade | Cost |
|---|---|---|
| 0.25 GiB | 5 GiB | Per-GiB monthly rate |
| Custom | 5 GiB | Per-GiB monthly rate |
Branch Limit Upgrades
If the default limit of 10 active branches isn't enough for your workflow, you can request an increase to 20. Just contact support and let us know about your project setup.
Connection Upgrades
By default, we allow up to 200 client connections via PgBouncer. For high-traffic applications, we can bump this limit to 500 connections upon request.
How Quotas Are Enforced
Here is a quick summary of how we handle resource limits:
| Resource | When do we warn you? | When do we block usage? | How to recover |
|---|---|---|---|
| Storage | 80% usage | 100% usage | Delete old data or extend your quota |
| Branches | — | At the limit (10 branches) | Delete expired branches or request a limit increase |
| Connections | — | At PgBouncer connection max | Reduce client pool sizes or request an increase |
| API requests | — | Rate limit hit | Wait a moment for the window to reset |
Helpful Queries for Storage Management
Here are some handy SQL queries you can run inside your branches to monitor your storage:
Checking Total Database Size
SELECT pg_database_size(current_database()) AS size_bytes,
pg_size_pretty(pg_database_size(current_database())) AS size_pretty;Inspecting Table Sizes
SELECT
schemaname || '.' || tablename AS table_name,
pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS total_size,
pg_size_pretty(pg_relation_size(schemaname || '.' || tablename)) AS table_size,
pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename) - pg_relation_size(schemaname || '.' || tablename)) AS index_size
FROM pg_tables
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC;Finding Your Largest Indexes
SELECT
indexname,
pg_size_pretty(pg_relation_size(indexname::regclass)) AS index_size
FROM pg_indexes
WHERE schemaname = 'public'
ORDER BY pg_relation_size(indexname::regclass) DESC
LIMIT 10;Checking for Dead Tuples (Table Bloat)
SELECT
schemaname || '.' || relname AS table_name,
n_dead_tup,
n_live_tup,
round(n_dead_tup::numeric / GREATEST(n_live_tup, 1) * 100, 2) AS dead_pct
FROM pg_stat_user_tables
WHERE n_dead_tup > 1000
ORDER BY n_dead_tup DESC;Estimating Bloat Across Tables
SELECT
schemaname || '.' || tablename AS table_name,
pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS total_size,
n_dead_tup,
n_live_tup,
CASE WHEN n_live_tup > 0
THEN round(n_dead_tup::numeric / n_live_tup * 100, 2)
ELSE 0
END AS bloat_pct
FROM pg_stat_user_tables
JOIN pg_tables USING (schemaname, tablename)
WHERE schemaname = 'public'
ORDER BY pg_total_relation_size(schemaname || '.' || tablename) DESC;Recommended Alerts to Set Up
To keep your application running smoothly, we recommend setting up monitoring alerts for these events:
Critical Alerts
| Metric | When to trigger | Recommended action |
|---|---|---|
| Storage usage ≥ 90% | usage_percent >= 90 | Extend your quota or archive/delete old data. |
| Storage usage = 100% | Writes blocked | Upgrade your quota immediately. |
| Active connections ≥ 180 | connections >= 180 | Investigate potential connection leaks. |
| Branch count = 10 | branch_count = 10 | Delete unused or expired branches. |
| Cache hit ratio < 0.90 | cache_hit_ratio < 0.90 | Optimize your queries or look into upgrading memory. |
| Transaction rollback rate > 5% | rollbacks / commits > 0.05 | Check application logs for database errors. |
Warning Alerts
| Metric | When to trigger | Recommended action |
|---|---|---|
| Storage usage ≥ 80% | usage_percent >= 80 | Plan a quota increase soon. |
| Dead tuple ratio > 20% | dead_pct > 20 | Run a VACUUM on the affected tables. |
| Long-running queries > 30s | query_duration > 30s | Optimize slow queries and check missing indexes. |
| Connection wait time > 5s | wait_time > 5s | The PgBouncer pool is building up—consider connection settings. |
Comprehensive Health Query
You can run this query to get a quick overview of your branch health:
-- Comprehensive branch health check
SELECT
'storage' AS metric,
pg_size_pretty(pg_database_size(current_database())) AS value
UNION ALL
SELECT
'active_connections',
COUNT(*)::text
FROM pg_stat_activity
WHERE datname = current_database() AND state = 'active'
UNION ALL
SELECT
'total_tables',
COUNT(*)::text
FROM information_schema.tables
WHERE table_schema = 'public'
UNION ALL
SELECT
'cache_hit_ratio',
round(
sum(heap_blks_hit)::numeric / GREATEST(sum(heap_blks_hit) + sum(heap_blks_read), 1),
4
)::text
FROM pg_statio_user_tables;API Rate Limits
To keep our gateway responsive, we rate-limit API calls:
| Endpoint | Limit | Time Window |
|---|---|---|
| API reads | 1000 requests | Per minute per project |
| API writes | 200 requests | Per minute per project |
| Connection attempts | 50 attempts | Per minute per branch |
| Credential rotations | 5 rotations | Per hour per branch |
Every API response contains headers showing your rate limit status:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1710940800If you exceed these limits, the gateway will return a 429 Too Many Requests status:
{
"error": "RATE_LIMITED",
"message": "Too many requests. Try again in 45 seconds.",
"retry_after": 45
}How is this guide?
