そごうソフトウェア研究所

SOA、開発プロセス、ITアーキテクチャなどについて書いています。Twitterやってます@rsogo

AWS Lambdaの何が嬉しかったか。Excel生成WebサービスのLambda関数化

AWS Lambdaを使った開発をやってみて何が嬉しかったのかを書いておこうと思います。

Lambda関数化したのは以下のようなWebサービスでした

  • SPAなWebアプリからJSONでリクエストを受け取る
  • JavaExcelを扱うライブラリであるPOIでExcelのテンプレートファイルにJSONで受け取ったデータを埋め込む
  • Excelをクライアントに返す(ブラウザでダウンロードが始まる)

Excelを触る時点で、Web系の技術だけで完結するのは難しいので、 この部分だけJavaでやるというのは今後もあるんじゃないかなと思います。

で、次のような課題がありました

  • 普段は大丈夫だけど、たまにでかいデータが来て、Java側でOutOfMemoryが発生する
  • そもそもExcel作成リクエストはたまにしか実行されない(業務のひとまとまりの作業が終わって帳票を出したいときだけ)
  • なのに、でかいデータのためにそれなりのサイズのEC2を用意しておかないといけない

Lambda関数化したことで

  • 使うときのみリソースを割り当てるので、思い切ったリソースを割り当ててもコスト的には下がる。これは処理頻度が低いサービスでは大きいメリットだと思います
  • 副次的な効果として、これまで同じJavaアプリケーションサーバー上で実行していた他のアプリケーションがExcel作成WebサービスのOutOfMemoryの巻き添えを食っていたのですが、単独機能のサービスとして切り出されたことで、お互いに影響を受けることがなくなりました。マイクロサービス化ができたわけです。

他の用途ですと、Javaの立ち上がり時の遅さが気になったかもしれませんが、 今回のケースだと、そもそもExcelを生成するのに10秒くらいかかるような Webサービスだったので性能は気になりませんでした。

まとめ

AWS Batchも使ってみたいなーと思っているサービスで、 バッチという処理頻度が低い処理に対して、 実行されるときだけ大きいリソースを割り当てて(必要だったら並行でいっぱい立ち上げて)、 終わったら終了というのは非常にクラウドらしく、魅力的です。

もちろんサーバレスになることで、運用が楽になるか、スケールするとかいい事はあると思うのですが、 処理面での直接的な効果として頻度が低い処理に、思い切りリソースを割り当てられるというメリットがありました。

Lambda関数をAPI Gatewayで公開する(シンプル版)

先日、JavaでLambdaを作るエントリを書きましたが、今回、API Gatewayを使って、Lambdaで実装した処理をWebサービスとして公開したいと思います。

API GatewayはRESTfulなAPIを簡単に作れます。 RESTfulなので、まずはリソースを定義して、そのリソースに対するGET、POST、PUT、DELETEなどのメソッドを提供します。 後はどのバックエンドに接続するのかが、基本的な手順です。

手順はこちらをベースに進めます。

docs.aws.amazon.com

Lambda関数の作成

バックエンドはLambda関数を使います。 こちらで公開されているNode.jsのサンプルを、そのまま使って作成しておきました。

docs.aws.amazon.com

APIの作成

f:id:begirama:20180513002008p:plain

リソースの定義

最初にリソースを定義します。

f:id:begirama:20180513002455p:plain

  • リソース名: 任意の名前です。ここではproxyとしています
  • リソースパス: /proxy/{itemId}など個別のパターンを設定できますが、ここではワイルドカードになる{proxy+}を使います。これで、ルートのURL以下はなんでも受け取ります。

メソッドの定義

f:id:begirama:20180513002542p:plain アクションで、メソッドの作成を選びます。

f:id:begirama:20180513002702p:plain Lambda関数プロキシを選択します。 リージョンを選択して、Lambda関数名に何文字か入力すると、候補が表示されます。

f:id:begirama:20180515072400p:plain ここまでの状態はこんな感じ。

APIのデプロイ

f:id:begirama:20180513003023p:plain このままでは呼び出せないので、APIのデプロイを行います。

f:id:begirama:20180513003052p:plain ステージというのが設定できて、ステージごとに認証やスロットリングの設定を変更することができます。 例えば、社内用のAPIと、取引先用のAPIなんかで分けることもできそうです。

f:id:begirama:20180513003200p:plain

APIの呼び出し

