GN⁺: 최적 성능을 위한 SQLite on Rails의 방법과 이유
(fractaledmind.github.io)- 지난 1년 동안 SQLite를 사용하여 Rails 애플리케이션을 성능 좋고 안정적으로 실행하는 방법을 깊이 이해하려고 노력해왔음
- 이 과정에서 여러 가지 교훈을 배웠으며, 이를 공유하고자 함
- 문제의 원인과 해결 방법을 설명할 것임
SQLite와 Rails의 문제점
- 기본적으로 SQLite를 사용한 Rails 애플리케이션은 바로 사용할 수 있는 상태가 아님
- 약간의 조정과 미세 조정을 통해 성능 좋고 안정적인 애플리케이션을 만들 수 있음
- Rails 8에서는 기본 설정만으로도 프로덕션 준비가 완료된 상태가 되도록 목표를 설정함
데모 애플리케이션 "Lorem News"
- "Lorem News"라는 데모 애플리케이션을 사용하여 문제와 해결책을 설명할 것임
- 이 애플리케이션은 Hacker News의 클론으로, 사용자들이 게시물과 댓글을 작성할 수 있음
성능 테스트
-
oha
로드 테스트 CLI와 애플리케이션 내 벤치마킹 경로를 사용하여 성능을 테스트함 - 단일 요청과 동시 요청을 통해 성능을 측정함
주요 문제: SQLITE_BUSY
예외
- SQLite는 한 번에 하나의 쓰기 작업만 허용하기 위해 쓰기 잠금을 사용함
- 여러 연결이 동시에 쓰기 잠금을 시도하면
SQLITE_BUSY
예외가 발생함 - 이 문제를 해결하기 위해 즉시 트랜잭션을 사용해야 함
즉시 트랜잭션
- 기본적으로 SQLite는 지연된 트랜잭션 모드를 사용함
- 즉시 트랜잭션을 사용하면 쓰기 잠금을 즉시 시도하고 실패 시 재시도할 수 있음
-
sqlite3-ruby
gem을 사용하여 기본 트랜잭션 모드를 즉시 모드로 설정할 수 있음
타임아웃 설정
-
database.yml
파일에서 타임아웃 설정을 통해SQLITE_BUSY
예외를 줄일 수 있음 - SQLite의
busy_timeout
설정을 사용하여 쓰기 잠금을 재시도할 수 있음
GVL(글로벌 VM 잠금) 문제
-
sqlite3-ruby
gem은 SQLite의 C 코드를 호출할 때 GVL을 해제하지 않음 - 이는 동시성 성능을 저하시킴
-
busy_handler
를 사용하여 GVL을 해제하고 성능을 개선할 수 있음
busy_timeout
재구현
-
busy_timeout
을 재구현하여 모든 쿼리가 동일한 빈도로 재시도하도록 설정함 - 이는 오래된 쿼리가 타임아웃되지 않도록 함
성능 개선
- 성능을 개선하기 위해 다음과 같은 설정을 적용해야 함
- 즉시 트랜잭션 사용
- 타임아웃 설정
-
busy_handler
사용 - WAL(Write-Ahead Logging) 모드 사용
- 읽기/쓰기 연결 풀 분리
GN⁺의 정리
- SQLite를 사용한 Rails 애플리케이션의 성능 문제와 해결책을 다룸
- 즉시 트랜잭션, 타임아웃 설정, GVL 해제, WAL 모드 사용, 읽기/쓰기 연결 풀 분리 등의 방법을 통해 성능을 개선할 수 있음
- 이 기사는 SQLite와 Rails를 사용하는 개발자들에게 매우 유용할 것임
- 유사한 기능을 가진 다른 프로젝트로는 PostgreSQL과 MySQL을 추천함
Hacker News 의견
-
Oldmoe의 Litestack 프로젝트 소개
- SQLIte와 Rails를 사용하는 사람들은 Oldmoe의 Litestack 프로젝트를 확인할 필요가 있음
- Litestack은 SQLite의 강력함을 활용하여 웹 애플리케이션 데이터 인프라를 제공하는 Ruby gem임
- SQL 데이터베이스, 빠른 캐시, 강력한 작업 큐, 신뢰할 수 있는 메시지 브로커, 전체 텍스트 검색 엔진, 메트릭스 플랫폼을 하나의 패키지로 제공함
- 현재 프로젝트에서 사용 중이며 매우 만족스러움
-
상세한 기사 작성에 대한 감사
- SQLite 웹 애플리케이션을 확장하려는 사람들에게 유용한 정보임
- Rails를 넘어 다른 프레임워크에서도 적용 가능함
- 작가에게 감사함
-
SQLite 관련 작업을 하는 사람들에게 추천
- 사용하는 언어나 프레임워크와 상관없이 SQLite 관련 작업을 하는 사람들은 이 기사를 읽어야 함
- 몇 년 전에는 직접 해결해야 했던 문제들을 다루고 있음
- 작가에게 감사함
-
FOSS 분석 시스템에 대한 질문
- 설치가 쉬운 FOSS 분석 시스템을 만들고 있음
- 이벤트 데이터를 별도의 SQLite 데이터베이스에 보내어 메인 앱의 데이터와 분리하려고 함
- 초당 1000개 이상의 이벤트를 처리할 수 있는 확장성에 대한 우려가 있음
- 서버 메모리에 이벤트를 저장하고 매초 한 번씩 일괄적으로 쓰는 방법을 고려 중임
- SQLite의 많은 DB 쓰기 문제를 해결할 수 있는 합리적인 방법인지 의견을 구함
-
sqlite3-ruby gem의 GVL 문제
- sqlite3-ruby gem은 SQLite 호출 시 GVL을 해제하지 않음
- 이는 대부분 합리적인 결정으로 보임
- Python 확장에서는 다른 방식으로 설계되었을 가능성이 있음
- extralite gem은 블로킹 중 GVL을 해제하며, 일반적으로 더 빠르고 동시성 문제도 없음
-
개인 웹서비스 설정
- 개인 웹서비스에서 사용하는 몇 가지 설정:
-
PRAGMA journal_mode = WAL
-
PRAGMA busy_timeout = 5000
-
PRAGMA synchronous = NORMAL
-
PRAGMA cache_size = 1000000000
-
PRAGMA foreign_keys = true
-
PRAGMA temp_store = memory
-
BEGIN IMMEDIATE
트랜잭션 사용
-
- 개인 웹서비스에서 사용하는 몇 가지 설정:
-
Django에 대한 질문
- 이 기사는 훌륭함
- Django에 대한 유사한 솔루션이 있는지 궁금함
- ArchiveBox는 Django를 통해 SQLite를 사용하며, Rails에서 언급된 문제를 자주 겪음
- 앱의 다른 채널을 통해 모든 쓰기를 직렬화하지 않는 SQLite 레이어 솔루션이 있으면 좋겠음
-
busy_timeout
기본 설정에 대한 의문- 매우 유익하고 잘 작성된 기사임
- 기본
busy_timeout
메서드가 오래된 쿼리를 벌주는 지연을 가지는 이유가 궁금함 - 왜 이것이 기본 설정으로 의미가 있는지 궁금함
-
SQLite와 Rails 사용에 대한 의견
- SQLite와 Rails를 좋아하지만, 이는 MS Access를 프로덕션 환경에서 사용하는 것과 유사함
-
Rails 통합 문제 해결에 대한 감사
- 통합 문제를 해결하고 다른 사람들을 도와주는 것을 항상 기쁘게 생각함
- 이러한 수정 사항이 기본 Rails 설정에 포함되기를 바람
- Rails 앱을 운영하며, 몇 년 전 Postgres로 전환하여 매우 만족하고 있음
- 여전히 대안이 있는 것은 좋으며, 다른 작업에 SQLite를 사용함