Slide 1

Slide 1 text

奥深きキャッシュの世界 2018-11-29

Slide 2

Slide 2 text

アジェンダ 1. キャッシュのおさらい 2. キャッシュの実装パターン 3. 起こりがちな問題とその対策 4. Thundering Herd問題の解決アプローチ 5. まとめ 2

Slide 3

Slide 3 text

駒原 雄祐 サイバーエージェント ソフトウェアエンジニア Media / Advertisement / Scala / golang / AWS etc... 誰? 3

Slide 4

Slide 4 text

キャッシュのおさらい 1 4

Slide 5

Slide 5 text

キャッシュってどういうもの? ◉ データソースの過負荷からの保護や低レイテンシの維 持のために ◉ データ利用者(アプリケーション等)の近くに配置された ◉ 高速な読み書きが可能な一時保存用のストア領域のこ と ◉ もしくはそのようなストア領域にデータソースから受けっ たデータを一時的に保持すること 5

Slide 6

Slide 6 text

一言で言えば データソースへのアクセス頻度を 下げる仕組み 6

Slide 7

Slide 7 text

キャッシュあれこれ ◉ メモリに対する、CPUのキャッシュ ◉ 閲覧済みデータを保持するブラウザキャッシュ ◉ スマホアプリの使用データのキャッシュ ◉ Web+DBシステムにおけるDBデータのキャッシュ 7

Slide 8

Slide 8 text

キャッシュあれこれ ◉ メモリに対する、CPUのキャッシュ ◉ 閲覧済みデータを保持するブラウザキャッシュ ◉ スマホアプリの使用データのキャッシュ ◉ Web+DBシステムにおけるDBデータのキャッシュ 8

Slide 9

Slide 9 text

“ Web+DBシステムで よくある構成 9

Slide 10

Slide 10 text

よくある構成 10 Web App Server Web App Server Web App Server RDB Load Balancer Internet Traffic

Slide 11

Slide 11 text

よくある障害 11 アクセス過多による 負荷高騰 →スローダウン Web App Server Web App Server Web App Server RDB Load Balancer Internet Traffic

Slide 12

Slide 12 text

よくある障害 12 そのままサービス全 体が障害に Web App Server Web App Server Web App Server RDB Load Balancer Internet Traffic

Slide 13

Slide 13 text

“ キャッシュを配置 13

Slide 14

Slide 14 text

キャッシュを配置 14 Web App Server Web App Server Web App Server RDB Load Balancer Internet Traffic

Slide 15

Slide 15 text

キャッシュを配置 Cache 15 RDBの前段にキャッ シュレイヤーを配置 RDBはキャッシュを すり抜けてくる問合せ だけを処理 Web App Server Web App Server Web App Server RDB Load Balancer Internet Traffic

Slide 16

Slide 16 text

キャッシュの 実装パターン 2 16

Slide 17

Slide 17 text

実装パターン ◉ Primed Cache ◉ Demand Cache ◉ (観点は異なるが) Local Caching 17

Slide 18

Slide 18 text

“ Primed Cache 18

Slide 19

Slide 19 text

Primed Cache 19 Data Source Client 使用するデータを予め キャッシュにロード Cache

Slide 20

Slide 20 text

Primed Cache 20 Data Source Client クライアントはキャッシュにの み問合せを行う Cache

Slide 21

Slide 21 text

Primed Cache 21 Data Source Client ◉ パフォーマンスのばらつきが少なく、常に高速なレスポン スが可能 ◉ 適用できるユースケースが限られる ○ 使用されるデータセットが予測可能であること ○ 予測から漏れたデータはたとえデータソースにあって もクライアントは取得できない Cache

Slide 22

Slide 22 text

“ Demand Cache 22

Slide 23

Slide 23 text

Demand Cache (1) 23 Data Source Client ②Respond data in cache キャッシュからの取得を試み、 キャッシュにあればそれを使用 Cache ①Try to read from cache

Slide 24

Slide 24 text

Demand Cache (2) 24 Data Source Client ①Try to read from cache ②Read from data source if cache missed キャッシュになかった場合、 データソースに問合せる Cache

Slide 25

Slide 25 text

③Store requested data to cache Demand Cache (2) 25 Data Source Client ①Try to read from cache ④Respond data from data source データソースからのデータを返す とともに、キャッシュにデータを保 存し、次回以降の問合せに備え る Cache ②Read from data source if cache missed

Slide 26

Slide 26 text

