継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

FastAPI と Falcon を Docker イメージにした時のサイズとレスポンスのスピードを計測した

タダです.

業務で API で使用する言語を置き換える検討をしていたりするんですが, FastAPI と Falcon を比較した際Docker イメージにした時の軽量さとベンチマークしてみてどれくらいレスポンスが速いかを計測した機会があったので,この記事に検証した内容をまとめておきます.

FastAPI と Falcon の特徴

2つの Web フレームワークがどのような特徴を持っているかをさらっておきます.FastAPI は Python 3.6 以上の速い Web フレームワークであると謳ってます.また,ASGI を使います.

FastAPI fastapi.tiangolo.com

特徴としては次のようなものがあります.

・ 速い。非常に高性能で、NodeJSやGoと同等のパフォーマンスを発揮します(StarletteとPydanticのおかげです)。利用可能なPythonフレームワークの中で最も高速なものの一つです。

・コード化が速い。機能開発のスピードを約200%~300%向上させます。

・バグが少ない。人間(開発者)によるエラーを約40%削減。

・ 直感的。素晴らしいエディタサポート。デバッグ時間を短縮。

・ 簡単。使いやすく、学習しやすいように設計されています。ドキュメントを読む時間を短縮。

・ 短い。コードの重複を最小限に抑えます。各パラメータ宣言から複数の機能を利用できるようになっています。

・ 堅牢。すぐに使えるコードを取得できます。自動でインタラクティブなドキュメントを提供します。

・ 標準ベース。APIのオープンスタンダードに基づいています(完全に互換性があります)。OpenAPI(以前はSwaggerとして知られていた)とJSON Schema。

Falcon も速く,軽量な Web フレームワークです. WSGI を使います.

Falcon falcon.readthedocs.io

特徴としては次のようなものがあります.

URIテンプレートRFCに基づくルート

・リソースへのURIのRESTに触発されたマッピング

・グローバル、リソース、およびメソッドのフック

・慣用的なHTTPエラー応答

Unicodeの完全サポート

・直感的なリクエストオブジェクトとレスポンスオブジェクト

・geventのような非同期ライブラリでうまく機能します

・安全なAPIを作成するための最小限の攻撃対象領域

・包括的なテストスイートで100%のコードカバレッジ

・他のPythonパッケージへの依存関係はありません

Python 2.7、3.5以降をサポート

・PyPyと互換性があります

Docker イメージ化した時のサイズ

Alphine Linux ベースの Docker イメージでビルドした場合のサイズですが,2つとも同じくらいのサイズ感でした.

# Falcon
$ docker images | grep falcon
falcon                                     latest              c74c32855823        About a minute ago   49.2MB
# FastAPI
$ docker images | grep fastapi
fastapi                      latest              88fd7ac8fa13        3 hours ago          50.1MB

どれくらい早くレスポンスがされるかのベンチマーク

wrk2 を使って以下の条件でベンチマークしてみました.

  • プログラムは Hello World を返すもの
  • 30 秒間で固定してベンチマークをする
  • 秒間100,500,1000,5000,10000 リクエストを投げてその結果を確認する

10のコネクションを維持して、秒間 100 リクエスト投げた時の計測

FastAPI,Falcon ともに大きな差異はない.

# FastAPI
wrk2 -t2 -c10 -d30s -R100 http://localhost:8000/
Running 30s test @ http://localhost:8000/
  2 threads and 10 connections
  Thread calibration: mean lat.: 7.524ms, rate sampling interval: 25ms
  Thread calibration: mean lat.: 7.752ms, rate sampling interval: 26ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.06ms    3.00ms  22.78ms   79.35%
    Req/Sec    53.01     71.93   208.00     80.30%
  3002 requests in 30.01s, 416.29KB read
Requests/sec:    100.02
Transfer/sec:     13.87KB

# Falcon
wrk2 -t2 -c10 -d30s -R100 http://localhost:8080/
Running 30s test @ http://localhost:8080/
  2 threads and 10 connections
  Thread calibration: mean lat.: 32.647ms, rate sampling interval: 209ms
  Thread calibration: mean lat.: 20.032ms, rate sampling interval: 185ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     6.24ms    4.06ms  29.98ms   82.81%
    Req/Sec    49.44      5.69    62.00     84.00%
  3002 requests in 30.01s, 483.72KB read
Requests/sec:    100.03
Transfer/sec:     16.12KB

50のコネクションを維持して、秒間 500 リクエスト投げた時の計測

Latency で FastAPI と Falcon の間に差がでてきた.

# FastAPI
wrk2 -t2 -c50 -d30s -R500 http://localhost:8000/
Running 30s test @ http://localhost:8000/
  2 threads and 50 connections
  Thread calibration: mean lat.: 7.982ms, rate sampling interval: 28ms
  Thread calibration: mean lat.: 7.915ms, rate sampling interval: 27ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     5.11ms    2.25ms  19.73ms   81.05%
    Req/Sec   254.29     63.28   423.00     61.43%
  14991 requests in 30.00s, 2.03MB read
Requests/sec:    499.66
Transfer/sec:     69.29KB

# Falcon
wrk2 -t2 -c50 -d30s -R500 http://localhost:8080/
Running 30s test @ http://localhost:8080/
  2 threads and 50 connections
  Thread calibration: mean lat.: 46.716ms, rate sampling interval: 204ms
  Thread calibration: mean lat.: 45.854ms, rate sampling interval: 200ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    14.29ms   20.81ms 135.04ms   88.48%
    Req/Sec   250.44      6.95   280.00     80.10%
  14991 requests in 30.00s, 2.36MB read
Requests/sec:    499.63
Transfer/sec:     80.51KB

100のコネクションを維持して、秒間 1000 リクエスト投げた時の計測

1000リクエストになったら Requests を捌く量も Latency と同じくひらいてきた.

# FastAPI
 wrk2 -t2 -c100 -d30s -R1000 http://localhost:8000/
