Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

Amazon Aurora Serverless v2 で、PostGISを利用した位置情報検索の性能を試す

久しぶりにピアノの基礎練を始めたphonypianistです。 ハノンは指のトレーニング・リハビリには最適です。単純な音階なので、弾いてて楽しくはないですが😓 指を動かすと脳も活性化する話もありますが、その目的ならピアノでなくてもPCのキーボードをひたすら打っても良いのかも?🤔

さて、少し前に、Aurora Serverless v2が一般提供されて、LambdaからAuroraを使うのが、かなり実用的になりました。 aws.amazon.com

v1に比べると、おおよそ以下の点が改善されています

v1に引き続き、PostgreSQLにも対応しているため、PostGISも利用可能になっています。

今回、Aurora Serverless v2でPostGISを使った際に、どれくらいの性能が出るかを計測してみました。

概要

あらかじめ、ランダムに生成した位置情報のレコードをデータベースに入れておきます。 そして、ある地点から指定した半径以内にある点を取得する処理を行います。

検索イメージ

この取得処理にかかる処理時間を計測します。

計測条件

今回は、Lambdaからクエリを発行します。そのため、RDS Proxyを使用します。

Aurora Serverless v2検証構成

Aurora Serverless v2の最小ACUは2、最大ACUは16で設定しました。

データ量や発行するクエリは以下の通りとします。

  • 母体データ件数を100,000件~1,000,000件で変化させる。
  • クエリ実行でヒットする件数を100件~1000件で変化させる。
  • use_spheroid=true(回転楕円体を使った計算)でST_DWithin関数を用いて位置検索を行う(指定した点から半径xxxメートル以内のレコードを抽出)。

Lambdaから実際に発行するクエリは以下の通りです。数値部分は上記条件に合うように適宜変更します。

SELECT
    address,
    ST_AsGeoJson(geometry)
FROM
    points
WHERE
    ST_DWithin(geometry, ST_GeomFromText('POINT(139.6147861 35.5080426)', 4326), 1000, true);

計測結果

母体データ件数とヒットする件数を変化させて処理時間を計測した結果は以下となりました。(単位は秒)

↓母体件数\ヒット件数→ 100件 500件 1000件
100,000 1.69 1.74 1.70
500,000 2.04 2.00 2.11
1,000,000 2.52 2.63 2.78
5,000,000 7.12 7.12 7.12
10,000,000 12.20 11.72 11.68

グラフにすると以下のようになります。

位置情報検索の性能傾向

母体データ件数とほぼ比例して、処理時間も長くなっています。 検索でヒットする件数の影響はあまりなさそうです。

1000万件で10秒台なので、これくらいのデータ量なら、API Gateway+Lambda経由で実行しても同期処理が可能ですね。

まとめ

Aurora Serverless v2でも、(当然ではありますが)PostGISを使用できました。 Aurora Serverless v2でRDS Proxyに対応したこともあり、v1より便利にLambdaから使えるようになっています。 ぜひお試しください。

それでは!

Acroquest Technologyでは、キャリア採用を行っています。
  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
  少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。 www.wantedly.com

Athenaでデータの格納形式ごとのクエリ実行性能を比較してみた

こんにちは、唄うエンジニア、miyajima です。

仕事の中でAmazon Athenaを利用する機会があったため、今回はそのAmazon Athenaを使った性能比較を試してみました。


Amazon Athena とは - Amazon Athena

Amazon Athena はS3に保存されているファイルのデータをSQL形式のクエリで直接検索することができるサービスです。
大量のデータを高速に分析、検索することができ、また一度テーブル定義すれば、あとはS3にファイルを置くだけで検索できるようになるのでデータの追加変更も容易です。またサーバレスですから費用は検索に使用した分だけとなり、運用コストも抑えられます。

Athenaサポートしているデータ形式は、以下のように多数用意されています。

  • 一般的なデータファイル形式: CSV, TSV, JSON
  • Hadoopの分散処理に適用した形式: Apache Avro, Apache Parquet, ORC
  • その他、 Apache WebServer、CloudTrail、Logstashのログ形式など

サポートされる SerDes とデータ形式 - Amazon Athena

またGZIPなどでの圧縮したデータも扱えます。

そのため、それぞれのデータ形式によって性能差がどのくらいあるのかは把握しておきたいところです。
また、AthenaはS3上の指定したパス上の全ファイルを1つのテーブルとして扱うため、ファイル数やファイルサイズがどのようにパフォーマンスに影響するのかも気になりますね。
ということで、いくつかの観点に沿って実行時間を調べてみました。

比較内容

今回は以下の観点にそって性能比較を行いました。

観点 内容
ファイルサイズ/ファイル数 データファイルの分割数を 1 / 10 / 100 / 1,000 / 10,000 にした5パターンのデータセットを用意し、それぞれの検索速度を比較
ファイル形式 同一の情報量のデータをCSVJSON、Parquet形式で用意し、検索速度を比較する。
圧縮/非圧縮 同一のデータをgzip圧縮した場合としない場合で比較。この時ファイルサイズによる影響も比較する。
検索方法 各ファイル形式に対して全検索、LIKE検索、列指定検索を比較する。

また、今回はデータ形式を主眼に比較を行っており、パーティションは指定していません。できれば別の機会に、パーティション指定の有無による性能比較も試したいと思います。

今回の調査にあたり使わせていただいたデータは、以下のページで公開されているWebサーバのログファイルです。

AIT Log Data Set V1.1 | Zenodo

この中から、Webサーバのアクセスログ(14万行)を複数のフォーマットに変換してS3に登録し、検索してみました。
mail.cup.com-access.log(抜粋)

192.168.10.190 - - [29/Feb/2020:00:00:02 +0000] "GET /login.php HTTP/1.1" 200 2532 "-" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0"
192.168.10.4 - - [29/Feb/2020:00:00:09 +0000] "POST /services/ajax.php/kronolith/listTopTags HTTP/1.1" 200 402 "http://mail.cup.com/kronolith/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/77.0.3865.90 HeadlessChrome/77.0.3865.90 Safari/537.36"
192.168.10.190 - - [29/Feb/2020:00:00:12 +0000] "POST /login.php HTTP/1.1" 302 601 "http://mail.cup.com/login.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0"
192.168.10.190 - - [29/Feb/2020:00:00:13 +0000] "GET /services/portal/ HTTP/1.1" 200 7696 "http://mail.cup.com/login.php" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0"
192.168.10.190 - - [29/Feb/2020:00:00:14 +0000] "GET /themes/default/graphics/head-bg.png HTTP/1.1" 200 380 "http://mail.cup.com/themes/default/screen.css" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0"
192.168.10.190 - - [29/Feb/2020:00:00:14 +0000] "GET /themes/default/graphics/logo.png HTTP/1.1" 200 2607 "http://mail.cup.com/themes/default/screen.css" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0"
::1 - - [29/Feb/2020:00:00:18 +0000] "OPTIONS * HTTP/1.0" 200 110 "-" "Apache/2.4.25 (Debian) OpenSSL/1.0.2u (internal dummy connection)"
192.168.10.190 - - [29/Feb/2020:00:00:19 +0000] "GET /mnemo/ HTTP/1.1" 200 5681 "http://mail.cup.com/services/portal/" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:73.0) Gecko/20100101 Firefox/73.0"
192.168.10.190 - - [29/Feb/2020:00:00:22 +0000] "GET /services/portal/ HTTP/1.1" 200 7053 "http://mail.cup.com/nag/list.php" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/77.0.3865.90 HeadlessChrome/77.0.3865.90 Safari/537.36"
192.168.10.190 - - [29/Feb/2020:00:00:26 +0000] "GET /mnemo/ HTTP/1.1" 200 5179 "http://mail.cup.com/services/portal/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/77.0.3865.90 HeadlessChrome/77.0.3865.90 Safari/537.36"
...

なお今回の性能比較の中で、全く同じ条件(データ形式、ファイルサイズ、ファイル数)でクエリ実行した場合でも、実行時間が平均値に対して20~40%程度のバラつきがありました。それもコールドスタートで発生するような「初回のみ遅い」という訳ではなく、連続で実行した時も途中の実行回だけ他よりも時間がかかる、というようなこともありました。
これは、AWS内部リソース割り当ての調整などで実行時間に差が発生しているのかもしれません。

