ミクシィでインターンしてきました

今年の夏はミクシィの SRE としてインターンしてました. 主にモンスト周りの運用の改善を行いました. 楽しかったので,思い出を振り返りたいと思います.

mixi-recruit.snar.jp

選考

きっかけは皆様おなじみのサポーターズ 1on1 面談イベントです. 良さげなインターンのオファーを求めてそこで様々な企業の方とお話しする機会があったのですが, そのつながりでミクシィの人事の方と面談する機会がありました.

インターンのお話を伺う中で,参加者一人一人のやりたいことに向き合ってくれそうな雰囲気を感じて, 選考を受けてみることにしました.

SRE のインターンに参加したいことを伝えて,2回の技術面接を受け, その結果 CTO室 SRE グループに配属してもらえることになりました.

個人的に驚いたのは,面接の結果が出るのが幾ら何でも早すぎることです.笑 書類選考もどの面接でも,30分〜1時間で結果を伝えてくれました. インターン先は色々悩んでたのですが,最終的な決め手となったのはこの点です.

各社員の信頼関係と意思決定の速さが感じられて好印象でした.

インターン

配属先のメンターとの顔合わせがあり, 大規模なサービスに関わってみたいという私の希望を叶える形で, モンスト関連の仕事をするということを聞きました.

モンストは今までやっていなかったのですが, これを期に始めることにしました.

また,その顔合わせで,SRE として働く上での参考図書として, 以下の本を勧められました.

  1. SRE サイトリライアビリティエンジニアリング
  2. Clean Architecture 達人に学ぶソフトウェアの構造と設計
  3. リーダブルコード
  4. マスタリング TCP/IP 入門編
  5. Infrastructure as Code
  6. 入門 監視
  7. UNIXという考え方

3,4,6は読んだことがあったので, インターンまでに1,5,7を読んでみることにしました (2は時間がなくて読めませんでした >< そのうち読みます).

勧めていただいたどの本も本当に面白かったです. 特に感銘を受けたのは 5の IaC 本です. IaC といえば,まあ Terraform や CloudFormation みたいにインフラをコードとして管理するみたいな感じでしょ, 本に書くことなんてあるの??とか思ってたのですが,大間違いでした.

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

Infrastructure as Code ―クラウドにおけるサーバ管理の原則とプラクティス

  • 作者:Kief Morris
  • 発売日: 2017/03/18
  • メディア: 単行本(ソフトカバー)

インフラをコードとして管理することで, ソフトウェア開発の知見をインフラ開発に応用できることを説明した本で, インフラを VCS で管理したり,CI/CD したり,TDD したりするための ノウハウが満載でした.

Google の SRE 本もとっても面白くて, やっぱりこれから SRE がアツいなと再認識しました (逆に選考の時にこの本すら読めてなかったのに採用してくれたの感謝しかないです笑)

そんな感じで,インターンに参加するまでは大学の研究と インターン前のインプットに努めました.

インターン

オフィス

個人的な要望として,インターンはリモートじゃなくて オフラインでやりたいなーと思っていました. しかし昨今の情勢により,出社してのインターンは厳しいかも... と諦めかけていたところ,色々調整してくださって, オフラインでインターンに参加できることになりました.

関西の実家暮らしの民としては, インターンくらいしか東京で一人暮らしをする機会がないので, とっても嬉しかったです. また,渋谷スクランブルスクエアの新しいオフィスを満喫できてよかったです.

私が普段働いていたオフィスは SSS の34階にあって,見晴らしが最高でした. また,スープやコーヒーが飲み放題なのも嬉しかったです. 作業が行き詰まった時に,スープを片手にオープンスペースのソファで寛ぎながら 景色を眺める時間が好きでした笑

ただ,主にお世話になっていたモンストのサーバーチームの方々がフルリモート勤務だったので, オフィスでリモートワーク(?)をしてる感じでした.

業務内容

リアルタイムメッセージングサーバーの改善

モンストと一口に言っても,それを支える色々なサービスがあります. その中で私がメインに扱ったのは,モンストのサーバーからユーザーへの リアルタイム通信や,ユーザー同士のリアルタイムメッセージングを提供する WebSocket サーバーです.

これを改善しようというのが私のメインのタスクでした.

ログレベルを設定ファイルから柔軟に設定できるように機能追加

初めは簡単なタスクをこなして慣れていこう,ということで簡単なタスクから. 現状の実装ではロガーの設定がデフォルトでしか使えなかったので, これを柔軟に設定するための機能を追加しよう,という内容でしたが, これがなかなか苦労しました.

リモートワークで質問しづらい状況だったため, 既存のコードの構造を読み解くのにも一苦労です. はっきり言ってあまりにわからなさすぎて絶望していました. それでも,メンターの方々が要領を得ない私の質問にも粘り強く答えてくださったおかげで, なんとか実装できました.

カナリアリリースの導入

現状のリリースフローでは,Kubernetes で書かれたリソースを kubectl apply -f みたいな感じで単純にアップグレードしていました. しかしこれだと,もしアップグレードしたバージョンにバグがあった時に, 全ユーザーに影響が出てしまいます. ステージング環境でしっかりテストするとはいえ, プロダクション環境でしか起こらないバグもあり得ます.

そこで,カナリアリリースを導入することにしました. これにより,プロダクション環境で動いている 古いバージョンのアプリケーションとは別の環境(カナリア環境)に 新しいバージョンをデプロイし, ごく少数のリクエストをカナリアに振り分けて動作を確認することで, より安全にリリースできるようになります.

今回のインターンでは, カナリアリリースの手順をサポートするコマンドを実装したり, リクエストの振り分けロジックを書いたりしました.

