Slide 1

Slide 1 text

note の Elasticsearch 更新系を支える技術 または私は如何にして心配するのを止めて 結果整合性を愛するようになったか note株式会社 露木 航平

Slide 2

Slide 2 text

note inc. 2014年から交通系ITで地理検索をやっていました。 
 2021年から現職。
 データエンジニア→検索→推薦へ。 
 
 GitHub / X / note
 露木 航平
 Kohei TSUYUKI
 バックエンドエンジニア 2

Slide 3

Slide 3 text

note inc. 今⽇やる話 2023年4⽉の Elasticsearch 勉強会にて、紹介だけにとどめた更新アーキテクチャの話をします 検索体験の前提となる、⼤量のデータをできる限り鮮度よく正確に届けるための更新の仕組みに焦点を当てます 3

Slide 4

Slide 4 text

⽬次 note inc. ● 05 …… 会社概要 ● 10 …… 検索の更新にまつわる課題 ● 15 …… 同期ズレへの対処 ● 20 …… コンバート失敗への対処 ● 23 …… 全件インデックスへの対処 ● 29 …… 2年間運⽤しての感想と反省 ● 32 …… まとめ 4

Slide 5

Slide 5 text

⽬次 note inc. ● 05 …… 会社概要 ● 10 …… 検索の更新にまつわる課題 ● 15 …… 同期ズレへの対処 ● 20 …… コンバート失敗への対処 ● 23 …… 全件インデックスへの対処 ● 29 …… 2年間運⽤しての感想と反省 ● 32 …… まとめ 5

Slide 6

Slide 6 text

note inc. 6

Slide 7

Slide 7 text

note inc. 7 事業概要
 だれもがインターネット上で自由にコンテンツを発表・販売できるメディアプラットフォーム「note」と、noteを基盤に企業の情 報発信をかんたんかつ効果的に行うための高機能プラン「note pro」を中心に事業を展開。
 
 
 noteはクリエイターが文章や画像、音声、動 画を投稿して、ユーザーがそのコンテンツを楽 しんで応援できるメディアプラットフォームで す。だれもが創作を楽しんで続けられるよう、 安心できる雰囲気や、多様性を大切にしてい ます。個人も法人も混ざり合って、好きなもの を見つけたり、おもしろい人に出会えたりする チャンスが広がっています。
 法人向け高機能プラン。多くのひとが集まる noteの街でメディアをかんたんにつくり、情報 を届けることができます。「ブランディング」「リ クルーティング」「ファンコミュニティ作り」「サブ スクリプション」など目的はさまざま。届ける仕 組みと充実したサポートで、企業がポジティブ なユーザーとつながって関係を深めるお手伝 いをします。
 法人向けサービス
 ・コンテスト
 企業とコラボレーションし、note上でクリエイ ターから作品を募集する企画を開催
 ・イベント
 note連動イベント等のため、イベントスペー ス”note place”を貸出


Slide 8

Slide 8 text

note inc. 8 あらゆるクリエイターをエンパワーメントする CtoCのメディアプラットフォーム。 2014年4⽉スタート。 年間流通総額(税込) 累計会員登録者 MAU
 公開コンテンツ数 170 億円 938 万⼈ 6,574 万 5,462 万件 * 2025年2⽉末時点の数値 * MAU‧年間流通総額は2024年11⽉期実績

Slide 9

Slide 9 text

note inc. 公開コンテンツ数 5,462 万件 参考: ⽇本の住所が約4,000万件 9

Slide 10

Slide 10 text

⽬次 note inc. ● 05 …… 会社概要 ● 10 …… 検索の更新にまつわる課題 ● 15 …… 同期ズレへの対処 ● 20 …… コンバート失敗への対処 ● 23 …… 全件インデックスへの対処 ● 29 …… 2年間運⽤しての感想と反省 ● 32 …… まとめ 10

Slide 11

Slide 11 text

note inc. 検索エンジンのインデックス同期、 どうやってますか? 11

Slide 12

Slide 12 text

note inc. 本⽂データ 読み込み noteの検索システム更新系 この図の解説は後半でします 12 Job Worker Job Queue Object Storage Updater クリエイター Application データ 更新 ジョブ 送信 メッセージ 送信 Queue Reader 更新ログ 読み込み 直列化して 書き込み Database 更新ログ 読み込み Search Engine データ 更新 データ保存 DWH ⾏動ログなどの読み込み(今回は解説しない)

Slide 13

Slide 13 text