Running 30s test @ http://localhost:8000/
  2 threads and 100 connections
  Thread calibration: mean lat.: 241.727ms, rate sampling interval: 819ms
  Thread calibration: mean lat.: 240.797ms, rate sampling interval: 815ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   241.60ms  260.77ms 777.22ms   75.42%
    Req/Sec   481.31     47.68   583.00     79.17%
  29201 requests in 30.01s, 3.96MB read
Requests/sec:    973.16
Transfer/sec:    135.12KB

# Falcon
wrk2 -t2 -c100 -d30s -R1000 http://localhost:8080/
Running 30s test @ http://localhost:8080/
  2 threads and 100 connections
  Thread calibration: mean lat.: 124.110ms, rate sampling interval: 383ms
  Thread calibration: mean lat.: 124.684ms, rate sampling interval: 393ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   527.57ms  528.20ms   1.86s    73.48%
    Req/Sec   161.29    255.12     0.99k    71.57%
  16458 requests in 30.27s, 2.59MB read
  Socket errors: connect 0, read 0, write 0, timeout 600
Requests/sec:    543.71
Transfer/sec:     87.61KB

500のコネクションを維持して、秒間 5000 リクエスト投げた時の計測

# FastAPI
wrk2 -t2 -c500 -d30s -R5000 http://localhost:8000/
Running 30s test @ http://localhost:8000/
  2 threads and 500 connections
  Thread calibration: mean lat.: 3006.903ms, rate sampling interval: 11796ms
  Thread calibration: mean lat.: 3028.214ms, rate sampling interval: 11796ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    12.31s     3.42s   18.87s    58.52%
    Req/Sec   456.00     68.00   524.00    100.00%
  26269 requests in 30.01s, 3.56MB read
  Socket errors: connect 251, read 64, write 0, timeout 3514
Requests/sec:    875.44
Transfer/sec:    121.59KB

# Falcon
wrk2 -t2 -c500 -d30s -R5000 http://localhost:8080/
Running 30s test @ http://localhost:8080/
  2 threads and 500 connections
  Thread calibration: mean lat.: 2613.849ms, rate sampling interval: 10739ms
  Thread calibration: mean lat.: 2724.868ms, rate sampling interval: 10936ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     8.13s     2.36s   15.61s    65.29%
    Req/Sec   275.50     29.50   305.00    100.00%
  16381 requests in 31.28s, 2.58MB read
  Socket errors: connect 251, read 153, write 0, timeout 5439
Requests/sec:    523.76
Transfer/sec:     84.39KB

1000のコネクションを維持して、秒間 10000 リクエスト投げた時の計測

# FastAPI
wrk2 -t2 -c1000 -d30s -R10000 http://localhost:8000/
Running 30s test @ http://localhost:8000/
  2 threads and 1000 connections
  Thread calibration: mean lat.: 3875.593ms, rate sampling interval: 13352ms
  Thread calibration: mean lat.: 3795.580ms, rate sampling interval: 13303ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    12.20s     3.03s   18.14s    57.44%
    Req/Sec   514.50     39.50   554.00    100.00%
  27956 requests in 30.00s, 3.79MB read
  Socket errors: connect 751, read 0, write 0, timeout 9763
Requests/sec:    931.79
Transfer/sec:    129.26KB

# Falcon
wrk2 -t2 -c1000 -d30s -R10000 http://localhost:8080/
Running 30s test @ http://localhost:8080/
  2 threads and 1000 connections
  Thread calibration: mean lat.: 3676.434ms, rate sampling interval: 14540ms
  Thread calibration: mean lat.: 3547.812ms, rate sampling interval: 14319ms
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     7.69s     2.42s   17.81s    67.25%
    Req/Sec   155.50      5.50   161.00    100.00%
  16445 requests in 30.53s, 2.59MB read
  Socket errors: connect 751, read 45, write 0, timeout 12373
Requests/sec:    538.65

Docker イメージにした時のサイズとベンチマークを踏まえての所感

検証の結果を踏まえて次の感想を持ちました.

  • Docker イメージにした時の軽量さは2つに大差はない
  • リクエスト数が増えていった時も FastAPI のほうが処理スピードが速くリクエストを捌く量も多い

また, FastAPI のドキュメントをみていると,FastAPI を作る時に既存のフレームワークを参考に開発したと解説されており, Falcon も参考にして開発されているようなので改良されたのが FastAPI って位置付けなのだと感じました.

fastapi.tiangolo.com

まとめ

FastAPI と Falcon の比較をするための検証で行ったことを整理してみました.ベンチマークしてみて FastAPI が速くてパフォーマンスも優れているという所以を感じることができました.FastAPI を業務で使う機会が出てきたら習熟していけるようになっていきたいです.

『AWS Data Wrangler』の v1.0 がリリース! どんなアップデートがあったかを整理する

タダです.

以前紹介した「AWS Data Wrangler」が今年の4月に v1.0 でリリースされました.リリースから2ヶ月,過去記事に書いたものからどんなアップデートがあるかをまとめます.

公式ブログ

aws.amazon.com

過去記事

sadayoshi-tada.hatenablog.com

AWS Data Wrangler 概要

AWS Data Wrangler」は AWS の Redshift,Glue,Athena,EMR といったデータ解析系のサービスと連携しやすく,ETL 処理開発に集中できるオープンソースPython モジュールです.

An open-source Python package that extends the power of Pandas library to AWS connecting DataFrames and AWS data related services (Amazon Redshift, AWS Glue, Amazon Athena, Amazon EMR, etc).

Built on top of other open-source projects like Pandas, Apache Arrow, Boto3, s3fs, SQLAlchemy, Psycopg2 and PyMySQL, it offers abstracted functions to execute usual ETL tasks like load/unload data from Data Lakes, Data Warehouses and Databases.

