# SQLite의 트랜잭션

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

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=16105](https://news.hada.io/topic?id=16105)
- GeekNews Markdown: [https://news.hada.io/topic/16105.md](https://news.hada.io/topic/16105.md)
- Type: news
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2024-07-31T10:31:01+09:00
- Updated: 2024-07-31T10:31:01+09:00
- Original source: [reorchestrate.com](https://reorchestrate.com/posts/sqlite-transactions/)
- Points: 15
- Comments: 0

## Summary

SQLite는 개발자의 의도와 달리 최근 서버 프로세스의 백엔드 용도로 인기를 끌고 있습니다. SQLite는 엄격한 SERIALIZABLE 트랜잭션을 제공하여 데이터 일관성을 보장하며, 다양한 트랜잭션 모드를 통해 개발자에게 유연성을 제공합니다. SQLite가 제공하는 IMMEDIATE, EXCLUSIVE, DEFERRED 등에 대해 설명하며, 실험적 기능인 CONCURRENT 모드는 여러 쓰기 트랜잭션을 동시에 처리할 수 있어 성능 향상을 기대할 수 있습니다.

## Topic Body

- 최근 몇 년 동안 SQLite는 서버 프로세스의 백엔드 용도로 강력한 인-프로세스 고신뢰성 SQL 데이터베이스 엔진으로 각광받음  
- 전통적인 클라이언트 또는 엣지 애플리케이션 역할이 아닌 이런 용도로 SQLite가 사용되는 것을 SQLite 개발자들이 거의 적극적으로 만류하고 있음에도 불구하고 인기가 급증함  
  
### 내가 SQLite에 관심을 갖게 된 주요 이유들:  
- 개념적으로 단순함: 기본키로 파티션된 행/튜플의 B-트리를 상상해 봄. 이를 디스크에 안정적으로 지속시키기 위해 광범위하게 테스트되었고, SQL 상호작용 계층을 추가함   
- Litestream을 통해 실용적인 백업 전략을 수립할 수 있음. 원격 위치로 WAL을 백업하고 지속적으로 복제함. 백업은 간단한 명령어로 시작시 자동 복원 가능함.   
- 오프라인에서 실행 가능한 완전한 개발 환경을 여전히 좋아함  
- file::memory:를 통해 인-메모리 작업이 가능해 필요시 테스트 코드가 인스턴스를 쉽게 시작하고 종료할 수 있음  
  
#### Single-writer 제한  
- SQLite 개발자들에 의해 "서버에서의 SQLite 제한"이 잘 문서화되어 있고, 최상의 서버 측 설정이 분석됨. 하지만 눈에 띄는 제한은 고트래픽 웹사이트인데, 이는 쓰기가 많은 웹사이트를 의미함  
- WAL 모드에서 SQLite는 설계상 단일 Writer를 사용함. 이는 동시에 최대 1개의 쓰기 트랜잭션과 여러 읽기전용 트랜잭션을 허용함   
- 이 설계는 고트래픽 쓰기 집중 웹사이트의 병목을 해당 단일 Writer의 처리량 관리에 두고 있음. 이는 현대 기술의 핵심 구성요소 중 하나로 다시 돌아옴  
  
##### SQLite  
- SQLite는 기본적으로 엄격한 SERIALIZABLE Isolated 트랜잭션을 제공함. 이는 Isolation 보증의 가장 강력한 수준임  
- 단일 Writer를 사용함으로써 SQLite는 쓰기 트랜잭션이 진행되는 동안 기본 데이터가 변경되지 않았음을 쉽게 보장할 수 있는 Pessimistic concurrency control 형식을 사용하고 있음  
  
##### Postgres  
- Postgres는 실제로 SQL 표준에서 정의된 `SERIALIZABLE` 기본값과 다르며, 더 완화된 `READ COMMITTED`를 선택함(훨씬 더 복잡한 Multiversion concurrency control에도 불구하고)  
  - 이러한 엄격성 감소는 non-repeatable reads의 위험이 있음. 즉, 동일한 트랜잭션 내에서도 다른 COMMITTED 트랜잭션에 의해 백그라운드에서 값이 변경되면 동일한 읽기 쿼리를 여러 번 실행할 때 다른 결과를 검색할 수 있음   
  - 이 Isolation 수준을 선택함으로써 Postgres는 트랜잭션이 오래된 데이터로 작동할 위험을 열어둠. 개발자는 이 사실을 명심해야 함  
- `SERIALIZABLE`로 설정하면 Postgres는 optimistic-concurrency control 체계를 사용하여 transaction 중에 액세스한 데이터를 추적하고 커밋 전에 변경되지 않았는지 확인함  
  - Postgres는 메모리 사용량을 관리하기 위해 transaction에 따라 row level 또는 page level의 잠금을 기반으로 이를 수행함  
  - 이 패턴은 transaction이 커밋될 때 transaction에 의해 모니터링되는 데이터가 더 세분화될수록 변경될 가능성이 적기 때문에 기본 데이터가 변경되지 않을 것으로 예상되기 때문에 `optimistic`이라고 함  
  
##### FoundationDB  
- 트랜잭션은 관계형 데이터베이스에만 국한되지 않음. 분산 키-값 저장소에서 SERIALIZABLE 보장을 달성하기 위해 Optimistic concurrency control 사용  
- NoSQL이 등장했을 당시, ACID 보증이 있는 분산 NoSQL 저장소는 일반적이지 않았음. FoundationDB는 트랜잭션 매니페스토를 작성하여 개발자가 ACID 보증으로부터 크게 혜택을 받을 수 있음을 강조함  
- FoundationDB는 Optimistic concurrency control을 위한 코드 작성 방법과 때로는 동시 트랜잭션 충돌로 인해 데이터가 변경되고 트랜잭션이 자동으로 재시도된다는 사실에 대한 조언을 제공함  
  
#### Idempotence  
- 멱등 트랜잭션은 한 번 커밋할 때와 두 번 커밋할 때 동일한 효과를 갖는 트랜잭션임  
- FoundationDB는 트랜잭션을 멱등으로 만들기 위해 충돌로 인해 트랜잭션을 여러 번 재시도해야 하는 경우 문제 방지를 위한 패턴을 제공함  
  
그렇다면 이 모든 것을 염두에 뒀을때 SQLite는 어떤 옵션을 제공할까?  
  
### BEGIN …  
SQLite는 개발자들이 WAL 모드에서 DEFERRED vs IMMEDIATE로 축소될 수 있는 IMMEDIATE, EXCLUSIVE, DEFERRED 키워드 형태로 트랜잭션이 동작할 방식을 엔진에 표시할 수 있는 여러 방법을 제공함  
  
##### DEFERRED  
- 트랜잭션이 다른 읽기 또는 쓰기 트랜잭션과 동시에 실행될 수 있는 READ 모드로 시작됨  
- DB 상태를 수정하는 쿼리(INSERT, UPDATE, DELETE)가 실행되는 경우에만 차단 READ-WRITE 트랜잭션으로 업그레이드됨  
- 업그레이드시 DB가 다른 트랜잭션에 의해 잠겨있으면 SQLITE_BUSY 오류 반환. 클라이언트가 이를 처리해야 함  
  
##### IMMEDIATE  
- 트랜잭션이 즉시 READ-WRITE 모드로 시작됨  
- 쓰기 트랜잭션이 이미 실행 중인 경우 즉시 SQLITE_BUSY 반환  
- 클라이언트가 처리 방법을 결정해야 함  
  
##### CONCURRENT  
- SQLite에는 트랜잭션을 Pessimistic에서 제한된 Optimistic으로 이동하는 실험 지점이 있음  
- 제한된 이유는 Optimistic locking이 행/튜플 수준이 아닌 DB 페이지 수준(기본 4096 bytes)에서 작동하기 때문  
- CONCURRENT 모드에서 SQLite는 여러 쓰기 트랜잭션을 동시에 활성화할 수 있지만, 커밋 전에 트랜잭션 수행 중 액세스한 페이지가 트랜잭션 시작 이후 변경되지 않았는지 확인함  
- 충돌이 발생하지 않으면 변경 사항이 순차적으로 커밋되고 엄격한 SERIALIZABLE 보장을 달성함. 충돌 발생시 SQLITE_BUSY 반환  
  
##### HC-Tree  
- SQLite의 또 다른 실험 브랜치는 \[HC-Tree\]로, 낙관적 행/튜플 수준 잠금을 제공하는 것을 목표로 하는 진행 중인 작업임. 흥미로운 결과 중 하나는 그러한 설계의 성능 이점을 `BEGIN CONCURRENT` 브랜치와 비교하여 보여주는 우수한 벤치마크 세트를 제공한다는 것  
  
그들의 벤치마킹 접근 방식을 가져와서 표준 옵션에 대해 실행해 보는 것은 어떨까?  
  
### 벤치마킹  
  
##### nUpdate=1, nScan=0  
- 이 write-only 트랜잭션은 IMMEDIATE vs DEFERRED의 이점이 분명함을 보여줌. Locking은 즉시 발생하며 트랜잭션은 upgrading 비용의 영향을 받지 않음  
- CONCURRENT는 스레드 수가 늘어나고 충돌이 증가함에 따라 처리량 증가를 보여줌  
  
##### nUpdate=10, nScan=0   
- 예상대로 write 일괄 처리는 16 스레드에서 업데이트된 행 수에 많은 도움이 됨. CONCURRENT는 ~12k/sec에서 ~19k/sec로 증가함  
- IMMEDIATE vs DEFERRED는 덜 중요해짐. 트랜잭션 업그레이드 비용보다 업데이트 자체 비용이 더 중요해지기 때문  
  
##### nUpdate=1, nScan=10  
- 이 트랜잭션은 random read로 인해 page 수준 CONCURRENT locking의 약점을 노출시켜야 함   
- 업데이트할 트랜잭션에 IMMEDIATE를 사용하는 것이 DEFERRED 업그레이드 비용보다 중요한 이유를 바로 보여줌  
- CONCURRENT의 경우 기본 충돌이 실제로 크게 증가하지 않았기 때문에 이러한 결과가 매우 견고함   
  
##### nUpdate=0, nScan=10  
- 이 read-only 일괄 처리 트랜잭션은 Pessimistic concurrency control의 영향 보여줌  
- 모든 트랜잭션에 IMMEDIATE를 기본값으로 설정하면 안 되는 이유를 보여줌   
- CONCURRENT vs IMMEDIATE는 CONCURRENT 모드를 사용할 때 약간의 단점이 있음을 나타냄. "모든 경우에 성능이 약간 떨어짐"  
  - 하지만 CONCURRENT는 좋은 기본 옵션이 될 것임

## Comments



_No public comments on this page._
