Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7666d220c5 | ||
|
|
baa5e62bbe | ||
|
|
503d2a4876 | ||
|
|
48407aa202 | ||
|
|
ce6e8ba2cb | ||
|
|
96387108ff | ||
|
|
119b2c77ef | ||
|
|
1d2248139d | ||
|
|
0a9d19a7c6 | ||
|
|
082092c4ba |
@@ -67,6 +67,20 @@ spec:
|
||||
# and slower than the lz4/zstd this build also accepts). Compresses the
|
||||
# full-page images that dominate WAL volume after each checkpoint.
|
||||
wal_compression: "zstd"
|
||||
# A WAL segment is a fixed 16 MiB file, and archive_timeout forces a switch
|
||||
# on a timer -- the segment is then written and archived at its full size
|
||||
# no matter how little real WAL it holds. CNPG defaults to 5min, i.e. up
|
||||
# to 288 segments/day = 3.2 GB/day here, most of it padding.
|
||||
#
|
||||
# 15min, NOT the 30min used elsewhere in this rollout. This cluster holds
|
||||
# credentials, sessions and enrolment state, so its RPO is worth more than
|
||||
# the difference: 30min would save a further 0.3 GB/day, which does not
|
||||
# justify doubling the worst-case loss window on this data.
|
||||
#
|
||||
# It does carry a synchronous standby (instances: 2), so the realistic
|
||||
# exposure is losing both instances, not one -- but the conservative value
|
||||
# is cheap here and the aggressive one buys almost nothing.
|
||||
archive_timeout: "15min"
|
||||
# Async replication makes CNPG switchover slow because the standby has
|
||||
# to chase missing WAL via barman-cloud and replay queued records
|
||||
# before it can promote. With one synchronous standby, the standby is
|
||||
|
||||
@@ -53,6 +53,28 @@ spec:
|
||||
# and slower than the lz4/zstd this build also accepts). Compresses the
|
||||
# full-page images that dominate WAL volume after each checkpoint.
|
||||
wal_compression: "zstd"
|
||||
# THE dominant write source on this cluster, by three orders of magnitude.
|
||||
#
|
||||
# A WAL segment is a fixed 16 MiB file. archive_timeout forces a segment
|
||||
# switch on a timer so the archive never lags reality by more than that
|
||||
# interval -- and the switched-out segment is written and archived at its
|
||||
# full 16 MiB regardless of how few bytes of real WAL it contains.
|
||||
#
|
||||
# CNPG defaults this to 5min, i.e. up to 288 segments/day = 4.6 GB/day.
|
||||
# Measured here: this database is 21 MB and generates 4.5 MB/day of actual
|
||||
# WAL, while its RBD volume takes 211 MB/hour. Essentially every byte
|
||||
# written is padding -- roughly 1100x amplification.
|
||||
#
|
||||
# 30min cuts that to 48 segments/day. The cost is RPO: on total loss of
|
||||
# the cluster, up to 30min of Grafana state is unrecoverable rather than
|
||||
# 5min. Grafana's state here is dashboards, annotations and preferences,
|
||||
# and the write rate says it changes ~4.5 MB/day -- so that window is
|
||||
# nearly always empty anyway.
|
||||
#
|
||||
# Applied to gf-pg FIRST, alone, as a staged rollout: it is the lowest-risk
|
||||
# cluster and has the cleanest signal, since its real WAL is small enough
|
||||
# that any change in volume is unambiguously this parameter.
|
||||
archive_timeout: "30min"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
|
||||
@@ -86,8 +86,26 @@ spec:
|
||||
# inserts scattered across pages, so full-page writes after each
|
||||
# checkpoint dominate WAL volume; compression cuts that directly.
|
||||
#
|
||||
# Measured on this cluster: full-page images are 62.4% of WAL bytes, so
|
||||
# this is the single largest lever on recorder write volume.
|
||||
# Full-page images really are ~62% of WAL here: 61.9% measured as a
|
||||
# steady-state delta over one checkpoint cycle, against 62.4% lifetime.
|
||||
#
|
||||
# But switching pglz -> zstd changed NOTHING. Measured by
|
||||
# pg_current_wal_lsn advance over 20min windows: 4911 and 4976 MB/day
|
||||
# before, 5002 MB/day after.
|
||||
#
|
||||
# The reason is that `wal_compression: "on"` was never "no compression" --
|
||||
# it is pglz, which was already compressing these page images. zstd only
|
||||
# changes the ratio, and on recorder pages that margin is too small to
|
||||
# measure. The original framing of this change (that the clusters "were
|
||||
# not getting compression") was wrong; they were.
|
||||
#
|
||||
# Note the 62% figure is an upper bound: it is computed as
|
||||
# wal_fpi * 8192 / wal_bytes, which assumes uncompressed 8 KiB pages. With
|
||||
# compression active the true byte share is lower, which is consistent
|
||||
# with there being little headroom left for a better algorithm.
|
||||
#
|
||||
# Kept because zstd is not worse than pglz and costs nothing. It is simply
|
||||
# not a lever. The real one on this cluster was archive_timeout, below.
|
||||
#
|
||||
# zstd, not "on": the boolean spelling resolves to pglz, the original
|
||||
# 2016-era algorithm, which is both slower and weaker than the two
|
||||
@@ -95,9 +113,35 @@ spec:
|
||||
# best ratio on 8 KiB page images, and the CPU cost lands on a cluster
|
||||
# that is IO-bound on QLC NVMe, not CPU-bound.
|
||||
wal_compression: "zstd"
|
||||
# A WAL segment is a fixed 16 MiB file, and archive_timeout forces a switch
|
||||
# on a timer -- the segment is then written and archived at its full size
|
||||
# no matter how little real WAL it holds. CNPG defaults to 5min, i.e. up
|
||||
# to 288 segments/day = 4.6 GB/day that is largely padding.
|
||||
#
|
||||
# Verified on gf-pg first (the staged rollout): 211 -> 41 MB/hour, an 81%
|
||||
# cut, with archived segments falling from ~12/hour to ~1.5/hour in step.
|
||||
# Two independent signals moving together is what confirms the volume was
|
||||
# padding rather than data.
|
||||
#
|
||||
# The cost is RPO: up to 30min of WAL is unrecoverable if this cluster and
|
||||
# its volume are lost outright. This is a single-instance cluster, so the
|
||||
# archive is the only off-node copy.
|
||||
archive_timeout: "30min"
|
||||
# Fewer checkpoints => fewer full-page writes, since a page is only
|
||||
# fully-logged on its first modification after a checkpoint. Doubling the
|
||||
# interval halves how often each hot page pays that 8 KiB toll.
|
||||
# fully-logged on its first modification after a checkpoint.
|
||||
#
|
||||
# That reasoning did not show up in WAL volume: 15min -> 30min moved the
|
||||
# WAL rate not at all (4911/4976 -> 5002 MB/day). The likely reason is
|
||||
# that this workload is append-mostly -- a page is dirtied while it fills
|
||||
# and then not touched again, so its full-page image is paid once
|
||||
# regardless of how often checkpoints run. Interval only helps pages that
|
||||
# are re-dirtied across many cycles, which is not what a recorder does.
|
||||
#
|
||||
# Kept, but NOT because WAL improved. checkpoint_timeout also governs
|
||||
# data-file writes at checkpoint, and those are unmeasured here: this
|
||||
# volume takes ~11 GB/day against 4.9 GB/day of WAL, so most of its
|
||||
# traffic is outside what the WAL measurement covers. Revisit with a
|
||||
# data-file measurement before treating this value as justified.
|
||||
#
|
||||
# Recovery cost is bounded by how much WAL a single interval produces,
|
||||
# not by the interval itself: at the measured rate 30min is well under
|
||||
|
||||
@@ -46,6 +46,20 @@ spec:
|
||||
# and slower than the lz4/zstd this build also accepts). Compresses the
|
||||
# full-page images that dominate WAL volume after each checkpoint.
|
||||
wal_compression: "zstd"
|
||||
# A WAL segment is a fixed 16 MiB file, and archive_timeout forces a switch
|
||||
# on a timer -- the segment is then written and archived at its full size
|
||||
# no matter how little real WAL it holds. CNPG defaults to 5min, i.e. up
|
||||
# to 288 segments/day = 4.6 GB/day that is largely padding.
|
||||
#
|
||||
# Verified on gf-pg first (the staged rollout): 211 -> 41 MB/hour, an 81%
|
||||
# cut, with archived segments falling from ~12/hour to ~1.5/hour in step.
|
||||
# Two independent signals moving together is what confirms the volume was
|
||||
# padding rather than data.
|
||||
#
|
||||
# The cost is RPO: up to 30min of WAL is unrecoverable if this cluster and
|
||||
# its volume are lost outright. This is a single-instance cluster, so the
|
||||
# archive is the only off-node copy.
|
||||
archive_timeout: "30min"
|
||||
# Write-reduction tuning. These databases are largely idle, and the
|
||||
# Postgres defaults favour throughput over storage that wears out.
|
||||
#
|
||||
|
||||
@@ -27,7 +27,7 @@ spec:
|
||||
# Pinned to 7.4-alpine to match the LXC 225 compose exactly.
|
||||
# Any redis major-version bump is a separate migration.
|
||||
- name: redis
|
||||
image: redis:7.4.10-alpine # {"$imagepolicy": "flux-system:redis-immich"}
|
||||
image: redis:7.4.11-alpine # {"$imagepolicy": "flux-system:redis-immich"}
|
||||
args:
|
||||
- redis-server
|
||||
- --save
|
||||
|
||||
@@ -58,6 +58,18 @@ spec:
|
||||
# and slower than the lz4/zstd this build also accepts). Compresses the
|
||||
# full-page images that dominate WAL volume after each checkpoint.
|
||||
wal_compression: "zstd"
|
||||
# A WAL segment is a fixed 16 MiB file, and archive_timeout forces a switch
|
||||
# on a timer -- the segment is then written and archived at its full size
|
||||
# no matter how little real WAL it holds. CNPG defaults to 5min, i.e. up
|
||||
# to 288 segments/day = 4.1 GB/day that is largely padding.
|
||||
#
|
||||
# Verified on gf-pg first (the staged rollout): 211 -> 41 MB/hour, an 81%
|
||||
# cut, with archived segments falling from ~12/hour to ~1.5/hour in step.
|
||||
#
|
||||
# The cost is RPO: up to 30min of WAL is unrecoverable if both instances
|
||||
# are lost. The documents themselves live on the media PVC, not in
|
||||
# Postgres -- this database holds their metadata.
|
||||
archive_timeout: "30min"
|
||||
# See ak-pg cluster.yaml for the rationale on synchronous replication.
|
||||
synchronous:
|
||||
method: any
|
||||
|
||||
@@ -25,7 +25,7 @@ spec:
|
||||
type: RuntimeDefault
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:8.10.0-alpine # {"$imagepolicy": "flux-system:redis-paperless"}
|
||||
image: redis:8.10.1-alpine # {"$imagepolicy": "flux-system:redis-paperless"}
|
||||
args:
|
||||
- redis-server
|
||||
- --save
|
||||
|
||||
@@ -36,6 +36,20 @@ spec:
|
||||
# and slower than the lz4/zstd this build also accepts). Compresses the
|
||||
# full-page images that dominate WAL volume after each checkpoint.
|
||||
wal_compression: "zstd"
|
||||
# A WAL segment is a fixed 16 MiB file, and archive_timeout forces a switch
|
||||
# on a timer -- the segment is then written and archived at its full size
|
||||
# no matter how little real WAL it holds. CNPG defaults to 5min, i.e. up
|
||||
# to 288 segments/day = 4.6 GB/day that is largely padding.
|
||||
#
|
||||
# Verified on gf-pg first (the staged rollout): 211 -> 41 MB/hour, an 81%
|
||||
# cut, with archived segments falling from ~12/hour to ~1.5/hour in step.
|
||||
# Two independent signals moving together is what confirms the volume was
|
||||
# padding rather than data.
|
||||
#
|
||||
# The cost is RPO: up to 30min of WAL is unrecoverable if this cluster and
|
||||
# its volume are lost outright. This is a single-instance cluster, so the
|
||||
# archive is the only off-node copy.
|
||||
archive_timeout: "30min"
|
||||
# Write-reduction tuning. These databases are largely idle, and the
|
||||
# Postgres defaults favour throughput over storage that wears out.
|
||||
#
|
||||
|
||||
@@ -50,12 +50,39 @@ spec:
|
||||
# clone + subsequent steps share the workspace PVC and the k8s backend
|
||||
# can schedule them on different nodes -> an RWO volume then fails to
|
||||
# mount on the second node. CephFS (RWX) lets the workspace follow
|
||||
# the pipeline across nodes. The -ephemeral variant (reclaim Delete,
|
||||
# rook-ceph-cluster/storageclass.yaml) is REQUIRED here: workspaces
|
||||
# are throwaway, and the Retain class leaked one Released PV per
|
||||
# finished pipeline (issue #262 follow-up).
|
||||
# the pipeline across nodes. Reclaim MUST be Delete: workspaces are
|
||||
# throwaway, and a Retain class leaked one Released PV per finished
|
||||
# pipeline (issue #262 follow-up).
|
||||
#
|
||||
# ceph-filesystem-ci, not ceph-filesystem-ephemeral: a dedicated
|
||||
# CephFS whose pools are BOTH size 1 (infrastructure/
|
||||
# rook-ceph-cluster/cephfs-ci.yaml). The shared filesystem
|
||||
# replicates data0 2x and metadata 3x, which is the correct trade
|
||||
# for real data and pure waste for a git clone that is deleted
|
||||
# minutes later.
|
||||
#
|
||||
# Measured 2026-08-21, polling both pools every 15s (a 5-minute
|
||||
# interval misses an entire pipeline -- PVC created, used and
|
||||
# deleted inside ~2 minutes):
|
||||
# CI active 1.5min: data0 143.2 MB, metadata 17.2 MB
|
||||
# CI idle 16.0min: data0 0.0 MB, metadata 11.0 MB
|
||||
# data0 write volume is 100% CI. Per pipeline this moves the drive
|
||||
# cost from 71.6x2 + 8.2x3 = ~168 MB to ~80 MB.
|
||||
#
|
||||
# ceph-filesystem-ephemeral is NOT repointed, and the reason is a
|
||||
# technical constraint rather than a safety one. Its other consumer
|
||||
# is velero/restore-test.sh, whose probe PVCs are snapshot CLONES:
|
||||
# `ceph fs subvolume snapshot clone` operates within one filesystem,
|
||||
# so a snapshot taken in `cephfs` cannot be cloned into `cephfs-ci`.
|
||||
#
|
||||
# Those probes are just as throwaway as a CI workspace -- the
|
||||
# authoritative copy is the backup on Hetzner, and a lost probe just
|
||||
# means re-running the test -- so they are equally undeserving of
|
||||
# replication. Landing them unreplicated would mean a second size-1
|
||||
# data pool inside `cephfs`, and restore-test runs WEEKLY against
|
||||
# CI's ~96 pipelines/day, so it is not worth the complexity.
|
||||
- name: WOODPECKER_BACKEND_K8S_STORAGE_CLASS
|
||||
value: ceph-filesystem-ephemeral
|
||||
value: ceph-filesystem-ci
|
||||
- name: WOODPECKER_BACKEND_K8S_STORAGE_RWX
|
||||
value: "true"
|
||||
# MUST be a JSON object literal — the backend json.Unmarshal's this
|
||||
|
||||
@@ -80,6 +80,20 @@ spec:
|
||||
# and slower than the lz4/zstd this build also accepts). Compresses the
|
||||
# full-page images that dominate WAL volume after each checkpoint.
|
||||
wal_compression: "zstd"
|
||||
# A WAL segment is a fixed 16 MiB file, and archive_timeout forces a switch
|
||||
# on a timer -- the segment is then written and archived at its full size
|
||||
# no matter how little real WAL it holds. CNPG defaults to 5min, i.e. up
|
||||
# to 288 segments/day = 4.6 GB/day that is largely padding.
|
||||
#
|
||||
# Verified on gf-pg first (the staged rollout): 211 -> 41 MB/hour, an 81%
|
||||
# cut, with archived segments falling from ~12/hour to ~1.5/hour in step.
|
||||
# Two independent signals moving together is what confirms the volume was
|
||||
# padding rather than data.
|
||||
#
|
||||
# The cost is RPO: up to 30min of WAL is unrecoverable if this cluster and
|
||||
# its volume are lost outright. This is a single-instance cluster, so the
|
||||
# archive is the only off-node copy.
|
||||
archive_timeout: "30min"
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
---
|
||||
# A SEPARATE CephFS whose only purpose is Woodpecker CI workspaces, with both
|
||||
# pools at size 1.
|
||||
#
|
||||
# WHY THIS EXISTS
|
||||
# Woodpecker's Kubernetes backend creates a PVC per pipeline, fills it with a
|
||||
# checkout plus build output, and deletes it when the pipeline ends. Measured
|
||||
# 2026-08-21 by polling both pools at 15s (a 5-minute interval misses a whole
|
||||
# pipeline — its PVC is created, used and deleted inside ~2 minutes):
|
||||
#
|
||||
# one pipeline, a single 15s interval: data0 +71.6 MB metadata +8.2 MB
|
||||
# every interval without a CI pod: data0 ~0 metadata ~0
|
||||
#
|
||||
# CI drove essentially all of it. On the shared filesystem that costs
|
||||
# 71.6x2 + 8.2x3 = ~168 MB of drive writes per pipeline. Here it costs ~80 MB.
|
||||
#
|
||||
# WHY size 1 IS CORRECT HERE, HAVING BEEN REJECTED FOR THE MAIN FILESYSTEM
|
||||
# `cephfs` keeps metadataPool at size 3 because losing it destroys the whole
|
||||
# filesystem, not the objects on one OSD, and 550 GiB in cephfs-data0 depends
|
||||
# on it. None of that applies to this filesystem. Every byte in it is a fresh
|
||||
# git clone; the worst case of total loss is that in-flight pipelines fail and
|
||||
# get re-run. Replicating throwaway data is the thing being paid for, so it is
|
||||
# the thing removed.
|
||||
#
|
||||
# Losing an OSD takes these PGs offline and kills running pipelines. That is
|
||||
# not a regression: CI already breaks during Ceph maintenance.
|
||||
apiVersion: ceph.rook.io/v1
|
||||
kind: CephFilesystem
|
||||
metadata:
|
||||
name: cephfs-ci
|
||||
namespace: rook-ceph
|
||||
spec:
|
||||
metadataPool:
|
||||
replicated:
|
||||
size: 1
|
||||
# Rook refuses size 1 without this — it is asserting that an unreplicated
|
||||
# pool is deliberate rather than a typo. It is.
|
||||
requireSafeReplicaSize: false
|
||||
dataPools:
|
||||
- name: data0
|
||||
replicated:
|
||||
size: 1
|
||||
requireSafeReplicaSize: false
|
||||
metadataServer:
|
||||
activeCount: 1
|
||||
# No standby-replay, unlike `cephfs`. A standby exists there so a failed
|
||||
# MDS does not hang every CephFS mount cluster-wide; here the only casualty
|
||||
# is a running pipeline, and a second daemon to protect throwaway work is
|
||||
# not worth its memory.
|
||||
activeStandby: false
|
||||
resources:
|
||||
# Deliberately NOT the 4Gi that `cephfs` needs. That figure is driven by
|
||||
# immich's millions of cached inodes; a CI workspace is a few thousand
|
||||
# short-lived files that vanish with the pipeline.
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 512Mi
|
||||
limits:
|
||||
memory: 1Gi
|
||||
---
|
||||
# CI-only class. `ceph-filesystem-ephemeral` is deliberately left pointing at
|
||||
# the main filesystem: velero/restore-test.sh uses it to restore REAL data for
|
||||
# verification, which must not land on unreplicated pools.
|
||||
#
|
||||
# A new class rather than repointing the old one — StorageClass.parameters are
|
||||
# immutable, so `fsName` cannot be edited in place.
|
||||
apiVersion: storage.k8s.io/v1
|
||||
kind: StorageClass
|
||||
metadata:
|
||||
name: ceph-filesystem-ci
|
||||
provisioner: rook-ceph.cephfs.csi.ceph.com
|
||||
parameters:
|
||||
clusterID: rook-ceph
|
||||
fsName: cephfs-ci
|
||||
pool: cephfs-ci-data0
|
||||
csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
|
||||
csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
|
||||
csi.storage.k8s.io/controller-expand-secret-name: rook-csi-cephfs-provisioner
|
||||
csi.storage.k8s.io/controller-expand-secret-namespace: rook-ceph
|
||||
csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
|
||||
csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
|
||||
# Delete, for the same reason ceph-filesystem-ephemeral uses it: with Retain,
|
||||
# every finished pipeline leaks a Released PV pinning a subvolume (191 of them
|
||||
# by 2026-07-16, issue #262 follow-up).
|
||||
reclaimPolicy: Delete
|
||||
allowVolumeExpansion: true
|
||||
@@ -4,5 +4,6 @@ resources:
|
||||
- cephcluster.yaml
|
||||
- toolbox.yaml
|
||||
- storageclass.yaml
|
||||
- cephfs-ci.yaml
|
||||
- volumesnapshotclass.yaml
|
||||
- objectstore.yaml
|
||||
|
||||
Reference in New Issue
Block a user