形態素解析をするLambdaをsamでdeployしたい

本記事は[AWS LambdaとServerless Advent Calendar 2021](https://qiita.com/advent-calendar/2021/lambda)、21日目の記事です。

ちょっと前に業務でとあるアイテムのサジェスト系のAPIのプロトタイプを作る時にMecabを使おうと思ったのですが、ちょちょいとはできずに一旦含めないことにした事があります。
でも結局の所、必要になりそうなのでやっておこう。
似たようなネタはけっこうあって、何番煎じなん？ってトコはあるのですが、samかつコンテナでやる例は見つからなかったのでセーフとします。

#　何をやるのか

実際にはもうちょっと複雑なのですが、 `今回は名称のDBがあって、それに対して名詞の単語のみでクエリを実行する` というシンプルなシナリオとします。
例えば、 **「伝説の勇者が使ったとされるひのきのぼう」**で検索されたとします。

```
伝説	名詞,一般,*,*,*,*,伝説,デンセツ,デンセツ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
勇者	名詞,一般,*,*,*,*,勇者,ユウシャ,ユーシャ
が	助詞,格助詞,一般,*,*,*,が,ガ,ガ
使っ	動詞,自立,*,*,五段・ワ行促音便,連用タ接続,使う,ツカッ,ツカッ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
と	助詞,格助詞,引用,*,*,*,と,ト,ト
さ	動詞,自立,*,*,サ変・スル,未然レル接続,する,サ,サ
れる	動詞,接尾,*,*,一段,基本形,れる,レル,レル
ひのき	名詞,一般,*,*,*,*,ひのき,ヒノキ,ヒノキ
の	助詞,連体化,*,*,*,*,の,ノ,ノ
ぼう	名詞,一般,*,*,*,*,ぼう,ボウ,ボウ
```

名詞としては、**伝説、勇者、ひのき、ぼう**が抜き出せました。

今回はmecabという形態素解析エンジンをLambda上で用いて名詞に分解する、ということがやりたい、という話となります。本当はLambda Layerでやってみたかったのですが、250MBの制限があったりとか、Layer with コンテナができなかったりとかで諦めました。

## mecabとは
オープンソースの形態素解析エンジンです。文章を食わせると、名詞や動詞など単語に分解してくれます。分かち書きとデータベース検索を合わせて使う時になんかにも便利です。

https://ja.wikipedia.org/wiki/MeCab

> MeCabはオープンソースの形態素解析エンジンで、奈良先端科学技術大学院大学出身、現GoogleソフトウェアエンジニアでGoogle 日本語入力開発者の一人である工藤拓[1][2]によって開発されている。名称は開発者の好物「和布蕪（めかぶ）」から取られた。

## mecab-python3

pythonでmecabを使う時にはこのモジュールを使います。
https://pypi.org/project/mecab-python3/

が、mecabを使うにはこれだけでは足りず、別途mecabのライブラリ(バイナリ)や辞書をインストールしなければいけません。
requirements.txtを用意するだけでは足りず、モジュールごとアップロードするだけでも足りません。
これはlambdaをコンテナイメージとして構築する事にし、イメージ構築時にインストールする事にします。

## NEologd

NEologd とはmecabで使用できる、どデカい辞書です。
比較的新しいワードにも対応しているらしく、新しいデータを対象とするサービスでは必要になりそうです。
必要なものだけに絞ったりもできるらしですが、今回は深く考えずに突っ込んでいます。

# Lambdaの作成
## hello worldを一回作る(テンプレ)

まず、前提としてsam cliのインストールが必要です。まだの方はググってawsへのdocumentへたどり着いていただければと思います。
※古いsam cliをお使いの方はDocker imageに対応しているversionにupgradeして下さい

で、最初はsam initでテンプレから作っておきます

```bash
$ sam init --name lambda-python-mecab-sam-example 
Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1
What package type would you like to use?
	1 - Zip (artifact is a zip uploaded to S3)	
	2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 2

Which base image would you like to use?
	1 - amazon/nodejs14.x-base
	2 - amazon/nodejs12.x-base
	3 - amazon/nodejs10.x-base
	4 - amazon/python3.9-base
	5 - amazon/python3.8-base
	6 - amazon/python3.7-base
	7 - amazon/python3.6-base
	8 - amazon/python2.7-base
	9 - amazon/ruby2.7-base
	10 - amazon/ruby2.5-base
	11 - amazon/go1.x-base
	12 - amazon/java11-base
	13 - amazon/java8.al2-base
	14 - amazon/java8-base
	15 - amazon/dotnet5.0-base
	16 - amazon/dotnetcore3.1-base
	17 - amazon/dotnetcore2.1-base
Base image: 4

...

(続く)
```

一番のポイントは　**What package type would you like to use?** で `2` と答えることです。

```
(続き)

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

AWS quick start application templates:
	1 - Hello World Lambda Image Example
	2 - PyTorch Machine Learning Inference API
	3 - Scikit-learn Machine Learning Inference API
	4 - Tensorflow Machine Learning Inference API
	5 - XGBoost Machine Learning Inference API
Template selection:  1

    -----------------------
    Generating application:
    -----------------------
    Name: lambda-python-mecab-sam-example
    Base Image: amazon/python3.9-base
    Architectures: x86_64
    Dependency Manager: pip
    Output Directory: .

    Next application steps can be found in the README file at ./lambda-python-mecab-sam-example/README.md
    

    Commands you can use next
    =========================
    [*] Create pipeline: cd lambda-python-mecab-sam-example && sam pipeline init --bootstrap
    [*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
```

その他にはpythonの3.9を選んだりしつつ、とりあえずHello Worldのテンプレを選びます

-----

上記を終えるとこんな感じの構成になってるはずです。

```
.
├── README.md
├── __init__.py
├── events
│   └── event.json
├── hello_world
│   ├── Dockerfile
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── template.yaml
└── tests
    ├── __init__.py
    └── unit
        ├── __init__.py
        └── test_handler.py
```

ここで編集する必要があるのは3つ

- Dockerfile
- app.py
- requirements.txt

ですね。
Dockerfileではmecabやライブラリをインストールします。
app.pyではMeCabをimportしたプログラムを動かします
requirements.txtではmecab-python3を含むライブラリを管理します。

## Dockerfileの作成

```Dockerfile
FROM public.ecr.aws/lambda/python:3.9

RUN yum install -y file tar gzip unzip gcc make swig gcc-c++ git

RUN curl -L 'https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7cENtOXlicTFaRUE' -o mecab-0.996.tar.gz \
    && tar zvxf mecab-0.996.tar.gz
RUN (cd mecab-0.996 && ./configure --prefix=/var/task --enable-utf8-only && make && make install)
RUN curl -L "https://drive.google.com/uc?export=download&id=0B4y35FiV1wh7MWVlSDBCSXZMTXM" -o mecab-ipadic-2.7.0-20070801.tar.gz \
    && tar -zxvf mecab-ipadic-2.7.0-20070801.tar.gz
RUN (cd mecab-ipadic-2.7.0-20070801 \
    && ./configure --prefix=/var/task --with-charset=utf8 --with-mecab-config=/var/task/bin/mecab-config && make && make install)
RUN git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git
RUN yum install -y which xz patch openssl
RUN ln -s /var/task/bin/* /var/lang/bin/
RUN (cd mecab-ipadic-neologd && ./bin/install-mecab-ipadic-neologd -n -a -y)

COPY app.py requirements.txt ./
RUN python3.9 -m pip install -r requirements.txt -t .

# Command can be overwritten by providing a different command in the template directly.
CMD ["app.lambda_handler"]
```

mecabやmecabで使用するどデカい辞書をインストールしています。
どデカいのでbuildにはけっこう時間がかかりますが、Dockerfileなので一度行えばcacheが効き、プログラムの更新などの際のbuildはさほど時間がかからなくなります。

## requirements.txt

```text
requests
wheel
mecab-python3==1.0.4
```

もともとあったのrequestsとmecab-python3を入れるときに必要になったwheelを入れる予定

## app.py

```python
import os
import sys
import json

# preload libmecab
import ctypes
libdir = os.path.join(os.getcwd(), '/var/task/lib')
libmecab = ctypes.cdll.LoadLibrary(os.path.join(libdir, 'libmecab.so'))

import MeCab

# IPA辞書を利用
ipadic_tagger = MeCab.Tagger('-r /dev/null -d /var/task/lib/mecab/dic/ipadic')
# NEologdを利用
neologd_tagger = MeCab.Tagger('-r /dev/null -d /var/task/lib/mecab/dic/mecab-ipadic-neologd')


def lambda_handler(event, context):

    keyword = event['queryStringParameters']['keyword']

    print (ipadic_tagger.parse(keyword))
    print (neologd_tagger.parse(keyword))
    return {
        "statusCode": 200,
        'headers':{
            'context-type':'text/plain'
        },
        "body": ipadic_tagger.parse(keyword),
    }

```

# build

buildを行います。
`sam build` と実行するだけなので簡単。
※dockerに割り当ててるメモリが2GBだとコケたので3GB以上にする必要がありそうです。macだとPreferencesのResourcesから。

```bash
$ sam build
Building image for HelloWorldFunction function
Setting DockerBuildArgs: {} for HelloWorldFunction function
Step 1/13 : FROM public.ecr.aws/lambda/python:3.9
 ---> b509e4dde698
Step 2/13 : RUN yum install -y file tar gzip unzip gcc make swig gcc-c++ git

......

Step 13/13 : CMD ["app.lambda_handler"]
 ---> Running in 29ad4bb3cd30
 ---> 6aab2db8b393
Successfully built 6aab2db8b393
Successfully tagged helloworldfunction:python3.9-v1


Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Test Function in the Cloud: sam sync --stack-name {stack-name} --watch
[*] Deploy: sam deploy --guided
```

# ローカルでテスト

```sam local start-api``` でローカルでテスト実行ができます。

```bash
$ 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
2021-12-12 22:03:37  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
```

ブラウザで [http://localhost:3000/hello?keyword=“裏切り者は誰だ！”世界的人気！２億ダウンロード「Among　Us」宇宙船を舞台にした人狼ゲーム！](http://localhost:3000/hello?keyword=“裏切り者は誰だ！”世界的人気！２億ダウンロード「Among　Us」宇宙船を舞台にした人狼ゲーム！)　みたいに開くとmecabの動作を確認できるはずです。

```bash
START RequestId: 27c6e221-ecb4-4ae3-b9ca-9f0101f4cba0 Version: $LATEST
“	記号,括弧開,*,*,*,*,“,“,“
裏切り者	名詞,一般,*,*,*,*,裏切り者,ウラギリモノ,ウラギリモノ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
誰	名詞,代名詞,一般,*,*,*,誰,ダレ,ダレ
だ	助動詞,*,*,*,特殊・ダ,基本形,だ,ダ,ダ
！	記号,一般,*,*,*,*,！,！,！
”	記号,括弧閉,*,*,*,*,”,”,”
世界	名詞,一般,*,*,*,*,世界,セカイ,セカイ
的	名詞,接尾,形容動詞語幹,*,*,*,的,テキ,テキ
人気	名詞,一般,*,*,*,*,人気,ニンキ,ニンキ
！	記号,一般,*,*,*,*,！,！,！
２	名詞,数,*,*,*,*,２,ニ,ニ
億	名詞,数,*,*,*,*,億,オク,オク
ダウンロード	名詞,サ変接続,*,*,*,*,ダウンロード,ダウンロード,ダウンロード
「	記号,括弧開,*,*,*,*,「,「,「
Among	名詞,固有名詞,組織,*,*,*,*
　	記号,空白,*,*,*,*,　,　,　
Us	名詞,固有名詞,組織,*,*,*,*
」	記号,括弧閉,*,*,*,*,」,」,」
宇宙船	名詞,一般,*,*,*,*,宇宙船,ウチュウセン,ウチュウセン
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
舞台	名詞,一般,*,*,*,*,舞台,ブタイ,ブタイ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
し	動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
人	名詞,一般,*,*,*,*,人,ヒト,ヒト
狼	名詞,一般,*,*,*,*,狼,オオカミ,オーカミ
ゲーム	名詞,一般,*,*,*,*,ゲーム,ゲーム,ゲーム
！	記号,一般,*,*,*,*,！,！,！
EOS

“	記号,括弧開,*,*,*,*,“,“,“
裏切り者	名詞,一般,*,*,*,*,裏切り者,ウラギリモノ,ウラギリモノ
は	助詞,係助詞,*,*,*,*,は,ハ,ワ
誰だ	名詞,固有名詞,一般,*,*,*,誰だ,ダレダ,ダレダ
！	記号,一般,*,*,*,*,！,！,！
”	記号,括弧閉,*,*,*,*,”,”,”
世界的	名詞,固有名詞,一般,*,*,*,世界的,セカイテキ,セカイテキ
人気	名詞,一般,*,*,*,*,人気,ニンキ,ニンキ
！	記号,一般,*,*,*,*,！,！,！
２	名詞,数,*,*,*,*,２,ニ,ニ
億	名詞,数,*,*,*,*,億,オク,オク
ダウンロード	名詞,サ変接続,*,*,*,*,ダウンロード,ダウンロード,ダウンロード
「	記号,括弧開,*,*,*,*,「,「,「
Among	名詞,固有名詞,組織,*,*,*,*
　	記号,空白,*,*,*,*,　,　,　
Us	名詞,固有名詞,組織,*,*,*,*
」	記号,括弧閉,*,*,*,*,」,」,」
宇宙船	名詞,一般,*,*,*,*,宇宙船,ウチュウセン,ウチュウセン
を	助詞,格助詞,一般,*,*,*,を,ヲ,ヲ
舞台	名詞,一般,*,*,*,*,舞台,ブタイ,ブタイ
に	助詞,格助詞,一般,*,*,*,に,ニ,ニ
し	動詞,自立,*,*,サ変・スル,連用形,する,シ,シ
た	助動詞,*,*,*,特殊・タ,基本形,た,タ,タ
人狼ゲーム	名詞,固有名詞,一般,*,*,*,人狼ゲーム,ジンロウゲーム,ジンロウゲーム
！	記号,一般,*,*,*,*,！,！,！
EOS

END RequestId: 27c6e221-ecb4-4ae3-b9ca-9f0101f4cba0
```

さくさくいきますね。Dockerfile使えるの便利すぎる

# deploy

最後にdeployして完了です。 ```sam deploy --guided``` でdeployが実行できます。2回目からは ```sam deploy``` だけでいいです。

```bash
$ sam deploy --guided

Configuring SAM deploy
======================

	Looking for config file [samconfig.toml] :  Not found

	Setting default arguments for 'sam deploy'
	=========================================
	Stack Name [sam-app]: lambda-python-mecab-sam-example
	AWS Region [ap-northeast-1]: 
	#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
	Confirm changes before deploy [y/N]: y
	#SAM needs permission to be able to create roles to connect to the resources in your template
	Allow SAM CLI IAM role creation [Y/n]: 
	#Preserves the state of previously provisioned resources when an operation fails
	Disable rollback [y/N]: y
	HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
	Save arguments to configuration file [Y/n]: y
	SAM configuration file [samconfig.toml]: 
	SAM configuration environment [default]: 

	Looking for resources needed for deployment:
	 Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-b1r5p8i4foj1
	 A different default S3 bucket can be set in samconfig.toml
	 Image repositories: Not found.
	 #Managed repositories will be deleted when their functions are removed from the template and deployed
	 Create managed ECR repositories for all functions? [Y/n]: Y

......


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

```

あとは待つだけ！
1GB超えの巨大なアプリケーションになってしまうのですが、ECR上にイメージが作られることになり、サイズ的な問題はクリアできます。

![スクリーンショット 2021-12-12 22.12.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/875ffc46-4df6-feae-2ebf-9e8f9cfebc7c.png)


#　完成

![スクリーンショット 2021-12-12 22.47.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308709/8ca7702c-ed9c-5602-ebb3-9a0546bdbdf0.png)

そんなわけで無事にmecabの使用できる、巨大なLambdaをsam cliでbuildからdeployまでできました！

今回のコードはこちら
https://github.com/ikegam1/lambda-python-mecab-sam-example

# 参考にさせていただいたサイト(多謝！)

- https://qiita.com/rtaguchi/items/f2b39572299b5399df76
- https://dev.classmethod.jp/articles/aws-lambda-with-mecab/