Amazon DynamoDB Backed な
テレコムコアシステムを構築・運用してる話
SORACOM: You Create, We Connect
Kenta Yasukawa, Ph. D.
Cofounder & CTO,
SORACOM, Inc.
2018/11/1
©2017 SORACOM, INC 2
自己紹介
安川 健太
Cofounder & CTO, SORACOM Inc.
略歴:
Researcher at Ericsson Research
AWS Solutions Architect
AWS NoSQL Developer & SA
Twitter: @thekentiest
Facebook: fb.me/kenta.yasukawa
LinkedIn: linkedin.com/in/kenta-y
好きなAWSのサービス:
VPC、DynamoDB
居住地:
Mountain View, CA, USA
©2017 SORACOM, INC 3
©2017 SORACOM, INC 4
IoTシステムに共通の形
©2016 SORACOM, INC 4
Internet CloudThings
IntelligenceConnected
Devices
あのボタンだって
そうですよね?
©2016 SORACOM, INC 7
シンプルに見えてたくさんの課題が
Internet CloudThings
セキュリティ
デバイスの
制約
ネットワーク接続
デバイスの
管理
クラウド側
の構成
デバイスを直接クラウドに繋ぐ SORACOM Air
通信事業者様
交換局
3G/LTE
外部から侵入できない安全な通信路 お客様と作る安全な通信路
お客様の
クラウド
Public
Endpoints
お客様
①SIMをインストール
Webコンソール
②Webから操作
API
③APIで自動化
©2017 SORACOM, INC 9
IoTシステムに共通の形
©2016 SORACOM, INC 9
Internet CloudThings
IntelligenceConnected
Devices
©2016 SORACOM, INC 10
SORACOM Airが可能にする構成
CloudThings
IntelligenceConnected
Devices
Internet
The Internet is
accessible, but
optional
• Amazon VPCとプライベート接続  SORACOM Canal
• プライベートクラウドと専用線/VPN接続  SORACOM Direct/Door
• デバイスとサーバを単一の仮想L2サブネットに接続  SORACOM Gate
閉域網を構築する各種サービス
The Internet
VPG IoTバックエンド
お客様のシステム
Virtual Private Gateway (VPG)を通してお客様のシステムと接続
Public
Endpoints
SORACOM Beam
デバイスとクラウドの橋渡しをする各種サービス
SORACOM Funnel
Amazon Kinesis
Family
Microsoft Azure
EventHubs
AWS IoT
Google Cloud
Pub/Sub
• Publicエンドポイントにセキュアに接続  SORACOM Beam
• クラウドサービスにデータを送信  SORACOM Funnel
• データ収集&可視化  SORACOM Harvest & Lagoon
簡易で省電力なプロトコル
- TCP / UDP raw socket
- HTTP, MQTT
- LoRaWAN, Sigfox
高機能でセキュアなプロトコル
- TCP over TLS
- HTTPS, MQTTS
SORACOM Airが
提供する安全な通信路
SORACOM
Harvest SORACOM
Lagoon
SORACOM Krypton
SIMで認証し、接続情報をセキュアにプロビジョニング
SORACOM
Krypton
SIM認証 ログイン要求
クレデンシャル
Amazon
Cognito
Kinesis
Video
Streamセルラー
WiFi
©2017 SORACOM, INC 14
SORACOM Airの裏側
通信キャリアとMVNO
インターネットモノ 基地局 データセンター
MVNO(L2契約)
MVNO
ISP
パケット交換
帯域制御
顧客管理
課金・・・
ブランド
販売網
通信キャリア
専
用
線
接
続
SORACOM Airの裏側
インターネットモノ 基地局 データセンター
MVNO
ISP
パケット交換
帯域制御
顧客管理
課金・・・
ブランド
販売網
通信キャリア
クラウドネイティブ設計な
GGSN/PGW, HLR/HSS
専
用
線
接
続
Session
Management
Authentication
& Authorization
Billing
API Gateway
API
API
Polaris: Cellular Core Interfaces
implemented as distributed system
Amazon
DynamoDB
Cellular Core Network
reinvented on AWS
Availability Zone
Availability Zone
IoT
Devices
3G/
LTE
MNO AWS
Direct
Connect
Dipper: Set of micro services that enable
Polaris and API
©2017 SORACOM, INC 18
アーキテクチャ全体についてはTMAで
tma soracom
Session
Management
Authentication
& Authorization
Billing
API Gateway
API
API
Polaris: Cellular Core Interfaces
implemented as distributed system
Cellular Core Network
reinvented on AWS
Availability Zone
Availability Zone
IoT
Devices
3G/
LTE
MNO AWS
Direct
Connect
Dipper: Set of micro services that enable
Polaris and API
Amazon
DynamoDB
今日の本題
なぜDynamoDBを選んだのか?
•SORACOMにとって大事な特性
• Availability
•Scalability
• Durability
•SORACOMにとって妥協できる特性
• Transactionサポート
•SQLサポート
求める特性を最もよく満たすから
•やりたいのは信頼性の高いサービスの提供
≠ 信頼性の高いDBシステムの構築運用そのもの
DBを運用しなくていい!
自分たちで運用する
DBシステム
SORACOMを支えるDBシステムを
支える人たちが必要!! 
AWSが運用してくれてる!!
Amazon DynamoDB
1. KVSだけでどうやって実装してるんですか?
2. Eventual Consistencyじゃシステムに不整合状態
起きませんか?
3. トランザクションなしで辛くないですか?
とはいえ、よく聞かれること
•DynamoDBはKVSではありません
• Partition Keyだけ使えば確かにKVS的
• Partition KeyとSort Keyも使えば範囲指定してク
エリも可
• Indexも使えば任意のAttributeでクエリ可
Q1. KVSだけでどうやって実装してるんですか?
DynamoDBのキー
引用元: https://www.slideshare.net/AmazonWebServicesJapan/20170809-black-belt-dynamodb/21
DynamoDBのIndex
引用元:https://docs.aws.amazon.com/ja_jp/amazondynamodb/latest/developerguide/GSI.html
• Strong Consistencyを指定して読むことも可
• Strong consistency
• Eventual consistency
• Conditional Updateを使えば楽観的ロックも可
2. Eventual Consistencyじゃシステムに不整
合状態起きませんか?
Increment version iff version == 1
Version = 1 -> 2
Increment version iff version == 1
•単一アイテムの更新はアトミック
• 1回の更新を1アイテムにまとめるように設計
•どうしても複数アイテム更新が必要なときは
•DynamoDB Stream + Lambda活用
• 冪等性をもたせてエラーが起きたらリトライ
3.トランザクションなしで辛くないですか?
•SORACOMがAmazon DynamoDBを使う理由
• 求める特性を最もよく満たすから
•DBを運用しなくていいから
• 安川が元DynamoDBチームにいたから
•SQLなDBとは設計時の考え方が違う
• 限られた関係性の表現
•アトミック更新が出来るのは1アイテムまで
ここまでのまとめ
NoSQL (DynamoDB) Firstでの開発
• 対象のModelの属性を決める
• Modelの更新/クエリの仕方を考える
• 更新は1アイテムに収められるか
• クエリはどの属性値で行うか
• Modelに対応するテーブルとそのスキーマを決める
• 出来るだけModelとテーブルの関係を1:1に
• スキーマとして設定するのはKeyとIndexだけ
テーブルの設計
SORACOM SIM管理の例で見てみる
Subscriberを扱うユースケースの例
• Subscriber Modelの特徴
• IMSIというIDを持つ
• 最初は特定のOperatorに紐づいて
いない
• 購入して登録するとそのOperator
に紐づく
• 更新/クエリの仕方の例
• IMSIだけで特定して更新/クエリ
• 特定のOperatorに紐づく一覧を取
得
SORACOMのSubscriber (≒ SIM)
Subscriber s =
{
imsi: “xyz”,
operatorId: “OP…”,
status: “active”,
…
}
• IMSIは単独でPrimary Key
• Partition Keyのみのスキーマを選択
• OperatorIDを持つ複数のSubscriber
• OperatorIDにIndexを作成
Subscriber (≒ SIM)のスキーマ設計
IMSI (Part) OperatorID Status …
1234567890000001 OP0012345678 active …
1234567890000002 OP0012345678 ready …
1234567890000003 instock …
OperatorID (Part) IMSI (Sort) Status …
OP0012345678 1234567890000001 active …
OP0012345678 1234567890000002 ready …
•Javaで書くならModelとテーブルの関係はAnnotation
で記述可
•AWS Java SDKのDynamoDBMapperを活用
ちなみに
@DynamoDBTable(tableName=“subscriber”)
public class Subscriber {
@DynamoDBHashKey
public String getImsi(){ … }
@DynamoDBAttribute
public String getOperatorId(){ ... }
}
•DynamoDB Streamを活用してTriggerを実装すると
簡単
更新履歴を保存したいような時は?
Stream PutItem
Subscriber
table
Subscriber
History
table
Lambda
function
さらにSubscriberのGroupを実装したい
• Group Modelの特徴
• 任意の数のSubscriberが所属
• Subscriberが所属するGroupは1つ
• 更新/クエリの仕方
• Group IDで属性を更新/クエリ
• 指定したSubscriberをGroupに追加・削
除
• 特定のGroupに所属するSubscriber一覧
を取得
• 指定したSubscriberの所属するGroupを
取得
Groupの設計
Group g =
{
id: “dead-beaf-cafe”,
operatorId: “OP…”,
configuration: { … },
…
}
• Groupの属性としてSubscriber IDのリ
ストを持つ
• 問題:
• アイテムサイズの上限で頭打ち
• Groupに追加する際、Groupと
Subscriber双方のアイテムの更新が
必要
• Groupに所属するSubscriberの情報
を取得するのにSubscriberの数分の
クエリが必要
ナイーブな実装(1)
Group g =
{
id: “dead-beaf-cafe”,
operatorId: “OP…”,
configuration: { … },
subscribers: [
“imsi1”, “imsi2”, …
]
}
• Group IDから所属する
Subscriber一覧を引くテーブ
ルを用意する
• 問題:
• Groupに追加する際、
GroupとSubscriber双方の
アイテムの更新が必要
• Subscriber更新時もGroup
側のテーブルの更新が必要
ナイーブな実装(2)
IMSI (Part) OperatorID Status Group ID
1234567890000001 OP0012345678 active …
1234567890000002 OP0012345678 ready …
1234567890000003 instock …
OperatorID (Part) Group ID (Sort) IMSI
OP0012345678 1234-5678-… 1234567890000001
OP0012345678 abcd-cafe-… 1234567890000002
OP0012345679 cdef-beef-… 1234567890000004
Subscribers Table
Group Table
1アイテム更新だけで出来ない?
• IMSIは単独でPrimary Key
• Partition Keyのみのスキーマを選択
• GroupIdのGSIを作成
SubscriberテーブルにIndexを張る
IMSI (Part) OperatorID Status Group ID
1234567890000001 OP0012345678 active 1234-5678-…
1234567890000002 OP0012345678 ready abcd-cafe-…
1234567890000003 instock cdef-beef-…
Group ID (Part) IMSI (Sort) Status …
1234-5678-… 1234567890000001 active …
abcd-cafe-… 1234567890000002 ready …
Subscribers Base Table
Group ID Index
OperatorID (Part) Group ID (Sort)
OP0012345678 1234-5678-…
OP0012345678 abcd-cafe-…
OP0012345679 cdef-beef-…
Group Table
• Partition-Sort KeyとIndexを使ってモデルを表現
• Index / Sort Key クエリ以外はフルスキャンなのでご
注意
• 出来るだけ1回の更新で済むように設計
• テーブルは分けずにむしろまとめていく方向で
• Indexや複合キーをうまく活用
• 値のProjectionは必要に応じて調整
テーブル設計で大事なこと
より複雑な処理をDynamoDBで
実現するためのテクニック
•二段階のソートキーがあるなら複合キーにIndex
•例:Product Typeで一覧しつつ、特定のSerial
Numberの範囲をクエリしたりしたい
複数属性で範囲指定クエリしたい場合
User ID (Part) Product Type Serial Number
1234567890000001 Button 0000001
1234567890000001 Button 0000002
1234567890000001 Modem 0000001
あるUserのButtonの一覧を取りたければ
SortKey BEGINS_WITH “Button_”
あるUserのButtonの中からSerial Numberがある範囲のクエリしたければ
SortKey BETWEEN “Button_xxx” AND “Button_yyy”
ProductType_SerialNumber (Sort)
Camera_0000001
Camera_0000002
Modem_0000003
•全体のサイズが1アイテムに収まるならいっそ
全部の値をアイテム内に収めてアトミック更新
複数の値を一度に更新したい場合の対処法
Product Line Products (Map) LastUpdatedTime
brandA {product1: 100, product2: 300, product3: 103, …} 2018-11-01-17:20
brandB {product1: 48, product2: 52, product3: 1231,…} 2018-10-31-09:30
brandC {product1: 1400, product2: 152, product3:1231, …} 2018-10-29-19:15
商品の在庫を管理する例:
brandA で product1が5個、product3が10個売れたとしたら
1. GetItemで brandA のアイテムを読む
2. Product1から5個、product3から10個差し引いてConditionalUpdate(lastUpdatedTime == 2018-11-01-17:20)
•複数アイテムに跨るなら処理に冪等性をもたせて、
エラーが起きたらリトライで復旧できるようにする
複数の値を一度に更新したい場合の対処法
Coupon Code Coupon Amount Coupon Usage History (Map)
1234-5678 1000 { 2018-09: 125,
2018-10: 300,
2018-11: 100 }
9abc-def0 500 { 2018-09: 50, 2018-10: 100, 2018-11: 200}
5678-9abc 2000 { 2018-09: 10, 2018-10: 500, 2018-11: 200}
毎月利用した分を差し引くクーポンを管理する例
クーポンの残額そのものを管理するのではなくて、月ごとの利用料を記録していく(途中状態の管理)
 特定月の締め処理にエラーが発生しても再度やり直すことで正しい状態に収束させられる(冪等性)