③Store requested data to cache Demand Cache (2) 26 Data Source Client ①Try to read from cache ④Respond data from data source ◉ 使用されるデータの予測ができないユースケースでも適用可能 ◉ ヒット率等の要因でパフォーマンスにばらつきが出る事がある Cache ②Read from data source if cache missed

Slide 27

Slide 27 text

“ Local Caching 27

Slide 28

Slide 28 text

Local Caching ◉ クライアントのオンメモリでキャッシュを持つ ○ アプリケーションプロセス内のメモリや、同一ホスト内のイ ンメモリストアなど ○ ランタイム標準のmap等に加え、各言語インメモリキャッ シュの実装も多数ある 28

Slide 29

Slide 29 text

Local Caching ◉ 主なユースケース ○ 外部キャッシュの負荷が大きく、すべての問合せをさばききれな い場合 ○ 外部キャッシュとの通信のレイテンシが許容できない場合 ○ 何らかの理由でデータソースの前段にキャッシュレイヤーを置く ことができない場合 29

Slide 30

Slide 30 text

30 Client Cache Local Caching ◉ 主なユースケース ○ 外部キャッシュの負荷が大きく、すべての問合せをさばき きれない ○ 外部キャッシュとの通信のレイテンシが許容できない ○ 何らかの理由でデータソースの前段にキャッシュレイヤー を置くことができない

Slide 31

Slide 31 text

31 Client Outside Cache Local Caching ①Try to read from on-memory cache ②Try to read from outside cache if on-memory cache missed Demand Cacheにおけるキャッシュとデータ ソース間と同じ処理を、クライアント上のメモリ とキャッシュ間でも行う On-memory Cache

Slide 32

Slide 32 text

④Store requested data to cache Demand Cache with Local Caching 32 Data Source Client ②Try to read from outside cache ③Read from data source ⑤Respond data from data source ◉ このように組み合わせて使うことも多い ○ データソースとキャッシュ間に差異が生まれやすいため、キャッ シュデータのライフサイクル管理が重要!! Outside Cache ①Try to read from on-memory cache ⑥Store requested data to on-memory cache On-memory Cache

Slide 33

Slide 33 text

起こりがちな問題と その対策 3 33

Slide 34

Slide 34 text

“ キャッシュの容量不足により Evictionが多発 34

Slide 35

Slide 35 text

Evictionとは ◉ 容量が溢れた時に、所定のルール(アルゴリズム)に従って データを追い出す機能のこと ○ アルゴリズムもいろいろある(LRU,LFU,FIFOなど) ◉ Evictionの発生が即問題なわけではない ○ が、Evictionが多発している状況だとキャッシュのヒット率に も影響してくる ◉ EvictionがOFFの状態で容量オーバーになると、それ以上 データが書き込めなくなるなどより深刻な事態に 35

Slide 36

Slide 36 text

何が起きた? ◉ 想定と実際のユースケースとの乖離 ○ 想定よりも実際のデータ量が多かった ○ 想定よりも実際のデータサイズが大きかった ◉ 想定以上にキャッシュの消費量が多かった ○ エンコーディング(どういう形式で格納されるか) ○ ブロックサイズ(どういう単位で格納されるか) ◉ モニタリングができていない ○ 容量が溢れそうなことを事前に検知すべき 36

Slide 37

Slide 37 text

対策 ◉ キャパプラちゃんとしよう ○ 想定データをある程度のデータ量実際に入れてみて 測定するのが一番確実 ◉ モニタリングちゃんとしよう ○ 容量の監視は基本中の基本 ◉ 水平分散可能なキャッシュ設計にして足りなく なったら足せるようにしよう 37

Slide 38

Slide 38 text

“ 水平分散しろって言うから 水平分散してたのに キャッシュノードを増設したら 障害になったんだけど (怒) 38

Slide 39

Slide 39 text

何が起きた? ◉ memcachedなどノード分散がクライアントに委ねられ ている場合に起こりがち ◉ キーから格納ノードの解決(分散アルゴリズム)が適 切でないことが原因 ○ 多くのキーのマッピング先が変わることで一時的に ヒット率が大幅に下がり、レイテンシの悪化やデータ ソースの過負荷につながった ◉ 増設時だけでなく、ノード障害等でキャッシュノードが 減ったときにも起こる 39

Slide 40

Slide 40 text

