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

# Relational Databases

> Configure relational databases for metadata and state storage in Cognee

Relational databases store metadata, document information, and system state in Cognee. They track documents, chunks, and provenance (where data came from and how it's linked).

<Info>
  **New to configuration?**

  See the [Setup Configuration Overview](./overview) for the complete workflow:

  install extras → create `.env` → choose providers → handle pruning.

  For a complete, copy-paste `.env` block that combines this layer with a vector and a graph store, see [Store Configurations](/guides/store-configurations).
</Info>

## Supported Providers

Cognee supports these relational database options:

* **SQLite** — File-based database, works out of the box (default)
* **Postgres** — Production-ready database with external hosting options
* **Turso (libSQL)** — A SQLite-compatible drop-in with optional embedded-replica sync for a hosted Turso database

## Configuration

<Accordion title="Environment Variables">
  Set these environment variables in your `.env` file:

  * `DB_PROVIDER` — The database provider (sqlite, postgres, turso)
  * `DB_NAME` — Database name
  * `DB_HOST` — Database host (Postgres only)
  * `DB_PORT` — Database port (Postgres only)
  * `DB_USERNAME` — Database username (Postgres only)
  * `DB_PASSWORD` — Database password (Postgres only)
  * `DB_TURSO_URL` — Remote Turso database URL, e.g. `libsql://<your-db>.turso.io` (Turso remote mode only; leave unset for a local libSQL file)
  * `DB_TURSO_AUTH_TOKEN` — Auth token for the remote Turso database (Turso remote mode only)
</Accordion>

## Setup Guides

<AccordionGroup>
  <Accordion title="SQLite (Default)">
    SQLite is file-based and requires no additional setup. It's perfect for local development and single-user scenarios.

    ```dotenv theme={null}
    DB_PROVIDER="sqlite"
    DB_NAME="cognee_db"
    ```

    **Installation**: SQLite is included by default with Cognee. No additional installation required.

    **Data Location**: Data is stored under the Cognee system directory. You can override the root with `SYSTEM_ROOT_DIRECTORY` in your `.env` file.
  </Accordion>

  <Accordion title="Postgres">
    Postgres is recommended for production environments or when you need external hosting.

    <Tabs>
      <Tab title=".env">
        Set the connection in your `.env` file:

        ```dotenv theme={null}
        DB_PROVIDER="postgres"
        DB_NAME="cognee_db"
        DB_HOST="127.0.0.1"            # use host.docker.internal when running inside Docker
        DB_PORT="5432"
        DB_USERNAME="cognee"
        DB_PASSWORD="cognee"
        ```
      </Tab>

      <Tab title="Python">
        Instead of (or in addition to) the `.env` file, set the same values at runtime with `cognee.config.set_relational_db_config()`. Call it before any `add()`, `cognify()`, or `remember()` so the connection is used from the first operation:

        ```python theme={null}
        import asyncio
        import cognee

        async def main():
            cognee.config.set_relational_db_config(
                {
                    "db_provider": "postgres",
                    "db_name": "cognee_db",
                    "db_host": "127.0.0.1",   # host.docker.internal inside Docker
                    "db_port": "5432",
                    "db_username": "cognee",
                    "db_password": "cognee",
                }
            )

            await cognee.remember(["Cognee stores its metadata in Postgres."])
            print(await cognee.recall(query_text="Where is metadata stored?"))

        asyncio.run(main())
        ```

        The dictionary keys match the `DB_*` variables in the `.env` tab. To also route embeddings and the graph into the same Postgres instance, pair this with [`set_vector_db_config({"vector_db_provider": "pgvector"})`](/setup-configuration/vector-stores) and [`GRAPH_DATABASE_PROVIDER="postgres_demo"`](/setup-configuration/graph-stores#postgres) (the older value `postgres` is still accepted).
      </Tab>
    </Tabs>

    **Installation**: Install the Postgres extras:

    ```bash theme={null}
    pip install "cognee[postgres]"
    # or for binary version
    pip install "cognee[postgres-binary]"
    ```

    **Docker Setup**: Use the built-in Postgres service:

    ```bash theme={null}
    docker compose --profile postgres up -d
    ```

    **Docker Networking**: When running Cognee in Docker and Postgres on your host, set:

    ```dotenv theme={null}
    DB_HOST="host.docker.internal"
    ```

    **Migrations**: The Cognee API server runs startup migrations during its lifespan startup. For standalone scripts, CI, or deployments where you manage database lifecycle explicitly, run [`run_migrations`](/python-api/run-migrations) before serving traffic — especially the first time you point Cognee at a fresh external Postgres database, or after upgrading the `cognee` package:

    ```python theme={null}
    import cognee

    await cognee.run_migrations()
    ```
  </Accordion>

  <Accordion title="Neon Postgres">
    Neon works with Cognee through the normal `postgres` relational provider. Cognee can use Neon Postgres for relational metadata, document information, chunks, pgvector storage, and Postgres graph state. Neon requires SSL/TLS for connections.

    Before configuring Cognee, create a Neon project, branch, database, and role, then copy the connection string from **Connection Details** in the Neon dashboard. A Neon connection string usually looks like this:

    ```text theme={null}
    postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require
    ```

    If your deployment uses `DATABASE_URL`, set it to the Neon connection string:

    ```dotenv theme={null}
    DATABASE_URL="postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require"
    ```

    If you prefer split settings, map the same connection string into Cognee's `DB_*` variables:

    ```dotenv theme={null}
    DB_PROVIDER="postgres"
    DB_NAME="neondb"
    DB_HOST="ep-example.us-east-2.aws.neon.tech"
    DB_PORT="5432"
    DB_USERNAME="user"
    DB_PASSWORD="password"
    DATABASE_CONNECT_ARGS='{"ssl": "require", "timeout": 10}'
    ```

    Install Postgres support in the environment where Cognee runs:

    ```bash theme={null}
    pip install "cognee[postgres]"
    # or
    pip install "cognee[postgres-binary]"
    ```

    When using split settings, express Neon's `?sslmode=require` parameter as `DATABASE_CONNECT_ARGS='{"ssl": "require"}'`. `DATABASE_CONNECT_ARGS` must be valid JSON. Cognee forwards these arguments to the main relational engine, per-dataset PGVector engines, and the `GRAPH_DATABASE_PROVIDER="postgres_demo"` graph engine **(demo)**.

    One Neon database can also back PGVector and the Postgres graph store:

    ```dotenv theme={null}
    VECTOR_DB_PROVIDER="pgvector"
    GRAPH_DATABASE_PROVIDER="postgres_demo"
    ```

    <Warning>
      The Postgres graph store is a **demo feature** — in production, use a graph-native backend such as Kuzu or Neo4j. A production-ready adapter is available as a licensed product; book a call with our sales team at [cognee.ai](https://www.cognee.ai). See [Graph Stores](/setup-configuration/graph-stores) for details.
    </Warning>

    For PGVector, enable the extension once in the Neon database:

    ```sql theme={null}
    CREATE EXTENSION IF NOT EXISTS vector;
    ```

    **Application database vs source database**: `DATABASE_URL` or the `DB_*` variables configure Cognee's own application database. Cognee uses this database for its internal metadata and state. To ingest an external Postgres database as data, pass that source database connection to `cognee.add()` instead:

    ```python theme={null}
    await cognee.add(
        "postgresql://user:pass@host:5432/source_db",
        dataset_name="postgres_data",
    )
    ```

    That source database is separate from Cognee's application database.

    **Direct vs pooled Neon hosts**: use the direct Neon host for setup, schema migrations, and default Cognee connections. The direct hostname does not contain `-pooler`:

    ```dotenv theme={null}
    DATABASE_URL="postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require"
    ```

    Neon pooled hosts route through PgBouncer and contain `-pooler` in the hostname:

    ```dotenv theme={null}
    DATABASE_URL="postgresql://user:password@ep-example-pooler.us-east-2.aws.neon.tech/neondb?sslmode=require"
    ```

    Prefer the direct host unless you have a specific need for pooled, high-concurrency application traffic after setup. Run migrations against the direct endpoint only. Neon PgBouncer does not support every session-level operation migrations and maintenance may rely on, and Cognee maintenance operations such as `CREATE DATABASE` and `DROP DATABASE` cannot run through the pooler. If setup or migrations fail on a `-pooler` host, switch to the direct host and retry.

    You can verify the same credentials with `psql`:

    ```bash theme={null}
    psql "postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require"
    ```

    If you use Cognee's relational database migration features with Neon, keep the application database and migration source database separate. Use direct hosts for both while running migrations:

    ```dotenv theme={null}
    # Application DB: Cognee's internal metadata store
    DATABASE_URL="postgresql://user:password@ep-example.us-east-2.aws.neon.tech/neondb?sslmode=require"

    # Migration DB: source data to convert into Cognee's knowledge graph
    MIGRATION_DB_PROVIDER="postgres"
    MIGRATION_DB_HOST="ep-source.us-east-2.aws.neon.tech"
    MIGRATION_DB_PORT="5432"
    MIGRATION_DB_USERNAME="readonly_user"
    MIGRATION_DB_PASSWORD="readonly_password"
    MIGRATION_DB_NAME="source_app_db"
    ```

    Use a different `MIGRATION_DB_NAME` unless you intentionally want to migrate Cognee's own internal tables into the knowledge graph.
  </Accordion>

  <Accordion title="Turso (libSQL)">
    A libSQL database file *is* a SQLite file, so Turso is a drop-in for the SQLite backend: Cognee talks to it through the same `aiosqlite` driver, the same sqlite dialect, and the same sqlite-dialect Alembic migrations. No migration changes are needed when switching between SQLite and Turso.

    **Installation**: Install the Turso extra:

    ```bash theme={null}
    pip install "cognee[turso]"
    ```

    **Local / embedded**: A libSQL file stored on disk under the Cognee data directory (named by `DB_NAME`). This is identical to the SQLite backend:

    ```dotenv theme={null}
    DB_PROVIDER="turso"
    DB_NAME="cognee_db"
    ```

    **Remote (embedded replica)**: Set `DB_PROVIDER="turso"` and point at a hosted Turso database with `DB_TURSO_URL` and `DB_TURSO_AUTH_TOKEN`:

    ```dotenv theme={null}
    DB_PROVIDER="turso"
    DB_NAME="cognee_db"
    DB_TURSO_URL="libsql://<your-db>.turso.io"
    DB_TURSO_AUTH_TOKEN="<your-token>"
    ```

    In remote mode Cognee reads and writes a fast local replica through `aiosqlite` exactly as in local mode, while `libsql-experimental` handles embedded-replica sync with the hosted primary. The replica is seeded from the primary before first use, and Cognee attempts a sync after each write within the operation. Seeding and syncing run off the event loop and are best-effort: a slow or unreachable primary is logged and never blocks or breaks a database operation, and the local replica stays usable.

    <Note>
      The remote write path applies through `aiosqlite`; whether libSQL's sync propagates those writes to the hosted primary depends on the driver's replica write-capture and should be confirmed against a live Turso database. The local drop-in path is fully exercised offline.
    </Note>

    <Info>
      Turso is a SQLite-compatible drop-in for Cognee's core relational backend, but DLT-based ingestion connectors do not yet treat `DB_PROVIDER="turso"` the same as `sqlite`. The main add → cognify → search pipeline is covered; DLT connector support needs a follow-up.
    </Info>
  </Accordion>
</AccordionGroup>

## Advanced Options

<Accordion title="Migration Configuration">
  The `MIGRATION_DB_*` variables point to a **source** database that you want to extract and migrate **into** Cognee's knowledge graph. This is entirely separate from the application database (`DB_*`) that Cognee uses for its own internal metadata and state.

  | Variable | Application DB (`DB_*`)                        | Migration DB (`MIGRATION_DB_*`)           |
  | -------- | ---------------------------------------------- | ----------------------------------------- |
  | Purpose  | Cognee's internal metadata store               | Source data you want converted to a graph |
  | Contains | Cognee's own tables (documents, chunks, state) | Your application's tables and rows        |

  **Does the migration DB need to be a different database than the application DB?**

  In practice, use a different database (different `DB_NAME` / `MIGRATION_DB_NAME`) unless you intentionally want to migrate Cognee's own internal tables into the knowledge graph. They can still live on the same Postgres server as long as they are different databases.

  <Tabs>
    <Tab title="SQLite Source">
      Use this when your source data is in a SQLite file, regardless of what `DB_PROVIDER` is set to:

      ```dotenv theme={null}
      # Application DB (Cognee's internal store)
      DB_PROVIDER="postgres"
      DB_NAME="cognee_db"
      DB_HOST="127.0.0.1"
      DB_PORT="5432"
      DB_USERNAME="cognee"
      DB_PASSWORD="cognee"

      # Migration DB (your source data — a separate SQLite file)
      MIGRATION_DB_PROVIDER="sqlite"
      MIGRATION_DB_PATH="/path/to/migration/directory"
      MIGRATION_DB_NAME="my_app_data.sqlite"
      ```
    </Tab>

    <Tab title="Same Postgres Server">
      Use this when your source data is in a separate Postgres database on the same server as Cognee's application DB. Set `MIGRATION_DB_NAME` to a **different** database name for the usual case:

      ```dotenv theme={null}
      # Application DB (Cognee's internal store)
      DB_PROVIDER="postgres"
      DB_NAME="cognee_db"
      DB_HOST="127.0.0.1"
      DB_PORT="5432"
      DB_USERNAME="cognee"
      DB_PASSWORD="cognee"

      # Migration DB (your source data — different DB name on the same Postgres server)
      MIGRATION_DB_PROVIDER="postgres"
      MIGRATION_DB_HOST="127.0.0.1"
      MIGRATION_DB_PORT="5432"
      MIGRATION_DB_USERNAME="cognee"
      MIGRATION_DB_PASSWORD="cognee"
      MIGRATION_DB_NAME="my_app_db"   # usually different from DB_NAME above
      ```
    </Tab>

    <Tab title="Different Postgres Server">
      Use this when your source data lives on a separate Postgres instance:

      ```dotenv theme={null}
      # Migration DB (separate Postgres instance)
      MIGRATION_DB_PROVIDER="postgres"
      MIGRATION_DB_HOST="db.example.com"
      MIGRATION_DB_PORT="5432"
      MIGRATION_DB_USERNAME="readonly_user"
      MIGRATION_DB_PASSWORD="readonly_password"
      MIGRATION_DB_NAME="production_db"
      ```
    </Tab>
  </Tabs>

  See the [Relational Database Migration example](/examples/relational-db-migration) for a complete walkthrough of migrating schema and data into a knowledge graph.
</Accordion>

<Accordion title="Managed Postgres with SSL (connect args)">
  Managed Postgres providers (Neon, RDS/Aurora, Azure Database for PostgreSQL) often require SSL. Pass asyncpg/SQLAlchemy connection arguments through the `DATABASE_CONNECT_ARGS` environment variable, which takes a JSON object:

  ```dotenv theme={null}
  DATABASE_CONNECT_ARGS='{"ssl": "require", "timeout": 10}'
  ```

  These connect args are forwarded to Cognee's main relational engine, per-dataset PGVector engines, and the `GRAPH_DATABASE_PROVIDER="postgres_demo"` graph engine **(demo)**. The maintenance engine that runs CREATE/DROP DATABASE also uses the SSL setting. Leaving `DATABASE_CONNECT_ARGS` unset is a no-op, so in-cluster Postgres needs no change.

  The maintenance engine talks to Postgres over asyncpg, which expects an `ssl` key rather than libpq's `sslmode`; if you supply `sslmode`, its value is mapped to asyncpg's `ssl` for maintenance operations. For **Neon** specifically, the maintenance engine also rewrites a `-pooler.` host to its direct endpoint, because CREATE/DROP DATABASE cannot run through Neon's PgBouncer connection pooler.

  The value must be a valid JSON object; invalid JSON raises a configuration error.
</Accordion>

<Accordion title="SQLite connection pooling (POOL_ARGS)">
  `POOL_ARGS` is honored on the SQLite relational engine, not just on Postgres.

  **Leaving it unset changes nothing.** The SQLite engine still uses SQLAlchemy's `NullPool` (no connection reuse), the same 120-second driver connect timeout, and the same WAL / `synchronous=NORMAL` / `busy_timeout=120000` pragmas on every connection. Nothing needs to be configured unless you deliberately want a pool.

  **Opt into a bounded pool** by setting any of the pool-sizing keys, which switches the engine from `NullPool` to SQLAlchemy's default bounded pool:

  ```dotenv theme={null}
  POOL_ARGS='{"pool_size": 5, "max_overflow": 10}'
  ```

  The keys applied on the SQLite engine are `pool_size`, `max_overflow`, `pool_recycle`, `pool_timeout`, `pool_pre_ping`, and `poolclass`. Any other engine keyword in `POOL_ARGS` is ignored on SQLite so it cannot collide with the SQLite-specific connect args Cognee assembles.

  **`poolclass` accepts the `"nullpool"` string**, normalized to the `NullPool` class exactly as on the Postgres engine — so the same `POOL_ARGS` value means the same thing on both backends:

  ```dotenv theme={null}
  POOL_ARGS='{"poolclass": "nullpool"}'
  ```

  **Contradictory options fail fast.** `NullPool` takes no sizing arguments, so combining it with sizing keys raises a `TypeError` when the engine is created rather than silently picking one:

  ```dotenv theme={null}
  # invalid — raises TypeError at engine creation
  POOL_ARGS='{"poolclass": "nullpool", "pool_size": 5}'
  ```

  Pick one or the other: sizing keys for a bounded pool, or `"poolclass": "nullpool"` on its own.

  Enabling a bounded pool changes how many SQLite connections Cognee keeps open and its resource usage — each pooled connection holds a file handle and, on the async driver, a worker thread. It is also not a fix for connections abandoned by callers: a session that is never closed still holds its connection regardless of the pool class.
</Accordion>

<Accordion title="Backend Access Control">
  Enable per-user dataset isolation for multi-tenant scenarios.

  ```dotenv theme={null}
  ENABLE_BACKEND_ACCESS_CONTROL="true"
  ```

  This feature is available for both SQLite and Postgres.
</Accordion>

## Troubleshooting

<Accordion title="Common Issues">
  **Postgres Connectivity**: Verify the database is listening on `DB_HOST:DB_PORT` and credentials are correct:

  ```bash theme={null}
  psql -h 127.0.0.1 -U cognee -d cognee_db
  ```

  **Docker Networking**: Use `host.docker.internal` for host-to-container access on macOS/Windows.

  **SQLite Concurrency**: SQLite connections now open in WAL (Write-Ahead Logging) journal mode with `synchronous=NORMAL` and a 120-second busy timeout, and the driver connect timeout is also 120 seconds. This lets concurrent writers wait for the write lock (up to the busy timeout) instead of immediately failing, which greatly reduces the `sqlite3.OperationalError: database is locked` errors that could occur under Cognee's parallel `cognify()` writes. No configuration is required — these settings apply automatically to every SQLite connection, and they stay in place even if you change the pool class (see *SQLite connection pooling (POOL\_ARGS)* under [Advanced Options](#advanced-options)). WAL mode creates `-wal` and `-shm` sidecar files next to the database file; include them when backing up or copying the database. For heavier multi-user workloads, still prefer Postgres. Note that this only smooths out the parallel writes Cognee itself issues within a single process — it is not a mechanism for sharing the database between multiple Cognee processes.

  **SQLite File Locks on Windows (pruning/deleting)**: When pruning or deleting a SQLite database, Cognee now disposes the cached SQLAlchemy engine (clearing the relational-engine cache and forcing garbage collection) before removing the file, so the underlying connection releases the file handle. If a stubborn Windows file lock still prevents removal after the retries, deletion no longer raises — it logs a warning and continues. In that case, the SQLite file may remain on disk and can be removed manually after the process releases the handle.
</Accordion>

<Accordion title="Neon SSL and connect args">
  Neon requires SSL/TLS. If you use a full Neon connection string, keep `sslmode=require` in the URL:

  ```dotenv theme={null}
  DATABASE_URL="postgresql://user:password@host/neondb?sslmode=require"
  ```

  If you use split `DB_*` settings instead of `DATABASE_URL`, pass SSL through `DATABASE_CONNECT_ARGS`:

  ```dotenv theme={null}
  DB_PROVIDER="postgres"
  DB_NAME="neondb"
  DB_HOST="ep-example.us-east-2.aws.neon.tech"
  DB_PORT="5432"
  DB_USERNAME="user"
  DB_PASSWORD="password"
  DATABASE_CONNECT_ARGS='{"ssl": "require", "timeout": 10}'
  ```

  `DATABASE_CONNECT_ARGS` must be valid JSON. Invalid JSON raises a configuration error before Cognee connects.
</Accordion>

<Accordion title="Missing LLM API key">
  Operations that extract or answer over memory need an LLM provider. If `remember()`, `cognify()`, `recall()`, or related workflows fail because no LLM credentials are configured, set the provider API key in your environment:

  ```dotenv theme={null}
  LLM_API_KEY="sk-..."
  ```

  See [LLM Providers](/setup-configuration/llm-providers) for provider-specific settings.
</Accordion>

<Accordion title="asyncpg prepared-statement / connection-pooler errors">
  On Postgres and PGVector, Cognee connects through the asyncpg driver, which caches prepared statements **per connection**. When you place a **transaction-mode** connection pooler in front of Postgres — PgBouncer in `transaction` mode, or the Supabase / Neon connection poolers — a single client connection is multiplexed across many short-lived server backends. The cached statement names can then collide or vanish between checkouts, surfacing as:

  ```text theme={null}
  asyncpg.exceptions.DuplicatePreparedStatementError: prepared statement "__asyncpg_stmt_1__" already exists
  ```

  or as intermittent `connection is closed` / `InterfaceError` pool errors under concurrency.

  **Preferred fix — use the direct (session-mode) endpoint.** Point `DB_HOST` (and `VECTOR_DB_HOST`) at the direct Postgres endpoint rather than the transaction pooler. Cognee already does this for its own `CREATE`/`DROP DATABASE` maintenance work, rewriting a Neon `-pooler.` host to the direct endpoint, because those statements cannot run through PgBouncer.

  **If you must route through a transaction-mode pooler**, disable asyncpg's statement cache through [`DATABASE_CONNECT_ARGS`](/setup-configuration/relational-databases):

  ```dotenv theme={null}
  DATABASE_CONNECT_ARGS='{"statement_cache_size": 0, "prepared_statement_cache_size": 0}'
  ```

  These connect args are forwarded to the main relational engine, the per-dataset PGVector engines, and the `GRAPH_DATABASE_PROVIDER="postgres_demo"` graph engine **(demo)**, so all three asyncpg connections stop caching prepared statements. You can combine them with the SSL keys in the same JSON object (for example `{"ssl": "require", "statement_cache_size": 0}`).
</Accordion>

<Accordion title="Too many connections (Neon free tier / hosted Postgres limits)">
  When the same Postgres backs relational metadata, PGVector, and the `GRAPH_DATABASE_PROVIDER="postgres_demo"` graph store, Cognee can open more connections than a low connection limit allows — most commonly on Neon's free tier — surfacing as:

  ```text theme={null}
  asyncpg.exceptions.TooManyConnectionsError: sorry, too many clients already
  # or: FATAL: remaining connection slots are reserved for non-replication superuser connections
  ```

  Cognee opens a **separate SQLAlchemy connection pool per engine**, and the `DB_*` / `DATABASE_CONNECT_ARGS` settings are reused across all of them:

  * **Relational engine** — QueuePool with `pool_size=5` and `max_overflow=35` (up to 40 connections), plus `pool_pre_ping=True` and `pool_recycle=280`.
  * **PGVector** — when backend access control is off and the relational provider is Postgres, PGVector **reuses the relational engine** and adds no connections of its own. It creates its own pool only under `ENABLE_BACKEND_ACCESS_CONTROL="true"` (one engine per dataset), sized from `VECTOR_POOL_ARGS` if set, otherwise from `POOL_ARGS`, and falling back to `pool_size=2`, `max_overflow=20` when neither is set.
  * **Postgres graph store (demo)** — always its own pool, with leaner defaults `pool_size=2` and `max_overflow=20` (up to 22 connections). Under access control it is also created per dataset.
  * **SQL cache engine** — the session cache builds its own engine whenever caching (or usage logging) is on and `CACHE_BACKEND` is `sqlite` or `postgres`, and it reads the relational `POOL_ARGS` for it. It adds no defaults of its own, so with `POOL_ARGS` unset the engine takes SQLAlchemy's own pool defaults. Only a Postgres cache consumes slots on the server — either `CACHE_BACKEND="postgres"` or a `CACHE_DB_URL` pointing at Postgres.

  So a single-user setup with `GRAPH_DATABASE_PROVIDER="postgres_demo"` can reach roughly 40 + 22 connections at peak, plus the cache engine's pool when the cache is also in Postgres, and backend access control multiplies the per-dataset pools by the number of datasets.

  When sizing for concurrency, note that authenticated API traffic no longer doubles its draw on the relational pool: an API-key request used to check out a second connection and hold it for the request's full lifetime, so it needed two slots at once — see the *Connections stuck in idle in transaction, or the pool deadlocking under concurrency* accordion below.

  **Shrink the pools** to fit the server's `max_connections`. `POOL_ARGS` applies to the relational engine, is reused by the Postgres graph engine and the SQL cache engine, and also sizes per-dataset PGVector engines whenever `VECTOR_POOL_ARGS` is unset; `VECTOR_POOL_ARGS` applies to per-dataset PGVector engines only, where it takes precedence over `POOL_ARGS`. Both take a JSON object:

  ```dotenv theme={null}
  POOL_ARGS='{"pool_size": 2, "max_overflow": 4}'
  VECTOR_POOL_ARGS='{"pool_size": 2, "max_overflow": 2}'
  ```

  To minimize idle connections entirely, disable pooling so each operation opens and closes its own connection:

  ```dotenv theme={null}
  POOL_ARGS='{"poolclass": "nullpool"}'
  ```

  `poolclass` is given as the string `"nullpool"` (case-insensitive), which Cognee normalizes to SQLAlchemy's `NullPool` class. The relational engine and the SQL cache engine both accept that string form, so this single value covers both — there is no separate cache-pool setting. On releases before version 1.4.2, the cache engine passed the string to SQLAlchemy unchanged and failed at startup with `CacheConnectionError: Failed to initialize SQL cache engine for …: 'str' object has no attribute '__dict__'`; if you hit that, upgrade rather than dropping `poolclass` from `POOL_ARGS`.

  The tradeoff is that under `NullPool` every SQLAlchemy session becomes a full connection setup — TCP, TLS, and SCRAM-SHA-256, about 14 ms of event-loop CPU each on asyncpg 0.30 before any network latency — so what you pay is set by how many sessions an operation opens rather than by a pool size. For sizing, an `add()` costs roughly **4 relational sessions and 7 statements per file**, measured over a 164-PDF add with real s3fs and asyncpg against Postgres with `poolclass: nullpool`. Those are peak-concurrency multipliers as much as CPU costs: the pipeline processes items concurrently, so a large add opens several of these connections at once against your server's `max_connections`. That figure lands as of the fix in [PR #4589](https://github.com/topoteretes/cognee/pull/4589), which cut it from \~11 sessions and \~15 statements per file; on releases before it, size against the higher number.

  Alternatively, route application traffic through Neon's pooled (`-pooler`) endpoint, which supports far more concurrent clients — but disable asyncpg's prepared-statement cache when doing so (see the *asyncpg prepared-statement / connection-pooler errors* accordion above), and keep setup and migrations on the direct endpoint.

  If the pool fills up even though your workload is small, check whether the connections are stuck in `idle in transaction` — see the accordion below.

  A pool that exhausts itself well below the sizes above is usually not under-sized: it can be connection *overlap* inside a single request, where one call holds a pooled connection while acquiring a second one — the pool fills at roughly half the concurrency its size suggests, and deadlocks once concurrency reaches `pool_size + max_overflow`. Shrinking `POOL_ARGS` does not help there — it makes the deadlock arrive sooner. That class of overlap is fixed in version 1.4.2, so upgrade rather than resize if this is what you are seeing.
</Accordion>

<Accordion title="Connections stuck in idle in transaction, or the pool deadlocking under concurrency">
  If Postgres accumulates backends sitting in `idle in transaction` and the pool eventually exhausts itself — with failures spreading to every request, authentication included, because the API-key lookup is itself a database query — several distinct Cognee-side causes produced that symptom, and all of them are fixed in version 1.4.2. Confirm the symptom from the server:

  ```sql theme={null}
  SELECT state, count(*) FROM pg_stat_activity WHERE datname = 'cognee_db' GROUP BY state;
  ```

  (Substitute your `DB_NAME` for `cognee_db` if you changed it.)

  There is nothing to configure: upgrade to version 1.4.2 or later (see the [changelog](/changelog)) and redeploy. To verify, re-run the query above while authenticated traffic is flowing — authenticated requests should no longer hold a connection beyond their API-key lookup. Any `idle in transaction` backends that remain are not coming from the fixed causes. The remaining causes are:

  * **Your own application code** holding a session open across a slow `await`.
  * **Cognee's background pipeline runs** being abandoned at shutdown, a separate known issue.
</Accordion>

<Accordion title="DatabaseNotCreatedError (Postgres)">
  For Postgres, the database named in `DB_NAME` must already exist before Cognee connects. Unlike SQLite, Cognee does **not** issue `CREATE DATABASE` for Postgres — it connects directly to `DB_NAME` and creates only the tables. If the database itself is missing, create it once with your Postgres tooling:

  ```bash theme={null}
  createdb -h 127.0.0.1 -U cognee cognee_db
  # or: psql -h 127.0.0.1 -U cognee -c "CREATE DATABASE cognee_db;"
  ```

  (The built-in Docker Postgres service from `docker compose --profile postgres up -d` already creates this database for you.)

  If you specifically see `DatabaseNotCreatedError` ("The database has not been created yet. Please call `await setup()` first."), Cognee reached Postgres but its tables (e.g. `principals`) don't exist yet. Run setup once to initialize the schema:

  ```python theme={null}
  from cognee.modules.engine.operations.setup import setup

  await setup()
  ```

  `remember()` creates the tables automatically through its underlying `add()` and `cognify()` steps, so this typically only surfaces when calling `search()` or `recall()` first on a fresh database.
</Accordion>

## When to Use Each

* **SQLite**: Local development, single-user applications, simple deployments
* **Postgres**: Production environments, multi-user applications, external hosting, co-location with pgvector
* **Turso (libSQL)**: A SQLite drop-in when you want a hosted, replicated database — the same aiosqlite driver and Alembic migrations apply unchanged, with optional embedded-replica sync against a remote Turso primary

<Columns cols={3}>
  <Card title="Vector Stores" icon="database" href="/setup-configuration/vector-stores">
    Configure vector databases for embedding storage
  </Card>

  <Card title="Graph Stores" icon="network" href="/setup-configuration/graph-stores">
    Set up graph databases for knowledge graphs
  </Card>

  <Card title="Overview" icon="settings" href="/setup-configuration/overview">
    Return to setup configuration overview
  </Card>
</Columns>
