이 글은 2005년에 "인터넷의 첫 페이지"라는 비전을 가지고 창립된 Reddit 이 시간이 지나고 사용자 수가 폭발적으로 증가함에 따라 Reddit 아키텍처가 어떻게 변경되었는지 설명하는 글입니다.

 

The Early Days of Reddit (Lisp -> Python 으로 전환)

Reddit은 처음에 Lisp 라는 프로그래밍 언어로 작성되었다고 합니다.

 

Lisp 는 코드를 마치 데이터처럼 취급하여 자유롭게 변경할 수 있고, 대화식으로 코드를 바로바로 테스트하면서 빠르게 작업할 수 있다는 장점이 있습니다.

 

하지만 Lisp 는 Reddit을 개발할 때 사용할 수 있는 라이브러리나 도구가 부족한 문제를 가지고 있어서 플랫폼을 구축하는데 큰 어려움이 있었기 때문에 Reddit 은 Lisp 에서 Python 으로 전환했습니다.

 

 

The Core Components of Reddit’s Architecture

Reddit 의 high-level architecture 는 다음과 같습니다:

  • Content Delivery Network: Reddit 은 콘텐츠 전송 지연을 줄이고 핵심 서버로의 트래픽을 분산시키기 위해 CDN 을 적극적으로 사용합니다. 
  • Frontend Application: 처음에는 jQuery 를 사용해서 프론트엔드 개발을 했다면 이후에는 Typescript 와 Node.js 를 도입해서 확장 가능하고 유지 보수하기 쉽도록 기술 스택을 채택하였습니다.
  • R2 Monolith: Reddit의 아키텍처에는 Python을 사용하여 구축된 큰 단일 애플리케이션인 R2 모놀리스가 있습니다. 이 애플리케이션은 검색과 같은 주요 기능과 함께 Things(게시물, 댓글 등) 및 Listings(게시물 모음)와 같은 다양한 기능들을 포함하고 있습니다.
  • 인프라 변경: 물리 서버를 폐기하고 전체 웹사이트를 AWS EC2로 모두 이전했습니다.

 

 

R2 Deep Dive

이전에 살펴본 R2 모놀리식은 다음과 같은 구조로 이뤄져있습니다:

  • 확장성 보장: 사용자 수나 요청 수가 급증하는 상황에서 서버 부하를 분산시키기 위해 R2 서버는 여러 서버에 배포되어 실행됩니다.
  • 로드 밸런서: 로드 밸런서는 앞단에 위치하며 들어오는 요청을 서버들로 라우팅하는 역할을 수행합니다.
  • 비동기 작업 큐: 비용이 많이 들어가는 연산들 중에 비동기로 처리가능한 것들은 RabbitMQ 를 통해 메시지 큐로 보내지고, Job Processor 가 이를 처리합니다.
  • 데이터베이스와 캐시: PostgreSQL 을 핵심 데이터베이스로 사용하며, 읽기 부하를 분산시키기 위해 PostgreSQL 앞에 Memcached 클러스터를 배치합니다. 그리고 가용성(availability) 와 회복성(resiliency) 이 좋다는 특징 때문에 Cassandra 데이터베이스를 추가로 이용합니다. 
    • (Cassandra 는 한대의 노드만 작동하더라도 서비스를 제공할 수는 있으니까, 이를 채택한 것 같습니다.)

 

 

The Expansion Phase

이제부터는 Reddit의 사용자 수가 급격하게 증가함에 따라 Reddit 의 디자인과 아키텍처가 어떻게 변경되었는지 살펴보겠습니다.

 

 

GraphQL Federation with Golang Microservices

2017년부터 Reddit은 GraphQL을 채택하기 시작했습니다.

  • (GraphQL 을 채택한 이유는 Reddit 이 Android, ios, Web 등 다양한 클라이언트 환경에서 데이터를 각자의 요구사항에 맞게 가져오기 쉽기 때문인 것 같습니다. 아무래도 GraphQL 을 쓰면 클라이언트마다 필요한 데이터만을 요청할 수 있기 때문인 것 같은데요. 예로 모바일 클라이언트는 네트워크 대역폭이 제한적일 수 있기 때문에, 최소한의 데이터만을 요청하는 식으로 사용하기 쉽습니다.)

 

