はじめに
弊社でRedshiftから移行したDWH、Snowflake(移行についてはSNOWDAY JAPANで発表させていただきました)に実装されているTimeTravel機能ですが、これは非常に便利な機能です。
どういう機能かはSnowflakeを使われた方なら存知だとは思いますが、簡単に説明すると、「ある日時のデータベースの状態を再現する」機能です。
たとえば、ある処理を実行した結果、データがおかしくなったので、原因を調査したいが差分が取れない、うっかりデータを削除してしまったが元に戻したい、といったケースはDB使っている人なら一度は経験していると思います。こういった場合、通常のDBですと、まずはバックアップデータから復元するという作業が発生しますが、バックアップ後にバッチが数本走っていてデータが何度も書き換わっていたりするので、復元や調査に時間がかかりがちです。
SnowflakeのTimeTravelは特定の日時やステートメントの前の状態でDB状態を復元できます。しかもやり方は簡単で、SELECT句にatやbeforeといったキーワードを追加するだけでできるのです。
たとえば5分前と現在のtableで追加更新されたデータを見たいのであれば、5分=300秒ですので
SELECT * FROM test_table MINUS SELECT * FROM test_table AT(OFFSET => -300) --巻き戻す秒数
という簡単なSQLでデータが取れます。
TimeTravelのViewへの適用試験
本題です。
この機能についてはドキュメントに書かれているとおりなのですが、書かれているのはTableやSchema、Databaseに関してであり、Viewに関する記述はありません。そこで実際にViewにTimeTravelが適用できるのかテストを行ってみました。
-- テーブル1を作成 CREATE TABLE work.test1(id,name) AS SELECT * FROM VALUES (1,'one'),(2,'two'),(3,'three'),(4,'four'),(5,'five'); -- テーブル2を作成 CREATE TABLE work.test2(id,name) AS SELECT * FROM VALUES (1,'一'),(2,'二'),(3,'三'),(4,'四'),(5,'五'); -- テーブル1とテーブル2をJOINしたViewを作成 CREATE VIEW work.test3 AS SELECT t1.id,t1.name ename ,t2.name jname FROM work.test1 t1 INNER JOIN work.test2 t2 ON t1.id = t2.id;
現在のViewの状態は
ID | ENAME | JNAME |
---|---|---|
1 | one | 一 |
2 | two | 二 |
3 | three | 三 |
4 | four | 四 |
5 | five | 五 |
となります。
ここで5分ほど時間を置いてから、
DELETE FROM work.test2; DELETE FROM work.test1 WHERE id < 3;
を実行して、test1テーブル、test2テーブルのデータを削除してしまいます。
当然ですが、test1テーブルを全削除していますので
SELECT * FROM work.test3;
の結果は空になります。
では
SELECT * FROM work.test3 at(offset => -300); --巻き戻す秒数
の結果はどうでしょうか。
ID | ENAME | JNAME |
---|---|---|
1 | one | 一 |
2 | two | 二 |
3 | three | 三 |
4 | four | 四 |
5 | five | 五 |
VIEW内部で使用していたtable1,table2の状態が両方とも復元されています。
つまり、TimeTravelはViewに対しても有効で、使用しているTableを巻き戻した状態でViewを再現するということになります!
特に3層スキーマ構成を真面目にやっているところには非常にありがたい機能ではないかと思います。
ただし制限もある
さて、ViewにもTimeTravelは適用されますが、ちょっと意地悪なテストをしたところ、制限もあることがわかりました。
何をしたかと言いますと、
CREATE VIEW work.test3 AS SELECT t1.id ,t1.name ename ,t2.name jname ,CURERNT_TIMESTAMP() testtime FROM work.test1 t1 INNER JOIN work.test2 t2 ON t1.id = t2.id;
という形で、Viewに現在時刻を入れてみたのです。
こちらで同様の実験を行うと、
- 作成時
ID | ENAME | JNAME | TESTTIME |
---|---|---|---|
1 | one | 一 | 2023-02-10 00:29:38.474000000 +00:00 |
2 | two | 二 | 2023-02-10 00:29:38.474000000 +00:00 |
3 | three | 三 | 2023-02-10 00:29:38.474000000 +00:00 |
4 | four | 四 | 2023-02-10 00:29:38.474000000 +00:00 |
5 | five | 五 | 2023-02-10 00:29:38.474000000 +00:00 |
- TimeTravel時
ID | ENAME | JNAME | TESTTIME |
---|---|---|---|
1 | one | 一 | 2023-02-10 00:35:43.911000000 +00:00 |
2 | two | 二 | 2023-02-10 00:35:43.911000000 +00:00 |
3 | three | 三 | 2023-02-10 00:35:43.911000000 +00:00 |
4 | four | 四 | 2023-02-10 00:35:43.911000000 +00:00 |
5 | five | 五 | 2023-02-10 00:35:43.911000000 +00:00 |
と、現在時刻を入れたTESTTIMEカラムにずれが発生していることがわかります。さすがに現在時刻までは5分前に戻してくれないようです。(OFFSET分引いてあげるだけなので、できそうな感じがしますが。)
恐らくCURRENT_DATEも同様ですので、たとえば
WHERE created_date >= CURRENT_DATE() -7
のような、現在日付や現在日時を利用した処理がView内に入っていた場合、以前とまったく同じ状態でViewを復元することはできないでしょう。
まとめ
TimeTravelはViewも巻き戻しSELECTができますが、現在日時処理のためのCURRENT_TIMESTAMPのような、CURRENT系が入ったViewの場合、同じデータが取得できるわけではないという制限があります。
ただ、CURRENT系が入ったViewはそれほど多くないでしょうから、だいたいのViewはTimeTravelした時刻とまったく同じデータをViewとして取得できますので、その有効さが大きく損なわれるわけではありません。