アップデートサマリー

  • Athena のパーティション射影をサポート
  • CSV,JSON,FWF 形式をサポート
  • S3 に格納した Parquet の読み込みと Glue カタログに Parquet メタデータの格納における Parquet スキーマを混合する
  • S3 に Parquet を書き込みと Glue カタログに Parquet メタデータの格納をサポート
  • Parquetでの uint8,uint16,uint32,uint64 のサポート
  • Glue カタログでの upsert_table_parameters,get_table_parameters,upsert_table_parameters 関数を追加
  • Athena の入れ子になった配列と構造体をサポート
  • 行数でチャンク化されたRead Parquet/Athena/Redshiftに対応
  • Docker で EMR をサポート
  • EMR でカスタム分類をサポート
  • Redshift の UNLOAD 処理で kms_key_id,max_file_size,region を引数としてサポート
  • Glue カタログ の上書き操作でテーブルのバージョニングをサポート
  • Athena の読み込み SQL 時に keep_files,ctas_temp_table_name を引数としてサポート
  • S3 のオブジェクトコピー時に replace_filenamesを引数としてサポート
  • 別のディレクトリにコピーする S3 オブジェクトをリストしたり,ターゲットのデータセットにマージする処理をサポート
  • モジュールの型アノテーションが登録された
  • S3 と EMR を moto でサポート
    • moto でのテストの改善は適宜行われている
  • Pandas DataFrame から Athena のカラムとパーティションタイプを抽出処理をサポート
  • 受信した S3 プレフィックスや S3 オブジェクトのパスのリストから Apache Parquet ファイルを読み込んだり,s3 上の Parquet ファイルをステージとして使用して Redshift のクエリ結果から Pandas DataFrame をロード,Athena 上で任意の SQL クエリの実行結果をPandas DataFrame として返す処理をサポート

まとめ

AWS Data Wrangler」が ver 1.0 リリースされたので 1.0 以降のアップデートサマリーを列挙しました.リポジトリを見ると issue も上がっていて近々でも対応されてたので今後もアップデートが楽しみです.自分自身としては「AWS Data Wrangler」を活用できるようまずはとっつきやすいチュートリアルから触れてナレッジを貯め込んでいきたいです.

pip install した時の SSL エラーが発生した場合の対応を整理した

タダです.

Mac のローカルで pip installしようとした時に SSL 関連のエラーの事象にあたりました.突然,pip installの実行ができなくなったため,この記事で対応方法を整理します.

事象の概要

boto3 を導入しようとしてみたところ,pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.と表示され,SSL のモジュールで利用できるものがないと表示されてコケました.

» pip install boto3                       
pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
~中略~
Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError("Can't connect to HTTPS URL because the SSL module is not available.",)) - skipping

実行環境は次の通りです.

  • macOS Majave
  • Python 3.6.8
    • pyenv で導入したもの

結論

結論としては, Python と pip のバージョンを上げて対応しました.

エラー対応の時系列

OpenSSL 周りを再インストール

まず,SSL 関連のエラーがでたのでbrew reinstall opensslを実行して OpenSSL を入れ直しました.

» brew unlink openssl        
Unlinking /usr/local/Cellar/openssl@1.1/1.1.1f... 0 symlinks removed
» brew reinstall openssl
==> Reinstalling openssl@1.1 
==> Downloading https://homebrew.bintray.com/bottles/openssl@1.1-1.1.1f.mojave.bottle.tar.gz
Already downloaded: /XXX/XXX/Library/Caches/Homebrew/downloads/f46ab457fe1e0a3e8679dd2725494506e01504b448ace015cf5d5eee0c7307f0--openssl@1.1-1.1.1f.mojave.bottle.tar.gz
==> Pouring openssl@1.1-1.1.1f.mojave.bottle.tar.gz
==> Caveats
A CA file has been bootstrapped using certificates from the system
keychain. To add additional certificates, place .pem files in
  /usr/local/etc/openssl@1.1/certs

and run
  /usr/local/opt/openssl@1.1/bin/c_rehash

openssl@1.1 is keg-only, which means it was not symlinked into /usr/local,
because openssl/libressl is provided by macOS so don't link an incompatible version.

If you need to have openssl@1.1 first in your PATH run:
  echo 'export PATH="/usr/local/opt/openssl@1.1/bin:$PATH"' >> ~/.zshrc

For compilers to find openssl@1.1 you may need to set:
  export LDFLAGS="-L/usr/local/opt/openssl@1.1/lib"
  export CPPFLAGS="-I/usr/local/opt/openssl@1.1/include"

For pkg-config to find openssl@1.1 you may need to set:
  export PKG_CONFIG_PATH="/usr/local/opt/openssl@1.1/lib/pkgconfig"

==> Summary
🍺  /usr/local/Cellar/openssl@1.1/1.1.1f: 8,057 files, 18MB

再度,pip installしてみるも事象改善しないため次のアプローチを検討しました.

pip install boto3                                   
pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
~中略~
pip is configured with locations that require TLS/SSL, however the ssl module in Python is not available.
Could not fetch URL https://pypi.org/simple/pip/: There was a problem confirming the ssl certificate: HTTPSConnectionPool(host='pypi.org', port=443): Max retries exceeded with url: /simple/pip/ (Caused by SSLError("Can't connect to HTTPS URL because the SSL module is not available.",)) - skipping

Python と pip のバージョンアップ

下記の issue を参考に Python が 3.6.8 だったところを 3.8 系に上げて,pip もバージョンアップしてみました.

github.com

» pyenv install --list
Available versions:
~中略~
3.8.0
~中略~
» pyenv install 3.8.0
python-build: use openssl@1.1 from homebrew
python-build: use readline from homebrew
Downloading Python-3.8.0.tar.xz...
-> https://www.python.org/ftp/python/3.8.0/Python-3.8.0.tar.xz
Installing Python-3.8.0...
python-build: use readline from homebrew
python-build: use zlib from xcode sdk
Installed Python-3.8.0 to /XXX/XXX/.pyenv/versions/3.8.0
» pyenv global 3.8.0
» curl -kL https://bootstrap.pypa.io/get-pip.py | python
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 1764k  100 1764k    0     0  1956k      0 --:--:-- --:--:-- --:--:-- 1954k
Collecting pip
  Downloading pip-20.0.2-py2.py3-none-any.whl (1.4 MB)
     |████████████████████████████████| 1.4 MB 3.0 MB/s
