Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
GoのGenericsを使った効率的なキャッシュの実装 / Effective Generic...
Search
motoki317
December 23, 2023
Programming
1.6k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
GoのGenericsを使った効率的なキャッシュの実装 / Effective Generic Cache in Golang
motoki317
December 23, 2023
More Decks by motoki317
See All by motoki317
はじめてのコンテナ / Container 101 in 3 days
motoki317
0
150
Other Decks in Programming
See All in Programming
torikago - Ruby::Boxで照らすモジュラモノリスの実行境界
se4weed
1
350
AI時代に設計が 最大の生産性レバーになる 意図駆動開発とデータを消さない設計|Don't Delete Your Data or Your Intent — Design as the Deepest Lever in the AI Era
tomohisa
1
730
夏だ!祭りだ!祭りとはドメインモデリングでは?
ryugen04
0
190
S3 を使うアプリケーションをローカル完結で動かすことに全力を注いでみた / Running S3 Apps Offline
contour_gara
0
390
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
240
『コードを書く以外の』エンジニアリング〜課金基盤移行プロジェクト推進のためのTips4選
yuriko1211
0
590
JAWS-UG横浜 #102 AWSサ終供養LT会 成仏できない AWS サービスたち 〜本日、三体供養します〜
maroon1st
0
340
壊れたパーサから始める関数型設計と構成的なパーサ #fp_matsuri
raiga0310
2
450
メールのエイリアス機能を履き違えない
isshinfunada
0
230
Laravelで学ぶ Webアプリケーションチューニング入門/web_application_tuning_101
hanhan1978
4
1.7k
Apache Hive: そしてCloud Native Lakehouseへ
okumin
1
210
Cloudflare is Agents
chimame
0
150
Featured
See All Featured
How GitHub (no longer) Works
holman
316
150k
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
680
Chasing Engaging Ingredients in Design
codingconduct
0
260
Max Prin - Stacking Signals: How International SEO Comes Together (And Falls Apart)
techseoconnect
PRO
0
360
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
0
570
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
470
SEO for Brand Visibility & Recognition
aleyda
0
4.7k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
210
VelocityConf: Rendering Performance Case Studies
addyosmani
333
25k
Fireside Chat
paigeccino
42
4k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
480
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
240
Transcript
Genericsを使った 効率的なキャッシュの実装 @toki
@toki traP所属 一般学生Gopher • ISUCON11本選作問 • ISUCON10, 12, 13 準優勝
近況 • ISUCON13でNaruseJunにまた負けた • traP部内向けPaaS「NeoShowcase」を作った motoki317 motoki1_
キャッシュとその罠 Genericなライブラリ 制作裏話 おまけ: 即時反映チェック 目次
(インメモリ)キャッシュの使い所 サーバーが高負荷時 データベース等へのクエリ数を減らす レスポンスタイムを短く 毎秒5000兆リクエストが送られて 固まるサーバーの図 [1] [1] 元ネタ https://trap.jp/post/2072/
キャッシュの罠 よくあるキャッシュ 1. キャッシュから取得 2. キャッシュに無ければ取得 3. キャッシュに保存 不具合、見抜けますか? よくありそうな感じの
Webサーバー内キャッシュ
例題の不具合 (1/3) 保存キーが違う! キーが複雑 キーの定義が複数箇所 → 実装したと思ったら 一切動いていなかった!
例題の不具合 (2/3) map同時読み書き sync.Map を使うべき → よくある感じの並行 Webサーバーだとpanicする
例題の不具合 (3/3) 更新ロックが無い 大量にリクエストが 来たら? fetchData 関数が 遅かったら? → 更新時に過負荷に
(Cache Stampede Problem)
キャッシュの色々な罠 罠1: キーが違う 罠2: 並行処理対応 罠3: 更新時の負荷 Genericsで 解決しよう (今日の話題)
キャッシュとその罠 Genericなライブラリ 制作裏話 おまけ: 即時反映チェック 目次
GoのGenerics (since 1.18, 2022-03-15) Genericsが実装されて久しい Goのサポート対象は最新 2 minor versions 現在
(as of 2023-12-23) は 1.20, 1.21 Goは後方互換性が強い → Genericsは既に問題なく導入できる [2] https://github.com/tottie000/GopherIllustrations 嬉しそうな Gohperくん [2]
ライブラリを作った キャッシュライブラリ “sc” [3] を作った Genericsを使用し、型安全 前述の3つの課題を解決 名前が何の略かは知らない すごい(sugoi)キャッシュ(cache) [3]
https://github.com/motoki317/sc
“sc” での書き方 sc ではどう書く? 1. 事前にデータ取得関数を定義 2. キャッシュから透過的に取得 3. データを返す
書き方を強制することで 課題を解決
ライブラリの恩恵 (1/3) データ取得関数を キャッシュへ事前に渡す キーのみが取得関数へ渡る Closureによる意図しない 変数のキャプチャを防ぐ → 値の更新時に キーを間違えようが無い!
ライブラリの恩恵 (2/3) 取得と更新時の ロックは自動的に行う 値は自動でセット → 難しい並行処理対応を 自分で行う必要が無い!
ライブラリの恩恵 (3/3) 重複呼び出しの抑制 非同期更新もできる stale-while-revalidate と似た処理 遅い更新処理でも大丈夫 → 値の更新時に負荷がかからない
キャッシュとその罠 Genericなライブラリ 制作裏話 おまけ: 即時反映チェック 目次
ライブラリ制作のよもやま話 Genericsの使い方 ライブラリインターフェイスの設計 キャッシュのお掃除方法
Genericsの使い方 Cache型にtype parameter キーにstructも指定可能 空のstruct{} = 引数が無いことを 擬似的に表現可能 キャッシュインスタンス生成 New()
のシグネチャ 値を取得する Get() のシグネチャ
インターフェイス設計 (1/2) Get() はあれど Set() は無い Get() に動的に更新関数を渡せない → 更新関数でキーや処理を
取り違えないための意図的な設計
インターフェイス設計 (2/2) 設定にFunctional Options Pattern 破壊的な変更を避け、 機能追加を行いやすい Genericな設定を取りたい場合は 相性が悪いかも 出来なくは無いが、類推が弱い
[4] command center: Self-referential functions and the design of options, https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html Functional Options Pattern [4]
キャッシュのお掃除 Expire した値を捨てるワーカー GC用に、値の参照を切るために定期実行 runtime.Finalizer により、キャッシュ自体の破棄で自動で停止 細かい話はコードを参照
キャッシュとその罠 Genericなライブラリ 制作裏話 おまけ: 即時反映チェック 目次 ちょっと難しいです
即時反映チェック ISUCONベンチマーカーは整合性チェックが厳しい だいたいのエンドポイントでは「ベンチマーカーは データの更新が即時反映されていることを期待して 検証を行います。」[5] ちょっとでもキャッシュするとバレてFailに [5] ISUCON12本選 当日マニュアル, https://gist.github.com/shirai-suguru/770d30d16688a07ba78e0a188cd99f9f
即時反映チェックを倒す方法 クエリ数は減らしたいが、即時反映もしたい 「singleflightを使えばいいじゃん」 [5] ISUCON12本選 当日マニュアル, https://gist.github.com/shirai-suguru/770d30d16688a07ba78e0a188cd99f9f → 実はダメです
singleflight [6] では ダメな理由 [6] https://pkg.go.dev/golang.org/x/sync/singleflight 取得中に更新リクエストが 来ると詰む
singleflight [6] では ダメな理由 [6] https://pkg.go.dev/golang.org/x/sync/singleflight 取得中に更新リクエストが 来ると詰む
singleflight [6] では ダメな理由 [6] https://pkg.go.dev/golang.org/x/sync/singleflight 取得中に更新リクエストが 来ると詰む ベンチマーカーはこの時点で 次の取得リクエスト(B)が
更新されていることを期待
singleflight [6] では ダメな理由 [6] https://pkg.go.dev/golang.org/x/sync/singleflight 取得中に更新リクエストが 来ると詰む ベンチマーカーはこの時点で 次の取得リクエスト(B)が
更新されていることを期待 しかしBは前の取得結果を 利用してしまう! Bで古い結果が返る
即時反映チェックを倒す方法 [7] x/sync/singleflight の注意点とゼロタイムキャッシュ #Go – Qiita, https://qiita.com/methane/items/27ccaee5b989fb5fca72 Zero Time
Cache [7] ベンチマーカーに気づかれない範囲でキャッシュ 処理の圧縮ともいう
ttl = 0 かつ sc.EnableStrictCoalescing() で利用可能 Zero Time Cache [7]
[7] https://qiita.com/methane/items/27ccaee5b989fb5fca72
ttl = 0 かつ sc.EnableStrictCoalescing() で利用可能 Zero Time Cache [7]
[7] https://qiita.com/methane/items/27ccaee5b989fb5fca72
ttl = 0 かつ sc.EnableStrictCoalescing() で利用可能 Zero Time Cache [7]
[7] https://qiita.com/methane/items/27ccaee5b989fb5fca72
ttl = 0 かつ sc.EnableStrictCoalescing() で利用可能 Zero Time Cache [7]
BのGet開始前の結果 BにとってStale [7] https://qiita.com/methane/items/27ccaee5b989fb5fca72
ttl = 0 かつ sc.EnableStrictCoalescing() で利用可能 Zero Time Cache [7]
BのGet開始前の結果 BにとってStale Aが返り次第、 ZTCは次のSELECTを開始 [7] https://qiita.com/methane/items/27ccaee5b989fb5fca72
ttl = 0 かつ sc.EnableStrictCoalescing() で利用可能 Zero Time Cache [7]
BのGet開始前の結果 BにとってStale Aが返り次第、 ZTCは次のSELECTを開始 BのGet開始後の結果 BにとってFresh Bで更新後の結果が返る [7] https://qiita.com/methane/items/27ccaee5b989fb5fca72
ttl = 0 かつ sc.EnableStrictCoalescing() で利用可能 Zero Time Cache [7]
BのGet開始前の結果 BにとってStale Aが返り次第、 ZTCは次のSELECTを開始 BのGet開始後の結果 BにとってFresh Bで更新後の結果が返る ポイント: 「即時反映」は 0秒以上前の結果 を許可しない = リクエスト前開始の SELECT結果を利用できない [7] https://qiita.com/methane/items/27ccaee5b989fb5fca72
即時更新チェックを倒す別の方法 データの更新箇所が全て分かっているとき 可能な限りキャッシュしながら 更新時に明示的にInvalidate → sc で利用可能!
ttl = 任意 更新時に Forget() で利用可能 明示的なInvalidate
ttl = 任意 更新時に Forget() で利用可能 明示的なInvalidate
ttl = 任意 更新時に Forget() で利用可能 明示的なInvalidate
ttl = 任意 更新時に Forget() で利用可能 明示的なInvalidate 更新時に Forget() を呼ぶことで
以降の Get() は値を再利用せず 直ちにSELECTが飛ぶ
ttl = 任意 更新時に Forget() で利用可能 Illust: https://github.com/tottie000/GopherIllustrations 明示的なInvalidate 更新時に
Forget() を呼ぶことで 以降の Get() は値を再利用せず 直ちにSELECTが飛ぶ 前回のSELECTを無視して 直ちにSELECTが飛ぶ
ttl = 任意 更新時に Forget() で利用可能 Illust: https://github.com/tottie000/GopherIllustrations 明示的なInvalidate 更新時に
Forget() を呼ぶことで 以降の Get() は値を再利用せず 直ちにSELECTが飛ぶ 前回のSELECTを無視して 直ちにSELECTが飛ぶ Bで更新後の結果が返る
ttl = 任意 更新時に Forget() で利用可能 Illust: https://github.com/tottie000/GopherIllustrations 明示的なInvalidate 更新時に
Forget() を呼ぶことで 以降の Get() は値を再利用せず 直ちにSELECTが飛ぶ 前回のSELECTを無視して 直ちにSELECTが飛ぶ Bで更新後の結果が返る ポイント: 「即時反映」は 0秒以上前の結果 を許可しない = リクエスト前開始の SELECT結果を利用できない
まとめ
(インメモリ)キャッシュは銀の弾丸ではない TTLの設定やinvalidate処理はユーザーが行う ここの不具合はライブラリ側では防げない インメモリキャッシュは サーバー分散時にinvalidate処理が辛い groupcache [8] 等の分散キャッシュを用いる Redis, memcached
等でも可 [8] https://github.com/golang/groupcache
まとめ Generic(型安全)でバグを埋めにくい キャッシュライブラリ “sc” を作った 対ISUCONベンチマーカー即時反映チェック対応 github.com/motoki317/sc このスライドのリンク