# lobste.rs, MariaDB에서 SQLite로 전환

> Clean Markdown view of GeekNews topic #31413. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=31413](https://news.hada.io/topic?id=31413)
- GeekNews Markdown: [https://news.hada.io/topic/31413.md](https://news.hada.io/topic/31413.md)
- Type: GN+
- Author: [neo](https://news.hada.io/@neo)
- Published: 2026-07-14T09:10:39+09:00
- Updated: 2026-07-14T09:10:39+09:00
- Original source: [lobste.rs](https://lobste.rs/s/ko1ji1/lobste_rs_is_now_running_on_sqlite)
- Points: 1
- Comments: 1

## Topic Body

- 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](https://github.com/lobsters/lobsters/issues/539)에서 시작됨
  - 당시 MariaDB와의 호환성을 고려해 MySQL을 대안으로 제안했지만, 이미 PostgreSQL 전환 논의가 진행 중이었음
  - 2025년 Rahul이 [K1의 MariaDB 인수](https://github.com/lobsters/lobsters/issues/539#issuecomment-2604683262)를 언급하면서 PostgreSQL 마이그레이션 세부 논의가 이어짐
  - 2월에는 Rahul이 [lobsters가 SQLite에서 실행될 수 있는지](https://github.com/lobsters/lobsters/issues/539#issuecomment-2640076884) 질문했고, 2025년 6월부터 프로젝트 참여가 본격화됨
- 2025년 8월의 [첫 번째 PR](https://github.com/lobsters/lobsters/pull/1705)은 작업이 중단된 뒤 GitHub에서 오래된 PR로 닫혀 다시 열 수 없었음
  - [두 번째 PR](https://github.com/lobsters/lobsters/pull/1871)에는 성능 테스트, 자체 [DB 간 마이그레이션 스크립트](https://github.com/lobsters/lobsters/blob/main/lib/tasks/migrate.rake), 데이터 무결성 관련 디버깅이 포함됨
  - 기존 MariaDB/MySQL→SQLite 변환 스크립트 가운데 요구를 충족하는 것이 없어 별도 스크립트를 작성함

### 첫 배포 실패와 성능 병목 수정
- 2월 21일 첫 배포는 [배포 체크리스트](https://gist.github.com/pushcx/eb7cdf2dc9707dc3ab9e7173d197ddfc/a11e6d5865776f3bb4e1adbc56582a3b1f99878c)를 준비해 진행했지만, 배포 직후 읽기 전용 상태의 트래픽만으로 모든 CPU가 **100%** 에 도달함
  - 원인을 찾지 못해 [롤백](https://github.com/lobsters/lobsters/pull/1924)함
  - 프로덕션 DB에 접근할 수 없어 실제 성능 문제를 사전에 재현하기 어려웠음
- 실패 이틀 뒤 [세 번째이자 최종 PR](https://github.com/lobsters/lobsters/pull/1927)을 열어 검색 관련 문제를 수정하고, 실제 lobsters 데이터의 절반 크기를 로컬에 만드는 [대량 데이터 생성 스크립트](https://github.com/lobsters/lobsters/pull/1927/changes/c3c12e6f134c9d67c37c57a0390f9d3a2c9f26d8)를 추가함
  - 데이터를 생성하는 데 **일주일**이 걸림
  - 첫 배포의 병목은 대형 테이블에서 두 쿼리가 일으킨 전체 테이블 스캔과 별도의 N+1 문제였음
  - 이를 해결한 [변경 1](https://github.com/lobsters/lobsters/pull/1927/changes/14a8c5f412cc23c887a5b809453596c02d8df6d1), [변경 2](https://github.com/lobsters/lobsters/pull/1927/changes/4dc58fc1d98ed393c4b447c71568bc0cb700896b), [변경 3](https://github.com/lobsters/lobsters/pull/1927/changes/4476ae3e50602b5e0d7805fef40fc94a81daaa39)를 반영함
  - 추가 성능 문제를 추적할 수 있도록 배포 당일 아침 [느린 쿼리 로그](https://github.com/lobsters/lobsters/pull/1927/changes/a62fc572c068f7df45ade8a5fad117dafc68cc00)를 더함

### 재배포 결과와 운영 비용 절감
- 7월 11일 두 번째 배포에서는 배포·롤백 체크리스트를 다시 준비했으며, 전환 후에도 사이트가 정상 운영되고 CPU와 메모리 사용량도 안정적으로 유지됨
  - 사이트 지표와 IRC를 감시하면서 보고된 문제를 [수정 1](https://github.com/lobsters/lobsters/commit/1dd9d5e4d7ecc7f924a88c8792ec44f7c90d4691), [수정 2](https://github.com/lobsters/lobsters/commit/e88a07e1f121cbae772340c29e482399b1e92b75)로 즉시 처리함
  - 월요일 트래픽 급증에서도 문제가 없었고, CPU·메모리 사용량 감소와 더 빠른 체감 응답성을 확인함
  - MariaDB VPS를 종료하면 **VPS 비용이 절반**으로 줄어들며, 마이그레이션 이슈도 닫힘

### MariaDB와 달랐던 SQLite의 제약
- SQLite gem의 **사용자 정의 함수(UDF)** 지원을 이용해 SQLite에 없던 [`regexp`, `if`, `stddev`](https://github.com/lobsters/lobsters/blob/main/config/initializers/sqlite_functions.rb)를 구현함
  - SQL 마이그레이션을 위한 우회 처리를 과도하게 추가하지 않아도 됐음
- MariaDB에서 일부 ID에 사용하던 **unsigned bigint**를 SQLite가 지원하지 않아 bigint로 변경함
- 정렬 규칙도 MariaDB보다 제한적임
  - MariaDB에서는 `utf8mb4_general_ci`를 사용했지만 SQLite에서는 [`NOCASE`](https://www.sqlite.org/datatype3.html#collating_sequences)를 적용함
  - `NOCASE`는 전체 UTF 대소문자 접기를 지원하지 않고 **ASCII 문자만 처리**함
- SQLite 전체 텍스트 검색 테이블에는 기본값이 아닌 권장 방식인 [Contentless-Delete Tables](https://www.sqlite.org/fts5.html#contentless_delete_tables)가 적합함
- Rails의 [기본 PRAGMA 설정](https://github.com/rails/rails/blob/v8.0.5/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb#L84-L91)은 lobsters에서 정상적으로 작동함
  - 데이터베이스 마이그레이션은 DB별로 달라 기존 마이그레이션을 [old_migrations](https://github.com/lobsters/lobsters/tree/main/db/old_migrations) 디렉터리로 옮기고 `db:migrate`가 계속 작동하도록 함

### 테스트와 협업에서 얻은 교훈
- lobsters 코드베이스에서는 [검색 파서](https://github.com/lobsters/lobsters/blob/main/app/models/search_parser.rb)와 렌더링 속도를 높이는 해킹인 [heinous_inline_partials](https://github.com/lobsters/lobsters/blob/main/config/initializers/heinous_inline_partial.rb)를 확인함
  - **테스트 스위트** 덕분에 대규모 수동 테스트 없이 SQLite 전환의 정확성을 검증할 수 있었음
- 참여자 사이의 원활한 소통이 마이그레이션 성공을 좌우함
  - 프로덕션 DB에 접근하지 못한 상태에서 기반 데이터베이스를 교체하기는 매우 어려웠음
  - 같은 작업을 다시 한다면 사전에 **현실적인 데이터 규모**를 준비해야 함
- 테스트 중 전체 테이블 스캔이 발생하면 실패하도록 설정할 수 있었다면 첫 배포의 성능 문제를 미리 잡을 수 있었음
- 프로덕션과 유사한 데이터셋을 직접 작성하거나 일주일씩 기다리지 않고 쉽게 만들 수 있는 도구가 필요함

## Comments



### Comment 61755

- Author: neo
- Created: 2026-07-14T09:10:45+09:00
- Points: 1

###### [Lobste.rs 의견들](https://lobste.rs/s/ko1ji1/lobste_rs_is_now_running_on_sqlite) 
- @thomas0가 대규모 마이그레이션을 꼼꼼히 맡아줬고, @355E3B는 계획과 운영을 도왔으며, #539 관련 기여자와 [#lobsters](https://lobste.rs/chat) 덕분에 읽기 전용 시간도 즐겁게 넘길 수 있었음  
  원래 #539에서는 PostgreSQL을 계획했지만, 실제 작업 지원자가 SQLite를 사용했고 예상 수요보다 크고 복잡한 해법을 피하고 싶어 **SQLite**를 선택함. PostgreSQL은 평소 기본 선택이지만 별도 서비스를 운영·조정·유지해야 하는 부담이 있음  
  놀랍게도 마이그레이션 후 **CPU와 RAM 사용량이 모두 감소**했으며, 자세한 차트는 [#539 마지막 부분](https://github.com/lobsters/lobsters/issues/539#issuecomment-4959857588)에 있음

- SQLite의 기본 설정을 볼 때마다 놀라게 됨. **WAL, 외래 키, `synchronous=NORMAL`** 등을 기본으로 제공하는 신규 프로젝트용 모드가 있으면 좋겠고, 이 밖에도 개선할 기본값이 더 있을 듯함
  - 디스크 변경의 예약이나 형식이 **HDD에는 유리하지만 NVMe에는 상당히 부적합**했던 부분도 있었던 것으로 희미하게 기억함

- SQLite 데이터베이스의 **백업 전략**이 궁금함. [litestream](https://litestream.io/) 같은 도구를 사용하는지, 다른 방식을 쓰는지 알고 싶음
  - [매일 밤 실행되는 작업](https://github.com/lobsters/lobsters/blob/main/config/recurring.yml#L14-L16)이 [restic을 호출](https://github.com/lobsters/lobsters/blob/main/app/jobs/restic_job.rb#L14)함

- lobste.rs에서는 **업데이트 작업**을 어떻게 처리하는지 궁금함
  - SQLite의 [update](https://sqlite.org/lang_update.html) 문을 사용함. 다른 종류의 업데이트를 뜻했다면 구체적으로 알려주길 바람

- SQLite에는 이미 [iif 함수](https://www.sqlite.org/lang_corefunc.html#iif)가 있지만, 같은 영역에서 **사용자 정의 함수**를 운영하는 것 자체도 흥미로움

- SQLite가 PostgreSQL이나 MySQL과 같은 수준의 **동시성**을 지원하지 못한다고 생각했는데, [개요 문서](https://sqlite.org/wal.html#overview)를 보면 이제 여러 프로세스에서 사용할 수 있되 lobste.rs는 단일 서버에서 실행해야 한다고 이해하면 되는지 궁금함  
  프로세스들이 공유 메모리나 `mmap` 같은 방식으로 SQLite 내부에서 동기화되어 동시에 쓰기는 하나만 가능하고 읽기는 여러 개 허용되는지, 대기 중인 쓰기 작업은 큐에서 차례를 기다리는지도 알고 싶음
  - [SQLite FAQ](https://sqlite.org/faq.html#q5)가 일부 의문에 답해줄 수 있음. 여러 프로세스에 걸쳐 **여러 읽기 작업**을 수행할 수 있지만 쓰기는 한 번에 하나만 가능하고, SQLite는 파일 시스템 잠금으로 이를 보장함  
    쓰기 대기에는 명시적인 공유 메모리 큐가 아니라 잠시 대기한 뒤 재시도하는 방식이 쓰이는 듯함. 여러 머신에서 SQLite를 쓰는 것이 완전히 불가능한지는 확실하지 않지만, NFS로 파일을 공유하는 단순한 접근은 명시적으로 권장되지 않으므로 **단일 서버**라고 이해하는 것이 합리적인 근사치이며 요즘은 한 대로도 상당한 규모를 처리할 수 있음
  - 쓰기 작업을 어느 정도 대기시키기는 하지만 **기본 설정은 좋지 않아서 조정이 필요**함. 현재 사용하는 설정은 다음과 같고, `busy_timeout`이 작동하도록 즉시 트랜잭션 모드도 켜야 함  
    ```sql  
    "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;"  
    ```  
    참고 자료는 https://fractaledmind.github.io/2024/04/15/sqlite-on-rails-the-how-and-why-of-optimal-performance/ 와 https://kerkour.com/sqlite-for-servers 및 https://www.sqlite.org/lang_transaction.html#immediate 이며, 두 번째 링크의 `SQLITE_BUSY` 부분과 세 번째 링크에서 자세한 내용을 볼 수 있음

- PR, 이슈, Gist에 세부 내용이 매우 잘 정리된 인상적인 업그레이드임. 이 변경으로 **사이트 검색 같은 기능적 차이**가 생겼는지, 현재 SQLite 파일의 디스크 크기는 얼마인지 궁금함
  - 검색 결과에는 SQLite의 [순위 산정 기능](https://www.sqlite.org/fts5.html#sorting_by_auxiliary_function_results)을 사용하므로 MariaDB의 결과 순서와 달라질 수 있음. [검색 테스트](https://github.com/lobsters/lobsters/blob/main/spec/models/search_spec.rb)는 있지만 순위 검증은 적어서 기능적 변화가 있을 가능성은 있으나 근거는 아직 없음  
    SQLite 파일 크기는 어딘가에서 **3.8GB**라고 했던 것으로 기억함