그리고 2021년 초, Reddit은 GraphQL Federation 을 다음의 이유로 도입했습니다:

  • Retiring the monolith (모놀리식 어플리케이션 퇴출)
  • Improving concurrency (동시성 향상)
  • Encouraging separation of concerns (관심사 분리 촉진)

 

GraphQL Federation은 여러 작은 GraphQL API(서브 그래프라고도 함)를 하나의 큰 GraphQL API(슈퍼 그래프라고 함)로 결합하는 방법입니다.

  • (이건 Microservice 의 API Aggregation 과 유사한 개념인데요. 마이크로서비스에서 API Aggregation 은 여러 서비스의 API 를 하나의 API로 통합합니다. 이렇게 함으로써 클라이언트는 여러 서비스에 대해서 개별적으로 API 요청할 필요 없이, 하나의 집계된 API 를 통해 필요한 데이터를 취합해서 요청할 수 있게 합니다. GraphQL Federation 도 이와 유사하게 여러 서브그래프의 데이터를 슈퍼그래프 하나로 요청할 수 있게 합니다.)

 

2022년부터 Reddit 은 핵심 기능인 Subreddits 과 Comments 에 대한 서브 그래프를 제공하기 시작하는데요. 기존의 방식에서 안전하게 Graph Federation 으로 이관하기 위해서 점진적인 마이그레이션 작업을 합니다.

  • (Subreddits 는 특정 주제나 관심사에 중심을 둔 독립된 커뮤니티 섹션입니다. 디시인사이드로 치면 갤러리와 같은 거라고 아시면 됩니다.)

 

점진적인 마이그레이션을 위해서는 새로 구축한 서브 그래프의 에러율과 대기 시간을 평가하고, 문제가 발생할 경우 모놀리식으로 트래픽을 전환할 수 있는 능력을 필요로 했습니다.

 

그러나 Graph Federation 은 이러한 기능을 제공하지는 않기 때문에 앞단에 로드 밸런서를 두고 Blue/Green 배포를 이용해 모놀리식 서버와 서브 그래프 서버 둘을 배포한 상태에서 트래픽을 조절하는 식으로 마이그레이션을 진행했습니다.

 

 

Data Replication with CDC and Debezium

Reddit은 초기에 WAL(write-ahead log) 세그먼트를 사용하여 데이터베이스 복제를 사용했습니다.

  • (이 방식은 WAL 파일을 지속적으로 AWS S3 에 아카이빙하고, 그 날 밤에 복제 서버에 마스터 서버의 변경 사항을 따라 잡는 매커니즘입니다.)

이 방법은 문제가 발생할 포인트 (e.g S3 백업 실패 등) 가 많았기 떄문에 신뢰성 있는 복제를 위해 Debezium과 Kafka Connect 를 사용하는 Change Data Capture (CDC) 를 도입했습니다.

 

CDC 를 이용하는 방식은 PostgreSQL 에서 행이 추가, 삭제 또는 수정될 때마다 Debezium이 이러한 변경 사항을 감지하고 Kafka 토픽에 기록하고, 다운스트림 커넥터가 Kafka 토픽에서 읽고 대상 테이블에 변경 사항을 업데이트하는 방식입니다.

 

 

Managing Media Metadata at Scale

Reddit은 수십억 개의 게시물을 호스팅하며, 이 중 많은 수가 이미지, 비디오, GIF, 내장된 제3자 미디어 콘텐츠를 포함하고 있습니다.

사용자들이 미디어 콘텐츠를 점점 더 빠른 속도로 업로드함에 따라, 이러한 Media 메타 데이터들에 대한 검색 기능이 더 중요해졌습니다.

 

이런 상황에서 기존 방식의 아키텍처는 다음과 같은 문제점이 있었습니다:

  • 메타 데이터가 여러 시스템에 분산되어 있었고 일관성 없는 저장 형식과 다양한 쿼리 패턴이 존재했습니다
  • 메타데이터 정보를 위해 직접 S3 버킷을 쿼리하는 등의 비효율적인 방법이 사용되었습니다.
  • 메타 데이터의 변경 사항을 추적하는 매커니즘이 없었습니다.

 

