Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions scripts/mysql_backup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,16 @@ TCP 연결만으로는 애플리케이션이 기동했는지, 알림 경로가

2번과 4번 사이에는 API 서버가 새 토큰을, DB EC2가 이전 토큰을 사용하므로 알림이 거부됩니다. 1번만 수행한 시점에는 API 서버가 아직 이전 토큰을 들고 있어 알림이 정상 동작합니다. 백업 자체는 회전 중에도 계속 동작하며, 회전은 백업 실패가 없는 시점에 수행합니다.

## 최초 설치의 첫 dump

`Persistent=true`는 마지막 트리거 시각(`/var/lib/systemd/timers/stamp-mysql-backup-dump.timer`)을 기준으로 놓친 발화를 따라잡습니다. 최초 설치에는 이 기록이 없어 기준이 없으므로, 타이머를 켜도 즉시 실행되지 않고 다음 03:00 KST를 기다립니다.

**dump 없이 binlog만 있으면 복구할 수 없습니다.** binlog는 변경 이력이고, 재생을 시작할 지점은 dump에 기록된 `CHANGE REPLICATION SOURCE TO`가 알려줍니다. 첫 dump가 생기기 전까지는 S3에 객체가 쌓여도 복구 수단이 없고, 업로드가 정상이라 외부에서 보면 문제가 없어 보입니다. 04:00에 설치하면 이 상태가 23시간 이어집니다.

그래서 설치는 **dump 트리거 기록이 없을 때만** 첫 dump를 즉시 만듭니다. 기록이 있으면 유효한 기준점이 이미 있으므로 설치가 스케줄에 개입하지 않습니다. 재설치마다 dump를 만들면 Object Lock 때문에 14일 동안 지울 수 없는 객체가 쌓이기도 합니다.

첫 dump는 완료를 기다리지 않고 시작만 합니다. dump는 최대 2시간까지 실행될 수 있어 설치가 그만큼 매달리면 배포 세션이 끊어집니다. 시작에 실패하면 설치는 성공으로 두고 경고만 남깁니다. 이 경우 복구 기준점이 없는 상태이므로 수동으로 실행하거나 다음 스케줄을 기다려야 합니다.

## 재설치가 백업에 주는 영향

설치는 몇 번을 다시 실행해도 같은 결과가 됩니다. 디렉터리 생성은 있으면 넘어가고, 파일은 `.new`로 만든 뒤 `mv`로 바꿔치기하며, 설정 파일은 부분 병합 없이 통째로 덮어씁니다. 검증이나 교체가 실패하면 이전 파일과 타이머 상태로 되돌립니다.
Expand Down
30 changes: 28 additions & 2 deletions scripts/mysql_backup/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@ readonly INSTALL_LOCK_FILE="/run/lock/solid-connection-mysql-backup-install.lock
# binlog는 최대 4분, dump는 최대 2시간 실행되므로, dump가 도는 중이라면
# 기다리기보다 중단하고 다른 시각에 다시 실행하는 편이 낫습니다.
readonly LOCK_WAIT_SECONDS=300
readonly BINLOG_TIMER_UNIT="mysql-backup-binlog.timer"
readonly DUMP_TIMER_UNIT="mysql-backup-dump.timer"
readonly DUMP_SERVICE_UNIT="mysql-backup-dump.service"
# systemd 는 Persistent 타이머의 마지막 트리거 시각을 이 파일의 mtime 으로 기록합니다.
readonly DUMP_TIMER_STAMP_FILE="/var/lib/systemd/timers/stamp-$DUMP_TIMER_UNIT"
readonly -a TIMER_UNITS=(
mysql-backup-binlog.timer
mysql-backup-dump.timer
"$BINLOG_TIMER_UNIT"
"$DUMP_TIMER_UNIT"
)

if ((EUID != 0)); then
Expand Down Expand Up @@ -98,6 +103,8 @@ declare -A TIMER_WAS_ENABLED=()
declare -A TIMER_WAS_ACTIVE=()
transaction_started=false
transaction_committed=false
# 타이머를 켜면 systemd 가 stamp 파일을 만들 수 있어, 켜기 전에 확인해 둡니다.
dump_was_triggered=false

register_target() {
local target="$1"
Expand Down Expand Up @@ -191,6 +198,10 @@ for timer in "${TIMER_UNITS[@]}"; do
register_target "/etc/systemd/system/timers.target.wants/$timer"
done

if [[ -e "$DUMP_TIMER_STAMP_FILE" ]]; then
dump_was_triggered=true
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

register_target "$INSTALL_LIB_DIR/backup-common.sh"
for script in "$CANDIDATE_DIR"/bin/*; do
register_target "$INSTALL_BIN_DIR/$(basename "$script")"
Expand Down Expand Up @@ -249,4 +260,19 @@ release_backup_locks
systemctl enable --now "${TIMER_UNITS[@]}"
transaction_committed=true

# Persistent 는 마지막 트리거 기록이 있어야 놓친 발화를 따라잡습니다.
# 기록이 없는 최초 설치에서는 다음 03:00 까지 dump 가 생기지 않는데,
# 기준점이 되는 dump 가 없으면 binlog 만으로는 재생 시작점이 없어 복구할 수 없습니다.
# 그래서 첫 dump 는 스케줄을 기다리지 않고 바로 만듭니다.
# 이미 dump 기록이 있으면 유효한 기준점이 있으므로 설치가 스케줄에 개입하지 않습니다.
if [[ "$dump_was_triggered" != "true" ]]; then
echo "No previous dump was recorded; creating the first dump now so that binlogs have a recovery base."
# dump 는 최대 2시간까지 실행될 수 있어 완료를 기다리지 않습니다.
# 설치는 이미 커밋되었으므로, 트리거가 실패해도 설치를 실패로 만들지 않고 사실만 알립니다.
if ! systemctl start --no-block "$DUMP_SERVICE_UNIT"; then
Comment thread
Hexeong marked this conversation as resolved.
echo "Failed to start the first dump. Until it succeeds there is no recovery base," >&2
echo "so run it manually or wait for the next scheduled run at 03:00 KST." >&2
fi
fi

systemctl --no-pager --full status "${TIMER_UNITS[@]}"
Loading