EBS スナップショットと仲良くなりたい、と思ったら coldsnap

結論

EBS スナップショットをダウンロードしたり EBS スナップショットとしてアップロードしたかったら coldsnap を使おう。Rust で書かれてて速そう。

coldsnap どうやって使うの?

Rust で書かれてるので cargo install coldsnap とかするとインストールできる。

$ coldsnap download snapshot-00000000000000000 snapshot-00000000000000000.img

と実行すると snapshot-00000000000000000.imgsnapshot-00000000000000000 の中身が落ちてくる。凄い!

$ coldsnap upload test.img

と実行すると test.img の内容でスナップショットができる。凄い!

以下蛇足

EBS スナップショットをダウンロード、何や?

EBS direct API というのがあって EBS スナップショットがユーザーにも扱えるようになってます。 Action が 6 つしかなくてとてもシンプル。

  • CompleteSnapshot
  • GetSnapshotBlock
  • ListChangedBlocks
  • ListSnapshotBlocks
  • PutSnapshotBlock
  • StartSnapshot

AWS CLI で試してみる

下準備

インスタンスの上で試してみます。 EBS ボリュームを作ります。1 GiB で十分なのでそれで。

$ volume_id=$(aws --output text ec2 create-volume --availability-zone $AZ --size 1 --query 'VolumeId')

インスタンスにアッタッチして使えるようにしましょう。