• Conditional Updateを使った楽観的ロック
楽観的ロックもうまく使うと強力です
Increment version iff version == 1
Version = 1 -> 2
Increment version iff version == 1
@DynamoDBTable(tableName=“subscriber
”)
public class Subscriber {
@DynamoDBHashKey
public String getImsi(){ … }
@DynamoDBAttribute
public String getOperatorId(){ ... }
@DynamoDBVersion
public Long getVersion() {…}
}DynamoDBMapperなら モデルクラスに
@DynamoDBVersion アノテーションをつけるだけ!
楽観的ロックを活用したJobスケジューラ
• ある時刻になったら一度だけ発火
するJobスケジューラが欲しい
• Workerが1台じゃ心配だから複
数台にしたい
• けど複数のWorkerがJobを取り
に行ったら同時に実行しちゃう
かもしれない
楽観的ロックを活用したJobスケジューラ
Job ID (Part) Time to Fire Status
1234-5678 2018-11-01-17:20 notStarted
9abc-def0 2018-10-31-09:30 Executing
5678-9abc 2018-10-29-19:15 Finished
1. Fetch a job to execute
2. Update status iff status == notStarted
3. Execute Job because step 2 succeeds
Status = notStarted -> Executing
1. Fetch a job to execute
2. Update status iff status == notStarted
3. Skip the job because step failed
•Jobテーブルを複数WorkerでPollingして未実行のイ
ベントを見つけたらStatusを更新して実行
楽観的ロックを活用したJobスケジューラ
Job ID (Part) Time to Fire Status PartitionId
1234-5678 2018-11-01-17:20 notStarted 1
9abc-def0 2018-10-31-09:30 Executing
5678-9abc 2018-10-29-19:15 Finished
def0-9abc 2018-11-02-19:39 notStarted 10
• 未実行の処理を並列に効率よく見つけるためにSparse Index
• 各Jobに一定範囲 (e.g. 1〜10) の乱数をPartitionIdとして設定してGSI
• 各Workerは乱数の範囲をそれぞれクエリしてJob取得
• 終わったらPartitionIdを消す( Sparse Indexから消える)
Partiction ID (Part) Time to Fire (Sort) Status Job ID
1 2018-11-01-17:20 notStarted 1234-5678
10 2018-11-02-19:39 notStarted def0-9abc
Job登録
1からい
きまーす
自分は5
から
DynamoDBを使ったシステムの運用
•DynamoDBにはBuilt-inのバックアップやレプリ
ケーションの仕組みはありません
•DynamoDBにはキャパシティをAuto Scalingする仕
組みはありません
•DynamoDBにはRegionを超えてテーブルをレプリ
ケーションする仕組みはありません
昔は、、、
でした
でした
でした
•DynamoDBにはPITRもバックアップもあります!
•DynamoDBには設定するだけでテーブルのAuto
Scalingができます!
•DynamoDBのGlobal Tableを作ればデータは自動的
にRegion間レプリケーションされます!
今は!
• Throttling発生時のアプリの挙動
• 並列処理をEvent loopで実現なら
そんなに心配ない
• Javascriptとかlibeventとか
• 並列処理がThreadベースなら要注意
• JavaのSDKとか
• 場合によってはアプリ全体に波及も
とはいえ気をつけた方がいい大事なこと
Photo from https://pxhere.com/en/photo/203547 (cropped to fit)
実際Throttling発生をテストしてみると
3回リトライして諦めるようにしても10秒間
に100+リクエストしか完了してない
 完了してない多くのリクエストは