これらのばらつきの影響を軽減するため、今回の測定では、5回測定した処理時間のうち最大値・最小値を除いた中央3値の平均を測定値とすることとしました(オリンピックでの芸術点の付け方と同じです)。

また、今回使用したデータは生の状態でも数百MB程度のものですが、GB、TB単位の大量のデータを扱う場合は、今回の誤差の影響は相対的に小さくなるのではないか、と思います。

比較結果

ファイルサイズ/ファイル数による比較

まず検証方法ですが、情報としては全く同じデータをJSONCSV、Parquetの形式で用意し、更にそのデータを1 / 10 / 100 / 1,000 / 10,000 のファイル数で分割したデータセットを作成します。
つまり、ここまでで以下の15パターンのデータセットが用意されました。

形式 ファイル数 1ファイルのデータ件数 1ファイルのサイズ(平均)
JSON
1
148,534
49,019.3 kB
JSON
10
14,854
4,901.9 kB
JSON
100
1,486
490.2 kB
JSON
1,000
149
49.0 kB
JSON
10,000
15
4.9 kB
CSV
1
148,534
37,124.2 kB
CSV
10
14,854
3,712.4 kB
CSV
100
1,486
371.2 kB
CSV
1,000
149
37.1 kB
CSV
10,000
15
3.7 kB
Parquet
1
148,534
-
Parquet
10
14,854
-
Parquet
100
1,486
-
Parquet
1,000
149
-
Parquet
10,000
15
-

※Parquetのデータはファイルを直接読み込んで得る形式ではなく、CSVデータをを元に変換しています。

そしてこれらのパターンそれぞれをAthenaの別々のテーブルに格納し、クエリを実行しました。

クエリ実行時間は、以下のようになりました。

ファイル分割数 1ファイルのデータ数 JSON CSV Parquet
1 148,534 2.17秒 2.19秒 1.90秒
10 14,854 2.15秒 2.10秒 1.83秒
100 1,486 2.11秒 2.34秒 1.89秒
1,000 149 2.03秒 2.65秒 2.26秒
10,000 15 3.09秒 3.04秒 2.49秒

ファイルサイズ・ファイル数での比較

今回は、そこまで影響は大きくはなかったですが、どの形式もサイズが小さいファイルが多くなると、処理時間が長くなる傾向が見えました。

圧縮/非圧縮の比較

今回はgzip圧縮した場合と非圧縮の場合、またファイル形式ははJSONCSVで試しました。
さらに、圧縮したデータの操作はファイルサイズや分割数にも影響する可能性があるため、ファイル分割数を上と同様にいくつかのパターンで実施しました。

ファイル分割数 1ファイル 100ファイル 10000ファイル
JSON:非圧縮 2.17秒 2.11秒 3.09秒
JSON:gzip 3.03秒 2.62秒 3.77秒
CSV:非圧縮 2.19秒 2.34秒 3.04秒
CSV:gzip 2.59秒 2.47秒 3.06秒

圧縮・非圧縮での比較

今回のデータではJSONCSVともに、検索にかかる時間はGZIP圧縮した方がわずかに長かったです。圧縮したデータを扱う方が効率は良くなるように感じますが、検証に用意出来たデータのサイズがそれほど大きくないため、あまりその効果が反映されなかったのかもしれません。

またデータ保存の観点では、データを圧縮した方がコスト効率がよいため、その点を踏まえて実際のデータで試した方がいでしょう。

ファイル形式と検索方法による比較

次はクエリによる比較です。 今回検証に使用したクエリは以下の3パターンです。

  • 全検索("SELECT * FROM <テーブル名>")
  • LIKE検索("SELECT * FROM <テーブル名> WHERE path LIKE '%.php%' ")
  • 列指定検索("SELECT host, path, status FROM <テーブル名>")

この3つのクエリで、JSON / CSV / Parquet の3種類の形式を、更にファイル分割数も替えて検索しました。

全検索

データ形式 1ファイル 100ファイル 10,000ファイル
JSON 2.17秒 2.11秒 3.09秒
CSV 2.19秒 2.34秒 3.04秒
Parquet 1.90秒 1.89秒 2.49秒

ファイル形式での比較(全検索)

LIKE検索

データ形式 1ファイル 100ファイル 10,000ファイル
JSON 1.82秒 1.53秒 2.69秒
CSV 1.94秒 1.64秒 2.71秒
Parquet 2.10秒 1.92秒 1.49秒

ファイル形式での比較(LIKE検索)

列指定検索

データ形式 1ファイル 100ファイル 10,000ファイル
JSON 1.80秒 1.54秒 2.60秒
CSV 1.46秒 1.35秒 2.59秒
Parquet 1.45秒 1.50秒 1.63秒

ファイル形式での比較(列指定検索)

CSVJSONは、どの検索方式でもファイル数が増えるにしたがって処理時間も増えているのが分かりますが、ParquetはLIKE検索、列検索の際、ファイル数が増えても処理時間が増えていません(というかLIKE検索は処理時間が減ってすらいます)。
Parquetは列指向データ形式といい、列単位でデータを取得、検索する際に最適化されたフォーマットです。この形式はデータの格納や検索を最適化するためのエンコーディングが複数用意されています。
エンコーディング方法の例:

エンコーディング方式 概要 効果の高いデータ列
Dictionary Encoding 出現する値を辞書に格納し、テーブルにはそのキーを格納する 出現する値の種類が限られているようなデータ
Run Length Encoding (RLE) 値と、その値を繰り返す回数のペアを格納する 同じ値が連続して出現するようなデータ
Delta Encoding 一定件数ごとに区切った中でのデータの前後の差分を抽出してその差分を保持する 一定のペースで変化する数値データ

参照: Encodings | Apache Parquet

そのため、これらの方式に適するデータを格納するようなスキーマを設計し、またそのカラムを検索、抽出の対象にできれば、よりParquetの特性を生かし、処理を高速化させる事が出来るのではないかと思います。

また今回はデータ量が数十MB程度しかありませんでしたので、それを強く実感できるほどではないのかもしれません。機会があれば、もっと多量のデータで検証してみたいと思います。

まとめ

今回の検証のまとめです。

  • 大量データを扱う場合は特に、処理時間、コストの両方の観点から、Parquet形式を積極的に使う。
  • ファイル数が細かく、多くなると、処理時間が長くなる傾向がある。
  • 今回のデータでは圧縮ファイルを使った場合の性能改善は見られなかった。更に大きなデータでの検証が必要。

それぞれで性能に差分は確認できましたが、より多くのデータ、複雑な条件になった時には、異なる傾向が見えるかもしれません。 機会があれば、別のデータセットで試してみたいと思います。

また、Athenaの性能改善を行う上で、AWS公式のパフォーマンスチューニングの情報は把握しておくとよいでしょう。

Amazon Athena のパフォーマンスチューニング Tips トップ 10 | Amazon Web Services ブログ

Acroquest Technologyでは、キャリア採用を行っています。
  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
  少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。 www.wantedly.com

エッジでLookout for Visionを実行すると爆速だった話

こんにちは、機械学習エンジニアの駿です。

先日庭の花壇にヒマワリの種をまいたのですが、早速芽が出てきました。
夏に黄色い花が咲くのが今から楽しみです。

今回はAWSの外観検査サービスである Amazon Lookout for Vision が、 AWS IoT Greengrass を使ってエッジでの推論が可能になったため、試してみました。

今までは Lookout for Visionクラウド側での推論(判定)しかできず、画像をネットワーク越しに送るため、オーバーヘッドが発生していました。
そのため、例えば工場の生産ラインで外観検査をしていたとすると、製品の撮影・検査をした後で、異常なモノを仕分けをしたりしますが、それに時間がかかってしまう、といった状況が発生することになってしまいます。

しかし、 Lookout for Vision のモデルを Greengrass でパッケージ化しエッジデバイスに配置して検査を行うことで、画像を送信するオーバーヘッドがなくなり、リアルタイムで処理ができるようになります。

Amazon Lookout for Vision