note inc. ものすごく抽象化した、よくある構成図 検索エンジンを使う場合、以下の流れが頻出 1. RDBなどに保存されたソースとなるデータを 2. コンバータープログラムが読み込み、検索エンジン向けに加⼯し 3. 検索エンジンにインデクシングする 13 ソースとなるデータ (RDBなど) 検索エンジン (Es, Solr など) コンバーター データ読み込み データ書き込み 状態の差分をどう管理するかが課題

Slide 14

Slide 14 text

note inc. よくある構成において頻出の課題 1. データソースと検索インデックスの間に同期ズレが発⽣する 2. データの振る舞い変更により、コンバート処理が失敗する 3. 障害などを起因として、任意タイミングでの全件インデックスが必要になる この3つの課題を解決するためにやったことと、反省点をお伝えします 14

Slide 15

Slide 15 text

⽬次 note inc. ● 05 …… 会社概要 ● 10 …… 検索の更新にまつわる課題 ● 15 …… 同期ズレへの対処 ● 20 …… コンバート失敗への対処 ● 23 …… 全件インデックスへの対処 ● 29 …… 2年間運⽤しての感想と反省 ● 32 …… まとめ 15

Slide 16

Slide 16 text

note inc. 同期ズレを完全になくすことは不可能 ● 前提として、データソースと検索インデックスの同期ズレを完全になくすことは不可能 ○ RDBの最新状態と、検索インデックスの状態には必ず差分が⽣じる ○ どれだけ遅延しても良いかを合意することが⼤事 ■ noteの本番環境の場合は最⼤5分程度の遅延を許容している ● 同期を⾏う際にとりうるオプション ○ 定期的に、全件に対して検索インデックス更新を⾛らせる ○ データ更新時に、アプリケーションが検索エンジンの更新APIを直接実⾏する ○ ジョブキューに更新ログを送信し、⼀定時間ごとに直列化した更新内容をまとめて反映する ■ noteではこの⽅式を採⽤ 16

Slide 17

Slide 17 text

note inc. 定期的な全件インデックス更新のPros/Cons ● Pros ○ 更新時点での正しいデータが保証できる ■ 考えることが減る ○ 静 的なデータ配 信になるのでコンテナと 相性が良い ● Cons ○ ⼤量データでは更新に数⽇かかる ○ 最新データへの追随に時間がかかる ⼩規模データや、ほぼ静的なデータで利⽤したい 地理検索ではよく利⽤するパターン 17 検索エンジン App 定期 更新 RDB データ読み込み

Slide 18

Slide 18 text

note inc. アプリケーションが直接更新する際のPros/Cons ● Pros ○ データソースと 検 索 インデックスの 同期ズレを短くできる ○ 構築するものが少なくて済む ○ 挙動が理解しやすい ● Cons ○ ⼀度に⼤量のデータ更新があった場合の 挙動が保証しにくい ○ 全件更新後の最新化と相性が悪い ○ 耐障害性が低い 更新頻度が低めのデータで利⽤したい 物語投稿サイトTalesでは定期更新+この⽅式を採⽤ 18 検索エンジン App 購⼊ スキ マガジン 追加 スキ 購⼊ スキ 記事A 記事B 記事C 更新 イベントごとに送って うまく更新 できるのか…?

Slide 19

Slide 19 text

note inc. ジョブキュー+定期更新のPros/Cons ● Pros ○ ⼤量データ更新時でもデバッグ可能 ○ 任意タイミングでの最新化が可能 ○ スケーラブルで耐障害性が⾼い ● Cons ○ データソースと 検 索 インデックスの 同期ズレは⼤きくなる ○ 構築するものが増える ○ 挙動が理解しにくい ⼤量データに頻繁な更新が⼊る場合で利⽤したい 5,000万件超のコンテンツが常にupdateされ続ける noteではこの⽅式で堅牢に運⽤できる 19 検索エンジン App 購⼊ スキ マガジン 追加 スキ 購⼊ スキ 記事A 記事B 記事C Queue メッセージ送信 Object Storage 直列化して保存 Converter 更新

Slide 20

Slide 20 text

⽬次 note inc. ● 05 …… 会社概要 ● 10 …… 検索の更新にまつわる課題 ● 15 …… 同期ズレへの対処 ● 20 …… コンバート失敗への対処 ● 23 …… 全件インデックスへの対処 ● 29 …… 2年間運⽤しての感想と反省 ● 32 …… まとめ 20

Slide 21

Slide 21 text

