使い捨てに出来るのは、作り直せるものだけ
안녕하신게라!パナソニック コネクト株式会社クラウドソリューション部の加賀です。
前回「ロードバランサとL4/L7、基本の"キ"」で、同じ箱を何個も並べて案内係に配らせる形まで来ました。1台落ちてもユーザは気づかず、デプロイのたびに1台ずつ外して入れ替えられる。箱は、いつ捨てても構わないものになりました。
ただ、ログイン情報については「外部のRedisやDBへ追い出す」と書いたきり、その先を説明していません。ユーザがアップロードしたプロフィール画像も同じです。箱Aに保存すれば、次に箱Bへ振り分けられた瞬間に消えたように見える。かといって100個の箱すべてにコピーして回るわけにもいきません。
今回は、箱の外に追い出されるデータを、どこへ、どうやって永続化するのか見ていきます。
(取り上げるサービス名と既定値はAWSのものです。他クラウドにも考え方は使えます。)
箱と一緒に捨てられる、プロフィール画像とログイン情報
コンテナの回で書いたとおり、コンテナが書き込んだ内容は一番上の薄い層に溜まるので、docker rm ひとつで画像も一緒に消えます。仮想マシンでも同じで、EC2の インスタンスストア はホスト直結の物理ディスクで速い代わりに、インスタンスを捨てれば中身も一緒に消えます1。速いという理由だけで選んで、空になったディレクトリを見て青ざめる経験をしたくは無いものです。
しかも、箱を捨てるのはインフラ側の都合です。ユーザのプロフィール画像は、本人が何もしていないのに、デプロイの巻き添えで消えてしまいます。
ログイン情報も、箱Aのメモリにセッションを持っていれば、次のリクエストが箱Bへ回った時点でそのユーザは初対面になります。前回のスティッキーセッションは同じ箱へ送り続けて先送りする手でしたが、置き場所が箱の中のままだと、箱を入れ替えれば紐付いていた人ごとログアウトしてしまいます。
12-Factor Appの「ステートレス」が言っているのは、ファイルやメモリを使うなということではありません。1回のリクエストを処理し終えるまでの作業場として使うのは構わない。持ち越したくなった時点で、それは箱の外に置くものです。
厄介なのは、この線を踏み越えてもエラーにはならないことです。開発環境は箱が1つしかないので、/tmp に画像を置こうがメモリにセッションを持とうが、動きます。壊れる条件は箱の台数が増えること。しかも「たまに画像が出ない」「ときどきログアウトする」という再現しにくい形で出ます。
で、箱の外に置くには、どのような手段があるのか。
ブロック、ファイル、オブジェクト。違うのは「何を渡すか」
クラウドのストレージは大きく3種類に分かれます。容量や値段の違いだと思われがちですが、本質的な差はアプリとストレージの間で何をやり取りするかにあります。
ブロックストレージ(AWSならEBS)は、生のディスクをネットワーク越しに貸し出すサービスです。渡すのは「何番目のブロック」という番地だけで、どんなファイルシステムを載せるかは借りた側の仕事。だから mkfs してから mount する手順が要ります。OSから見れば単なるディスクなので、DBでも起動ディスクでも置けて、遅延も小さい。
弱点は、原則として1つのインスタンスにしか繋げないこと。Multi-Attach という例外はありますが、「共有フォルダが手に入る」機能ではありません。XFSやEXT4は複数のサーバから同時に触られる前提で作られていないので、クラスタファイルシステムが別途要ります。知らずに両側から書くと、ファイルシステムが静かに壊れます。
ファイルストレージ(EFS)は、ファイルシステムごと向こうが持っていて、NFSで貸してくれるものです。渡すのは「パスと、そのどこからどこまで」。複数のサーバから同時にマウントして、普通のディレクトリとして読み書き出来ます。昔ながらのNASのマネージド版で、途中だけの書き換えも追記もロックも、アプリ側の作法をそのまま持ち込めるのが強みです。
その代わり、整合性を保つぶん遅延はブロックストレージより大きく、GB単価も3種類で一番高い。「とりあえず全部EFSに置いとけば共有できるやん」で始めたシステムの請求書が、半年後にちょっと高いよねと話題になったりします。その頃にはもう、誰も移行したがりません。
3つめのオブジェクトストレージ(S3)だけ、毛色が違います。キーと値を預かる倉庫で、渡すのは「キー」と「中身まるごと」。HTTPのAPIで PUT して GET する、それだけです。ディレクトリに見えるものは、キーに含まれる / をコンソールがそれっぽく表示しているだけで、実体は階層構造を持っていません。
| 観点 | ブロック(EBS) | ファイル(EFS) | オブジェクト(S3) |
|---|---|---|---|
| やり取りの単位 | ディスク上のブロック | ファイルの一部(パス指定) | オブジェクト全体(キー指定) |
| ファイルシステム | 自分で作る | 向こうが持っている | 無い |
| 同時に繋げる相手 | 原則1台(Multi-Attachは条件付き) | 多数のサーバから同時に | HTTPが届けばどこからでも |
| 部分的な書き換え | 出来る | 出来る | 出来ない(丸ごと置き直す) |
| AZをまたげるか | 不可(同一AZ) | 可(リージョン内の複数AZ) | 可(既定で3AZ以上) |
| 向いている用途 | OSの起動ディスク、DBのデータ領域 | 複数台で共有する作業領域、既存アプリの移行 | 画像・動画・ログ・バックアップ |
この表の肝が「部分的な書き換え」の行です。S3には、1バイトだけ直すという操作がありません。5MBのファイルの末尾に1行足したければ、5MBを丸ごとアップロードし直します。追記も、ロックも、リネームもない。このあとの話は、ほぼ全部ここから派生します。
【コラム】マウント出来れば勝ち、ではなかった
「S3をファイルシステムとしてマウント出来れば、安くて無限の共有フォルダになるのでは」という発想は誰もが一度は通ります。実際 s3fs や goofys といったツールがあり、mount して ls すれば中身も見えます。
ただ、見えることと使えることは別です。ロックも追記もアトミックなリネームも無いので、それらに依存した普通のソフトウェアが普通に壊れます。私も昔「とりあえず s3fs でマウントしてあれば良いよ」と言われて本番に持ち込み、flock が効かずにバッチが二重起動してデータを壊したことがあります(詳しくは「s3fsで痛い目を見たので S3 Filesを調べ倒した」に)。
現在は公式の Mountpoint for Amazon S3 があり、2026年4月には東京リージョンでAmazon S3 Files(NFS経由でPOSIX互換)も一般提供されました。選択肢は増えましたが、「何が出来て何が出来ないか」を把握せずに使えるものではない、という点は変わりません。
丸ごと置き直すか、1行だけ直すか
ここまで来ると「じゃあ全部S3でええやん」と思えてきます。HTTPが届けばどこからでも触れて、AZもまたげて、画像も動画もログも置ける。ところが、ユーザのアカウント情報や口座残高をS3に置くのは、控えめに言って地獄です。理由は、先ほどの表の「部分的な書き換えが出来ない」に全部入っています。
ユーザ情報を1つのJSONファイルとしてS3に置いたとしましょう。メールアドレスを変えたいだけでも、やることは「丸ごとダウンロードし、1項目だけ書き換えて、丸ごとアップロードし直す」。ファイルが1MBあれば1MB往復します。ロックが無いと、同じ瞬間に別の処理が同じファイルを読んで書き戻したら、片方の変更は何の警告もなく消えます2。えらいこっちゃ。
「user_id が1000番台の人を、登録日順に並べて100件」のような取り出し方も出来ません。キーで引く倉庫なので、中身で絞り込みたければ全部ダウンロードして自分で調べることになります。100万件あれば100万回です。
データベースは、こういった処理を丸ごと引き受けるために存在しています。1行だけ更新する、条件で絞って並べ替える、複数の更新をまとめて「全部成功か、全部なかったことにするか」のどちらかに倒す。最後のがトランザクションで、その性質を並べたACIDの末尾のDは Durability(永続性)。「コミットしたと返事をした以上、直後に電源が落ちても残っている」という約束で、今回のテーマそのものが設計目標に入っているわけです。
前回、セッションの逃がし先として挙げたRedisもこの仲間に入ります。主記憶(メインメモリ)にデータを持つぶん速い。代わりに、再起動で中身が残るかどうかは永続化の設定次第です。失っても再ログインで済むセッションには最適で、注文履歴を置く場所ではありません。
逆に、動画や画像をデータベースに入れるのも筋が悪い。1行が巨大になるとキャッシュの効率は落ち、バックアップは重くなり、取り出すたびにDBのコネクションを握ったままになります。数MBのバイナリを BLOB 列に突っ込んだテーブルは、数年後に誰も触れない時限爆弾になります。アイコンくらいならええやろ、と自分で作ったことがあるので強くは言えませんが。
実体は安い倉庫へ、そこを指す目印と検索したい項目だけをデータベースへ。画像そのものはS3で、ファイル名・サイズ・投稿日時・投稿者IDはDBの行に入れておく。一覧表示はDBへの1回の問い合わせで済み、実体の転送はS3が受け持ちます。
この形のとき、DBとS3の間で整合性を保つ仕組みは誰も用意してくれません。S3に置いたあとDBへの書き込みが失敗すれば、誰からも参照されないオブジェクトが残る。逆順にすれば、行はあるのに実体が無い状態になります。前者は課金されるだけで実害が小さいので、先にS3へ置く順序が選ばれることが多く、残骸はライフサイクルルールや棚卸しで掃除します。綺麗な解決策ではなく、安全側に倒した割り切りです。
【コラム】Webサーバを太らせないための、署名付きURL
上の図で、ユーザが1GBの動画を上げるとき、その1GBはWebの箱を経由してS3へ流れていきます。箱のCPUも帯域も、ただの中継作業で食い潰されるわけです。
これを避けるのが 署名付きURL(presigned URL)です。アプリは「このキーに、これから10分間だけ、アップロードしてよい」という条件に自分の権限で署名し、そのURLだけをブラウザへ返す。ブラウザはS3へ直接アップロードし、箱が運ぶのは数百バイトのURLだけになります。
有効期限はCLIで最大7日、マネジメントコンソールからなら最大12時間です。長く取ったURLが漏れれば、その期間ずっと誰でも使えます。もっと怖いのが、URLの権限が「署名した人の権限」になること。署名に使う権限は、必要な操作とキーの範囲だけに絞っておくのが鉄則です。
クラウドに預けて消える仕事と、残る仕事
データを外へ追い出すと、箱の死は怖くなくなります。代わりに、置き場所そのものを守る仕事が始まります。といっても、その仕事はオンプレミスの頃よりだいぶ減りました。
自前でストレージを持っていた時代、最初の仕事は3年後の容量を見積もることでした。見積りを取り、稟議を通し、納品を待ち、ラックに載せて、やっと使える。多めに買えば使わないディスクの代金を払い続け、少なく買えば足りなくなった時に慌てる。どちらに転んでも誰かが詰められるお仕事です。
S3には、この見積りがありません。1オブジェクトあたりのサイズには上限があります(公式の案内では50TB)が、入れられる個数のほうは青天井です。つまり「容量無制限」の正体は、サイズの上限が消えたのではなく、何個置いても怒られないことのほう。ディスクを買い足す計画も、空き容量の監視も要らない。オンプレミスの時代を知っていると、これだけで十分に魔法です。
次に消えるのが、壊れた機器の面倒です。オンプレミスでは同じ内容を複数のディスクへ書いておき(RAID)、壊れたら差し替える。そのためにスペアを在庫し、保守契約を結び、ランプが赤くなったら交換に行く。差し替えた後は残りのディスクから中身を作り直すのですが(リビルド)、その最中にもう1本飛ぶと全部消えます。そこまでやっても守れるのは1つの建屋の中だけです。
S3は99.999999999%、いわゆる11ナインの耐久性を持つように設計されています。大半のストレージクラスは最低3つのアベイラビリティゾーンにまたがって冗長に保存され、チェックサムで定期的に検査されます。ディスクが壊れても、ラックごと落ちても、データセンタが1つ消えても残る。同じ水準をオンプレミスで作るなら、装置を2セット買って別の建屋に置き、その間を結ぶ回線を引き、同期の仕組みを運用し続けることになります。11ナインという数字の意味は、小数点以下の桁数ではなく、その構築と運用が月額に入っていることです。
保存時の暗号化も、いまのS3は既定で有効です。オンプレミスでは自分で仕込む仕事でした。残るのは「鍵を誰が握るか」の判断で、そこは「「とりあえずKMS」の前に」に書いてますし、次回でも取り上げます。
ただし、11ナインが守ってくれるのはここまでです。この数字は「預けたデータが、機器の故障で勝手に失われないこと」の設計上の目標値であって、操作ミスは守備範囲の外。つまり、ここから先が自分に残る仕事です。しかもオンプレミスの頃から何ひとつ変わっていません。RAIDでミラーしたディスクも、rm -rf を打てば複数のディスクから同時に消えます。S3も同じで、aws s3 rm s3://<本番バケット> --recursive を撃ち込めば、11ナインの冗長性をもって3つのAZから同時に消えます。冗長化の仕組みから見れば、どちらも正規の削除依頼を遂行したまでです。
だから操作ミスには、別の仕掛けを自分で有効にする必要があります。バージョニングを有効にすれば、削除は「削除マーカーを置く」操作に変わり、上書きも旧版を残したまま新版を積む形になります。一定期間の削除を禁じたければ Object Lock、別リージョンへ複製したければレプリケーション。いずれもデフォルトで無効なので、「S3に置いたから安心」と思わず、設定値を確認するようにしましょう。
戻せることを確かめるのも、肩代わりしてもらえません。確認すべきは「どこまで戻せれば良いか(RPO: Recovery Point Objective)」と「いつまでに戻せば良いか(RTO: Recovery Time Objective)」の2つ。RPOが1時間なら1時間ごとに取る必要があり、RTOが30分なら30分で復旧できる手段でなければ意味がありません。RDSのスナップショットは取得の指示こそ一瞬ですが、そこから新しいインスタンスを起こす復元には相応の時間がかかる。この差を測っていないと、障害の最中に初めて知ることになります。
バックアップ運用で一番多い失敗は取り忘れではなく、取れてはいたが、戻せなかったです。スクリプトは正常終了していたが中身が0バイトだった、暗号化キーを一緒に消していた、復元手順を知っている人が退職していた。どれも、実際に戻してみるまで分かりません。だから復元訓練をやるのですが、これが面倒なので真っ先に省かれる。消防訓練と同じで、日を決めて強制しないと一生やらないやつです。
請求書も引き取ることになります。オンプレミスは買った時点で金額が決まり、あとは電気代と保守費でした。クラウドは置いているデータ量がそのまま毎月の金額になるので、消し忘れたデータに何年も払い続けることが起きます。S3はアクセス頻度が下がるほど保管料が安くなるストレージクラスを持ちますが、安いクラスには最低保管期間が付きます。その期間より前に消しても、期間分は請求される。安いからクラスを落とす、ではなく、そのデータを置き続けるべきかを先に決めることです。
【コラム】安いのは保管料だけ、という読み違い
ストレージのコストは、置いておく料金(保管料)、出し入れする料金(リクエスト料)、外へ運び出す料金(データ転送料)の3つで出来ています。比較記事に載っているGB単価は、たいてい1つ目だけの話です。
アクセス頻度の低いクラスは、保管料が安い代わりに取り出し料金がかかり、リクエスト単価も高く設定されています。「毎日全件スキャンするバッチ」が走るデータを安いクラスへ落とすと、保管料で浮いた額をリクエスト料が軽く超えていきます。どのクラスを選ぶかは、容量ではなくアクセスのパターンで決まる、と覚えておくと事故が減ります。
迷うなら、アクセスパターンを見て自動で階層を移動してくれるS3 Intelligent-Tieringという選択肢もあります。料金体系は改定されるので、本番の設計をする前に必ず最新の料金表を見てください。
おわりに
時計を合わせ、相手を確かめ、住所を引き、荷物を届け、プロセスで処理し、権限で守り、箱に詰め、案内係が行列を捌く。ここまでの8回で、サーバが何台死んでもサービスが動き続ける形まで来ました。今回はそこに、サーバが死んでもデータは死なないという条件が加わりました。
やったことは、突き詰めれば置き場所を分けただけです。作り直せるものは箱の中に置いていい。OSもアプリケーションのコードも、イメージから作り直せます。作り直せないものは箱の外へ出し、丸ごと扱うならオブジェクトストレージへ、1行ずつ触るならデータベースへ。外に出した以上は、消えない仕掛けも、戻す訓練も、鍵の管理も、請求書も、自分の仕事として引き取る。
派手さはありません。うまくいっている限り誰の話題にもならない部類の仕事です。それでも、サーバの入れ替えが怖くなくなるのも、障害のあとに「データは無事です」と言えるのも、この地味な線引きが先に済んでいるからです。動いてて当たり前と思われているものほど、失われた瞬間に全員が気づく。NTPやDNSと同じ、縁の下のインフラの宿命です。
さて、データを箱の外へ出したことで、アプリと置き場所はネットワークで隔てられました。置き場所の側から見て、「この画像をください」と言ってきたのが自分のアプリなのか、まったく無関係の第三者なのかを、どうやって見分ければいいのでしょうか。権限と隔離の回で扱ったのは1台のLinuxの中の話でした。今度は、その線をクラウド全体に引き直すことになります。
次回「クラウドの権限と境界の基本の"キ"(IAM)」へ続きます。
お断り
記事内容は個人の見解であり、所属組織の立場や戦略・意見を代表するものではありません。
あくまでエンジニアとしての経験や考えを発信していますので、ご了承ください。
-
消えるのは停止・休止・終了したときと、下のディスクが壊れたときです。再起動では消えません(イベントごとの可否は公式の一覧にあります)。再起動で試すと残ってしまうので、気づくのはたいてい一度停止して起動し直した後になります。 ↩
-
既定の
PUTが無条件に上書きするからで、条件を付けて弾くことは出来ます。条件付きリクエストなら、If-None-Matchで「同じキーが無いときだけ置く」、If-Matchで「ETagが変わっていなければ上書きする」を指定できます。対象はPutObject・CompleteMultipartUpload・CopyObjectで、追加料金はありません。ただし衝突を検出して弾くだけなので、待たせて順番に通すロックの代わりにはならず、呼ぶ側でリトライを書くことになります。 ↩