Lookout for Visionは画像が正常か異常を判定する、異常検知のサービスです。
教師画像は最低30枚と少ない枚数で始められますが、非常に高い精度で異常を判定することができます。

詳細については以下の記事があるので、そちらをご覧ください。

acro-engineer.hatenablog.com

AWS IoT Greengrass

Greengrass は Raspberry Piなどのエッジデバイス上にIoTアプリケーションを構築、デプロイ、管理するためのクラウドサービスです。
Lambda関数、Dockerコンテナなどをコンポーネントとしてパッケージ化し、エッジデバイスにデプロイ、実行することができます。

そんな Greengrass が2021年末に Lookout for Vision モデルのコンポーネントに対応し、
GPUを備えたエッジデバイスにデプロイすることで、クラウドに画像を送ることなく、オンプレミスかつリアルタイムの外観検査ができるようになりました。

構成

今回はエッジデバイスにJetsonNanoを使用し、 Greengrass サービスを通じて、 Lookout for Vision モデルコンポーネントと、 それを利用するための EdgeAgent コンポーネントをデプロイします。

今回の構成図(公式ドキュメントより)

手順

大まかな流れは下記のようになります。

  1. JetsonNano 上で Greengrass のサービスを起動する
  2. Lookout for Vision モデルを Greengrass 用にコンポーネント化する
  3. モデルコンポーネントAWSが提供している EdgeAgent を JetsonNano にデプロイする
  4. Python スクリプトからデプロイされたモデルを起動し、検査を実行する

早速、詳細の説明に入ります。

1. JetsonNano 上で Greengrass のサービスを起動する

公式ドキュメントでは動作環境としてJetson Xavierを推奨していますが、すぐに用意できなかったので、今回は JetsonNano を使ってみました。
ただし、性能を考慮した場合、実際の運用などでは Jetson Xavier などを利用するほうが安全だと思われます。

(1) JetsonNano に JetPack をインストール

エッジデバイス上で Lookout for Vision のモデルを使用するためには、GPUとCUDA、TensorRT などのライブラリが必要です。
JetPack を使うと、これらのライブラリをひとつひとつインストールする必要がなく、 既に整った環境を作ることができます。

JetPack4.5.1 のSDカードイメージをダウンロードし、公式ドキュメント に従ってセットアップを行います。

なお、Greengrass でデプロイする Lookout for Vision モデルが対応しているのが、バージョン4.4と4.5系のみのため、間違えて最新バージョンを入れないように注意が必要です。

(2) Greengrass サービスおよび Lookout for Vision モデルの起動に必要なライブラリのインストール

Greengrass サービスの起動に Java が必要になります。
今回はAWSが提供する Corretto をインストールしました。

また、Lookout for Vision のモデルの起動に Python3.8 もしくは 3.9 が必要です。
今回は 3.8 をインストールしました。

(3) クライアントアプリに必要なライブラリのインストール

公式ドキュメント に従って、 grpc をインストールし、サービス定義ファイルからクライアントインターフェイスを生成します。

また、画像読み込みのための Pillow もインストールします。

(4) Greengrass サービスのダウンロードおよび起動

Greengrass のコンソールから 「1つのCoreデバイスをセットアップ」を選択します。

「1つのCoreデバイスをセットアップ」

コアデバイス名など必要な情報を入力すると、インストーラのダウンロードコマンドおよび実行コマンドが生成されるので、エッジデバイス上でコマンドを実行します。

インストールが終わると、自動で Greengrass サービスの実行が始まります。

また、インストール中に Greengrass ユーザ ggc_user が作成されています。
そのままでは Greengrass でデプロイされたコンポーネントGPU にアクセスできないため、 ggc_user を video グループに追加します。

sudo usermod -a -G video ggc_user

Greengrass コンソールから追加したエッジデバイスが確認でき、ステータスが「正常」となっていたら Greengrass の準備は完了です。

JetsonNanoがCoreデバイスとして設定できた

(5) DLR のインストール

Lookout for Vision が libdlr.so を必要とするのですが、 pip でインストールできるDLRには .so ファイルが含まれていないようです。

ggc_user として whl からインストールすることで、 モデルコンポーネントが読み込めるようになります。
下記コマンドを ggc_user として実行してください。

curl -O https://neo-ai-dlr-release.s3-us-west-2.amazonaws.com/v1.10.0/jetpack4.5/dlr-1.10.0-py3-none-any.whl
python3.8 -m pip install dlr-1.10.0-py3-none-any.whl

2. Lookout for Vision モデルを Greengrass 用にコンポーネント化する

既に学習済みのモデルがあるものとします。

プロジェクトのページに遷移し左のメニューで「モデルのパッケージ」を選択します。
「モデルパッケージングジョブを作成」ボタンを押し、「モデルの選択」など必要な項目を埋めていきます。

「モデルパッケージングジョブを作成」

  • ターゲットハードウェア設定

    2022/05/07現在、プリセットの設定は Jetson Xavier 用しかないため、JetsonNano を使用する際は「ターゲットプラットフォーム」を選択して、設定を行います。

    項目
    オペレーティングシステム LINUX
    アーキテクチャ ARM64
    アクセラレーター NVIDIA
    コンパイラオプション {"gpu-code": "sm_53", "trt-ver": "7.1.3", "cuda-ver": "10.2"}

    コンパイラオプションは使用するデバイス、インストールしたライブラリのバージョンによって異なります。
    GPUコード、TensorRT バージョン、CUDA バージョンをそれぞれ指定します。
    上記値は JetsonNano+JetPack4.5.1の場合の値です。

    ターゲットハードウェアの設定

最後に「モデルパッケージングジョブを作成」ボタンを押して、パッケージングを開始します。
コンソール上で、「成功」ステータスになったら完了です。

Greengrass コンソールのコンポーネントページからも作成したモデルコンポーネントを確認することができます。

3. モデルコンポーネントAWSが提供している EdgeAgent を JetsonNano にデプロイする

Greengrass コンソールのデプロイページからデプロイを作成します。

今回はデプロイターゲットにコアデバイスを選択し、上で登録した JetsonNano にのみデプロイします。

コンポーネントの選択」画面で、パッケージした Lookout for Vision モデルコンポーネントを選択します。
このコンポーネントAWSが提供する aws.iot.lookoutvision.EdgeAgent に依存しているため、 そちらも自動でデプロイされます。

コンポーネントの選択」

そのほかの設定を変更する必要は必要ありません。
「デプロイ」を選択して、 JetsonNano にデプロイします。

デプロイのステータスが「完了」になったら成功です。

4. Pythonスクリプトからデプロイされたモデルを起動し、検査を実行する

エッジデバイス上で ggc_user としてログインします。

Pythonインタプリタを起動し、モデルの起動と検査を試してみます。

(1) モデルの起動

import grpc
from edge_agent_pb2_grpc import EdgeAgentStub
import edge_agent_pb2 as pb2
channel = grpc.insecure_channel("unix:///tmp/aws.iot.lookoutvision.EdgeAgent.sock")
stub = EdgeAgentStub(channel)
model_component_name = "lfv_component_aarm"

# まずはモデルが止まっていることを確認します。
model_description_response = stub.DescribeModel(pb2.DescribeModelRequest(model_component=model_component_name))
model_description_response.model_description.status == pb2.STOPPED
# -> True

# モデルを起動します
stub.StartModel(pb2.StartModelRequest(model_component=model_component_name))
# -> status: STARTING
# 起動するのを待ってから
model_description_response = stub.DescribeModel(ob2.DescribeModelRequest(model_component=model_component_name))
model_description_response.model_description.status == pb2.RUNNING
# -> True

(2) 検査実行

モデルが起動したら画像に対して検査を実行できます。

エッジデバイスに画像を用意して、Pillow で読み込んだものをモデルに送ります。

from PIL import Image

image = Image.open(image_path)
image = image.convert("RGB")
detect_anomalies_response = stub.DetectAnomalies(
    pb2.DetectAnomaliesRequest(
        model_component=model_component_name,
        bitmap=pb2.Bitmap(
            width=image.size[0],
            height=image.size[1],
            byte_data=bytes(image.tobytes())
        )
    )
)

