TECH 으로 돌아가기
TECH HACKER NEWS 오늘 8분 읽기 32 READS

쓰던 DB 위에 올리는 오픈소스 Durable Objects, '솔리드 오브젝트'

쓰던 DB 위에 올리는 오픈소스 Durable Objects, '솔리드 오브젝트'
SOURCE IMAGE · HACKER NEWS

클라우드플레어가 대중화한 Durable Objects는 분산 상태 관리의 골칫거리를 우아하게 정리한 모델이다. 하나의 객체가 하나의 정체성을 갖고, 요청이 아무리 동시에 몰려도 한 번에 한 호출씩만 처리하며, 상태는 요청이 끝난 뒤에도 살아남는다. 문제는 이 모델을 쓰려면 대개 특정 벤더의 런타임에 상태와 청구서, 그리고 떠날 수 있는 자유까지 함께 넘겨야 했다는 점이다. 새로 공개된 오픈소스 프로젝트 '솔리드 오브젝트(Solid Objects)'는 같은 모델을 라이브러리 형태로 제공한다. 별도의 데몬이나 브로커, 외부 계정 없이 이미 운영 중인 SQLite·PostgreSQL·MySQL 안에서 각 객체가 정체성과 지속되는 상태, 그리고 순서가 보장되는 메일박스를 갖도록 만드는 방식이다.

스윕 코드가 쌓이던 자리

프로젝트의 출발점은 흔한 운영 부채였다. 개발자가 유지하던 한 애플리케이션은 5분마다 크론 잡을 돌려 모든 활성 계정을 불러온 뒤 종료 예정인 계정이 있는지 확인했다. 일주일이면 2,014번 실행에 대기열 시간만 37분이 쌓였는데, 그렇게 걸러낸 계정은 고작 8개였다. 같은 앱의 다른 기능인 예약 발행은 대상마다 키-값 저장소에 키 하나씩을 만들어두고, 30분마다 전체를 훑으며 정규식으로 키를 파싱해 대상을 복원했다. 그러고도 최대 30분씩 늦어져서, 컨트롤러는 그 공백을 메우려 지연된 복제 잡을 하나 더 큐에 넣었다. 각 조각이 앞선 조각의 결함을 덮으려 덧붙여진 구조였다.

저자는 이를 자신만의 실수로 여기다가, 에어비앤비 창업자 브라이언 체스키가 '호스트가 예약을 취소하면 어떻게 되나'라는 글타래에 남긴 답을 읽고 생각을 바꿨다. 실제로는 취소가 아니었다는 것이다. "호스트가 취소한 게 아니라, 우리가 이중 예약을 했다." 예약이 곧 제품의 전부인 회사조차, 그가 임시방편으로 떠받치고 있던 바로 그 버그를 출시한 셈이었다.

여섯 조각을 객체 하나로

선택지는 둘로 좁혀졌지만 둘 다 마뜩잖았다. 올바른 모델인 클라우드플레어 Durable Objects로 옮겨 벤더 종속을 떠안거나, 비효율적인 전수 스윕을 계속 유지하거나. 세 번째 길은 이미 눈앞에 있었다. 마감 시각이 데이터베이스에 들어 있으니 스케줄도 그곳으로 옮기면 됐다. 각 엔티티가 자기 설정이 바뀔 때 리마인더 하나를 걸어두고 정해진 시각에 스스로 깨어나게 한 것이다. 저자는 기존 스윕을 나란히 돌려 운영 환경에서 두 결과가 일치하는 것을 확인한 뒤에야 크론 항목을 지웠다.

