Storage Requirements
A branch is only as fast and as cheap as the storage under it. The two methods need different things from it.
Database branches
Clone branches share the source's blocks only on file systems with
reflinks:
| File system | Copy-on-write clone |
|---|---|
XFS with reflink=1 (the default of modern mkfs.xfs) | yes |
| Btrfs | yes |
| ZFS 2.2+ | yes |
| ext4 | no: a branch is a full copy, made by the kernel |
Most CSI drivers format volumes as ext4 unless the StorageClass asks for
another file system. Set csi.storage.k8s.io/fstype: xfs:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-xfs
provisioner: <your CSI driver>
parameters:
csi.storage.k8s.io/fstype: xfs
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
XFS on ordinary network block storage is the sweet spot for database branches: a 10 GB branch in about 0.13 seconds, with no extra space.
Remember to set file_copy_method: clone for the standbys too, see
Replicas.
Things to know
-
ZFS counts clones against the volume's size. On ZFS, a cloned database shares blocks in the pool but counts in full against the volume's quota (the PVC size), on the primary and on every standby. In a test with a 10 GiB volume, each branch of a 1.5 GB database (277 MB on disk with lz4) added 277 MB to the volume's usage, while the pool's free space barely changed. After 39 branches the volume was full, and the primary and the standby both stopped with "disk quota exceeded". Branches can't be deleted while PostgreSQL is down.
ZFS volumes are thin-provisioned, so a larger size costs no disk: size them for the databases and all their branches. The create dialog warns when an instance is on ZFS. To recover a full instance, raise its storage size; with OpenEBS ZFS LocalPV the volumes grow online and the pods start again within a minute. Or use XFS, which counts shared blocks once.
-
XFS fragmentation. Writing to the source after branching fragments its files, and later branches of it get slower (from 0.13 s to about 2 s for 10 GB in our tests).
VACUUM FULLof the source brings it back.
Instance branches
Snapshot branches need Kubernetes storage that can snapshot a volume and
create a new volume from that snapshot without copying the data:
copy-on-write snapshots and clones, exposed through the
CSI snapshot API
with a VolumeSnapshotClass for the instance's storage.
| Storage | Fit |
|---|---|
| OpenEBS ZFS LocalPV | instant snapshots and clones; a branch runs on the node of the snapshotted volume |
| Portworx, Ceph RBD through Rook, NetApp ONTAP through Trident | instant, space-efficient snapshots and clones across nodes |
| Block storage whose "clone" copies the volume | a copy, not a branch: minutes per branch and the full size every time |
With OpenEBS ZFS LocalPV, snapshots and clones live on the node of the source volume, so every instance branch runs on that node. To branch at scale across nodes, use a storage platform with instant clones across nodes.
Example StorageClass and VolumeSnapshotClass for OpenEBS ZFS LocalPV:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: openebs-zfspv
provisioner: zfs.csi.openebs.io
parameters:
poolname: zfspv-pool
fstype: zfs
recordsize: 16k
compression: lz4
dedup: "off"
# Without it each volume, and each clone, reserves its full size in the pool.
thinprovision: "yes"
allowVolumeExpansion: true
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: openebs-zfspv
annotations:
snapshot.storage.kubernetes.io/is-default-class: "true"
driver: zfs.csi.openebs.io
deletionPolicy: Delete
Retained volumes on ZFS
On ZFS, a volume made from a snapshot keeps the snapshot, and the snapshot
keeps the volume it was taken of. When a rollback or promote
replaces a volume that restore points still need, the plugin sets the old
PersistentVolume to Retain, labels it
plugin-branching.plugins.openeverest.io/retained-from, and releases it
once no snapshot refers to it. Delete restore points you no longer need.
Branches aren't free forever
A branch owns only what it writes. But the source's blocks that change after the branch point stay allocated while the branch exists, so a long-lived branch slowly costs space. Give branches a lifetime: delete them when the task, job or pull request is done.