레딧(Reddit)의 아키텍처 진화의 여정
본 포스팅은 https://blog.bytebytego.com/p/reddits-architecture-the-evolutionary?ref=dailydev 의 글을 읽고 관심 가는 내용만 정리한 글이다.
레딧은 “인터넷 첫 페이지”가 되고자 하는 비전을 가지고 2005년에 설립되었다. 오래 시간동안 세계에서 가장 인기있는 큰 규모의 커뮤니티로 발전했고, 월간 사용자 수가 약 10억명이 넘는 큰 규모의 서비스로 성장했다.
최근 IPO도 성공적으로 진행하였고, 많은 사용자들의 콘텐츠가 성공의 주된 요인이겠지만, 사용자가 증가함에 따라 아키텍처도 진화한 점도 기술적 요인이라 볼 수 있다.
초기
초기 레딧은 Lisp으로 만들어졌다가 2005년 12월 Python으로 재작성되었다.
Lisp도 훌륭한 언어이지만, 사용자층이 많지 않다보니 라이브러리가 부족한 단점이 존재했다. 레딧 창업자 중 한명인 Steve Huffman은 이 문제를 아래와 같이 표현했다.
“우리는 다른 사람들의 어깨위에서 서비스를 구축하고 있는데, 상황이 어렵다. 기댈 어깨가 많지 않다.”
아키텍처의 핵심 구성 요소
아래 그림은 레딧의 상위 아키텍처의 구성 요소를 보여준다.
- CDN은 Fatly의 CDN을 진입 대문으로 사용한다.
- Frontend은 2009년 초기에는 jQuery를 사용하다가 나중에 Node.js와 Typescript로 전환하였다.
- R2는 모놀리스 애플리케이션이다. Python을 이용하여 구축한 모놀리식 애플리케이션이다.
인프라 관점에서 레딧은 2009년에 베어메탈 서버를 없애고 모두 AWS로 이전했다. S3를 사용해서 썸네일을 호스팅하고 로그를 저장했다. 애플리케이션 서버들은 모두 EC2로 전환했다.
현재 우리 레거시 시스템도 비슷한 구조다.
R2
R2는 레딧의 핵심이다. 거대한 모놀리식 애플리케이션이며 아래 그림과 같이 구성되어 있다.
사용자 트래픽을 견디기위해 동일한 애플리케이션을 여러 서버에 배포하여 운영한다.
로드 밸런서는 앞에 위치하여 적절한 애플리케이션에 라우팅 작업을 수행한다.
사용자 투표와 같은 작업은 리소스를 많이 사용하기에 RabbitMQ를 통해 비동기 대기열로 작업한다.
데이터 저장 관점에서는 Postgres를 사용한다. 로드를 줄이기 위해 DB앞에 Memcache를 배치하여 부하를 줄인다.
이런 변화는 기존 아키텍처를 유지 할 수 없다라는 결론에 다다랐다.
Golang 마이크로서비스와 GraphQL
레딧은 2017년에 GraphQL을 도입하는 여정을 시작했다. GraphQL은 클라이언트가 원하는 데이터만 요청할 수 있도록 하는 Spec이다. 각 클라이언트마다 조금씩 다른 데이터를 요청하는 경우에 탁월한 선택이다.
2021년 초에 몇 가지 주요 목표를 가지고 GraphQL로의 전환을 시작했다.
GraphQL Federation은 여러 개의 작은 GraphQL API(하위그래프)를 하나의 커다란 GraphQL API(수퍼그래프)로 결합하는 방법이다. 수퍼 그래프는 클라이언트 애플리케이션이 쿼리를 보내고 응답하는 대문 역할을 수행한다.
수퍼그래프는 클라이언트가 수퍼그래프에 쿼리를 보내면 해당 쿼리를 응답하는데 필요한 데이터가 있는 하위 그래프를 파악한다. 관련 부분을 하위 그래프로 라우팅하고, 응답을 수집하여 결합한 응답을 클라이언트에 전달한다.
2022년 레딧의 GraphQL팀은 Subreddit, Comments와 같은 기능에 몇 가지 새로운 하위 그래프를 추가했다. 전환 단계에서 파이썬 모놀리스와 새로운 Go 하위 그래프가 함께 동작하여 쿼리를 수행한다. 더 많은 기능들이 Go 하위 그래프로 이관됨에따라 모놀리스는 결국 폐기될 수 있다.
아래의 그림은 점진적인 전환을 보여준다.
래딧의 주요 요구사항은 모놀리스에서 새로운 Go 하위 그래프로의 기능 마이그레이션을 점진적으로 처리하는 것이다.
문제가 발생할 경우 모놀리스로 다시 전환할 수 있는 기능을 갖추면서 오류율과 대기 시간을 평가하면서 트래픽을 새로운 곳으로 점점 늘리기를 원했다.
하지만, 불행하게도 GraphQL Federation은 점진적 트래픽 마이그레이션을 지원하는 방법을 제공하지 않는다. 그래서 레딧은 아래와 같이 Blue/Green 하위 그래프 배포를 선택했다.
위 접근 방식에서는 파이썬 모놀리스와 Go 하위 그래프가 스키마의 소유권을 공유하게 된다. 로드 밸런서는 게이트웨이와 하위 그래프 사이에 위치하여 구성에 따라 새로운 하위 그래포 또는 모놀리스로 라우팅한다.
위 설정을 사용하면 모놀리스 또는 새로운 하위 그래프에서 처리하는 트래픽 비율을 제어할 수 있기에 마이그레이션 과정에서 레딧의 안정성이 향상되게 된다.
현재도 레딧은 중단을 최소화하면서 마이그레이션을 계속 진행중이다.
CDC 및 Debezium을 사용한 데이터 복재
처음에는 레딧은 기본 데이터베이스에서 생성된 WAL 세그먼트를 사용하여 데이터 복제를 지원했다. WAL은 커밋되기전 DB에 대한 모든 변경사항을 기록하는 파일이다. 쓰기 작업중 오류가 발생하면 로그에서 변경 사항을 복구할 수 있다.
레딧은 복제본 데이터를 읽을 수 있는 S3에 WAL 파일을 지속적으로 보관했다. 그러나 이 방식에는 문제가 있었다.
- 일일 스냅샷을 밤에 실행했기에 낮 시간대의 데이터와 불일치가 발생
- DB의 스키마 변경이 빈번하였기에 스냅샷 생성 및 복제에 문제가 발생
- 복제 프로세스 취약성 및 백업 실패 발생
데이터 복제 안정성을 높이기 위해 레딧은 Debezium 및 Kafka Connect를 사용하는 CDC(Change Data Capture)을 사용하기로 결정했다.
Debezium은 지연 시간이 짧은 데이터 스트리밍 플랫폼을 제공하는 오픈소스 프로젝트이다.
해당 프로젝트 한국어 설명이 없어서, 작성해서 기여했다. (https://github.com/debezium/debezium/blob/main/README_KO.md)
Postgres에서 행이 추가, 삭제 또는 수정될 때마다 Debezium은 변경사항을 수신하고 Kafka Topic에 Produce한다. Connect는 Topic에서 변경사항을 읽고 대상 테이블을 업데이트한다.
아래는 위에서 설명한 프로세스이다.
Debezium을 이용한 것은 여러 대상 시스템에 실시간 데이터 복제를 가능하게 했기 때문에 큰 변화가 생겼다.
우리도 Modernization 작업 시 CDC를 고려중에 있다. 오픈소스를 채택하려면 내부 역량이 꽤 올라가야 할 것으로 판단된다.
끝으로,
레딧의 아키텍처 여정은 수년에 걸쳐 시장의 변화에 따라 지속적인 발전을 거듭해왔다. 당장은 와닿지 않지만, 사용자가 폭발적으로 증가할 것으로 판단되는 서비스를 운영하고 있다면 레딧처럼 진화를 해야 한다.
Originally published at https://giljae.com.