pogovet v2

slug 중심 구조로 재구성한 차세대 문서 피드

← 홈으로
Article2026-03-22·Seth Raphael

Site Up. Costs Down: Optimizing OpenClaw''s 1M Weekly Active Users

ClawHub가 주간 100만 사용자 규모로 커지며 발생한 읽기·대역폭 폭주를 Convex 데이터 모델 최적화로 크게 줄인 운영 사례다.

원문/원본: https://stack.convex.dev/optimizing-openclaw기존 공개 버전: pogovet.com
Site Up. Costs Down: Optimizing OpenClaw''s 1M Weekly Active Users

📰 Site Up. Costs Down: Optimizing OpenClaw’s 1M Weekly Active Users

🖼️ 기사 썸네일

🧩 4컷 인포그래픽

💡 한 줄 요약

ClawHub가 주간 100만 사용자 규모로 커지며 발생한 읽기·대역폭 폭주를 Convex 데이터 모델 최적화로 크게 줄인 운영 사례다.

📌 핵심 요약

  • ClawHub는 빠른 출시와 성장에는 성공했지만, 공개 카탈로그 페이지의 데이터 읽기 구조 때문에 트래픽과 비용이 급격히 커졌다.
  • 핵심 문제는 실시간 반응형 구독, 대용량 조인, 넓은 읽기 집합이 겹치며 불필요한 재실행과 대역폭 낭비를 만든 점이다.
  • 해결책은 실시간이 필요 없는 화면을 query() 기반 스냅샷 조회로 바꾸고, 다이제스트 테이블과 비정규화로 핫패스 읽기 구조를 단순화하는 것이었다.
  • 여기에 복합 인덱스 추가와 compare-before-write 같은 패턴을 적용해 트리거성 무효화와 불필요한 쓰기까지 줄였다.
  • 그 결과 하루 약 9TB 수준의 데이터 트래픽을 약 600GB 수준으로 낮추며, 다운타임 없이 운영 최적화를 진행했다.

🧩 주요 포인트

  1. 빠른 제품 출시와 실제 성장 확인은 가능했지만, 초기 데이터 모델은 100만 사용자 규모를 전제로 설계된 구조가 아니었다.
  2. 공개 브라우징 화면에서는 실시간 구독보다 '작고 안정적인 읽기 집합'이 비용과 성능에 더 중요했다.
  3. 핫패스 최적화의 핵심은 조인을 줄이고, 다이제스트 테이블과 비정규화로 UI에 필요한 최소 문서만 읽게 만드는 것이다.
  4. compare-before-write는 값이 바뀌지 않은 쓰기를 막아 대량 무효화와 연쇄 재실행을 줄이는 데 특히 효과적이었다.
  5. 이 글은 완벽한 선설계보다, 실제 트래픽 이후 병목을 측정하고 구조를 반복적으로 고치는 운영 최적화가 더 현실적임을 보여준다.

🧠 상세 정리

1. 이 글의 출발점: ‘서비스는 살아 있었지만, 너무 비싸게 살아 있었다’

이 사례의 흥미로운 점은 서비스가 먼저 망가지지 않았다는 데 있다. ClawHub는 주말 동안 바이브 코딩에 가깝게 빠르게 만들어졌고, 예상보다 훨씬 큰 트래픽을 받았지만 실제로는 계속 동작했다. Convex가 비효율적인 쿼리 패턴도 어느 정도 흡수해 줬기 때문에 사용자 입장에서는 큰 장애가 없었다.

하지만 운영 관점에서는 다른 문제가 드러났다. 서비스는 살아 있었지만, 너무 많은 바이트를 읽고 너무 많은 대역폭을 쓰고 있었다. 즉 ‘사이트가 안 죽는 것’과 ‘효율적으로 운영되는 것’은 전혀 다른 문제였고, ClawHub는 바로 그 차이를 극단적으로 보여주는 사례였다.

2. 바이브 코딩은 PMF를 빠르게 만들지만, 비용 구조까지 자동으로 최적화해 주지는 않는다

글은 바이브 코딩과 agentic engineering을 구분한다. 바이브 코딩은 일단 빠르게 만들어 보고 실제 수요를 확인하는 데 유리하다. ClawHub가 짧은 시간 안에 제품-시장 적합성에 도달할 수 있었던 것도 이 방식 덕분이다.