note inc. データの振る舞いはときどき変わる ● サービスの仕様変更などでデータの振る舞いが 変化すると、コンバートが失敗する場合がある ○ 開発者全員が検索エンジンを知っている わけではないので、変更が抜けがち ● アプリケーションから直接更新する アーキテクチャでコンバート失敗すると、 復旧が難しい ○ ⼤規模データで採⽤しにくい理由の⼀つ note では更新メッセージを Protocol Buffers で定義 変換を単体テストで担保することで失敗を抑⽌ # code def self.message(action_type:, event_at:, ar_obj:) body_hash = case action_type # 略 when :update_status { 'update_note_status_body' => { 'id' => ar_obj.id, 'status' => ar_obj.status.upcase } } # 略 msg = Messages::UpdateLog.new(body_hash) Messages::UpdateLog.encode_json(msg) end # test context 'statusが更新(update)されたとき' do let!(:action_type) { :update_status } it 'メッセージの形式が正しいこと' do msg = JSON.parse(subject) expect(body['id']).to eq ar_obj.id expect(body['status']).to eq ar_obj.status.upcase end end 21

Slide 22

Slide 22 text

note inc. Protocol Buffers を経由する理由 ● 型が保証でき、汎⽤的な形式にシリアライズできるなら何でも良かった ○ Ruby だけでは型が保証できないので、Rubyの外からガードレールを作りたかった ● できるだけ⾔語中⽴にしたかった ○ Ruby on Railsで完結しなかったシステムなので、複数⾔語でメッセージを同じように扱いたかった ➡ 採⽤から2年、更新メッセージ変換関係のエラーは未発⽣ 22

Slide 23

Slide 23 text

⽬次 note inc. ● 05 …… 会社概要 ● 10 …… 検索の更新にまつわる課題 ● 15 …… 同期ズレへの対処 ● 20 …… コンバート失敗への対処 ● 23 …… 全件インデックスへの対処 ● 29 …… 2年間運⽤しての感想と反省 ● 32 …… まとめ 23

Slide 24

Slide 24 text

note inc. 要件によって運⽤難易度は⼤きく変わる 全件インデックスをした後、最新のデータに追随する必要がある 以下の3つの質問への答え次第で、運⽤難易度は⼤きく変わる ● データを全件インデックスするのにどれくらい時間がかかるか? ○ 1営業⽇以上必要であれば、1回の全件インデックスが⼤仕事になる ● 最新性と情報の正しさはどれくらい重要か? ○ 要求が厳しければ厳しいほど、運⽤難易度は⾼くなる ● 更新履歴の取得は容易か? ○ たとえば毎秒100更新あっても、シーケンシャルな取得が容易なら再構成できる 24

Slide 25

Slide 25 text

note inc. note の場合はどうだったか ● データを全件インデックスするのにどれくらい時間がかかるか : ⼤仕事 ○ 記事 ……… 2⽇ ○ それ以外 … 最⼤2時間 ● 最新性と情報の正しさはどれくらい重要か : 厳しくはない ○ 5分程度の遅延は許容可能 ○ 情報の正しさについては、間違っていても指摘ベースで修正すれば基本的にはOK ● 更新履歴の取得は容易か : 難しい ○ 更新履歴を簡単にクエリできるシステムが存在しない ➡ 2⽇分の更新履歴を取り扱うシステムが必要 25

Slide 26

Slide 26 text

note inc. 2⽇分の更新履歴を反映する技術(1/3) コンテキスト図 26 本⽂データ 読み込み 26 Job Worker Job Queue Object Storage Updater クリエイター Application データ 更新 ジョブ 送信 メッセージ 送信 Queue Reader 更新ログ 読み込み 直列化して 書き込み Database 更新ログ 読み込み Search Engine データ 更新 データ保存 DWH ⾏動ログなどの読み込み(今回は解説しない)

Slide 27

Slide 27 text

note inc. 2⽇分の更新履歴を反映する技術(2/3) S3/更新ログの設計 27 ● s3:///logs//YYYY-MM-DDTHH:mm:ss.nsZ.log の形式で保存 ○ タイムスタンプ順にしておくことで、s3 ls を使えば時系列順の取得が容易 ● 中⾝は更新ログの Protocol Buffers から JSON シリアライズした、1⾏1イベントの jsonl ○ 1⾏ごとに読み込んでいけば良く、更新処理プログラムは省メモリで動作する ● 更新ログはオペミスで複数回最新化処理を施してしまっても良いように定義 {"Index":"USERS","EventAt":"2025-04-14T01:08:39.068362808Z","updateIntBody":{"id":653,"Field":"following_count","value":1}} {"Index":"USERS","EventAt":"2025-04-14T01:08:39.069777626Z","updateIntBody":{"id":585,"Field":"follower_count","value":1}} {"Index":"USERS","EventAt":"2025-04-14T01:09:41.856327539Z","updateIntBody":{"id":653,"Field":"following_count","value":2}} {"Index":"USERS","EventAt":"2025-04-14T01:09:41.857213623Z","updateIntBody":{"id":644,"Field":"follower_count","value":5}}

