요즘IT
위시켓
최근 검색어
전체 삭제
최근 검색어가 없습니다.

본문은 요즘IT와 번역가 David가 함께 Wafris의 블로그 글 <Rearchitecting: Redis to SQLite>을 번역한 글입니다. 필자인 마이클 벅비(Michael Buckbee)는 Wafris(무료 오픈 소스 웹 애플리케이션 방화벽)의 공동 창업자로, 다양한 조직의 웹사이트 방어를 지원해 온 보안 전문가이자, 애플리케이션 개발자입니다. 이 글에서는 데이터베이스 관리 시스템 Redis와 SQLite의 장단점을 알아보고, Redis에서 SQLite로 전환하게 된 이유를 소개합니다.

회원가입을 하면 원하는 문장을
저장할 수 있어요!

다음

회원가입을 하면
성장에 도움이 되는 콘텐츠를
스크랩할 수 있어요!

확인

개발

우리가 ‘Redis’에서 ‘SQLite’로 재설계한 이유

년차,
어떤 스킬
,
어떤 직무
독자들이 봤을까요?
어떤 독자들이 봤는지 궁금하다면?
로그인

본문은 요즘IT와 번역가 David가 함께 Wafris의 블로그 글 <Rearchitecting: Redis to SQLite>을 번역한 글입니다. 필자인 마이클 벅비(Michael Buckbee)는 Wafris(무료 오픈 소스 웹 애플리케이션 방화벽)의 공동 창업자로, 다양한 조직의 웹사이트 방어를 지원해 온 보안 전문가이자, 애플리케이션 개발자입니다. 이 글에서는 데이터베이스 관리 시스템 Redis와 SQLite의 장단점을 알아보고, Redis에서 SQLite로 전환하게 된 이유를 소개합니다.

 

필자에게 허락을 받고 번역했으며, 글에 포함된 각주(*표시)는 ‘번역자주’입니다. 글에 포함된 링크는 원문에 따라 표시했습니다.

 

배경

Wafris는 웹 애플리케이션 방화벽을 오픈소스로 제공하고 있습니다. 저희는 다양한 프레임워크를 지원하는데, 그중에서도 Rails 미들웨어 클라이언트를 제공하고 있죠. 처음 v1 클라이언트를 출시했을 때는 여러분의 앱과 함께 로컬 Redis 데이터스토어를 배포해야 했습니다. 하지만 이제 v2 Rails 클라이언트를 출시하면서 SQLite를 백엔드 데이터스토어로 사용하게 되었습니다.

 

이 글에서는 저희가 Redis에서 SQLite로 마이그레이션*하기로 결정한 이유, 성능에 대한 고려 사항, 그리고 아키텍처의 변화에 대해 다룰 예정입니다. 클라이언트(배포된 미들웨어*)를 위해 Redis에서 SQLite로 전환하게 된 의사결정 과정이 궁금하시다면 계속 읽어주세요.

 

*미들웨어: 서로 다른 시스템이나 프로그램들을 연결해 주는 다리 역할을 하는 소프트웨어

*마이그레이션: 한 시스템에서 다른 시스템으로 데이터를 이동하거나, 데이터베이스의 구조를 변경하는 과정

 

 

요약

  • SQLite에는 장점도 있고 단점도 있습니다.
  • Redis도 마찬가지로 장단점이 있죠.
  • 전통적인 *RDBMS(Postgres/MySQL)도 장단점이 있습니다.

 

*RDBMS: Relational Database Management System의 약자로, 데이터를 표 형태로 저장하고 관리하는 시스템

 

이러한 데이터 저장소는 서로 완벽하게 대체할 수 있는 게 아닙니다. 그냥 바꿔치기하려고 하면 골치 아픈 일이 생길 거고요. 그래서 저희가 v1의 Redis 기반 클라이언트를, v2의 SQLite 기반 클라이언트로 재설계하는 과정에서 진행한 테스트와 의사결정 과정을 자세히 설명해 보려고 합니다.

 

 

