최근 몇 년간 서버리스 아키텍처와 엣지 컴퓨팅은 애플리케이션 배포 방식으로 큰 인기를 얻고 있습니다. 하지만 애플리케이션의 상태(state)와 데이터를 서버리스 또는 엣지 함수 내부에 저장하는 문제는 전혀 다른 이야기입니다. 데이터베이스 연결 관리, 여러 위치에서 데이터를 빠르게 조회할 수 있도록 하는 것 등 해결해야 할 난제가 많습니다. 실제로 서버리스 접근을 지원하는 데이터베이스 서비스는 소수에 불과하며, 그중에서도 엣지 함수에 적합한 서비스는 더욱 드뭅니다.
Upstash는 서비스 출시 첫날부터 요청당 과금(per-request pricing) 모델과 낮은 지연 시간을 제공하는 서버리스 Redis 호환 데이터베이스를 선보여 왔습니다. 여기에 더해 데이터베이스 위에 직접 구축된 일급(first-class) REST API도 함께 제공합니다. REST API는 특히 서버리스 함수 환경에서 연결 관리의 번거로움을 없애주며, 엣지 로케이션이나 웹 브라우저처럼 제약이 많은 환경에서도 데이터베이스에 접근할 수 있게 해줍니다.
오늘 저희는 글로벌 데이터베이스(Global Database)를 발표하게 되어 기쁩니다. 이는 데이터베이스를 전 세계 곳곳에, 클라이언트와 엣지 로케이션 가까이 배치하여 낮은 지연 시간의 읽기를 제공하기 위한 한 걸음 더 나아진 조치입니다. 글로벌 데이터베이스는 무료 티어에서 이용할 수 있으므로 비용 부담 없이 바로 사용해 보실 수 있습니다.
언제 사용해야 할까?
글로벌 데이터베이스는 서로 다른 대륙의 여러 리전에 배포되며, 사용자가 전 세계에 분산되어 있는 경우 클라이언트 요청을 가장 가까운 리전으로 라우팅해 지연 시간을 최소화합니다. Upstash 글로벌 데이터베이스는 다음과 같은 용도로 활용할 수 있습니다.
엣지 함수(Cloudflare Workers, Fastly Compute): 내장 REST API와 모든 엣지 로케이션에서의 낮은 지연 시간 접근 덕분에 완벽한 솔루션입니다.
멀티 리전 서버리스 배포: AWS Lambda, Vercel, Netlify 함수는 여러 리전에 배포할 수 있습니다. 글로벌 데이터베이스는 서버리스 함수가 어디에 있든 낮은 지연 시간의 데이터 접근을 보장합니다.
웹/모바일 플랫폼: 읽기 전용 REST API를 활용하면 웹/모바일 애플리케이션에서 Redis 데이터베이스에 직접 접근할 수 있습니다. 사용자가 어디에 있든 낮은 지연 시간을 기대할 수 있습니다.
글로벌 데이터베이스의 또 다른 목표는 리전 단위 장애에 대한 복원력 확보입니다. 특정 리전을 사용할 수 없게 되면 요청이 가장 가까운 가용 리전으로 자동 라우팅되므로, 데이터베이스는 계속 사용 가능한 상태를 유지합니다.
작동 방식
글로벌 데이터베이스 모델에서는 동일한 데이터베이스의 여러 복제본(replica)이 하나의 클러스터를 구성합니다. 각 복제본은 클러스터의 다른 멤버들과 연결되어 있으며, 장애 감지기(failure detector)를 사용해 서로의 생존 여부를 추적합니다. 클러스터 멤버십과 장애 감지는 모두 고시프(gossip) 기반 통신 프로토콜로 관리됩니다(참고: SWIM — Scalable Weakly-consistent Infection-style Process Group Membership Protocol).
데이터 복제(정확히는 개별 쓰기/업데이트/삭제 연산)에는 단일 리더(single leader) 복제 모델이 사용됩니다. 일련의 키 그룹이 리더 복제본에 할당되며, 리더는 멤버십 변경 이후 리더 선출 메커니즘을 통해 선출됩니다. 나머지 복제본은 해당 키 그룹에 대한 리더의 백업 역할을 수행합니다. 장애 감지기가 리더 복제본의 장애를 감지하면 나머지 복제본들이 새로운 리더 선출 라운드를 시작해 새 리더를 뽑습니다. 선출 과정 동안 데이터베이스는 짧은 시간 동안 사용 불가 상태가 되며, 선출이 완료될 때까지 모든 요청이 차단됩니다.
쓰기 요청은 오직 리더 복제본만 수신하고 처리합니다. 백업 복제본은 클라이언트에게 알리지 않은 채 내부적으로 쓰기를 리더에게 전달합니다. 따라서 클라이언트는 리더든 백업이든 복제본 유형에 관계없이 쓰기 요청을 보낼 수 있습니다. 쓰기 요청을 처리한 후 리더 복제본은 해당 내용을 백업 복제본들에게 전파합니다.
일관성에 대해 조금 더
현재 글로벌 데이터베이스는 약한 일관성(weak consistency)을 제공하며, 아직 강한 일관성(strong consistency)은 지원하지 않습니다. 쓰기 요청의 응답은 리더 복제본이 연산을 처리한 직후 클라이언트에게 반환되며, 백업 복제본 과반수의 ACK를 기다리지 않습니다. 쓰기 결과는 백업 복제본들에 비동기적이고 병렬적으로 복제됩니다.
읽기 요청은 어떤 복제본에서든 처리되므로 읽기 확장성이 뛰어나지만, 같은 키에 대한 쓰기 결과가 백업 복제본에 도달하기 전까지는 읽기 요청이 오래된(stale) 값을 반환할 수 있다는 의미이기도 합니다.
충돌이 없다면 훨씬 쉬울 텐데요!
네트워크 파티션(network partition)과 같은 클러스터 전체 장애가 발생하면 동일한 키에 대해 여러 리더가 선출될 수 있습니다. 즉, 여러 복제본이 쓰기를 수용하게 되고 복제본 간에 데이터가 갈라질 수 있습니다. Calvin이나 Spanner(또는 Paxos, Raft 같은 프로토콜)를 활용한 더 강력한 모델로 처음부터 충돌 자체를 방지하는 것도 가능하지만, 그것은 당분간 저희가 선택하지 않을 다른 길입니다.
대신 글로벌 데이터베이스는 충돌이 발생하도록 허용하고, LWW(Last-Write-Wins, 마지막 쓰기 우선) 알고리즘으로 이를 해결해 복제본들이 결국 동일한 상태로 수렴하도록 합니다. Upstash 데이터베이스의 모든 쓰기에는 고유하고 단조 증가하는(monotonic) 시퀀스 번호가 부여됩니다. 복제본이 리더가 될 때마다 자신이 처리하는 첫 번째 쓰기의 시퀀스를 기록하며, 두 리더 복제본이 서로를 발견하면 리더가 된 이후의 쓰기 내역을 공유하고 충돌을 해소합니다.
모든 복제본이 동등하지는 않습니다
어떤 복제본은 다른 복제본보다 더 '동등'합니다. 그중 일부만 리더로 선출될 수 있기 때문입니다. 글로벌 데이터베이스 클러스터에서 복제본은 러너(learner)로 표시될 수 있으며, 이 경우 리더 선출에 부적합하게 됩니다. 러너 복제본은 항상 읽기 전용으로 유지되며 리더 후보가 될 수 없습니다.
러너 복제본을 추가하는 것은 클러스터 안정성에 영향을 주지 않으며, 분리(split)되더라도 쓰기 충돌을 일으키지 못합니다. 리더와 분리된 상태에서도 읽기 요청은 계속 처리하고, 분리가 해소되면 남은 쓰기를 동기화합니다. 따라서 러너 복제본은 더 많은 엣지 로케이션으로 확장하는 데 매우 유용한 도구입니다.
더 많은 리전, 더 많은 복제본?
초기 릴리스에서는 5개 리전에 5개의 복제본을 갖춘 글로벌 데이터베이스를 제공합니다. 미국과 EU 외부의 복제본은 러너로 표시되므로, 미국 및 EU 리전의 복제본만 리더로 선출될 수 있습니다. 이를 통해 네트워크 파티션 중 쓰기 충돌 가능성을 줄이는 동시에, 네트워크 파티션을 걱정하지 않고 러너 복제본 형태로 더 많은 리전을 추가할 수 있습니다.
아직 갈 길이 멉니다
현재 글로벌 데이터베이스는 읽기 연산의 지연 시간을 최적화하고 최소화하도록 설계되었습니다. 따라서 쓰기가 많은 워크로드에는 적합하지 않습니다. 저희는 더 나은 설계를 통해 쓰기 연산의 지연 시간도 개선하기 위해 노력하고 있습니다.
또한 초기 5개 리전 구성 외에도, 사용자들의 수요와 피드백에 따라 향후 더 많은 리전이나 서로 다른 리전 그룹을 개방하는 것을 검토하고 있습니다.
생각이나 아이디어를 공유하고 궁금한 점을 문의하려면 Twitter와 Discord를 통해 저희에게 연락해 주세요.