フレクトのクラウドblog re:newal

http://blog.flect.co.jp/cloud/からさらに引っ越しています

AWS Step Function をユニットテストするには

エンジニアの佐藤です。こんにちは。今回は「AWS Step Function のユニットテスト」を構想し、実装までに直面した課題と私が考えた解決方法についてお話ししたいと思います。

AWS Step Functionとは

公式ページに書かれてますが、 Step Functions は様々なサービスをつなげて「サーバーレス・ワークフロー」を編成する仕掛けです。 AWS には古来から様々なワークフロー支援サービスがありますが、その中では最も新しいものです。

ことの始まり

筆者はある日、とある業務システムをどうやって実装したものかと考えていました。AWSでやってくれという話でしたので、AWSの各種サービスを比較検討し、最終的にStep Functionsを選択しました。重視したポイントは以下のようなものです。

  1. 拡張性が高い。
  2. 高可用性設計。
  3. ワークフローの進捗が視覚的に確認できる。
  4. ワークフロー定義が読みやすい。

今回の業務システムには短時間で完了する軽いタスクと、リソースも時間も必要な重いタスクの2種類があり、筆者は前者をLambda Functionに、後者をECS(Fargate)で実行することにしました。

Step Functionのユニットテスト・・・って?

ところで昨今のシステム開発は、テストコードを記述してユニットテストを行うのが一般的です。筆者はこのStep Functionsで作成されるワークフロー設定(ステートマシン)についてのユニットテストを構想しました。ところが、考えていくうちに、これが結構深いタスクであることに、徐々に気がついてきました。一般的な話ですが、システムを人間の体に例えると、手足に相当する機能のユニットテストについては、準備作業はそれほど難しくありません。一方で中枢的な機能のテストのための準備作業は大きくなりがちです。動作が確実な仮の末端部品を接続してあげる必要があるからです。「ワークフロー管理」機能は、この観点から言うとシステムの最も中枢的な部分であり、その単体テストのためには仮の末端部品の接続が必要ということになります。今回で言えば、Lambda FunctionとECSに仮のモジュールを入れてあげる必要がある。

Lambda FunctionとECSの仮のモジュール、と構想したところで、筆者は更に寒気を覚えました。よく考えてみるとこの2つ、機能コードを配置するまでの作業がそれなりに手間なのです。例えばLambda Functionの場合、以下のようなステップがあります。

  1. ライブラリを含めた機能コードのアーカイブ
  2. アーカイブのS3への配置
  3. IAMの設定
  4. Lambda Functionの設定

ECSの場合はこうなります。

  1. 機能コードを含むDockerコンテナのビルド
  2. ECRへコンテナを配置
  3. IAMの設定
  4. タスク定義の作成

この「Lambda Functionの設定」と「タスク定義の作成」には、環境変数などの設定も含まれます。テスト用の仮のモジュールなどと言いながら、一式全部必要なわけです。ステートマシンのテストをするために「システムの設計方針に従って各種の設定を発行・注入する仕掛け」の開発が必要になる・・・そう気がついた筆者は、とんでもないことを始めてしまったのではないかと、暗い気持ちになりました。中でも筆者が負担に感じたのがIAMです。Lambda FunctionもECSも機能コードの実行環境であり、実行の際に「インフラ的にできること(実行権限)」の範囲をIAMロールとして定義する必要があります。しかしこれがまた、慎重さと地道な努力を要する作業なのです。「とりあえずAdmin!」とやっつけたい気持ちはやまやまですが、これは悪魔の囁き。権限設定はミニマムからスタートしないと、権限のダイエットはとても大変なのです。

なお、Lambda FunctionとStep Functionsについては、AWSがローカルランタイムを配布していますが、今回は利用を見送りました。ローカル版のFargateはありませんし、ローカル環境固有の設定負担や互換性問題もあるでしょう。それなら最初から本物を使ったほうがいい、ユニットテスト用のAWSアカウントを短時間使うだけならそんなに経費もかからない、と判断しました。

各種設定の発行・注入する仕掛け・・・って?

で、その仕掛けをどうしたのかというと、この点は大変悩んだところでした。

筆者が最初に検討したのはAWS Cloud Formationでした。しかし調べていくと、これはこれで良くないところがあると思いました。まず引っかかったのが、テンプレートの入出力がいずれもリストとなっており、階層的にできない点です。また、最終的にJSONYAML形式のテンプレートにする必要があり、いくつか見て回った範囲ではこのテンプレートの調整負担が大きく見え、Colud Formationは見送りました。

次に検討したのがCDKでした。re:Invent18で発表されたもので、基本的にNodeJSやPythonなどのプログラム言語でCloud Formationのテンプレートを効率的に作成する仕掛けです。しかしこちらはStep Functionsの実装がまだプレビュー状態で、またステートマシンの定義方法が独特で良くないと思いました。また、ランダムな付加文字列の入ったリソース名となる点も、やり込むと散らかって良くないだろうなと思えました。

筆者が最終的に選択したのは、以下のような方式でした。

  1. まずプロジェクト名に連なる一連の名前体系を決め、これを環境変数で設定する。
  2. Python言語でdeploy.pyとundeploy.pyを記述し、ここからboto3 SDKを用いて直接リソースの作成と削除を行う。リソース定義は、これらのソースコードに記述する。
  3. ユニットテストはdeploy.py実行 -> テスト -> undeploy.py実行のフローで行う。

幸いなことに今回のシステムの開発言語はPythonでした。そしてAWSリソースの定義は、たいていJSONで書かれており、Python言語のdictionaryとほとんど同義(しかもコメントも挿入できる)。そして複数のリソース定義を体系的に作っていくにはプログラムフローが効率的で、デプロイ自体もその延長でSDKを呼び出してやってしまえば、小さくまとまらないかと目論んだわけです。

最初に「プロジェクト名に連なる一連の名前体系を決める」ですが、今回構想した「Step FunctionsステートマシンがLambda FunctionとECSコンテナを実行する」実装の場合、以下のようになりました。

export AWS_ACCOUNT_ID=`aws sts get-caller-identity --output json | jq -r -j ".Account"`
export AWS_DEFAULT_REGION=`aws configure get default.region | tr -d \\n`
export PROJECT_NAME=project01