このカナリアリリースは, 初めから仕様が決まっていたわけではなく, できるといいよね,みたいな感じの issue があっただけでした. そのため,カナリアリリースを実現するための方法や 導入するソフトウェア(Istio や Spinnakerなど)の調査を行い, いろんな方々にアドバイスをいただきながらやり方を決めていきました.

そのような作業を通して, 仕様を決めながら機能を追加したり, 技術選定を行う体験ができました.

メモリ消費量の削減

この WebSocket サーバーは Golang で書かれていたのですが, ある処理を行うたびに goroutine が一つずつ増えていく という実装になっていました. goroutine は一個増えるたびに少なくとも数KBのメモリを消費します. goroutine が数個増えても問題になりませんが, モンストの場合,数百万ユーザーが同時に使用することを想定しなければならないので, 現状の実装では非常に多くのメモリを必要とする可能性がありました.

そこで,チャンネルや goroutine をうまく使いまわすことで 新しく goroutine が生成されることを防ぐようにしました. これによって,メモリ消費量が急増する要因が一つ減り, より平和になりました.

データセンター作業

モンストの一部はオンプレで動いています. せっかくなので,データセンターの運用作業を 体験させていただけることになりました.

データセンターは思ったよりもずっと強固なセキュリティで守られており, テロ対策がしっかりと取られていて驚きました.

作業としては,サーバーの部品の交換・増設を行いました. 今まさに本番環境で動いているDBのラックを触る必要があり, 一つでも間違えたらモンストが即緊急メンテに入るよ って(軽く)脅されていたこともあって,緊張感のある作業でした笑

しかしその分貴重な体験ができてとても有意義でした.

感想

今まで参加したインターンでは, 修羅場を経験することが多かったのですが, 今回は大きな修羅場もなく, 楽しく平和にインターンすることができました笑

夜に集まって飲み会や懇親会は残念ながらできなかったのですが, お昼に美味しいランチに連れて行ってもらえたりして, 温かい職場でした.

また,かねてより興味のあった SRE のような作業もすることができ, あまり使ったことのなかった KubernetesGolang などのツールを勉強できたり, 丁寧なコードレビューを受けることができたりして学びも大きかったです.

苦労したのは,全社的にリモートワークを行っており, 自分の今取り組んでいるタスクに詳しい人に気軽に質問などができなかったことです.

もちろん Slack や GitHub で質問を投げると丁寧に教えてもらえるのですが, 新しい issue に取り組むときは,そもそも何を質問すればよいのかわからず困っていました.

僕の場合は,ちゃんとした質問の形ではなくても, とにかく何かメッセージを送らないとどうにもならないことに気づいたので, Slack や GitHub に現状の理解度を書いて反応をもらえるようにしたりしていました.

ただ,メンターの方が出勤していて, 気軽に質問できる時の方がすぐに問題を解決できたので, 文脈を知らない状態でリモートワークってやっぱり難しいなあ と感じました. これに関しては仕方ないですね. それでも,何人かの社員の方は毎日出勤してくださって, たまに雑談に付き合ってもらえたりして, 僕が寂しくならないように配慮してくれていて嬉しかったです笑

今回の経験を生かして,今後も頑張っていきます! それでは!

追記(2020年10月26日): インターンの成果をミクシィのテックブログでまとめていただきました (nobutadaさん本当にありがとうございます😭).

medium.com

本記事では,私の文章力不足により,技術的な詳細が伝わらなかったと思いますが, テックブログの記事では,コードも交えて非常にわかりやすく解説されていますので, ぜひご一読ください!

実際に、最後のパフォーマンスチューニングは2つ目のカナリアリリースを利用してリリースしました。 すごく便利です。ありがとうございました!

嬉しい...こちらこそ本当にありがとうございました...!

AWS SAM + LocalStack によるサンプルアプリケーションの作成

Serverless Framework,AWS SAM,AWS CDKなど, サーバーレスのローカル開発環境を行うツールが増えてきました.

使っている人が多いのはおそらくServerless Frameworkですが, AWS公式がメンテナンスしていて, ローカルでのLambda関数のテストができるAWS SAMは今なお有力な選択肢だと思います.

しかしサーバーレスは,クラウド環境では手軽に扱える反面, ローカル環境での開発はハマりどころや辛いところが多いです.

そこで本記事では,AWS SAM を使って,API Gateway を通してLambdaを呼び出し, DynamoDBとやりとりするアプリを作っていきます. LambdaのランタイムはNode.jsです.

また,DynamoDBはLocalStackのコンテナにホストします. DynamoDBだけであればAWS公式が提供している DynamoDBのDockerイメージを使うことができますが, SQSやS3などのサービスも使うことが多いため, LocalStackの使用をおすすめします.

バージョン情報

macOS Catalina 10.15.4
AWS CLI 1.16.310
AWS SAM CLI 0.44.0
Docker 19.03.8

構築手順

プロジェクト作成

sam initコマンドでプロジェクトを作成します. 今回はQuick Start Templatesから作っていきます.

> sam init --name sam-sample --runtime nodejs12.x

Which template source would you like to use?                                                                                                                                                                        
        1 - AWS Quick Start Templates
        2 - Custom Template Location
Choice: 1

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

-----------------------
Generating application:
-----------------------
Name: sam-sample
Runtime: nodejs12.x
Dependency Manager: npm
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./sam-sample/README.md

これで,簡単なAPIが作られました.

確認

サンプルのAPIの動作確認です.

> sam local start-api 

Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-04-29 10:23:23  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

ターミナルの別ウィンドウを開いてcurlします.

> curl http://localhost:3000/hello

{"message":"hello world"}

hello worldが返ってきました.

LocalStackの設定

Docker networkの作成

> docker network create nw-sam-sample

docker-composeでLocalStackコンテナを作成・起動

今回はDynamoDBしか使いませんが, この記事をご覧の方の中には, DynamoDBとSQSを両方使いたい場合があるかもしれないので, 参考までに両方ホストするためのファイルを書いておきます.