공교롭게도 이 첫 버전을 내놓은 이틀 뒤, 쇼피파이가 재고 예약을 두고 같은 아키텍처를 공개했다. 레디스에 있던 재고 예약을 MySQL로 옮겨 단위당 한 행으로 두고 SKIP LOCKED로 점유하는 방식이었다. 서로 다른 자리에서 같은 결론에 도달한 셈으로, 이것이 오래된 문제의 재발견이 아니라 지금 실무자들에게 닥치고 있는 문제라는 방증이다. 이런 일을 손으로 처리하려면 행 잠금에서 시작해, 잠금이 두 요청에 걸치지 못하니 레디스 잠금을 더하고, 만료를 위한 지연 잡과 expires_at 컬럼, 그 잡이 프로세스와 함께 죽는 걸 발견한 뒤 붙이는 스위퍼, 재시도 코드, 그리고 앞선 쓰기와 어긋날 수 있는 브로드캐스트까지 필요하다. 서로 어긋나선 안 되는 여섯 조각을 솔리드 오브젝트는 객체 하나로 대체한다.

어떻게 동작하나

동작 원리는 이렇다. 두 요청이 서로 다른 프로세스에서 동시에 reserve를 호출해도 둘 다 event-42의 메일박스로 들어가고, 런타임이 한 번에 한 턴씩 커밋하므로 수량이 0 밑으로 내려가지 않는다. 10분 뒤 해제는 행으로 저장되므로 배포가 있어도 예정대로 발화한다. 각 정체성은 순서가 보장된 메일박스와 펜싱된 리스를 갖고, 리스를 획득한 한 프로세스만 턴을 실행한 뒤 상태·리마인더·효과·브로드캐스트를 함께 커밋한다. 리스를 잃은 워커는 커밋할 수 없다. 레디스는 선택 사항이며 깨어나는 지연을 줄여줄 뿐이다.

성능 수치는 정직하게 제시돼 있다. 노트북에서 SQLite로 측정했을 때 한 프로세스 안에서 큐 적재부터 커밋 완료까지 걸리는 지속 호출은 p50 기준 약 2.6ms다. 다만 두 프로세스에 걸쳐 폴링만 쓰면 약 1초를 기다리는데, PostgreSQL 알림과 선택적 레디스 웨이크업이 존재하는 이유가 여기 있다. 저자는 이 값들이 개발자 노트북에서 나온 것이며 애플리케이션의 실제 처리 용량을 예측하지 못한다고 분명히 못 박는다. 설치는 Solid Queue나 Solid Cache처럼 데이터베이스에 테이블을 추가하는 방식이고, 화면 갱신은 레일스에서는 반응형 ERB와 터보 스트림으로, 노드에서는 커밋된 프로젝션으로 이뤄진다. SQLite WASM 기반 브라우저 런타임은 오프라인 쓰기를 서버로 재생해 준다.

아직 1.0 이전이라는 점

한계도 명확하다. 엣지 배치나 지역 간 라우팅은 없고, 두 정체성에 걸치는 트랜잭션도 지원하지 않는다. 전달은 순서가 보장되지만 '최소 한 번'이라 외부 효과는 반드시 멱등하게 설계해야 하며, 정확히 한 번(exactly-once) 전달은 제공되지 않는다. 무엇보다 아직 1.0 이전 버전이다. 현재 10만 명 이상 사용자를 둔 한 애플리케이션에서 운영 중이고 shuffleupandplay.com에 데모가 있지만, 제3자의 프로덕션 도입 사례는 아직 없다. 프로젝트는 재현 가능한 벤치마크와 PostgreSQL·MySQL에서의 추가 안정화 기간을 거쳐 1.0으로 가겠다는 로드맵을 내걸고 있다. 실무자 입장에서 판단 기준은 저자 본인이 제시한 한 문장으로 요약된다. 지켜야 할 불변식이 요청 하나 안에 온전히 들어간다면 그냥 트랜잭션을 쓰면 되고, 상태가 요청의 경계를 넘어 살아남으며 동시성까지 다스려야 할 때 이런 모델이 값을 한다는 것이다.

SOURCE · HACKER NEWS
원문 전체 보기 → https://solidobjects.dev/blog/introducing-solidobjects
SHARE
NEXT · CHOOSE

변화를 읽었다면,
내가 만들 수익 구조를 고릅니다.

정보를 더 모으는 데서 멈추지 않고, 광고·외주·판매·중개·구독 중 내 상황에 맞는 출발점을 정해보세요.

21가지 수익 구조 살펴보기
처리 중...