1
0
Fork 0
mirror of https://github.com/borgbackup/borg.git synced 2025-03-03 10:17:40 +00:00

Document retention of oldest archive, add to example and tests

This commit is contained in:
Alf Mikula 2020-09-17 06:52:46 -07:00
parent 69a58d2be0
commit e208ba68bd
4 changed files with 132 additions and 22 deletions

View file

@ -1,14 +1,18 @@
borg prune visualized borg prune visualized
===================== =====================
Assume it is 2016-01-01, today's backup has not yet been made and you have Assume it is 2016-01-01, today's backup has not yet been made, you have
created at least one backup on each day in 2015 except on 2015-12-19 (no created at least one backup on each day in 2015 except on 2015-12-19 (no
backup made on that day). backup made on that day), and you started backing up with borg on
2015-01-01.
This is what borg prune --keep-daily 14 --keep-monthly 6 would keep. This is what borg prune --keep-daily 14 --keep-monthly 6 --keep-yearly 1
would keep.
Backups kept by the --keep-daily rule are marked by a "d" to the right, Backups kept by the --keep-daily rule are marked by a "d" to the right,
backups kept by the --keep-monthly rule are marked by a "m" to the right. backups kept by the --keep-monthly rule are marked by a "m" to the right,
and backups kept by the --keep-yearly rule are marked by a "y" to the
right.
Calendar view Calendar view
------------- -------------
@ -16,7 +20,7 @@ Calendar view
2015 2015
January February March January February March
Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
1 2 3 4 1 1 1y 2 3 4 1 1
5 6 7 8 9 10 11 2 3 4 5 6 7 8 2 3 4 5 6 7 8 5 6 7 8 9 10 11 2 3 4 5 6 7 8 2 3 4 5 6 7 8
12 13 14 15 16 17 18 9 10 11 12 13 14 15 9 10 11 12 13 14 15 12 13 14 15 16 17 18 9 10 11 12 13 14 15 9 10 11 12 13 14 15
19 20 21 22 23 24 25 16 17 18 19 20 21 22 16 17 18 19 20 21 22 19 20 21 22 23 24 25 16 17 18 19 20 21 22 16 17 18 19 20 21 22
@ -53,16 +57,16 @@ Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa Su
List view List view
--------- ---------
--keep-daily 14 --keep-monthly 6 --keep-daily 14 --keep-monthly 6 --keep-yearly 1
------------------------------------------------- ----------------------------------------------------------------
1. 2015-12-31 (2015-12-31 kept by daily rule) 1. 2015-12-31 (2015-12-31 kept (2015-12-31 kept
2. 2015-12-30 1. 2015-11-30 2. 2015-12-30 by daily rule) by daily rule)
3. 2015-12-29 2. 2015-10-31 3. 2015-12-29 1. 2015-11-30 1. 2015-01-01 (oldest)
4. 2015-12-28 3. 2015-09-30 4. 2015-12-28 2. 2015-10-31
5. 2015-12-27 4. 2015-08-31 5. 2015-12-27 3. 2015-09-30
6. 2015-12-26 5. 2015-07-31 6. 2015-12-26 4. 2015-08-31
7. 2015-12-25 6. 2015-06-30 7. 2015-12-25 5. 2015-07-31
8. 2015-12-24 8. 2015-12-24 6. 2015-06-30
9. 2015-12-23 9. 2015-12-23
10. 2015-12-22 10. 2015-12-22
11. 2015-12-21 11. 2015-12-21
@ -76,18 +80,23 @@ Notes
----- -----
2015-12-31 is kept due to the --keep-daily 14 rule (because it is applied 2015-12-31 is kept due to the --keep-daily 14 rule (because it is applied
first), not due to the --keep-monthly rule. first), not due to the --keep-monthly or --keep-yearly rule.
Because of that, the --keep-monthly 6 rule keeps Nov, Oct, Sep, Aug, Jul and The --keep-yearly 1 rule does not consider the December 31st backup because it
Jun. December is not considered for this rule, because that backup was already has already been kept due to the daily rule. There are no backups available
kept because of the daily rule. from previous years, so the --keep-yearly target of 1 backup is not satisfied.
Because of this, the 2015-01-01 archive (the oldest archive available) is kept.
The --keep-monthly 6 rule keeps Nov, Oct, Sep, Aug, Jul and Jun. December is
not considered for this rule, because that backup was already kept because of
the daily rule.
2015-12-17 is kept to satisfy the --keep-daily 14 rule - because no backup was 2015-12-17 is kept to satisfy the --keep-daily 14 rule - because no backup was
made on 2015-12-19. If a backup had been made on that day, it would not keep made on 2015-12-19. If a backup had been made on that day, it would not keep
the one from 2015-12-17. the one from 2015-12-17.
We did not include yearly, weekly, hourly, minutely or secondly rules to keep We did not include weekly, hourly, minutely or secondly rules to keep this
this example simple. They all work in basically the same way. example simple. They all work in basically the same way.
The weekly rule is easy to understand roughly, but hard to understand in all The weekly rule is easy to understand roughly, but hard to understand in all
details. If interested, read "ISO 8601:2000 standard week-based year". details. If interested, read "ISO 8601:2000 standard week-based year".

