Limits
Branch caps, storage quotas, compute accounting, extension mechanisms, and monitoring recommendations.
Limits
AxiomDB enforces resource limits to ensure fair usage, prevent runaway costs, and maintain cluster stability. This page documents every limit, how it is measured, how it is enforced, and how to extend it when needed.
Branch Limits
| Limit | Value | Scope | Enforcement |
|---|---|---|---|
| Active branches per project | 10 | Per project | Hard cap at creation time |
main branch per project | 1 | Per project | Auto-created, cannot be deleted |
| Max branch slug length | 48 characters | Per branch | Validation at creation |
| Min branch slug length | 3 characters | Per branch | Validation at creation |
| Max database name length | 63 characters | Per branch | Postgres system limit |
| Concurrent branch creations | 3 | Per project | Queue-based throttling |
Branch Count Enforcement
When a branch creation request arrives, the gateway checks:
SELECT COUNT(*)
FROM branches
WHERE project_id = :project_id
AND status IN ('creating', 'active', 'expired');If the count is ≥ max_branches (default 10), the request is rejected:
{
"error": "BRANCH_LIMIT_REACHED",
"message": "Project has reached the maximum of 10 active branches.",
"limit": 10,
"current": 10,
"retry_after": null
}Database Name Length Calculation
The database name is derived as:
sq_<app_key>_<env>_br_<slug>Maximum lengths:
app_key: 48 charsenv: 7 chars (stagingis longest)slug: 48 chars- Prefixes/separators: 8 chars (
sq_,_,_br_)
Total worst case: 3 + 48 + 1 + 7 + 4 + 48 = 111 — but Postgres limits database names to 63 characters. In practice, the control plane validates that the derived name does not exceed 63 characters and rejects requests that would overflow.
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 | Enforcement |
|---|---|---|---|
| Default storage quota | 0.25 GiB | Per project (shared across branches) | Soft warning + hard block |
| Max storage quota | 5 GiB | Per project | Extension limit |
| Storage measurement interval | 60 seconds | Per branch | Polling from pg_database_size() |
| Storage warning threshold | 80% | Per project | Alert notification |
| Storage hard block threshold | 100% | Per project | Write rejection |
How Storage Is Measured
Storage is measured at the project level by summing the sizes of all branch databases:
-- 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;Total project storage:
project_storage = Σ pg_database_size(branch_database) for all active branchesStorage Quota Enforcement
0% 80% 100%
│ │ │
Normal operation │ Warning emitted │ Writes blocked │
◄─────────────────┼──────────────────────┼──────────────────────►
│ │ │
│ ┌───────────────┐ │ ┌───────────────┐ │
│ │ ALERT_EMAIL │ │ │ WRITE_BLOCKED │ │
│ │ sent to team │ │ │ error on │ │
│ │ │ │ │ INSERT/UPDATE │ │
│ └───────────────┘ │ └───────────────┘ │When storage exceeds 80% of the quota:
{
"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"
}When storage reaches 100% of the quota, the control plane issues a SET default_transaction_read_only = on for all non-owner roles:
-- 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;Owner role retains write access so you can clean up data or extend the quota.
Extending Storage
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 }'Maximum extension is 5 GiB. To request a larger quota, contact support.
Compute Accounting
AxiomDB tracks compute usage per branch for billing and capacity planning.
Tracked Metrics
| Metric | Unit | Description |
|---|---|---|
cpu_seconds | Seconds | Total CPU time consumed by queries. |
active_connections | Count | Current number of active connections. |
total_connections | Count | Total connections since branch creation. |
transactions_committed | Count | Total committed transactions. |
transactions_rolled_back | Count | Total rolled-back transactions. |
rows_fetched | Count | Total rows returned by queries. |
rows_inserted | Count | Total rows inserted. |
rows_updated | Count | Total rows updated. |
rows_deleted | Count | Total rows deleted. |
Compute Query
-- 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;Connection Count
-- Active connections
SELECT COUNT(*)
FROM pg_stat_activity
WHERE datname = current_database()
AND state = 'active';Extension Mechanisms
Storage Extension
| Current Quota | Max Extension | Cost |
|---|---|---|
| 0.25 GiB | 5 GiB | Per-GiB monthly rate |
| Custom | 5 GiB | Per-GiB monthly rate |
Branch Limit Extension
The default 10-branch limit can be extended to 20 by request. Contact support with justification.
Connection Limit Extension
The default 200 PgBouncer client connections can be extended to 500 for high-traffic projects.
Quota Enforcement Summary
| Resource | Soft Warning | Hard Block | Recovery |
|---|---|---|---|
| Storage | 80% usage | 100% usage | Extend quota or delete data |
| Branches | — | At limit | Delete expired branches or extend limit |
| Connections | — | At PgBouncer max | Reduce app connection pool or extend limit |
| API requests | — | Rate limit hit | Wait for window reset |
Storage Measurement Queries
Useful SQL queries for monitoring storage within your branches:
Total Database Size
SELECT pg_database_size(current_database()) AS size_bytes,
pg_size_pretty(pg_database_size(current_database())) AS size_pretty;Per-Table Size
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;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;Dead Tuples (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;Table Bloat Estimate
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 Monitoring Signals
Set up alerts for these signals to proactively manage your AxiomDB resources:
Critical Alerts
| Signal | Threshold | Action |
|---|---|---|
| Storage usage ≥ 90% | usage_percent >= 90 | Extend quota or archive old data. |
| Storage usage = 100% | Writes blocked | Immediate quota extension required. |
| Active connections ≥ 180 | connections >= 180 | Investigate connection leaks. |
| Branch count = 10 | branch_count = 10 | Delete unused branches. |
| Cache hit ratio < 0.90 | cache_hit_ratio < 0.90 | Increase memory or optimize queries. |
| Transaction rollback rate > 5% | rollbacks / commits > 0.05 | Investigate application errors. |
Warning Alerts
| Signal | Threshold | Action |
|---|---|---|
| Storage usage ≥ 80% | usage_percent >= 80 | Plan quota extension. |
| Dead tuple ratio > 20% | dead_pct > 20 | Run VACUUM on affected tables. |
| Long-running queries > 30s | query_duration > 30s | Investigate slow queries. |
| Connection wait time > 5s | wait_time > 5s | PgBouncer queue is building up. |
Monitoring Query for Dashboard
-- 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;Rate Limits
| Endpoint | Limit | Window |
|---|---|---|
| API (read) | 1000 requests | Per minute per project |
| API (write) | 200 requests | Per minute per project |
| Connection attempts | 50 attempts | Per minute per branch |
| Credential rotations | 5 rotations | Per hour per branch |
Rate limit headers are included in all API responses:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1710940800When rate limited, the API returns 429 Too Many Requests:
{
"error": "RATE_LIMITED",
"message": "Too many requests. Try again in 45 seconds.",
"retry_after": 45
}