RSS듀오랩스
웹 인프라

웹서비스 외부 백업: S3에 올린 사본이 같이 지워지지 않게 하는 방법

작성자
듀오랩스 대표·14분 읽기

매일 밤 서버에서 이런 명령이 돈다고 해 보겠습니다.

rclone sync /data/uploads s3:company-backup/uploads

rclone 문서sync 를 이렇게 설명합니다.

Destination is updated to match source, including deleting files if necessary.

원본 폴더가 실수로 비워지거나 랜섬웨어에 암호화된 날 밤에도 이 명령은 성실하게 돕니다. 다음 날 아침 S3에는 비워진 폴더가, 혹은 암호화된 파일이 똑같이 올라가 있습니다. 백업은 매일 성공했고, 정작 필요한 순간에 원본과 함께 망가졌습니다.

"S3에 올렸다"와 "외부 백업"이 다른 이유

흔히 외부 백업을 "서버 밖 다른 곳에 사본을 두는 것"으로 이해합니다. 그러면 위 상황이 설명되지 않습니다. 사본은 분명 다른 곳에 있었습니다. 문제는 위치가 아니라 권한이었습니다. 서버가 가진 키로 백업을 덮어쓰고 지울 수 있다면, 서버에 일어난 일은 백업에도 일어납니다.

그래서 저는 외부 백업이 되려면 조건이 셋 필요하다고 봅니다. 운영과 다른 곳에 있어야 하고, 운영의 키로는 지울 수 없어야 하고, 돌아갈 과거 시점이 여러 개 있어야 합니다.

흔히 인용하는 3-2-1 원칙은 이 중 첫째만 말합니다. US-CERT 문서의 원문은 사본 3개, 서로 다른 매체 2종, 그중 1개는 사업장 밖에 두라는 데서 끝납니다. Veeam은 여기에 변경할 수 없는 사본 1개와 복원 오류 0건을 더해 3-2-1-1-0이라고 부릅니다. 저는 이 확장이 나머지 두 조건을 채우려는 것으로 읽습니다.

업체 자동 백업이 지켜 주지 못하는 자리

관리형 DB는 대부분 자동 백업을 켜 둡니다. 기본값을 보면 생각보다 짧습니다.

서비스 기본 자동 백업 시점 복구(PITR) 알아 둘 점
AWS RDS 콘솔로 만들면 7일, API·CLI로 만들면 1일 보관(최대 35일) 보관 기간 안의 임의 시점. 로그는 5분마다 S3로 복원은 항상 새 인스턴스로
Supabase Free 없음, Pro 7일, Team 14일 Pro 이상에서 유료 부가 기능 Storage에 올린 파일은 DB 백업에 들어가지 않음
PlanetScale Postgres 12시간마다, 2일 보관 2일 전부터 5분 전까지 백업이 클러스터와 같은 클라우드·리전에 저장됨

출처는 각 사 문서입니다(RDS 보관 기간, RDS 시점 복구, Supabase, PlanetScale).

이 백업들은 쓸모가 큽니다. 실수로 테이블을 지웠을 때 가장 빨리 돌아가는 길입니다. 다만 외부 백업은 아닙니다. PlanetScale 문서는 백업이 클러스터와 같은 클라우드 업체, 같은 리전에 저장된다고 적고 있습니다. 계정이 잠기거나 결제가 끊기거나 그 리전에 문제가 생기면 DB와 백업에 한꺼번에 손이 닿지 않습니다. 보관 기간이 이틀이면, 금요일 밤에 생긴 문제를 월요일 아침에 발견했을 때 돌아갈 시점이 이미 없습니다.

Supabase 칸의 마지막 줄도 봐야 합니다. 공식 문서는 Storage API로 저장한 객체가 DB 백업에 포함되지 않는다고 적습니다. 사용자가 올린 사진과 첨부 파일은 따로 챙겨야 합니다.

잃어도 되는 시간으로 고르는 덤프와 PITR

DB를 밖으로 빼는 방법은 크게 둘입니다.

하나는 논리 덤프입니다. pg_dump 는 DB를 파일 하나로 뽑습니다. PostgreSQL 문서에 따르면 DB를 쓰는 중에도 일관된 시점으로 뽑고, 다른 사용자의 읽기·쓰기를 막지 않습니다. 파일 하나라 다루기 쉽고, 다른 업체의 Postgres에 그대로 부어 넣을 수 있습니다. 대신 덤프와 덤프 사이의 변경은 잃습니다. 하루 한 번 뜨면 최대 하루치를 잃습니다.