docker-compose.yaml

version: "3.3"

services:
  localstack:
    container_name: localstack-sam-sample
    image: localstack/localstack
    ports:
      - 8080:8080
      - 4569:4569 # DynamoDB
      - 4576:4576 # SQS
    environment:
      - SERVICES=dynamodb,sqs
    networks:
      - nw-sam-sample

networks:
  nw-sam-sample:
    external: true

LocalStackを起動します.

> docker-compose up -d

DynamoDB テーブルを作成

スキーマ情報のファイルを作成

Id をプライマリキーとするテーブルを作っていきます.

schema.json

{
    "AttributeDefinitions": [
        {
            "AttributeName": "Id",
            "AttributeType": "S"
        }
    ],
    "TableName": "tb-sam-sample",
    "KeySchema": [
        {
            "AttributeName": "Id",
            "KeyType": "HASH"
        }
    ],
    "BillingMode": "PAY_PER_REQUEST"
}

LocalStackに作成

> aws dynamodb create-table --endpoint-url http://localhost:4569 --cli-input-json file://schema.json

コンテナをdownすると,テーブルが全て消えるので注意

確認

> aws dynamodb list-tables --endpoint-url http://localhost:4569

{
    "TableNames": [
        "tb-sam-sample"
    ]
}

Lambda関数の追加

サンプルプロジェクトに含まれているHelloWorldFunctionの下に, DynamoDBとやり取りするLambda関数であるSamSampleFunctionを作成します.

今回は3つのパスを用意します.

  • POST /sample:新たにリソースを作成
  • GET /sample:全てのリソースを表示
  • GET /sample/{id}:パスパラメータidで指定したIdを持つリソースを表示

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-sample

  Sample SAM Template for sam-sample
  
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

  # add
  SamSampleFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: sam-sample.handler
      Runtime: nodejs12.x
      Events:
        Create:
          Type: Api
          Properties:
            Path: /sample
            Method: post
        Index:
          Type: Api
          Properties:
            Path: /sample
            Method: get
        Show:
          Type: Api
          Properties:
            Path: /sample/{id}
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

次に,そのハンドラをhello-world/sam-sample.js内に書いていきます.

注意点

Macの場合, ハンドラ内のDynamoDBエンドポイントは http://localhost:4569ではなく http://host.docker.internal:4569としてください.

今回,Dockerコンテナ同士をDockerネットワークでつなぐので, localhostでは動きません.

ちなみに,Linuxではhttp://localstack:4569になります.

hello-world/sam-sample.js

const AWS = require('aws-sdk');
AWS.config.update({ region: 'ap-northeast-1' });

const dynamoDBEndpoint = 'http://host.docker.internal:4569';
const dynamoDB = new AWS.DynamoDB({ endpoint: dynamoDBEndpoint });
const docClient = new AWS.DynamoDB.DocumentClient({ service: dynamoDB });

const tableName = 'tb-sam-sample';

exports.handler = async (event, context) => {
    try {
        switch (event.httpMethod) {
            case 'POST': {
                const payload = JSON.parse(event.body);

                await docClient.put({
                    Item: {
                        Id: payload.id,
                        Data: payload.data
                    },
                    TableName: tableName
                }).promise();

                return {
                    statusCode: 200,
                    body: {}
                }
            }

            case 'GET': {
                if (event.pathParameters && event.pathParameters.id) {
                    const id = event.pathParameters.id;

                    const res = await docClient.get({
                        Key: { Id: id },
                        TableName: tableName
                    }).promise();

                    return {
                        statusCode: 200,
                        body: JSON.stringify(res.Item)
                    }
                } else {
                    const res = await docClient.scan({
                        TableName: tableName
                    }).promise();

                    return {
                        statusCode: 200,
                        body: JSON.stringify(res.Items)
                    }
                }
            }

            default: {
                return {
                    statusCode: 400,
                    body: JSON.stringify({
                        message: 'invalid input'
                    })
                }
            }
        }
    } catch (err) {
        console.log(err);
        return {
            statusCode: 500,
            body: JSON.stringify({
                message: 'internal server error'
            })
        }
    }
};

起動

> sam local start-api --docker-network nw-sam-sample

確認

> curl http://localhost:3000/sample -X POST -d '{"id": "1", "data": "hello"}' 
> curl http://localhost:3000/sample
[{"Data":"hello","Id":"1"}]
> curl http://localhost:3000/sample/1
{"Data":"hello","Id":"1"}

完成です!

最後に

今回はAWS SAMとLocalStackでLambda関数のローカル開発環境を構築し, API Gatewayを通してDynamoDBのCRUDを行うサンプルアプリケーションを作成しました.

今回作成したアプリケーションは, LocalStackにアクセスするためのものなので, これを参考にしてデプロイする際は, 適宜環境変数を用いてAWSサービスに対するエンドポイントをスイッチしてください.

この記事が少しでも参考になれば幸いです.

今回作成したリポジトリです.

github.com

motch のアーキテクチャを紹介

2019年度の未踏事業に採択されて, IoTデバイス管理アプリ「motch」を作りました.

arailly.hatenablog.com

私は主にバックエンドの開発を担当し, 以前から興味のあったサーバーレスアーキテクチャを駆使して実装しました. せっかくなので,そのアーキテクチャを紹介します.

アーキテクチャの全体像

全体像のアーキテクチャの概要図はこんな感じです.

f:id:arailly:20200301112321p:plain

開発環境

紆余曲折を経て,AWS SAM + LocalStackに落ち着きました. SAMはAPIやLambdaのエミュレーションができるので, ローカルでのテストが捗ります. ランタイムはNode.jsで,TypeScriptから生成していました.

サーバーレスあるあるですが, クラウド環境が楽な反面, ローカル環境が辛くなりがちです.