is_anomalous = detect_anomalies_response.detect_anomaly_result.is_anomalous
confidence = detect_anomalies_response.detect_anomaly_result.confidence
print(f"Image is anomalous - {is_anomalous}")
print(f"confidence - {confidence:.2}")
# -> Image is anomalous - True
# -> confidence - 0.97

(3) モデルの停止

stub.StopModel(StopModelRequest(model_component=model_component_name))
model_description_response = stub.DescribeModel(ob2.DescribeModelRequest(model_component=model_component_name))
model_description_response.model_description.status == pb2.STOPPED
# -> True

channel.close()

結果

精度

前述の投稿で使用している Metal Nut 画像から正常を5枚、異常を5枚使って検査を実行したところ、すべて正しく判定することができました。

クラウド側で判定した場合と比較して、エッジデバイス上で判定しても同等の精度が出ることがわかりました。

実行時間

下記の関数を作り、画像1枚を推論するのにかかる時間を計測してみました。

def timeit():
    start_time = time.time()
    stub.DetectAnomalies(...)
    print(f"took {time.time() - start_time} seconds")

上記関数を10回実行した結果は下記のようになり、平均で0.25秒/枚で検査ができることになります。

# 所要時間(ms)
1 399.8
2 374.2
3 212.9
4 216.0
5 217.8
6 212.5
7 215.5
8 212.8
9 218.3
10 216.5
平均 249.6

awscli でクラウド側の Lookout for Vision モデルを使用した場合はネットワークの往復も含めて約3秒でした。

awscli の場合は画像変換の時間も含まれるため単純な比較はできませんが、エッジで実行することで10倍以上早くなっています。
Jetson Xavier などより計算力のあるデバイスを用いることで、さらにリアルタイム性のある外観検査ができるようになりますね。

オレゴンリージョンの Lookout for Vision を使用しているため、東京リージョンのものを用いるよりもさらに伝送時間がかかっていると思われます。)

料金

Greengrass を使ってエッジデバイスにデプロイした Lookout for Vision モデルを使用する場合、エッジ推論ユニットに基づいて月額料金がかかります。
1デバイス上で120検査/分までの検査は1エッジ推論ユニットとして扱われ、1エッジ推論ユニットは月100USD かかります。

個人で実施するにはちょっとお高めになっているので、工場など大規模で検査を行う必要があるユースケースを想定しているのがわかります。

まとめ

今回はGreengrassを使ってLookout for VisionのモデルをJetson Nanoにデプロイし、外観検査を行いました。
普段エッジデバイスを使うことがあまりないこともあり、最初の環境構築でバージョン不整合などでつまづいてしまいました。

実際に動かしてみて、エッジでの検査は画像をネットワーク越しに送信する必要がない分、10倍も早く実行できることがわかりました。
それでいてクラウドと同じモデルを使っているため、精度は同等です。

また、今回は Pythonインタプリタを使って検査を実行しましたが、クライアントアプリを作成して Greengrass コンポーネントとしてデプロイすることもできます。

今度は実際にカメラを繋いで、どれくらいのFPSが出せるのか、なども試してみたいです。

Acroquest Technologyでは、キャリア採用を行っています。
  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
  少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。 www.wantedly.com

モデル最適化ソフトウェアOpenVINOを用いた性能高速化とモデル比較の実験

皆さんこんにちは。
@tereka114です。

モデル最適化の選択肢の一つであるOpenVINOを試してみました。
モデル最適化とは、モデルの精度を殆ど落とさず、高速化する技術で、以下のような恩恵が得られることが知られています。

①特にGPU等を利用できない、RasberryPiのようなエッジデバイス上で機械学習モデルを動かすケースで性能を上げられる
②モデル最適化のエンジンが動く環境であれば、一度構築したモデルを複数の環境で実行させることができる

今回、その技術の一つであるOpenVINOとそれを用いた有名なモデルのベンチマークを紹介します。

OpenVINO

Intel社が開発したディープラーニングを高速に実行するためのソフトウェアです。
OpenVINOが学習済のモデルをハードウェアに合わせて最適化し、CPU、GPUなどのアクセラレータで高速で推論できるようにします。

www.intel.com

本記事では、インストールに関して詳細を扱いません。
インストール方法は次のリンク先を参考にしてください。

docs.openvino.ai

PyTorchからOpenVINOを動かす

PyTorchは実装のしやすさとPyTorch Image Models(timmライブラリ)を利用した学習モデルの多様性から私も含め、多くのデータサイエンティストが利用しています。
そのため、今回は、PyTorchで作られたモデルからOpenVINOを動かしてみます。

github.com

OpenVINOを利用する手順

PyTorchのモデルをOpenVINO形式に変換するには次のステップが必要になります。

1. PyTorchのモデルからONNX形式に変換する。
2. ONNXからOpenVINO形式に変換する。
3. OpenVINOモデルで推論する。

モデル変換・推論の流れ
PyTorchのモデルをONNXに変換する。

OpenVINOはPyTorchのモデルを直接変換できないため、まずはONNXに変換します。
PyTorchにONNX変換を行う関数が用意されているため、その関数を利用します。
ここで、モデルはevalを呼び出して推論モードにしておくことが必要です。
なぜならば、推論時の最適化を行う必要があるためDropoutやBatch Normalizationなど学習、推論で挙動が変わるものでは、期待する計算ができなくなります。

また、SwinTransformerには、ONNXに備わっていない演算があるため、その関数を外部からONNXに登録(roll関数)しています。

import torch
import torch.onnx as torch_onnx
import timm
import argparse
import torch
from torch.onnx.symbolic_helper import parse_args, _slice_helper
from sys import maxsize as maxsize


@parse_args('v', 'is', 'is')
def roll(g, input, shifts, dims):
    # Swin Transformerの計算に必要なOperatorを定義
    assert len(shifts) == len(dims)
    result = input
    for i in range(len(shifts)):
        shapes = []
        shape = _slice_helper(g, result, axes=[dims[i]], starts=[-shifts[i]], ends=[maxsize])
        shapes.append(shape)
        shape = _slice_helper(g, result, axes=[dims[i]], starts=[0], ends=[-shifts[i]])
        shapes.append(shape)
        result = g.op("Concat", *shapes, axis_i=dims[i])
    return result

parser = argparse.ArgumentParser()
parser.add_argument("--model")
parser.add_argument("--output")
parser.add_argument("--size", type=int)

args = parser.parse_args()

# モデルの読み込み
torch.onnx.symbolic_registry.register_op('roll', roll, '', version=9)
net = timm.create_model(args.model, pretrained=True)
net.eval()
# モデル出力のための設定
model_onnx_path = args.output # 出力するモデルのファイル名
input_names = ["input"] # データを入力する際の名称
output_names = ["output"] # 出力データを取り出す際の名称

# ダミーインプットの作成
input_shape = (3, args.size, args.size) # 入力データの形式
batch_size = 1 # 入力データのバッチサイズ
dummy_input = torch.randn(batch_size, *input_shape) # ダミーインプット生成

# 変換実行
if "swin" in args.model:
    # Swin Transformer用に、ONNXのOpsetを固定
    output = torch_onnx.export(
        net, dummy_input, model_onnx_path,export_params=True, 
        verbose=False, input_names=input_names, output_names=output_names, opset_version=11)
else:
    output = torch_onnx.export(
        net, dummy_input, model_onnx_path,export_params=True, 
        verbose=False, input_names=input_names, output_names=output_names)

この実装を次のコマンドで動かします。

python pytorch_to_onnx.py --model resnet50 --output resnet50.onnx --size 224
ONNXからOpenVINO形式に変換する。

ONNXからOpenVINOへの変換はOpenVINOのモデル最適化コマンドを実行するのみです。
前段のResNet50のモデルを利用して、変換する場合は以下のコマンドです。

python /opt/intel/openvino_2021/deployment_tools/model_optimizer/mo.py --input_model resnet50.onnx
OpenVINOモデルで推論する。

最後にOpenVINOを動作させます。
事前に以下のコマンドでOpenVINOのPythonモジュールをインストールします。

pip install openvino

以下、先程までコンパイルしたモデルの推論の実装です。
PyTorch、ONNX、OpenVINOの推論速度を比較する実装も含まれています

import numpy as np
import time as tm
import timm

