네트워크를 통한 SQLite 사용: 주의사항과 고려 사항

네트워크를 통한 SQLite 사용: 주의사항과 고려 사항

SQLite 라이브러리 사용자, 특히 응용 프로그램 개발자들은 네트워크로 연결된 서로 다른 시스템에서 SQLite 데이터베이스에 접근하려 할 때, 네트워크 파일 시스템 어딘가에 있는 데이터베이스 파일을 가리키는 파일 이름을 지정해 데이터베이스 연결을 그냥 여는 방법을 종종 고려해요. (여기서는 "원격 데이터베이스"라고 부를게요) 이 "파일"은 그다음 운영체제 API를 통해 접근되는데, 이 API는 로컬 파일에 대한 I/O인 것처럼 착각하게 만드는 환상을 제공해요. 그 환상은 괜찮지만 중요한 부분에서 불완전해요.

이 단순한 "원격 데이터베이스" 접근 방식은 단일 SQLite 데이터베이스를 여러 시스템에서 사용하는 최선의 방법은 보통 아니에요("작동하는" 것처럼 보여도), 여러 가지 문제와 골치 아픈 일을 자주 일으키기 때문이에요. 이러한 문제는 특정 사용 방식에서는 불가피하지만, 빈번하거나 반복적이지는 않기 때문에, 응용 프로그램 개발자는 초기 테스트 성공만 믿고 원격 데이터베이스 사용이 원하는 대로 동작할 것이라고 판단해서는 안 돼요.

출처: 문서

원격 데이터베이스 파일에서 발생하는 문제점

아래 다이어그램은 이후 논의를 위해 참조할 구성 요소와 그 연결 관계를 보여줘요:

Client Application  —  SQLite Database Engine  —  Database File(s)
     |                        |                          |
  SQLite API Calls        DB Engine File I/O

문제는 위 세 블록 사이의 두 데이터/제어 채널(data/control channel)의 속성과 사용 방식에서 발생해요.

채널 트래픽 양 (Channel Traffic Volume)

"API 호출(API Call)" 채널은 "파일 I/O(File I/O)" 채널보다 더 적은 정보를 전달해요. 쿼리를 제출하거나 데이터 수정을 지정하는 API 호출은 일반적으로 데이터를 저장하거나 찾기 위해 데이터베이스 파일과 오가는 비트보다 훨씬 적은 비트만 왕복하면 돼요. 쿼리 결과 검색은 요청되지 않은 데이터를 읽지 않고는 찾을 수 없는 경우가 많기 때문에, 보통 API 트래픽보다 훨씬 많은 파일 트래픽이 필요해요.

채널 대역폭 (Channel Bandwidth)

API 호출 채널은 프로세서 주 메모리 속도(기가워드/초)로 동작하고, 데이터는 종종 참조(reference)로 전달되며(따라서 복사되지 않아요) 반면 가장 빠른 파일 I/O 채널조차 더 느려요. 파일 I/O는 데이터를 복사해야 하고, 보통은 비트 직렬화(bit-serialization)를 요구하는 매체를 거쳐야 해요. 회전하는 자기 매체(magnetic media)의 경우 전송은 플래터 회전과 헤드 이동을 기다린 다음 회전 속도에 의해 제한돼요.

파일 I/O 채널에 네트워크 연결이 포함되면(먼 쪽 끝에 실제 파일 I/O가 추가로 있음) 추가적인 지연이 생겨요. 원시 전송 속도가 대역폭을 제한하지 않더라도 트래픽은 양쪽 끝에서 패킷화되고 버퍼링되어야 해요. I/O 핸들러의 추가 계층은 스케줄링 지연을 더해요. 하지만 느려진 전송은 네트워크 파일 시스템에서 가장 덜 중요한 문제예요.

채널 신뢰성 (Channel Reliability)

"API 호출" 채널은 오류율이 무시할 정도로 낮아 언급조차 되지 않을 만큼 매우 신뢰성이 높아요. 이 채널은 시스템의 전원이 꺼질 때만(운석 충돌 같은 걸 제외하면) 실패해요.

