SQLite로 실제 쇼핑몰을 운영하며 배운 것들

ultrathink.art는 AI 에이전트가 자율적으로 운영하는 이커머스 쇼핑몰이다. 상품 디자인, 주문 처리, 블로그 작성까지 모두 AI가 담당한다. 이 글은 그 쇼핑몰을 실제 Stripe 결제까지 처리하는 프로덕션 환경에서 SQLite로 운영하면서 겪은 경험을 담고 있다.


구성: 파일 4개, 볼륨 1개

프로덕션 환경에서 primary(주문·상품·사용자), cache(Rails 캐시), queue(백그라운드 잡), cable(Action Cable) 총 4개의 SQLite 데이터베이스를 운영하며, 모두 Docker 볼륨 하나에 저장된다.

Rails 8이 SQLite를 1등 선택지로 만들어줬고, 실제로 배포 단순화, 커넥션 풀 관리 불필요, 별도 DB 서버 없음 등의 장점을 누렸다.


WAL 모드가 동시성을 가능하게 하는 원리

SQLite 기본 저널 모드는 쓰기 시 DB 전체를 잠가 동시 요청이 많은 웹 앱에는 부적합하다. WAL(Write-Ahead Logging) 모드에서는 쓰기가 별도 -wal 파일에 추가되고 읽기는 메인 파일을 계속 사용하므로, 다수의 읽기와 단일 쓰기가 동시에 가능하다. Rails 8은 SQLite에 WAL 모드를 기본 활성화한다.


사고: 주문 2건이 사라졌다

2월 4일, 2시간 동안 11번의 커밋을 main에 푸시했다. 각 푸시마다 Kamal의 블루-그린 배포가 실행되어 기존 컨테이너와 신규 컨테이너가 동시에 동일한 WAL 파일을 열게 되는 겹침 구간이 생겼다. 배포 11회가 겹치면서 컨테이너 A가 드레이닝 중에 B가 시작되고, B가 완전히 준비되기 전에 C의 배포가 시작되는 상황이 발생했다.

주문 16번과 17번은 Stripe에서 결제가 성공했고 고객 계좌에서 금액도 빠져나갔지만, DB에는 레코드가 남지 않았다. sqlite_sequence로 확인하니 자동증가 카운터는 17을 가리켰는데 실제 행은 15개뿐이었다.


해결책: 배포 속도를 늦춰라

해결책은 기술적이 아니라 절차적이었다. 관련 변경 사항을 묶어 배포하고, 빠른 연속 푸시를 피하는 규칙을 AI 에이전트들이 따르는 거버넌스 파일(CLAUDE.md)에 명시했다.

이는 SQLite 문제가 아니라 배포 파이프라인 문제다. PostgreSQL은 TCP 소켓을 통해 연결되므로 새 컨테이너도 동일한 DB 서버에 연결되어 쓰기 순서를 DB 엔진이 관리한다. SQLite는 공유 Docker 볼륨의 파일시스템 잠금에 의존하는데, 컨테이너가 겹치면 이것이 깨진다.


sqlite_sequence: 포렌식 도구로 활용하기

sqlite_sequence 테이블은 SQLite에서 가장 저평가된 디버깅 도구다. 나중에 삭제된 행이라도 과거에 자동증가 값이 할당된 최댓값을 기억한다. 현재 행 수와 시퀀스 값이 예상 밖으로 벌어지면 무언가 행을 잘못 삭제했다는 신호다.


아무도 말 안 해주는 함정들

PostgreSQL 개발자들이 습관적으로 쓰는 ILIKE는 SQLite에서 구문 오류를 낸다. LOWER(name) LIKE를 대신 써야 한다. json_extract는 값이 숫자로 저장됐다면 정수를 반환해 문자열 비교 시 조용히 실패한다. kamal app exec는 매번 새 컨테이너를 생성하는데, 2GB RAM 서버에서 동시에 두 번 실행하면 OOM 킬러가 웹 프로세스를 죽인다.


다시 선택해도 SQLite를 쓸 것인가?

그렇다. 단일 서버에 적당한 쓰기 부하라면 SQLite는 인프라 복잡도를 통째로 없애준다. 백업도 sqlite3 .backup 명령 하나면 충분하다(WAL 모드와 동시 쓰기를 안전하게 처리한다). 수평 확장이나 진정한 멀티 라이터 동시성이 필요해지는 날이 오면 그때 PostgreSQL로 마이그레이션하면 된다. Rails는 그 전환을 간단하게 만들어준다.


시사점

SRE 입장에서 인상적인 부분은 기술적 해결책이 아니라 절차적 거버넌스로 문제를 해결했다는 점이다. AI 에이전트 거버넌스 파일(CLAUDE.md)에 배포 빈도 규칙을 명시하는 방식은 사람 팀의 런북(runbook)과 정확히 같은 발상이다. 파일 기반 DB의 특성을 제대로 이해하고 운영 방식을 거기에 맞추는 것이 핵심 교훈이다.


원문: ultrathink.art Blog, 2026.04.03

1개의 좋아요

최근 사이드 프로젝트를 진행하면서는 Docker 환경이 기본이 되다 보니, PostgreSQL을 비교적 고민 없이 기본 선택지처럼 사용해 왔습니다. 기능적으로 더 풍부하고 다양한 상황에 대응하기에 유리하며, 개인 프로젝트에서는 자원 제약도 크지 않기 때문에 자연스러운 선택이었습니다.

하지만 이번 사례를 통해 “항상 더 강력한 기술이 더 좋은 선택인가”에 대해 다시 한 번 생각해보게 되었습니다.

SQLite는 단순한 대체재가 아니라, 전제 조건만 맞는다면 인프라 복잡도를 크게 줄여줄 수 있는 충분히 강력한 선택지입니다. 특히 단일 서버 환경, 낮은 쓰기 경쟁, 단순한 배포 구조에서는 오히려 PostgreSQL보다 운영 측면에서 더 효율적일 수 있습니다.

반면, 운영 방식이 SQLite의 전제 조건(단일 writer, 파일 기반 락)에 맞지 않는 경우에는, 기술 자체의 문제가 아니라 구조적인 한계로 인해 장애가 발생할 수 있다는 점도 확인할 수 있었습니다.

결국 중요한 것은 기술의 성능이나 기능 그 자체가 아니라,
해당 시스템의 요구사항과 운영 방식에 얼마나 적합한 선택인지라고 생각합니다.

이 글을 통해 저도 잊었던 무엇인가에 대해서 하나 배워갑니다.