import torch
import onnxruntime
from openvino.inference_engine import IECore

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("--model")
parser.add_argument("--output")
parser.add_argument("--size", type=int)

args = parser.parse_args()
SIZE = int(args.size)

# Pytorchの準備
net = timm.create_model(args.model, pretrained=True)
net.eval()
# ONNXの準備
session = onnxruntime.InferenceSession(f"{args.output}.onnx")

# OpenVINOの準備
ie = IECore()
model_path = f'{args.output}.xml'
weight_path = f'{args.output}.bin'
net_openvino = ie.read_network(model=model_path, weights=weight_path)
exec_net = ie.load_network(network=net_openvino, device_name='CPU', num_requests=1)

# 時間計測用
time_onnx = 0
time_openvino = 0
time_pytorch = 0

# 予測結果比較用
out_onnx = []
out_pytorch = []
out_openvino = []

TIMES = 300
for i in range(TIMES):
    image = torch.rand(1, 3, SIZE, SIZE)
    with torch.no_grad():
        start_time = tm.time()
        out = net(image)
        out_pytorch.append(np.argmax(out[0]))
        time_pytorch += tm.time() - start_time

    start_time = tm.time()
    preds = session.run(["output"], {"input": image.cpu().numpy()})
    out_onnx.append(np.argmax(preds[0]))
    time_onnx += tm.time() - start_time

    start_time = tm.time()
    outputs = exec_net.infer(inputs={'input': image.cpu().numpy()})['output']
    out_openvino.append(np.argmax(outputs[0]))
    time_openvino += tm.time() - start_time

# 推論結果の整合性確認のため
print(np.sum(np.array(out_pytorch) == np.array(out_openvino)))
print(np.sum(np.array(out_pytorch) == np.array(out_onnx)))
print(np.sum(np.array(out_onnx) == np.array(out_openvino)))

# 計算結果
print('PyTorch: ', time_pytorch / TIMES)
print('ONNX: ', time_onnx / TIMES)
print('Open VINO: ', time_openvino / TIMES)

実行は次のコマンドです。

python infer.py --size 224 --model resnet50--output resnet50

性能実験

OpenVINOを利用すればどの程度高速化されるのか
画像認識の有名なモデルと先程の実装を用いて、性能を比較しました。

計測環境

現在一般的に利用されるモデルを中心に計測しました。
モデルの精度・性能の目安はPyTorchのモデル実装の宝庫であるtimmライブラリのリンクをご確認ください。

github.com

結果は次のとおりです。PyTorch(s),ONNX(s),OpenVINO(s)は1枚あたりの推論速度を示しています。
PyTorchと比較して、30-70%ほどの高速化を達成し、また、ONNXよりもほとんどの場合で高速化を達成できました。
また、本方式では、最終的な推論結果は変わりませんでした。

Model Image Size PyTorch(s) ONNX(s) OpenVINO(s) 高速化率(Pytorch) 高速化率(ONNX)
resnet50 224 0.271 0.137 0.112 58.67% 18.25%
resnet152 224 0.795 0.409 0.332 58.24% 18.83%
convnext_tiny 224 0.324 0.201 0.182 43.83% 9.45%
swin_tiny_patch4_window7_224 224 0.317 0.135 0.184 41.96% -36.30%
mobilenetv2_120d 224 0.065 0.031 0.028 56.92% 9.68%
mobilenetv3_large_100_miil 224 0.03 0.014 0.019 36.67% -35.71%
vit_tiny_patch16_224 224 0.088 0.053 0.057 35.23% -7.55%
vgg16 224 1.09 0.31 0.275 74.77% 11.29%
vgg19 224 1.309 0.388 0.339 74.10% 12.63%
tf_efficientnet_b0_ns 224 0.054 0.024 0.024 55.56% 0.00%
tf_efficientnet_b7_ns 224 0.448 0.216 0.18 59.82% 16.67%
モデル最適化

棒グラフはPyTorch(s)、ONNX(s)、OpenVINO(s)の値を表示しており、低い値であればよりよい性能であることを示しています。

最後に

CPUで処理を行った場合、OpenVINOの結果が最も早い場合が多かったです。
IoTデバイス上で動作させた場合に少し性能に満足できない場合にOpenVINOを適用すると良いかもしれません。
推論性能に困った場合の選択肢の一つに入れると良いと思います。

Acroquest Technologyでは、キャリア採用を行っています。


  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
Kaggle Grandmasterと話したいエンジニアWanted! - Acroquest Technology株式会社のデータサイエンティストの採用 - Wantedlywww.wantedly.com

Amazon PersonalizeのEC用レコメンダーを利用してみた

こんにちは、@shin0higuchiです😊

今回はAmazon Personalize を利用して、ECサイト向けのレコメンデーションを実現したいと思います。

皆さんも普段ECサイトを利用するかと思いますが、
おすすめされた商品を、つい買ってしまう方もいるのではないでしょうか。(私も、買うつもりが無かった物につい手を出してしまうことがあります...)

近頃のECサイトでは、ユーザーの年代や性別、商品の閲覧履歴などを踏まえて、ひとりひとりに最適化(パーソナライズ)された推薦(レコメンデーション)を導入することが多くあります。

ユーザーごとに的確な商品を推薦することは、売り上げの増加に直結しますが、適切なレコメンダーを構築するには相応の仕組みや知識が必要となり、ハードルが高いと言えます。通常は、学習のためのデータ加工やモデルの設計なども含め自分で実施したり、計算リソースの整備をしたりと、導入までの時間やコストが膨らみがちかと思います。
AWSAmazon Personalizeを利用することで、専門知識がなくとも簡単にレコメンダーを構築することが可能です。

2021年の末に、Amazon Personalizeの新機能としてEC向けに最適化されたレコメンダーがリリースされたのでそちらを試してみました。
aws.amazon.com




1. Amazon Personalize とは?

「Amazon Personalize」は、Amazon.com でも利用されているという、機械学習アルゴリズムが利用でき、手軽に高精度なレコメンデーションを実現できるサービスです。
前述のとおり、機械学習の専門知識がなくとも利用できるというのが大きな売りと言えるでしょう。
f:id:shin0higuchi:20220220235836p:plain:w600
f:id:shin0higuchi:20220220234114p:plain:w600

2. ECサイトレコメンダーの基本的な使い方

Amazon Personalize を利用する際の基本的な使い方の流れは次のようになります。

  1. データの準備
  2. S3へのデータ配置
  3. データセットの作成
  4. レコメンダーの作成
  5. 推論(レコメンド)の実行

データの準備

以下の3種類のデータが登録可能。
最低限 Interactions のデータがあればレコメンド可能ですが、より適切なレコメンドのためには3種類とも用意するのが良いと言えます。

No. 種別 概要 含まれるカラム例
1 Users ユーザのマスタ情報 ユーザID、任意のカテゴリ・数値データ(性別や年齢等)
2 Items 商品のマスタ情報 商品ID、任意のカテゴリ・数値(商品カテゴリや価格、登録日等)
3 Interactions ユーザの商品に対するアクションを記録したもの タイムスタンプや、アクションの種別

上記のデータは、CSV形式で用意します。
例えばInteractionsであれば、次のようなデータを用意することになります。

USER_ID,ITEM_ID,TIMESTAMP,EVENT_TYPE
1000,1,1645375967,view
1000,2,1645376900,view
1000,1,1645380267,purchase
2000,5,1645450267,view
...

上記のデータは、ユーザ1000はが商品1と2を閲覧したあとで商品1を購入しているのがわかりますね。
こういったデータを学習データとして用いてレコメンダーを構築していきます。

S3へのデータ配置

用意したCSVファイルはS3の任意のバケットに配置しておき、後述のPersonalize側の設定でパスを指定します。この時、Personalizeからアクセスできるようにバケットポリシーを設定します。

{
  "Version": "2012-10-17",
  "Id": "PersonalizeS3BucketAccessPolicy",
  "Statement": [ 
    { 
      "Sid": "PersonalizeS3BucketAccessPolicy",
      "Effect": "Allow", 
      "Principal": { 
        "Service": "personalize.amazonaws.com" 
      }, 
      "Action": [ "s3:GetObject", "s3:ListBucket" ], 
      "Resource": [ "arn:aws:s3:::bucket-name", "arn:aws:s3:::bucket-name/*" ] 
    }
  ] 
} 

