lobste.rs, MariaDB에서 SQLite로 전환
(lobste.rs)- lobste.rs는 MariaDB에서 SQLite로 전환한 뒤 월요일 트래픽 급증까지 안정적으로 처리했으며, CPU·메모리 사용량 감소와 더 빠른 체감 응답성을 확인함
- 별도 MariaDB VPS를 내리면 VPS 비용이 절반으로 줄어들고, 2019년 시작된 마이그레이션 과제도 마무리됨
- 첫 배포에서는 읽기 전용 트래픽만으로 모든 CPU가 100%에 도달해 롤백했으나, 대형 테이블의 전체 테이블 스캔 2건과 N+1 문제를 수정한 뒤 재배포에 성공함
- SQLite 사용자 정의 함수로
regexp·if·stddev를 구현하는 한편, unsigned bigint 미지원과 ASCII만 처리하는NOCASE등 호환성 제약을 해결해야 했음 - 프로덕션 데이터베이스에 접근하지 않고 기반 DB를 바꾸려면 현실적인 크기의 테스트 데이터, 테스트 스위트, 배포·롤백 체크리스트와 긴밀한 협업이 필요함
2019년 논의에서 SQLite 전환까지
- 마이그레이션 논의는 2019년 이슈 #539에서 시작됨
- 당시 MariaDB와의 호환성을 고려해 MySQL을 대안으로 제안했지만, 이미 PostgreSQL 전환 논의가 진행 중이었음
- 2025년 Rahul이 K1의 MariaDB 인수를 언급하면서 PostgreSQL 마이그레이션 세부 논의가 이어짐
- 2월에는 Rahul이 lobsters가 SQLite에서 실행될 수 있는지 질문했고, 2025년 6월부터 프로젝트 참여가 본격화됨
- 2025년 8월의 첫 번째 PR은 작업이 중단된 뒤 GitHub에서 오래된 PR로 닫혀 다시 열 수 없었음
- 두 번째 PR에는 성능 테스트, 자체 DB 간 마이그레이션 스크립트, 데이터 무결성 관련 디버깅이 포함됨
- 기존 MariaDB/MySQL→SQLite 변환 스크립트 가운데 요구를 충족하는 것이 없어 별도 스크립트를 작성함
첫 배포 실패와 성능 병목 수정
- 2월 21일 첫 배포는 배포 체크리스트를 준비해 진행했지만, 배포 직후 읽기 전용 상태의 트래픽만으로 모든 CPU가 100% 에 도달함
- 원인을 찾지 못해 롤백함
- 프로덕션 DB에 접근할 수 없어 실제 성능 문제를 사전에 재현하기 어려웠음
- 실패 이틀 뒤 세 번째이자 최종 PR을 열어 검색 관련 문제를 수정하고, 실제 lobsters 데이터의 절반 크기를 로컬에 만드는 대량 데이터 생성 스크립트를 추가함
재배포 결과와 운영 비용 절감
- 7월 11일 두 번째 배포에서는 배포·롤백 체크리스트를 다시 준비했으며, 전환 후에도 사이트가 정상 운영되고 CPU와 메모리 사용량도 안정적으로 유지됨
MariaDB와 달랐던 SQLite의 제약
- SQLite gem의 사용자 정의 함수(UDF) 지원을 이용해 SQLite에 없던
regexp,if,stddev를 구현함- SQL 마이그레이션을 위한 우회 처리를 과도하게 추가하지 않아도 됐음
- MariaDB에서 일부 ID에 사용하던 unsigned bigint를 SQLite가 지원하지 않아 bigint로 변경함
- 정렬 규칙도 MariaDB보다 제한적임
- MariaDB에서는
utf8mb4_general_ci를 사용했지만 SQLite에서는NOCASE를 적용함 NOCASE는 전체 UTF 대소문자 접기를 지원하지 않고 ASCII 문자만 처리함
- MariaDB에서는
- SQLite 전체 텍스트 검색 테이블에는 기본값이 아닌 권장 방식인 Contentless-Delete Tables가 적합함
- Rails의 기본 PRAGMA 설정은 lobsters에서 정상적으로 작동함
- 데이터베이스 마이그레이션은 DB별로 달라 기존 마이그레이션을 old_migrations 디렉터리로 옮기고
db:migrate가 계속 작동하도록 함
- 데이터베이스 마이그레이션은 DB별로 달라 기존 마이그레이션을 old_migrations 디렉터리로 옮기고
테스트와 협업에서 얻은 교훈
- lobsters 코드베이스에서는 검색 파서와 렌더링 속도를 높이는 해킹인 heinous_inline_partials를 확인함
- 테스트 스위트 덕분에 대규모 수동 테스트 없이 SQLite 전환의 정확성을 검증할 수 있었음
- 참여자 사이의 원활한 소통이 마이그레이션 성공을 좌우함
- 프로덕션 DB에 접근하지 못한 상태에서 기반 데이터베이스를 교체하기는 매우 어려웠음
- 같은 작업을 다시 한다면 사전에 현실적인 데이터 규모를 준비해야 함
- 테스트 중 전체 테이블 스캔이 발생하면 실패하도록 설정할 수 있었다면 첫 배포의 성능 문제를 미리 잡을 수 있었음
- 프로덕션과 유사한 데이터셋을 직접 작성하거나 일주일씩 기다리지 않고 쉽게 만들 수 있는 도구가 필요함
댓글과 토론
Lobste.rs 의견들
-
@thomas0가 대규모 마이그레이션을 꼼꼼히 맡아줬고, @355E3B는 계획과 운영을 도왔으며, #539 관련 기여자와 #lobsters 덕분에 읽기 전용 시간도 즐겁게 넘길 수 있었음
원래 #539에서는 PostgreSQL을 계획했지만, 실제 작업 지원자가 SQLite를 사용했고 예상 수요보다 크고 복잡한 해법을 피하고 싶어 SQLite를 선택함. PostgreSQL은 평소 기본 선택이지만 별도 서비스를 운영·조정·유지해야 하는 부담이 있음
놀랍게도 마이그레이션 후 CPU와 RAM 사용량이 모두 감소했으며, 자세한 차트는 #539 마지막 부분에 있음 -
SQLite의 기본 설정을 볼 때마다 놀라게 됨. WAL, 외래 키,
synchronous=NORMAL등을 기본으로 제공하는 신규 프로젝트용 모드가 있으면 좋겠고, 이 밖에도 개선할 기본값이 더 있을 듯함- 디스크 변경의 예약이나 형식이 HDD에는 유리하지만 NVMe에는 상당히 부적합했던 부분도 있었던 것으로 희미하게 기억함
-
SQLite 데이터베이스의 백업 전략이 궁금함. litestream 같은 도구를 사용하는지, 다른 방식을 쓰는지 알고 싶음
-
lobste.rs에서는 업데이트 작업을 어떻게 처리하는지 궁금함
- SQLite의 update 문을 사용함. 다른 종류의 업데이트를 뜻했다면 구체적으로 알려주길 바람
-
SQLite에는 이미 iif 함수가 있지만, 같은 영역에서 사용자 정의 함수를 운영하는 것 자체도 흥미로움
-
SQLite가 PostgreSQL이나 MySQL과 같은 수준의 동시성을 지원하지 못한다고 생각했는데, 개요 문서를 보면 이제 여러 프로세스에서 사용할 수 있되 lobste.rs는 단일 서버에서 실행해야 한다고 이해하면 되는지 궁금함
프로세스들이 공유 메모리나mmap같은 방식으로 SQLite 내부에서 동기화되어 동시에 쓰기는 하나만 가능하고 읽기는 여러 개 허용되는지, 대기 중인 쓰기 작업은 큐에서 차례를 기다리는지도 알고 싶음- SQLite FAQ가 일부 의문에 답해줄 수 있음. 여러 프로세스에 걸쳐 여러 읽기 작업을 수행할 수 있지만 쓰기는 한 번에 하나만 가능하고, SQLite는 파일 시스템 잠금으로 이를 보장함
쓰기 대기에는 명시적인 공유 메모리 큐가 아니라 잠시 대기한 뒤 재시도하는 방식이 쓰이는 듯함. 여러 머신에서 SQLite를 쓰는 것이 완전히 불가능한지는 확실하지 않지만, NFS로 파일을 공유하는 단순한 접근은 명시적으로 권장되지 않으므로 단일 서버라고 이해하는 것이 합리적인 근사치이며 요즘은 한 대로도 상당한 규모를 처리할 수 있음 - 쓰기 작업을 어느 정도 대기시키기는 하지만 기본 설정은 좋지 않아서 조정이 필요함. 현재 사용하는 설정은 다음과 같고,
busy_timeout이 작동하도록 즉시 트랜잭션 모드도 켜야 함
참고 자료는 https://fractaledmind.github.io/2024/04/… 와 https://kerkour.com/sqlite-for-servers 및 https://www.sqlite.org/lang_transaction.html#immediate 이며, 두 번째 링크의"PRAGMA foreign_keys=ON;" "PRAGMA journal_mode = WAL;" "PRAGMA synchronous = NORMAL;" "PRAGMA busy_timeout = 5000;" "PRAGMA temp_store = MEMORY;" "PRAGMA mmap_size = 134217728;" "PRAGMA journal_size_limit = 67108864;" "PRAGMA cache_size = 2000;"SQLITE_BUSY부분과 세 번째 링크에서 자세한 내용을 볼 수 있음
- SQLite FAQ가 일부 의문에 답해줄 수 있음. 여러 프로세스에 걸쳐 여러 읽기 작업을 수행할 수 있지만 쓰기는 한 번에 하나만 가능하고, SQLite는 파일 시스템 잠금으로 이를 보장함
-
PR, 이슈, Gist에 세부 내용이 매우 잘 정리된 인상적인 업그레이드임. 이 변경으로 사이트 검색 같은 기능적 차이가 생겼는지, 현재 SQLite 파일의 디스크 크기는 얼마인지 궁금함