Thread Poolの空き待ち
全リクエストがThrottledエラーになるテスト
なのでNon BlockingなClient書きました
8.924秒で全リクエストが
Throttled Exceptionで終了
全リクエストがThrottledエラーになるテスト
おわりに
• SORACOMはAmazon DynamoDBを使う理由
• 求める特性に最も合っていたから
• NoSQL Firstでの設計・開発
• 制約を知って設計すれば高可用で
スケーラブルなシステムに自然となっていく
• DynamoDBの運用面も近年急速に改善!
• もうバックアップやキャパシティ調整で
悩まない
• Throttling時の挙動にだけは気をつけて
One more thing…
• SORACOMのエンジニア
• DevOps
• 開発を中心に運用まで実施
• OpsDev
• 運用を中心に見据えて開発を実施
• Support
• お客様を中心に見据えて技術力を
発揮
We are hiring!!
D
DevOps OpsDev
業務の様子は社員紹介ブログにて
https://careers.soracom.jp/
ご興味をお持ちの方
《 株式会社ソラコムのビジョン 》
世界中のヒトとモノをつなげ
共鳴する社会へ
AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed なテレコムコアシステムを構築・運用してる話

AWS Dev Day Tokyo 2018 | Amazon DynamoDB Backed な テレコムコアシステムを構築・運用してる話