"파일 I/O" 채널은 로컬 저장 장치에 직접 닿을 때 역시 매우 신뢰성이 높아요. (회전 저장 장치의 MTBF는 100만 시간을 넘고, NVRAM은 더 오래가요.) 로컬 장치에는 데이터베이스 관리 소프트웨어가 ACID 동작을 보장하도록 설계될 수 있게 하는 중요한 특성도 있어요: 프로세스의 모든 쓰기가 장치에 완료되면(POSIX fsync() 또는 Windows FlushFileBuffers() 호출이 반환되면) 파일 시스템은 "쓰여진" 데이터를 이미 저장했거나, 이후에 쓰여질 데이터를 저장하기 전에 저장하게 돼요.

클라이언트와 실제 저장 장치 위의 파일 시스템 사이에 네트워크 파일 시스템 장치와 소프트웨어 계층이 끼어들면, 실패와 오동작의 중요한 원인이 생겨요. 네트워크 데이터 전송은 오류 검사가 잘 되지만, 전송 패킷이 한 번 보내졌다고 모두 확실히 목적지에 도착하지는 않아요. 일부 패킷은 다른 패킷에 의해 덮이면서 재전송되어야 해요. 패킷 덮임(packet clobbering) 상황에서는 반복적인 재시도로 인해 유사한 데이터가 로컬 저장 장치에 도달하는 데 필요한 시간을 넘는 지연이 발생할 수 있어요. 클라이언트가 쓴 내용의 일부는 다른 부분과 시간 순서가 맞지 않게 저장될 수 있어요.

네트워크 파일 시스템 쓰기에서 발생하는 순서 어긋남(disordering)과 완전한 데이터 손실 때문에, 어떤 파일 쓰기 집합이 끝났다는 것을 다음 쓰기 집합이 시작되기 전에 정확히 알 수 있는 것이 중요해요. 이 보장은 견고하게 설계되고 올바르게 구현된 fsync()(또는 동등한) OS 함수를 사용해서 얻어요. 불행히도 일부 응용 프로그램의 경우 네트워크 파일 시스템의 sync 동작은 로컬 파일 시스템의 sync보다 덜 견고할 수 있어요. 네트워크 패킷 전송 오류 상황에서 견고한 sync를 달성하는 것은 어렵고, 성능을 우선해 안전 장치가 느슨해지는 경우도 있어요.

네트워크 파일 시스템의 파일 잠금에도 비슷한 위험이 있어요. SQLite는 쓰기 작업에 배타적 잠금(exclusive lock)을 사용하는데, 일부 네트워크 파일 시스템에서는 이 잠금이 잘못 동작하는 것으로 알려져 있어요. 이로 인해 데이터베이스 손상이 발생한 적이 있어요. 그런 파일 시스템 설계자들이 더 흔한 사용 사례에 맞춰 구현을 바꾸면 또 발생할 수 있어요.

결론적으로, 네트워크 파일 시스템의 sync와 잠금 신뢰성은 구현과 설치 환경마다 다르다는 거예요. 그것이 의존하는 설계 가정은 응용 프로그램이 테스트된 환경에서보다 실제로 의존하는 환경에서 덜 성립할 수 있어요. 그것에 의존하는 것은 자신(과 고객)의 책임이에요. How To Corrupt Your Database Files를 참고하세요.

성능 및 신뢰성 문제 (Performance and Reliability Issues)

위 다이어그램과 논의에서, 두 채널 중 하나에 네트워크 링크가 끼어들면 성능(즉 "속도")이 저하된다는 것이 분명해요. API 호출 채널과 파일 I/O 채널 간의 상대적 트래픽 양을 고려하면, 그런 삽입은 API 호출 채널에서 성능 영향이 더 적다는 것을 알 수 있어요.

