くらしのマーケット開発ブログ

「くらしのマーケット」を運営する、みんなのマーケット株式会社のテックブログです

レポート作成機能の開発とS3・ECSに関する小話

こんにちは、バックエンドエンジニアの富永です!
今年も早いもので、もう11月ですね〜。
最近東京では過ごしやすい気候が続いています。寒さが苦手な自分にとってはずっとこのままでいてほしいな〜、と願って止まない今日この頃です。

さて私事ですが、今年6月にみんなのマーケットにジョインし、レポート作成機能という新規機能の開発を担当させていただきました。
その際に、Amazon S3(以下、S3)・Amazon Elastic Container Service(以下、ECS)を活用し開発しました。前職で少しAWSの経験はあったのですが、AWS SDKを使っての開発はほぼ初めての経験で、機能要件を満たすためにそれぞれのサービスを調べたり、操作する中で勉強になったことがありました。
そこで、本記事では、開発したレポート作成機能の説明とS3・ECSに関する小話をお伝えしたいと思います。

レポート作成機能の開発に至った背景

現在、コンサルティング本部の担当者が業務の一環で利用している、レポートがあります。 従来、このレポートは下記フローチャートの流れで作成していました。レポート作成は、月2〜3回の頻度で発生しており、その度にコンサルティング本部の担当者と、SREエンジニアの担当者の間でコミュニケーションが発生し、お互いに煩わしさを感じていました。 そこで、このコミュニケーションコストを削減することを目的に、今回のレポート作成機能の開発を行いました。

図1.レポート作成のフローチャート(レポート作成機能開発前)

   

レポート作成機能について

開発したレポート作成機能の画面イメージ1は、以下の通りです。
コンサルティング本部の担当者は、レポート作成画面で必要な情報を入力し、「レポートを作成」ボタンをクリックします。
入力した情報を元に、システム内部でECSタスクを実行するコマンドを生成し、ECSタスクを実行します。
実行したECSタスクの中では、複数種類のレポートを作成しており、レポートの作成が完了したら、そのファイルをS3にアップロードするようになっています。
なおレポートはhtmlファイルになっており、レポート確認画面の「開く」リンクをクリックすると、レポートを別タブで開くことができるようになっています。

図2.レポート作成画面の画面イメージ
図3.レポート確認画面の画面イメージ

S3・ECSに関する小話

本章では、S3・ECSについて調べたり操作していく中で分かったことについて、5点共有させていただきます。

1.【S3】AWS マネジメントコンソールからオブジェクトを作成する際の注意点

一度のレポート作成で、複数種類のレポートファイルが作成されるため、それらを一つのフォルダの中にまとめ、そこからファイルを取得するように実装しています。
これを実装する際に、AWS マネジメントコンソールを使って、一つのフォルダの中に、複数種類のレポートファイルをアップロードした後の状況を再現し、ファイル取得以降の処理を実装しようとしていました。
この際に、レポートファイルのみをオブジェクトとして取得したかったのですが、設定方法によっては期待通りに動かないことがあったため、そのときに学んだことを共有させていただきます。
例えば、「hoge」バケットの中にfuga/piyo.txtのファイルをアップロード(保存)する場合を考えてみましょう。

 hoge/(バケット)
  └ fuga/
    └ piyo.txt

AWS マネジメントコンソールからこの設定を行う方法として、以下の2パターンがあります。

  • パターン1. 「hoge」バケット直下から、「piyo.txt」が入った「fuga」フォルダをアップロードする方法
  • パターン2. 「hoge」バケット直下に、「fuga」フォルダを作成し「piyo.txt」をアップロードする方法

パターン1、パターン2の操作手順は、それぞれ以下の通りです。2

パターン1の操作手順

1.「hoge」バケットの「アップロード」ボタンをクリックします。


図4.手順1の画面例(パターン1)

2.開いた画面で「フォルダの追加」ボタンをクリックし「fuga」フォルダを選択した後、「アップロード」ボタンをクリックします。


図5.手順2の画面例(パターン1)

パターン2の操作手順

1.「hoge」バケットの「フォルダの作成」ボタンをクリックします。


図6.手順1の画面例(パターン2)

2.開いた画面で「フォルダ名(fuga)」を入力し「フォルダの作成」ボタンをクリックします。


図7.手順2の画面例(パターン2)

3.「fuga」フォルダに移動し、「ファイルを追加」ボタンをクリックし「piyo.txt」ファイルを選択後、「アップロード」ボタンをクリックします。


図8.手順3の画面例(パターン2)

以上のパターン1,2の手順を実施した後、どちらも画面上には、以下のような形で「piyo.txt」を確認することができます。


図9.「piyo.txt」の画面

次に、それぞれの操作完了後、「hoge」バケットの中身を取得します。

■パターン1の結果

$ aws s3api list-objects --bucket hoge
{
    "Contents": [
        {
            "Key": "fuga/piyo.txt", 
            "LastModified": "2021-11-10T02:48:47.000Z", 
            "ETag": "\"f290d73a3d0ba856b5d82eff7a1e8ece\"", 
            "StorageClass": "STANDARD", 
            "Size": 3,
            "Owner": {
                "DisplayName": "(Onwerの表示名)", 
                "ID": "(OnwerのID)"
            }
        }
    ]
}