Collecting wheel
  Downloading wheel-0.34.2-py2.py3-none-any.whl (26 kB)
Installing collected packages: pip, wheel
  Attempting uninstall: pip
    Found existing installation: pip 19.2.3
    Uninstalling pip-19.2.3:
      Successfully uninstalled pip-19.2.3
Successfully installed pip-20.0.2 wheel-0.34.2

再度,pip install してみたらコケずに boto3 を導入できました.

» pip install boto3
Collecting boto3
  Downloading boto3-1.12.36-py2.py3-none-any.whl (128 kB)
     |████████████████████████████████| 128 kB 1.3 MB/s
Collecting s3transfer<0.4.0,>=0.3.0
  Using cached s3transfer-0.3.3-py2.py3-none-any.whl (69 kB)
Collecting jmespath<1.0.0,>=0.7.1
  Using cached jmespath-0.9.5-py2.py3-none-any.whl (24 kB)
Collecting botocore<1.16.0,>=1.15.36
  Downloading botocore-1.15.36-py2.py3-none-any.whl (6.1 MB)
     |████████████████████████████████| 6.1 MB 3.3 MB/s
Collecting urllib3<1.26,>=1.20; python_version != "3.4"
  Using cached urllib3-1.25.8-py2.py3-none-any.whl (125 kB)
Collecting docutils<0.16,>=0.10
  Using cached docutils-0.15.2-py3-none-any.whl (547 kB)
Collecting python-dateutil<3.0.0,>=2.1
  Using cached python_dateutil-2.8.1-py2.py3-none-any.whl (227 kB)
Collecting six>=1.5
  Using cached six-1.14.0-py2.py3-none-any.whl (10 kB)
Installing collected packages: urllib3, docutils, jmespath, six, python-dateutil, botocore, s3transfer, boto3
Successfully installed boto3-1.12.36 botocore-1.15.36 docutils-0.15.2 jmespath-0.9.5 python-dateutil-2.8.1 s3transfer-0.3.3 six-1.14.0 urllib3-1.25.8

まとめ

これまで動作していた pip installSSL エラーが発生したのでその対処をまとめました.突然のpip installが動作しなくなる事象は戸惑ったので同じ事象に遭遇したら,Python や pip のバージョンを見直してみるのは1つの手段してみると良いかもしれません.

Python でクラウドの構成図を作ろう!『Diagrams』でね

タダです.

皆さん,普段システムの構成図ってどうやって管理していますか?ExcelPowerPoint,専用のツールを使われていたり色々な方法で管理されているのですが,以前 PlantUML の形式で作図できる「AWS-PlantUML」を紹介させてもらいました.

sadayoshi-tada.hatenablog.com

今回も同じコードでの作図ですが Python で記述できるツールの「Diagrams」を紹介します.

github.com

Diagrams の概要

Diagrams」は Pythonクラウドサービスのアーキテクチャを作図するツールです.「Diagrams」では AWS のほか,Azure,GCP,Alibaba,Oracle Cloud のアイコンに対応しています.

diagrams.mingrammer.com

diagrams.mingrammer.com

diagrams.mingrammer.com

diagrams.mingrammer.com

diagrams.mingrammer.com

このツールのメリットは,コードで構成図を管理するため変更の履歴追跡が可能になることです.そして,何より手で矢印やリソースのグルーピングを細かに弄らなくてもいいところです!僕は手で作図をしていて図の重なりで細かな調整をするのが面倒だと思ってしまいますが,その手間が何行かのコードで解決できます.

Diagrams の導入

Diagrams」の導入は簡単です.pipであれば次のコマンドで導入できます.また,Graphviz も必要なので適宜インストールしてください.

pip install diagrams

diagrams.mingrammer.com

Diagrams での描画

Diagrams」でどれくらいのコード量で作図ができるかを実際に2つの例でみていきます.

Web3層構造

1つ目のパターンとして Web の3層構造を AWS で構成するコード例です.

コード例

from diagrams import Diagram, Cluster
from diagrams.aws.compute import AutoScaling,EC2
from diagrams.aws.network import ELB
from diagrams.aws.database import RDS

with Diagram("Scale-Out Computing", show=False):
    lb = ELB("LB")
    db = RDS("DB")
    with Cluster("Scaling Servers"):
        srv_group = [EC2("web01"),
                    EC2("web02"),
                    EC2("web03")]

    lb >> srv_group >> db

図を出力するためには次のようにコードを実行します.コードの実行が成功すると,コードと同じ階層に構成図がデフォルトですとpng形式で生成されます.なお,形式は,svg,jpg,pdfに対応しています.

python xxx.py

ELB で受けたトラフィックを3台までスケールする EC2 とデータを格納する RDS を表現してみました.10数行でこの絵を書けるのだから楽ですね.

f:id:sadayoshi_tada:20200331001349p:plain

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

2つ目の例としてサーバーレスアーキテクチャを作図してみます.題材として「実践 AWS CDK - TypeScript でインフラもアプリも!」で扱った感情分析システムのワークフローのコード例です.

sadayoshi-tada.hatenablog.com

コード例

from diagrams import Diagram, Cluster
from diagrams.onprem.client import User
from diagrams.aws.storage import S3
from diagrams.aws.integration import SF,SNS
from diagrams.aws.compute import Lambda
from diagrams.aws.ml import Comprehend
from diagrams.aws.compute import Compute

with Diagram("Osenchi Architecture", show=False):

    with Cluster("Osenchi WorkFlow"):
        with Cluster("State Machine"):
            wf =  SF("Step Functions")

        with Cluster("Send E-mail"):
            wf >> SNS("Email Topic") >> User("Client")

        with Cluster("Delete Object"):
            wf >> Lambda("Delete Objects Functions")>>S3("Input")

        with Cluster("Call DetectStatement API"):
            wf >> Lambda("Call Comprehend Functions") >> Comprehend("DetectStatement API") >> S3("Ouput")

    User("Client") >> S3("Input") >> wf

出力された図が次のものです. f:id:sadayoshi_tada:20200331001836p:plain

