Slide 1

Slide 1 text

Rabbit🐰の キャッシュ・バッチ化のしくみ @syusui_s Rabbitのキャッシュ・バッチ化の仕組み © 2023 by Syusui Moyatani is licensed under CC BY-SA 4.0

Slide 2

Slide 2 text

自己紹介 しゅうすい @syusui_s Webエンジニアです。 ● Scrapbox書いてます(仕様や使い方) ● Nostrクライアントを作っています 0:10 +30

Slide 3

Slide 3 text

🐰Rabbit TweetDeck風クライアント https://syusui-s.github.io/rabbit/ ● 軽快な動作 ● マルチカラム ● ショートカット 0:40 +20

Slide 4

Slide 4 text

投稿の表示が大変 外部 サーバの 画像 プロフィール (kind0) リポスト数 (kind 6) リアクション数 (kind 7) 色々な関連情報が必要になる 1:00 +20

Slide 5

Slide 5 text

毎回取得すると遅い 表示のたびに取得するのは遅くて通信量も多い ● プロフィール等の表示が遅くなる ● スマホの通信量を消費してしまう ● サーバの制限に引っかかる ○ 一部リレーは購読数を制限している 1:20 +20

Slide 6

Slide 6 text

通信量・回数を減らす仕組みが欲しい Rabbitでは以下の手法を取り入れています ● キャッシュ ○ データを保存し、しばらくの間再利用する ● バッチ化 ○ 複数のイベントに対する購読をまとめて行う 1:40 +10

Slide 7

Slide 7 text

キャッシュ 1:50 +5

Slide 8

Slide 8 text

キャッシュ データを保存し、しばらく再利用する仕組みのこと ● プロフィール ○ あまり変更されないので、使いまわしても問題が少ない ○ 保存すればアイコンと名前がすぐに見られる ※他にもキャッシュできるものはあるが時間の都合で割愛 フォロー一覧、リポスト数、リポストされた投稿など 1:55 +15

Slide 9

Slide 9 text

TanStack Query 通信向けのキャッシュライブラリ 特徴 ● SWR戦略に基づく ● SolidJSに対応 1:15 +15

Slide 10

Slide 10 text

単純なキャッシュの仕組み 保存したデータはいつかは古くなり、再取得が必要 保存期間を決めておき、時間が経ったら破棄 有効 (データは新しいはず) 破棄 破棄までの期限 保存 1:30 +30 破棄すると、読み込みでユーザを待たせてしまう

Slide 11

Slide 11 text

SWR戦略 (stale-while-revalidate) 期限が切れてもすぐに削除せず一旦持ってるデータを表示 その裏で新しいデータを取得し、画面に表示する 有効 (データは新しいはず) 期限切れ (古いはずだが持っておく) 破棄 有効期限 破棄期限 保存 読み込みでユーザを待たせない 2:00 +30

Slide 12

Slide 12 text

通信の流れ クライアント リレー キャッシュ機構 Aさんのプロフィールがほしい Aさんのプロフィールです Aさんのプロフィールがほしい Aさんのプロフィールです 一旦今あるAさんのデータを返します Aさんのプロフィールがほしい Aさんのは保存してたのがあるよ Aさんのプロフィールがほしい stale Aさんのプロフィールがほしい 新しいAさんのプロフィールです 有効 期限切れ Aさんのプロフィールです 3:00 +60

Slide 13

Slide 13 text

TanStack Queryを使う上での課題 Promise前提 nostr-toolsのコールバックスタイルから変換が必要 4:00 +10

Slide 14

Slide 14 text

イベントのPromise化 まずは単純に一件だけのキャッシュに対応 プロフィール1件、リポストされたイベント1件 など ※nostr-toolsを使っています 4:10 +20

Slide 15

Slide 15 text

複数イベントの場合 更新があるたびにキャッシュを更新 割とゴリ押しな実装… 4:30 +30

Slide 16

Slide 16 text

バッチ化 5:00

Slide 17

Slide 17 text

単純に購読すると クライアント リレー Bさんのプロフィールがほしい Cさんのプロフィールがほしい Aさんのプロフィールがほしい Dさんのプロフィールがほしい Eさんのプロフィールがほしい そんなにリクエストしないで! (イベントEの取得失敗) 5:00 +10

Slide 18

Slide 18 text

動機 一部のリレーは購読数の制限がある (おそらく負荷軽減のため) 制限回避のために 購読数を少なく保ちたい 5:10 +10

Slide 19

Slide 19 text

バッチ化 複数の購読(REQ)を一つの購読にまとめる仕組み kinds: [0] authors: ["8234…e6a2"] kinds: [0] authors: ["3bf0…459d"] kinds: [0] authors: [ "8234…e6a2", "3bf0…459d" ] プロフィールの問い合わせを統合する例 5:20 +20

Slide 20

Slide 20 text

バッチ化 クライアント リレー バッチ化機構 Aさんのプロフィールがほしい Bさんのプロフィールがほしい Cさんのプロフィールがほしい まとめてA,B,Cのプロフィールがほしい Aさんのプロフィールです 3秒 Aさんのプロフィールです Bさんのプロフィールです Cさんのプロフィールです 3つのリクエストを1つにまとめられた Bさんのプロフィールです Cさんのプロフィールです レスポンスの振り分け 5:40 +60

Slide 21

Slide 21 text

レスポンスの振り分け kind: 0 pubkey: "8234…e6a2" content: {"name":"jack"} kind: 0 pubkey: "3bf0…459d" content: {"name":"fiatjaf"} 公開鍵 resolver "8234…e6a2" () => { … } "3bf0…459d" () => { … } Promise Promise ※new Promise((resolve) => …) の resolve関数をMapに保存しておく 公開鍵等のIDに基づいてPromiseをfullfillする 6:40 +60

Slide 22

Slide 22 text

詳しく知りたい人は https://gist.github.com/syusui-s/745a2b507744 073f9009c94222ad78fc 7:40 +10

Slide 23

Slide 23 text

最後に キャッシュとバッチ化で効率よく通信できる 今後の展望 - 楽観的mutation - プロフィール変更、リポスト、リアクションを即座にキャッシュに反映する - タイムラインの情報を拾ってキャッシュに乗せておく - 投機的キャッシュ - 投稿を投機的にキャッシュに乗せて、リポスト等の表示を効率化する - InfiniteQueryによるタイムライン自体のキャッシュ - いつかこの辺に対応したクライアントライブラリを作りたい…

Slide 24

Slide 24 text

おまけ: 発表で話せなかったところ

Slide 25

Slide 25 text

プロフィールのキャッシュ時間の工夫 staleTime: 5分 名前やアイコンがすこし 古くてもあまり問題ない cacheTime: 1日 よくTLに居る人を キャッシュするように 3:00 +30

Slide 26

Slide 26 text

プロフィールのキャッシュ時間の工夫 staleTime: 4時間 投稿は編集できないので 期限切れにしなくてOK cacheTime: 4時間 時間が経ったらリポストは されないので4時間程度で ■■さんがリポスト ●●さんがリポスト