export LAMBDA_FUNCTION_NAME="${PROJECT_NAME}_lambda"
export LAMBDA_FUNCTION_ARCHIVE_NAME="${FUNCTION_NAME}.zip"
export S3_LAMBDA_ARCHIVE_PATH="${PROJECT_NAME}/${FUNCTION_ARCHIVE_NAME}"
export BUCKET_NAME=s3_bucket_name12345s
export LAMBDA_POLICY_NAME="${PROJECT_NAME}LambdaPolicy"
export LAMBDA_ROLE_NAME="${PROJECT_NAME}LambdaRole"
export LAMBDA_FUNCTION_ARN="arn:aws:lambda:${AWS_DEFAULT_REGION}:${AWS_ACCOUNT_ID}:function:${LAMBDA_FUNCTION_NAME}:prd"

export ECR_REPOSITORY_NAME=$PROJECT_NAME
export ECS_CLUSTER_NAME=$PROJECT_NAME
export ECS_CONTAINER_TAG="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_DEFAULT_REGION}.amazonaws.com/${ECR_REPOSITORY_NAME}:latest"
export ECS_TASK_ROLE_NAME="${PROJECT_NAME}TaskRole"
export ECS_TASK_POLICY_NAME="${PROJECT_NAME}TaskPolicy"
export ECS_TASK_EXECUTION_ROLE_NAME="${PROJECT_NAME}TaskExecutionRole"
export ECS_TASK_EXECUTION_POLICY_NAME="${PROJECT_NAME}TaskExecutionPolicy"
export ECS_TASK_DEFINITION_NAME="${PROJECT_NAME}Task"
export ECS_SUBNETS="subnet-12345678"

export SFN_NAME=$PROJECT_NAME
export SFN_ROLE_NAME="${PROJECT_NAME}SfnRole"
export SFN_POLICY_NAME="${PROJECT_NAME}SfnPolicy"

もういきなりお腹いっぱいな感じがありますが、ここに列挙したどれ一つ不要なものはありません。機能コードをサーバーレスに埋めていくとは、こういう手間を伴うものなのです。

デプロイコードとユニットテスト

そしてここからLambda Function、ECSコンテナ、そしてStep Functionsステートマシンをデプロイするコードを書いていくわけですが、ステートマシンをデプロイする部分をご紹介すると、以下のようになりました。

def deploy():
    print('deploying...')

    # (中略)

    containerDef = {
        'CLUSTER_ARN': f"arn:aws:ecs:{AWS_DEFAULT_REGION}:{AWS_ACCOUNT_ID}:cluster/{ECS_CLUSTER_NAME}",
        'TASK_DEFINITION_NAME': ECS_TASK_DEFINITION_NAME,
        'CONTAINER_TAG': ECS_CONTAINER_TAG,
        'ENVIRONMENTS': [
            {
                "Name":"TASK_TOKEN",
                "Value.$":"$$.Task.Token"
                },
            {
                "Name":"DATA",
                "Value.$":'$'
            }
        ]
    } # end of containerDef

    sm = get_steps(
        lambdaFunctionArn=LAMBDA_FUNCTION_ARN,
        containerDefinition=containerDef
    )

    sfnClient = boto3.client('stepfunctions')
    print('creating state machine...')
    # print(json.dumps(sm, indent=4))
    res = sfnClient.create_state_machine(
        name=SFN_NAME,
        definition=json.dumps(sm, indent=4),
        roleArn=f'arn:aws:iam::{AWS_ACCOUNT_ID}:role/{SFN_ROLE_NAME}',
        tags=[TAG_sl]
    )
# end of deploy

Step Functionsのステートマシン自体は、JSON文書として記述されます。ここへステートマシンから実行するLambda FunctionやECSのタスク情報を含めていくわけですが、これをステートマシン定義取得関数get_stepsの引数で指定するようにしておきました。 こうすれば、ユニットテストからこのデプロイコードが呼び出されたときに、本番とは違う仮のLambda FunctionやECSタスク情報を設定することができるだろうとの目論見です。

get_steps自体は以下のような実装になりました。

def get_steps(
    lambdaFunctionArn=None,
    containerDefinition=None
):
    SFN_NAME = os.environ['SFN_NAME']

    sm = {
        "Comment": SFN_NAME,
        "StartAt": "STEP1",
        "TimeoutSeconds": 300,
        "States": {
            "STEP1": {
                "Type": "Task",
                "Resource": lambdaFunctionArn,
                "TimeoutSeconds": 10,
                "InputPath": "$.input",
                "ResultPath": "$.res1",
                "OutputPath": "$",
                "Next": "STEP2",
                "Catch": [ {
                    "ErrorEquals": [ "States.ALL" ],
                    "Next": "FAILED"
                }]
            }, # end of STEP1
            "STEP2": {
                "Type": "Task",
                "Resource": "arn:aws:states:::ecs:runTask.waitForTaskToken",
                "TimeoutSeconds": 300,
                "Parameters": __create_ecs_parameters(containerDefinition),
                "InputPath": "$.res1",
                "ResultPath": "$.res2",
                "OutputPath": "$",
                "Next": "SUCCEEDED",
                "Catch": [ {
                    "ErrorEquals": [ "States.ALL" ],
                    "Next": "FAILED"
                }]
            }, # end of STEP2
            "SUCCEEDED": {
                "Type": "Succeed"
            },
            "FAILED": {
                "Type": "Fail"
            }
        } # end of States
    } # end of sm

    return sm
# end of get_steps

これはステートマシンのユニットテストの方法論の開発のためのテストのために仮組みしたものですが、これが下の図のようなステートマシンになります。

f:id:masashi-sato-flect:20191209141151p:plain
ステートマシン

予想外に苦労したのが __create_ecs_parameters の部分です。ステートマシンからECSを呼び出すにはタスク定義のARNを指定する必要がありますが、このタスク定義は更新の度に「project01Task:77」のように末尾の序数が加算されていき、この数字を指定する手段はありません。つまり目的のコンテナが指定されているタスク定義を(たいていは最新のタスク定義でしょうが)掘り出すしかないようなのです。これを掘り出してステートマシンのコンテナ定義を作成するのが、下記のような内容の __create_ecs_parameters 関数になります。