변화의 계기

Wafris를 처음 만들 때부터 우리의 목표는 개발자들이 최대한 쉽게 자신의 사이트를 보호할 수 있게 하는 것이었습니다. 하지만 v1에서는 그 약속을 지키는 데 아쉬운 점이 있었습니다. 당시에는 사용자가 직접 제공하는(사용자가 직접 설정하는 방식) Redis 데이터 스토어로, Wafris 클라이언트를 지원하는 게 좋은 선택이라고 생각했습니다.

 

이러한 결정은 우리가 Heroku 생태계에서 성장했기 때문입니다. Heroku에서는 Redis를 쉽게 클릭 한 번으로 시작하고, 원격으로 접근하기 쉬운 방법으로 배포할 수 있었습니다. Sidekiq 같은 성공적인 프로젝트도 비슷한 모델을 사용하고 있었기 때문이죠. 하지만 실제 생태계는 그보다 훨씬 복잡했습니다. 많은 사용자들이 Redis 배포 과정에서 디버깅하며, 해결하기 어려운 문제를 겪었습니다. 다시 말해, 우리는 여러분의 일을 쉽게 만들어 주려고 했는데, 오히려 ‘Redis 데이터베이스 관리자’로 만드는 실수를 범한 거죠.

 

게다가 RailsWorld 2023에 참가했을 때, Redis에 대한 부정적인 분위기를 읽었는데요. Rails 애플리케이션에는 당연히 Redis 서버를 실행해야 한다는 가정에 많은 사람들이 불편함을 느낀 것 같습니다.

 

 

속도 문제

Redis가 RDBMS보다 “빠르다”라고 하지만, 여전히 연결, 메모리, 프로세스 등을 관리해야 하는 데이터베이스입니다. 이러한 점이 오히려 시스템을 더 복잡하고 취약하게 만듭니다. 우리가 원하는 건 정반대인데 말이죠. 그리고 이보다 더 중요한 건 바로 클라우드 환경에서의 네트워크 지연 문제입니다. 네트워크 지연은 우리에게 큰 문제입니다. 여러분의 앱으로 들어오는 모든 HTTP 요청을 Wafris에 저장된 규칙과 대조해 평가해야 하기 때문입니다.

 

그래서 우리는 v1 클라이언트를 최대한 빠르게 만들려고 노력했지만, 종종 네트워크 자체가 느려 애플리케이션 속도가 저하되는 상황에 직면하곤 했습니다.

 

 

모놀리식에 대한 잘못된 가정

물론 완전히 분산된 애플리케이션도 있고, 대부분의 Rails 앱들이 ‘위대한 모놀리스’라고 하지만, 우리가 가정했던 것과는 다른 분산 앱들을 많이 발견했습니다. 여러 영역에 배포되는 앱, 책임이 겹치는 여러 서버로 기능을 나누는 앱 아니면 일부만 Rails로 되어 있고, 다른 언어나 프레임워크와 함께 배포되는 앱이죠.

 

그리고 대부분 실제 프로덕션 환경에서 이런 것들은 간단하지 않아서, Redis를 사용하는 데 더 많은 마찰을 일으켰습니다.

 

 

아키텍처를 다시 생각하게 된 계기

Wafris는 웹 애플리케이션 방화벽으로, Rails에서는 미들웨어로 설치됩니다. “IP 1.2.3.4를 차단” 같은 규칙을 설정할 수 있고, 누군가가 여러분의 사이트에 접근할 때 그 요청을 이러한 규칙과 비교해 평가합니다.

 

간단히 두 단계로 요약해 보겠습니다.

  1. HTTP 요청을 규칙과 비교합니다. (규칙과 일치하면 403, 아니면 200)
  2. 처리 결과를 보고합니다. (차단됨, 허용됨, 통과됨)

 