Clustesを使うことで関連処理のグループ化が可能です.ワークフローで呼び出す各処理を1つずつグループ化していくとワークフローの作図もできます.

diagrams.mingrammer.com

まとめ

Diagrams」いかがでしたでしょうか? Pythonに慣れていて手での作図にモヤモヤしている人に有用なツールと思います.主要なクラウドサービスだけでなく,オンプレミスのリソース表現もあります.AWS 観点で行くとアイコンも最新になっているし,構成図もコードで管理できれば履歴の管理もし易いと感じました.興味を持った人はぜひ「Diagrams」使ってみて欲しいです!

AWS 謹製のデータ分析モジュール『AWS Data Wrangler』チュートリアルの紹介

タダです.

AWS 謹製の Python データ分析モジュールの「AWS Data Wrangler」がリリースされました.今回は普段 Python を使ってデータ分析の勉強をしているため,「AWS Data Wrangler」を公式ブログチュートリアルを参考に使ってみた所感を書いていきます.

aws.amazon.com

github.com

利用方法はドキュメントで確認していきましょう.

aws-data-wrangler.readthedocs.io

AWS Data Wrangler のメリット

AWS Data Wrangler」のメリットは下記の通りです.

  • AWS Data Wrangler」を利用することで, Athena や S3 の CSV データから Pandas を数行のコードで実現できる
  • PySpark から Redshift に連携できるため利用者は ETL(Extract/Transform/Load) に集中することが可能
    • AWS Data Wrangler」登場前は, ETL 処理にいくまでにサービスへの接続設定,各種コーディング必要だったが ETL 処理に集中していける

最大のメリットは, 利用者は ETL 処理に集中してコーディングを行える ことだと読み取れます.それでは実際に環境を作ってどれくらい簡単かをコーディングして確認していきます.

AWS Data Wrangler を使って ETL を行う

今回の環境は以下の画像の環境で,ブログで紹介された構成です.CSV を S3 に配置し,SageMaker から「AWS Data Wrangler」経由で Athena,S3 の CSVデータにアクセスした後,ETL 処理後の CSV データを S3 に出力するチュートリアルとなっています.

f:id:sadayoshi_tada:20191006114150p:plain 引用元: https://aws.amazon.com/jp/blogs/news/how-to-use-aws-data-wrangler/ シナリオの構成図より

1. S3 への CSV データをアップロード

まず,S3 へ CSV データをアップロードします.データは下記のGreen Taxi Trip Records(CSV) の1月データを使いました.

www1.nyc.gov

ローカルにダウンロードしたデータを S3 にアップロードします.

f:id:sadayoshi_tada:20191006115108p:plain

2. Athena でデータベースおよびテーブルを作成する

Athena でデータベースとテーブルを作成します.

# データベース作成
CREATE DATABASE greentripdata;

#テーブル作成
CREATE EXTERNAL TABLE green_tripdata(
  VendorID string, 
  lpep_pickup_datetime string,
  lpep_dropoff_datetime string,
  store_and_fwd_flag string,
  RatecodeID string,
  PULocationID string,
  DOLocationID string,
  passenger_count int,
  trip_distance double,
  fare_amount double,
  extra double,
  mta_max double,
  tip_amount double,
  tolls_amount double,
  ehail_fee string,
  improvement_surcharge double,
  total_amount double,
  payment_type string,
  trip_type string,
  congestion_surcharge double
  )
ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' 
LOCATION 's3://S3バケット名/CSV データ格納ディレクトリ/'; 

そして,後続でも使うためテーブルのデータ数を確認しておきます.630919 件のデータがあることを確認しました.

select count(*) from green_tripdata

f:id:sadayoshi_tada:20191006115522p:plain

3. SageMaker から AWS Data Wrangler 経由で Athena,S3 の CSVデータにアクセスする

SageMaker ノートブックインスタンス起動時に設定する IAM ロールにAmazonS3FullAccessAmazonAthenaFullAccessを付与しておきます.起動後に,「AWS Data Wrangler」モジュールをインストールします.

!pip install awswrangler

Collecting awswrangler
  Downloading https://files.pythonhosted.org/packages/ce/ab/677e5f5aa33584a6bacc15b7eaabea31f5ad7eb4e850a3105f5b73ebc99e/awswrangler-0.0.8.tar.gz
Collecting pyarrow>=0.14.0 (from awswrangler)
  Downloading https://files.pythonhosted.org/packages/c9/ed/e9fda0abcf087e0288ce78f744dffbfc2ac8dfba6f242a8ab025d76bee27/pyarrow-0.15.0-cp36-cp36m-manylinux1_x86_64.whl (60.1MB)
    100% |████████████████████████████████| 60.1MB 815kB/s eta 0:00:01
Collecting pandas>=0.25.1 (from awswrangler)
  Downloading https://files.pythonhosted.org/packages/73/9b/52e228545d14f14bb2a1622e225f38463c8726645165e1cb7dde95bfe6d4/pandas-0.25.1-cp36-cp36m-manylinux1_x86_64.whl (10.5MB)
    100% |████████████████████████████████| 10.5MB 7.8MB/s eta 0:00:01
Requirement already satisfied: botocore>=1.12.239 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from awswrangler) (1.12.239)
Requirement already satisfied: boto3>=1.9.239 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from awswrangler) (1.9.239)
Collecting s3fs>=0.3.4 (from awswrangler)
  Downloading https://files.pythonhosted.org/packages/01/5c/5899c874ac3a00c4b99be983eae22c8a3800c3d5fc3d22f6f1e5058aacf2/s3fs-0.3.4-py3-none-any.whl
Collecting tenacity>=5.1.1 (from awswrangler)
  Downloading https://files.pythonhosted.org/packages/1e/a1/be8c8610f4620c56790965ba2b564dd76d13cbcd7c2ff8f6053ce63027fb/tenacity-5.1.1-py2.py3-none-any.whl
Collecting pg8000>=1.13.2 (from awswrangler)
  Downloading https://files.pythonhosted.org/packages/16/32/ae895597e43bc968e0e3e63860e9932b851115457face0d06d7f451b71fc/pg8000-1.13.2-py3-none-any.whl
