スケールするシステムにおけるエンティティの扱いと 分散id生成
TRANSCRIPT
スケールするシステムにおけるエンティティの扱いと分散 ID生成安田裕介
ChatWork2016/03/26
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
アジェンダ•エンティティとは何か•エンティティの何が難しいのか•エンティティの難しさの解決策: Event Sourcing•アクターとエンティティ•エンティティの IDの分散発行方法•エンティティの一意性の保証:シャーディング
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
エンティティ•一意に識別できるオブジェクト•ユーザー、メッセージなど• IDを持つ•同一エンティティの状態は変わり続ける• e.g. メッセージは編集されたり消されたりする•ドメインイベントやコマンドもエンティティ (不変 )
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
エンティティは変化するから難しい
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
エンティティと素直に付き合っているとシステムはスケールしない記憶デバイス、ファイルシステム、データベース、アーキテクチャはスケールするためにイミュータブルになっていく
ミュータブルなエンティティの状態をインフラストラクチャに永続化しない方法を考えないといけないImmutability Changes Everything Designing Data-Intensive Applications
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
ドメインイベントによる Event Sourcing
•不変のイベントだけで状態を管理する手法に Event Sourcingがある
•エンティティの状態が変更された時ドメインイベントが起きる•不変のドメインイベントの歴史があればエンティティの状態が復元できる•ドメインイベントだけ永続化すれば追記のみにできる
未読 +1 未読 +1 未読 -1 未読 +1 未読 -1 未読 +1
未読件数2
ドメインイベントの歴史
エンティティの状態
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
エンティティをアクターで表現してEvent Sourcingする
• エンティティの状態をアクターの内部に隔離する• コマンドメッセージを受け取ったらドメインイベントを永続化してエンティティの状態を変更• アクターは状態や位置、障害を隠蔽するので、外からは無限の寿命を持つオブジェクトのように見える
Akka Persistence and Eventuate
Akka PersistenceはアクターでEvent Sourcingを行う実装の1つ
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
エンティティアクターの実装class MessageEntity(messageId: MessageId, roomId: ChatRoomId) extends PersistentActor with ActorLogging { import MessageProtocol._
override def persistenceId: String = Message.persistenceId(messageId, roomId)
var messageState: Message = _
def receiveCommand: Receive = { case Commands.PostMessage(id, roomId, message, createdAt) => { val p = Message.postMessage(id, roomId, message, createdAt) persist(MessagePosted(p)) { event => updateState(MessagePosted(p)) context.system.eventStream.publish(MessagePosted(p)) } } case Commands.DeleteMessage(id, roomId, deletedAt) => { val e = messageState.deleteMessage(deletedAt) persist(MessageDeleted(e)) { event => updateState(MessageDeleted(e)) saveSnapshot(messageState) context.system.eventStream.publish(MessageDeleted(e)) } } }
def receiveRecover: Receive = { case event: MessagePosted => updateState(event) case event: MessageDeleted => updateState(event) }
def updateState(event: MessageProtocol.Event): Unit = event match { case MessagePosted(newMessage) => messageState = newMessage case MessageDeleted(deletedMessage) => messageState = deletedMessage }}
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
エンティティの IDは誰が決める?• Event Sourcingではエンティティは DBに存在しない•エンティティはシステムに存在する• IDの発行を DBに任せられない•そもそも分散 DBは連番 IDを発行できない
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
分散 ID生成器 : Twitter Snowflake•システムをスケールさせるには、従来の中央集権的なDBによる ID生成ではなく、分散独立した ID生成が必要になる
• Snowflakeは分散 ID生成サービス• Twitter社がMySQLから Cassandraに移行した時に作られた• https://github.com/twitter/snowflake
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
Chatworkの ID生成器
Scalaを用いて分散IDワーカを実装する PHPでID生成器を実装してみました
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
ID生成アクターを作ってみるclass IdWorker(dcId: DatacenterId, wId: WorkerId) extends Actorwith IdWorkerImpl with ActorLogging { import IdWorkerProtocol._
val datacenterId: Long = dcId.value val workerId: Long = wId.value
var sequenceId: Long = 0L
var lastTimestamp = -1L
def receive: Receive = { case msg@GenerateId(replyTo) => { val NextId(idOpt, timestamp, nextSequence) = nextId(timeGen(), lastTimestamp, sequenceId) sequenceId = nextSequence lastTimestamp = timestamp idOpt match { case Some(id) => replyTo ! IdGenerated(id) case None => { log.debug("retrying to avoid id duplication") self ! msg } } } }}
ID生成のアルゴリズムは同じ状態をアクター内に閉じ込めて同期コードを排除時間分解能を超えた時にブロックしない
1つの IdWorkerでおよそ 40k/secの IDを生成可能ソースコード
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
エンティティ(分散 ID生成器)の一意性をどう保証するか•エンティティはシステムに複数存在してはならない•複数存在すると同時に異なるドメインイベントが発行され矛盾した状態になる• ID生成器もエンティティで、同じ IDのものが複数存在してはならない(重複した IDの発行につながる)• Snowflakeは Zookeeperを使って ID生成器を管理している
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
エンティティの一意性を保証する手法シャーディングShard Coordinator
Shard Region Shard Region Shard Region
IdW
orke
r-1
IdW
orke
r-2
IdW
orke
r-3
…
IdW
orke
r-30
IdW
orke
r-31
IdW
orke
r-32
…client
GenerateId(workerId = 30)
Node 1 Node N
ID生成器とエンティティの一意性は同じ手法で実現可能
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
シャーディングに対応した分散 ID生成器のソースコードhttps://github.com/TanUkkii007/reactive-snowflake
スケールするシステムにおけるエンティティの扱いと分散 ID生成
2016/03/26 © ChatWork All rights reserved.
まとめ•エンティティは一意に識別できるオブジェクト•エンティティは状態が変わり続けるから難しい•システムをスケールさせるためには不変なデータを扱う必要がある•Event Sourcingをつかえば可変なエンティティではなく不変なドメインイベントを永続化できる•DBに頼らずにエンティティの IDを発行する手法がある•システムレベルでエンティティの一意性を保証する技術にシャーディングがある