다른 하나는 WAL 아카이빙입니다. 기본 백업 한 벌에 WAL 파일을 계속 이어 붙여 두면, 기본 백업 이후 아무 시점으로나 되돌릴 수 있습니다. 직접 운영한다면 pgBackRestWAL-G가 이 일을 합니다. 둘 다 S3 호환 저장소에 올리고, 전체·증분 백업과 암호화를 지원합니다. 관리형 DB에서는 업체가 이 방식을 대신 돌리고, 그것이 위 표의 PITR입니다.

저는 이 선택을 기술 취향이 아니라 "몇 시간치 데이터가 사라져도 사업이 버티는가"로 봅니다. 하루치를 잃어도 다시 입력할 수 있는 사내 시스템이면 매일 덤프로 충분합니다. 주문과 결제가 쉬지 않고 들어오는 서비스라면 PITR을 켜고, 그 위에 덤프를 외부로 한 번 더 뺍니다. PITR은 업체 안에 있고 덤프는 업체 밖으로 나가는 사본이라, 둘은 서로를 대신하지 않습니다.

DB 백업에 들어 있지 않은 업로드 파일과 키

업로드 파일이 객체 스토리지에 있다면 가장 싼 보호 장치는 버전 관리입니다. S3에서 버전 관리를 켜면 삭제가 파일을 지우는 대신 삭제 표시를 하나 얹고, 덮어써도 이전 버전이 남습니다. 대신 버전마다 차이분이 아니라 파일 전체 크기로 과금됩니다. 1GB짜리 파일을 매일 덮어쓰면 30일 뒤 30GB가 쌓이므로, 수명 주기 규칙으로 오래된 버전을 정리해 둡니다.

키는 반대로 백업과 떨어져 있어야 합니다. restic은 백업을 기본으로 암호화하고, pgBackRest도 저장소 암호화를 켤 수 있습니다. 그 암호를 백업과 같은 버킷에 두면 암호화한 의미가 없고, 암호를 잃으면 백업이 있어도 열지 못합니다. 앱이 DB 안의 일부 값을 암호화해 저장한다면 그 키도 백업 목록에 들어가야 합니다. DB를 완벽히 되살려도 키가 없으면 그 값만 영영 읽히지 않습니다.

운영 키로 지울 수 없게 만드는 세 가지 장치

첫째는 S3의 Object Lock입니다. 정해 둔 기간 동안 객체를 덮어쓰거나 지울 수 없게 막습니다. 컴플라이언스 모드에서는 루트 사용자를 포함해 누구도 기간 전에 지우지 못합니다. 거버넌스 모드는 별도 권한과 우회 헤더를 가진 사용자만 지울 수 있습니다. Object Lock은 버전 관리가 켜진 버킷에서만 동작하고, 한 번 켜면 끌 수 없습니다.

둘째는 올리기만 되는 경로입니다. 서버에 주는 키에는 업로드 권한만 넣고 삭제 권한은 뺍니다. 버전 관리가 켜진 버킷이라면 같은 이름으로 덮어써도 이전 버전이 남습니다. restic을 쓴다면 rest-server--append-only 로 띄우는 방법도 있습니다. 새 백업은 받되 기존 백업의 삭제와 수정은 거부합니다.

셋째는 계정을 가르는 것입니다. 백업을 운영과 다른 계정, 가능하면 다른 업체에 둡니다. AWS 안에서라면 AWS Backup Vault Lock의 컴플라이언스 모드가 있습니다. 최소 72시간의 유예 기간이 지나면 어떤 사용자도, AWS도 잠금을 바꾸거나 지울 수 없습니다.

하나 놓치기 쉬운 곳이 있습니다. S3 문서에 따르면 버킷 정책으로도 수명 주기 규칙이 하는 삭제는 막지 못합니다. 운영 키가 수명 주기 규칙을 고칠 수 있다면 "보관 기간 1일" 규칙 하나로 백업을 비울 수 있습니다. 규칙을 바꾸는 권한도 운영 키에서 떼어 둡니다.

잠금 기능에서 갈리는 외부 저장소 다섯 가지