Requirement already satisfied: numpy>=1.14 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from pyarrow>=0.14.0->awswrangler) (1.14.3)
Requirement already satisfied: six>=1.0.0 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from pyarrow>=0.14.0->awswrangler) (1.11.0)
Requirement already satisfied: pytz>=2017.2 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from pandas>=0.25.1->awswrangler) (2018.4)
Requirement already satisfied: python-dateutil>=2.6.1 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from pandas>=0.25.1->awswrangler) (2.7.3)
Requirement already satisfied: urllib3<1.26,>=1.20; python_version >= "3.4" in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from botocore>=1.12.239->awswrangler) (1.23)
Requirement already satisfied: docutils<0.16,>=0.10 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from botocore>=1.12.239->awswrangler) (0.14)
Requirement already satisfied: jmespath<1.0.0,>=0.7.1 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from botocore>=1.12.239->awswrangler) (0.9.4)
Requirement already satisfied: s3transfer<0.3.0,>=0.2.0 in /home/ec2-user/anaconda3/envs/python3/lib/python3.6/site-packages (from boto3>=1.9.239->awswrangler) (0.2.1)
Collecting fsspec>=0.2.2 (from s3fs>=0.3.4->awswrangler)
  Downloading https://files.pythonhosted.org/packages/95/2c/31fce3889ce89ec13e47201c71a0cb6d2ff6e5c7b5fed066fe0ac5c5e22b/fsspec-0.5.1-py3-none-any.whl (56kB)
    100% |████████████████████████████████| 61kB 30.3MB/s ta 0:00:01
Collecting scramp==1.1.0 (from pg8000>=1.13.2->awswrangler)
  Downloading https://files.pythonhosted.org/packages/bb/ef/6bdba6756ba7ccb81187833504ebba0511af750a2d9beaa04e4b56c3974f/scramp-1.1.0-py3-none-any.whl
Building wheels for collected packages: awswrangler
  Running setup.py bdist_wheel for awswrangler ... done
  Stored in directory: /home/ec2-user/.cache/pip/wheels/d9/81/7d/f4e8f56f0d44f17a571fcbe5b90a4ceb6001d6debdf8951be9
Successfully built awswrangler
Installing collected packages: pyarrow, pandas, fsspec, s3fs, tenacity, scramp, pg8000, awswrangler
  Found existing installation: pandas 0.24.2
    Uninstalling pandas-0.24.2:
      Successfully uninstalled pandas-0.24.2
  Found existing installation: s3fs 0.1.5
    Uninstalling s3fs-0.1.5:
      Successfully uninstalled s3fs-0.1.5
Successfully installed awswrangler-0.0.8 fsspec-0.5.1 pandas-0.25.1 pg8000-1.13.2 pyarrow-0.15.0 s3fs-0.3.4 scramp-1.1.0 tenacity-5.1.1
You are using pip version 10.0.1, however version 19.2.3 is available.
You should consider upgrading via the 'pip install --upgrade pip' command.

AWS Data Wrangler」経由で Athena,S3 の CSVデータにアクセスしてデータの件数を確認してみます.2. Athena でデータベースおよびテーブルを作成するで確認したのと同じ630919件であることを確認できました.

import pandas as pd
import awswrangler

session = awswrangler.Session()
df = session.pandas.read_sql_athena(
    sql="select * from green_tripdata",
    database="greentripdata"
)

print(df)

【output】
       vendorid  lpep_pickup_datetime  lpep_dropoff_datetime  \
0       VendorID  lpep_pickup_datetime  lpep_dropoff_datetime   
1              2   2018-12-21 15:17:29    2018-12-21 15:18:57   
2              2   2019-01-01 00:10:16    2019-01-01 00:16:32   
3              2   2019-01-01 00:27:11    2019-01-01 00:31:38   
4              2   2019-01-01 00:46:20    2019-01-01 01:04:54   
...          ...                   ...                    ...   
630914         2   2019-01-31 23:08:27    2019-01-31 23:22:59   
630915         2   2019-01-31 23:21:26    2019-01-31 23:23:05   
630916         2   2019-01-31 23:30:05    2019-01-31 23:36:14   
630917         2   2019-01-31 23:59:58    2019-02-01 00:04:18   
630918         2   2019-01-31 23:18:22    2019-01-31 23:26:06   

        store_and_fwd_flag  ratecodeid  pulocationid  dolocationid  \
0       store_and_fwd_flag  RatecodeID  PULocationID  DOLocationID   
1                        N           1           264           264   
2                        N           1            97            49   
3                        N           1            49           189   
4                        N           1           189            17   
...                    ...         ...           ...           ...   
630914                   N           1           255           226   
630915                   N           1            75           151   
630916                   N           1            75           238   
630917                   N           1            74            74   
630918                   N           1            75           262   

        passenger_count  trip_distance  fare_amount  extra  mta_max  \
0                   NaN            NaN          NaN    NaN      NaN   
1                     5           0.00          3.0    0.5      0.5   
2                     2           0.86          6.0    0.5      0.5   
3                     2           0.66          4.5    0.5      0.5   
4                     2           2.68         13.5    0.5      0.5   
...                 ...            ...          ...    ...      ...   
630914                1           3.33         13.0    0.5      0.5   
630915                1           0.72          4.0    0.5      0.5   
630916                1           1.75          7.0    0.5      0.5   
630917                1           0.57          5.0    0.5      0.5   
630918                1           2.11          8.5    0.5      0.5   

        tip_amount  tolls_amount  ehail_fee  improvement_surcharge  \
0              NaN           NaN  ehail_fee                    NaN   
1             0.00           0.0        NaN                    0.3   
2             0.00           0.0        NaN                    0.3   
3             0.00           0.0        NaN                    0.3   
4             2.96           0.0        NaN                    0.3   
...            ...           ...        ...                    ...   
630914        2.14           0.0        NaN                    0.3   
630915        1.06           0.0        NaN                    0.3   
630916        0.00           0.0        NaN                    0.3   
630917        1.00           0.0        NaN                    0.3   
630918        1.96           0.0        NaN                    0.3   

        total_amount  payment_type  trip_type  congestion_surcharge  
