SnowflakeのTime Travel

SnowPro Coreの頻出テーマだと感じたTime Travel。
資格取得時に固め打ちした記憶があるが、補強ついでにもう少し詳し目に公式を読んでみる。
古くなったり間違っていたりするかもしれないので、事の真偽については公式を参照のこと。

Time Travelの理解と使用
https://docs.snowflake.com/ja/user-guide/data-time-travel.html

Time Travelとは

通常、データ削除後に削除したデータにアクセスするには削除前にデータのバックアップが必要。
バックアップしてリストアして、というのはある意味DB製品の基本的な動作仕様であって、
SnowflakeにもSnowflakeのフルマネージドなポリシーに基づいて仕組みが用意されている。

Snowflakeではデータが自動的・透過的にバックアップされ、
明示的にバックアップ・リストアせずに削除後に削除前のデータにアクセスできる。
何も気にしないでも裏で勝手にバックアップ・削除されるため大分手間が省略される。
当然ストレージコストを余分に消費するが保持期間を設定することでバランスを制御できる。

公式には以下の用途で使われる、と書いてある。

  • 誤って削除したデータの復元
  • 特定時点の復元
  • 任意期間の使用量・操作の分析

データのライフサイクル

重要な観点として、データにはステートがあり、ライフサイクルが決まっている。

ステート 削除種別 用途
通常 現在のデータに対するクエリ、DDL、DML、など
Time Travel 論理削除 更新・削除された過去のデータへのクエリ
過去の特定の時点についてテーブル・スキーマ・DB全体のクローン
削除されたテーブル・スキーマ・DBの復元
Fail-safe 物理削除 一定期間(Retantion Period)が過ぎるとデータはFail-safeに移動。
操作不可。Snowflakeへ問い合わせて何とかなる可能性がある

データの保持期間(Retentiono period)

ユーザはデータの保持期間を変更できる。
保持期間は日単位で設定する。デフォルト値は1(24時間)。
ゼロを設定するとTime Travelを使用しない設定。
設定範囲はテーブル種別、Snowflakeのエディションによって異なる。
通常のテーブルについて、エディションごとの設定範囲は以下の通り。
(Temporaryテーブル,Transientテーブルは通常1日を超えて使わないはずなので以下では除外)

エディション 0(Time Travelを使用しない) 1日 〜90日
Standard 可(デフォルト) 不可
Enterprise+ 可(デフォルト)

さらに、ACCOUNTADMINロールを持つユーザはユーザの設定範囲を限定できる。
デフォルト値は DATA_RETENTION_TIME_IN_DAYS、
最小値は MIN_DATA_RETENTION_TIME_IN_DAYS。

最小値設定はデフォルト値設定を上書きしない。
デフォルト値が最小値よりも小さい場合、いずれかの大きい方が適用される。

コスト

Time Travelは論理削除のステートでありストレージコストがかかる。
データが変更された時点から1日ごとに課金。(ちょっと詳細不明…)
テーブルを丸ごとDROPした場合には丸ごと保存されるがなるべく差分が保存される。

ETLなどに使う1日未満のデータはTransientテーブルに格納することになっている。
また、より短いセッション内で使うデータはTemporaryテーブルに格納することになっている。
そのような用途であれば長いTime Travelは不要だし、そもそもFail-safeも不要。
これらのテーブル種別については、Time Travel期間は最大1日となっていて、
さらに後続のステートであるFail-safeに遷移しない。

逆に言うと、Transient,Temporaryテーブルを使うことでTime TravelとFail-safeの
余分なコストを最大1日に抑えることができる。

ちなみにTemporaryテーブルについてはセッションを落としたときにテーブルが破棄されると、
Time Travelの保持期間も終了する。

行ったり来たりだが、Transient,TemporaryについてはFail-safeが無いので
Time Travel終了後は完全にアクセス不能となる。

保持期間の変更

テーブルの保持期間を変更すると、現在のデータとTime Travelにある全てのデータに影響する。

変更 影響
保持期間の延長 現在Time Travelにあるデータの保持期間が長くなる。
例えば保持期間=10を保持期間=20に変更した場合
現在Time Travel3日目のデータの残り期間は7日から17日に伸びる。
保持期間の短縮 現在アクティブなデータには新しい保持期間が適用される。
例えば保持期間=10を保持期間=5に変更した場合
現在Time Travel 7日目のデータはFail-safeへ遷移。
現在Time Travel3日目のデータの残り期間は7日から2日に変わる。

データライフサイクルの遷移はバックグラウンドで非同期に行われるため、
ALTERコマンドで保持期間を変更したとしてすぐに上記の更新が走るわけではない。

オブジェクト階層に対する再帰的な影響