View file

@ -10,3 +10,4 @@ pytest-cov
pytest-benchmark pytest-benchmark
Cython!=0.27 Cython!=0.27
twine twine
python-dateutil

View file

@ -4164,7 +4164,11 @@ def define_borg_mount(parser):
rules do not count towards those of later rules. The time that each backup rules do not count towards those of later rules. The time that each backup
starts is used for pruning purposes. Dates and times are interpreted in starts is used for pruning purposes. Dates and times are interpreted in
the local timezone, and weeks go from Monday to Sunday. Specifying a the local timezone, and weeks go from Monday to Sunday. Specifying a
negative number of archives to keep means that there is no limit. negative number of archives to keep means that there is no limit. As of borg
1.2.0, borg will retain the oldest archive if any of the secondly, minutely,
hourly, daily, weekly, monthly, or yearly rules was not otherwise able to meet
its retention target. This enables the first chronological archive to continue
aging until it is replaced by a newer archive that meets the retention criteria.
The ``--keep-last N`` option is doing the same as ``--keep-secondly N`` (and it will The ``--keep-last N`` option is doing the same as ``--keep-secondly N`` (and it will
keep the last N archives under the assumption that you do not create more than one keep the last N archives under the assumption that you do not create more than one

View file

@ -1,4 +1,5 @@
import argparse import argparse
import dateutil.tz
import errno import errno
import io import io
import json import json
@ -2059,6 +2060,101 @@ def test_prune_repository(self):
# the latest archive must be still there # the latest archive must be still there
self.assert_in('test5', output) self.assert_in('test5', output)
# Given a date and time in local tz, create a UTC timestamp string suitable
# for create --timestamp command line option
def _to_utc_timestamp(self, year, month, day, hour, minute, second):
dtime = datetime(year, month, day, hour, minute, second, 0, dateutil.tz.gettz())
return dtime.astimezone(dateutil.tz.UTC).strftime("%Y-%m-%dT%H:%M:%S")
def _create_archive_ts(self, name, y, m, d, H=0, M=0, S=0):
loc = self.repository_location + '::' + name
self.cmd('create', '--timestamp', self._to_utc_timestamp(y, m, d, H, M, S), loc, src_dir)
# This test must match docs/misc/prune-example.txt
def test_prune_repository_example(self):
self.cmd('init', '--encryption=repokey', self.repository_location)
# Archives that will be kept, per the example
# Oldest archive
self._create_archive_ts('test01', 2015, 1, 1)
# 6 monthly archives
self._create_archive_ts('test02', 2015, 6, 30)
self._create_archive_ts('test03', 2015, 7, 31)
self._create_archive_ts('test04', 2015, 8, 31)
self._create_archive_ts('test05', 2015, 9, 30)
self._create_archive_ts('test06', 2015, 10, 31)
self._create_archive_ts('test07', 2015, 11, 30)
# 14 daily archives
self._create_archive_ts('test08', 2015, 12, 17)
self._create_archive_ts('test09', 2015, 12, 18)
self._create_archive_ts('test10', 2015, 12, 20)
self._create_archive_ts('test11', 2015, 12, 21)
self._create_archive_ts('test12', 2015, 12, 22)
self._create_archive_ts('test13', 2015, 12, 23)
self._create_archive_ts('test14', 2015, 12, 24)
self._create_archive_ts('test15', 2015, 12, 25)
self._create_archive_ts('test16', 2015, 12, 26)
self._create_archive_ts('test17', 2015, 12, 27)
self._create_archive_ts('test18', 2015, 12, 28)
self._create_archive_ts('test19', 2015, 12, 29)
self._create_archive_ts('test20', 2015, 12, 30)
self._create_archive_ts('test21', 2015, 12, 31)
# Additional archives that would be pruned
# The second backup of the year
self._create_archive_ts('test22', 2015, 1, 2)
# The next older monthly backup
self._create_archive_ts('test23', 2015, 5, 31)
# The next older daily backup
self._create_archive_ts('test24', 2015, 12, 16)
output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=14', '--keep-monthly=6', '--keep-yearly=1')
# Prune second backup of the year
assert re.search(r'Would prune:\s+test22', output)
# Prune next older monthly and daily backups
assert re.search(r'Would prune:\s+test23', output)
assert re.search(r'Would prune:\s+test24', output)
# Must keep the other 21 backups
# Yearly is kept as oldest archive
assert re.search(r'Keeping archive \(rule: yearly\[oldest\] #1\):\s+test01', output)
for i in range(1, 7):
assert re.search(r'Keeping archive \(rule: monthly #' + str(i) + r'\):\s+test' + ("%02d" % (8-i)), output)
for i in range(1, 15):
assert re.search(r'Keeping archive \(rule: daily #' + str(i) + r'\):\s+test' + ("%02d" % (22-i)), output)
output = self.cmd('list', self.repository_location)
# Nothing pruned after dry run
for i in range(1, 25):
self.assert_in('test%02d' % i, output)
self.cmd('prune', self.repository_location, '--keep-daily=14', '--keep-monthly=6', '--keep-yearly=1')
output = self.cmd('list', self.repository_location)
# All matching backups plus oldest kept
for i in range(1, 22):
self.assert_in('test%02d' % i, output)
# Other backups have been pruned
for i in range(22, 25):
self.assert_not_in('test%02d' % i, output)
# With an initial and daily backup, prune daily until oldest is replaced by a monthly backup
def test_prune_retain_and_expire_oldest(self):
self.cmd('init', '--encryption=repokey', self.repository_location)
# Initial backup
self._create_archive_ts('original_archive', 2020, 9, 1, 11, 15)
# Archive and prune daily for 30 days
for i in range(1, 31):
self._create_archive_ts('september%02d' % i, 2020, 9, i, 12)
self.cmd('prune', self.repository_location, '--keep-daily=7', '--keep-monthly=1')
# Archive and prune 6 days into the next month
for i in range(1, 7):
self._create_archive_ts('october%02d' % i, 2020, 10, i, 12)
self.cmd('prune', self.repository_location, '--keep-daily=7', '--keep-monthly=1')
# Oldest backup is still retained
output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=7', '--keep-monthly=1')
assert re.search(r'Keeping archive \(rule: monthly\[oldest\] #1' + r'\):\s+original_archive', output)
# Archive one more day and prune.
self._create_archive_ts('october07', 2020, 10, 7, 12)
self.cmd('prune', self.repository_location, '--keep-daily=7', '--keep-monthly=1')
# Last day of previous month is retained as monthly, and oldest is expired.
output = self.cmd('prune', '--list', '--dry-run', self.repository_location, '--keep-daily=7', '--keep-monthly=1')
assert re.search(r'Keeping archive \(rule: monthly #1\):\s+september30', output)
self.assert_not_in('original_archive', output)
def test_prune_repository_save_space(self): def test_prune_repository_save_space(self):
self.cmd('init', '--encryption=repokey', self.repository_location) self.cmd('init', '--encryption=repokey', self.repository_location)
self.cmd('create', self.repository_location + '::test1', src_dir) self.cmd('create', self.repository_location + '::test1', src_dir)