8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PostgreSQLスケールアウトパターン Instagram way

Last updated at Posted at 2016-12-03

アンチパターンばかり書いてきましたけど、たまにはアンチじゃないものも出しましょう。
PGConf.Asia2016 で Tantan の Victor Blomqvistさんもやってた、Instagram方式です。

元ネタ

DBのスケールアウト

Webサービスが順調に成長すると、顧客当たりのデータ量が徐々に増える、顧客が増えるで、DBのデータ量も増えていきます。
Apache/Tomcatの能力不足は、セッション管理だけ最初に注意して構成しておけば10や20に台数増やしていくのは容易な話で、問題はDBをどうするかです。

1台のDBServerでは処理能力が追いつかなくなってきたら?
参照中心のアプリケーションならリード・レプリカつくって負荷分散が初手になるかもしれませんが、追加・更新側のトランザクション処理が追いつかなくなったら?
顧客を2分割して、2つのDBServerに収容する水平分割となります。さらに増えたら、増えたところを分割して顧客を細分化していきます。

DB水平分割の運用問題

1台のDBServerでは処理能力が追いつかないから分割するので、それなりのデータ量が蓄積しています。移動するのは半分としても、pg_dump, psql -f でやるのは工事時間を長引かせることになります。

ストレージレベルで分割・移動

Instagramのやり方です。ここではAWS上のサービスとして話を進めます。

解決策

  • 小さく多数の論理shardを、少数の物理shard サーバーに mapする
  • 物理Shard = Postgres データベースクラスタ(≒ DB Server)
  • 論理Shard = Postgres の schema
  • ノード分割は、EBS Snapshot で丸ごとコピーして、DROP SCHEMA CASCADE で重複分を削除する。DBSizeが大きくても高速コピー可能。

8個の論理shardを2 server に格納した状態から 4server に再分割する例


// 8 logical shards on 2 machines
user_id % 8 = logical shard
logical shards -> physical shard map
{
  0: A, 1: A,
  2: A, 3: A,
  4: B, 5: B,
  6: B, 7: B
}

// 8 logical shards on 4 machines
user_id % 8 = logical shard
logical shards -> physical shard map
{
  0: A, 1: A,
  2: C, 3: C,
  4: B, 5: B,
  6: D, 7: D
}
  • machineA:
  • shard0
  • shard1
  • shard2 machineC稼働後に削除
  • shard3 machineC稼働後に削除
  • machineC:
  • shard0 machineC稼働後に削除
  • shard1 machineC稼働後に削除
  • shard2
  • shard3

分割引越の手順

  1. 対象顧客のSCHEMAをReadOnlyに
  2. EBS Snapshot
  3. 新規DBServerにSnapshotから作ったVolumeをattach
  4. 新規DBServerをwarmup
  5. 新規DBServerでSCHEMAのReadOnly制限解除
  6. SCHEMAとDBServerのMapを切替。(同時にReadOnly制限解除)
  7. 重複SCHEMAをDROP CASCADE

pg_dump/psql -f のSQLレベルだったデータ移動が、EBS SnapshotによるAWSのバックヤード処理に移りました。

warmupに少々時間がかかるので、さらに改善の余地があります。Snapshotから作ったばかりのVolumeは、新規DBServerが実際にアクセスしたところから徐々に実体化していくので、運用Webサービス用のDBではwarmupは必須です。

分割引越の手順2

  1. EBS Snapshot
  2. 新規DBServerにSnapshotから作ったVolumeをattach
  3. 新規DBServerをリードレプリカとして構成
  4. 新規DBServerをwarmup
  5. 対象顧客のSCHEMAをReadOnlyに
  6. 新規DBServerのリードレプリカ構成を解除して独立マスターにする
  7. 新規DBServerでSCHEMAのReadOnly制限解除
  8. SCHEMAとDBServerのMapを切替。(同時にReadOnly制限解除)
  9. 重複SCHEMAをDROP CASCADE

対象顧客のSCHEMAをReadOnlyにしたときには、対象顧客のデータはすべて新規DBServerに入っていて、warmupも済んでいる状態です。ReadOnly制限のかかる時間は、顧客データ量に比例するところのない定数時間となります。

分割DBの悩み Global Unique Key

単一DBなら SERIAL型とするだけです。複数serverでユニークかつ全体ロック・相互通信は回避したい。Hotspot問題になって、INSERT処理が遅くなってしまうので。

解決策

  • ID順≒時刻順としたいので、epoch msec をID先頭に含める。
  • 異なるShardのレコードをUNIONして並べたときでも、msecの分解能では ID順=時刻順
  • だいたい時刻順で十分とできるときに割り切ってしまう
  • Shard 毎にID生成関数をもち、ID内部に ShardIDを含めることでユニーク性を担保する。
  • 1msec の中で複数挿入の事態に備えて、10bit程度はShard内で自動increment。
  • 全体を64bitに納めるために epoch msec を 2011-01-01起点 の 41bit(41年間)、ShardId 13bit(8K個)、シリアル10bit とする
  • 2052年問題ができちゃうけど
  • 全体でのユニーク性が41年間は保つ。2PhaseCommitとか全体ロックなしでLocalの関数処理だけで済む。
8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?