문제는 빠른 출시를 가능하게 했던 코드가 대규모 트래픽을 전제로 설계된 것은 아니라는 점이다. 사이트가 빨라 보이더라도 내부에서는 전체 테이블 스캔, 대용량 문서 읽기, 루프 안 조인, 과도한 구독 재실행이 조용히 누적될 수 있다. 이 글은 ‘잘 돌아가는 것처럼 보이는 서비스’가 실제로는 엄청난 낭비를 품고 있을 수 있다는 점을 보여준다.

3. 병목의 핵심은 공개 브라우징 화면에 실시간 모델을 과하게 적용한 데 있었다

가장 큰 비용 문제는 공개 카탈로그 페이지 같은 고트래픽 읽기 화면에서 발생했다. useQuery, usePaginatedQuery 같은 반응형 구독은 협업 기능이나 즉시 업데이트가 필요한 화면에서는 매우 강력하지만, 수백 명이 동시에 같은 목록을 보고 있고 사소한 변경이 계속 발생하는 공개 페이지에서는 오히려 비용 증폭 장치가 될 수 있다.

문제는 Convex의 실시간 모델이 쿼리가 읽은 모든 문서를 읽기 집합(read set)으로 잡고, 그중 하나라도 바뀌면 전체 쿼리를 다시 실행한다는 점이다. 다운로드 수 증가, 새 스킬 발행, 프로필 업데이트처럼 사소한 쓰기 하나가 페이지 전체 재실행을 유발할 수 있고, 스크래퍼나 다수 클라이언트가 각기 다른 페이지네이션 구독을 걸고 있으면 이 비용이 기하급수적으로 커진다.

4. 첫 번째 해법은 ‘실시간이 꼭 필요한 곳’과 ‘그렇지 않은 곳’을 분리하는 것이다

이 글이 주는 첫 번째 실전 교훈은 간단하다. 모든 화면을 실시간으로 만들지 말라는 것이다. 공개 검색 결과나 카탈로그 목록처럼 사용자가 페이지 로드 시점의 정합성만 확보되면 충분한 화면은, 오히려 실시간 재정렬이 UX에도 큰 이득이 없다.

그래서 해법은 반응형 subscription을 무조건 유지하는 대신, 필요 없는 곳은 convex.query() 기반 일회성 조회로 바꾸는 것이다. 백엔드 쿼리는 같아도 실행 방식이 바뀌면 불필요한 invalidation과 재실행이 줄어든다. 즉 이 글은 ‘실시간 여부’를 기술 취향이 아니라 접근 패턴 기준으로 다시 설계해야 한다고 말한다.

5. 두 번째 해법은 조인을 줄이고, 읽기 집합 자체를 더 작게 만드는 것이다

글이 반복해서 강조하는 또 다른 포인트는 ‘UI가 실제로 쓰는 것보다 훨씬 많은 데이터를 읽고 있었다’는 점이다. Convex는 필드 프로젝션 없이 전체 문서를 읽기 때문에, 목록 페이지에서 실제로 필요한 정보가 200바이트인데 3KB 문서를 통째로 읽으면 그 자체로 낭비가 된다. 여기에 루프 안 ctx.db.get() 조인까지 들어가면 페이지 하나를 렌더링하기 위해 읽는 데이터가 눈덩이처럼 커진다.

이를 해결하기 위해 글은 다이제스트 테이블과 비정규화를 권한다. 즉 자주 읽는 핫패스에 필요한 필드만 모아 놓은 경량 테이블을 따로 만들고, 조인해야 할 값도 미리 평평하게 펴서 넣는 방식이다. 이렇게 하면 UI는 가장 작은 문서 집합만 읽고 끝날 수 있다. ClawHub에서는 이 방식으로 페이지 읽기를 약 195KB에서 20KB 수준으로 줄였다고 설명한다.

6. 세 번째 해법은 ‘불필요한 쓰기’를 막아 무효화 연쇄를 차단하는 것이다

읽기 최적화 못지않게 중요한 것이 쓰기 최적화다. 특히 트리거나 크론, 백필 작업이 다이제스트 테이블을 계속 갱신하는 구조에서는, 값이 실제로 바뀌지 않았는데도 동일한 내용을 다시 쓰는 순간 모든 구독자가 다시 무효화될 수 있다. 이 비용은 단순히 ‘쓰기 1회’가 아니라 ‘쓰기 × 구독자 수 × 구독자당 읽기 문서 수’로 증폭된다.