対策 ◉ 分散アルゴリズムにConsistent Hashingを採用する ○ キャッシュノード増減時のキー→ノードのマッピン グの変更を最小限に 40 https://ja.wikipedia.org/wiki/%E3%82%B3%E3%83%B3%E3%82%B7%E3%82% B9%E3%83%86%E3%83%B3%E3%83%88%E3%83%8F%E3%83%83%E3%8 2%B7%E3%83%A5%E6%B3%95 (参考) コンシステントハッシュ法 - Wikipedia

Slide 41

Slide 41 text

“ アプリケーションの プロセス起動直後が遅いよー 41

Slide 42

Slide 42 text

何が起きた? ◉ Local Cachingをしている場合に起こりがち ◉ リリースによるプロセス再起動や、オートスケーリン グによるスケールアウトの直後などに起こる ◉ プロセス起動直後はローカルキャッシュが空っぽの 状態 ○ すべての問合せに対して外部にデータを取りに 行ってしまう 42

Slide 43

Slide 43 text

対策 ◉ 対策1: ローカルキャッシュのウォームアップ ○ 起動後、待受ポートを開く前に頻出データを先読み ○ ただし、頻出データが分かっている前提 ◉ 対策2: ローカルキャッシュを一部Primed Cache方式 に ○ 件数が少なくて参照頻度の高いマスタデータなどは、常 に全件をローカルキャッシュに持っておくのも一つの手 43

Slide 44

Slide 44 text

“ 分散キャッシュのうち 一部のノードが遅い or/and 負荷が高い 44

Slide 45

Slide 45 text

何が起きた? ◉ Local Cachingなし+分散キャッシュの組み合わせで 起こりがち ◉ 特定のキーに対する過剰なキャッシュへの問合せ ◉ 結果的にそのキーを持っているキャッシュノードにア クセスが集中 ◉ 分散キャッシュだけでなく分散ストレージでもよくある ◉ 「ホットスポット問題」と呼ばれたりする 45

Slide 46

Slide 46 text

対策 ◉ 対策1: キーに一定の範囲内の乱数を組み合わせ、同 一キーを複数ノードに保持する ○ 参照時も乱数を生成してそのどれかを抽選することで 参照ノードがバラける ○ どの程度の数を生成するかが重要な課題に ■ 生成数が多いほどヒット率が下がる ■ 生成数が不十分だと格納先が重複して偏りが解消 されない ■ 件数の多いデータだとキャッシュ容量圧迫も ◉ 対策2: Local Cachingを導入する 46

Slide 47

Slide 47 text

“ キャッシュしてるのに 突然大量の問合せが データソースに来始めちゃった・・ 47

Slide 48

Slide 48 text

何が起きた? ◉ 存在しない(もしくは無効な)データへの問合せが大量 に発生している ○ 問合せ時にデータが見つからないため、キャッ シュに載らない ○ そのため、以降発生する同じデータへの問合せ もすべて空振り ○ 結果、データソースまで大量の問合せが到達 ◉ 頻出マスタデータの削除など ◉ 急に発生したりするので、障害に陥りやすい 48

Slide 49

Slide 49 text

対策 ◉ 「該当データがなかった」という結果をキャッシュする ○ このようなキャッシュの事を「ネガティブキャッシュ」 と呼ぶ (DNSなどでは一般的) 49

Slide 50

Slide 50 text

“ 事前の想定よりもヒット率が低い or/and 一定間隔でヒット率が 下がる 訴えてやる! 50

Slide 51

Slide 51 text

何が起きた? ◉ 非常に高頻度なアクセス状況で起きやすい ◉ キャッシュ(ローカルキャッシュ含む)で保持するデータ の有効期限切れ(Expiry)や明示的な削除がトリガ ◉ 次にキャッシュがロードされるまでの間、大量の問合 せがデータソースまで到達してしまう ◉ 低速なデータソースであるほど影響が大きい ◉ 「Thundering Herd問題」や「Cache Stampede問題」 と呼ばれたりする 51

Slide 52

Slide 52 text

図解(Demand Cacheの流れ) 52 get(A) return valueOf(A) get(A) return valueOf(A) get(A) return valueOf(A) set(A) get(A) return valueOf(A) キャッシュの生 存期間 Data Source (key=A) Cache (key=A) Client

Slide 53

Slide 53 text

図解(Demand Cacheの流れ) 53 get(A) return valueOf(A) get(A) return valueOf(A) get(A) return valueOf(A) set(A) get(A) return valueOf(A) Data Source (key=A) Cache (key=A) Client このスキマ時間に 悲劇は起こる

Slide 54