0                NaN  payment_type  trip_type                   NaN  
1               4.30             2          1                   NaN  
2               7.30             2          1                   NaN  
3               5.80             1          1                   NaN  
4              19.71             1          1                   NaN  
...              ...           ...        ...                   ...  
630914         18.39             1          1                   0.0  
630915          6.36             1          1                   0.0  
630916          8.30             1          1                   0.0  
630917          7.30             1          1                   0.0  
630918         11.76             1          1                   0.0  

[630919 rows x 20 columns]

4. ETL 処理の実行

それでは ETL 処理の実行をしていきます.まず,trip_distanceカラムのデータが0の部分を分析対象外として行の削除処理を行います.削除する行は10721行であることを確認できます.

# trip_distanceが0の値を抽出
rows_drop = df.index[df["trip_distance"] == 0.00]
# trip_distanceが0の値の件数を確認
print(df.loc[rows_drop].count())

【output】
vendorid                 10721
lpep_pickup_datetime     10721
lpep_dropoff_datetime    10721
store_and_fwd_flag       10721
ratecodeid               10721
pulocationid             10721
dolocationid             10721
passenger_count          10721
trip_distance            10721
fare_amount              10721
extra                    10721
mta_max                  10721
tip_amount               10721
tolls_amount             10721
ehail_fee                    0
improvement_surcharge    10721
total_amount             10721
payment_type             10721
trip_type                10721
congestion_surcharge      1228
dtype: int64

trip_distanceカラムの0のデータ部分を削除していきます.総データ数が630919から10721行を削除するので,620198件のデータ削除処理しました.

# trip_distanceが0の値を削除
df_drop = df.drop(rows_drop)
print(df_drop)

【output】
        vendorid  lpep_pickup_datetime  lpep_dropoff_datetime  \
0       VendorID  lpep_pickup_datetime  lpep_dropoff_datetime   
2              2   2019-01-01 00:10:16    2019-01-01 00:16:32   
3              2   2019-01-01 00:27:11    2019-01-01 00:31:38   
4              2   2019-01-01 00:46:20    2019-01-01 01:04:54   
5              2   2019-01-01 00:19:06    2019-01-01 00:39:43   
...          ...                   ...                    ...   
630914         2   2019-01-31 23:08:27    2019-01-31 23:22:59   
630915         2   2019-01-31 23:21:26    2019-01-31 23:23:05   
630916         2   2019-01-31 23:30:05    2019-01-31 23:36:14   
630917         2   2019-01-31 23:59:58    2019-02-01 00:04:18   
630918         2   2019-01-31 23:18:22    2019-01-31 23:26:06   

        store_and_fwd_flag  ratecodeid  pulocationid  dolocationid  \
0       store_and_fwd_flag  RatecodeID  PULocationID  DOLocationID   
2                        N           1            97            49   
3                        N           1            49           189   
4                        N           1           189            17   
5                        N           1            82           258   
...                    ...         ...           ...           ...   
630914                   N           1           255           226   
630915                   N           1            75           151   
630916                   N           1            75           238   
630917                   N           1            74            74   
630918                   N           1            75           262   

        passenger_count  trip_distance  fare_amount  extra  mta_max  \
0                   NaN            NaN          NaN    NaN      NaN   
2                     2           0.86          6.0    0.5      0.5   
3                     2           0.66          4.5    0.5      0.5   
4                     2           2.68         13.5    0.5      0.5   
5                     1           4.53         18.0    0.5      0.5   
...                 ...            ...          ...    ...      ...   
630914                1           3.33         13.0    0.5      0.5   
630915                1           0.72          4.0    0.5      0.5   
630916                1           1.75          7.0    0.5      0.5   
630917                1           0.57          5.0    0.5      0.5   
630918                1           2.11          8.5    0.5      0.5   

        tip_amount  tolls_amount  ehail_fee  improvement_surcharge  \
0              NaN           NaN  ehail_fee                    NaN   
2             0.00           0.0        NaN                    0.3   
3             0.00           0.0        NaN                    0.3   
4             2.96           0.0        NaN                    0.3   
5             0.00           0.0        NaN                    0.3   
...            ...           ...        ...                    ...   
630914        2.14           0.0        NaN                    0.3   
630915        1.06           0.0        NaN                    0.3   
630916        0.00           0.0        NaN                    0.3   
630917        1.00           0.0        NaN                    0.3   
630918        1.96           0.0        NaN                    0.3   

        total_amount  payment_type  trip_type  congestion_surcharge  
0                NaN  payment_type  trip_type                   NaN  
2               7.30             2          1                   NaN  
3               5.80             1          1                   NaN  
4              19.71             1          1                   NaN  
5              19.30             2          1                   NaN  
...              ...           ...        ...                   ...  
630914         18.39             1          1                   0.0  
630915          6.36             1          1                   0.0  
630916          8.30             1          1                   0.0  
630917          7.30             1          1                   0.0  
630918         11.76             1          1                   0.0  

[620198 rows x 20 columns]

# trip_distanceが0の値の件数を確認
df_lens = df_drop.count()
print(df_lens)

【output】
vendorid                 620198
lpep_pickup_datetime     620198
lpep_dropoff_datetime    620198
store_and_fwd_flag       620198
ratecodeid               620198
pulocationid             620198
dolocationid             620198
passenger_count          620197
trip_distance            620197
fare_amount              620197
extra                    620197
mta_max                  620197
tip_amount               620197
tolls_amount             620197
ehail_fee                     1
improvement_surcharge    620197
total_amount             620197
payment_type             620198
trip_type                620198
congestion_surcharge      83310
dtype: int64

不要データを削除したものに対してデータ内のカラムの置き換えを行います.payment_typeという項目に対してデータの置き換えを行います.データの置き換えしたことで一部のみの表示ですがCredit cardに置き換わっていることを確認しました.