Slide 28

Slide 28 text

note inc. 2⽇分の更新履歴を反映する技術(3/3) 反映状況の管理 28 ● s3:///last_update/.last_updated_at.json に更新状況の状態を保存している ● updater はファイルから最終更新⽇時を読み取り、s3 ls で次に更新するファイルを知る ● 疎結合を指向していたが、素直にRDB管理にしておけばよかった ● 更新失敗がもっと起こるかと思っていたが、データ形式由来での更新失敗が⼀度も起こっていないので 備えが有効に機能しているかは不明 { "filename": "logs/users/2025-04-18T02:10:01.306546641Z.log", // 最後に処理したファイル名 "status": true, // 更新の成否 "reason": "" // 更新失敗していた場合の理由 }

Slide 29

Slide 29 text

⽬次 note inc. ● 05 …… 会社概要 ● 10 …… 検索の更新にまつわる課題 ● 15 …… 同期ズレへの対処 ● 20 …… コンバート失敗への対処 ● 23 …… 全件インデックスへの対処 ● 29 …… 2年間運⽤しての感想と反省 ● 32 …… まとめ 29

Slide 30

Slide 30 text

note inc. メッセージ指向で全てを賄うのは難しい ● 最⼤2⽇分の更新を適⽤する必要があるので、メッセージ指向⾃体には必然性がある ○ 運⽤開始から 2,000 万件以上コンテンツが増加しており、全件インデックスも複数回実⾏しているが 全件インデックスでの障害は発⽣していない ● ただし、ときおり更新ログが送られておらず、更新が抜ける場合がある ○ noteは10年以上運⽤された、実装上も運⽤上も⾮常に複雑な Ruby on Rails アプリケーション ■ ログが抜ける原因も単純ではない ○ どこかの分岐で送信処理が抜けたり、実装が忘れられるのを⾒越して、 あとから辻褄を合わせる機構のほうが重要だった 30

Slide 31

Slide 31 text

note inc. 素直に全部Rubyで書いておけば良かった ● 疎結合や型安全を実現したく、当時所属していたデータ基盤チームがGoを利⽤していたことから Goで実装したが、素直に全部Rubyで書くほうが経済的だった ○ 他の開発者が参⼊しやすい ○ 開発環境やローカル環境でのセットアップが楽になる ○ 今は Packwerk が note 内にかなり普及しており、ある程度疎結合にできる ○ 実装当時はRails→Snowflakeのルートがなかったことからシステムを分ける必然性があったが、 Reverse ETL のフローが確⽴したことでその制約もなくなった ○ 本体の Ruby on Rails より堅牢であってもオーバーエンジニアリングなので、無理しなくて良かった ■ ⾔語中⽴なアーキテクチャなので、⼯数さえかければ移⾏可能なのは救い ● 物語投稿サイトTales はこの反省を⽣かして、バックエンドと⾔語を揃えて実装している 31

Slide 32

Slide 32 text

⽬次 note inc. ● 05 …… 会社概要 ● 09 …… Introduction: 検索の更新にまつわる課題 ● 14 …… 同期ズレへの対処 ● 19 …… コンバート失敗への対処 ● 22 …… 全件インデックスへの対処 ● 29 …… 2年間運⽤しての感想と反省 ● 32 …… まとめ 32

Slide 33

Slide 33 text

note inc. まとめ ● データソースと検索インデックスの同期ズレを完全になくすことはできない ○ どれくらい遅延と正しさを担保するのか、合意を得る必要がある ○ この制約次第で難易度は⼤きく変わるが、noteは⽐較的緩め ● 全件インデックスをした後最新のデータに追随する必要があるので、更新履歴を保存するシステムを実装 ○ データの振る舞い変更に対して頑強にするべく、Protocol Buffers を⽤いて Ruby に型を強制 ● スケーラビリティと耐障害性については⽬論⾒通り実現できたが、2年間運⽤すると課題も⾒えてくる ○ メッセージ指向で全てを賄うのは難しい ■ 結果整合性を第⼀に構築すればよかった ○ note本体より堅牢である意味が薄いので、素直に全部Rubyで書いておけばよかった ■ 時間経過で制約が変化するので、⾔語中⽴にしておくことは重要 33

Slide 34

Slide 34 text

Thank you 
 !

Slide 35

Slide 35 text

note inc. 35 の公式SNSはこちら。 note フォローお待ちしています!