Skip to content

Commit ac9f181

Browse files
committed
feat: delete old db backups in scheduled db backup task
keep last 100 by default
1 parent 6442e0b commit ac9f181

5 files changed

Lines changed: 85 additions & 4 deletions

File tree

TODO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
## QOL
1111

1212
- [x] More restrictive `!!pb confirm` and `!!pb abort`
13-
- [ ] Prune DB backups
13+
- [x] Prune DB backups
1414

1515
## Documents
1616

docs/config.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -839,7 +839,8 @@ Configurations for the SQLite database, used by Prime Backup
839839
"enabled": true,
840840
"interval": null,
841841
"crontab": "0 6 * * 0",
842-
"jitter": "1m"
842+
"jitter": "1m",
843+
"max_amount": 100
843844
},
844845
"compact_pack": {
845846
"enabled": true,
@@ -867,6 +868,17 @@ within the [storage root](#storage_root) periodically, just in case something wr
867868

868869
Database backups are stored with the `.tar.xz` format, and won't take up much space
869870

871+
#### backup.max_amount
872+
873+
The maximum number of database backup files to keep
874+
875+
The backup job will delete old database backup files whose names match `db_backup_YYYYMMDD_HHMMSS.tar.xz`.
876+
It keeps the latest `max_amount` files by the timestamp parsed from the file name.
877+
Set `max_amount` to `0` or a negative value to keep all database backup files
878+
879+
- Type: `int`
880+
- Default: `100`
881+
870882
#### compact_pack
871883

872884
The pack file compaction job

docs/config.zh.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,8 @@ Prime Backup 所使用的 SQLite 数据库的相关配置
841841
"enabled": true,
842842
"interval": null,
843843
"crontab": "0 6 * * 0",
844-
"jitter": "1m"
844+
"jitter": "1m",
845+
"max_amount": 100
845846
},
846847
"compact_pack": {
847848
"enabled": true,
@@ -869,6 +870,17 @@ Prime Backup 所使用的 SQLite 数据库的相关配置
869870

870871
数据库备份将以 `.tar.xz` 格式存储,不会占用太多空间
871872

873+
#### backup.max_amount
874+
875+
要保留的数据库备份文件数量上限
876+
877+
数据库备份作业会清理文件名匹配 `db_backup_YYYYMMDD_HHMMSS.tar.xz` 的旧数据库备份文件。
878+
它会根据文件名中解析出的时间保留最新的 `max_amount` 个文件。
879+
`max_amount` 设置为 `0` 或负数表示保留全部数据库备份文件
880+
881+
- 类型:`int`
882+
- 默认值:`100`
883+
872884
#### compact_pack
873885

874886
打包文件整理作业

prime_backup/config/database_config.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ class BackUpDatabaseConfig(CrontabJobSetting):
1616
interval = None
1717
crontab = '0 6 * * 0'
1818
jitter = Duration('1m')
19+
max_amount: int = 100
1920

2021

2122
class CompactPackDatabaseConfig(CrontabJobSetting):

prime_backup/mcdr/task/db/create_db_backup_task.py

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
import contextlib
2+
import dataclasses
3+
import datetime
4+
import re
25
import tarfile
36
import threading
47
import time
58
from pathlib import Path
6-
from typing import Optional
9+
from typing import Optional, List
710

811
from typing_extensions import override
912

@@ -15,8 +18,15 @@
1518
from prime_backup.utils.run_once import RunOnceFunc
1619

1720

21+
@dataclasses.dataclass(frozen=True)
22+
class DbBackupFile:
23+
path: Path
24+
date: datetime.datetime
25+
26+
1827
class CreateDbBackupTask(HeavyTask[Optional[threading.Thread]]):
1928
__task_sem = threading.Semaphore(1)
29+
_db_backup_file_regex = re.compile(r'^db_backup_(?P<date>\d{8})_(?P<time>\d{6})\.tar\.xz$')
2030

2131
@property
2232
@override
@@ -56,6 +66,11 @@ def tar_thread():
5666
))
5767
except Exception:
5868
self.logger.exception('db backup: Compress database backup to {} failed'.format(db_backup_file))
69+
else:
70+
try:
71+
self._delete_old_db_backup_files(db_backup_root)
72+
except Exception:
73+
self.logger.exception('db backup: Delete old database backups failed')
5974
finally:
6075
sem_releaser()
6176
temp_db_path.unlink(missing_ok=True)
@@ -77,3 +92,44 @@ def tar_thread():
7792
except Exception:
7893
sem_releaser()
7994
raise
95+
96+
@classmethod
97+
def __parse_db_backup_file(cls, path: Path) -> Optional[DbBackupFile]:
98+
if not path.is_file():
99+
return None
100+
if (match := cls._db_backup_file_regex.fullmatch(path.name)) is None:
101+
return None
102+
103+
try:
104+
date = datetime.datetime.strptime(match['date'] + match['time'], '%Y%m%d%H%M%S')
105+
except ValueError:
106+
return None
107+
return DbBackupFile(path=path, date=date)
108+
109+
@classmethod
110+
def _get_db_backup_files(cls, db_backup_root: Path) -> List[DbBackupFile]:
111+
files: List[DbBackupFile] = []
112+
for path in db_backup_root.iterdir():
113+
if (backup_file := cls.__parse_db_backup_file(path)) is not None:
114+
files.append(backup_file)
115+
files.sort(key=lambda f: (f.date, f.path.name), reverse=True)
116+
return files
117+
118+
def _delete_old_db_backup_files(self, db_backup_root: Path):
119+
max_amount = self.config.database.backup.max_amount
120+
if max_amount <= 0:
121+
return
122+
123+
db_backup_files = self._get_db_backup_files(db_backup_root)
124+
files_to_delete = db_backup_files[max_amount:]
125+
if not files_to_delete:
126+
return
127+
128+
self.logger.info('db backup: Deleting {} old database backup(s), keeping latest {}'.format(len(files_to_delete), max_amount))
129+
for backup_file in files_to_delete:
130+
try:
131+
backup_file.path.unlink()
132+
except Exception:
133+
self.logger.exception('db backup: Failed to delete old database backup {}'.format(backup_file.path.as_posix()))
134+
else:
135+
self.logger.info('db backup: Deleted old database backup {}'.format(backup_file.path.as_posix()))

0 commit comments

Comments
 (0)