この辺りの知見もぜひ共有したいと思っています.

リクエスト処理

HTTPS

API Gateway + Lambda + DynamoDB で構成. 認証・認可はCognitoを使用しています.

f:id:arailly:20200318155602p:plain

MQTTS

IoT Core + SQS + Lambda + DynamoDB で構成.

IoTデバイスにとってHTTPSリクエストは重いので, MQTTと呼ばれるより軽量な通信プロトコルに対応しました. IoT Coreのトピックへのパブリッシュを生存報告とみなします. また,IoTデバイスが増えてもスケールするよう, メッセージは一旦SQSのキューにためてバッチ処理を行います.

f:id:arailly:20200318155559p:plain

なぜトピックへのパブリッシュを生存報告とするのか

AWS IoTを使った死活監視といえば, MQTT接続の切断イベントを利用することが多いと思います. しかし今回は,トピックへのパブリッシュを生存報告とみなし, 生存報告が途切れたら故障とみなす,というアプローチを取りました.

その理由は,想定している生存報告間隔が数十分から数時間と比較的長いからです. 頻繁にセンサーデータを送るシステムであれば常に接続している方がいいと思いますが, 数時間に一度しか通信を行わないのであれば, 生存報告のために接続し続けることによる消費電力が無視できないと考えました.

UIのリアルタイム更新

API Gateway (WebSocket Endpoint)+ Lambda + DynamoDB で構成.

APIGWのWebSocket Endpointによる双方向通信により実現しています. 接続時,切断時にそれぞれLambdaが起動し, 接続情報をDynamoDBで管理します. デバイスの状況に変化が生じたら, 接続情報を参照し, 即座にクライアントにデータを送信します.

f:id:arailly:20200318155606p:plain

死活監視アルゴリズム

DynamoDB Stream + Lambda + SQS で構成.

f:id:arailly:20200318155556p:plain

一番悩んだポイントです. motchでは,「生存報告が定期的に行われなくなると故障と判断する」 という故障検出アプローチを取っています. そのため,「生存報告が来ない」ことに反応する必要があります.

最も単純な解決法はポーリングだけどあんまりかっこよくありません. 一定時間おきにテーブルのスキャンが発生するのでスケールしませんし.

生存報告を受信すると,一定時間(生存報告間隔)後に故障検出を行うプロセスを生成し, 制限時間までに生存報告が来たらそのプロセスをキャンセルする, というアプローチを取りたかったのですが, サーバーレスなのでそんな器用なことはできません.

生存報告を受信すると, 一定時間後にLambdaが立ち上がる遅延起動のような機能があればいいのになあと思いましたが, 残念ながらそんなものは無かったので自分で再現することにしました.

色々調べてみると, SQSのメッセージは遅延時間付きで送信できることがわかりました. つまり,SQSにメッセージを送信すると, 遅延時間後にそれが反映されます. さらに,SQSのメッセージ受信をトリガーにしてLambdaを起動することができます.

そこで,これを使って, あるデバイスの生存報告を受信すると, 一定時間後にLambdaを起動してそのデバイスの生存報告状況を確認することができました.

これによって,インデックスを使えばテーブルのスキャンは発生しませんし, Lambdaが並列に起動するのでそこそこスケールします.

Lambdaの遅延起動のような機能を実装したい, という方がいればぜひ参考にしてください.

バイス情報・SDKの管理

DynamoDB Stream + Lambda + S3 + IoT Core で構成.

管理対象のデバイスが追加されると,認証情報を埋め込んだSDKを生成します. また,IoT Coreにもモノを作成し,証明書をアタッチします. これによって,SDKをインストールするだけで監視が可能になります.

f:id:arailly:20200318155551p:plain

SDKクラウドリソースのCI/CD

一応CI/CDも頑張っています. SDKの開発リポジトリを更新したら, SDKダウンロード用のS3バケットの中身を更新します. バックエンドも,developブランチにプッシュ・マージしたら ステージング環境にデプロイされ, masterブランチにマージしたら本番環境にデプロイするようにしています.

また,ほとんどのクラウドリソースはCloudFormationで管理しているので, IaCを実現できています.

f:id:arailly:20200318155548p:plain

ざっと解説するとこんなところです. 試したり作ったり壊したりを繰り返して, このアーキテクチャに落ち着きました.

ちょっとしたこだわりポイントは,固定費を0円にしたことです. これで,ユーザーが増えるまでは低コストでアプリを運用できます. その割には,ユーザーが増えてもそこそこスケールする構成にできたのではないかと思っています.

DynamoDBもセカンダリインデックスを使わず, パーティションキーとソートキーだけで高速にGETできるように設計しました.

アーキテクチャの今後の展望

HTTPSハンドラ

色々実装してわかったことは, Lambdaはちゃんと書かないとバグの温床になるということです. したがって,Lambdaの数を最小限にすることが重要です.

現状のアーキテクチャではHTTPハンドラが全てAPIGW + Lambdaなので, Lambdaが乱立しています. 多くのAPIはシンプルなCRUDしかしてないので, 全部AppSyncに乗せたいなあと思っています.

UIのリアルタイム更新

実は,このリアルタイム更新のためにAppSyncを使ったリアルタイムサブスクリプションを実装したのですが, IoTデバイスからGraphQLを叩くのが難しいことに気付いて結局無駄になっちゃいました..

それから大急ぎで実装したので,今回はAPIGWのWebSocketを採用しましたが, IoT Coreのトピックのリアルタイムサブスクリプションを使って実装することで こちらもLambdaレスにしたいです.

クラウドリソースの監視

かなりの数のクラウドリソースがあるので, 実運用時はCloudWatchを使ってちゃんとリソース監視しないと...

可用性

