Improves interval example

Makes specific notes on week numbers, clears some ambiguity in the
yearly candidate wording, and more.

The matching test was previously missing assertions on the weekly
archives, now fixed along with better explanations.
This commit is contained in:
Hugo Wallenburg 2026-06-20 18:03:55 +02:00
parent 378f7c27cc
commit 89c6a2cccc
No known key found for this signature in database
2 changed files with 42 additions and 18 deletions

View file

@ -7,9 +7,9 @@ to "thin out" over time while retaining most recent archives. Your backup
script runs `borg create`, immediately followed by `borg prune`.
Assume today is 2026-06-04 and you always start your backups at 16:00. You have
been creating backup archives starting at 16:00 on most days going back to late
2025. Today, `borg create` took a little longer than usual. It's 16:12 now and
you run `borg prune`.
been creating backup archives starting at 16:00, followed by pruning, on most
days going back to late 2025. Today, `borg create` took a little longer than
usual. It's 16:12 now and you run `borg prune`.
You want Borg to keep one archive per day for one week, four weekly archives,
one archive per month for five months, and two yearly backups. For that, you
@ -115,16 +115,22 @@ later reference time, e.g. `--since '2026-06-04 23:59:59'`.
and `--keep-monthly`, too. The archive is effectively kept by `--keep-daily`,
but how this affects other rules differs between *count*- and *interval*-based
policies. For *interval*-based rules like `--keep-monthly 5m` it has no effect:
The rule simply keeps one archive fewer.
The rule simply keeps one archive fewer in that case.
For *count*-based rules like `--keep-weekly 4` it has an effect: The policy
tells Borg to keep 4 weekly archives, and if 2026-05-31 is kept by another
rule already, Borg compensates by keeping an older archive instead.
Consequently, Borg will also keep the 2026-05-03 archive.
tells Borg to keep 4 weekly archives. Weekly slots are identified by ISO week
number; in this example the four kept archives cover weeks 18 through 21. If
2026-05-31 is kept by another rule already, Borg compensates by keeping an
older archive instead. Consequently, Borg will also keep the 2026-05-03 archive.
Since 2026-05-24 and 2026-03-31 were skipped, there are no perfect candidates
for that week and month. Borg chooses the next best candidate, so it keeps
2026-05-23 as the weekly and 2026-03-30 as the monthly candidate.
The week 22 slot is consumed by that same 2026-05-31 archive. Any other
archive from that week is not kept by weekly, even if it falls within the
daily window boundary. This is why 2026-05-27 is pruned: it has just fallen
out of the daily window, and no other rule covers it.
Since 2026-05-24 and 2026-03-31 were skipped, Borg substitutes the next best
candidate for each: 2026-05-23 fills the week 21 weekly slot, and 2026-03-30
fills the March monthly slot.
The implementation of `--keep-monthly 5m` is somewhat special: Borg defines a
month as a fixed 31-day period, independent of the actual calendar dates
@ -144,6 +150,6 @@ Intervals and counts can be mixed freely. Yearly retention in this example is
done by retention count instead of intervals. A count rule paired with interval
rules behaves just as it would if all preceding rules were also counts:
Archives already kept by earlier rules are excluded from consideration. In this
example there is only one "true" yearly candidate, so the oldest archive at
2025-11-15 is kept. This oldest archive will be kept until the rolling backup
scheme reaches "steady state" (when all retention rules are fully satisfied).
example, every yearly slot is consumed by earlier rules; no true yearly
candidate remains, so the oldest archive at 2025-11-15 is kept to preserve
rolling backup semantics.

View file

@ -123,9 +123,7 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
# All timestamps are at exactly 16:00 UTC (matching the example).
# Backups on most days from 2025-11-15 to 2026-06-04, with skips on
# 2026-03-31 and 2026-06-03. At the inclusive interval boundaries,
# 2026-05-28 (1w before --since) and 2025-12-31 (155d before --since)
# are kept.
# 2026-03-31, 2026-05-24, and 2026-06-03.
archive_dates = [
(2025, 11, 15),
(2025, 12, 31),
@ -133,6 +131,11 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
(2026, 2, 28),
(2026, 3, 30),
(2026, 4, 30),
(2026, 5, 3),
(2026, 5, 10),
(2026, 5, 17),
(2026, 5, 23),
(2026, 5, 27),
(2026, 5, 28),
(2026, 5, 29),
(2026, 5, 30),
@ -153,6 +156,7 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
"--dry-run",
"--since=2026-06-04T16:00:00+00:00",
"--keep-daily=1w",
"--keep-weekly=4",
"--keep-monthly=5m",
"--keep-yearly=2",
)
@ -166,18 +170,27 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
"backup_2026-05-31",
"backup_2026-05-30",
"backup_2026-05-29",
# 2026-05-28 is at the inclusive boundary (exactly 1w before --since).
"backup_2026-05-28",
]
for i, name in enumerate(daily_kept, 1):
assert re.search(rf"Keeping archive \(rule: daily #{i}\):\s+{name}", output)
# Weekly W22 slot is consumed by 05-31 (already kept by daily),
# so weekly reaches back to W18 to fill all 4 slots.
weekly_kept = [
"backup_2026-05-23", # W21 — no Sunday candidate (05-24 skipped)
"backup_2026-05-17", # W20
"backup_2026-05-10", # W19
"backup_2026-05-03", # W18
]
for i, name in enumerate(weekly_kept, 1):
assert re.search(rf"Keeping archive \(rule: weekly #{i}\):\s+{name}", output)
monthly_kept = [
"backup_2026-04-30",
"backup_2026-03-30",
"backup_2026-02-28",
"backup_2026-01-31",
# 2025-12-31 is at the inclusive boundary (exactly 155d before --since).
"backup_2025-12-31",
]
for i, name in enumerate(monthly_kept, 1):
@ -186,6 +199,11 @@ def test_prune_repository_example_interval(archivers, request, backup_files):
# No true yearly candidates remain; only the oldest archive fills the slot.
assert re.search(r"Keeping archive \(rule: yearly\[oldest\] #1\):\s+backup_2025-11-15", output)
# 05-27 was kept by daily yesterday (window started 05-26) but falls out
# today (window shifted to 05-28). W22 slot is consumed by daily-held 05-31,
# so weekly doesn't save it either.
assert re.search(r"Would prune:\s+backup_2026-05-27", output)
def test_prune_quarterly(archivers, request, backup_files):
# Example worked through by hand when developing the quarterly