📂 개발
google-sheets-backup

Google Sheets로 10분마다 자동 백업하는 시스템 구축기, 백업의 중요성

#Rails, #Google Sheets, #Background Jobs, #Solid Queue,2025-11-21

"데이터 유실 사고에서 배운 교훈"

이 글에서는 백업 시스템이 어떻게 구현되었는지, 그리고 왜 Google Sheets를 선택했는지 공유하고자 합니다.


왜 Google Sheets인가?

모든 서비스에는 백업 기능이 있어야 한다고 생각합니다. 더불어 KOA는 한명의 개발자로 구성되어 있기에 최대한 간단하면서도 빠르게 구현할 수 있는 백업 시스템이 필요했습니다.

비회원 신청의 경우 구글폼으로 구현하고, 백업과 구글폼 응답을 함께 관리하기 위해 구글시트를 선택하여 백업 시스템을 구현했습니다.

즉, 사용자가 적은 환경에서 최대한 빠르게 구현하면서도 비개발자인 팀원들과 쉽게 공유하고 구글폼과 연동하기에 용이하다 생각하여 구글시트를 선택했습니다.

구글시트의 장점

  • 브라우저에서 즉시 조회/검색 가능
  • 빠르게 구현 가능
  • 비개발자도 접근 가능
  • 시간별 히스토리 추적
  • 무료 (Google Workspace)

구현 과정

시스템 아키텍처

Solid Queue (10분마다 자동 실행)
    ↓
SheetsBackupJob
    ↓
GoogleSheetsService (Google Sheets API 호출)
    ↓
Google Sheets
  - party-application (신청자 정보 23개 컬럼)
  - user (회원 정보 17개 컬럼)
  - record (백업 실행 로그)

1. Solid Queue 스케줄러 설정

Rails 8부터 제공되는 Solid Queue를 사용하여 10분마다 자동으로 백업이 실행되도록 설정했습니다.

# config/recurring.yml
production:
  sheets_backup:
    class: SheetsBackupJob
    queue: default
    schedule: every 10 minutes

2. Self-Healing Background Job

핵심 아이디어는 남은 게 있으면 스스로 스케쥴을 돌려 백업한다는 것입니다.

# app/jobs/sheets_backup_job.rb
class SheetsBackupJob < ApplicationJob

  def backup_party_applications(service)
    # 아직 백업 안 된 데이터만 조회
    applications = PartyApplication
      .where(backed_up_to_sheets: false)
      .includes(:party_post, :user, :payment)

    # Google Sheets에 백업
    result = service.backup_applications(applications)

    if result[:success]
      # 백업 완료 플래그 업데이트
      PartyApplication.where(id: applications.pluck(:id))
        .update_all(backed_up_to_sheets: true)

      # 남은 데이터가 있으면 1분 후 자동 재실행
      remaining = PartyApplication.where(backed_up_to_sheets: false).count
      if remaining > 0
        SheetsBackupJob.set(wait: 1.minute).perform_later
      end
    end
  end
end

3. Google Service Account 인증

프로덕션 환경에서는 OAuth 대신 Service Account를 사용합니다.

설정 순서:

  1. Google Cloud Console에서 Service Account 생성
  2. JSON key 다운로드
  3. Google Sheets에서 Service Account email에 편집 권한 부여
# app/services/google_sheets_service.rb
def authorize
  Google::Auth::ServiceAccountCredentials.make_creds(
    json_key_io: File.open(JSON_KEY_PATH),
    scope: Google::Apis::SheetsV4::AUTH_SPREADSHEETS
  )
end

4. 멱등성 보장

네트워크 타임아웃 등으로 같은 데이터가 두 번 백업되는 것을 방지하기 위해 backed_up_to_sheets 플래그를 사용합니다.

# Migration
add_column :party_applications, :backed_up_to_sheets, :boolean, default: false

# 백업 안 된 것만 조회 → 백업 → 플래그 업데이트
applications = PartyApplication.where(backed_up_to_sheets: false)
service.backup_applications(applications)
PartyApplication.where(id: application_ids).update_all(backed_up_to_sheets: true)

