> ## Documentation Index
> Fetch the complete documentation index at: https://docs.asktable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Operations and capacity planning

> Understand AskTable's default database capacity and troubleshoot DATABASE_BUSY.

<Check>
  The defaults fit typical single-node deployments. Keep the pool settings unchanged unless `DATABASE_BUSY` occurs repeatedly or you add Web or Worker processes.
</Check>

## Default connection capacity

| Item                                        |                           Default |
| ------------------------------------------- | --------------------------------: |
| Web processes                               |                               `1` |
| Primary-database ceiling per Web process    | `20` (pool `10` + temporary `10`) |
| Worker processes                            |                               `1` |
| Primary-database ceiling per Worker process |   `10` (pool `5` + temporary `5`) |
| Workbook ceiling per process                |                              `10` |
| Built-in PostgreSQL `max_connections`       |                             `100` |

With Workbook enabled, the conservative application peak is `51` connections:

```text theme={null}
Web 20 + Worker 10 + Workbook 20 + LISTEN/NOTIFY 1 = 51
```

The PostgreSQL ceiling of `100` leaves sufficient headroom. `POOL_SIZE` is the number of retained connections, not the connection ceiling; add `MAX_OVERFLOW` to get that ceiling.

## When DATABASE\_BUSY occurs

`DATABASE_BUSY` means AskTable temporarily could not acquire a database connection or PostgreSQL reached its connection limit. The page retries automatically and offers a manual Retry action if the database remains busy.

If the error keeps returning:

1. Check logs to determine whether Web requests or background refreshes are affected.
2. For refresh contention, first reduce `AT_WORKER_MAX_JOBS`.
3. Do not enlarge pools when PostgreSQL already reports exhausted connections.
4. Increase only the affected Web or Worker pool, and only after confirming PostgreSQL has headroom and the local pool is timing out.

## When adding processes

Each additional Web process adds one Web primary-database pool, up to one Workbook pool, and one `LISTEN/NOTIFY` connection. Each additional Worker adds one Worker primary-database pool and up to one Workbook pool.

Tune in this order: shorten transactions, reduce background concurrency, adjust the affected role pool, and raise the PostgreSQL limit last. Keep at least 10 connections available for migrations and troubleshooting.

<AccordionGroup>
  <Accordion title="Complete configuration reference">
    All settings are static environment variables. Restart the affected process after changing them.

    | Setting                     |                               Default | Description                                                         |
    | --------------------------- | ------------------------------------: | ------------------------------------------------------------------- |
    | `AT_SERVER_WORKERS`         |                                   `1` | Web processes; CLI `--workers` takes precedence                     |
    | `AT_WEB_DB_POOL_SIZE`       |                                  `10` | Primary-database connections retained by each Web process           |
    | `AT_WEB_DB_MAX_OVERFLOW`    |                                  `10` | Temporary connections allowed when the Web pool is full             |
    | `AT_WORKER_NUM_PROCS`       |                                   `1` | Worker processes                                                    |
    | `AT_WORKER_MAX_JOBS`        |                                  `10` | Maximum concurrent jobs in each Worker                              |
    | `AT_WORKER_DB_POOL_SIZE`    |                                   `5` | Primary-database connections retained by each Worker process        |
    | `AT_WORKER_DB_MAX_OVERFLOW` |                                   `5` | Temporary connections allowed when the Worker pool is full          |
    | `AT_DB_POOL_TIMEOUT`        |                                  `30` | Maximum seconds to wait for a primary-database connection           |
    | `AT_DB_POOL_RECYCLE`        |                                 `300` | Primary-database connection recycle interval in seconds             |
    | `WORKBOOK_PG_DSN`           | Separate database in local PostgreSQL | Enables Workbook when non-empty; set it explicitly empty to disable |
    | `AT_WORKBOOK_POOL_MIN`      |                                   `1` | Minimum Workbook connections retained by each process               |
    | `AT_WORKBOOK_POOL_MAX`      |                                  `10` | Maximum Workbook connections used by each process                   |

    Web and Worker pool capacity is configured separately; both roles share the wait and recycle settings. All pool values must be non-negative, and Workbook must satisfy `0 <= MIN <= MAX`.
  </Accordion>

  <Accordion title="Calculate the exact connection ceiling">
    `B` is `AT_WORKBOOK_POOL_MAX` when Workbook is enabled and `0` otherwise.

    ```text theme={null}
    Application connection ceiling =
      AT_SERVER_WORKERS
        × (AT_WEB_DB_POOL_SIZE + AT_WEB_DB_MAX_OVERFLOW + B + 1)
      + AT_WORKER_NUM_PROCS
        × (AT_WORKER_DB_POOL_SIZE + AT_WORKER_DB_MAX_OVERFLOW + B)
    ```

    The application ceiling plus at least 10 operations connections must remain below PostgreSQL's connections available to regular users. That number is `max_connections` minus `reserved_connections` and `superuser_reserved_connections`.
  </Accordion>

  <Accordion title="Inspect connection usage">
    Inspect PostgreSQL limits:

    ```sql theme={null}
    SHOW max_connections;
    SHOW reserved_connections;
    SHOW superuser_reserved_connections;
    ```

    Inspect connection sources and long transactions:

    ```sql theme={null}
    SELECT application_name, state, count(*) AS connections,
           max(now() - xact_start) AS longest_transaction
    FROM pg_stat_activity
    WHERE datname IN ('asktable', 'asktable_workbook')
    GROUP BY application_name, state
    ORDER BY connections DESC;
    ```

    Inspect container logs:

    ```bash theme={null}
    docker compose logs --since=30m asktable | grep -E 'DATABASE_BUSY|creating postgresql pool|refresh_table_task'
    docker compose logs --since=30m asktable-pg
    ```
  </Accordion>
</AccordionGroup>

<Note>
  For external PostgreSQL, use the provider's effective connection limit. AskTable does not change it automatically.
</Note>
