Upgrade to Pro — share decks privately, control downloads, hide ads and more …

数十億のレコードを持つ 5年目サービスの 設計と障害解決

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for KNR KNR
October 27, 2023
8.8k

数十億のレコードを持つ 5年目サービスの 設計と障害解決

Kaigi on Rails 2023

Avatar for KNR

KNR

October 27, 2023
Tweet

Transcript

  1. 8 講談社×ピクシブによる少女・女性マンガアプリ 雑誌発売と同時に最新話を配信&オリジナル作品も 多数 モバイルアプリがメインでAndroid / iOS • AWS •

    Ruby on Rails(6.1) • MySQL(5.7) • Redis(6.2) https://palcy.jp/      もしくはアプリストアで 「パルシィ」と検索      
  2. • Active Model Serializerの利用
 ◦ ほぼJSONでレスポンスを返すWebAPIが主で使いやすい
 ◦ expire期間も指定できる
 ◦ 多少のロジックを実装できる


    ▪ 故に内部でDBアクセスする実装を行えばN+1が起こる
 キャッシュを使う 15 1.同一のデータを返すもの
  3. キャッシュを使う 16 1.同一のデータを返すもの class ComicV2Serializer < ActiveModel::Serializer cache expires_in: 1.hour,

    except: [:is_liked, :episode_count …] attributes :id, :title, :ruby, :author, :author_image_url … has_many :tags def author_image_url object.author_image_url.nil? ? 'https://example.com/default.png' : object.author_image_url end … end Serializer側の実装例
  4. キャッシュを使う 17 Railsのアップデート中に... 障害が発生 いくつかのAPIで500エラーが発生 Rendering 500 with exception: no

    implicit conversion of String into Hash 5.1から5.2にアップデートした際発生 キャッシュからの読み込みに失敗 キャッシュキーを変更して回避
  5. 不要なデータは使わない 30 特定ユーザーのスロークエリ • 原因はお気に入りの機能 ◦ APIでユーザーお気に入り全件と閲覧情報をJOINさせていたこと が原因 ◦ 多く読んでいる&多くお気に入りに入れることが原因だったのでヘ

    ビーユーザーに多かった ◦ 0時に多く発生していたのは0時に情報が更新されるため、この時 間にアクセスするユーザーが多かったから 作品メタ情報
  6. テーブルを適切な形に変更する 38 2023年夏 - 障害発生 障害が発生 Rendering 500 with exception:

    2147483648 is out of range for ActiveModel::Type::Integer with limit 4 bytes
  7. テーブルを適切な形に変更する 39 2023年夏 - 障害発生 障害が発生 Rendering 500 with exception:

    2147483648 is out of range for ActiveModel::Type::Integer with limit 4 bytes >ActiveModel::Type::Integer.new.send(:max_value) => 2147483648
  8. テーブルを適切な形に変更する 40 障害となった作品閲覧に対するミッション履歴 • create_table "user_comic_browse_mission_histories" do |t| t.bigint "user_id",

    null: false t.bigint "mission_id", null: false t.integer "user_comic_browse_history_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["user_id", "mission_id"] end # 作品閲覧に対して達成されたミッションの履歴
  9. キャッシュを使う 41 作品の閲覧履歴管理テーブル • create_table "user_comic_browse_histories" do |t| t.bigint "user_id",

    null: false t.bigint "comic_id", null: false t.datetime "browse_at", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false t.index ["user_id", "comic_id"] end https://railsguides.jp/active_record_basics.html
  10. テーブルを適切な形に変更する 42 BIGINTの対応漏れ(一次対応) • ALTER TABLEは可能か? ◦ user_comic_browse_mission_histories自体も数十億をゆう に超える ◦

    時間が全く予測できない ◦ int型→bigint型への変更なのでオンラインDDLは難しい • 障害対応のため一旦機能自体を止めることになった
  11. テーブルを適切な形に変更する 47 BIGINTの対応漏れ(恒久対応&他テーブルへの対応) class CreateUserComicBrowseMissionBigintHistories < ActiveRecord::Migration[6.1] def change create_table

    :user_comic_browse_mission_bigint_histories do |t| # テーブル名を変更 t.integer :user_id, null: false t.integer :mission_id, null: false t.bigint :user_comic_browse_history_id, null: false # bigint型に変更 t.timestamp end end end # 下線部分が差分
  12. class TargetTable < ApplicationRecord after_commit :duplicate_bigint_table, on: :create def duplicate_bigint_table

    TargetBigintTable.create!(self.attributes) end end 49 モデル側の実装
  13. class TargetTableMoveTask LIMIT = 10_000 def execute end_id = TargetTable.minimum(:id)

    start_id = end_id - LIMIT while end_id > 0 target_table = TargetTable.where(id: start_id..end_id) records = target_table.map do |record| TargetBigintTable.new(record.attributes) end TargetBigintTable.import!(records, on_duplicate_key_update: update_attrs) end_id = start_id start_id = end_id - LIMIT start_id = 0 if start_id <= 0 sleep 0.5 end end end 50 バッチの実装
  14. テーブルを適切な形に変更する 51 BIGINTの対応漏れ(恒久対応&他テーブルへの対応) user_comic_browse_mission_histories bkp_user_comic_browse_mission_histories テーブル名を変更 対応済みテーブルが参照さ れるようになる user_comic_browse_mission_histories user_comic_browse_mission_bigint_histories

    class SwapUserComicBrowseMissionBigintHistories < ActiveRecord::Migration[6.1] def change rename_table :user_comic_browse_mission_histories, :bkp_user_comic_browse_mission_histories rename_table :user_comic_browse_mission_bigint_histories, :user_comic_browse_mission_histories end end
  15. テーブルを適切な形に変更する 54 BIGINTの対応漏れ(恒久対応&他テーブルへの対応) def check_auto_increment_result query = <<~SQL SELECT table_name,

    auto_increment FROM information_schema.tables WHERE 1900000000 < auto_increment AND auto_increment < 2147483648 ORDER BY auto_increment DESC; SQL results = ActiveRecord::Base.connection.execute(query) results.each do |row| notice_alert("int型の上限が近付いています。テーブル名 : #{row[0]} ") notice("table_name: #{row[0]}, auto_increment: #{row[1]}") # Slack通知処理 end end +--------------+------------------------+ | table_name | auto_increment | +--------------+------------------------+ | table_name | (0-9)+| ….
  16. テーブルを適切な形に変更する 55 まとめ(テーブルを適切な形にする ) • Xデーはある日突然やってくる ◦ 履歴系のテーブルはBIGINT型を使う ◦ ALTER

    TABLEは無理でもRENAME TABLEは高速。余裕があ るうちにカラムの型を見直しておく ◦ auto_incrementの監視機能を実装しておく