Platform

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

LimitValueScopeEnforcement
Active branches per project10Per projectHard cap at creation time
main branch per project1Per projectAuto-created, cannot be deleted
Max branch slug length48 charactersPer branchValidation at creation
Min branch slug length3 charactersPer branchValidation at creation
Max database name length63 charactersPer branchPostgres system limit
Concurrent branch creations3Per projectQueue-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 chars
  • env: 7 chars (staging is 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

LimitValueScopeEnforcement
Default storage quota0.25 GiBPer project (shared across branches)Soft warning + hard block
Max storage quota5 GiBPer projectExtension limit
Storage measurement interval60 secondsPer branchPolling from pg_database_size()
Storage warning threshold80%Per projectAlert notification
Storage hard block threshold100%Per projectWrite 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 branches

Storage 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

MetricUnitDescription
cpu_secondsSecondsTotal CPU time consumed by queries.
active_connectionsCountCurrent number of active connections.
total_connectionsCountTotal connections since branch creation.
transactions_committedCountTotal committed transactions.
transactions_rolled_backCountTotal rolled-back transactions.
rows_fetchedCountTotal rows returned by queries.
rows_insertedCountTotal rows inserted.
rows_updatedCountTotal rows updated.
rows_deletedCountTotal 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 QuotaMax ExtensionCost
0.25 GiB5 GiBPer-GiB monthly rate
Custom5 GiBPer-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

ResourceSoft WarningHard BlockRecovery
Storage80% usage100% usageExtend quota or delete data
BranchesAt limitDelete expired branches or extend limit
ConnectionsAt PgBouncer maxReduce app connection pool or extend limit
API requestsRate limit hitWait 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;

Set up alerts for these signals to proactively manage your AxiomDB resources:

Critical Alerts

SignalThresholdAction
Storage usage ≥ 90%usage_percent >= 90Extend quota or archive old data.
Storage usage = 100%Writes blockedImmediate quota extension required.
Active connections ≥ 180connections >= 180Investigate connection leaks.
Branch count = 10branch_count = 10Delete unused branches.
Cache hit ratio < 0.90cache_hit_ratio < 0.90Increase memory or optimize queries.
Transaction rollback rate > 5%rollbacks / commits > 0.05Investigate application errors.

Warning Alerts

SignalThresholdAction
Storage usage ≥ 80%usage_percent >= 80Plan quota extension.
Dead tuple ratio > 20%dead_pct > 20Run VACUUM on affected tables.
Long-running queries > 30squery_duration > 30sInvestigate slow queries.
Connection wait time > 5swait_time > 5sPgBouncer 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

EndpointLimitWindow
API (read)1000 requestsPer minute per project
API (write)200 requestsPer minute per project
Connection attempts50 attemptsPer minute per branch
Credential rotations5 rotationsPer hour per branch

Rate limit headers are included in all API responses:

X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 987
X-RateLimit-Reset: 1710940800

When rate limited, the API returns 429 Too Many Requests:

{
  "error": "RATE_LIMITED",
  "message": "Too many requests. Try again in 45 seconds.",
  "retry_after": 45
}

On this page