df_replace = df_drop.replace(
    {'payment_type': 
        {
            '1': 'Credit card', 
            '2': 'Cash', 
            '3': 'No charge', 
            '4': 'Dispute', 
            '5': 'Unknown', 
            '6': 'Voided trip'
        }
    }
)

print(df_replace['payment_type'])

【output】
0         payment_type
2                 Cash
3          Credit card
4          Credit card
5                 Cash
              ...     
630914     Credit card
630915     Credit card
630916     Credit card
630917     Credit card
630918     Credit card
Name: payment_type, Length: 620198, dtype: object

5. ETL 後のデータを別の CSV ファイルにして S3 に出力する

ETL 後のデータを別の CSV ファイルにして S3 に出力します.replace_csvフォルダに CSV データを出力します.S3 に2件のデータが出力されていることを確認しました.

session.pandas.to_csv(
    dataframe=df_replace,
    path="s3://xxxx/replace_csv/",
    sep=",",
    database=None,
    table=None,
    partition_cols=None,
    preserve_index=True, 
    mode='append',
    procs_cpu_bound=None, 
    procs_io_bound=None
)

【output】
['s3://xxxx/replace_csv/c379726f1d6d4b1b939fd64c730f059d.csv',
 's3://xxxxreplace_csv/febc156980ec4a0ea23a640558a3a596.csv']

f:id:sadayoshi_tada:20191006122354p:plain

出力後のデータの件数が行削除後のデータ件数かも確認します.620198のデータ件数であることを確認できました.一緒ですね.

df2 = session.pandas.read_sql_athena(
    sql="select * from green_tripdata_replace",
    database="greentripdata"
)

print(df2)

【output】
      vendorid lpep_pickup_datetime  lpep_dropoff_datetime  \
0       "315602"                  "2"  "2019-01-16 17:12:12"   
1       "315603"                  "2"  "2019-01-16 17:05:29"   
2       "315604"                  "2"  "2019-01-16 17:30:44"   
3       "315605"                  "2"  "2019-01-16 17:09:35"   
4       "315606"                  "2"  "2019-01-16 17:37:14"   
...          ...                  ...                    ...   
620193  "315597"                  "2"  "2019-01-16 18:00:02"   
620194  "315598"                  "2"  "2019-01-16 17:08:57"   
620195  "315599"                  "2"  "2019-01-16 17:29:20"   
620196  "315600"                  "2"  "2019-01-16 17:24:21"   
620197  "315601"                  "2"  "2019-01-16 18:01:00"   

           store_and_fwd_flag ratecodeid pulocationid dolocationid  \
0       "2019-01-16 17:28:05"        "N"          "1"         "74"   
1       "2019-01-16 17:13:48"        "N"          "1"         "95"   
2       "2019-01-16 17:44:44"        "N"          "5"        "134"   
3       "2019-01-16 17:16:01"        "N"          "1"        "130"   
4       "2019-01-16 17:46:56"        "N"          "1"        "130"   
...                       ...        ...          ...          ...   
620193  "2019-01-16 18:15:39"        "N"          "1"        "182"   
620194  "2019-01-16 17:17:41"        "N"          "1"         "75"   
620195  "2019-01-16 17:33:48"        "N"          "1"         "75"   
620196  "2019-01-16 17:56:35"        "N"          "1"         "97"   
620197  "2019-01-16 18:43:47"        "N"          "1"         "97"   

        passenger_count  trip_distance  fare_amount  extra  mta_max  \
0                   NaN            NaN          NaN    NaN      NaN   
1                   NaN            NaN          NaN    NaN      NaN   
2                   NaN            NaN          NaN    NaN      NaN   
3                   NaN            NaN          NaN    NaN      NaN   
4                   NaN            NaN          NaN    NaN      NaN   
...                 ...            ...          ...    ...      ...   
620193              NaN            NaN          NaN    NaN      NaN   
620194              NaN            NaN          NaN    NaN      NaN   
620195              NaN            NaN          NaN    NaN      NaN   
620196              NaN            NaN          NaN    NaN      NaN   
620197              NaN            NaN          NaN    NaN      NaN   

        tip_amount  tolls_amount ehail_fee  improvement_surcharge  \
0              NaN           NaN     "0.0"                    NaN   
1              NaN           NaN     "0.0"                    NaN   
2              NaN           NaN     "0.0"                    NaN   
3              NaN           NaN     "0.0"                    NaN   
4              NaN           NaN     "0.0"                    NaN   
...            ...           ...       ...                    ...   
620193         NaN           NaN     "0.0"                    NaN   
620194         NaN           NaN     "0.0"                    NaN   
620195         NaN           NaN     "0.0"                    NaN   
620196         NaN           NaN     "0.0"                    NaN   
620197         NaN           NaN     "0.0"                    NaN   

        total_amount payment_type      trip_type  congestion_surcharge  
0                NaN      "16.62"  "Credit card"                   NaN  
1                NaN        "9.8"         "Cash"                   NaN  
2                NaN      "18.02"  "Credit card"                   NaN  
3                NaN       "9.36"  "Credit card"                   NaN  
4                NaN      "11.16"  "Credit card"                   NaN  
...              ...          ...            ...                   ...  
620193           NaN       "15.3"  "Credit card"                   NaN  
620194           NaN        "9.8"         "Cash"                   NaN  
620195           NaN       "8.76"  "Credit card"                   NaN  
620196           NaN       "23.3"  "Credit card"                   NaN  
620197           NaN       "34.8"         "Cash"                   NaN  

[620198 rows x 20 columns]

まとめ

リリースされた Python データ分析モジュールの「AWS Data Wrangler」のチュートリアルを行なってみました.Pandas で CSV を読み書きするときに JupyterNotebook の実行環境のローカルに配置して処理していましたが,S3 や Athena に接続設定などを書かずにローカルに ETL 処理対象があるかのようにデータを扱えた印象でした.本モジュールのメリットにあるように ETL 処理に集中していくことが可能なのかと感じます.AWS のデータ解析のエコシステムを作るときに登場してくる存在として今後のアップデートに注目していきたいですし,採用も検討していきたいですね!