全てシングルAZなので, 東京リージョンで障害が起きたら終わりです. この辺りの知識が乏しいので, 時間があるときに勉強したいなあと思っています.

最後に

まだまだやれることは残っていますが, 未踏期間という限られた時間の中ではベストを尽くせたと思っています.

motchは3月中にリリース予定なので,応援してもらえると嬉しいです.

今後もアプリを改善して開発を継続する予定なので, ここはこうした方がいいよ,というフィードバックは大歓迎です.

あとは,せっかくサーバーレスで頑張ったので, AWSコミュニティやサーバーレスコミュニティで登壇もしてみたいなあと思ってます.

これからも頑張ります.

2019年度の未踏を終えて

2019年度の未踏が終わったのでこの1年を振り返ります.

未踏とは

IPA情報処理推進機構)が行なっている「未踏IT人材発掘・育成事業」のことです.

「未踏事業」は、ITを駆使してイノベーションを創出することのできる独創的なアイディアと技術を有するとともに、これらを活用する優れた能力を持つ、突出した人材を発掘・育成することを目的としています。

未踏事業ポータルページ:IPA 独立行政法人 情報処理推進機構

ざっくり言うと,IT技術を使って自分のやりたいことをやらせてもらえるプロジェクトです. プロジェクト達成のために依託金をもらえて,その使途は自由です. そして,プロジェクトマネージャ(PM)によるアドバイスを受けながらプロジェクトを進めていきます. 私たちはさくらインターネットの田中さん(@kunihirotanaka) に指導していただきました. いつも優しくも厳しいアドバイスをくださったり, いろんな人を紹介してくれたり, 本当にお世話になっています.

未踏期間で作ったもの

大学の同期(@mc475_46)と二人で, IoTデバイスの監視を誰でも簡単に実現できるSaaSmotch」を開発しました.

motchを使うと,SDKをインストールして2行追加するだけでIoTデバイスの監視が可能になります. また,直接インターネットに接続できないデバイスに対応するため,motch daemonを提供しています. motch daemonをインストールしたコンピュータのUSBポートにIoTデバイスを挿入するだけで, デバイスの管理ができるようになります.

SDKdaemonの内部では,motchのバックエンドに対して定期的に生存報告が行われていて, それが途切れればユーザーに通知がされるようになっています.

また,motchのバックエンドに対して,Webhookとして直接リクエストを送ることで デバイスの管理も可能になっています.

現在テスト中で,3月中にリリースするために開発を進めています. 応援していただけると嬉しいです🥰

未踏期間でやったこと

相方は主にデバイスSDKとWebフロントエンドを, 私は主にバックエンドを担当しました. 応募当時,クラウドコンピューティングとサーバーレスに興味があったので, 今回は自分で勝手にサーバーレス縛りで開発を進めていきました.

サーバーレスアーキテクチャとは

サーバーレスアーキテクチャとは,ざっくり一言で言うと, FaaS(処理時間だけ起動するサーバのようなもの)と BaaS(アプリケーションに必要な部品(DB,MQなど)をAPIを通して利用可能なサービス) を組み合わせてアプリケーションを構成する設計手法のことです1

なぜサーバーレスか

一般的に,サーバーレスを利用すると,常にサーバーを起動してクライアントからのリクエストを待ち受ける 従来のアーキテクチャ(サーバーフルとも言われます)に比べて,運用コストが低くなると言われています.

その理由は,DBやMQを自分でホストしたりメンテナンスしたりする必要がなく, サーバーが落ちることを心配しなくていいからです.

また,アプリケーションのローンチ初期はアプリケーションのアクセス回数が少ないため, 従量課金型のサービスを組み合わせた方が常時起動するサーバーを抱えるよりも安く済みます.

サーバーレスを採用した本音は

単なる技術的興味です笑

実は,未踏応募前の私はクラウドも初心者でサーバーレスもほとんど触ったことがなく, その特徴を聞いていつか使ってみたいなあと思っていました. しかし,周りに誰もサーバーレスの経験がある人がいなくて,友達との開発案件でも, 何度か提案はしたものの却下されてしまいました.

そんな時に相方から未踏に誘われました. 未踏なら実装にこだわっても許されるのではないかと思い, サーバーレスでやってみたいと提案書に書いたのがきっかけです.

結果的に,考えられる他のアプローチよりも低コストでそこそこスケーラブルな バックエンドができたのでよかったです.

実装したアーキテクチャ

最終的には,こんなアーキテクチャを実装しました.

f:id:arailly:20200301112321p:plain

運用に必要な部分はまだまだこれから実装しなければいけませんが, 機能要件としては,自分が作れる中で最高のものができたと思っているので, 心から満足しています.

アーキテクチャの詳細の解説記事もそのうち書きます.

追記:書きました.

arailly.hatenablog.com

サーバーレスを使い込んだ感想

辛いところもたくさんありますが, Webアプリを作るときのファーストチョイスとして間違いないと思います.

いいところ

第一に,クラウドベンダがインフラを丁寧に整備してくれてることです. サーバーの設定・運用の手間がないと人生がこんなにも豊かになるのかと再認識できました. そして,安くてそこそこスケールします. 規模が大きくなってくると高いのでリプレースが必要ですが, それはユーザーが増えたときに考えればいいです.

辛いところ

まだまだ新しい分野なので,サービスによっては情報が少ないことです. 特に辛いのは,AWSのドキュメントが読みにくいことです.

このチュートリアルでは、Amazon DynamoDB テーブルを AWS AppSync に移行してそれを GraphQL API に接続する方法について説明します。

AWS AppSync がユーザーに代わって DynamoDB リソースをプロビジョニングするようにできます。または、必要であれば、データソースとリゾルバーを作成することで、既存のテーブルを GraphQL スキーマに接続できます。いずれの場合も、GraphQL ステートメントを使用して DynamoDB データベースへの読み取りと書き込みを行うことができ、リアルタイムデータをサブスクライブできます。