データセットの作成

  • PersonalizeのメニューからDataset Groupを作成する。

f:id:shin0higuchi:20220304014233p:plain:w600

用意したCSVデータに合わせて、JSONスキーマを定義する。
f:id:shin0higuchi:20220313002736p:plain:w600

  • 必要に応じて、同様の手順でUser / Item のデータも登録する

f:id:shin0higuchi:20220313003219p:plain:w600

上記はS3から一括でデータを登録する方法ですが、以下のAPIからデータを登録することも可能です。
https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/API_UBS_PutUsers.html
https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/API_UBS_PutItems.html
https://docs.aws.amazon.com/ja_jp/personalize/latest/dg/API_UBS_PutEvents.html

レコメンダーの作成

これまでのAmazon Personalizeでは、ここから自前で「Solution」と「Campaign」を作成する必要がありました。(Solution≒「学習モデル」、Campaign≒「レコメンド実行APIのエンドポイント」と考えてもらえば良いかと思います。)

ですが、ECサイトドメインでは上記に相当する「レコメンダー」と呼ばれるものが用意されており、それを選択して有効化するのみで利用可能です。
f:id:shin0higuchi:20220313003605p:plain:w600

以下5種類のレコメンダーが用意されています。

  • Customers who viewed X also viewed(Xを見たお客様はこちらも見ています)
  • Frequently bought together(よく一緒に購入されている商品)
  • Best sellers(よく売れている商品)
  • Most viewed(よく閲覧されている商品)
  • Recommended for you(あなたへのオススメ)

いずれも実際に Amazon.com で利用されている内容のようで、実用的なものばかりですね。

推論(レコメンド)の実行

レコメンドの実行は REST API や、各種SDKが提供されています。たとえばPythonで呼び出す場合は以下のようになります。userIdが10のユーザに対して、5件のレコメンドを返す内容です。コメントアウトしていますが、フィルタを定義することで結果を絞って返すことも可能です。商品のカテゴリを絞ったり、特定の商品を除外したりする用途で利用できそうですね。

import boto3
client = boto3.client('personalize-runtime')
response = client.get_recommendations(
    userId='10',
    numResults=5,
#    filterArn='string',
#    filterValues={
#        'string': 'string'
#    },
    recommenderArn='作成されたレコメンダーのARN'
)

レスポンスは以下のように返ってきます。itemList というのが実際にレコメンドされた商品ですね。いとも簡単にレコメンダーが構築できました。

{
  "ResponseMetadata": {
    "RequestId": "22587508-868d-4683-b435-27fffe5db320",
    "HTTPStatusCode": 200,
    "HTTPHeaders": {
      "content-type": "application/json",
      "date": "Sat, 05 Mar 2022 10:33:53 GMT",
      "x-amzn-requestid": "22587508-868d-4683-b435-27fffe5db320",
      "content-length": "319",
      "connection": "keep-alive"
    },
    "RetryAttempts": 0
  },
  "itemList": [
    {
      "itemId": "210"
    },
    {
      "itemId": "582"
    },
    {
      "itemId": "535"
    },
    {
      "itemId": "93"
    },
    {
      "itemId": "30"
    }
  ],
  "recommendationId": "RID-d7fea433-9a85-45dd-868f-616e2f2d2870"
}

まとめ

Amazon Personalizeを利用することで、ECサイト向けのレコメンダーを構築する方法をご紹介しました。特にECサイトでの利用を想定して最適化されたモデルを、手軽にそのまま適用することができるのは大きな強みだと感じました。今回はデータ準備も含めて構築は1,2時間程度で済みました。全てゼロから実装したら検証も含め数週間~数ヶ月かかるのではないかと思います。

近頃のシステム開発の特徴として、時間をかけずに短期間で目に見える成果を求められるケースが多い印象があります。自前でゼロから構築すると時間やコストがかかるうえ、それに見合った効果が得られるかが見通しづらく、導入に踏み切るのは難しいと感じる方も多いのではないでしょうか。

ちなみに本記事では説明を割愛していますが、Amazon Personalizeは運用面でもかなり使いやすい設計になっています。レコメンドした後のユーザーの反応をフィードバックしてモデルを改善していくことも可能で、個人的にはそのメリットも非常に大きいと感じています。

精度の確認方法についてですが、従来の利用方法(レシピを自分で選択してSolutionと呼ばれるモデルを作る)では確認することができたのですが、ECサイト用のレコメンダーでは現状サポートされていないようです。現状ではレコメンド結果から自分で精度を算出する必要がありそうです。
#これまでも機能追加が頻繁におこなわれているので、そのうち実装されるのではないかと思っています。

また、料金体系は試される前に事前確認するようにしてください。(これまでの方式と異なりデータセット内のユニークユーザー数が料金に影響する点に注意です)
aws.amazon.com


Amazon.comでの実績をもとにECサイトに最適化されたレコメンダー。
是非導入を検討してみてはいかがでしょうか。


今回の記事は以上になります。最後までお読みいただきありがとうございました。


Acroquest Technologyでは、キャリア採用を行っています。

  • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
  • Elasticsearch等を使ったデータ収集/分析/可視化
  • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
  • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
Kaggle Grandmasterと話したいエンジニアWanted! - Acroquest Technology株式会社のデータサイエンティストの採用 - Wantedlywww.wantedly.com

AWS Lake Formationでデータレイク体験! #3 きめ細かな権限管理

機械学習アプリケーションエンジニアの@yktmです。

AWSで構築するデータレイクがテーマの本連載、今回は第3回目、最終回です。 前回は、AWS Lake Formationで構築したデータレイクとAmazon Athenaの連携を確認しました。今回は、Lake Formationの権限管理で、Athenaからのクエリにアクセスコントロールをかける部分を試していきます。

データレイクにはとにかくどんなデータも格納される、ということは誰がどのデータにアクセスできるか管理する必要がでてきます。 Lake Formationを使うと、データレイクにおけるアクセスコントロールを、要件に合わせ細かく設定することができます。

実際に権限管理を試してみると、確かに細かく権限管理できることが実感できました。他方、Lake Formationにおける権限管理の考え方が複雑なため、自分にとっては難しい部分もありました。 本記事では、実際に試してわかった、権限管理の具体的な設定方法、および権限管理の考え方をまとめてみたいと思います。

構成図

1. Lake Formationの権限管理

最初に、Lake Formationで、どのような権限管理ができるのか、簡単に見ていきます。

第1回の記事で書いたように、データレイクでは、事業・業務横断的にデータを管理するケースがあるかと思います。 データの中には、個人情報、業務上の秘匿情報など、自由にアクセスされたくないデータも混在していることも考えられます。 そのようなデータは、漏洩した際のリスクがあり、データレイク利用者すべてにアクセスを許可するのは非常にリスクが高くなります。

AWS Lake Formationは、データレイクに柔軟にきめ細かな「権限管理」を付与できることが特徴の一つです。

AWS Lake Formationは、

  • データベース、テーブルレベルのアクセス管理
  • 列、行、または行・列組み合わせによるセルレベルのアクセス管理

を提供しています。

例を通して、整理してみたいと思います。
「顧客マスタテーブル」と「販売実績テーブル」が存在し、セールスとアナリストがいる事業所を仮定します。

アナリストに、個々の顧客個人情報を見せないように、テーブルからデータを参照すること自体を禁止します。
また、販売時にサードパーティーの決済システムを利用していたとして、決済時に発行されるIDも必要ないでしょう。不要な列は、列レベルで参照を禁止します。
セールスには、担当地域の顧客情報は参照できても、それ以外の個人情報は参照させたくない。とすれば、担当販売地域のデータにのみアクセス可能にできます。

上記ケースのアクセスコントロール例を図示してみます。

アクセスコントロールの例

本記事では、テーブルレベルのアクセスコントロール、および列レベルのアクセスコントロールまでを扱っていきます。

2. 構築時のポイント

具体的な設定方法に移る前に、権限管理する際のポイントをまとめてみたいと思います。

