[hermes-agent] SQLite WAL 모드를 활용한 Read-Path 최적화: Lock Contention 해결하기
PR 링크: NousResearch/hermes-agent#73344 상태: Merged | 변경: +336 / -47
들어가며
많은 Python 애플리케이션에서 sqlite3를 사용할 때, 단일 커넥션에 의존하며 threading.Lock을 통해 동시성 문제를 해결하곤 합니다. 하지만 Hermes-agent와 같이 읽기 작업이 빈번한 환경에서는, 쓰기 작업이 진행되는 동안 모든 읽기 작업이 self._lock에서 대기(convoy)하게 되어 심각한 성능 저하가 발생합니다. 본 PR은 SQLite의 WAL(Write-Ahead Logging) 모드를 활용하여, 읽기 전용 커넥션을 스레드별로 분리함으로써 쓰기 작업과 읽기 작업이 서로를 차단하지 않도록 최적화한 사례를 다룹니다.
코드 분석
1. Read-Path 분리 및 스레드별 커넥션 관리 (hermes_state.py)
핵심은 threading.local()을 사용하여 각 스레드가 자신만의 읽기 전용 커넥션을 갖게 하는 것입니다. 이를 통해 self._lock을 거치지 않고도 안전하게 데이터를 조회할 수 있습니다.
# Before: 모든 읽기 작업이 self._lock을 점유
with self._lock:
cursor = self._conn.execute("SELECT * FROM sessions WHERE id = ?", (session_id,))
# After: _read_ctx()를 통해 WAL 모드에서 Lock 없이 읽기 수행
with self._read_ctx() as conn:
cursor = conn.execute("SELECT * FROM sessions WHERE id = ?", (session_id,))
_get_read_conn 메서드는 mode=ro URI를 사용하여 읽기 전용 커넥션을 생성하며, _read_conns 세트를 통해 모든 활성 커넥션을 추적합니다. 이는 리소스 누수를 방지하고, close() 호출 시 모든 커넥션을 안전하게 정리하기 위함입니다.
2. 라이프사이클 및 Race Condition 방지
리뷰 과정에서 지적된 close() 호출 시점과 커넥션 생성 간의 Race Condition을 해결하기 위해 _read_conns_closed 플래그를 도입했습니다.
# _get_read_conn 내부의 안전 장치
with self._read_conns_lock:
if self._read_conns_closed:
conn.close()
self._read_local.failed = True
return None
self._read_conns.add(conn)
왜 이게 좋은가
- Lock Contention 제거: WAL 모드에서는 읽기와 쓰기가 서로를 차단하지 않습니다. 기존에는 쓰기 작업이 완료될 때까지 모든 읽기 요청이
self._lock에서 대기해야 했으나, 이제는 각 스레드가 독립적으로 데이터를 조회할 수 있어 처리량(throughput)이 비약적으로 향상되었습니다. - 리소스 관리:
WeakSet대신 강한 참조를 가진set을 사용하고,close()시점에 명시적으로 커넥션을 정리함으로써 파일 디스크립터(fd) 누수 문제를 완벽히 해결했습니다. - 안정성:
_read_conns_closed플래그를 통해 종료 과정에서 발생하는 예기치 않은 커넥션 생성을 방지하여, 시스템 종료 시점의 안정성을 확보했습니다.
이러한 최적화는 데이터베이스 접근이 잦은 고성능 에이전트 시스템에서 필수적인 패턴입니다. 특히 SQLite를 사용할 때 WAL 모드와 스레드별 커넥션 전략을 결합하면, 단일 커넥션의 한계를 극복하고 동시성 성능을 극대화할 수 있습니다.
참고 자료
- https://docs.python.org/3/library/sqlite3.html
- https://www.sqlite.org/wal.html
- https://docs.python.org/3/library/threading.html#threading.local
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
PR Analysis 의 다른글
- 이전글 [hermes-agent] SQL GROUP BY를 활용한 세션 통계 쿼리 최적화: 575ms에서 1ms 미만으로
- 현재글 : [hermes-agent] SQLite WAL 모드를 활용한 Read-Path 최적화: Lock Contention 해결하기
- 다음글 [triton] Triton JIT의 메모리 누수 방지: Reference Cycle 제거를 통한 성능 최적화
댓글