신뢰성 영향은 더 명확한 결과로 더 쉽게 고려할 수 있어요: API 호출 채널에 네트워크 링크를 끼워 넣으면 때때로 호출 실패가 발생할 수도 있어요. 하지만 클라이언트 응용 프로그램이 SQL/SQLite 트랜잭션을 제대로 사용했다면, 그런 실패는 트랜잭션을 실패시키고 롤백시킬 뿐 데이터 무결성을 해치지 않아요. 반면 네트워크 링크를 파일 I/O 채널에 끼워 넣으면(API 호출 삽입처럼) 트랜잭션이 실패할 수 있는데, 추가로 원격 데이터베이스가 손상되는 효과가 있어요.

이러한 네트워크 불안정성 문제는 SQLite를 롤백 모드(rollback mode)로 사용하면 완전히 또는 수용 가능한 수준으로 완화할 수 있어요. 하지만 SQLite 라이브러리는 네트워크를 통한 시나리오에서는 테스트되지 않았고, 그렇게 테스트하는 것도 합리적으로 가능하지 않아요. 따라서 원격 데이터베이스 사용은 사용자의 책임으로 이루어져요.

권장 사항 (Recommendations)

일반적으로, 데이터가 응용 프로그램과 네트워크로 분리되어 있다면 클라이언트/서버 데이터베이스를 사용하는 것이 좋아요. 데이터베이스 엔진이 데이터베이스 트래픽에 대한 대역폭 감소 필터 역할을 하기 때문이에요.

데이터가 응용 프로그램과 네트워크로 분리되어 있다면, 낮은 트래픽의 링크가 네트워크를 가로지르도록 하고, 높은 트래픽의 링크는 가로지르지 않도록 하는 것이 좋아요. 즉 데이터베이스 엔진이 데이터베이스 자체와 같은 머신에 있어야 해요. PostgreSQL 같은 클라이언트/서버 데이터베이스가 그런 경우예요. SQLite는 데이터베이스 엔진이 응용 프로그램과 같은 머신에서 실행되어, 원격 데이터베이스 시나리오에서 더 높은 트래픽의 링크가 네트워크를 가로지르게 강제한다는 점에서 달라요. 그 결과 일반적으로 성능이 더 낮아요.

네트워크 파일 시스템은 데이터베이스 일관성을 유지하면서 동시에 읽기와 쓰기를 동시에 수행하는 기능을 지원하지 않아요. 따라서 서로 다른 여러 머신의 여러 클라이언트가 동시에 데이터베이스 읽기와 쓰기를 해야 한다면 다음과 같은 선택지가 있어요:

  1. 클라이언트/서버 데이터베이스 엔진을 사용해요. PostgreSQL이 훌륭한 선택이에요. 이것의 변형으로는,

  2. SQLite 데이터베이스를 WAL 모드로 호스팅하되, 모든 읽기와 쓰기를 데이터베이스 파일을 저장하는 같은 머신의 프로세스에서 수행해요. 원격 머신에서 오는 읽기/쓰기 요청을 중계하는 프록시를 데이터베이스 머신에서 실행해요.

  3. SQLite를 롤백 모드로 사용해요. 즉 여러 동시 읽기 또는 하나의 쓰기는 가능하지만, 동시 읽기와 쓰기는 불가능하다는 뜻이에요.

응용 프로그램 프로그래머는 사용자가 가능하면 원격 데이터베이스를 선택할 가능성을 인지해야 해요. 위 선택지 중 하나를 적용했거나, 한 번에 하나씩 배타적 접근을 사용하지 않는 한, 프로그래머는 신뢰성이 크게 중요하지 않은 경우가 아니라면 그 선택을 차단하는 것을 고려해야 해요.

요약 (Summary)

자신과 고객에게 맞는 기술을 선택하세요. 데이터가 응용 프로그램과 다른 머신에 있다면 클라이언트/서버 데이터베이스를 고려해야 해요. SQLite는 데이터와 응용 프로그램이 같은 머신에 공존하는 상황을 위해 설계되었어요. SQLite는 많은 원격 데이터베이스 상황에서도 작동하게 만들 수 있지만, 그런 시나리오에서는 보통 클라이언트/서버 솔루션이 더 잘 작동해요.

더 알아보기 (Learn more)