Skip to main content
Airhouse is a server layer on top of two open technologies: DuckDB runs your queries, and DuckLake stores your tables. Airhouse adds the Postgres wire protocol, authentication, roles, multi-tenancy, resource limits, and an Admin API. It doesn’t have its own query engine, SQL parser, or storage format.

Key concepts

An isolated Airhouse workspace with its own catalog and its own storage location. You pick the database with the standard Postgres database connection parameter (for example psql -d demo). Users are created per database, and a user can only connect to the database it belongs to.
Inside every session, your persistent tables live in the DuckLake catalog named lake. Each session starts in lake with the default schema main, so orders and the fully qualified lake.main.orders refer to the same table. Objects created outside lake (for example in DuckDB’s in-memory memory or temp catalogs) are session-local and disappear when you disconnect.
Every user has exactly one role: reader (queries only), writer (queries and data changes), or admin (everything, including creating and dropping tables). See Users, roles, and security.
For applications that serve many end users, a service account mints short-lived database credentials on demand, so your platform never stores a long-lived password per user. See Service accounts.

Components

The data plane and control plane are the same binary. On a single host they run in one process. On Kubernetes they run as separate Deployments so each can scale on its own.

The life of a query

  1. Connect. The client opens a Postgres connection. The database parameter names the Airhouse database. If it doesn’t exist, the connection fails with SQLSTATE 3D000.
  2. Authenticate. Airhouse verifies the password with SCRAM-SHA-256 against the registry. Expired or revoked credentials are rejected.
  3. Open a session. The connection gets a DuckDB session with the database’s DuckLake catalog attached as lake. Each connection’s work is isolated from other connections.
  4. Authorize. Every statement is checked against the user’s role (reader, writer, or admin) and, for schema-scoped writers, against their allowed schemas. It’s also checked against rate and concurrency limits. Refused statements never reach the engine.
  5. Execute. DuckDB plans and runs the query. DuckLake uses the catalog to find which Parquet files hold the data, and DuckDB reads only the files and columns it needs.
  6. Return results. Results stream back in the Postgres wire format, with standard command tags such as SELECT 42 and INSERT 0 3.
  7. Write, if the statement changes data. DuckLake writes new Parquet files to object storage, then commits the change to the catalog as a new snapshot.

Multi-tenancy

An Airhouse database (a tenant in the Admin API) is the unit of isolation:
  • Separate catalog. Each database has its own DuckLake catalog in its own Postgres database, accessed with its own Postgres role.
  • Separate storage prefix. Each database’s Parquet files live under their own prefix in the bucket.
  • Separate users. Users and minted credentials belong to exactly one database. Connecting to another database with them fails.
  • Separate limits. Operators can cap concurrent queries and queries per minute per database, as well as per user. See Query limits.
Clients choose the database with the standard Postgres database parameter, so every Postgres tool works without changes.

Transactions and consistency

  • ACID transactions. DuckLake makes every commit atomic. A statement, or a BEGIN … COMMIT block, either applies completely or not at all.
  • Snapshots. Each commit creates a new catalog snapshot. Queries read a consistent snapshot, and you can query earlier ones with time travel.
  • Serialized writes per database. Airhouse commits writes to the same database one at a time. Reads never wait for writes, and writes to different databases don’t affect each other.
  • Visibility. Other connections see a change once it’s committed. Each server process can also cache SELECT results (60 seconds by default) and clears its cache when it runs a write to that database. With several server replicas, a replica that didn’t handle the write can return a cached result until the cache entry expires. If you need read-after-write across connections, lower or disable the cache with [cache] ttl_secs or enabled.

Where your data lives

Airhouse runs in your environment, and your data stays in infrastructure you choose: Because the data is standard Parquet and the catalog is standard DuckLake, it stays readable with DuckDB and DuckLake tooling. For encryption at rest, use your object storage’s and Postgres’ encryption features. See Security.

Deployment topologies

Single node

One container runs both planes. It’s the simplest setup, suited to evaluation, development, and small workloads. See Installation.

Kubernetes

The Helm chart runs the data plane as an autoscaled Deployment behind HAProxy, and the control plane as its own Deployment. Data-plane pods are stateless: all durable state is in Postgres and object storage, so pods can be added, removed, or restarted freely. See Installation.