추상적으로 보면, 이는 데이터베이스에서 규칙을 ‘읽는’ 단계(1단계)와 그 요청이 어떻게 처리됐는지 보고서를 ‘쓰는’ 단계(2단계)로 구성됩니다.

 

논리적으로 볼 때, 첫 번째 ‘읽기’ 단계가 두 번째 ‘쓰기’ 단계보다 훨씬 더 중요합니다.

  • 읽기, 즉 “요청”은 순차적으로 처리해야 합니다.
  • 요청 필터링이 제대로 작동해야 합니다. 그렇지 않으면 잘못된 요청이 통과할 수 있습니다.
  • 읽기, 즉 “요청”은 사용자가 체감하는 사이트 성능에 영향을 미치므로, 시간에 민감합니다.
  • 쓰기, 즉 “보고”는 좀 더 느리게, 일괄적으로, 비동기로 처리할 수 있습니다.

 

 

SQLite의 등장

SQLite가 일반적으로 어떤 용도에 적합한지는 이미 다른 곳에서 잘 설명해 둔 글이 있어, 아래 첨부한 자료들을 참고해 주세요. 

 

 

우리가 SQLite를 선택한 이유?

앞서 말했듯이 우리의 가장 큰 병목 지점은 네트워크 입출력이며, Stephen은 SQLite 문서에서 이런 문장을 언급했습니다.

 

“SQLite는 RDBMS와 비교하기보다는 fopen()* 시스템 콜*을 사용하는 파일 DB*와 비교했을 때 이점을 갖습니다.”

 

*fopen(): C 프로그래밍 언어에서 사용되는 파일 입출력 함수 

*시스템 콜: 프로그램이 운영체제의 커널에 직접 서비스를 요청하는 방법

*파일 DB: 파일 시스템에 직접 데이터를 저장하고 관리하는 데이터베이스 방식

 

이론적으로 보면, 네트워크 왕복 시간을 없애버리니까 Redis 솔루션보다 훨씬 빠를 거라고 생각했죠. 그래서 우리는 SQLite와 Redis의 성능을 비교해 보기로 했습니다.

 

 

SQLite와 Redis 벤치마킹하기

벤치마킹이 참 묘한데 정밀한 숫자로 자기 자신을 속이는 어두운 기술입니다. 거기다 데이터베이스의 벤치마킹은 좀 더 까다롭습니다. 제가 본 모든 데이터베이스 벤치마크에는 별표와 조건이 가득했고, Hacker News의 댓글은 “컴파일할 때 이 플래그만 설정했어도 읽기 속도가 3% 더 빨라졌을 텐데, 이걸 안 한 걸 보면 뒷돈을 받았거나 NFT 사기를 치는 게 분명해!” 같은 내용이 담겨있습니다.

 

하지만 우리에겐 이점이 있습니다. 우리는 완벽한 조건과 세심하게 조정된 설정으로 절대적인 속도를 얻으려는 게 아닙니다. 우리는 다른 누구도 기대하지 않는 극단적인 사용 사례에 대해 우리만의 특별하고 편향된 벤치마크를 만들고 있습니다.

 

최적화 조정은 의도적으로 무시하기

우리의 목표는 사람들이 Wafris를 앱에 던져 넣고, “그냥 잘 작동하게” 하는 것입니다. 이론적인 벤치마크 스위트(benchmark suite)*로 테스트하는 것도 아니죠.

*Benchmark suite: 소프트웨어나 하드웨어의 성능을 측정하고 비교하는 데 사용되는 일련의 표준화된 테스트 도구들을 의미함.

 

우리는 애플리케이션의 핫 패스(hot path)*와 가장 복잡한 쿼리를 테스트하고 있습니다.

*Hot path: 프로그램 실행 중 가장 자주 실행되는 코드 경로를 의미함.

 

