Skip to content

D1 Automatic Scale-out

Authrim forecasts growth in accounts and the information used to find their storage locations, then adds D1 databases as needed. It creates the schema, verifies the settings that connect Workers to D1, and registers each database as a destination for new accounts once preparation is complete.

Capacity is distributed across multiple databases. The size limit of each D1 database remains unchanged, and existing accounts keep their storage locations when a database is added. Applications continue using Authrim’s identity APIs; Authrim manages database additions and account placement.

Authrim stores account state, personal information and other sensitive values, and storage-location lookup information separately. Their implementation names are Core, PII, and Lookup. Each can use multiple D1 databases. Administrative and database-management information is stored separately and is outside the account-growth forecast.

New accounts are assigned databases that meet the tenant’s placement and storage-region requirements. Reads and writes use the registered storage locations. They do not scan every D1 database or select a destination from database names alone.

Lookup is an index used to find users. It resolves an email address, an external IdP’s user identifier, or an account ID to an account and its account-state and personal-information D1 databases. It is separate from the databases that store personal-information values.

For an email lookup, Authrim derives an index value from the email address and searches Lookup. It filters matches by the requested tenant. When exactly one account matches, it verifies that account’s storage locations before reading or writing. No matches or multiple matches produce an error; Authrim does not search other D1 databases one by one.

Index values are derived from identifiers using a secret key, rather than using raw email addresses as index keys. Insufficient lookup capacity can affect both user and storage-location searches, so it is forecast separately from account storage.

flowchart TB
    Start(["Scheduled check / new account allocation"]) --> Count["Measure allocations and lookup growth"]
    Count --> Predict["Smooth registration rate and forecast required capacity"]
    Predict --> Enough{"Enough capacity, including databases being created?"}
    Enough -->|Yes| Keep(["Use existing storage; no addition"])
    Enough -->|No| Authority{"Automatic provisioning ready?"}
    Authority -->|No| Operator["Check settings / execute pending operations with setup tool"]
    Authority -->|Yes| Request["Record request / check creation ceiling and budget"]
    Request --> Create["Create D1, apply schema, update Worker connections"]
    Create --> Check{"Connection and readiness checks passed?"}
    Check -->|No| Retry["Keep inactive / record failure, retry or operator action"]
    Check -->|Yes| Publish["Distribute storage locations and allow allocations"]
    Publish --> New(["Assign new accounts to added databases too"])

This chart shows account-storage capacity decisions. Lookup capacity is also forecast, but its observations come from lookup registration and publication. Exhausting the creation budget can delay database creation.

See PII separation for the identity data model and storage-location validation.

Authrim selects databases with valid assignments that are running, healthy, open to new allocations, and below their target account count. The corresponding internal states are active, healthy, and eligible. It also checks whether storage is shared across tenants (shared_pool) or dedicated to one tenant (tenant_exclusive), and respects the configured storage region.

Among eligible databases, Authrim prefers the lowest ratio of allocated accounts to target accounts, calculated as allocated_account_count / target_account_count. Ties are resolved by account count, then partition ID. Account and personal-information storage are reserved together; an assignment with only one destination is rejected. Added databases are used only after allocation and storage-location information are ready.

Added databases provide capacity for new accounts. Changing the storage location of an existing account requires a separate migration operation.

The account forecast uses the cumulative count of successful allocations to calculate a registration rate. An exponentially weighted moving average (EWMA) smooths the latest sample. The planner then calculates:

forecast new accounts = ceil(EWMA registration rate × forecast horizon)
projected accounts = allocated accounts + forecast new accounts
usable capacity = floor(raw target capacity × (1 − headroom))
additional units = ceil(max(0, projected accounts − usable capacity) / usable target capacity per unit)

Forecasts are calculated separately for each data type, shared or dedicated placement, owning tenant where applicable, and storage region. Requested, being-created, prepared, and running databases count toward planned capacity. Their internal states are requested, provisioning, ready, and active. Including planned capacity prevents repeated requests for the same deficit. A database cannot receive accounts until it is ready.

Before calculating capacity, Authrim validates observation times, cumulative counts, integer ranges, and settings. It uses fixed-point integer arithmetic for rates and percentages. A stale observation or invalid counter is rejected instead of becoming a new capacity decision.

Suppose one capacity unit has a target of 100,000 accounts and 20% headroom. Its usable forecast capacity is 80,000. With 75,000 allocated accounts and a smoothed rate of 10 registrations per second over 900 seconds, the projection is 84,000. The 4,000-account deficit requests one additional unit for the relevant data type and placement.

This is an illustration of the planner, not a measured throughput or storage-size guarantee. The actual request also accounts for databases being created and creation budgets.

Forecasting storage-lookup capacity separately

Section titled “Forecasting storage-lookup capacity separately”

Storage-lookup growth is measured through active storage-location records and successful registrations. Its forecast uses an EWMA, forecast horizon, headroom, and capacity weights for lookup database units. Adding account or personal-information databases does not by itself establish that lookup storage has enough capacity.

Adding lookup capacity also coordinates the assignment of lookup partitions, called buckets, and the distribution of storage-location information. Moving lookup information and moving account data are separate operations. Moving a lookup record to another D1 database does not change where the account or its personal information is stored.

The following are schema defaults for Authrim’s database-management settings. They are planning values, not Cloudflare quotas or sizing recommendations for every deployment. An installed environment may use different settings.

These are stored database-management values. Adding Worker environment variables with the same names does not change them.

