Pinterest가 6명의 엔지니어만으로 1100만명의 사용자로 확장한 방법
(engineercodex.substack.com)교훈들
- 잘 알려진, 검증된 기술 사용
- Keep it Simple
- 너무 창의적으로 생각하지 않기(동일한 노드를 추가하여 확장가능한 아키텍처로 결정)
- 옵션을 제한하기
- DB 샤딩 > 클러스터링
- 즐겁게! (신입 엔지니어도 첫주부터 코드에 기여 가능)
2010년 3월: 클로즈 베타, 엔지니어 1명
- MySQL 1대 + 웹서버(Django + Python) 1대 + 엔지니어 1명(2 명의 공동창업자 포함). Rackspace에 호스팅
2011년 1월: 1만명 사용자(MAU), 엔지니어 2명
- AWS EC2 웹 서버 스택(EC2 + S3 + CloudFront)
- Django + Python
- Redundacy를 위한 4대의 웹서버
- NGINX 를 리버스 프록시이자 로드 밸런서로
- MySQL 1대에 Read-only 세컨더리 1대
- Counter 용 MongoDB
- 1개의 태스크 큐와 2대의 태스크 프로세서(비동기 작업)
2011년 10월: 320만 MAU, 엔지니어 3명
- 10개월간 급속도로 성장해서 1.5달에 2배씩 사용자가 늘어남
- 2011년 3월 아이폰 앱 출시가 성장을 주도한 것중 하나
- 빠르게 성장하면서 기술 쪽 문제가 더 자주 발생
- 핀터레스트는 이때 실수를 함 : "아키텍처를 지나치게 복잡하게 만들었음"
- 엔지니어가 3명밖에 없는데, 데이터에 사용하는 DB 기술이 5가지였음
- 수동으로 MySQL을 샤딩하면서 동시에 Cassandra 와 Membase(현재 Couchbase)를 사용하여 데이터를 클러스터링함
- 그들의 "너무 복잡한 스택"
- 웹서버 스택(EC2 + S3 + Cloudnfront)
- Flask(Python)으로 백엔드를 옮기기 시작
- 웹서버 16대
- API 엔진 2대
- NGINX 프록시 2대
- 수동으로 샤딩된 5대의 MySQL DB + 9대의 읽기 전용 세컨더리
- 카산드라 노드 4개
- Membase 노드 15개(3개의 별도 클러스터)
- Memcache 노드 8개
- Redis 노드 10개
- Task 라우터 3대 + Task 프로세서 4대
- Elastic Search 노드 4개
- Mongo 클러스터 3개
- 웹서버 스택(EC2 + S3 + Cloudnfront)
- 클러스터링이 잘못됨
- 이론적으로는 클러스터링이 자동적으로 데이터스토어를 확장, 고가용성 및 로드밸런싱 해주면서 SPOF를 없애주지만
- 안타깝게도 실제로는 클러스터링은 너무 복잡하고, 업그레이드 메커니즘이 어려우며, 큰 SPOF 를 가짐
- 각 DB에는 DB에서 DB로 라우팅하는 클러스터 관리 알고리듬이 있음
- DB에 문제가 발생하면 새 DB가 추가되고 이를 관리해줘야 하지만
- Pinterest의 클러스터 관리 알고리듬에 버그가 발생해서 모든 노드의 데이터가 손상되고 데이터 리밸런싱이 중단되었으며 몇 가지 수정할 수 없는 문제가 발생
- Pinterest의 해결책은?
- 시스템에서 모든 클러스터링 기술(카산드라, 멤베이스)을 제거
- (더 검증된) MySQL + Memcached로 올인
2012년 1월: 1100만 MAU, 엔지니어 6명
- 약 1200만 에서 2100 DAU
- 이 시점에 아키텍처를 단순화하는데 시간을 할애함
- 클러스터링 및 카산드라를 제거하고 MySQL, Memcache, 샤딩으로 대체
- 단순화된 스택
- Amazon EC2 + S3 + Akamai (CloudFront 대체)
- AWS ELB (Elastic Load Balancing)
- 90 Web Engines + 50 API Engines (Flask 이용)
- 66 MySQL DBs + 66 secondaries
- 59 Redis Instances
- 51 Memcache Instances
- 1 Redis Task Manager + 25 Task Processors
- 샤딩된 Apache Solr (Elasticsearch 대체)
- 제거한 것들: Cassanda, Membase, Elasticsearch, MongoDB, NGINX
Pinterest가 DB를 수동 샤딩한 방법
데이터베이스 샤딩은 단일 데이터세트를 여러 개의 데이터베이스로 분할하는 방법
장점: 고가용성, 로드 밸런싱, 데이터 배치를 위한 간단한 알고리듬, 데이터베이스를 쉽게 분할하여 용량 추가, 데이터 찾기 쉬움
- 처음 샤딩했을때 문제가 있어서, 몇달에 걸쳐서 점짐적으로 수동 샤딩을 진행
- 전환 순서
- 1 DB + Foreign Keys + Joins
- 1 DB + Denormalized + Cache
- 1 DB + Read Slaves + Cache
- 여러대의 기능적으로 샤딩된 DB들 + Read Slaves + Cache
- ID로 샤딩된 DB들 + Backup Slaves + Cache
- 데이터베이스 계층에서 테이블 조인과 복잡한 쿼리를 제거하고 많은 캐싱을 추가
- 데이터베이스 전반에 걸쳐 고유한 제약 조건을 유지하는 데 많은 노력이 필요했기 때문에 사용자 이름과 이메일과 같은 데이터는 거대한 샤딩되지 않은 데이터베이스에 보관
- 모든 테이블이 샤드에 올라감
2012년 10월: 2200만 MAU, 엔지니어 40명
- 아키텍처는 그대로 유지하면서, 몇대의 똑같은 시스템들을 추가
- Amazon EC2 + S3 + CDNs (EdgeCast, Akamai, Level 3)
- 180 web servers + 240 API engines (Flask)
- 88 MySQL DBs + 88 secondaries each
- 110 Redis instances
- 200 Memcache instances
- 4 Redis Task Managers + 80 Task Processors
- Sharded Apache Solr
- 하드디스크에서 SSD로 옮기기 시작
- 중요한 교훈: 제한적이고 검증된 선택(limited, proven choices)이 좋았다는 것
- EC2와 S3를 고수하다 보니 구성 선택의 폭이 제한되어 골칫거리가 줄어들고 단순성이 높아졌음
- 하지만 새로운 인스턴스는 몇 초 만에 준비될 수 있게 됨. 즉, 단 몇 분 만에 10개의 멤캐시 인스턴스를 추가 가능
Pinterest 의 데이터베이스 구조
- IDs
- Instragram과 비슷하게, 샤딩 때문에 유니크한 ID 구조를 가지고 있음
- 64bit ID의 구성
- Shard ID : 어떤 샤드인지. 16 bit
- Type : 객체 타입(Pin 같은 것 ) 10 bit
- Local ID : 테이블에서의 포지션. 38 bit
- 이 ID의 Lookup 구조는 그냥 간단한 파이썬 딕셔너리
- Tables
- 개체 테이블과 매핑 테이블이 있었음
- 객체 테이블은 핀, 보드, 댓글, 사용자 등을 위한 것. MySQL Blob(JSON)에 매핑된 로컬 ID
- 매핑 테이블은 보드를 사용자에 매핑하거나 좋아요를 핀에 매핑하는 등 객체 간의 관계형 데이터를 위한 테이블. 전체 ID와 타임스탬프에 매핑된 전체 ID
- 모든 쿼리는 효율성을 위해 PK(기본 키) 또는 인덱스 조회. 모든 조인을 제거
인스타그램이 오직 3명의 엔지니어로 1400만 사용자를 확보한 방법
이것과 같은 시리즈의 글이면서, 내용도 연결되네요.
"간단하게 유지할 것. 잘 알려지고, 검증된 기술들을 사용할 것"