Rails 8 프로덕션에서 SQLite 네 개를 분리해 운영하기
네 데이터베이스의 역할을 구분한다
Rails 8의 Solid Stack을 사용하면 PostgreSQL이나 Redis 없이도 애플리케이션 데이터, 캐시, 백그라운드 작업, Action Cable 상태를 SQLite로 운영할 수 있다. production에서는 이 역할을 한 파일에 몰아넣기보다 primary, cache, queue, cable 네 데이터베이스로 분리하는 편이 좋다.
분리의 목적은 “SQLite라서 무조건 네 개가 필요하다”가 아니다. 캐시 정리나 queue 부하가 글과 댓글을 저장하는 primary DB에 미치는 영향을 줄이고, 각 역할의 migration과 장애 범위를 명확하게 만드는 데 있다. 대신 백업과 복구 대상이 네 파일로 늘어난다는 운영 비용을 받아들여야 한다.
database.yml에 역할과 migration 경로를 적는다
각 DB는 동일한 adapter 설정을 공유하되 파일과 migration 경로를 분리한다.
production:
primary:
<<: *default
database: storage/production.sqlite3
cache:
<<: *default
database: storage/production_cache.sqlite3
migrations_paths: db/cache_migrate
queue:
<<: *default
database: storage/production_queue.sqlite3
migrations_paths: db/queue_migrate
cable:
<<: *default
database: storage/production_cable.sqlite3
migrations_paths: db/cable_migrate
queue는 worker thread뿐 아니라 dispatcher와 supervisor가 연결을 사용할 수 있다. 따라서 공통 RAILS_MAX_THREADS 값을 그대로 재사용하지 말고 실행 구조에 맞는 별도 연결 수를 정한다. connection timeout은 일시적인 writer 경합을 흡수할 수 있어야 하지만, 긴 timeout으로 지속적인 병목을 숨겨서는 안 된다.
storage 전체를 영속화한다
컨테이너 배포에서는 네 DB 파일만 개별 mount하기보다 storage/ 디렉터리 전체를 uid가 일치하는 영속 볼륨에 둔다. SQLite는 WAL과 SHM 보조 파일을 만들 수 있으므로 디렉터리에 쓰기 권한이 필요하다. 첫 배포 전에 root가 빈 볼륨을 소유하게 만들면 non-root Rails 프로세스가 부팅하지 못할 수 있다.
배포 gate에는 다음 확인을 포함한다.
- 애플리케이션 프로세스가 uid 1000 등 의도한 사용자로 실행된다.
- 네 DB 파일과 WAL/SHM을 만들 수 있다.
db:prepare가 역할별 migration 경로를 모두 적용한다.- 컨테이너 재시작 뒤 글, 세션, 작업 상태가 유지된다.
- production secret이 바뀌어 저장된 signed URL이나 세션이 무효화되지 않는다.
여러 DB를 하나의 트랜잭션처럼 보지 않는다
primary에 글을 저장하고 queue에 작업을 넣는 과정은 서로 다른 SQLite 파일에 기록될 수 있다. 네 DB를 가로지르는 원자적 트랜잭션이 자동으로 제공된다고 가정하면 안 된다. 작업은 중복 실행돼도 안전한 형태로 만들고, primary 상태를 다시 조회해 이미 완료된 경우 빠르게 종료하도록 설계한다.
캐시는 언제든 재생성 가능한 데이터만 저장한다. queue와 cable도 역할상 재구성 가능한 부분이 있지만, 진행 중인 중요한 작업이나 연결 상태를 복구 후 어떻게 처리할지는 별도 정책이 필요하다. “파일이 분리되어 있다”는 사실과 “데이터 손실 영향이 없다”는 판단은 같은 말이 아니다.
백업과 복구는 네 파일을 같은 시점으로 맞춘다
Litestream 같은 도구로 네 DB를 복제할 때는 각 파일을 별도 replica로 다룬다. daemon 상태가 실행 중이라는 사실만 확인하지 말고 각 DB의 local txid와 replica txid가 따라잡았는지 점검한다. 1초 동기화 설정은 정상 상태의 목표 RPO일 뿐, 복제가 멈춘 시간을 보상하지 않는다.
복구할 때는 애플리케이션 쓰기를 먼저 중단하고 네 DB에 동일한 UTC 시점을 사용한다. 기존 production 파일 위에 곧바로 덮어쓰지 말고 격리된 빈 볼륨에 복원한 뒤 무결성 검사, schema 확인, 글과 댓글 관계, queue 실패 건수를 검증한다. 그 다음에만 교체나 롤백을 결정한다.
SQLite와 Solid Stack은 작은 서비스의 운영 구성을 단순하게 만들 수 있다. 다만 단순함은 백업을 생략해서 얻는 것이 아니라, 파일 위치·권한·migration·복구 절차를 명시적으로 관리할 때 얻어진다.
댓글
아직 댓글이 없습니다.
댓글 남기기