Pinterestが6人のエンジニアだけで1100万人のユーザーへと拡大した方法
(engineercodex.substack.com)教訓
- よく知られた、実証済みの技術を使う
- Keep it Simple
- 過度に創造的に考えない(同一ノードを追加して拡張可能なアーキテクチャを選ぶ)
- 選択肢を絞る
- DBシャーディング > クラスタリング
- 楽しく! (新任エンジニアでも初週からコードに貢献可能)
2010年3月: クローズドベータ、エンジニア1人
- MySQL 1台 + Webサーバー(Django + Python)1台 + エンジニア1人(共同創業者2人を含む)。Rackspaceでホスティング
2011年1月: ユーザー1万人(MAU)、エンジニア2人
- AWS EC2 Webサーバースタック(EC2 + S3 + CloudFront)
- Django + Python
- 冗長性のためのWebサーバー4台
- NGINX をリバースプロキシ兼ロードバランサーとして使用
- MySQL 1台にRead-onlyセカンダリ1台
- Counter用MongoDB
- 1つのタスクキューと2台のタスクプロセッサ(非同期処理)
2011年10月: 320万 MAU、エンジニア3人
- 10か月間で急成長し、1.5か月ごとにユーザー数が2倍に増加
- 2011年3月のiPhoneアプリ公開が成長を牽引した要因の1つ
- 急成長に伴い、技術面の問題がより頻繁に発生
- Pinterestはこの時にミスをした: "アーキテクチャを過度に複雑にしてしまった"
- エンジニアは3人しかいないのに、データに使うDB技術は5種類あった
- MySQLを手動でシャーディングしながら、同時にCassandraとMembase(現在のCouchbase)でデータをクラスタリングしていた
- 彼らの「複雑すぎるスタック」
- Webサーバースタック(EC2 + S3 + Cloudnfront)
- Flask(Python)へバックエンドの移行を開始
- Webサーバー16台
- APIエンジン2台
- NGINXプロキシ2台
- 手動でシャーディングされたMySQL DB 5台 + 読み取り専用セカンダリ9台
- Cassandraノード4台
- Membaseノード15台(3つの独立クラスター)
- Memcacheノード8台
- Redisノード10台
- タスクルーター3台 + タスクプロセッサ4台
- Elastic Searchノード4台
- Mongoクラスター3台
- Webサーバースタック(EC2 + S3 + Cloudnfront)
- クラスタリングはうまくいかなかった
- 理論上は、クラスタリングはデータストアを自動的に拡張し、高可用性とロードバランシングを実現しながらSPOFをなくしてくれるが
- 残念ながら実際には、クラスタリングは複雑すぎて、アップグレードの仕組みが難しく、大きなSPOFを抱えていた
- 各DBには、DBからDBへルーティングするクラスター管理アルゴリズムがある
- DBに問題が発生すると、新しいDBを追加してそれを管理しなければならないが
- Pinterestのクラスター管理アルゴリズムにバグが発生し、全ノードのデータが破損し、データのリバランシングが停止し、いくつかの修復不能な問題が起きた
- Pinterestの解決策は?
- システムからすべてのクラスタリング技術(Cassandra、Membase)を削除
- (より実証済みの)MySQL + Memcachedに全面移行
2012年1月: 1100万 MAU、エンジニア6人
- 約1200万から2100 DAU
- この時点でアーキテクチャの単純化に時間を割いた
- クラスタリングとCassandraを除去し、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の代替)
- 削除したもの: Cassandra、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
- データベース層でテーブル結合と複雑なクエリを排除し、多くのキャッシュを追加
- データベース全体で一意制約を維持するために多大な労力が必要だったため、ユーザー名やメールアドレスのようなデータは巨大な非シャーディングDBに保存
- すべてのテーブルがシャードに載る
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個のMemcacheインスタンスを追加可能
Pinterestのデータベース構造
- IDs
- Instagramと似ており、シャーディングのためにユニークなID構造を持つ
- 64bit IDの構成
- Shard ID : どのシャードか。16 bit
- Type : オブジェクトタイプ(Pinのようなもの)10 bit
- Local ID : テーブル内の位置。38 bit
- このIDのLookup構造は、単純なPythonの辞書にすぎない
- Tables
- オブジェクトテーブルとマッピングテーブルがあった
- オブジェクトテーブルは、Pin、Board、コメント、ユーザーなどのためのもの。ローカルIDがMySQL Blob(JSON)にマッピングされる
- マッピングテーブルは、Boardをユーザーに対応付けたり、いいねをPinに対応付けたりするなど、オブジェクト間のリレーショナルデータのためのテーブル。完全なIDとタイムスタンプにマッピングされた完全なID
- すべてのクエリは効率のためにPK(主キー)またはインデックス参照を使う。すべての結合を排除
1件のコメント
Instagramがわずか3人のエンジニアで1400万人のユーザーを獲得した方法
これと同じシリーズの文章で、内容もつながっています。
「シンプルに保つこと。よく知られ、実証された技術を使うこと」