Reddit 에서는 분산화된 미디어 메타 데이터를 새롭게 모을 수 있는 저장소가 필요하다고 판단했고, 요구사항으로는 다음과 같았습니다:

  • 확장성이 있어야 합니다.
  • 100K 읽기 요청/초의 규모에서 50ms 미만의 지연 시간으로 데이터 검색을 지원해야 합니다.
  • 메타 데이터 생성과 업데이트를 지원해야 합니다.

 

이를 위한 솔루션으로 PostgreSQL 과 Apache Cassandra 가 있었고, PostgreSQL 을 선택합니다:

  • PostgreSQL 은 SQL 문을 이용해서 다양한 칼럼으로 검색할 수 있는 반면에, Apache Cassandra 는 주로 파티션 키와 클러스터링 칼럼을 기준으로 검색을 해야한다는 단점이 있었습니다.
  • 또한 Apache Cassandra 는 빠른 읽기/쓰기 성능을 제공하긴 하지만 데이터 수정 비용과 일관성 보장이 더 PostgreSQL 보다 크긴 합니다. 기본적으로 Cassandra 는 데이터가 여러 노드에 분산되서 저장되기 떄문입니다.

 

 

기존 미디어 메타 데이터 저장소 또한 PostgreSQL 데이터베이스로 옮기기 위해서는 마이그레이션이 필요한데요. Reddit 에서는 마이그레이션 과정을 다음과 같이 작업했습니다:

  • 미디어 메타 데이터를 저장할 때 기존 저장소와 PostgreSQL 둘 모두에 Dual Write 를 합니다.
  • 이전 저장소에서 PostgreSQL 로 부족한 데이터만 backfill 로 채웁니다.
  • 클라이언트에서 미디어 메타 데이터를 읽을 때는 dual read 를 합니다.
  • 모든 Read Request 에 대해서 데이터 비교 작업을 수행한 후 데이터가 일치하지 않는다면 해당 이슈를 수정합니다.
  • 점점 많은 트래픽을 새 PostgreSQL 로 라우팅 합니다.

 

 

PostgreSQL 로 마이그레이션한 이후 이를 좀 더 확장성 있게 사용하기 위해서 Reddit 은 다음 두 가지 전략을 채택합니다:

  • 범위 기반 파티셔닝을 사용한 테이블 파티셔닝
    • (범위 기반 파티셔닝은 여기서 게시물 날짜 기반으로 파티셔닝을 적용한 것 같습니다. 아무래도 오래된 게시물보다 최근 게시물에 접근을 더 많이하고, 파티셔닝을 하지 않고 하나의 테이블로만 관리하게 된다면 테이블과 인덱스 데이터가 지나치게 커져서 쿼리 성능에 영향을 줄 수 있기 때문입니다.)
  • 비정규화된 JSONB 필드에서 읽기 제공
    • (목표로 한 읽기 성능을 제공하기 위해 PostgreSQL 에서 미디어 데이터를 비정규화하고 서빙한 것 같습니다.)

 

이렇게 적용한 이후 최종적으로 목표로 한 읽기 지연 시간을 달성했습니다: p50에서 2.6ms, p90에서 4.7ms, p99에서 17ms.

 

 

Just-in-time Image Optimization

Reddit은 매일 수십억 개의 이미지를 제공하며, 사용자들은 게시글, 댓글, 프로필 등에 이미지를 업로드합니다.

 

이를 다양한 종류의 기기에서 이미지를 볼 수 있도록 여러 해상도와 포맷으로 이미지를 제공해야 하기 때문에, Reddit은 포스트 미리보기, 썸네일 등 다양한 용도에 맞게 이미지를 변환해야합니다.

 

기존에는 외부 벤더를 통해 이미지 최적화 작업을 헀습니다. 아무래도 이미지 처리에 집중하기 보다는 Reddit 의 핵심 기술과 역량에 집중하기 위해서입니다.

 

하지만 사용자가 늘고 트래픽 또한 증가함에 따라 Reddit 은 이미지 처리 작업을 내부 in-house 기능으로 사용하기로 결정합니다.

 