■パターン2の結果

$ aws s3api list-objects --bucket hoge
{
    "Contents": [
        {
            "Key": "fuga/", 
            "LastModified": "2021-11-10T02:48:46.000Z", 
            "ETag": "\"af572fda0c25b593deaecb6663a081df\"", 
            "Size": 0,
            "StorageClass": "STANDARD", 
            "Owner": {
                "DisplayName": "(Onwerの表示名)", 
                "ID": "(OnwerのID)"
            }
        }, 
        {
            "Key": "fuga/piyo.txt", 
            "LastModified": "2021-11-10T02:48:47.000Z", 
            "ETag": "\"f290d73a3d0ba856b5d82eff7a1e8ece\"", 
            "StorageClass": "STANDARD", 
            "Size": 3,
            "Owner": {
                "DisplayName": "(Onwerの表示名)", 
                "ID": "(OnwerのID)"
            }
        }
    ]
}

パターン1だと「piyo.txt」しか存在しません。一方、パターン2だと「piyo.txt」と「fuga」フォルダがオブジェクトとして存在します。
これは、S3のデータモデルがフラットな構造になっていることが起因しています。
S3では、バケットを作成し、そのバケットにオブジェクトが直接格納されます。そのため、バケットの中には、そもそもフォルダという概念はなく「フォルダの作成」で、見た目としての「フォルダ」を作成した場合も、内部的にはオブジェクトとして管理さています。

今回のように、何も意識せずに、AWSマネジメントコンソールで「フォルダの作成」の操作を行ってしまうと、本来想定していなかった不要なオブジェクトを生成してしうまうことになってしまうため、意外と注意が必要だなと思いました。
なおAWS CLIやAWS SDKを使って、S3にファイルをアップロードする場合も同様の注意が必要です。

以上の話を踏まえ、今回の開発ではフォルダはオブジェクトに含めたくなかったため、パターン1の方法でアップロードし、開発を進めていくようにしました。

オブジェクトキー名の作成

Amazon S3 のデータモデルはフラットな構造をしています。バケットを作成し、バケットにオブジェクトを保存します。サブバケットやサブフォルダの階層はありません。ただし、Amazon S3 コンソールで使用されているようなキー名のプレフィックスや区切り記号を使用して論理的な階層を暗示できます。

2.【S3】署名付きURLの有効期限

レポートのhtmlファイルは、画面を描画するタイミングで署名付きURLを発行し、それをアンカータグにセットすることで、別タブで開くことができるようにしています。
署名付きURLを使うことで、本来はアクセス権限がないユーザでも、対象のファイルにアクセスすることが可能になります。
この署名付きURLには、有効期限があり、デフォルトでは15分に設定されています。
開発当初、有効期限を考慮できておらず、15分経過した後にページをリロードすると以下のようなエラー画面が表示されていました。
ちなみに有効期限は最大7日間、設定することができます。

図10.有効期限切れの画面例

有効期限 デフォルト15分(「Options Hash」部分を参照)

Options Hash (params): Expires (Integer) — default: 900 — the number of seconds to expire the pre-signed URL operation in. Defaults to 15 minutes.

有効期限 最大7日間

IAM ユーザー: 最大 7 日間有効 (AWS Signature Version 4 を使用した場合)

3.【S3】ライフサイクルの設定時間と物理削除の遅延

コンサルティング本部の担当者から「古いレポートは見れなくしてほしい」という要望があり、対応する必要がありました。
そこで活用したのが、S3のオブジェクトのライフサイクルになります。

オブジェクトのライフサイクルを設定することで、バックアップの取得、削除を自動で行ってくれます。

ここで注意しなければいけないのが、ライフサイクルの設定時間です。

ライフサイクル設定の要素

Amazon S3 は、ルールに指定された日数をオブジェクトの次の新しいバージョンが作成された時間に加算し、得られた日時を翌日の午前 00:00 (UTC) に丸めることで、時間を算出します。たとえば、バケット内に 2014 年 1 月 1 日の午前 10 時半 (UTC) に作成されたオブジェクトの現行バージョンがあるとします。現行バージョンを置き換えるオブジェクトの新しいバージョンが 2014 年 1 月 15 日の午前 10 時半 (UTC) に作成され、3 日間の移行ルールを指定すると、オブジェクトの移行日は 2014 年 1 月 19 日の午前 0 時 (UTC) となります。

上記から、日数計算の開始は 0時0分(UTC) とありますので、日本時間では 午前9時00分 から計算を開始するようです。 また、作成日時は丸めて算出されるということですので、削除日時は以下のようになります。

例:削除期間を1日に設定した場合(以下、全て日本時間) * 作成日時:2021年11月10日 午前8時59分→ 削除日時:2022年11月11日 午前9時00分 * 作成日時:2021年11月10日 午前9時01分→ 削除日時:2022年11月12日 午前9時00分