Slide 54 text

図解(高トラフィックな状態) 54 キャッシュに乗るまで全 部のget(A)がデータソー スまで・・・ Data Source (key=A) Cache (key=A) Client get(A) return valueOf(A) get(A) return valueOf(A) get(A) return valueOf(A) set(A) get(A) return valueOf(A)

Slide 55

Slide 55 text

図解(Local Cachingでも同様) 55 Outside Cache (key=A) Local Cache (key=A) Client get(A) return valueOf(A) get(A) return valueOf(A) get(A) return valueOf(A) set(A) get(A) return valueOf(A)

Slide 56

Slide 56 text

Thundering Herd問題の 解決アプローチ 4 56

Slide 57

Slide 57 text

“ Semaphore 57

Slide 58

Slide 58 text

Semaphore (セマフォ) ◉ セマフォとは ○ 対象リソースに対する現時点で利用可能な数を 管理する仕組み ○ セマフォを使うことで「同時並行可能な最大数を 超えないように制御」できる 58

Slide 59

Slide 59 text

Data Source (key=A) Cache (key=A) Client セマフォを使わない場合 59 get(A) set(A)

Slide 60

Slide 60 text

Data Source (key=A) Cache (key=A) Client セマフォ(同時利用可能数2) を使った場合 60 get(A) set(A) セマフォが0になっ たので以降の問合 せは空きが出るま で待つ 最大でも同時 利用可能数の 問合せ get(A) セマフォに空きが出 たので待っていた問 合せをする

Slide 61

Slide 61 text

参考 61 ◉ Java ○ java.util.concurrent.Semaphore https://docs.oracle.com/en/java/javase/11/docs/api/java. base/java/util/concurrent/Semaphore.html ◉ Golang ○ golang.org/x/sync/semaphore https://godoc.org/golang.org/x/sync/semaphore

Slide 62

Slide 62 text

“ Singleflight (for golang) 62

Slide 63

Slide 63 text

singleflight (for golang) 63 ◉ 同時利用可能数1のセマフォに近い ◉ 空きが出るまで待つのではなく、同時実行中の最初 の実行された結果を他の問合せ結果に使い回す ◉ Golangでは拡張ライブラリに組み込まれている ○ golang.org/x/sync/singleflight https://godoc.org/golang.org/x/sync/singleflight

Slide 64

Slide 64 text

Data Source (key=A) Cache (key=A) Client singleflight (for golang) 64 get(A) 最初の1本以外は最 初の1本の結果を待 つ データソースに 到達するのは 最初の1本だ け 待っていた他の問 合せに対しても、1 本目と同じ結果を返 す set(A)

Slide 65

Slide 65 text

“ Asynchronously refresh 65

Slide 66

Slide 66 text

Asynchronously refresh (非同期リフレッシュ) 66 ◉ キャッシュがない状態での問合せに対しては、同期 的にデータソースにアクセスしてキャッシュに載せる ◉ それ以降は、対象データに問合せがあるうちは、一 定間隔で非同期にキャッシュを更新する ◉ 一定期間以上問合せがなければキャッシュをクリア (有効期限切れ)する ◉ 参考 : Caffeine Cache (Javaでの実装) https://github.com/ben-manes/caffeine/wiki/Refresh

Slide 67

Slide 67 text

Data Source (key=A) Cache (key=A) Client Asynchronously refresh 67 get(A) set(A) get(A) キャッシュがない状 態からは同期的に 取得してキャッシュ へ キャッシュが 載ってからは 非同期に更新 問合せがある間は キャッシュが切れな い

Slide 68

Slide 68 text

まとめ 5 68

Slide 69

Slide 69 text

「キャッシュ」と 一口に言っても ◉ いろんな実装方法がある ◉ いろんな障害パターンがある ◉ それに対する対策もたくさん 69

Slide 70

Slide 70 text

最後に ◉ キャッシュ構築の「基本形」にはお決まりのパターン がある ○ 一方、思わぬところで足をすくわれるのが「キャッシュ」 という領域 ○ いざ障害になると長期化することも多く、決して甘く見 てはいけない ○ 特にWeb系の人は割と軽いノリで「キャッシュ入れよ う」となりがち?(先入観) ◉ 考慮すべき点はきちんと考慮して、頑強なキャッシュ を構築しましょう 70

Slide 71

Slide 71 text

Have a happy caching life!! 71

Slide 72

Slide 72 text

ご清聴ありがとうございましたm(_ _)m 72