def __create_ecs_parameters(container_definitions):

    ECS_SUBNETS          = os.environ['ECS_SUBNETS'].split(',')
    TASK_DEFINITION_NAME = container_definitions['TASK_DEFINITION_NAME']
    CONTAINER_TAG        = container_definitions['CONTAINER_TAG']
    
    ecsClient = boto3.client('ecs')

    lTd = []
    nextToken = None
    while True:
        params = {
            'familyPrefix': TASK_DEFINITION_NAME,
            'status':       'ACTIVE',
            'sort':         'DESC'
        }
        if nextToken != None:
            params['nextToken'] = nextToken
        res = ecsClient.list_task_definitions(**params)

        if 'nextToken' in res:
            nextToken = res['nextToken']
        lTd.extend(res['taskDefinitionArns'])
        if nextToken is None:
            break
    # end of while (true)

    tdArn = ''
    for td in lTd:
        tdDesc = ecsClient.describe_task_definition(
            taskDefinition=td
        )
        image = tdDesc['taskDefinition']['containerDefinitions'][0]['image']
        # print(image)
        if image == CONTAINER_TAG:
            tdArn = td
            break
    # end of for (td)
    if tdArn == '':
        raise Exception(f'no task definition {TASK_DEFINITION_NAME} found for container {CONTAINER_TAG}')

    ret = {
        'LaunchType': 'FARGATE',
        'Cluster': container_definitions['CLUSTER_ARN'],
        'TaskDefinition': tdArn,
        'Overrides': {
            'ContainerOverrides': [{
                'Name': TASK_DEFINITION_NAME,
                'Environment': container_definitions['ENVIRONMENTS']
            }] # end of ContainerOverrides
        }, # end of Overrides
        'NetworkConfiguration': {
            'AwsvpcConfiguration': {
                'Subnets': ECS_SUBNETS,
                'AssignPublicIp': 'ENABLED'
            }
        } # end of NetrowkConfiguration
    }
    return ret
# end of __create_ecs_parameters

このような工夫を経て、ようやく当初の目的の「ステートマシンのユニットテスト」に到達しました。テストコード全体は以下のようになりました。

