Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
updated_at に依存したら大変なことになった / Don't depend on up...
Search
megane42
November 28, 2019
Programming
650
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
updated_at に依存したら大変なことになった / Don't depend on updated_at
Presented at :
https://gotanda-rb.connpass.com/event/155193/
megane42
November 28, 2019
More Decks by megane42
See All by megane42
Immutable ActiveRecord
megane42
0
370
Rails deprecation warning に立ち向かう技術 / v.s. rails deprecation warnings
megane42
0
850
OSS コミットゴルフのすすめ / Let's play OSS-contribute-golf
megane42
0
140
ゆる計算理論ラジオ / P vs NP for beginner
megane42
1
300
How to Make "DJ giftee"
megane42
1
1k
Rails 6 Upgrade "Practical" Guide
megane42
6
1.4k
本当は怖い Rails の `build_xxx` / The Hard Facts of `build_xxx` of Rails
megane42
0
300
Other Decks in Programming
See All in Programming
iOSDCのペンライトを自動制御したい!
akkeylab
0
140
【高い買い物LT会】初任給で話題の国産フィジカルAIを買った話
akagami
PRO
0
180
一人だけ、Kiroが静止する日
hideg
0
140
Workers Cache を知る
syumai
0
310
半永久的に提供し続けられるプライベートクラウドを目指して ― 利用者の認知負荷を抑えるAPI抽象化とハードウェア世代交代の基盤設計
tomokon
0
270
標準パッケージに uuid が追加された 背景から見る Go らしい意思決定 / go_127_uuid_decision
convto
5
9k
Heart of Swift Concurrency
koher
0
1.1k
TiDB Cloudのカスタムコントローラーによるオートスケール対応
takaidohigasi
0
140
AHC070解法紹介
eijirou
0
140
ハーネス設計入門 〜 基礎知識の整理から実務へのステップアップ 〜
kinopeee
18
19k
iOSDC Japan 2026 - Swiftで作って学ぼう!データベース自作入門
kaseken
2
400
Webの地図
yosuke_furukawa
PRO
6
4.9k
Featured
See All Featured
Design in an AI World
tapps
1
340
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
6
1.2k
30 Presentation Tips
portentint
PRO
1
410
Raft: Consensus for Rubyists
vanstee
142
7.7k
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
Making Projects Easy
brettharned
120
6.8k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
38
3k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
162
16k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
508
140k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
42k
Between Models and Reality
mayunak
4
470
How GitHub (no longer) Works
holman
316
150k
Transcript
updated_at に依存したら ⼤変なことになった megane42 / Hikaru Kazama @ giftee 2019/11/28
gotanda.rb
免責 このスライドに載せたコードや スキーマは⼀部簡略化しています このしくじりによるトラブルは、今はすべて解消しています
第 I 部 : 背景
弊社 giftee デジタルギフトを作って売っています
プロダクト giftee campaign platform フォロー / RT すると抽選でギフトをプレゼントします
機能紹介 実績集計機能 管理画⾯から ⽇ごとの抽選者数 がわかる
create_table "entries" do |t| # ... t.bigint "campaign_id", null: false
t.string "lottery_status", default: "fresh" t.datetime "created_at", null: false t.datetime "updated_at", null: false # ... end
class Entry # ... enum lottery_status: [ :fresh, :winner, :loser
] def draw! 抽選処理 ? winner! : loser! end def self.daily_drawers group("DATE(updated_at)").count end # ... end
当時の実装 抽選を回すと entry の updated_at が更新される 逆に、それ以外に更新される機会はない じゃあ updated_at を使って
⽇ごとの抽選者数 を集計しよう 後述しますが、 これ⾃体はしくじりじゃない と思ってます
第 II 部 : いよいよしくじります
悲劇はこの⽇起きた 2018-09-26 とある⼤型メンテの⽇ entries テーブルにカラムを追加 entries テーブルのレコード全体にデータ遡及が必要
Entry.winners.each do |e| e.update(new_column: "foo") end
+---------+----------------+---------------------+---------------------+ | id | lottery_status | created_at | updated_at |
+---------+----------------+---------------------+---------------------+ | 5 | winner | 2017-08-04 02:37:51 | 2018-09-26 01:53:54 | | 20 | winner | 2017-08-04 12:55:04 | 2018-09-26 01:53:54 | | 42 | winner | 2017-08-04 13:57:21 | 2018-09-26 01:53:54 | | 50 | winner | 2017-08-04 14:07:08 | 2018-09-26 01:53:54 | | 93 | winner | 2017-08-04 14:55:32 | 2018-09-26 01:53:54 | | 109 | winner | 2017-08-04 15:08:01 | 2018-09-26 01:53:54 | | 121 | winner | 2017-08-04 15:18:09 | 2018-09-26 01:53:54 | | 137 | winner | 2017-08-04 15:28:05 | 2018-09-26 01:53:54 | | 147 | winner | 2017-08-04 15:36:19 | 2018-09-26 01:53:54 | | 177 | winner | 2017-08-04 15:56:18 | 2018-09-26 01:53:54 |
しくじり データ遡及⽤スクリプトで updated_at を考慮していなかった その結果、ほとんどのレコードの updated_at がメンテ時刻に更新 された 実績が壊れた 2018-09-26
の当選者が⼤量に発⽣
第 III 部 : 対応
⼀次対応 created_at で代⽤ ほとんどの場合 created_at と updated_at は数秒の差しかない entry レコード作成直後に抽選を実⾏しているから
恒久対応 drawed_at カラムを新設 抽選実⾏時に時刻を埋める 既存のレコードに関しては created_at の値をコピー
class Entry # ... def draw! + transaction do 抽選処理
? winner! : loser! + update!(drawed_at: Time.zone.now) + end end # ... end
と簡単に⾔うけれど entries テーブルにカラムを増やすのはかなり⼤変 レコード数が多い n000 万のオーダー 常にレコードが増え続けている マイグレーション中にロックがかかると困る ⼆次被害を避けるために、念⼊りなリハーサルを実施
第 IV 部 : 教訓
何がしくじりだったのか? はじめから drawed_at のような専⽤カラムを作らなかったこと? メンテ時に updated_at を考慮し忘れたこと?
トレードオフ もし drawed_at を⽤意するなら: 抽選処理の実装時に、 drawed_at を埋める処理を忘れずに書く必 要がある データ遡及メンテのときは、何も考えなくてよい else
( updated_at に依存するなら): 抽選処理の実装時には、何も考えなくてよい データ遡及メンテのときに、 updated_at を更新してしまわない か気にする必要がある
トレードオフ(抽象化) もし専⽤カラムを作るなら: 定常的な開発時にひと⼿間かかる 突発的なメンテ時に何も考えなくてよい else ( updated_at に依存するなら): 定常的な開発時に何も考えなくてよい 突発的なメンテ時にひと⼿間かかる
個⼈的な意⾒ 突発的なメンテ時の⽅が、慌てていることが多い 突発的なメンテ時に何も考えなくてよい⽅がうれしい 基本的には専⽤カラムを⽤意した⽅がよさそう
別の観点からの教訓 知識に経験が伴うと⼈は強くなる created_at updated_at とは別に専⽤カラムを⽤意する流派があ ること⾃体は知っていたが、「なぜそうするのか」まではわかっ ていなかった 今は、 ⾔葉ではなく⼼で理解できた わけもわからず従っているベストプラクティスがまだまだある
きっとそれらにも理由がある
まとめ updated_at に依存したコードを書いている⼈は、突発メンテ時に ⼗分気をつけましょう 不安な場合は専⽤カラムを作りましょう たくさん経験を積んで or 共有しあって強くなっていきましょう
updated_at に依存したら ⼤変なことになった megane42 / Hikaru Kazama @ giftee 2019/11/28
gotanda.rb
おまけ : DB メンテのリハーサル中に得た知⾒
nullable なカラムを追加している最中でも INSERT できる (Aurora (MySQL)) MySQL にはオンライン DDL という機能があり、ALTER
TABLE 中 に更新系のクエリが実⾏できる https://dev.mysql.com/doc/refman/5.7/en/innodb-online-ddl- operations.html MySQL 互換をうたっている Aurora は、その辺も互換性あるの? やってみたら追加できた !!! 必ずご⾃⾝の環境でも確認してください !!!
テーブル全体を UPDATE したら徐々にロック された (Aurora (MySQL)) UPDATE entries SET drawed_at
= created_at WHERE entries.drawed_at IS NULL 上記の SQL を実⾏すると、 entries テーブルが ID : 1 から徐々にロ ックされていった 更新ができないだけでレコード新規追加はできる 対象レコードを「ID 1 から 100 万まで」のように絞りながら⼩刻み に実⾏していくことで、影響を最⼩化できる !!! 必ずご⾃⾝の環境でも確認してください !!!