Lock in $30 Savings on PRO—Offer Ends Soon! ⏳

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

KNR
October 27, 2023
8k

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

Kaigi on Rails 2023

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の監視機能を実装しておく