ポイントは(1) IAM権限範囲(2) ペルソナ(3) 権限管理方式の3つあります。 それぞれ簡単に見ていきます。

(1) IAM権限範囲

Lake Formationは、データソースへのアクセスコントロールを行います。同様に、IAMもアクセスコントロールを実現するために利用されます。 したがって、それぞれ権限を細かく設定するか、粗く設定するか、組み合わせを考慮することが必要になります。

基本シナリオは、以下の2通りです。

方法 Lake Formation IAM
方法1(デフォルト) アクセスコントロールしない きめ細かにアクセスコントロールする
方法2(推奨) きめ細かにアクセスコントロールする 粗くアクセスコントロールする

このうち、AWSの推奨は「方法2」になります。 「方法1」は、IAM、S3バケットポリシーなど、複数のサービスにまたがり、複雑に権限管理する必要があります。その上、リソースの変更、追加時、全設定を見直す必要が出てきてしまいます。

ゆえに、「方法2」が推奨されています。しかし、Lake Formationのデフォルト設定は「方法1」になっています。
これは、AWS Glue データカタログの既存動作との互換性を保つためのようです。 したがって、Lake Formationの設定で、「方法2」に切り替える必要があります。 具体的な切り替え方法は後述します。

ここでの話は、以下のドキュメントにより詳細が書かれています。 docs.aws.amazon.com

(2) ペルソナ

どの属性のユーザに何のIAMポリシーを付与するか、その類型のことを、AWS Lake Formation ペルソナと呼びます。

例えば、データレイクの管理者や、データアナリスト、などです。 それぞれに、どの程度の権限を付与するか事前に設計することが重要になります。

以下、AWSが提案しているペルソナの例です。 docs.aws.amazon.com

(3) 権限管理方式

Lake Formationは2つの権限管理方式を提供しています。

名前付きリソースアクセスコントロールは、「ユーザAは、テーブルXにアクセス可能である」のように、ユーザごとに設定する権限管理方式です。
TBACは、「開発者タグを持つユーザは、テーブルXにアクセス可能である」のように、タグ付けによる権限管理方式です。