5. 실행 로그 기록

Google Sheets의 record 시트에 매 실행마다 로그를 기록하여 모니터링합니다.

...
│ 2025-12-01 10:00  │ PartyApplication│ 성공 │ 100   │ -        │
│ 2025-12-01 10:10  │ PartyApplication│ 성공 │ 100   │ -        │
│ 2025-12-01 10:20  │ PartyApplication│ 실패 │ 0     │ Timeout  │
...

사고에서 배우는 백업의 중요성

2025년 11월 23일, 데이터 유실 사고

발생 시각: 2025년 11월 23일 자정 발견 시각: 00:23 (23분 후) 영향 범위: 소셜 로그인 사용자 다수 (실 결제 고객 4명 포함)

사고 원인

Solid Queue의 자동 삭제 스케줄 작업에서 치명적인 설계 결함이 있었습니다.

# config/recurring.yml (사고 당시 설정)
production:
  cleanup_unconfirmed_emails:
    command: "User.where(email_confirmed: false).where('created_at < ?', 24.hours.ago).destroy_all"
    schedule: every day at 12am

문제점:

  • 이메일 회원가입: email_confirmed: false → 24시간 후 미인증 시 삭제 (의도한 동작)
  • 소셜 로그인 (Kakao, LINE): OAuth로 인증 완료 → 하지만 email_confirmed: false로 생성 → 24시간 후 의도치 않게 삭제

결과적으로 24시간 이상 된 모든 소셜 로그인 사용자가 자정에 일괄 삭제되었습니다.

백업 덕분에 살아난 고객들

복구 과정 (총 7분 소요):

00:23 - 사고 발견 (사용자 수 급감 확인)
00:25 - Google Sheets 접속
00:27 - 실 결제 고객 검색 (payment_id 존재하는 row 필터링)
00:30 - 고객 4명 정보 추출 완료 (이름, 전화번호, 이메일, 결제 내역)

복구된 데이터:

  • 고객 이름, 전화번호, 이메일
  • 파티 신청 정보
  • 결제 금액, 결제 일시
  • 결제 ID, 주문명

결과: 모든 실 결제 고객에게 개별 연락하여 재신청 안내 완료

만약 백업이 없었다면?

  • 실 결제 고객의 연락처를 알 수 없음
  • 어떤 고객이 피해를 입었는지 파악 불가
  • 고객 신뢰 완전 상실
  • 법적 문제 발생 가능 (결제는 되었으나 서비스 미제공)

배운점

1. "백업은 선택이 아닌 필수"

백업이 있어야 안정적 서비스가 가능하고 모든 상황에서 복구 가능하다는 것을 느꼈다.

2. "접근성이 중요하다"

DB dump 방식:

# 개발자만 가능
ssh production-server
pg_dump database > backup.sql
psql database < backup.sql

Google Sheets 방식:

1. 브라우저 열기
2. Google Sheets 접속
3. 검색/필터링
4. 데이터 확인

비개발자도, 새벽에도, 어디서든 즉시 확인 가능합니다.

물론 직접 관리자 페이지를 구현할 수 있지만, Google Sheet를 사용하면 무료로 빠르게 구현 가능합니다.

3. "Self-Healing으로 단순하게"

복잡한 Queue 분산 로직 대신:

  • 남은 게 있나 확인
  • 있으면 1분 후 다시 실행

4. "모니터링이 필수"

사고 발견이 23분이나 걸린 이유: 모니터링 부재

개선 사항:

  • record 시트에 매 실행마다 로그 기록
  • Mailer 활용하여 이상 발생(유저수 급감)시 메일 전송 구현

마치며

  • Google Sheets는 무료이고 간단하다.
  • Self-Healing 패턴으로 단순하게 구현 가능하다.
  • 10분마다 실행해도 서버 부하가 거의 없다.

가장 중요한 건 백업 그 자체이기에 초기 환경에서 충분히 도입할만하다.

백업은 "만약"을 대비하는 것이기에 99%의 시간 동안은 사용하지 않지만, 위기 순간에 그 무엇보다 중요하다는 것을 느낄 수 있었습니다.