CSV ファイルのダウンロード機能を作りました

クロスワープの大鷲です。

MODD には少し前から、アンケートを受け付けるという機能が搭載されています。
しかし、これまでは、その回答を MODD の管理画面からダウンロードすることができないという機能の不足がありました。
最近、その機能不足を解消するために、アンケートの回答を CSV 形式でダウンロードする機能を作っていました。
これまでは社内のエンジニアが依頼を受けて対応していましたが、今後はお客様が管理画面からダウンロードすることが可能になります。

従来からあった受注データの CSV ダウンロード機能なども、将来的にはこの仕組みに乗せていく予定で、そのために、使いまわせるような設計を意識しました。

設計概要

「ダウンロードが要求されたら、その要求を一旦キューに入れて、バックグラウンドでファイルを作成し、出来たらダウンロード URL をメールで通知する」というのを基本方針としました。
MODD では従来から AWS を利用しているため、AWS のサービスを使った設計を考えました。
結果、以下のようなものになりました。

  • キューの代わりに S3 に要求内容を書いたファイルをアップロード
  • そのファイルが作られたことを Lambda で検知して処理開始
  • Lambda の処理時間制限を回避するために、実処理は AWS Batch にやらせる
  • 最終的に CSV ファイルができたら、それをまた S3 にアップロード
  • 時限 URL を発行して、SES でメール送信

f:id:cw_owashi:20180629131155p:plain

実行要求

MODD の管理画面で「ダウンロード」ボタンを押すと、ライブラリが以下のような JSON ファイルを作り、S3 にアップロードします(実際には改行もコメントもありません)。
このファイルを「要求ファイル」と呼んでいます。

{
    // 一意なリクエスト ID
    "RequestId": "05c526d8-6d1b-43f4-8332-165f4ae2c934",

    // 実行する AWS Batch のジョブ定義名
    "BatchId": "survey-download-development",

    // AWS Batch のジョブに渡すパラメーター
    "Parameters": {
        "SURVEY_ID": "8d875764-b793-4f80-a81d-286155a91923"
    },

    // 時限 URL の有効期限
    "ExpireAt": "2018-07-01T10:52:24.0357505+09:00",

    // E メール通知の設定
    "EmailNotification": {

        // メールの差出元アドレス
        "From": "example@crosswarp.com",

        // メールの受信者のアドレス
        "Recipients": [
            "owashi@crosswarp.com"
        ],

        // メール テンプレート名
        "TemplateName": "modd-data-download-development"
    }
}

バッチの実行

上記のような要求ファイルが S3 にアップロードされたことをトリガーにして Lambda が起動します。
しかし、ファイルに出力したいレコード数はこの時点では未知のため、ファイル生成にどれくらい時間がかかるのかわかりません。
Lambda には 5 分という実行時間制限があるため、AWS Batch という仕組みに逃がしています。

AWS Batch

AWS Batch は Docker コンテナーを実行するマネージド サービスです。
AWS ECS (Elastic Container Service) 上で動きますが、ECS よりもさらに抽象化されており、起動して、処理して、終了するという形態に特化しています。
処理要求はキューに入れられて順次処理されるようになっています。

今回は、コンテナーの中に 2 つのアプリケーションを入れています。
1 段目(共通処理アプリ)は上記の要求ファイルを読み込んで、2 段目のアプリ(個別処理アプリ)を起動します。その際に、要求ファイル中の Parameters の値を個別処理アプリに渡します。
個別処理アプリは、渡されたパラメーターに基づいて、DB に対するクエリを行い、CSV ファイルをローカル ディスク上に生成します。
個別処理アプリの実行が終了すると、それを待っていた共通処理アプリが、作られたファイルを拾い上げて S3 にアップロードします。

メールでの通知

CSV ファイルが S3 にアップロードされると 2 つめの Lambda が動き出します。
ここでは、まず、アップロードされたファイルに対して時限(署名付き)URL を生成します。
その後、SES のテンプレート メール機能を使って、メールの受信者(管理画面のログインユーザー)に URL をメールで通知します。

メールの送信に関する情報も要求ファイルに含まれているため、CSV ファイルの情報から要求ファイルを特定しなければなりません。
ここは、CSV ファイルのファイル名を、要求ファイルと同じ(拡張子だけが異なる)ものにすることで実現しています。

後始末

この仕組みの中では、作成した要求ファイルや CSV ファイルをクリーンアップしていません。そのため、そのままでは、使い終わった不要なファイルが S3 にどんどん溜まってしまいます。
この問題は、S3 のバケットにライフサイクル ポリシーを設定することで、作成から一定期間で自動削除されるようにすることで対処しています。

今後の展開

まずはアンケートの回答ダウンロードから実装しましたが、今後、受注データや商品データのダウンロードなども、この方式に置き換えていく予定です。
そのためには

  • 受注や商品のダウンロード ボタンのクリック時の処理を、上記のような要求生成処理に置き換える
  • AWS Batch 内で動いて実際にファイルを生成する個別処理アプリを、受注データ用、商品データ用などでそれぞれ作成する

という作業が必要になります。

アンケートでは、パラメーターはアンケート ID だけでしたが、たとえば受注データ ダウンロードの場合、ダウンロード対象の受注番号をすべてコマンドラインに乗せることはできません。
そのような場合は、受注番号のリスト ファイルを作り、それを要求に含めます。
リスト ファイルを S3 にアップロードして、そのパスをパラメーターに含めるのはクライアント側ライブラリがやりますので、呼び出し側である MODD 管理画面は S3 を直接意識する必要はありません。
また、要求パラメーターに S3 のパスが書かれていると判断した場合、AWS Batch 内の共通処理アプリが S3 からダウンロードして、ローカル ディスク上のパスに書き換えて、個別処理アプリに渡します。
そのため、個別処理アプリは、ローカル ディスクからリスト ファイルを読み込んで、ローカル ディスクに結果のファイルを作るだけです。
S3 とのやりとりは、すべて共通処理アプリに任せられるようにしています。

終わりに

今回の件は、MODD で初めて、Docker や AWS Lambda を実践投入する案件となりました。
そのため、Docker コンテナーのビルドのために Linux (Ubuntu) サーバーを立てたり*1、Lambda のバージョン管理の仕組みを学んだり、全面的に .NET Core で作ったりと、新しいことをいろいろと学びました。

また、今回は Lambda の実行時間の制約を逃れるために Batch を使いましたが、Step Functions の利用なども検討していきたいと思います。

今後もこうした新しいテクノロジーの採用を進め、MODD を刷新して行きたいと思っています。

*1:社内のビルド サーバーが Windows Server 2012 R2 なので Docker for Windows がインストールできませんでした