class TestSfn(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        deploy(test=True)
    # end of setUpClass

    @classmethod
    def tearDownClass(cls):
        undeploy()
    # end of tearDownClass   
    
    def test_sfn(self):
        
        AWS_ACCOUNT_ID     = os.environ['AWS_ACCOUNT_ID']
        AWS_DEFAULT_REGION = os.environ['AWS_DEFAULT_REGION']
        SFN_NAME           = os.environ['SFN_NAME']
        
        sfnClient = boto3.client('stepfunctions')
        
        execName = SFN_NAME + '_' + str(datetime.now().timestamp())
        input = {
            'input': {"Number1":10,"Number2":5}
        }
        
        res = sfnClient.start_execution(
            stateMachineArn=f'arn:aws:states:{AWS_DEFAULT_REGION}:{AWS_ACCOUNT_ID}:stateMachine:{SFN_NAME}',
            name=execName,
            input=json.dumps(input)
        )
        execArn = res['executionArn']
        res = {}
        while True:
            res = sfnClient.describe_execution(
                executionArn=execArn
            )
            status = res['status']
            if status != 'RUNNING':
                print(res)
                break
            time.sleep(5)
        # end of while True
        output = json.loads(res['output'])
        self.assertEqual(output['res2']['Difference'], 5)
    # end of test_sfn
# end of class TestClass01

このテストコードを実行することで、当初の目論見であったステートマシンのユニットテストが実行できるようになりました。テスト用のLambda Functionやコンテナと組み合わせれば、複雑な状態管理の作り込みと繰り返しの動作確認が可能になったと思います。

ふり返って、どうか

正直なところ、なかなか複雑な気持ちです。ステート管理・高可用性・柔軟かつ効率的なリソースアロケーションを目標として計画した目論見でしたが、結局CI/CDみたいなことをやることになってしまいました。Step Functionを本格的に業務活用しようとすると、自動デプロイの仕掛けを何かしら用意する必要があると感じました。設計目標はクリアできそうですが、けっこう大変でした。

特に設定が煩雑になったのはECSを呼び出す部分です。この点については以下のような支援機能がStep Functionに用意されていると、より開発作業が効率的になるのではないかと思いました。

  • ステートマシンとECSの入出力の高機能化。ステートマシンの入力値をJSONのまま受け渡しできるAPIの追加など。(現状では環境変数文字列にする必要がある。)
  • 個別のステートマシンからはECRのコンテナのみを指定して実行できるように簡略化。ステートマシン全体に既定のコンテナクラスタやタスク定義を設定する機能など。

最後までお読みいただきありがとうございました。

フレクトが行くAWS re:Invent 2019 (12/5)

CI事業部ウエダです。こんばんは

本日はFLECTとして参加するのは最終日となります。

先ほどre:Playが終わり、ホテルに戻ってきたの余韻に浸りながらre:Playの速報をお送りしたいと思います

会場

f:id:flect-ueda:20191206171311j:plain 各レセプション会場でもすごい人数ですが、一堂に集まるのでとにかく広いです。 セキュリティのためか持ち込むものも厳格に決められています。 とはいえ、飲食物は用意していただけるので特に必要なものはありません。

食事

会場につくとまず食事 f:id:flect-ueda:20191206171257j:plain 様々な料理が屋台として出店されています。

1つ1つの食事の量は少なめなので食べ比べる楽しみがあります。

また食事とは別に、飲み物を提供する場所が点在しており、好きなものを注文できます。 f:id:flect-ueda:20191206172622j:plain

テント

大きなテントがいくつか用意されておりそれぞれ、ライブやハウスミュージックが大音量でかかっています。 f:id:flect-ueda:20191206172825j:plain ライブは近くで聞くと重低音が響き、迫力があります。

f:id:flect-ueda:20191206173726j:plain 別のテントではいい感じのリズムで周りを盛り上げます。

ゲーム

大型のゲームと80〜90年代のレトロゲームが設置してあります。 f:id:flect-ueda:20191206172855j:plain 私は試しにボタン早押しゲームに参加しました。

接戦の結果勝利することができましたが、対戦相手とは互いの検討を祝してレトロゲームをやったりと楽しめました。

こういった意図しない出会いの場が生まれるものre:inventのいいところだと思います。

ドローン

最後はドローンです。夜空に様々な形を繰り出す姿は圧巻です。

最後にIntelのロゴを表示するのはご愛嬌でしたが、、、 f:id:flect-ueda:20191206173222j:plain

大変エキサイティングな体験ができた1週間でした。

名残惜しいですがこれから帰国です。

この体験を業務で生かして行こうと思います。

フレクトが行くAWS re:Invent 2019 (12/4)

みなさんこんにちは。技術開発室の岡田です。

現在ラスベガスで絶賛開催中のAWS re:invent2019に参加しています。 昨日のキーノートでは怒涛の新サービス発表がありましたね。 特に私の担当している機械学習関連では、SageMakerの新サービスが多く発表され、スーパーエキサイティングな1日になりました。 興奮醒めやらぬ状況ではありますが、この発表をうけて新たに追加されたセッションも含め、本日までに参加できた機械学習関連のセッションについて、ご報告したいと思います。 (ちょっと専門用語が多めになります。)

ちなみに、今日も朝ごはんと昼ご飯は提供されました。😊

キーノート振り返り

f:id:Wok:20191205053101j:plain

昨日のキーノートでは、レイヤごとに次の新サービス・機能が発表されました。

  • Framework

    • TensorFlow: 20%高速化(MASK R-CNN)
    • PyTorch: 22%高速化(同上)
    • mxnet: 22%高速化(同上)
  • SageMaker

    • SageMaker Studio IDE: 以下のものを含むMLのための統合開発環境
    • SageMaker Notebooks: 起動の高速化や、コンテナ管理と連動できるように拡張したNotebook
    • SageMaker Experiments: 実験ごとのパラメータや設定を管理
    • SageMaker Debugger: トレーニング中に発生した問題をリアルタイムに特定
    • SageMaker Autopilot: トレーニングに最適なパラメータを探索
    • SageMaker Monitor: 運用中のモデルの精度をモニタリング
  • Service

    • Amazon Kendra:検索
    • Amazon Fraud Detector: 不正検知
    • Amazon Cloud Guru: コードレビュー
    • Contact Lens:コールセンタ向け

これらに関連して追加されたセッションのうち、本日までに次のものに参加することができました。

  • Optimizing Your Machine Learning Models on Amazon SageMaker
  • The new Amazon SageMaker Model Monitor: Address concept drift & model quality
  • Intro to Amazon SageMaker Debugger: Get insights into ML model training
  • Introducing Amazon SageMaker Studio, the first full IDE for ML

また、関連して次のものもセッションに参加しました。

  • Leadership session: Machine learning

Optimizing Your Machine Learning Models on Amazon SageMaker

機械学習モデルをトレーニングするときのパラメータをSageMaker Autopilotを用いて自動でチューニングして、よりよいモデルを作成するワークショップです。

パラメータチューニングの方法にはグリッドサーチやランダムサーチを使うことが多いですが、SageMaker Autopilotはガウス過程回帰とベイズ最適化を用いてパラメータを探索します。また、ここでチューニングできるパラメータは一般的なハイパーパラメータに加え、機械学習アルゴリズム自体も自動で選択してくれます。さらに、Feature Engineeringも実施してくれるようです(!?)。探索の結果いくつかの候補を出してくれるので、(一般的には)もっともよいメトリックのモデルを選択する、ということになります。

ただ、やはり会場ではパラメータチューニングに伴って発生する料金についての質問が出ていました。パラメータチューニングするためには何度もトレーニングを実施する必要があり、大量の計算リソースを必要としますので、当然の懸念だと思います。そして回答も至極当然の回答で、探索(トレーニング)にかかった時間だけそのインスタンスの料金を払うことになるよ、とのことでした。大きなモデルに使うと結構な料金がかかってしまうのではないかという不安はありますね。ただ、グリッドサーチよりも少ないイテレーションでより良いパラメータを見つけてくれる期待はあるので、グリッドサーチをやる前提であるのであれば、代わりにこちらを使った方が安上がりになる可能性もあります。また、あまり機械学習に詳しくない人にとっては、ほぼ全自動でモデルの性能を向上させることができるという点は見逃せません。このため結構使われるのではないかと思います。とても期待のできる機能だと思いました。

このワークショップで用いたノートブックは下記のgithub上にあります。 実行する際には使用したリソースの料金が発生しますのでご留意ください。

なお、このノートブックのLab2を実行すると、Autopilotで作成したパラメータの候補の一覧を中間生成物のノートブックで見ることができます。残念ながらワークショップで利用した環境のものはすでに消されてしまったようで詳細確認できなくなってしまいましたが、記憶だとFeature Importanceなど使ってしっかりFEしてたような気がします。いや、すごい。

The new Amazon SageMaker Model Monitor: Address concept drift & model quality

SageMaker Model Monitorのセッションです。

concept driftは、ざっくり説明すると、モデル作成中に想定した世界が時間の流れとともに変化し、モデルが現実世界に対応できなくなることです。一般的には、上記のAutopilotのようなことを行い、モデル作成の際に与えられたデータを用いて、そこで(汎化性能も含た)最高の性能を出せるようにチューニングします。しかし、時間とともに取得できるデータのばらつきが変わるため、徐々に性能が劣化していきます。例えば、温暖化の影響を受けて天候が変わるとか。最近の日本の気候もだいぶ変わったように思いますよね。このため、モデルは作成時点が最も精度が高く、徐々に劣化していくといわれたりします。

Model Monitorはこの劣化度合いをモニタリングして、一定の条件に至るとアラートを上げる機能とのことです。ただ、今回の説明を聞く限り、この劣化を検知するための条件は、(Model Monitorがリコメンドしてくれるようですが、最終的には)モデル開発者が決める必要があるようです。下図のBaseline statistics and constraintsのところですね。このため、ちょっと機械学習の知識がないと敷居が高いのではないかという印象でした。Autopilotで自動でモデルチューニングした後にこの条件を作成し、その良しあしを判断ができるのかが気になるところです。 f:id:Wok:20191205184705j:plain

とはいえ、concept driftの話はFLECTが扱っている案件でも話題になりますので期待したい機能です。

Intro to Amazon SageMaker Debugger: Get insights into ML model training

SageMaker Debuggerのセッションです。

大規模なモデルを作成した経験をお持ちの方であればお分かりになると思いますが、モデルのトレーニングにはかなりの時間がかかります。なので、トレーニングを開始したら数日放置しておくということもあると思います。そして数日後にトレーニングが完了したモデルを確認すると期待したほどの精度向上がなく落ち込むと。このような悲劇を回避するためにTensorflowであれば、TensorBoardを用いたりCallbackをうまく使って通知を上げたりするのが一般的かと思います。ただ、これを行うためにはモデル開発のコードを修正する必要があり、かなり神経を使います。Sage Maker Debuggerはコード変更することなくトレーニング時の異常をリアルタイムに検知、報告してくれる機能とのことです。

f:id:Wok:20191205192023j:plainレーニング中に作成されるデータ(おそらくチェックポイント)をS3に保管しし、逐一それを解析する処理を起動するという感じでしょうか。 確かに、デバッグコードをモデル開発のコードに直接書くと見通しが悪くなったりしがちなので、そういう意味で地味にありがたい機能だと思います。

Introducing Amazon SageMaker Studio, the first full IDE for ML

SageMaker Studioのセッションです。

SageMaker Studioは、今回発表のあった各種機能を取り込んだ機械学習モデルの統合開発環境となります。

図のような繰り返し開発を行うときに活躍します。 f:id:Wok:20191205185115j:plain 左の箱のBuildフェーズはNoteBooksを使ってAutopilotでチューニング、Debuggerでデバッグしながら中央の箱と行ったり来たりのイテレーションを行う。このイテレーションはExperimentsで管理され、最終的にはソリューションとして最適なモデルをデプロイできる。デプロイした後はMonitorでコンセプトドリフトを監視して問題があれば再度モデルを作り直す。という流れになるかと思います。

こう考えると、さすが機械学習開発プロセスを熟知しているAWSの統合環境だなと思いました。 おそらくここまで機械学習のモデル開発の流れに合わせて機能を組み込んだ開発環境は見たことがないので、今回のSageMaker Studioの発表で開発プロセスが一段レベルアップした感じです。 今後、Google, MSといった他社の出方が気になります。

Leadership session: Machine learning

本セッションはキーノートの直後に実施されたものです。基本はキーノートの発表に沿ったものが説明されましたが、キーノートでは語られなかっ た情報もありました。

一つ目がDeep Java Library(DJL)です。

Java開発者にend-endの機械学習開発APIを提供するようです。 従来AWSのForecasting teamでは、データサイエンティストチームがPythonを用いて作成したモデルを、Javaで開発しているML Infraチームが数週間かけてリファクタリングして使っていたのだそうです。多くのエンタプライズサービスはJavaで書かれているのだから、このコストはいろんな開発現場で発生しているはず。そこでDJLを使って初めからJavaで作成しておくことで、このコストを削減するということのようだ。 実際に、AWSのForecasting TeamはDJLを使うことで開発期間を30%削減できるようになったそうです。 f:id:Wok:20191205201741j:plain

確かにPython,特にJupyterNotebookで作成するコードはそのまま使えないことは、私もよく経験することなので、なるほどなとは思いました。

二つ目はSQL for MLです。 下図の例では、Amazon Aurora Amazon SageMaker, Amazon Comprehendと連携して、リアルタイムで推論結果を返します。 f:id:Wok:20191205193559j:plain まぁ、そういう流れになるだろうなという範疇ではあります。個人的にはSQLがあまり得意ではないので、DBという形すら取らずに、すべて自然言語でDBからAIからすべてのデータソースをクエリできるようにしてほしい。

さいごに

以上が私が参加した機械学習のセッションのご報告となります。 FLECTは明日が最後の参加日となります。そして、明日はキーノートがあります。また何か大きな発表があったら、さすがに体力が持たないかも。 どこかの会社様は数十人体制で来てるとのこと。大当たりでしたねー。今回のような大幅アップデートを4人で捌くのは正直きつい。 来年は偉い人にお願いして増員してもらうかな。そして私は来年も来たいなー。 ということで最終日。頑張って楽しんできます!では、おやすみなさい。(こちらは午前3時。)

フレクトが行くAWS re:Invent 2019 (12/3)

みなさん初めまして、CI事業部のウエダです。 今回が初のre:invent参加となります。

業務ではAWSは絶賛利用中で、日々AWSの新しい機能を学びそれを業務に反映し良いサイクルを回すことを心がけております。

海外のイベントともあり戸惑うことはありますがre:invent中は刺激的な毎日を過ごしております。

初めての朝食

f:id:flect-ueda:20191204160641j:plain

美味しそうな料理で迷いますが、連日の食事のボリュームで胃がもたれ気味だったのでコーヒーとマフィンだけの軽い食事で済ませました。

怒涛の新サービスラッシュ

1日目の発表に物足りなさを感じていた身としては嬉しい悲鳴です。

現地から社内へSlackで現地の状況を共有しているのですが、最初から最後まで休まるところがなかったです。

個人的には業務に直接関係のある、インスタンスタイプの追加、Amazon EKS on AWS Fargate、Amazon S3 Access Points、Amazon CodeGuru辺りが気になりました。 f:id:flect-ueda:20191204152455j:plain

早く情報を収集して使いこなせるようにならないと思いきや、幸いなことに、新サービス用のセッションが追加されました。 スケジュールの組み直しで大忙しです。

SageMaker、RedShiftのサービスが劇的に増えたので、そろそろ本腰を入れて機械学習とBigDataに力を入れないと、、、 f:id:flect-ueda:20191204162051j:plain こうして見ると改めて今回のSageMakerのサービスの充実具合が分かりますね

説明がひと段落したタイミングでバンド生演奏でこれまでの説明内容に沿った曲が流れ最後に歌詞が表示される演出はよかったですね。 f:id:flect-ueda:20191204152500j:plain 発表の内容によっては演出過剰でシラけてしまうリスクがありますが、今回発表のあったサービスはどれも期待以上のものなので、会場の盛り上がりがすごかったです。

帰国したら、既存のシステム構成をまた見直さないと、、、

ワークショップに参加

セッションだとどうしても受け身になりがちなので、せっかくチャンスなのでジャンルは問わず様々なレベルのワークショップに参加しています。

やはりワークショップだと実際に手を動かすので記憶に残りやすいですね。

気になる費用や、面倒臭いVPCなどの下準備は専用のアカウントを発行してくれるのですぐに課題に取りかかることができ有意義な時間を過ごせました。

最高レベルの400番台のものも参加し、時間が厳しいのでタイムアウトとなってしまいましたが、何をやっているかは理解できていたので日々の実務の成果が確認でき感慨深かったです。

とはいえ言語の壁が難しいですね。今の私のレベルでは聞きとるところまでで精一杯です。

こちらの意図を言葉で表せないないのがもどかしいです。精進しなけば、、、

AWS DeepComposerの実機をGET

Midnight Madnessで発表のあったDeepComposerです。 サービス発表時に現場の盛り上がりを目の当たりにしただけに、なんとかワークショップに参加して無事実機を手に入れることができました。 f:id:flect-ueda:20191204162515j:plain 自分の拙いピアノ演奏した曲を簡単な操作でJazz調、Rock調のちゃんとした曲になるのは興味深かったです。

ワークショップの後、別会場で実機を受け取るのですが、みんな気になるのか、その箱の中は何かと複数の人から聞かれました。

SalesforceブースでAWSのTrailheadを実施

f:id:flect-ueda:20191204160406j:plain スタンプラリーで2つ以上集めるともれなくグッズをもらえるということでやってみました。

先日のdreamforceで発表のあったAWSのトレイルと実施してコーディのぬいぐるみをGETしました。

まだこれからどんな発表があるか分かりませんが、残る期間も楽しんで挑戦したいと思います。

Heroku のアドオン「Sqreen」で簡単にWAF!

こんにちは。エンジニアのヤス・コバヤシです。

Heroku Advent Calendar 2019 5日目を担当させていただきます!

qiita.com

Webアプリケーションを開発するにあたって気をつけなければいけないのはセキュリティ対策ですよね。

OWASPトップ10とかブルートフォース攻撃(総当たり攻撃)とかアカウント乗っ取りとか、脆弱性を悪用した攻撃にどう対処していくかは悩みどころです。

いくらWEB APIREST APIの仕様に沿ってガチガチにガードしたと言っても、それはその攻撃自体をWebアプリケーションの中で受け止めてチェックすることになるので、フレームワークやライブラリに脆弱性があったらひとたまりもありません。

ですのでWebアプリケーションに届く前にガードする必要があるわけですね。

それが皆さんご存知のWAF(ウェブアプリケーションファイアウォール)になります。

ところでこのWAFですが、AWS には AWS WAF 、Azure には Azure Web アプリケーション ファイアウォールと言うものがありますが。。。

HerokuにはWAFがあるの?

あります!

それが「Sqreen」です。 elements.heroku.com

と言う事で今回は Heroku のAddOn「Sqreen」のご紹介をしたいと思います。

「Sqreen」ってどんな感じ?

構築する前に「これはどんなアドオンなの?どんな機能があるの?」と気になる事かと思います。

画面をお見せしながらご紹介しますね。

f:id:yasuyuki-kobayashi-flect:20191203165038p:plain
Monitoring画面

「Sqreen」はリアルタイムで攻撃をブロックし、セキュリティ状況を可視化してくれます。 これがメイン画面とも言えるMonitoring画面です。

f:id:yasuyuki-kobayashi-flect:20191203165325p:plain
Settings画面

設定画面では攻撃があった時にどんなHttp Statusで返すかと言う設定ができたり、IPアドレスによるガードや受け入れを設定できるブラックリストホワイトリストの設定もできたり、

f:id:yasuyuki-kobayashi-flect:20191203170055p:plain
Integrarion画面

攻撃があったりした時に通知する先も設定できて、先日Heroku NewRelic APMを始めよう!でお伝えしたNewRelicやSlackへの通知、WebhookでPOST先を指定する事もできます。

f:id:yasuyuki-kobayashi-flect:20191203170720p:plain
Alertモード画面

もちろんメールでの送信も可能。

また緊急度(高/中/低)によって通知も「直ちに」「1日のダイジェスト」「1週間のダイジェスト」と設定できます。

このように「Sqreen」はフレキシブルに設定できるので、アプリケーション運用時には頻繁にSqreenコンソールに確認に行かなくても良いし、大したことのないセキュリティアラートで深夜に起こされる、呼び出される事もないです(笑)。

f:id:yasuyuki-kobayashi-flect:20191203171739p:plain
WAFルール画面

そしてWAFですが、デフォルトで50ものルールがすでに設置されています。

「Sqreen」を構築しただけですぐにWAFが機能するのは嬉しいですね。

もちろん御自身のルールを追加する事も可能ですし、既存のルールをどのように扱うか(”何もしない”とかも)をコントロール可能です。

f:id:yasuyuki-kobayashi-flect:20191203171649p:plain
WAF画面

またいくつかのルールに引っかかるようなアクセスをしたIPは「15分間遮断する」と言った、攻撃者を諦めさせる嬉しい機能も!

その他の機能

「Sqreen」はWAFだけの機能じゃないです。

f:id:yasuyuki-kobayashi-flect:20191203173927p:plain
Protections

「クリックジャッキング」「クロスサイトスクリプティング」「MIME Sniffing」と言ったクライアント側のセキュリティ保護対策、そして自身のアプリケーションの脆弱性までチェックして教えてくれます。

Webアプリケーションの中も外もしっかり保護してくれる、もう至れり尽くせりですね。

そしてWebアプリケーションに「Sqreen」を搭載した成果が!

「で、このSqreenってやつは本当にガードしてくれるの?」と半信半疑な方もいらっしゃるのではないでしょうか。

我々が構築したWebアプリケーションに「Sqreen」を搭載したんですが、

f:id:yasuyuki-kobayashi-flect:20191204112759p:plain
インシデント画面
なんとウクライナ(またはウクライナ経由)の攻撃を見事にガードしてくれました!!

「Sqreen」は攻撃者の位置をIPアドレスから逆探知もしてくれるんですよ。

すごくないですか。

構築方法は?

皆さんいかがでしたか? 「Sqreen」なかなかの高機能でしょ。 プランは無料からもありますのでこれをアドオンしない理由はありませんよね。

さて最後に「Sqreen」の構築方法ですが、

f:id:yasuyuki-kobayashi-flect:20191204093758p:plain
AddOn
アドオンしただけではすぐには動きません。 Webアプリケーションを守るために「Sqreen」を関連づける(dynoの中で「Sqreen」監視プログラムを動かし「Sqreen」本体に認識させる)ことが必要です。

この設定方法に関しては皆さん様々な言語でWebアプリケーションを構築していると思いますので、私が説明するよりこちらをご覧ください。

devcenter.heroku.com

英語ですけどブラウザの翻訳機能(最近はかなりいい感じに翻訳してくれるようになりましたよね)を使えば簡単に読んで理解できますよ。

それではみなさん、「Sqreen」を搭載して"ハッピーHerokuライフ"をお過ごしください!

フレクトが行くAWS re:Invent 2019 (12/2)

f:id:Wok:20191203190754j:plainみなさんこんにちは。技術開発室の岡田です。

現在、ラスベガスではAWS re:invent2019が開催されていますね! 今回のre:inventには、弊社から西中、上田、尾野、岡田の4名のエンジニアが、昨日よりラスベガス入りをして参加しております。

前回までの私の投稿は機械学習に関連する記事ばかりでしたが、 今回は私もre:Invent 2019に参加していますのでこの様子をご報告しようかと思います。 本投稿では、本格的にセッションが開始される12/2の様子をご報告します。 なお、上田もAWS re:invent2019に関するブログを投稿予定ですので、そちらも併せてご覧いただけますとありがたいです。

さて、少し振り返ると、昨日の夜にはMidnight Madnessというパーティがあり、その中で新製品が発表されるという驚きの展開がありました。 はたして、Keynoteがある本日はどうだったでしょうか。参加報告として、大まかに時系列に沿って報告をしたいと思います。

セッション開始の日は朝食が出ない

いきなり技術的な話じゃなくてごめんなさい。ですが、今回はこの調子で進めます。 出発前、前回の参加者やWeb上の話によるとイベント開催中の朝食と昼食はイベント会場内で準備されるとのことでした。 このため、朝食の準備は特に考えず会場に向かったのですが、食事を食べる場所は特に案内されておりませんでした。 どうも今回はセッション開始の日は朝食が出ないようです。そして、食事が出る場合は公式のページにしっかりと記載されていますね。 完全に情報収集不足でした。12/2はランチは出ますが朝食は出ないようです。 f:id:Wok:20191203171159p:plain

https://reinvent.awsevents.com/schedule

ということで、会場の一つであるベネチアンホテルの中にあったショップで軽食を購入して凌ぐことにしました。 後述しますが、このre:inventというイベントの恐ろしいところは、その会場の大きさです。 戦いきるためには適切にエネルギーの補充がかなり重要となりますので、ここで食事が取れてよかったです。

写真は、順番待ちしていた私を元気よく呼んでくれている店員さんの図。 f:id:Wok:20191203190754j:plain

会場間の移動は40分、会場内の移動はプラス20分

前提知識として知っておいていただきたいのが、re:inventのイベント開催エリアの大きさです。全体像はこちらのURLで示されています。 https://reinvent.awsevents.com/around/campus_overview/

googleマップで距離を測ってみると端から端までで3Kmくらいあります。人間の歩行速度は大体時速4Kmといわれていますので、40分くらいの行程です。 実際参加者3人がそれぞれ歩いて時間を計ってみましたが、やはり全員40分くらいかかっていましたのでそのくらいだと思っておけばいいと思います。 そして、各会場はホテルなのですが、中にカジノが併設されているホテルでとにかくでかい。単純に地図上の大きさだけでもでかいとわかると思いますが、 そのうえ建物内の壁やフロアの概念があるから実際の移動距離はもっと多くなります。とにかく移動が大変。 MGMというホテルでは、入り口から一番遠いと思われる会場まで歩いて20分ほどかかりました。 (実際にその会場でのセッションに参加しました。)

Finding a needle in a haystack: Use AI to transform content management

一つ目のセッションとしてこちらに参加しました。 本セッションでは、非構造化データをSagemakerの機能を用いて構造化できる情報として変換し、それを他の処理に利用することで業務効率を向上させる、という話がなされました。 彼らが提案する参照実装はこちらです。 f:id:Wok:20191203190927j:plain S3に入れた非構造化データをlambdaを経由してAmazon Textract、Amazon Transcribe、Amazon Translateで標準化し、Amazon Comprehendで分析して、検索エンジンに利用したり、グラフDBにして関連性を定義したり、その他機械学習に用いたりするという形になっています。

そして、これの実際の例として、資本家向けに会社の安全度を分析する会社がやっているメール分析処理を取り上げて、その内容と効果が説明されました。 f:id:Wok:20191203190202p:plain S3上のメールのContent部分をAmazon SageMakerを用いてノイズ除去を行い、Attachments部分からテキストを抽出する。その結果をAmazon Comprehendで処理することで必要な情報を抽出しています。 このような処理を行うことにより、従来行っていたメール分析の時間を60%短縮できたそうです。

私も機械学習を主に担当しているため、AIにより作成された100%確実とは言い切れない情報をどのように活用していくのかは興味がある内容でしたのでとても参考になるセッションでした。

セッション開始の日でも昼食は出る。

昼食はしっかりと準備されていました。大きな会場でビュッフェ形式で食べるか、バッグに詰められた昼食セットをもらって任意の場所で食べるかを選べました。 私は、温かいご飯がべたかったのでビュッフェ形式で食べました。豆のスープがとてもおいしかったです。デザートもあって満足満足。 f:id:Wok:20191203191309j:plain f:id:Wok:20191203192228j:plain

腹ごなしに40分歩く

昼食をとったのがベネチアンホテルで、次のセッション会場がMGMだったため、イベント開催エリアの端と端になります。 シャトルバスも出てはいるのですが、ちょうど時間も空いていましたので、歩いてみることにしました。 予想通り40分かかりましたが、途中にはM&M'sのお店があったり、 Hersheyのお店があったりで、甘いもの好きにはなかなか目を楽しませてもらえる通りになっています。 昼間に大通り(Stripと呼ばれるらしい)を歩いている限りは特に危険な感じもなかったです。(自己責任でお願いします。)

Get started with AWS DeepRacer

二つ目は、DeepRacerのワークショップに参加しました。 前回のre:inventなど、他のイベントでもこのワークショップは開催されてきていますが、今回新たにGrageという機能が追加されました。 Grageでは主に次のことができるようになります。

一つ目のセンサーの種類ですが、従来一つのカメラがついていましたが、二つのカメラを搭載し、ステレオカメラとして使って深度を図ることができるようになります。また、より高精度に深度を計測できるようにLIDARをアドオンすることができます。LIDARは可視光で距離などを図る技術だったかと思います。(全然専門じゃないので違ってたらごめんなさい。一応Wikiを載せておく。LIDAR - Wikipedia) 二つ目のニューラルネットワークの構成は、従来DeepRacerで使われているCNN*1のレイヤ数を変えるというもの。増やせば精度向上が期待できるが、トレーニング、推論の時間が延びてしまうというデメリットがある。

今回のワークショップでは、上記のGrageの設定や他の設定については、トレーニング時間が短くなるものを選択してモデルを作成してみるというものでした。ベースラインのモデルとして簡単に作れることがわかるようになります。ここまでできれば、先ほどのパラメータをチューニングしてさらなる高みを目指すことができそうです。今回使われたリソースは下記のgithubに上がっていますので興味のある方はのぞいてみると良いかと思います。なお、トレーニングにはAWSのアカウントが必要で、使用したリソースに対する料金も発生するのでその点はご留意ください。

github.com

The Quadに参加

今回のre:inventでは、ExpoとThe Quadの二つの展示会場があります。 今回はDeepRacerのワークショップ会場に近いほうのThe Quadを見てきました。 Smart家電の専用ブースができていたり、RoboMakerのブースがあったり、情報技術をハードウェアと連動させる技術が多くみられたように見える。 f:id:Wok:20191203193630j:plainf:id:Wok:20191203193550j:plain f:id:Wok:20191203193729j:plain

ちょうどこのタイミングでWelcome Receptionが行われていたので、料理とお酒が提供されていました。わたしはマカロンなどを頂きました。おいしい。

f:id:Wok:20191203193811j:plain f:id:Wok:20191203193837j:plain

ML in retail: Solutions that add intelligence to your business

三つ目のセッションにMachine Lerningをretail業界に導入する勧めのセッションを聞いてきました。 大きく次の2点をデモを交えながら説明していました。

  • 需給未来予測によるプロセスの効率化(プロセス改善)
  • パーソナライゼーション、推薦システムによるユーザ体験の向上 f:id:Wok:20191203195755j:plain

この内容自体は理にかなっているとは思いますが、ずいぶん前から言われてきたことで、あまり目新しいことはないなという印象を受けてしまいました。 上記を実現するためのGUI&AIのデモを見せてもらえたのが目新しいところかもしれませんが、一つ目のセッションのように具体的な事例をあげての説明よりはだいぶ説得力に欠けていたかな。

シャトルバスは渋滞にはまるリスクがある

キーノートを聴講するために、昼食をとった場所ベネチアンホテルに戻ります。さすがに40分歩く体力がなかったのでシャトルバスに乗りました。 しかし、シャトルバスが通る道は結構渋滞するらしくかなり時間がかかってしまいました。多分歩いたのと同じくらい時間がかかっていると思います。 結局キーノート開始のギリギリ25分前についてぎりぎり間に合いましたが、かなりはらはらしました。 f:id:Wok:20191203193947j:plain 渋滞のリスクを考えて、余裕を持ってシャトルバスを利用したほうがよいですね。

Monday night live

キーノートとliveが組み合わさったセッションです。 開始時間はキーノートの時間であり、その前からライブは開始されていたようです。 25分前にベネチアンホテルに到着したのち、会場までやはり10分くらいかかったので、15分前にようやく会場入りすることができました。 大きなホールが会場になっており、巨大な壁面ディスプレイ5~6枚が連なっているステージになります。 ライブ自体はその端のほうでやっており、聴講者は会場に到着した順番に逆の端から席に座るシステムだったので、最後のほうに到着した結果バンドの真ん前が席になりました。 とってもかっこいいバンドだったのですごいラッキーでした。(名前知りたい。後で調べよう。) f:id:Wok:20191203194008j:plain

さて、キーノートの内容ですが、今回は Re:Invent Super Computerという名でHPC向けのクラウド環境を提供する発表がなされました。聞いている感触だとHPCを実現するためのインフラの提供という感じです。いくつか課題というか要件というかがある中でそれぞれの施策を提供できるようにAWSが準備しているという説明。例えば、非効率なTCPをEFAに変えることでスループットを向上させることができる。システムをチップに最適化することでスケーラビリティを確保できるようになっている。特定のVM/コンテナにハードウェアを占有させ不要なオーバヘッドをなくすなど。これらをまとめ上げれば、Re:Invent Super ComputerとしてHPCのタスクに十分な性能を持つVM・コンテナが生成できるということではないかと理解した。 f:id:Wok:20191203195940j:plain f:id:Wok:20191203200010j:plain f:id:Wok:20191203200042j:plain

また、環境保護プロジェクトを進めていて、パリ協定を10年前倒しする後押しをするという話もあった。 f:id:Wok:20191203201850j:plain f:id:Wok:20191203201921j:plain

一見すると、高性能なコンピューティングと環境にやさしいコンピューティングは、トレードオフの関係にも見えて、両立は難しそうだなぁと思いましたが、 AWSは、もちろん計算効率を上げて電力消費を削減する方法も考えているとは思いますが、今回大々的に発表したのはそれではなく、環境にやさしい発電方法で環境を守るという大きな視点からの取り組みのことでした。電力消費がーとかいっても、そもそもクリーンに電力を供給できてればいくらでも使っていいじゃないか、ということ(言い過ぎ)。やはりGAFAはスケールが違うなぁ。

以上が、12/2の報告内容です。

次回は、また機械学習の記事に戻るか、もしかしたらもう一回re:inventの内容を報告するかします。 ではまたよろしくお願いします。 (やばい、やばい、早く寝ないと明日のキーノートに寝坊する。ということで、誤字脱字、変な文章ご容赦を。)

*1:こちらは専門ですが、以前のブログで書いているのでここでは説明しません。そちらを見てください。[http://cloud.flect.co.jp/entry/2019/07/08/134938

Apexからレポートを実行しCSVで出力する

エンジニアの藤野です。

Salesforceの主要な機能の一つであるレポートは、Apexから実行することもできます。 公式のドキュメントとしては下記のものなどがあります。

この記事では一例として、レポートの実行結果をCSVとして出力するコードを実装し、解説します。

実行環境は下記のとおりです。

使用するレポート

レポートの題材として、Trailheadのレポートの形式設定の「マトリックスレポート」で作成している「Revenue Trend by Type」レポートを使用します。

これは行、列ともにレベル1のグルーピングを行うマトリックスレポートです。

ゴールと出力例

適当なレコードを作成し、「Revenue Trend by Type」実行しました。

レポート実行結果
レポート実行結果

これに対して、ゴールは下記のCSVを出力することです。 CSVファイル名はレポート名をそのまま使うものとします。 また、詳細は出力しません。

Revenue+Trend+by+Type.csv:

完了予定月,種別,-,Existing Customer - Upgrade,Existing Customer - Replacement,Existing Customer - Downgrade,New Customer,合計
2019/01/01,金額 合計:,0.0,0.0,0.0,0.0,1.0E7,1.0E7
2019/02/01,金額 合計:,0.0,0.0,0.0,2000000.0,0.0,2000000.0
2019/03/01,金額 合計:,880000.0,0.0,0.0,0.0,0.0,880000.0
2019/05/01,金額 合計:,0.0,0.0,5000000.0,0.0,0.0,5000000.0
2019/07/01,金額 合計:,0.0,0.0,0.0,0.0,0.0,0.0
2019/08/01,金額 合計:,0.0,0.0,0.0,100000.0,0.0,100000.0
2019/09/01,金額 合計:,0.0,2.0E7,0.0,0.0,0.0,2.0E7
合計,金額 合計:,880000.0,2.0E7,5000000.0,2100000.0,1.0E7,3.798E7

また、このCSVExcelでそのまま表示できるものとします。

Excelで表示した状態:

CSVをExcelで表示
CSVExcelで表示

続きを読む