GraphQL ステートメントが DynamoDB オペレーションに変換され、レスポンスが GraphQL に変換されるように、特定の設定ステップを完了しておく必要があります。このチュートリアルでは、いくつかの実際のシナリオおよびデータアクセスパターンを使用して、その設定手順の概要を示します。

チュートリアル:DynamoDB リゾルバー」より

初見で読めたらすごいと思います.日本語で書かれていないことは確かなので, 私はAWS語と呼んでいます. 一方で,ドキュメントがあるだけありがたいのは確かなので, 社会貢献として,時間があるときにノウハウをブログで共有しようと思います.

また,運用が楽である反面, ローカル開発環境でクラウド環境を再現するのが難しいです.

そして,サービスによってはCloudFormation管理が大変です. CloudFormationを書きたくないので 私はAWS SAMを使ってましたが, SAMは基本的にはLambdaの開発ツールだと思っているので, Lambdaを使わない選択肢を取るのが難しいです. APIGWのService Proxyとか.

あとは,クラウドのシステム障害時はどうしようもないです. ユーザーへの謝罪と神頼みしかやることがありません.

未踏を振り返って

頑張ったこと

応募当初,私はサーバーレスだけではなくAWSそのものの初心者でした. しかし,プロジェクト名に「サーバーレス」という単語を入れたからには, そのプロジェクト名に恥じないようなアーキテクチャを実装して, 最終報告会で発表しよう,と心に決めました.

そこで,手の空いた時間にはAWSのドキュメントを片っ端から読み込みました. このおかげでAWS語が読めるようになりました.

また,ドキュメントを読むだけではなく, とにかく手を動かしてサービスを実際に触ってみることを心がけていました. 実際に,20以上のAWSのサービスを実際にシステムに組み込んでみて, 使えるかどうか判断していました.

自分の部屋に閉じこもっていると視野が狭くなるのを感じたので, AWSやサーバーレスの勉強会,ワークショップ, コンペティションに積極的に参加して学びました.

それ以外にも,AWSで大規模なモバイルアプリを構築している会社の方にAWSをハックする方法を聞いたり, AWSのソリューションアーキテクトの人にもフィードバックをもらいながら開発を進めました.

その結果,そこそこスケーラブルで低コスト,かつメンテナンス性の高い motchのバックエンドを実装することができました.

また,100個以上のAWSリソースを組み合わせてアプリケーションを作った経験から, AWSのサーバーレス系サービスについて深く理解することができました.

院試や本業の研究,そのほかのプロジェクトとの並行作業だったのも少し大変でした. 結果的に,本業をおろそかにすることなく,未踏を完遂させられてよかったです. 研究室の指導教員の理解があって本当に助かりました.

未踏のいいところ

未踏はいいぞ.

最先端の技術を知ることができることです. そしてそれ以上に,それを実現する優秀な人々と話せることです. 未踏関係者みんなすごいです.尊敬してます.

さらに,PMやOB,外部のアドバイザーなど, 普通に生きてたら話せないようなすごい人に指導してもらえるのも素晴らしいところだと思います.

また,進捗報告が基本的に東京開催なのですが, 私のような首都圏在住者以外の人には交通費, 宿泊費をきちんと出してくれます. 朝早い時や夜遅くまでかかる時は,前泊, 後泊の宿泊費まで出してくれるので, その度に東京観光ができます (結局未踏関連でやることが多いので忙しくて遊べた試しがないですが).

お金を出してくれるだけではなく, 毎月クリエータやPMとしっかり議論する機会があるので, とても有意義でした.

そして,事務局の方が親切で対応が丁寧なことです. 一応国が絡むプロジェクトなので, お役所仕事的なタスクが降ってきたり, ややこしい制約があったりするのですが, 事務局が間に入ってきちんと面倒な仕事を吸収してくれます. ミーティングの準備なども本当にいいです.

残念だったところ

唯一残念だったのが,未踏期間を通して, 私の成果をきちんと評価してもらえなかったところです.

未踏には,言わばフルスタック信仰および 低レイヤー信仰が存在するように感じます. それに対して,クラウドのような抽象化された技術を組み合わせた提案はそれらと両極に位置するものです. そのため,未踏関係者にクラウドに詳しい人が少なく, 未踏の定期進捗報告会においても, アーキテクチャに関して有意義なフィードバックが得られたことはほとんどありませんでした. それどころか,誰でも簡単にできるんだろう,そんなこと小学生でもできるよ, というようなことを言われたのが辛かったです.

サーバーレスコミュニティや未踏の外部のAWSに詳しい方々には 私の実装したアーキテクチャを評価してもらえましたし, そのような方々からのフィードバックの方が参考になりました.

おそらく,クラウドを応用した提案が今まで未踏にほとんどなかったのだと思います. 今思うと,進捗報告にサーバーレスに詳しい人を招待してもらえるように提案しておけばよかったです.

最後に

少し悔しいこともありましたが,この未踏期間, 全力を尽くして頑張って本当によかったです. 自分が心から満足できるアーキテクチャを作り込むことができました. 同期のクリエータ,PM,OBとの交流も本当に有意義で, かけがえのない9ヶ月間になりました. 今後もこのつながりを大事にしていきたいと思います. IT技術を使ってやりたいことがあるという人は, 遠慮なく応募してみてください! 2020年度以降から依託金も増えましたし!

最後に,PMの田中さん,同期・OBのクリエータ,事務局の方々, お世話になった方々,本当にありがとうございました.

これからも頑張ります.

全国医療AIコンテストを開催しました

f:id:arailly:20191002103605p:plain

2019年9月28日,29日の2日間で「阪大AIメディカル研究会主催 全国医療AIコンテスト 2019」 を開催しました. 本記事はその記録です.