가장 복잡한 쿼리는 IP 범위(v4와 v6)를 카테고리에 매핑하는 “lexical decimal" 데이터 구조를 요청하는 쿼리입니다. 가장 단순한 예시는 IP -> 국가 매핑으로, IP 주소가 두 주소 범위 안에 있으면 해당 국가를 반환하는 경우입니다.

 

이 구조는 필연적으로 큰 구조(수백만 행)이며, 특히 IPv6에서는 각 항목이 매우 크기 때문입니다. 이를 구현하기 위해 우리는 미리 범위 계산해서 다음과 같은 방식으로 저장했습니다.

 

들어오는 모든 HTTP 요청에 대해, 최악의 경우 요청 IP를 이렇게 확인해야 합니다.

  1. 사용자 정의 허용 범위
  2. 사용자 정의 차단 범위
  3. GeoIP 범위
  4. IP 평판 범위

 

이게 바로 우리가 말하는 “핫 패스”입니다. 이 특정 쿼리 유형이 너무 중요해서 다른 쿼리 유형이나 기능은 무시해도 될 정도죠. 그래서 벤치마킹 스크립트에서는 이 쿼리 유형만 테스트했습니다. 덕분에 앱의 다른 중요한(하지만 핫 패스와는 무관한) 부분을 벤치마크로 옮기는 데 며칠씩 걸리는 수고를 덜 수 있었습니다.

 

 

테스트 프로토콜

 

 

테스트는 제 맥북 에어 M2에서 진행했으며, 홈브루로 설치한 Redis와 로컬 SQLite DB를 사용했습니다.

  1. 우리가 가지고 있는 범위 데이터셋(120만 개 항목)으로 테스트했습니다.
  2. 여러 세트의 IP를 SQLite와 Redis에 같은 순서로 돌렸습니다.
  3. 각 세트마다 테스트를 5번씩 돌리고 평균을 냈습니다.

 

 

테스트 결과

 

 

SQLite가 Redis를 완전히 압도했습니다. 마치 과대 선전된 UFC 파이터가 펜스에서 뛰어내려 킥 펀치를 날리는 것처럼 말입니다. (물론 우리의 특수한 사용 사례에 한정해서요) SQLite가 로컬에 설치된 Redis 인스턴스보다 약 3배 정도 빨랐습니다. 물론 여러분의 경우는 다를 수 있죠. 그리고 이건 네트워크 지연 시간을 고려하기 전의 결과라는 걸 다시 한번 말씀드립니다.

 

우리로서는 정말 대단한 결과입니다. SQLite가 로컬에서 Redis와 비슷한 성능만 내도 네트워크 시간을 완전히 없앨 수 있어서 유리하기 때문이죠. 물론 다시 한번 강조하지만, 이 테스트는 매우 단순하고 불완전한 방식으로 진행됐습니다. 대신 우리가 관찰한 현실 세계의 결함을 그대로 반영하는 방식으로 진행했으므로, 의미가 있습니다.

 

 

차트에서 놓친 점은?

벤치마크는 현실과 동떨어진 환경에서 이뤄지죠. 이 차트가 보여주지 못하는 부분이 있습니다.

 

  • 만약 벤치마크에서 SQLite 성능이 훨씬 나빴다고 해도 (예를 들어 2배 정도 느렸다고 해도), ‘실제 환경’에서는 여전히 더 빠를 것입니다. 네트워크 지연 때문이죠. Redis가 같은 데이터 센터나 지역에 있어도 마찬가지입니다.
  • 아주 튼튼한 Redis 서버(클러스터, 샤딩 등)가 있다고 해도 네트워크 대역폭이나 연결 수 등의 제한이 있고, 지역 간 지연 시간도 있습니다. 반면 SQLite는 무료로 거의 무한한 수평 확장이 가능합니다.
  • SQLite로 하면 시작하기도 훨씬 수월합니다. 대부분의 사용자들은 SQLite가 쓰이고 있다는 사실조차 모를 것입니다. 그냥 웹 앱에 해당 gem을 추가하면 바로 쓸 수 있기 때문이죠.
  • Redis에서도 성능을 더 최적화할 방법은 많습니다. 하지만 사용자들에게 기본적인 설정 변경 (캐시 제거 정책 등)조차 설득하기 어려웠습니다. 이는 Redis 관리가 너무 번거롭고, 사용자들은 Redis 관리자가 되길 원하지 않기 때문이죠.

 

