ZFS Integration
On a ZFS pool, ftm-lakehouse can create a tuned ZFS dataset per storage type when a dataset is first touched. The transport – a local zfs subprocess, or a host-side socket agent for containers – is the external zfs-agent package; ftm-lakehouse contributes the tuning and the call.
Local Mode
If the lakehouse runs directly on a ZFS-backed filesystem, enable ZFS dataset creation:
export LAKEHOUSE_URI=/zpools/tank/lakehouse
export LAKEHOUSE_ON_ZFS=1
export LAKEHOUSE_ZFS_POOL=zpools/tank/lakehouse
LAKEHOUSE_ZFS_POOL is the ZFS dataset path (no leading slash) the per-dataset children are created under; it must match the pool layout.
Each new dataset gets these ZFS datasets (zfs create via zfs-agent):
| ZFS Dataset | recordsize | compression | Purpose |
|---|---|---|---|
{dataset}/ |
(inherited) | (inherited) | Parent, with atime=off, xattr=sa, dnodesize=auto |
{dataset}/archive |
1M | zstd-9 |
Content-addressed file storage |
{dataset}/statements |
1M | off |
Delta Lake parquet – already zstd-compressed |
Both children also set sync=standard and logbias=throughput.
Separate statements pool
With the statements tables on their own volume (LAKEHOUSE_STATEMENTS_URI), name the ZFS dataset path that volume is mounted from, as LAKEHOUSE_ZFS_POOL names the one of LAKEHOUSE_URI:
export LAKEHOUSE_URI=/zpools/hdd/lakehouse
export LAKEHOUSE_STATEMENTS_URI=/zpools/ssd/lakehouse
export LAKEHOUSE_ON_ZFS=1
export LAKEHOUSE_ZFS_POOL=zpools/hdd/lakehouse
export LAKEHOUSE_ZFS_STATEMENTS_POOL=zpools/ssd/lakehouse
The parent and archive are created under LAKEHOUSE_ZFS_POOL; the statements child under a parent of its own ({dataset}/, same properties) in LAKEHOUSE_ZFS_STATEMENTS_POOL. On ZFS the two statements settings go together – one without the other fails at startup. A socket agent creates under one --pool only, so this works in local mode, or with an agent whose pool holds both paths.
Mountpoint Ownership
ZFS creates mountpoints owned by root:root. Set the zfs-agent package's ZFS_OWNER to chown new mountpoints; unset, no chown happens:
- Local mode: set
ZFS_OWNERwhereverftm-lakehouseorftm-lakehouse zfs initruns. - Socket mode: ownership is the agent's – pass
--ownertozfs-agentor setZFS_OWNERwhere it runs. The client sends none.
Socket Agent Mode
Containers typically lack the ZFS tools. Instead, the host runs the zfs-agent daemon, which listens on a Unix socket and runs zfs create for the container.
flowchart LR
subgraph container["Docker Container"]
app["ftm-lakehouse<br/>ensure_zfs_dataset()"]
end
subgraph host["Host"]
agent["zfs-agent"]
zfs["zfs create ..."]
agent --> zfs
end
app -- "JSON over /run/zfs.sock" --> agent
On the host (requires pip install zfs-agent):
The agent checks the peer UID (SO_PEERCRED), restricts the socket to 0600, allowlists properties and restricts creates to its pool – see the zfs-agent documentation for the protocol and its ZFS_* environment.
Mount the socket into the container and set the environment:
services:
api:
image: ftm-lakehouse
user: "1000:1000"
environment:
LAKEHOUSE_URI: /zpools/tank/lakehouse
LAKEHOUSE_ON_ZFS: "1"
LAKEHOUSE_ZFS_POOL: zpools/tank/lakehouse
ZFS_SOCKET: /run/zfs.sock
volumes:
- /run/zfs.sock:/run/zfs.sock
- /zpools/tank/lakehouse:/zpools/tank/lakehouse
The container's user: UID must match the agent's --allowed-uid – peer credentials cross the bind-mounted socket unchanged. With ZFS_SOCKET set, creates go over the socket instead of a local zfs subprocess.
Manual Initialization
To create a dataset's ZFS datasets without starting the application:
This creates the parent, archive and statements ZFS datasets. --pool defaults to LAKEHOUSE_ZFS_POOL; the statements dataset goes to LAKEHOUSE_ZFS_STATEMENTS_POOL when that is set.
Environment Variables
Lakehouse-side: LAKEHOUSE_ON_ZFS, LAKEHOUSE_ZFS_POOL and LAKEHOUSE_ZFS_STATEMENTS_POOL – see the configuration reference. Transport-side (ZFS_SOCKET, ZFS_OWNER, ZFS_POOL, ZFS_ALLOWED_UID, ZFS_EXTRA_PROPS, ZFS_LOG_LEVEL): the zfs-agent package.