그래서 compare-before-write가 매우 중요해진다. 기존 값과 새 값을 비교해 달라진 것이 없으면 아예 쓰지 않는 것이다. 글에서는 이 단순한 방어 로직이 ClawHub에서 가장 영향력이 큰 수정이었다고 설명한다. 실제 변경이 없는 크론 업데이트가 거대한 invalidation 폭발을 만들고 있었고, 이를 막자 대역폭 스파이크가 급격히 평탄화됐다.

7. 인덱스와 백필 전략도 ‘읽고 버리는 비용’을 줄이는 방향으로 설계해야 한다

이 글은 필터를 애플리케이션 코드에서 처리하는 방식도 강하게 비판한다. 예를 들어 의심스러운 스킬을 화면에서 숨기기 위해 모든 문서를 읽고 나서 JS에서 걸러내면, 이미 읽기 비용은 다 지불한 뒤다. 그래서 복합 인덱스를 만들어 데이터베이스가 애초에 필요 없는 문서를 건너뛰게 해야 한다.

또한 새 다이제스트 테이블을 백필할 때도 한 번에 몰아치면 활성 구독자를 계속 무효화한다. 그래서 배치 간 지연을 두고, 중지 플래그를 넣고, 큰 mutation은 Action → Query → Mutation으로 쪼개는 방식이 필요하다. 즉 최적화는 단순히 쿼리문 하나 고치는 작업이 아니라, 읽기·쓰기·백필·운영 루프 전부를 재설계하는 과정이라는 점이 드러난다.

8. 이 사례의 진짜 메시지: ‘처음부터 완벽하게’보다 ‘성공 후 병목을 계측하고 줄이는 능력’이 중요하다

마지막 메시지는 매우 현실적이다. 처음부터 모든 기능과 모든 데이터 모델 상호작용을 완벽하게 예측하는 것은 사실상 불가능하다. 그런 식의 선설계는 오히려 출시 속도를 늦추고 PMF 탐색을 방해할 수 있다. 그래서 Convex가 제공하는 안전망 위에서 먼저 빠르게 출시하고, 이후 실제 트래픽이 붙었을 때 병목을 계측하고 가장 비싼 경로부터 줄여나가는 방식이 더 현실적이라는 것이다.

글은 npx convex insights --prod로 가장 비싼 쿼리를 찾고, 원인을 읽고, 데이터 모델을 고치고, 무중단 배포하고, 다시 대시보드를 보는 루프를 반복하라고 제안한다. ClawHub는 이 과정을 4일 동안 8번 반복하며 9TB/day 수준의 트래픽을 약 600GB/day 수준까지 줄였다. 결국 이 글의 핵심은 ‘빠르게 만드는 능력’과 ‘나중에 싸게 고치는 능력’이 함께 있어야 진짜 스케일이 가능하다는 데 있다.

🧾 핵심 주장 / 시사점

  • 대규모 서비스에서는 '무엇을 더 계산하느냐'보다 '무엇을 덜 읽게 하느냐'가 비용 절감의 핵심이 될 수 있다.
  • 실시간성이 필요 없는 화면까지 반응형 구독으로 설계하면, 사용자 증가와 함께 비용 구조가 급격히 악화될 수 있다.
  • 성장 이후의 최적화는 실패가 아니라 정상적인 운영 단계이며, 병목을 측정하고 핫패스를 재설계하는 능력이 중요하다.

✅ 액션 아이템

  • 현재 서비스에서 실시간 구독이 꼭 필요한 화면과 스냅샷 조회로 충분한 화면을 분리해, 불필요한 반응형 읽기 경로를 줄인다.
  • 가장 비싼 읽기 경로를 기준으로 조인 수, 읽기 집합 크기, 비정규화 가능성, 인덱스 상태를 점검하는 핫패스 리뷰를 수행한다.
  • 쓰기 경로에 compare-before-write 같은 방어 패턴을 도입해, 값이 바뀌지 않은 업데이트로 인한 무효화·재실행 비용을 줄일 수 있는지 검토한다.

❓ 열린 질문

  • 우리 서비스에서 가장 먼저 비용을 터뜨릴 가능성이 큰 화면은 '쓰기 많은 화면'일까, 아니면 '대규모 공개 조회 화면'일까?
  • 데이터 모델을 더 정규화하는 것이 장기적으로 유리할까, 아니면 핫패스만큼은 과감히 비정규화하는 편이 더 현실적일까?
  • 빠른 출시 이후 병목을 반복적으로 고치는 방식이 맞다면, 우리는 어떤 지표를 봐야 가장 먼저 구조 변경 결정을 내릴 수 있을까?

🌐 원문 링크

https://stack.convex.dev/optimizing-openclaw

태그

연관 글