$ token=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 10")
$ instance_id=$(curl -s -H "X-aws-ec2-metadata-token: $token" http://169.254.169.254/latest/meta-data/instance-id)
$ aws ec2 attach-volume --volume-id $volume_id --instance-id $instance_id --device /dev/sdx --query 'State'
"attaching"

インスタンスから見えるようになりました。

$ sudo fdisk -l /dev/nvme7n1 
Disk /dev/nvme7n1: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 4096 bytes / 4096 bytes

空っぽの EBS ボリュームですがとりあえずスナップショット作ってみます。

$ snapshot_id_1=$(aws --output text ec2 create-snapshot --volume-id $volume_id --query 'SnapshotId')

スナップショットの構成を読んでみる

ListSnapshotBlocks を使うとスナップショットがどのように構成されているかを表示することができます。

$ aws ebs list-snapshot-blocks --snapshot-id $snapshot_id_1
{
    "Blocks": [],
    "ExpiryTime": 1663734712.053,
    "VolumeSize": 1,
    "BlockSize": 524288
}

524288 B = 512 KiB のブロックが 0 コで構成されているスナップショットだ、という表示が出ました。 何も書いてないので当然ですね。 BlockSize は現在のところ 512 KiB だけだそうです。

内容がないと面白くないので少しだけ書いてみます。

$ echo -n wozozo | sudo dd of=/dev/nvme7n1 
0+1 records in
0+1 records out
6 bytes copied, 0.0215656 s, 0.3 kB/s

スナップショットを再度取って構成を表示してみると?

$ snapshot_id_2=$(aws --output text ec2 create-snapshot --volume-id $volume_id --query 'SnapshotId')
$ aws ebs list-snapshot-blocks --snapshot-id $snapshot_id_2
{
    "Blocks": [
        {
            "BlockIndex": 0,
            "BlockToken": "ACIBAfD21rcx+g2ieMHO/YtJBM48t3hp4jkczhv3SIk6UU1GHGMbO++vGwlZ"
        }
    ],
    "ExpiryTime": 1664332577.454,
    "VolumeSize": 1,
    "BlockSize": 524288
}

ブロック 1 つで構成されているスナップショットであることが確認できました!!

スナップショットのブロックを取得してみる

このブロックを取得してみましょう。 GetSnapshotBlock が使えます。 ブロックの指定には BlockToken 等を指定します。

$ aws ebs get-snapshot-block --snapshot-id $snapshot_id_2 --block-index 0 --block-token "ACIBAfD21rcx+g2ieMHO/YtJBM48t3hp4jkczhv3SIk6UU1GHGMbO++vGwlZ" wozozo.dat
{
    "DataLength": "524288",
    "Checksum": "Z8xlnwXWxcayKPxJmnIdw3A+aC7haNio+dC+KgEAJ8U=",
    "ChecksumAlgorithm": "SHA256"
}

wozozo.dat にブロックが書き込まれたはずなので確認してみましょう。

$ ls -l wozozo.dat 
-rw-rw-r-- 1 ubuntu ubuntu 524288 Sep 21 04:35 wozozo.dat
$ openssl sha256 -binary wozozo.dat | base64
Z8xlnwXWxcayKPxJmnIdw3A+aC7haNio+dC+KgEAJ8U=
$ od -c wozozo.dat 
0000000   w   o   z   o   z   o  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
0000020  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
2000000

未使用部分はヌル文字で埋められた 512 KiB のブロックがダウンロードできていました。 チェックサムも合っていますね!

差分を確認してみる

AWS CLIListChangedBlocks もサポートしてるので試してみます。 ディスクの冒頭を上書きして、更にちょっと進んだところにも書いてスナップショットを取ります。

$ echo -n dancho | sudo dd of=/dev/nvme7n1 
0+1 records in
0+1 records out
6 bytes copied, 0.0192607 s, 0.3 kB/s
ubuntu@ip-172-31-0-38:~$ echo -n dancho | sudo dd of=/dev/nvme7n1 bs=512K seek=16
0+1 records in
0+1 records out
6 bytes copied, 0.0176629 s, 0.3 kB/s
ubuntu@ip-172-31-0-38:~$ snapshot_id_3=$(aws --output text ec2 create-snapshot --volume-id $volume_id --query 'SnapshotId')

直前のスナップショットと比較してみると

ubuntu@ip-172-31-0-38:~$ aws ebs list-changed-blocks --first-snapshot-id $snapshot_id_2 --second-snapshot-id $snapshot_id_3
{
    "ChangedBlocks": [
        {
            "BlockIndex": 0,
            "FirstBlockToken": "ACIBAV721rcx+g2ieMHO/YtJBM48t3hp4jq01Byg+OVf01PvSs1NvFrpHieR",
            "SecondBlockToken": "ACIBAWW0tZi8nuBpxLyQyWckOBc0xGOU1ovXWL1fhSTVPL2ZjU6iUhTmtZnk"
        },
        {
            "BlockIndex": 16,
            "SecondBlockToken": "ACIBAY3+0jkvSBeEojA1fi37tpi2Z/UkctZcE3GqAy2JKI0HmD1gDMAv5IkF"
        }
    ],
    "ExpiryTime": 1664339386.537,
    "VolumeSize": 1,
    "BlockSize": 524288
}
$ snapshot_id_3=$(aws --output text ec2 create-snapshot --volume-id $volume_id --query 'SnapshotId')

更新と追記で 2 つのエントリーが確認できました。

結論

こんな感じのことをまるっとやってくれる coldsnap すてき!

時系列データの予測がしたい

時系列データというのは時刻など時間の何かがインデックスになってるデータのことだそうで、例えば毎日の平均気温とかがそうです。全くもってランダムだったりするかもしれませんが、気温のように

  • 24 時間周期とか 1 年周期で上がったり下がったりしながら
  • でも温暖化してるんじゃない? と上がっているような、氷河期に向かうときは下がっていくような
  • 隕石が落ちたり噴火があったりで空が何かで覆われると涼しく (寒く?) なったり
  • そもそも毎日の天気とかエルニーニョとか暑めだったり寒めだったり

  • 周期性 (複数あるかも、季節性とも)
  • 長期的なトレンド (温暖化とか氷河期とかは周期性かもしれませんが...)
  • 例外的なイベントによる外れ値的なものたち
  • 観測や環境によるノイズ

などの成分に分けて理解しようとするのが基本のようです。

予測をしようと思ったら、周期とトレンドはそのまま伸ばしてみつつ、例外的といってもバレンタインのチョコの売り上げとかは予測できる例外と思ってみたりトレンドの変更点も分かれば追加してみたりしつつ、未来のことを予測してみようというのが時系列の予測です。

どんなもんがあるのかなと思って少し調べてみたので、忘れないようにメモっておきます。

前処理

成長というのは倍々で起こることもあるので、そんなときは log を取ると真っ直ぐになって嬉しいです。漸化式のように、差を取ると分かりやすくなることもあるかもしれません。そんな感じで、与えられている数列を操作して別の数列を作ってそれで予測を立てて、逆の手順で欲しかった予測値を出すことがあります。「前処理大全」でも少し時系列データの前処理に触れられています。

AR-MA 系、若しくは ARIMA

AR は Autoregressor の、MA は Moving Average の略です。それぞれ、次の値は「過去の幾つかの値の線形結合 + ノイズになってる」、「過去のノイズの線形結合 + ノイズ」になっているという過程で線形結合の係数を探していくやりかたです。ノイズは正規分布を仮定して。

  • 前処理として 1 階差分を 1 回以上取るのを Integrated と言うらしく ARIMA と言ってみたり
  • 季節性 = Seasonality で複数階の差分を取って考えてみて SARIMA と言ってみたり
  • 外的要因 = eXternal REGressor を考えて ARIMAX と言ってみたり
  • ノイズが大きさをダイナミックにしてみて (Conditional Heteroscedasticity) ARCH と言ってみたり
  • お互いに関係がありそうな複数の時系列をベクトルにして AR して VAR (Vector AR) と言ってみたり

色々なバリエーションがあります。詳しくは Wikipedia自己回帰移動平均モデルとかARCHモデルを。

例えば、アメリカセンサス局が作った X-12-ARIMA っていう実装があるらしく操作マニュアル日本銀行がウェブページに置いています。そんだけ古典的で枯れているということのようです。

時系列分析と状態空間モデルの基礎」という本が丁寧で分かりやすいでした。写経のし甲斐がある。

TRAMO-SEATS

スペイン銀行が作ったらしいです。X-12-ARIMA の後継で X-13ARIMA-SEATS というのがあるのですが、「季節調整法に関する最近の動向:X-12-ARIMA から X-13ARIMA-SEATS へ」によると ARIMA も TRAMO-SEATS もいけるようにしたもののようです。ヨーロッパ政府機関などでは TRAMO-SEATS が主流のようですね。使ってないのでこれ以上何も言えないのですが、「R {seasonal} パッケージで X13-ARIMA-SEATSを使う」とのことで R でもいけるみたいです。

状態空間モデル

状態空間という、予測したい値とは別に何か (多次元かもしれない) 時系列データを考えて、そっちの予測と状態から欲しかった観測値の関係のパラメータを探していくモデル。ARIMA と表現できるモデルが結構被っているみたいなんだけど、実際どこまで被っているのかは未確認。でも ARIMA と違って解釈は状態空間に意味を込めやすく楽らしい。ARIMA は過去のデータの線型結合を取りますが、それは確かに理解しづらそう。

1 つ制約があって、状態空間は直前の値にだけ依存するようにしています。マルコフ性、って言うのかな。

状態空間の遷移も状態からの観測も関数なら何でもいいんだけど、何でもいいと色々大変なので、線型に絞って考えることが多くそれでも色々良いみたい。状態遷移にも観測にもノイズを乗せるのですが、これも正規分布を仮定することが基本のようです。ただ、それだと表現力が限られたりもするのでそこから逸脱して、となると色々な方法を使わないと状態遷移・観測の関数の推測が難しくなっていって色々な方法が提案されています。「状態空間モデル」というフレームワークがあって、状態遷移・観測に応じて様々な関数推測の方法があるみたい。

状態空間モデルは「時系列分析と状態空間モデルの基礎」でも扱われていますが、「基礎からわかる時系列分析」だと数式てんこ盛りで、信号処理の話などを引用しつつ説明してくれています。

EWMA (Exponential Weighted Moving Average)

ごめんなさい、「基礎からわかる時系列分析」で Holt-Winters っていう方法が紹介されてて EWMA の一種だって書いてあったんです。名前からしてきっと Linux の load average と同じような計算しているに違いありません。というか、これは AR-MA に入れるべきか? 1つブログ記事を紹介してお茶を濁します: 異常検知のための未来予測:オウム返し的手法からHolt-Winters Methodまで

Wiener-filter

ごめんなさい、「基礎からわかる時系列分析」で Wiener-filter っていう方法が (ry。カルマンフィルタってのが状態空間モデルでの予測の方法で出てきますが、「フィルタ」って付いてるやつは信号処理由来のにおいがプンプンしますね。「やる夫で学ぶディジタル信号処理」がとてつもなく分かりやすくて感動しました。

ランダムフォレスト

ランダムフォレストで回帰ができるので、これで時系列予測しちゃいます。こんな感じで

Prophet

Facebook が作った、業務知識さえあれば使える、みたいなのを目指してるライブラリです。数値を見る、というよりは曲線をフィットさせる、という考えかた、らしい。「fb Prophet の解剖で学ぶベイズ時系列モデリング」で Stan による実装の話が紹介されています、素晴しすぎる。

実際、

  • 何も考えずに時系列データを入れるだけで「いい感じ」にやってくれ
  • トレンドの変換点も頻度指定ありで勝手に推測してくれたり細かい日付けの指定ができたり
  • 季節性も勝手に判断してくれたり強制させたり時前で好みの季節性指定できたり
  • 祝日なども複数の種類を簡単に指定して前後何日くらい影響があるか指定できたり

分かり易く色々なパラメータが指定できて良い感じでした。

DeepAR

Amazon の実装で、RNN (recurrent neural network) 使っています。近々、近々試そうと思っています。SageMaker 含めて。

実装

ARIMA・ランダムフォレスト・状態空間モデル・prophet は Python でも R で何かしら実装があります。prophet はどっちも結局 Stan への wrapper だそうですが、他のやつは実装によって色々挙動や調整のし易さが違いそうですね...。ARIMA とランダムフォレスト枯れているというか説明も沢山ありそうで prophet の次に手軽そうでした。

100% な予測はありえないので、コスパとか相性を見ながら良い方法が選べるといいですね!