Qiita yubaさんが書いていらっしゃる
現在時刻の取得をコード中に直に書かない(http://qiita.com/yuba/items/f52f90c4bd249d24b7d6)
にすごく共感し、私も書きたくなったので。
#とあるバッチ処理での出来事(現在時刻がコードに埋め込まれているので、再実行(リラン)できないじゃん)
業務要件
次のようなバッチ処理の業務要件がありました。
「外部○○システムの□□データの更新日時を、前日20時締めで自システムに取り込んで、翌日のオンラインサービスで開示したい」
というものです。
制約
この処理の他にもバッチ処理があり、処理時間の制約もあって、このバッチ処理は、前日の22時から処理を開始しなければなりませんでした。
実装イメージ
INSERTとUPDATEが絡むので、実際はもう少し複雑なSQLなのですが、次のようなSQLだと思ってください。
前日の22時から処理を開始してよいという制約が逆に都合が良いという判断から、単純に現在日時**「NOW()」**を取得して、時分秒だけ20時に置き換えています。
SELECT
*
FROM
T_TABLE_A
WHERE
UPD_DTTM <= STR_TO_DATE(DATE_FORMAT(NOW(),'%Y/%m/%d 20:00:00'), '%Y/%m/%d %H:%i:%s')
;
このバッチ処理の後続で、別のバッチ処理が深夜AM1時頃異常終了しました!!
夜間障害CALLの電話がAM1時になりました。タクシーを飛ばして出社して、障害対応開始。
漸くAM3時に、直接障害を起こしたバッチ処理の修正が終わりました。
オンラインサービスは9時からなので、まだ余裕です。再実行をするのことになりました。
今回の障害は、自システムのテーブル群が色々更新された後での障害だったので、再実行開始点は、
外部システムからの取り込み処理である、このバッチ処理からとせざるをえない状況でした。
ここで、さらに問題発生。上記のSQLの現在日時を取得している**「NOW()」です。
業務要件では、「前日20時締め」の縛り**があるので、前日20時以下のデータを取り込まなければなりません。
しかし、異常終了したのは翌日のAM1時です。このまま再実行してしまうと、20時以降のデータが取り込まれてしまいます。
現在日時の取得は、SYSDATEやNOW等の現在日付をコードに直接埋め込まない。間に一枚噛ませること。
結局、現在日時の取得は、「NOW()」を止めて、Viewから取得するように変更しました。
このViewは日々使うときは「NOW()」から現在日時を取得し、障害対応等の再実行するときは、テーブルから現在日時を取得します。
もっと言うと、オンライン、バッチともにViewから現在日付を取得するように統一しておくと、「今日はn月m日のつもり」というようなテストができるので、テストのしやすさ的にもGOODです。
顧客の受け入れテストやシステム総合テストなどでは、ある日の本番データを、1日遅れくらいの頻度でテスト機に持ってきて、テストを実施したりしますが、こんな時「今日はn月m日のつもり」ということができれば、1日遅れであっても、あたかも当日のふりをすることができます。
-- 現在日時を取得するView(日々使う)
CREATE VIEW V_DATETIME (CURRENT_DTTM)
AS
SELECT
NOW() AS CURRENT_DTTM
;
-- 現在日時を取得するView(再実行用。実テーブル内の日時)
CREATE VIEW V_DATETIME (CURRENT_DTTM)
AS
SELECT
CURRENT_DTTM
FROM
T_DATETIME
;
-- 再実行用の日付を登録するレコード
INSERT INTO T_DATETIME (ID, CURRENT_DTTM) VALUES (1, STR_TO_DATE('2015/08/30 20:00:00', '%Y/%m/%d %H:%i:%s'));
-- 現在日時をViewから取得するように変更
SELECT
*
FROM
T_TABLE_A
WHERE
UPD_DTTM <= STR_TO_DATE(DATE_FORMAT((SELECT CURRENT_DTTM FROM V_DATETIME),'%Y/%m/%d 20:00:00'), '%Y/%m/%d %H:%i:%s')
;