推奨されているのは、TBACです。理由は2つあります。

  • TBACでは管理する権限の数が、名前付きリソースメソッドよりも少なく済む
  • TBACでは、データソースに1つタグを付与し、各ユーザに対応するタグを付与すればよいです。
    つまり、「ユーザの数 + 管理対象データソース数」のみ管理するだけになります。
    他方、名前付きリソースアクセスコントロールでは、「ユーザの数 x 管理対象データソース数」となります。
  • TBACは、名前付きリソースメソッドよりもスケーラブル
  • リソースが増えた場合、名前付きリソースメソッドでは、全ユーザに対し新たに権限付与が必要になります。他方、TBACでは、リソースにタグを付与するだけで済みます。

    TBACに関するドキュメントは、以下になります。 docs.aws.amazon.com

    3. 権限管理してみる

    ここまで、権限管理に関する考え方を見てきました。ここからは、実際にLake Formationで権限管理していきたいと思います。

    まず、権限付与されていない状態でのクエリ実行を試します。権限管理されていない状態では、どのテーブルにもアクセスできない状態となります。

    次に、テーブルレベルのアクセスコントロールをかけていきます。この時、許可されたテーブルからデータを取得できるようになります。

    最後に、列レベルのアクセスコントロールを試していきたいと思います。許可された列のみ参照できるようにしていきます。

    権限付与されていない場合

    1回目の記事で作成した「データアナリスト」のIAMユーザでログインします。 Athenaコンソールを開き、salesテーブルからデータ取得するクエリを発行してみます。

    以下に添付するキャプチャは、クエリ発行の結果です。 ここから、①テーブルの候補が出てこないこと、②SELECT文の結果データを取得できていないことがわかります。

    権限付与されていないときのアクセスコントロール

    まずは、権限付与されていなければ、アクセスできないことがわかりました。

    権限管理用のタグ作成 (LF-Tag作成)

    「LF-Tag」とは、Lake Formationのタグベース権限管理で利用するタグのことです。 このタグをテーブルや列に付与することで、同じタグを持つユーザのみアクセスできる、といった具合にアクセスコントロールを実現することができます。

    タグベースでの権限管理方式については、権限管理方式で触れました。本記事では、LF-Tagを利用して、権限管理していきます。

    以下、roleというキーに対し、「leader」と「analyst」という2つの値をもつLF-Tagを作成していきます。

    「Add LF-Tag」を押下します。

    「key」と「value」に値を入れ、「Add LF-Tag」を押下します。

    LF-Tag作成後、一覧に作成したタグが表示されます。

    続いて、「データアナリスト」のIAMユーザに、「role=analyst」のタグを付与します。

    「Permissions」 > 「Data lake permissions」 > 「Grant」を選択し、権限付与画面に移動して以下のように設定します。

    テーブルレベルのアクセスコントロール

    続いて、許可されたテーブルにのみアクセスできることを確認していきます。 まず、「データレイク管理者」のIAMユーザで、Lake Formationコンソールを開きます。

    データベースへのアクセス権限付与

    少なくともデータベースに対してDESCRIBEを実行できなければ、テーブルの一覧も取得できません。したがって最初に、データベース自体へのアクセス権限を付与します。

    「Permissions」 > 「Data lake permissions」 > 「Grant」を選択し、権限付与画面に移動して以下のように設定します。

    この段階で、データアナリストはAthenaからデータベースを参照できるようになり、テーブルは参照できない状態になります。

    テーブルにLF-Tagを付与

    次に、roleがanalystのユーザに許可するテーブルを設定します。 「Data catalog」>「Table」から、アクセス許可するテーブルを選択し、「Actions」>「Edit LF-tags」からLF-tags編集画面に移動します。

    「role」タグの値を「analyst」に設定します。

    Athenaから権限設定結果を確認

    タグの設定が完了すると、データアナリストはAthenaからsalesテーブルを参照できるようになります。SELECT文を実行した結果が以下です。

    列レベルのアクセスコントロール

    ここまで、テーブルレベルでアクセスコントロールできることは確認できました。 次に、より細かなコントロールの例として、例レベルのアクセスコントロールをかけていきたいと思います。

    テーブルレベルのアクセスコントロールと同様に、LF-Tagを使って、列レベルのアクセスコントロールを設定していきます。

    テーブルの権限を「leader」に変更。

    「Tables」 > 「テーブル名」 > 「Edit schema」を選択します。

    アクセス許可する列に「analyst」のタグを付与

    analystが参照可能とする列を選択し、「Edit tags」を選択します。

    roleをanalystに変更し、「Save」します。

    Athenaから権限設定結果を確認

    列単位での設定は以上になります。データアナリストIAMユーザでログインし、Athenaコンソールを開くと、指定した列のみが参照できるようになっています。 SELECT文でも、許可された列のみが参照できるようになっています。

    5. 最後に

    AWS Lake Formationを試してみて、LF-Tagを使った設定はかなり簡単で、柔軟に設定できるなと感じました。

    ただ、気を付けておきたい点として、権限を付与、制限する要素それぞれの、「役割・作用」の理解が重要なのだと思いました。

    データレイクにおいて、過剰なアクセスコントロールは使い勝手を著しく損ない、「データスワンプ」になってしまうと言われています。 過剰な権限管理を避けながらも、スケーラブルかつ、安全にアクセスコントロールを設計するのは、エンジニアとしての力量が試されるな、と感じます。

    ここまで全三回に渡りLake Formationを使ったデータレイク構築を試してみた感想として、 Lake Formationは難しい部分はあれど、Lake Formationなしで同じことはしたくない、と感じました。

    ユーザごとの権限管理にせよ、テーブル・列レベルのアクセスコントロールにせよ、Lake Formationなしで構築することは技術的には可能と思います。 しかし、Lake Formationを使う方が簡単にかつ一元的に管理できるのは間違いなく、 Lake Formationの謳い文句である、「安全なデータレイクを数日で簡単にセットアップできる」も、その通りだった、と感じました。

    本連載では、Lake Formationの基本的な使い方と、権限管理に注目してきましたが、AWS Lake Formationには魅力的な機能がまだまだ備わっています。 例えば、行、セルレベルの権限管理、Governed Tableが提供するACID トランザクションのサポートなどです。

    今後、AWS Lake Formationの他の機能も試していければと思います。

    Acroquest Technologyでは、キャリア採用を行っています。
    • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
    • Elasticsearch等を使ったデータ収集/分析/可視化
    • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
    • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
      少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。 www.wantedly.com

    AWS Lake Formationでデータレイク体験! #2 Athenaで簡単データ連携

    機械学習アプリケーションエンジニアの@yktmです。

    AWSで構築するデータレイクがテーマの連載、今回は第2回目です。 前回は、AWS Lake Formationの概要と基本となるデータレイクの構築を確認しました。今回は、Amazon Athenaを利用し、前回構築したデータレイクから実際にクエリでデータを取得する部分を試していきます。

    AthenaはS3からSQLで簡単にデータを取得できることが特徴のサービスですが、実際使うと本当に手軽に扱うことができました。 その一方、気を付けておきたいポイントも分かったので、その点を踏まえ、まとめていきたいと思います。

    本記事で扱うのは、赤枠部分です。

    構成図

    1. Athenaとは

    まずはAthenaについて、その概要を見ていきます。

    Athenaは、SQLで、S3内のデータを集計・分析できるサービスです。

    Athenaの特徴は、主に以下4点です。

  • サーバレスで、インフラの管理、設定は不要
  • ソフトウェアの更新や、インフラのスケーリングは自動で行われるため、考える必要はないです。純粋にデータをどう取得、加工するかに集中できます。
  • ハイパフォーマンス
  • 高速にS3からデータを取得、操作できるようにパフォーマンスが最適化されています。クエリの並列実行も自動で行われるため、大規模なデータも、クエリ結果が短時間で取得できます。
  • AWSサービスとの統合、連携
  • Athenaは、AWS Glueと統合されており、Glueのクロール機能やETL機能と組み合わせることができます。また、フェデレーティッド SQLを利用すれば、DynamoDB、Redshift、CloudWatchなど複数のデータソースに対し分析することができます。
  • 機械学習の利用
  • Athena SQL クエリで SageMaker 機械学習モデルを呼び出すことができます。機械学習の推論により、異常検知や予測などの分析が可能になります。

    2. 利用方法

    ここからは、実際にAthenaを利用して、前回構築したデータレイクからデータを取得していきます。 前回の記事で作成した、「データレイク管理者」のIAMユーザで操作していきます。

    (1) クエリ結果保存用バケット作成

    Athenaからクエリを発行すると、その結果は自動的にS3に保存されます。 まずは、クエリ結果保存用のバケットをS3に作成しておきます。

    次にAthenaコンソールを開き、「設定」 > 「管理」から、「クエリ結果の設定」で、クエリ結果の保存先バケットを指定します。

    (2) SELECT文でデータ取得する。

    Athenaのクエリエディターを開くと、前回の記事で作成したテーブルの一覧が表示されていることが確認できます。

    なお、今回は前回の記事でGlueで作成したテーブルにクエリを発行しています。S3にcsvファイルを配置しただけでは、Athenaから参照できないのでご注意ください。

    続いて、SELECT文でsalesテーブルのデータを取得してみます。

    SELECT * FROM "sales" WHERE shop_id = 25 LIMIT 10; をクエリとして入力し、「実行」を押すとクエリ結果が表示されます。

    LIKE句なども、もちろん利用可能です。

    2014年のデータのみに絞り込む場合、SELECT * FROM "sales" WHERE date LIKE '%.2014' LIMIT 10;というクエリで取得できます。

    以下のドキュメントで、クエリに関して詳細に説明されています。

    docs.aws.amazon.com

    3. Athenaを利用する上での注意点

    Athenaは、簡単にS3上のデータを分析することができますが、他方、意図しない超過課金が発生するリスクもあります。データを大量に読み込ませないために、最低限気を付けておきたいポイントを簡単にまとめていきます。

    ワークグループによる制限

    Athenaには、「ワークグループ」という機能があります。

    このワークグループを利用することで、クエリ実行時のスキャンの最大量を設定できます。 スキャン最大量を超えるクエリが実行された場合、クエリはキャンセルされます。

    検証時など、意図しない大量データスキャンを避けるためにも、この設定をしておくことで安心できると思います。 ワークグループ作成時、もしくはワークグループの編集画面より、制限を設定できます。

    ワークグループは、スキャンの最大量制限のほかにも、

    • クエリを実行できるユーザーを管理
    • ワークグループ毎のメトリクス取得
    • ワークグループ毎のコスト管理

    にも利用できます。

    読み込みカラムの指定

    SELECT文でクエリを発行する際、読み込み対象のカラムを明示的に指定することが推奨されています。 特に、大量のカラムがあるテーブルや、文字列ベースのカラムが多い場合に有効なアプローチです。

    Parquet または ORC を利用する。

    AthenaはCSV、TSV、JSONなどのデータ形式のほか、Apache ParquetやLogstash、CloudTrailなどの形式をサポートしています。(サポートしているデータ形式)

    この中でも、Parquet・ORCを利用することが、よいとされています。 Parquetは、Hadoopで利用される列指向のデータ形式です。ORCは、Hiveで利用される列指向のデータ形式です。
    これらのデータ形式を利用するメリットは、2つあります。
    一つは、parquet形式にすることで、単純にデータ量を圧縮できることが挙げられます。 一例として、1TBのCSVファイルも、parquet形式にすることで、130 GBまで削減することができるとされています。

    databricks.com

    データ量を削減できることで、S3の利用コストを削減することができます。

    二つ目の理由として、列指向形式に変換することで、クエリ実行時のスキャンデータ量の削減が期待できる点が挙げられます。 列指向形式のデータにすることで、必要な列のみが選択的に読み込まれ、不要なデータのスキャンを避けることができるため、 コスト削減、パフォーマンス向上に寄与します。

    参考資料

    本記事では、よく使いそうなものに絞って、ベストプラクティス、チューニング方法を挙げてみました。 より詳しい最適化、チューニング方法は、以下のAWSが出している資料に載っています。

    aws.amazon.com

    www.slideshare.net

    4. コスト

    Athenaで発生する料金は「クエリ」に対して発生します。

    クエリ実行時のスキャンデータ量 1TBにつき、5USDが発生します。(リージョンによって、5USD以上の料金になります。)

    上記に加え、S3、Glue、Lambda、Sage Makerなど他サービスを利用する場合、それぞれの料金体系に応じた料金が発生します。 特にS3については、Athenaクエリ結果をS3に保存するため、転送量、リージョンには気を付けておきたい所かと思います。

    5. まとめ

    Athenaを利用するのは初めてでしたが、簡単にSQLでS3のデータを取得、分析できる点は便利だと感じました。

    データ分析をするとき、pythonでPandasを利用することが多いと思います。しかし、Pandasはパッケージとしてのサイズが大きく、サーバレス案件でAWS Lambda xPandasの組み合わせを採用すると、Lambdaのデプロイパッケージに関するサービスクォーターに達しないように意識する必要が出てきます。

    Lambda Layerに載せきれないといった時の代替手段として、Athenaを利用する手もあるんじゃないかなと思いました。 また、数百MB ~ 数GB程度の小~中規模程度のデータの検証、分析であれば、AWSコンソール上で手軽にクエリを実行できる点も魅力だと思います。

    他方、数十GB~数TB級の大規模データを分析する際は、綿密にチューニングしないと、コストとパフォーマンスの面で負担がかかってしまう点は注意が必要だと思いました。

    本記事では、Athena を使って、Lake Formationで構築したデータレイクからデータを取得するところまでを試してみました。 次の記事で、Lake Formationの権限管理で、Athenaからのクエリをアクセスコントロールしていきます。

    Acroquest Technologyでは、キャリア採用を行っています。
    • ディープラーニング等を使った自然言語/画像/音声/動画解析の研究開発
    • Elasticsearch等を使ったデータ収集/分析/可視化
    • マイクロサービス、DevOps、最新のOSSを利用する開発プロジェクト
    • 書籍・雑誌等の執筆や、社内外での技術の発信・共有によるエンジニアとしての成長
      少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。 www.wantedly.com