저장소 저장 가격(GB·월) 꺼낼 때 비용 버전 관리 삭제 잠금 서울 리전
AWS S3 Standard 서울 $0.025 서울 GB당 $0.126(전 리전 합산 월 100GB 무료) Object Lock(거버넌스·컴플라이언스)
AWS S3 Glacier Deep Archive 서울 $0.002 GB당 $0.005~0.022, 복원까지 12~48시간 Object Lock
Cloudflare R2 $0.015 무료 버킷 잠금(기간·날짜·무기한)
Backblaze B2 TB당 $6.95 저장량의 3배까지 무료, 넘으면 GB당 $0.01 Object Lock(거버넌스·컴플라이언스)
네이버 클라우드 Object Storage 확인 못 함 확인 못 함 일본 리전만 한국 리전은 단일 모드, 0~365일

2026년 9월 11일에 확인한 값입니다(S3 가격, Deep Archive 복원, R2 가격, R2 버킷 잠금, B2 가격, B2 Object Lock, 네이버 클라우드 버킷 설정).

이 표에서 먼저 볼 것은 저장 가격이 아니라 오른쪽 세 칸입니다. 저장 가격 차이는 작은 서비스에서 몇 달러 수준입니다. 매일 뜬 1GB 덤프를 30일 보관하면 30GB이고, S3 서울 기준으로 월 $0.75입니다.

Deep Archive는 GB당 $0.002로 가장 싸지만 최소 180일 보관분이 과금되고, 꺼내는 데 12시간에서 48시간이 걸립니다. 오늘 복구해야 하는 백업을 여기에 두면 복구가 내일로 밀립니다. 저라면 월말 사본을 오래 보관하는 용도로만 쓰겠습니다.

R2는 꺼낼 때 비용이 없어서 복원 연습을 자주 하기에 좋습니다. 대신 버전 관리가 없어서 덮어쓴 파일을 되돌리지 못하고, 그 빈자리를 버킷 잠금 규칙으로 메워야 합니다. 네이버 클라우드는 AWS와 함께 서울 리전을 가진 곳이고 S3 API와 호환되지만, 한국 리전에서는 버전 관리가 제공되지 않는다고 문서에 적혀 있습니다.

작은 웹서비스에 제가 권하는 조합

저라면 업체 자동 백업과 PITR은 켜 둔 채 빠른 복구용으로만 기대하겠습니다. 외부 사본은 따로 둡니다. 매일 pg_dump 를 운영 DB와 다른 업체의 객체 스토리지로 보내고, 서버 키에는 올리기 권한만 줍니다. 그 버킷에는 버전 관리와 30일 잠금을 겁니다.

업로드 파일은 버전 관리를 켠 버킷에 두고, 같은 방식으로 다른 업체에 한 벌 더 복제합니다. 백업 암호와 앱의 암호화 키는 비밀번호 관리자처럼 백업과 다른 곳에 둡니다.

그리고 분기에 한 번은 빈 DB에 덤프를 부어 넣고 앱을 띄워 봅니다. 3-2-1-1-0의 마지막 0이 복원 오류 0건인 데는 이유가 있습니다. 백업 파일이 생겼다는 것과 그 파일로 서비스가 다시 선다는 것은 다른 사실이고, 둘째는 복원해 봐야만 확인됩니다.

여기서부터는 확인이 더 필요한 부분

가격은 2026년 9월 11일 기준입니다. AWS 가격 페이지는 숫자를 스크립트로 채워서, 위 값은 AWS가 공개하는 가격 목록 API에서 읽었습니다. 네이버 클라우드의 원화 가격은 페이지에서 읽지 못했습니다. 요금 계산기에서 직접 보셔야 합니다.

R2의 버킷 잠금은 S3처럼 거버넌스·컴플라이언스 모드를 나누지 않고, 누가 잠금 규칙을 지울 수 있는지도 문서에 적혀 있지 않습니다. 운영 키로 규칙을 지울 수 있다면 앞에서 말한 권한 문제가 그대로 남습니다. 개인정보가 담긴 백업을 해외 리전에 둘 때 따라야 할 요건은 서비스마다 달라서 이 글에서 다루지 않았습니다.

이 게시글 공유하기

마지막 수정:

공유하실 때는 출처(Duolabs)와 원문 주소를 표시해 주세요.