이미지 최적화를 위한 서비스는 다음과 같이 두 가지 백엔드 컴포넌트가 있습니다:

  • Gif2Vid 서비스: GIF를 MP4로 실시간으로 크기 조정하고 변환합니다. GIF는 사용자들이 좋아하는 미디어 타입이긴 하지만, 파일 크기가 크다는 점과 높은 계산 자원 요구로 인해 애니메이션 데이터로는 비효율적입니다.
  • 이미지 최적화 서비스: govips(라이브러리 libvips를 위한 래퍼)를 사용하여 모든 다른 이미지 타입을 처리합니다. 이 서비스는 캐시 미스 트래픽의 대부분을 처리하며, 이미지 변형(흐림 효과, 자르기, 크기 조정, 이미지 겹치기, 포맷 변환 등)을 담당합니다.
    • (사용자가 원하는 형태의 이미지를 요청하는데 이게 없다면 캐시 미스가 발생하는 것 같습니다. 그리고 이때 Image Optimizer Service 가 원본 이미지를 가지고 원하는 형태의 이미지를 만들어서 CDN 에 등록하는 작업을 하는 것 같습니다.) 

Reddits 은 이렇게 이미지 최적화 프로세스를 통해서 기존 외부 벤더를 사용하는 비용을 0.9% 로 크게 줄였고, 전송하는 데이터 양도 20%로 감소시켰습니다.

 

 

Real-Time Protection for Users at Reddit’s Scale

Reddit은 수십억 명의 사용자가 이용하는 커뮤니티로 안전을 유지하기 위해 적절한 콘텐츠 관리가 필수적입니다.

 

기존 아키텍처인 Rule-Executor-V1 (REV1) 는 2016년에 개발된 시스템으로 사용자가 새로운 콘텐츠를 게시하거나 댓글을 다는 것과 같은 행동을 할 때 실시간으로 정책을 위반하는지 판단하고 위반한다면 차단하기 위한 규칙 기반 엔진입니다.

 

이 아키텍처는 Lua 스크립트로 규칙을 작성하면 특정한 이벤트가 발생했을 때 자동적으로 수행하는 식으로 작동합니다.

  • (Lua Script 를 사용한 이유는 여러가지가 있지만 C와 같은 언어로 작성된 호스트 프로그램 내에서 실행될 때 매우 빠르기 때문에 사용한 것으로 보입니다.)

예를 들면 다음과 같이 게시물에 유해한 본문 내용이 적혀져있다면 이를 차단하도록 하는 비동기 조취가 수행될겁니다.

 

하지만 Rule-Executor-V1 (REV1) 는 여러 단점들이 존재했기 때문에 개선이 필요했습니다:

  • 레거시 인프라(EC2 인스턴스)에서 실행되었으며, 이제는 삭제된 버전인 Python 2.7 을 사용했습니다.
    • (지금은 K8s 를 적극적으로 이용하고 있다고 합니다.)
  • 콘텐츠 위반을 판단하는 규칙들은 버전 관리되지 않고 있으며, 확장성 문제가 있었습니다. (Scale Out 이 안되는 구조였다고 합니다.)

 

그래서 이를 개선한 REV2 (Snooron) 를 만들었습니다:

  • 이는 Apache Flink 를 사용해서 대규모 데이터 스트림 처리를 할 수 있습니다.
  • 규칙의 구성과 관리가 코드를 통해 이루어지며, Github을 통한 버전 관리와 S3로의 백업을 포함합니다.

 

 

Reddit’s Feed Architecture

Reddit 의 피드 아키텍처 또한 시간이 지나면서 많이 개선되었습니다.

 

피드 아키텍처를 개선할 때 몇 가지 핵심 목표는 다음과 같습니다:

  • 다양한 팀이 피드와 연동하기 때문에, 이들이 피드를 빠르게 이해하고, 구축하며, 테스트할 수 있어야 합니다. 이를 위해 아키텍처는 높은 개발 속도를 지원하고 확장 가능해야 합니다.
  • 사용자 참여와 전반적인 Reddit 경험에 중요한 요소인 '상호작용까지의 시간(TTI)'과 스크롤 성능이 만족스러워야 합니다.
  • iOS, Android, 웹사이트 등 다양한 플랫폼에서 피드가 일관되게 제공되어야 합니다.

 