Policy fieldDefaultMeaning
target_account_count100,000Target accounts per unit of account storage.
account_forecast_horizon_seconds900Account forecast horizon of 15 minutes.
account_scale_out_headroom_bps2,000Reserve 20% of target capacity.
account_registration_ewma_alpha_bps5,000Latest account-rate sample has 50% weight.
lookup_target_active_route_count100,000Target storage-location records per lookup unit before capacity weighting.
lookup_forecast_horizon_seconds86,400Storage-lookup forecast horizon of 24 hours.
lookup_scale_out_headroom_bps2,000Lookup-storage headroom of 20%.
lookup_registration_ewma_alpha_bps2,500Latest lookup-registration-rate sample has 25% weight.
max_concurrent_provisioning2Concurrent provisioning limit.
max_d1_resources1,000Authrim’s D1 resource ceiling for the environment.
daily_d1_create_budget20Daily database-creation budget.

The account horizon accepts 60–2,592,000 seconds; storage lookup accepts 300–2,592,000 seconds. Headroom and EWMA weights are integers where 100 represents 1%. Headroom accepts 0–9,000, and EWMA weights accept 1–10,000. Account targets must also reflect profile size, indexes, observed storage growth, and workload. Counting accounts alone cannot prevent a database full of unusually large records from reaching its byte limit.

The automatic executor requires AUTHRIM_AUTOMATIC_PROVISIONING=true, non-empty and distinct CLOUDFLARE_D1_API_TOKEN and CLOUDFLARE_WORKERS_API_TOKEN secrets, and a completed automatic-provisioning registration in Authrim. The tokens need the relevant database-creation and Worker-update permissions. Setting the flag alone does not complete setup.

Use the setup tool’s automatic-provisioning flow to prepare and verify registration and permissions. Keep credentials in Worker secrets. The database-management Worker is configured to run scheduled checks every minute. Account allocations also trigger capacity checks. Scheduled automatic forecasting and database creation run only when automatic provisioning is ready.

For opening the setup screen, see the setup wizard.

Open the environment details in the setup tool and use Automatic provisioning. Confirm that the selected environment and Cloudflare account match before proceeding.

  1. Select “Create one-time Cloudflare token”, create the temporary token, and paste it into the input.
  2. Select “Enable”. The tool prepares the registration and updates the database-management Worker.
  3. The tool registers purpose-specific credentials and verifies permissions. Complete the revocation of the bootstrap token as well.
  4. Confirm that the screen reports readiness. If interrupted, follow its recovery instructions and resume the existing operation.

Before operation, check shared or tenant-dedicated placement, storage regions, daily creation budget, and environment-wide database ceiling. Include forecast horizon and headroom in capacity planning so creation can keep up with registrations. Monitor database counts separately from actual database sizes.

Register a new account and check that allocation counts and forecasts update. When a capacity request occurs, follow its operation ID through database creation, schema application, Worker connection verification, and storage-location distribution. Then confirm that the database is used for new account allocations.

If creation stops progressing, check the operation’s last error, creation budget, API-token permissions, and Worker connection settings in that order. A database that exists but has not passed readiness checks is not used for account storage.

If automatic execution is unavailable, recorded pending operations can be handed to the setup tool. From the Authrim repository, inspect a selected operation before executing it:

Terminal window
pnpm run setup control-provision --env prod --operation-id <operation-id> --dry-run
pnpm run setup control-provision --env prod --operation-id <operation-id>

Use the actual environment and operation ID. The setup tool needs the environment state, keys, and authorized Cloudflare access. Unrelated databases created manually are not automatically registered as Authrim storage destinations.

Authrim saves the capacity request and its operation ID. It then creates D1 databases, initializes their schema, updates and verifies Worker-to-D1 connection settings, distributes storage-location information, and checks readiness. A new destination is used only after these steps are complete.

Database-count ceilings, daily creation budgets, missing permissions, differences between registered and actual configuration, or Cloudflare errors can stop progress. Retriable operations retain their state for subsequent checks. Errors that cannot be retried require operator attention. Inspect the operation’s last error and the forecast’s requested operation ID before trying again. Repeatedly issuing new requests can obscure the original failure.

Check capacity forecasts and actual availability separately. A database being created counts toward planned capacity, but assigning accounts requires a running, healthy database that accepts allocations. If no valid destination is available, Authrim returns a capacity error rather than writing to an arbitrary D1 database.

Moving from shared databases to tenant-dedicated databases has a separate migration workflow. Capacity forecasts do not authorize that move, an automatic return to shared placement, or deletion of an old database. Cleanup has its own approval and safety checks. Release schema migrations are also separate from adding a capacity unit under the current schema.

Monitor physical D1 size, query latency, rows read/written, capacity ratios, forecast growth, pending operations, and Worker-to-D1 connection settings. Scale-out does not remove Cloudflare account limits, per-database limits, or billing. Cloudflare currently documents a maximum database size of 10 GB on Workers Paid and 500 MB on Free; verify the current D1 limits and pricing for the deployment.

The forecast is driven by account growth and storage-lookup growth. It is not a disk-utilization or request-per-second autoscaler, and it does not guarantee a particular cost or user count without workload-specific sizing.

Implementation references: packages/ar-control/src/account-allocation.ts, account-scale-out-planner.ts, account-scale-out-forecast.ts, lookup-scale-out-planner.ts, lookup-scale-out-forecast.ts, index.ts, packages/ar-lib-core/src/services/control-plane/, and packages/setup/src/cli/commands/control-provision.ts. Setting defaults are defined in the database-management D1 schema.