우리의 초점은

  1. Wafris를 쉽게 배포할 수 있게 만들기
  2. 규칙 평가를 최대한 빠르게 처리하기

 

이것이 핵심입니다. 다른 부분은 다 여기서 파생되므로, 위 두 가지 목표를 위해 아키텍처를 다시 설계하고 있습니다. 우리는 ‘최고의 데이터베이스 설정’이나 ‘우리(Wafris)가 관리하기 가장 쉬운 인프라’를 목표로 하지 않습니다.

 

 

결과는 또 다른 시작일 뿐입니다

좋습니다, 이제 SQLite가 Redis보다 빠르다는 걸 확인했습니다. 하지만 아직 모든 게 완벽한 건 아닙니다. 현실 세계에서는 트레이드오프가 있기 때문이죠. 위 테스트에서 우리가 간과한 엄청 큰 트레이드오프 중 하나는 ‘쓰기’ 작업을 전혀 고려하지 않았다는 것입니다.

 

명시적으로 말하진 않지만, ‘제대로 된’ 데이터베이스가 연결, 커넥션 풀, 트랜잭션 등 다양한 기능을 갖추는 이유는 읽기와 쓰기가 데이터베이스 내에서 서로 충돌하지 않도록 관리하기 위해서입니다. 이와 같은 기능들은 데이터베이스의 신뢰성을 높여주므로, 오라클(Oracle) 같은 회사는 이를 바탕으로 엄청난 가격을 매길 수 있는 것입니다.

 

예를 들어, 영국인이 운전하는 중국산 전기 슈퍼카를 생각해 보세요. 이 차는 빠르고 환상적이지만, 콘크리트 블록을 도시 전체에 나르는 데는 전혀 적합하지 않죠.

 

 

SQLite도 마찬가지입니다. 마치 NIO EP9 슈퍼카처럼, SQLite는 잘하는 일에 써야 하고, 맞지 않는 역할을 억지로 시켜선 안 됩니다.

 

 

동기화 아키텍처 구축하기

v1(기존 Redis) 버전에서는 업데이트 과정이 이렇게 진행됐습니다.

  1. 사용자가 Wafris Hub에서 규칙 (예: IP 1.2.3.4 차단)을 업데이트합니다.
  2. Wafris Hub가 여러분의 Redis 데이터 저장소에 있는 규칙을 업데이트합니다.

 

이 방식은 당연히 SQLite에서는 되지 않습니다. 웹 서버에 SQLite 데이터베이스를 푸시할 수 없기 때문입니다. 최근에는 이를 지원하는 SQLite 서비스 제공자가 있지만, 비용, 성능, 보안 등 여러 이유로 우리한테는 맞지 않았습니다. 여전히 개별 사용자들이 직접 배포하고, 포트를 열고, 인바운드 연결을 허용해야 하기 때문입니다.

 

그래서 v2(SQLite) 버전에서는 업데이트 과정이 이렇게 바뀌었습니다.

  1. 사용자가 Wafris Hub에서 규칙을 업데이트합니다.
  2. 일정 간격(시간이나 요청 횟수)에 클라이언트가 업데이트된 규칙이 있는지 확인합니다.
  3. 규칙이 업데이트됐다면, 클라이언트가 새로운 SQLite 데이터베이스를 다운로드합니다.

 

이 방식은 사용자가 설치 및 설정하는 부담을 크게 줄여주어 잘 작동하고 있습니다. 실제로 v2 클라이언트의 성공적인 설치율이 약 3배 정도 늘어난 걸 확인했습니다.

 

 