全国医療AIコンテスト 2019とは

医療AIに興味のある全国の大学生で集まって, AIを応用した医療・介護・ヘルスケアの最先端についての講演会と 医療データ解析コンペティションを行いました.

なぜこのようなイベントを開催することになったかというと, 全国の医学系の学部でAI系の勉強会を開催するサークルが増えていて, 一度そのような学生で集まって話してみたいなと思っていました.

そんな話を他の人にも持ちかけてみると,登壇していただける方や, 支援していただける企業の方が出てきてくださって,遠方からの学生の招待や 高速なクラウドの計算リソースを使用したコンペの開催が実現しました.

参加者数80名以上、10以上の大学の学生の方に参加していただき、文字通り全国規模のイベントでした。

非常に興味深い講演が聞けたり,全国の大学生と交流ができて非常に楽しかったです.

今回のイベントは,以下の団体・企業様にご支援いただきました. この場をお借りしてお礼申し上げます.

共催(敬称略):

  • 阪大医学部Python
  • コンソーシアム関西
  • ナレッジキャピタル

協賛(敬称略):

講演

どの講演も面白く,また,程よくテーマの多様性があってとても良かったです.

電子顕微鏡画像における耐性菌の識別 ー薬剤耐性化と形態変化の関係解明に向けてー」 青木 工太 先生(大阪大学産業科学研究所)

現代の大きな問題になっている,耐性菌についてのテーマです. 耐性菌をどのように検出して,どのように創薬に役立てていくかについてお話いただきました.

個人的に印象的だったのは,電子顕微鏡で撮影した細胞や分子の画像を非常に丁寧に前処理をしていることでした. どのように機械学習で使えるようにするかという工夫が面白かったです.

「疾患ゲノム研究の最新動向 ー機械学習の応用を含む解析手法の紹介ー」鈴木 顕 先生(大阪大学医学部 遺伝統計学教室)

ゲノム解析の方法や,それを実際に糖尿病の研究に適用して得られた成果をわかりやすく説明していただきました.

今後のゲノム解析バイオインフォマティクスの可能性を感じさせる興味深い発表でした.