이러한 목표를 지원하기 위해 Reddit은 완전히 새로운 Server Driven 피드 플랫폼을 구축했습니다. 피드의 백엔드 아키텍처에 몇 가지 주요 변경이 이루어졌습니다.

  • (Server Driven 피드 플랫폼으로 만든 이유는 일관된 UI 를 제공하기 위함인 것 같습니다.) 

 

그리고 이전에는 각 게시물이 게시물이 가질 수 있는 모든 정보를 포함한 'Post' 객체로 표현되었는데요 

 

이건 모든 것을 한 번에 전송하는 것과 같아 시간이 지남에 따라 'Post' 객체가 상당히 커지는 문제가 발생했습니다.

 

이는 클라이언트에도 입장에서도 큰 부담인데요. 각 클라이언트 애플리케이션은 UI에 무엇을 표시해야 할지 결정하는 복잡한 로직을 포함하고 있었으며, 대부분의 경우 이 로직은 플랫폼 간에 동기화되지 않는 문제가 발생했습니다.

 

아키텍처의 변경으로, 큰 객체에서 벗어나 대신 클라이언트가 렌더링할 정확한 UI 요소의 설명만을 전송하게 되었습니다. 백엔드는 요소의 유형과 순서를 제어합니다. 이 접근 방식은 '서버 주도 UI(Server-Driven UI)'로도 알려져 있습니다.

 

아래 이미지 기준으로 가장 오른쪽이 서버 주도 UI 방식입니다. 

 

 

Reddit’s Move from Thrift to gRPC

초기에 Reddit은 서로 다른 프로그래밍 언어로 작성된 서비스들이 서로 통신할 수 있게 하는 공통 인터페이스(API)를 정의할 수 있는 Thrift를 도입했습니다.

 

Thrift는 언어 독립적인 인터페이스를 제공하고, 각각의 특정 언어에 대한 코드 바인딩을 생성합니다. 이를 통해 개발자들은 자신들의 프로그래밍 언어에 자연스러운 구문을 사용하여 API 호출을 할 수 있으며, 언어 간 통신의 세부 사항을 신경 쓸 필요가 없다는 장점이 있습니다.

 

시간이 지나면서 Reddit의 엔지니어링 팀은 수백 개의 Thrift 기반 마이크로서비스를 구축했으나, Reddit 에서는 Thrift를 계속 사용하는 것이 좋지 않다라고 판단했습니다. 

 

그래서 Reddit 은 Thift 에서 gRPC 로 변경했습니다. 

 

2016년에 등장한 gRPC는 클라우드 네이티브 생태계 내에서 상당한 인기를 끌었는데요. gRPC의 주요 장점은 다음과 같습니다:

  • HTTP/2를 전송 프로토콜로서 기본 지원
    • gRPC는 기본적으로 HTTP/2를 전송 프로토콜로 사용합니다. HTTP/2는 헤더 압축, 단일 연결에서의 다중 요청 및 응답 처리(멀티플렉싱), 서버 푸시 등의 기능을 제공하여 통신의 효율성과 성능을 크게 향상시킬 수 있습니다.
  • Istio, Linkerd와 같은 여러 서비스 메시 기술에서 gRPC를 기본 지원
  • 공개 클라우드 제공업체들도 gRPC 네이티브 로드 밸런서를 지원
    • 클라우드 제공업체들이 제공하는 로드 밸런싱과 네트워킹 최적화는 gRPC 트래픽에 특화되어 있습니다. 이는 gRPC 애플리케이션이 클라우드 환경에서 더 높은 성능을 발휘할 수 있도록 도울 수 있습니다. 예를 들어, HTTP/2를 기반으로 하는 gRPC는 멀티플렉싱과 서버 푸시 기능을 효율적으로 사용할 수 있도록 네트워크가 최적화되어 있어, 데이터 통신이 더 빠르고 효율적으로 이루어집니다.

 

 

References

https://blog.bytebytego.com/p/reddits-architecture-the-evolutionary

+ Recent posts