SQLite 분산 아키텍처

이번에는 오토스케일링 기능이 켜진 클라우드 제공업체(AWS, Heroku, Fly 등)에 배포된 Rails 앱을 생각해 볼게요. 요청이 초당 100개에서 10,000개로 급증했다고 가정해 봅시다. 그러면 컴퓨팅 리소스(다이노, 머신, EC2 인스턴스)가 부하를 처리하기 위해 늘어나기 시작합니다. 그런데 데이터베이스도 그렇게 늘어날까요?

 

아마도 확실히 아닐 겁니다. 데이터베이스 없이도 병목 현상은 병목 현상이기 때문이죠. 혹시 몰라서 데이터베이스를 100배 초과로 할당해 둘 수도 있지만, 현실적으로 그렇게 하기는 힘들 겁니다.

 

 

실제로 이렇게 부하가 심할 때, Rails 앱을 죽이는 가장 큰 원인이 바로 이것입니다. 실제 DDoS 공격인 경우는 드뭅니다. 대부분 자격 증명 대입 공격이나 악성 봇이 사이트를 계속 공격해 오토스케일링이 작동하고, 이로 인해 데이터베이스 연결이 부족해 지면서 앱이 멈추는 경우가 많습니다.

 

 

따라서 SQLite DB를 각 컴퓨팅 인스턴스로 동기화하는 시스템으로 바꾸면, 해당 문제가 해결될 것입니다. 모든 호출을 새 컴퓨팅 인스턴스 내부에서 로컬로 처리할 수 있기 때문입니다.

 

 

그런데 쓰기(Write)는 어떡하죠?

이 글을 시작할 때 앱을 읽기(규칙 평가)와 쓰기(보고) 경로로 나누는 얘기했죠. 그런데 쓰기 경로는 쏙 빼고 얘기했습니다.

 

사실 우리는 쓰기 경로도 이렇게 재설계했습니다.

  • Wafris Hub에 비동기로 연결해서 보고하기
  • 보고 데이터를 한 번에 모아서 보내기
  • 클라이언트에서 데이터베이스 쓰기를 완전히 없애기

 

이 방식은 다른 사람들에겐 거의 적용되지 않을 수 있지만, 우리는 크게 신경 쓰지 않습니다. 우리의 관심은 Wafris 클라이언트를 쉽게 배포하고, 빠르게 쓰고 싶어 하는 100%의 사용자들입니다. 이 글이 여러분에게 어떤 교훈을 줄 수 있을지 모르겠지만, 작은 도움이라도 되길 바랍니다.

 

 

마치며

우리는 SQLite를 사용하는 v2 아키텍처에 매우 만족하고 있습니다. 덕분에 많은 사이트들이 공격을 받아도 끄떡없이 버티고 계속 돌아갈 수 있었죠. v2는 시작하기 훨씬 쉽고, 우리의 지원 업무도 줄여줬습니다. 사용자도 번거로움이 줄었고요. 이는 더 안전하고 보안이 강화된 인터넷을 위한 큰 성과라고 생각합니다.


<원문>

Rearchitecting: Redis to SQLite

 

위 번역글의 원 저작권은 Michael Buckbee에게 있으며, 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다

좋아요

댓글

공유

공유

작가
461
명 알림 받는 중

작가 홈

작가
461
명 알림 받는 중
요즘 해외 개발자들은 어떻게 일할까요? 기획자나 디자이너는요? 그래서 준비했습니다. 읽어볼만한 해외 소식들을 번역해 전합니다. "We are the world."

좋아요

댓글

스크랩

공유

공유

요즘IT가 PICK한 뉴스레터를 매주 목요일에 만나보세요

요즘IT가 PICK한 뉴스레터를
매주 목요일에 만나보세요

뉴스레터를 구독하려면 동의가 필요합니다.
https://auth.wishket.com/login