NVIDIAにおけるヘルスケアへの取り組みと研究環境最前線」阮 佩穎 氏(NVIDIA合同会社

NVIDIAの最新の研究やソフトウェアの開発状況についてお話いただきました.

混合精度演算によるDeep Learningのさらなる高速化やcumlなどのOSSアノテーションツールの開発などといった ハードウェア・ソフトウェア開発の最前線から,医療応用に関する研究まで,非常に面白いお話でした.

「ライフ・メディカル領域のAI構築 ー画像を中心にー」袴田 和巳 氏(LPIXEL株式会社)

機械学習による医療データ解析システムや,医療業界ならではのビジネスの難しさについてのご講演でした.

機械学習システムを臨床の現場に実装しようという試みについてのお話がとても面白かったです.

また,社内におけるKaggleコンペなどの取り組みや,コンペで身につくスキルとその使い所にも言及されていて, 非常に為になるお話でした.

創薬・製薬・介護領域でのAI技術 ー画像からグラフ理論までー」浅谷 学嗣 氏(株式会社EXAWIZARDS)

グラフ畳み込みを用いた創薬・製薬技術の開発や医療・介護・製薬の現場に役立つロボット開発などの 技術についてのご講演でした.

最先端の機械学習に,ソフトウェア・ハードウェアの非常に高い技術力が合わさることによる無限の可能性を感じました.

「医療データコンペ入門」秋山 理(大阪大学医学部医学科6年)

阪大AIメディカル研究会のエース,医学部6年にしてKaggle Masterの秋山さんによる, 医療データコンペの概要と,それに対する解法や取り組み方についてのお話でした.

医学生・医療従事者など,非専門家がデータサイエンスを学ぶのにコンペに参加するのが最適だということ, そしてコンペを通して得られた,医療データを解析する難しさなどの知見を共有していただきました.

医療データ解析コンペティション

概要

今回のコンペティションのテーマは,「眼底画像から糖尿病網膜症を診断する」でした.

EDAからモデリングまでの一通りを押さえたベースラインとなるコードを用意し, 初心者でも短いコンペ時間の間に楽しんでもらえるように工夫しました.

このベースラインコードを読むだけでもかなり勉強になったのではないかと思います.

さらに,日本マイクロソフト様のご厚意でAzureのVMを使わせていただくことができ, 一人一台のVMインスタンス(Tesla V100付き)を用意することで,画像コンペでも 現実的な学習時間を実現できました.

今回使用したデータはツカザキ病院様のTsukazaki Optos Public Projectで公開されているもので, コンペでの使用も快諾いただきました.この場を借りてお礼申し上げます.

解法

データセットとして,患者さんの基本情報とその眼底画像を配布しました. テーマとしては,「眼底画像から糖尿病網膜症を診断する」だったのですが, スコア向上の鍵となるのは,患者さんの基本情報データの方でした.

例えば,以下のような特徴が予測に有効です.

  • 同じ患者に対して何枚も眼底画像があれば,何らかの病気を発症している可能性が高い.
  • 片目に病変が見られた場合,もう片方の目にも発症している可能性が高い.
  • 同じ患者の眼底画像は,同じ病変がある可能性が高い.

したがって,ニューラルネットで画像から大まかな推測(特徴量抽出)を行って, テーブルデータにその値を加えて特徴量エンジニアリングを行い,最終的な予測結果を出力する, というのが今回の想定解法でした.

しかし残念ながら,ほとんどの参加者が,初めからアンサンブルやモデル勝負に挑んでしまい, かなりの時間を使ってしまっていたようです.

最終的に,想定解法によるスコアを上回るスコアは出なかったものの,上位に入賞した人は 患者さんの基本情報の重要性に気づいていて,少し嬉しかったです.

また,画像をリサイズしてもスコアがほとんど変わらないということに気付くと, 学習時間が圧倒的に短くなり,多くの実験ができたのではないかと思います.

出題者としての感想

今回,私と秋山さんでコンペ設計を行ったのですが,画像コンペの2値分類で評価指標がAUCという典型パターンだったので, それほど悩む部分がなく,想定外のリークも発生せず,とても楽しかったです.

ここに気づいてくれるかな,と期待しながら参加者を見守っているのもワクワクしました.

ですが,もう少しデータを眺めて,患者の情報やリークを利用する努力をしてほしかったなあというのが正直なところです. 機械学習を持つと全てが特徴量に見える」 という人類の習性を改めて実感しました.

また,今回コンペのプラットフォームとしてKaggle InClassを使わせていただいたのですが, こちらも非常に使いやすく,ありがたかったです.

イベント運営の反省点

  • イベント名を「全国AIメディカルコンテスト」にするべきだった笑 AIメディカル研究会っぽさが無い..
  • コンペSlackは参加者を事前に招待すべきだった.
  • 思ったより会場のWi-fiが弱かった.その可能性を踏まえて,事前に準備が必要だと思いました.
  • 上位入賞者の解法共有をもう少し充実させるべきだった.上位入賞者への質問時間や,スコアに効いたことと効かなかったこと両方を共有する場があればよかったなと思います.
  • コンペのルール設定が甘かった.外部データ使用禁止と書いてあったのに,ImageNetのPretrainは認めるなど,詰めの甘さが目立ちました.最終的に,外部データの使用を許可するという形でルールを訂正しましたが,数人の学生には多大なご迷惑をおかけしました.本当に申し訳ございませんでした.

全体を通して

今回のイベントでは,文字通り全国から学生が集まってくれて,懇親会では幅広い交流をすることができ, とてもいい刺激になりました.

また,多くのスポンサー企業のおかげで,グランフロント大阪のとっても綺麗な会場を借りることができたり, V100付きのVMを使った画像コンペを企画できたりして,非常にレベルの高いイベントを開催することができました.

アンケートでもコンテンツ・運営に対して共に高評価をいただき,嬉しかったです.

個人的に,今までAIメディカル研究会で開催したイベントの中でも,かなり満足度の高いものとなりました.

運営メンバーの皆様,AIMS顧問の新岡先生,登壇者の皆様,共催・協賛の団体・企業の皆様, この度は本当にありがとうございました.

「AIメディカル夏の学校」を開催しました

f:id:arailly:20190826134752j:plain

昨日,進学塾の研伸館さんと,阪医Python会,そしてAIメディカル研究会の共催で, 「AIメディカル夏の学校」というイベントを開催しました.

受験に追われる中高生に,大学の勉強や最先端の研究がどんなものか体験してもらい, それを今後の勉強のモチベーションにしてもらいたいという目的で, Pythonプログラミングの基礎とAI・機械学習の基礎を体験してもらうという趣旨のイベントです.

当初定員を18人に設定したのですが,募集開始後2日で21人の応募があって定員オーバーになってしまったので, 少し増枠して実施に至ったのですが,中高生のプログラミングやAIに関する関心の高さに驚きました.

当日のプログラムとしては,午前中は私がPythonプログラミングの基礎についての講義を, 午後からはAIメディカル研究会の新岡先生がAIと機械学習の基礎についての講義を,演習を交えながら行いました.

私の講義の資料を共有します↓ colab.research.google.com

非常にレベルの高い講義だったにも関わらず,楽しんでくれている参加者の生徒さんが多く, とても有意義なイベントとなりました.

また今回,講義資料として,Google Colabを使用しました. 環境構築が不要で,教材の共有も簡単なので,スムーズに講義を進めることができました. このようなツールを無料で使えるのは素晴らしいですね.

今回のイベントをきっかけに,プログラミングや機械学習の有用性と面白さを知ってもらい, 今後の勉強や研究,将来やりたいことに繋げてもらえれば嬉しいなと思います.

AIMSデータサイエンス初心者講座 #6 に登壇しました

昨日,AIメディカル研究会のイベント「AIMSデータサイエンス初心者講座 #6『Kaggle入門』」に登壇しました.

Kaggleの初心者向けコンペ House Prices: Advanced Regression Techniques をテーマに, データ可視化,特徴量エンジニアリング,モデリング,評価,そして提出まで,実際のKaggleコンペの流れを経験してもらいました.

さらに,私の書いたコードをKernelで共有し,受講者自身にそれをForkしてもらって進めてもらうという形式を取ることで,Kernelの使い方にも慣れてもらいました.

今回の講座で特に強調したのは,バリデーションの重要性です. データを予測するときは,必ず訓練,バリデーション,テストデータに分割すること,モデルの評価はテストデータで行う必要があること,など,初心者が失敗しやすいポイントを詳しく説明しました.

Kaggleはバリデーションを学ぶための最高の教材なので,予測モデルを構築して実運用する前に,一度Kaggleで失敗しながら学んでもらいたいです.

今回使用したKernelはこちらです.

www.kaggle.com

Kernelの中で扱っている内容は以下の通りです.もしこのKernelが参考になったらupvoteお願いします笑

  • pandas_profilingseabornを使ったEDA
  • Label encodingやScalingなどの簡単な前処理
  • Ridge RegressionとRandom Forestによるモデリング
  • Hold-out Validationと5-fold CVの実装
  • 特徴量の重要度のプロット

最後に,AIメディカル研究会では,学生を中心に機械学習やその医療応用についての勉強会やイベントを行なっています. どなたでも参加していただけるので,お気軽にご参加ください.

開催日程や内容は,Twitter(@ou_aims)やconnpassなどで定期的に告知しています.