Hacker News 의견
  • 예전에 SQLite의 블로킹 문제를 겪었는데, 원인은 디스크 조각화(fragmentation) 였음
    오래된 안드로이드 태블릿에서 앱을 몇 년간 매일 8시간씩 사용하던 사용자들이 느려짐과 락 오류를 호소했음
    데이터를 복사해 받아보면 재현이 안 됐는데, 결국 기기를 직접 받아 확인해보니 DB 파일을 새 위치로 복사했다가 원래 이름으로 되돌리는 식으로 ‘디프래그’하니 문제가 완전히 사라졌음
    Jellyfin DB에서도 같은 방법으로 성능 향상을 경험했음

    • 그건 조각화보다는 플래시 메모리 열화일 가능성이 높음. 혹시 eMMC 저장소가 들어간 저가형 태블릿이었는지 궁금함
    • SQLite의 VACUUM 함수로도 같은 효과를 낼 수 있는지 궁금함
    • 흥미로운 사례임. 하지만 사용자에게 직접 디프래그를 시킬 수는 없으니, 현실적인 해결책이 필요함
  • SQLite의 트랜잭션은 기본적으로 “deferred” 모드로 시작됨
    즉, 실제 쓰기 작업을 시도하기 전까지는 write lock을 잡지 않음
    SQLITE_BUSY 오류는 읽기 트랜잭션이 쓰기로 전환하려 할 때 이미 다른 트랜잭션이 write lock을 잡고 있을 때 발생함
    해결책은 busy_timeout을 설정하고, 쓰기를 포함하는 트랜잭션은 “immediate” 모드로 시작하는 것임
    관련 설명은 이 블로그 글에 잘 정리되어 있음

    • 나도 처음에 이건 SQLITE_BUSY 문제라고 생각했음. 관련 사례들을 여기에 모아두고 있음
    • SQLITE_BUSY는 일종의 아키텍처 냄새라고 생각함. WAL 모드에서는 읽기 전용 커넥션 풀과 단일 쓰기 커넥션 풀을 분리해 설계함. 이렇게 하면 락 보유 상태를 명확히 파악하고, 경합 상황을 사전에 설계할 수 있음
    • busy_timeout은 이 경우 적용되지 않음. WAL 모드에서는 페이지가 단일 로그 파일에 추가되므로, 읽기 중에 쓰기로 전환하려 하면 직렬화 보장을 위해 SQLite가 즉시 실패시킴. “immediate” 모드가 이를 방지함
    • 결국 SQLite를 쓰는 사람이라면 언젠가 이 문제에 한 번쯤은 데이고 원인을 추적하느라 시간을 쓸 것임
    • 블로그 글에서 SQLITE_BUSY 언급이 없던데, 아마 설정이 빠졌던 것 같음
  • 글의 일부 설명이 잘못된 듯함
    SQLite는 자체적으로 락 관리를 하기 때문에, 애플리케이션이 직접 파일 접근을 제어할 필요는 없음
    또한 WAL은 여러 병렬 쓰기를 허용하지 않음. 단지 읽기와 단일 쓰기를 동시에 수행할 수 있게 해줌

    • 나도 SQLite를 정말 좋아하지만, 이 글은 기본적인 동시성 개념부터 틀려 있어서 추천을 못 하겠음
  • SQLite는 훌륭한 데이터베이스지만, 기본 설정값(defaults) 이 너무 보수적이라 아쉬움
    실제 서비스용으로 쓰려면 여러 PRAGMA 설정을 조정해야 함

    • 어떤 PRAGMA를 기본으로 켜두면 좋은지 알고 싶음
    • 이런 상황이면 차라리 포크(fork) 해서 새 기본값을 만드는 게 낫지 않을까 싶음
  • SQLite의 새로운 hctree 기능이 안정화되면, 그때부터는 SQLite만 쓸 생각임
    이름의 hc는 아마 High Concurrency의 약자일 것 같음
    공식 문서 링크

  • 이런 글을 보면 문제의 근본 원인 분석보다는 임시방편적인 해결책에 그친다는 느낌을 받음
    더 깊은 디버깅과 연구로 정확한 원인을 밝혀내는 게 진짜 가치 있는 공유일 것 같음

    • 아마 글쓴이는 일부만 조사하고 불완전한 해결책을 공유한 듯함. HN에서 더 나은 답변을 유도하려는 의도였을 수도 있음. “틀린 답을 올리면 더 빨리 정답을 얻는다”는 말처럼 말임
  • WAL 모드도 결국 단일 쓰기, 다중 읽기 구조임을 이해하지 못한 듯함
    병렬 쓰기는 불가능하고, 단지 읽기 트랜잭션이 쓰기에 의해 블록되지 않게 해줄 뿐임
    완전한 MVCC가 있으면 좋겠지만, 현재 구조도 원리를 이해하면 충분히 잘 동작함

  • 나도 Jellyfin에서 비슷한 문제를 겪었음
    평소엔 잘 돌아가지만, 특정 상황에서 DB가 락된 채 멈추는 현상이 생김
    로그에는 “database is locked”만 남고, 결국 Docker 컨테이너를 재시작해야 해결됨
    주로 TV UI에서 여러 버튼을 빠르게 조작할 때 발생함

  • 약간 다른 이야기지만, SQLite in-memory DB를 대량의 insert/delete 작업에 쓰면 메모리 사용량이 점점 늘어남
    예를 들어 5분마다 10만 행을 넣고 지우는 작업을 며칠간 반복하면, macOS에서 메모리가 1GB까지 올라감
    이런 경우 조정할 만한 설정이 있을지 궁금함

    • 혹시 VACUUM을 주기적으로 돌리고 있는지, auto_vacuum이 켜져 있는지 확인해보길 권함
      VACUUM 문서
    • 버퍼가 사용 패턴에 맞게 동적으로 조정되는 정상 동작일 수도 있음
    • 모든 행을 지우는 경우라면, 그냥 테이블을 드롭하고 다시 생성하는 게 더 효율적임
  • SQLite는 훌륭하지만, 이런 문제를 보면 차라리 Postgres를 쓰는 게 낫지 않을까 싶을 때도 있음
    단일 파일 이동성이나 임베디드 용도가 아니라면 Postgres가 더 간단하게 동시성 문제를 해결함

    • 하지만 Jellyfin은 셀프호스팅 미디어 서버라서, Postgres를 요구하면 설치와 유지보수가 복잡해짐. SQLite가 더 적합함
    • Jellyfin은 대부분 가정용 단일 사용자 환경이므로 SQLite로 충분함. 다만 현재 설정이 최적은 아닌 듯함
    • SQLite의 장점을 무시하고 Postgres로 가자는 건, “캠핑하려는데 오두막을 지으라”는 말과 같음
    • Postgres를 쓰면 설치뿐 아니라 버전 업그레이드 시 마이그레이션도 신경 써야 함. SQLite는 이런 부담이 없음
    • Jellyfin은 최근 DB 코드를 Entity Framework로 리라이트해서, 앞으로는 DB 선택을 유연하게 할 수 있게 준비 중임