デプロイが完了すると、こんな感じのURLが割り当てられます。

f:id:begirama:20180515070649p:plain

このまま呼び出すと、

{"message":"Missing Authentication Token"}

エラーが返ってきます。 エラーメッセージの内容がわかりにくいのですが、上のURLはルートのもので、子リソース(ここではproxy)が定義されている場合には、そのまま呼び出すとこのエラーが返ってきます。

そこで、https://dummy.execute-api.ap-northeast-1.amazonaws.com/dev/proxyのような子リソースを含むURLで呼び出すと、 欲しかった結果を得られます。

Install Node.js v8 and npm 5.6 for macOS High Sierra

以前、Node.js v5をMacにインストールする手順を書きましたが、MacBook Proを新調したので、新しくセットアップした手順をメモします。

begirama.hatenablog.com

公式サイト Node.js からMac用のpkgファイルをダウンロードします。2018/5/14時点で安定版の最新はv8.11.1でした。

pkgを実行して、Node.jsをインストールします。

セットアップ後のバージョンの確認は以下です。

$ node -v
v8.11.1

ついでにnpmのバージョンも確認します。

$ npm -v
5.6.0

npmを使って、gulpをインストールしてみます。

$ sudo npm install --global gulp-cli
Password:
/usr/local/bin/gulp -> /usr/local/lib/node_modules/gulp-cli/bin/gulp.js
+ gulp-cli@2.0.1
added 238 packages in 5.825s

bowerも入れてみます。

$ sudo npm install -g bower
Password:
/usr/local/bin/bower -> /usr/local/lib/node_modules/bower/bin/bower
+ bower@1.8.4
added 1 package in 2.983s


   ╭─────────────────────────────────────╮
   │                                     │
   │   Update available 5.6.0 → 6.0.1    │
   │     Run npm i -g npm to update      │
   │                                     │
   ╰─────────────────────────────────────╯

正常に動作していそうですね。

JavaでAWS Lambdaの実装をやってみる

各種情報

  • 開発者ガイド docs.aws.amazon.com

  • AWS Lambdaには設計書と呼ばれるテンプレートが用意されていて、テンプレートをカスタマイズすることでいろいろな機能を試すことができます f:id:begirama:20180415104106p:plain

チュートリアル

チュートリアルへのリンクが公開されています。 aws.amazon.com

開発者ドキュメントの中に含まれるチュートリアルdocs.aws.amazon.com

テストと実行結果の確認

Jarのデプロイ