これを考慮し、日本時間の30日後に削除をしたかったため、ライフサイクルは以下のように設定をしました。 しかし、テストや運用で削除結果の様子を見ていたところ、午前9時00分ピッタリに削除されず、削除指定日のその日中には削除されるといった具合でした。
これに関しては公式からも、このような回答が出ているので、物理的なファイルの削除にはタイムラグがあるのかなと思っております。

図11.ライフサイクルの設定画面

4. 【ECS】タスクの実行結果は永久保存されない/【S3】ユーザ定義のメタデータの付与ができる

最後に、内部設計の際に調べていて気づいたことを共有させていただきます。

レポート確認画面では、レポート作成を実行したタイミングごとに、各種レポートのリンクと合わせて以下の情報を出力しています。

  • ECSタスクを実行した日時(レポートの作成日時)
  • ECSタスクを実行したユーザ(レポートの作成者)
  • ECSタスクの実行ステータス

この情報をどこに保存して、参照するのかについて、検討する必要がありました。 そこで、色々調べた結果、以下の4つの案について検討しました。

  • 案1.データベースに保存する案
  • 案2.ECSタスクの実行結果を取得する案
  • 案3.S3のオブジェクトに対し、ユーザ定義のメタデータを付与する案
  • 案4.S3にjsonファイルをアップロードする案

案1.データベースに保存する案

これは一瞬で却下となりました。 わざわざテーブルの新規作成や、カラム追加を行って管理するほど重要な情報ではないためです。

案2.ECSタスクの実行履歴を取得し、参照する案

次にECSタスクの実行結果を取得する案を考えました。 調べたところ、ListTasksというコマンドがあり、実行結果を一覧として取得することができそうでした。

しかし、よく見てみてると以下のような記述があり、タスクが停止したものは1時間しか保存されず、永久的に参照することは難しいようでした。そのためこの案は、却下となりました。

Recently stopped tasks might appear in the returned results. Currently, stopped tasks appear in the returned results for at least one hour.

案3.S3のオブジェクトに対し、ユーザ定義のメタデータの付与する案

他にも情報収集を行っていたところ、S3にアップロードするオブジェクトに対して、ユーザ定義のメタデータ情報を付与できることを知りました。

ユーザー定義のオブジェクトメタデータ

オブジェクトをアップロードするときに、そのオブジェクトにメタデータを割り当てることもできます。このオプション情報は、オブジェクトを作成するための PUT リクエストまたは POST リクエストを送信するときに、名前と値 (キーと値) のペアとして指定します。REST API を使用してオブジェクトをアップロードするときは、オプションのユーザー定義メタデータ名を「x-amz-meta-」で始め、他の HTTP ヘッダーと区別する必要があります。

例えばS3にレポートのhtmlファイルをアップロードする際に、今回画面上に表示したい情報をメタデータとして付与し、そこを参照することで、今回の要件を満たすことができると考えました。
しかし、この案は採用しませんでした。確かに要件は満たせそうだったのですが、今回はレポート作成を実行したタイミングごとの情報があれば十分だったので、この案は却下となりました。
レポートファイルごとに実行状況のステータスなどが必要な場合は、こちらを採用しても良かったのかなと思っています。

案4.S3にjsonファイルをアップロードする案

今回の開発では案4を採用し、次のように実装しました。
ECSタスクの実行ステータスを「作成中」として、下記情報を含んだjsonファイルを生成し、レポート作成前にS3にアップロードします。

  • ECSタスクを実行した日時(レポートの作成日時)
  • ECSタスクを実行したユーザ(レポートの作成者)
  • ECSタスクの実行ステータス

全てのレポートの作成が完了したら、ECSタスクの実行ステータスを「完了」にしたjsonファイルを、S3にアップロードします。
なおレポート作成中にエラーが発生した場合は、ECSタスクの実行ステータスを「エラー」にしたjsonファイルを、S3にアップロードします。

ECSタスクの実行開始までに若干のタイムラグはありますが、シビアな同期は求められず、やりたかったことも実現できたため、今回の開発では案4を採用しました。

おわりに

本記事では、レポート作成機能の説明とS3・ECSに関する小話をまとめました。
改めて、クラウドサービスについて知っていることで選択肢の幅が広がるな、と実感しました。それと同時に、全てをクラウドサービスで解決するのではなく要件に合わせてベストな選択をしていくことが大切、ということも実感しました。
また、くらしのマーケットでは他のAWSサービスも活用しているため、それらについても学んでいきたいなと思いました!

最後にみんなのマーケットでは、くらしのマーケットのサービス開発を一緒に盛り上げてくれるエンジニアを募集しています!
詳しくは、こちらをご覧ください。


  1. 今回機能開発したシステムは、弊社の社員が利用するシステムで外部公開していません。そのため、画面イメージを用いて説明しています。

  2. パターン1でオブジェクトの作成と確認を行った後、一度オブジェクトを全て削除し、その後パターン2で新しくオブジェクトの作成し、確認を行っています。その情報をまとめていますので、ご了承ください。