ZOZOTOWNで最大級のトラフィックを記録する福袋発売イベントで実施した負荷対策 - ZOZO TECH BLOG
こんにちは。開発部の廣瀬です。 本記事では、昨年障害が発生してしまったZOZOTOWNの福袋発売イベントについて負荷対策を実施し、今年の福袋イベント期間を無傷で乗り切った話をご紹介したいと思います。 ...
https://techblog.zozo.com/entry/sqlserver-tuning-luckybag
こんにちは。カート決済部の林です。ZOZOTOWN内のカートや決済の機能開発、保守運用を担当しています。
過去に福袋販売イベントの負荷対策の記事を掲載しました。
上記の記事では、タイムアウトしたプロセスがロックを掴んだままになっていたことが原因で、大量のブロッキングが発生していました。詳細な負荷や対策の内容について知りたい方は、ぜひ上記の記事を読んでみてください。
こちらの原因を解決するために、XACT_ABORTの設定をONにすることが有効であると記載しています。XACT_ABORTはトランザクション内でエラーが発生すると即座にロールバック+ロックの解放を指示できるオプションです。このオプションをONにすることで、タイムアウトした時点でロックが解放され、ブロッキングが発生しなくなりました。
ただし、設定をONに変えると一部動作が変わり、既存の処理が正常に動かなくなることがあります。弊社でONにした際も一部のストアドプロシージャ(以下ストアド)が正常に動かなくなりました。その時にXACT_ABORTの動作について確認したので、その確認内容について本記事で紹介したいと思います。
XACT_ABORTの動作について知りたい方や、これからONにしようとしている方などの参考になれば幸いです。
XACT_ABORTはSQL Serverのオプションの1つで、デフォルトではOFFになっています。
ON/OFFそれぞれの動作を以下の表に記載します。
設定 エラー時の動作 後続の処理
OFF 発生した処理のみがロールバック 実行される場合がある
ON トランザクション全体が終了しロールバック 実行されない
実際に上記の動作についてテスト用のテーブルを作成し動作確認を行います。
実際に動作を確認するために、テスト用のテーブルと初期レコードを準備します。テーブル作成は以下のDDLを実行し、table1とtable2を作成します。このとき、動作確認時にエラーを起こしやすくするために、table1のcol1とtable2のcol1に外部キー制約を設定します。
CREATE TABLE table1
(col1 INT NOT NULL PRIMARY KEY);
CREATE TABLE table2
(col1 INT NOT NULL REFERENCES table1(col1));
初期のレコードとして以下のクエリを実行しtable1に4レコードINSERTします。
INSERT INTO table1 VALUES (1);
INSERT INTO table1 VALUES (3);
INSERT INTO table1 VALUES (4);
INSERT INTO table1 VALUES (5);
ここまでで準備完了になります。
動作確認のために以下のクエリを実行します。
SET XACT_ABORT OFF;
GO
BEGIN TRANSACTION;
INSERT INTO table2 VALUES (1);
INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる
INSERT INTO table2 VALUES (3);
COMMIT TRANSACTION;
GO
1行目でXACT_ABORTをOFFに設定します。その後トランザクションを開始します。トランザクション内でtable2に3回のINSERTを行います。1回目と3回目のINSERTは正常に完了します。しかし、2回目のINSERTではtable1のcol1に2が入っているレコードが存在しないため、外部キー制約により失敗します。3回のINSERTが終わった後にCOMMIT TRANSACTIONを行います。
では、この時にtable2のレコードはどうなっているでしょうか。以下のクエリで見てみます。
SELECT * FROM table2
クエリの結果は以下になります。
2回目のINSERTでエラーになっているのですが、1,3回目のINSERTは反映されています。つまりは、XACT_ABORTがOFFの場合には一部のクエリがエラーになってもそのまま処理が続けられます。なので、エラーハンドリングを自前で行う必要があります。
例えば今回の場合に、2回目のINSERTが失敗したら全てロールバックするには以下のようにTRY-CATCHで囲います。
SET XACT_ABORT OFF;
GO
BEGIN TRY
BEGIN TRANSACTION;
INSERT INTO table2 VALUES (1);
INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる
INSERT INTO table2 VALUES (3);
COMMIT TRANSACTION;
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION;
END CATCH
GO
では次にONの場合の動作を見ていきます。OFFの動作確認時のtable2のレコードを以下のクエリで削除します。
DELETE table2
以下のクエリで動作確認をします。
SET XACT_ABORT ON;
GO
BEGIN TRANSACTION;
INSERT INTO table2 VALUES (1);
INSERT INTO table2 VALUES (2); -- 外部キー制約によりエラーになる
INSERT INTO table2 VALUES (3);
COMMIT TRANSACTION;
GO
クエリの内容としては1行目でXACT_ABORTをONにした以外はOFFの動作確認と同じです。このクエリを実行後に以下のクエリでtable2のレコードを見ます。
SELECT * FROM table2
取得できたレコードの件数は0件になります。つまりは、XACT_ABORTがONの場合にエラーが発生すると、その時点で処理が終わりロールバックされます。なので、ONの場合に自前でロールバックを行う必要はありません。
XACT_ABORTがOFFで動作するストアドを作成します。
続きはこちら