Jarファイルをアップロードすることでデプロイができます。 デプロイ時に必要なのは次の項目の設定です。 - Zip/Jarファイルの直接アップロードか、S3からのアップロードか - ランタイム - Java8を指定します - ハンドラー - エントリポイントになるメソッドの{パッケージ名}.{クラス名}::{メソッド名}の形式で指定します(例: example.Hello::handleRequest

デプロイして実行時にエラーになった場合には、コンソールにエラーが出力されます。 これはLambdaはJavaのランタイムとして、Java8がサポートされているが、ビルド時にJava9でコンパイルしてしまったためエラーが発生しています。 f:id:begirama:20180415084134p:plain

テストと実行結果の確認

テストの設定を行って、テストを実行します。入力イベントを設定する前に試してみることができます。

  • テストの設定 f:id:begirama:20180415103536p:plain

  • テスト結果の確認 f:id:begirama:20180415103452p:plain

エラー時

f:id:begirama:20180415091308p:plain

CouldWatchにもエラーのログが出力されています。

f:id:begirama:20180415093108p:plain

Apache ActiveMQ ArtemisのLibrary artemis-native-64 not foundメッセージは問題あるのか調査

AppPot - 企業向けスマートデバイスアプリ開発のためのプラットフォーム」の内部では、Apache ActiveMQ Artemisを使用しています。 下記のようなメッセージが出るので調査をしてみました。

DEBUG [org.apache.activemq.artemis.jlibaio] (MSC service thread 1-2) Library artemis-native-64 not found!
DEBUG [org.apache.activemq.artemis.jlibaio] (MSC service thread 1-2) artemis-native-32 -> error loading the native library: java.lang.UnsatisfiedLinkError: no artemis-native-32 in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
        at java.lang.Runtime.loadLibrary0(Runtime.java:870)
        at java.lang.System.loadLibrary(System.java:1122)
        at org.apache.activemq.artemis.jlibaio.LibaioContext.loadLibrary(LibaioContext.java:68)
        at org.apache.activemq.artemis.jlibaio.LibaioContext.<clinit>(LibaioContext.java:88)
        at org.apache.activemq.artemis.core.io.aio.AIOSequentialFileFactory.isSupported(AIOSequentialFileFactory.java:107)
        at org.wildfly.extension.messaging.activemq.ActiveMQServerService.start(ActiveMQServerService.java:155)
        at org.jboss.msc.service.ServiceControllerImpl$StartTask.startService(ServiceControllerImpl.java:1963)
        at org.jboss.msc.service.ServiceControllerImpl$StartTask.run(ServiceControllerImpl.java:1896)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

DEBUG [org.apache.activemq.artemis.jlibaio] (MSC service thread 1-2) Library artemis-native-32 not found!
DEBUG [org.apache.activemq.artemis.jlibaio] (MSC service thread 1-2) Couldn't locate LibAIO Wrapper
DEBUG [org.jgroups.protocols.FD_SOCK] (FD_SOCK pinger,swarm-jgroups,amapawsmbw02) amapawsmbw02: ping_dest is ip-10-236-2-198, pingable_mbrs=[ip-10-236-2-198, amapawsmbw02]
INFO  [org.wildfly.extension.messaging-activemq] (MSC service thread 1-2) WFLYMSGAMQ0075: AIO wasn't located on this platform, it will fall back to using pure Java NIO. Your platform is Linux, install LibAIO to enable the AIO journal and achieve optimal performance.

Apache ActiveMQ ArtemisのPersistenceのドキュメントに依ると、Java NIOとLinuxのネイティブの非同期IOライブラリ ( libaio ) を使う2つの実装が用意されているようです。

Persistence | ActiveMQ Artemis Documentation

libaioについては、更にこちらのドキュメントで、詳細に記述されています。

Libaio Native Libraries | ActiveMQ Artemis Documentation

libActiveMQAIO32.so または libActiveMQAIO64.so でlibaioを使うのですが、これがライブラリのパスに見つからないというメッセージがDEBUGレベルで出ています。 そのため、Java NIOを使いますよ、LibAIOを使ったほうが性能が良くなりますよ、というログがでているみたいですね。

動作させる上では問題なさそうですが、性能を向上させるためには対応したほうが良さそうです。

普通のWildFly10にはlibartemis-native-64.soが含まれているのを確認しましたが、 今このログがでているのはWildfly Swarmで実装しているアプリケーションででているので、 Wildfly Swarmのパッケージングを調査しようと思います。

EXPLAINによるMySQLの実行計画の確認

担当しているソフトウェアで、運用しているとMySQL内のレコードが増えて性能が悪化するというお問い合わせをいただきました。 そこで、Indexを改善しようということになったのですが、どこにIndexを付けるべきか確認するためにMySQLの実行計画を確認しました。

自分のバックグラウンドとしては、データベースは特別詳しいわけではないので、 Indexは論理的に付けてはいるものの、それがあっているのかどうか確証はありませんでした。

で、調べたところMySQLでも実行計画が確認できるみたいなので、今回、それを使って確認を行ってみました。

MySQL :: MySQL 5.6 リファレンスマニュアル :: 8.8 クエリー実行プランの理解

対象のテーブル

こんなテーブルです。ユーザーのセッション情報を格納するためのテーブルで、ログアウトされないと、レコードが蓄積されていきます。

MySQL [apppot]> desc UserSession;
+-----------------+--------------+------+-----+---------+-------+
| Field           | Type         | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| userToken       | varchar(255) | NO   | PRI | NULL    |       |
| appTableId      | bigint(20)   | YES  |     | NULL    |       |
| companyId       | bigint(20)   | YES  |     | NULL    |       |
| deviceUDID      | varchar(255) | YES  |     | NULL    |       |
| loginDate       | datetime     | YES  |     | NULL    |       |
| tokenExpireDate | datetime     | YES  |     | NULL    |       |
| userId          | bigint(20)   | YES  |     | NULL    |       |
+-----------------+--------------+------+-----+---------+-------+
7 rows in set (0.05 sec)

こんな感じのSQLが実行されます。

select * from UserSession 
where userId=205 
and appTableId=4 
and deviceUDID='apppotsdkjs'
 and tokenExpireDate>='2018-01-12 22:16:13';

MySQLの実行計画の確認は手軽で、EXPLAINを頭に付けるだけでOKです。

Index追加前

rowsに122381という値が入っていますが、いわゆるフルスキャンが発生して、全部のレコードをチェックしていることがわかりました。そりゃ遅いですね。

MySQL [apppot]> EXPLAIN select * from UserSession where userId=205 and appTableId=4 and deviceUDID='apppotsdkjs' and tokenExpireDate>='2018-01-12 22:16:13';
+----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table       | type | possible_keys | key  | key_len | ref  | rows   | Extra       |
+----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
|  1 | SIMPLE      | UserSession | ALL  | NULL          | NULL | NULL    | NULL | 122381 | Using where |
+----+-------------+-------------+------+---------------+------+---------+------+--------+-------------+
1 row in set (0.01 sec)

userIdにIndexを追加してみる

試しにuserIdにIndexを追加してみます。 ログインするユーザーが十分バラけていれば、これでも早くなりそうな感じがします。

ALTER TABLE UserSession 
ADD INDEX index_userId(userId);

実行計画はこんな感じ。チェックする対象のrowsの値が1/4になってますね。

MySQL [apppot]> EXPLAIN select * from UserSession where userId=205 and appTableId=4 and deviceUDID='apppotsdkjs' and tokenExpireDate>='2018-01-12 22:16:13';
+----+-------------+-------------+------+---------------+--------------+---------+-------+-------+-------------+
| id | select_type | table       | type | possible_keys | key          | key_len | ref   | rows  | Extra       |
+----+-------------+-------------+------+---------------+--------------+---------+-------+-------+-------------+
|  1 | SIMPLE      | UserSession | ref  | index_userId  | index_userId | 9       | const | 33436 | Using where |
+----+-------------+-------------+------+---------------+--------------+---------+-------+-------+-------------+
1 row in set (0.00 sec)

複数カラムのIndex

MySQLは複数のIndexあっても、どれか1つしか使ってくれません。 そこで、複数のカラムで1つのIndexを作ることができます。遅いSQLの条件で使っているカラムの組み合わせてIndexを作ります。

ALTER TABLE UserSession 
ADD INDEX index_get_session(userId, appTableId, deviceUDID, tokenExpireDate);

実行計画を見てみましょう。rowsの値が3桁減ってますね。 possible_keysが使用可能なIndex、keyが実際に使われたIndexということのようです。 新しく付けたindex_get_sessionが使われてますね。

MySQL [apppot]> EXPLAIN select * from UserSession where userId=205 and appTableId=4 and deviceUDID='apppotsdkjs' and tokenExpireDate>='2018-01-12 22:16:13';
+----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
| id | select_type | table       | type  | possible_keys                  | key               | key_len | ref  | rows | Extra                 |
+----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
|  1 | SIMPLE      | UserSession | range | index_userId,index_get_session | index_get_session | 792     | NULL |  258 | Using index condition |
+----+-------------+-------------+-------+--------------------------------+-------------------+---------+------+------+-----------------------+
1 row in set (0.08 sec)

以上です。

macOSにTensorFlowをセットアップする (2018/04更新)

マニュアルInstalling TensorFlow on macOS  |  TensorFlowでオススメされているvirtualenvを使った、セットアップを行います。

このドキュメントは2018/04/29にMacBook Pro 2017年モデルに環境構築をするために、再確認して更新しています。

使用した環境

  • macOS High Sierra
  • Tensorflow 1.8
  • pip
    Pythonのパッケージ管理ツール
  • Virtualenv
    Virtualenv — virtualenv 15.1.0 documentation
    Virtualenvは、独立したPython実行環境を作ることができるツールです。Virtualenvを使うことで、他の環境から影響を受けたり、与えたりすることがないので、好き放題実験することができます。

事前準備

macOSにHomebrewのセットアップを行っています。

1. Python環境の構築

pyenvでPythonの複数環境を構築して、Python3をインストールしていきます。

1.1. pyenvをHomebrewでインストール

$ brew install pyenv

1.2. Python3をインストール

利用できるPythonのバージョンを確認

$ pyenv install -l
Available versions:
  2.1.3
  2.2.3

(略)

  3.6.2
  3.6.3
  3.6.4
  3.6.5
  3.7.0b2
  3.7-dev
  3.8-dev
  activepython-2.7.14
  
  (略)

Python 3をインストール

$ pyenv install 3.6.5
python-build: use openssl from homebrew
python-build: use readline from homebrew
Downloading Python-3.6.5.tar.xz...
-> https://www.python.org/ftp/python/3.6.5/Python-3.6.5.tar.xz
Installing Python-3.6.5...
python-build: use readline from homebrew
Installed Python-3.6.5 to /Users/rsogo/.pyenv/versions/3.6.5

2. Virtualenvのインストール

pipを使ってVirtualenvをインストールします。 --upgradeは関連パッケージに新しいバージョンがある場合は、更新するというオプションです。

$ sudo pip install --upgrade virtualenv
The directory '/Users/rsogo/Library/Logs/pip' or its parent directory is not owned by the current user and the debug log has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want the -H flag.
The directory '/Users/rsogo/Library/Caches/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want the -H flag.
You are using pip version 6.0.6, however version 9.0.1 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.
The directory '/Users/rsogo/Library/Caches/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want the -H flag.
Collecting virtualenv
  Downloading virtualenv-15.1.0-py2.py3-none-any.whl (1.8MB)
    100% |################################| 1.8MB 1.9MB/s 
Installing collected packages: virtualenv

Successfully installed virtualenv-15.1.0

3. Tensorflowの環境を作る

3.1 VirtualenvでTensorflowの環境を作る

-pオプションでセットアップしたpython3の環境を指定して、Virtualenv環境を作ります。 tensorflowというディレクトリをターゲットとしています。ディレクトリ名は任意です。

rsogo-Mac-Book-2:~ rsogo$ virtualenv --system-site-packages -p ~/.pyenv/versions/3.6.5/bin/python3.6 tensorflow
Running virtualenv with interpreter /Users/rsogo/.pyenv/versions/3.6.5/bin/python3.6
Using base prefix '/Users/rsogo/.pyenv/versions/3.6.5'
New python executable in /Users/rsogo/tensorflow/bin/python3.6
Also creating executable in /Users/rsogo/tensorflow/bin/python
Installing setuptools, pip, wheel...done.

3.2. Virtualenv環境を有効にする

cd tensorflow/
$ source bin/activate
(tensorflow) Ryohei-no-MacBook-Pro:tensorflow rsogo$ 

これで、作ったVirtualenv環境に入っています

3.3. Tensorflowのインストール

$ pip3 install --upgrade tensorflow
Collecting tensorflow
  Downloading tensorflow-1.2.1-cp36-cp36m-macosx_10_11_x86_64.whl (34.1MB)
    100% |████████████████████████████████| 34.1MB 37kB/s 

(略)

Successfully built protobuf markdown html5lib
Installing collected packages: six, protobuf, numpy, markdown, backports.weakref, html5lib, bleach, werkzeug, tensorflow
Successfully installed backports.weakref-1.0rc1 bleach-1.5.0 html5lib-0.9999999 markdown-2.6.8 numpy-1.13.1 protobuf-3.3.0 six-1.10.0 tensorflow-1.2.1 werkzeug-0.12.2

4. 動作確認

4.1. チュートリアルをやってみる

下記のソースをtutorial1.pyという名前で作成します。

import tensorflow as tf
node1 = tf.constant(3.0, dtype=tf.float32)
node2 = tf.constant(4.0) # also tf.float32 implicitly
print(node1, node2)

sess = tf.Session()
print(sess.run([node1, node2]))

node3 = tf.add(node1, node2)
print("node3: ", node3)
print("sess.run(node3): ", sess.run(node3))

4.2. 実行してみます

$ python tutorial1.py 
Tensor("Const:0", shape=(), dtype=float32) Tensor("Const_1:0", shape=(), dtype=float32)
2017-08-06 00:32:11.351356: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use SSE4.2 instructions, but these are available on your machine and could speed up CPU computations.
2017-08-06 00:32:11.351380: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX instructions, but these are available on your machine and could speed up CPU computations.
2017-08-06 00:32:11.351385: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use AVX2 instructions, but these are available on your machine and could speed up CPU computations.
2017-08-06 00:32:11.351390: W tensorflow/core/platform/cpu_feature_guard.cc:45] The TensorFlow library wasn't compiled to use FMA instructions, but these are available on your machine and could speed up CPU computations.
[3.0, 4.0]
node3:  Tensor("Add:0", shape=(), dtype=float32)
sess.run(node3):  7.0

動いてそうですね。