オブジェクトはCompositeパターンに基づき所有関係を持っているが、
階層上、上位のオブジェクトに対する保持期間の変更は再帰的に下位のオブジェクトに反映される。
例えばDBに対する変更はスキーマに対して反映されるなど。

ワイルドカードを使った破壊的な変更は意図しない変更をもたらすため、慎重にやったほうが良い。
最上位のアカウントに対する保持期間の変更は推奨しないという記述がある。

上位オブジェクトのドロップと下位オブジェクトの保持期間

上位オブジェクトをドロップすることで自動的に下位オブジェクトがドロップされる。
その際、下位オブジェクトの保持期間は強制的にドロップした上位オブジェクトの保持期間が設定される。
例えば保持期間10日のデータベースをドロップしたとして、
保持期間15日のスキーマ、テーブルの保持期間は強制的に10日となる。

下位オブジェクトを先にドロップすれば、下位オブジェクトの保持期間が上書きされることはない。

Time Travel中のデータに対するクエリ

Time Travel中のデータにアクセスするために特別なストレージにアクセスする、という感じではなく、
SQLの拡張構文が用意され、自然にアクティブなデータとTime Travel中のデータの触り分けができる。

at句とbefore句が用意されている。
例えば公式に書かれている以下のような感じ。


---at句によりtimestampで指定された時点の履歴データを取得
select
    *
from
    my_table
at
    (timestamp => 'Fri, 01 May 2015 16:20:00 -0700'::timestamp_tz)
;
---5分前の時点で履歴データを取得
select
    *
from
    my_table
at
    (offset => -60*5)
;
---指定されたステートメントによる変更を含まないで、それ以前の履歴データを取得
select
    *
from
    my_table
before
    (statement => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726')
;

at句、before句が保持期間外を指す場合、クエリは失敗する。

Time Travel中のオブジェクトのクローン

SQLの拡張構文によってTime Travel中のDBやスキーマなどのオブジェクトをクローンできる。
CREATEと共にCLONEを使う。例えば公式に書かれている以下のような感じ。


---指定されたタイムスタンプで表される日付と時刻のテーブルのクローンを作成
---my_tableというテーブルをrestored_tableというテーブルにクローン
create table
    restored_table
clone
    my_table
at
    (timestamp => 'Sat, 09 May 2015 01:01:00 +0300'::timestamp_tz)
;
---現在時刻の1時間前に存在していたスキーマと配下の全てのオブジェクトをクローン
create schema
    restored_schema
clone
    my_schema
at
    (offset => -3600)
;
---指定されたステートメントの完了前に存在していたデータベースと配下の全てのオブジェクトを復元
create database
       restored_db
clone
       my_db
before
      (statement => '8e5d0ca9-005e-44e6-b858-a8f5b37c5726');

CLONEも、指定したオブジェクトの保持期間を超えてTime Travel時間を指定するとエラーとなる。

オブジェクトのドロップと復元

オブジェクトの履歴はオブジェクトに紐づく、という書き方が正しいかは不明だが、
オブジェクト自体をドロップした場合の履歴は、オブジェクト配下の変更・削除の履歴とは少し異なる。
Time Travelは通常差分を履歴として残すが、オブジェクトのドロップによって完全な履歴が残る、
と公式に記述がある。

DROPによってオブジェクトをドロップした後、UNDROPによってドロップしたオブジェクトを復元する。
DROPした後、CREATEしたとしてもUNDROP扱いにはならないし、DROPした古いオブジェクトは残る。
永遠に完全な履歴のhistoryが積み重なっていく、historyのある時点のオブジェクトを対象に
UNDROPする、という扱いとなる。

UNDROPにより復元するテーブルと同名称のテーブルが存在する場合エラーとなる。


--- mytableという名前のテーブルをdrop
drop table mytable
;
--- mytableという名前のテーブルをundrop
undrop table mytable
;

オブジェクトのhistoryについてもSQLの拡張構文で確認できる。
showとhistoryを合わせて使用する。公式は以下の通り。
オブジェクトの保持期間がすぎてTime Travelから消えるとshow historyで表示されなくなる。


--- mytestdb.myschemaスキーマ配下にあるloadから始まるテーブル名の履歴を表示
show tables history like 'load%' in mytestdb.myschema
;
--- mytestdbデータベース配下のスキーマの履歴を表示
show schemas history in mytestdb
;

まとめ

自力でバックアップ・リストア操作なしで、Snowflakeが勝手にオブジェクトをバックアップしてくれる。
SQLの拡張構文を通してアクティブなデータと似た形でオブジェクトをリストアできる。
Time Travelに保持される期間はカスタマイズできる。
みたいなことについて、公式ドキュメントを読んで確認してみた。