こう 聞かれます
- 「本番の
大量の データを、 止めずに 書き換える (バック フィル) には、 どうしますか」 - 「数千万行の
表で、 新しく 足した 列に 値を 埋めたい。 どう 流しますか」 - 「書き換えの
途中で 落ちたら、 どうしますか」
面接官が 見ている点
- 1 回の
大きな UPDATE が、 長い ロックや DB の 詰まりを 起こすと 分かっているか - 小さく
分ける、 どこまで 済んだかを 残す、 何度流しても 同じ 結果に なる、の 3 つを 押さえているか - 流している
間も DB の 様子を 見て、 速さを 絞ったり 止めたりできるか - 終わった
後に、 件数や 中身を 照らして 確かめ、 戻し方まで 用意しているか
短い答え
- 1 回の UPDATE で
全部を 書き換えず、 主キーの 範囲で 小さく 分けて、 短い トランザクションで 少しずつ流します。 - どこまで
済んだかを 記録して 途中から 再開できるようにし、 何度流しても 同じ 結果に なる 条件で 書きます。 - 流している
間は DB の 負担や 複製の 遅れを 見て、 間を 空けたり 止めたりし、 終わったら 件数と 中身を 照らして 確かめます。
この
少し 詳しい答え
スキーマを
1 回で 書き換えると 何が起きるか
数千万行を 1 本の UPDATE で
小さく 分け、 続きから流す
主キーの
JavaScript
const rows = [];
for (let id = 1; id <= 7; id++) {
rows.push({ id, name: "user" + id, fullName: null });
}
let lastId = 0;
function runBatch(size) {
const batch = rows
.filter((r) => r.id > lastId && r.fullName === null)
.slice(0, size);
for (const r of batch) {
r.fullName = r.name.toUpperCase();
}
if (batch.length > 0) {
lastId = batch[batch.length - 1].id;
}
return batch.length;
}
let count;
while ((count = runBatch(3)) > 0) {
console.log("書いた " + count + " 件 続きは id " + lastId + " より後");
}
console.log("残り " + rows.filter((r) => r.fullName === null).length + " 件");書いた 3 件 続きは id 3 より後
書いた 3 件 続きは id 6 より後
書いた 1 件 続きは id 7 より後
残り 0 件ここで
SQL で
SQL クエリ
UPDATE users SET full_name = name
WHERE id > 30000 AND id <= 31000 AND full_name IS NULL;様子を 見ながら流す
塊と
書き換えは
終わった 後の 確かめと戻し方
終わったら、
戻せるように、
トレードオフ
小さく
話す例は
よく ある間違い
- 1 本の UPDATE で
全部を 書き換える 長い ロックで 普段の リクエストが 待たされ、 失敗すると 全部が 巻き戻ります - OFFSET で
続きを数える 後ろへ 進むほど 遅くなり、 流している 間に 行が 増減すると 抜けや 重なりが 出ます。 主キーの 範囲で進めます - アプリの
保存の 処理を そのまま使う メールや 通知のような 副作用が、 行の 数だけ動きます
深掘りの問い
- 「塊の
大きさは、 どう 決めますか」本番に 近い データで 流し、 1 つの 塊が 短い 時間で 終わり、 ロックの 待ちや 複製の 遅れが 目立たない 大きさに する、と答えます - 「流している
間に、 同じ 行が 普段の 処理で 更新されたら」 新しい 行や 更新には アプリが 新旧の 両方を 書くようにし、 書き換えは 値が 入っていない 行だけを 対象に するので、 上書きで ぶつからない、と答えます - 「書き換えの
中身に 誤りが 見つかったら」 すぐ止め、 控えや 残しておいた 古い 列から 直す範囲を 決めて、 同じく 小さく 分けて 流し直す、と答えます
