- Wafris는 Rails 미들웨어 클라이언트를 제공하는 오픈 소스 웹 애플리케이션 방화벽 회사임
- v1 클라이언트는 로컬 Redis 데이터 저장소가 필요했으나, v2에서는 SQLite를 사용함
- Redis에서 SQLite로 마이그레이션하기로 결정한 배경과 성능 고려사항, 아키텍처 변경 사항을 설명
TL;DR
- SQLite는 잘하는 것과 못하는 것이 있음
- Redis는 잘하는 것과 못하는 것이 있음
- 전통적인 RDBMS(Postgres/MySQL)는 잘하는 것과 못하는 것이 있음
- 이러한 데이터 저장소는 직접 교체할 수 없으며, 그렇게 하려고 하면 곤란해짐
- 이 글은 Redis 기반 v1 클라이언트를 SQLite 기반 v2 클라이언트로 리아키텍팅하면서 거친 테스트와 의사 결정 과정을 설명함
변경을 강제한 요인
- Wafris의 목표는 개발자들이 쉽게 사이트를 보호할 수 있게 만드는 것임
- Redis 배포 이슈로 인해 v1은 이 목표를 완전히 달성하지 못함
- Heroku 등 Redis를 쉽게 사용할 수 있는 환경에서 작업했기에 Redis를 선택했으나, 많은 사용자가 Redis 배포 이슈를 겪음
- Redis 같은 별도 DB를 사용하게 하는 건 사용자를 위한 게 아님
"속도"란 무엇인가?
- Redis는 전통적 RDBMS보다 "빠르지만", 여전히 연결, 메모리, 프로세스 등을 관리해야 함
- 클라우드 환경에서는 네트워크 지연이 큰 문제가 될 수 있음
- 들어오는 HTTP 요청마다 Wafris 규칙을 평가해야 하므로 네트워크 지연이 애플리케이션 속도를 늦출 수 있음
단일체(Monolith-ish) 가정
- 완전 분산 애플리케이션도 있지만, 대부분 Rails 앱은 "장엄한 모놀리스(Majestic Monoliths,단일체)"임
- 여러 영역에 배포되고, 기능이 겹치는 서버로 분할되거나, 부분적으로만 Rails인 앱은 Redis 사용 시 더 많은 문제가 발생함
아키텍처에 대한 재고
- Wafris는 Rails 미들웨어로 설치되는 웹 애플리케이션 방화벽임
- 간단히 2단계로 나누면 1) HTTP 요청을 규칙과 비교하고, 2) 처리 결과를 보고함
- 규칙 "읽기"(1단계)가 "쓰기"(2단계)보다 훨씬 중요함
- 읽기는 순차적으로 처리되어야 하고, 실패하면 안되며, 사용자 체감 성능에 영향을 줌
- 쓰기는 느리게, 일괄 처리하거나 비동기로 할 수 있음
Enter SQLite
- SQLite가 적합한 용도에 대해서는 다른 이들이 잘 설명하고 있음
- SQLite는 클라이언트/서버 DB와 경쟁하는 게 아니라 fopen()과 경쟁함
- 네트워크 왕복을 제거하면 Redis보다 훨씬 빨라질 것으로 예상됨
- SQLite와 Redis의 벤치마크 평가를 결정함
SQLite와 Redis 벤치마킹
- 벤치마킹은 정밀한 숫자로 자신을 속이는 어두운 기술임
- 데이터 저장소 벤치마킹은 더욱 까다로움
- 절대적 속도를 구하려는 게 아니라 우리 데이터와 사용 사례에 특화된 벤치마크를 만듦
- 최적화 트윅은 무시함. Wafris를 앱에 넣으면 바로 작동하길 원함
- 이론적 벤치마크가 아닌 우리 앱의 주요 경로와 최악의 쿼리를 테스트함
- IP 범위(IPv4, IPv6)를 범주에 매핑하는 복잡한 "lexical decimal" 자료구조 요청이 최악의 쿼리임
- 범위 조회를 미리 계산해 SQLite 테이블과 Redis 정렬 집합에 씀
- 들어오는 HTTP 요청마다 요청 IP를 허용/차단 사용자 지정 범위, GeoIP 범위, IP 평판 범위와 비교해야 함
테스트 프로토콜
- M2 맥북 에어에서 홈브루로 설치한 Redis와 로컬 SQLite DB로 테스트함
- 기존 범위 데이터 세트(120만 개 항목)에 대해 테스트함
- 여러 IP 세트를 SQLite와 Redis에 동일한 순서로 실행함
- 각 배수마다 테스트를 5번 실행하고 평균을 냄
테스트 결과
- SQLite가 Redis를 압도적으로 이김(우리의 특수한 사용 사례에서)
- SQLite는 로컬 Redis 인스턴스보다 약 3배 빨랐음
- 네트워크 지연을 고려하기 전의 결과임
- SQLite가 Redis와 동등하기만 해도 네트워크 시간을 없앨 수 있어 이득임
차트에서 빠진 것
- SQLite 성능이 벤치마크에서 2배 나빠도 실제로는 네트워크 지연 때문에 더 빠를 수 있음
- Redis 서버를 아무리 강력하게 해도 네트워크 대역폭, 연결 등의 한계가 있고 지역 간 지연이 있음
- SQLite는 "무료로" 거의 무한한 수평 확장이 가능함
- SQLite로 온보딩이 훨씬 좋아짐. 사용자는 사용되는지도 모를 것임
- Redis에서 더 많은 성능을 뽑아낼 수 있지만, 사용자가 Redis 설정을 변경하도록 설득하기 어려웠음
결과는 시작에 불과함
- SQLite가 Redis보다 빠르다고 입증했지만 실제 절충이 있음
- 위 테스트에서는 쓰기를 고려하지 않음
- 읽기와 쓰기가 경쟁하는 것을 관리하기 위해 DB에 연결, 연결 풀, 트랜잭션 등이 필요함
- 마치 전기 슈퍼카가 콘크리트 블록을 싣고 가기 어려운 것처럼, SQLite를 적합하지 않은 역할에 쓰면 안 됨
동기화 아키텍처 구축
- v1(Redis)에서는 사용자가 Wafris Hub에서 규칙을 업데이트하면 Redis 데이터 저장소의 규칙이 업데이트됨
- SQLite에서는 웹 서버로 "푸시"할 수 없으므로 작동하지 않음
- v2(SQLite)에서는 1) 사용자가 Wafris Hub에서 규칙 업데이트 2) 일정 간격으로 클라이언트가 업데이트된 규칙 확인 3) 규칙이 업데이트되면 완전히 새로운 SQLite DB 다운로드
- 이는 사용자의 설치 및 구성 책임을 크게 줄여줌
- v2 클라이언트의 설치 성공률이 3배 증가함
SQLite 분산 아키텍처
- 자동 확장이 활성화된 클라우드 제공업체에 배포된 Rails 앱을 고려해보자
- 요청이 100req/s에서 10,000req/s로 증가하면 컴퓨팅 인스턴스는 확장되지만 DB는 그렇지 않음
- 실제로 Rails 앱이 과부하로 인해 중단되는 주된 이유임
- SQLite DB를 각 컴퓨팅 인스턴스에 동기화하면 모든 호출을 로컬로 유지할 수 있어 이 문제를 해결함
쓰기는 어떻게 하나?
- 앱을 읽기(규칙 평가)와 쓰기(보고) 경로로 분할한 다음 쓰기 경로는 무시했음
- 쓰기 경로는 1) Wafris Hub에 비동기로 연결해 보고 2) 보고서 일괄 전송 3) 클라이언트에서 DB 쓰기 완전 제거로 재설계함
- 다른 사람들에게는 작동하지 않겠지만, 우리는 배포가 쉽고 빠른 Wafris 클라이언트를 원하는 사용자만 신경 씀
결론
- SQLite를 사용하는 v2 아키텍처에 매우 만족함
- 이미 많은 사이트가 공격을 견디고 온라인 상태를 유지하는 데 도움이 됨
- 시작하기가 훨씬 쉬워져 우리의 지원 작업과 사용자의 번거로움이 줄어듦
- 이는 더 안전하고 보안성 높은 인터넷을 위한 승리라고 생각함