CSVから始める製造業データ分析:S3 Tablesで差分更新し、Bedrock Knowledge Baseから自然言語でクエリしてみた

CSVから始める製造業データ分析:S3 Tablesで差分更新し、Bedrock Knowledge Baseから自然言語でクエリしてみた

CSVをS3 tablesに溜め込んで、Bedrock Knowledge Baseからアクセスする方法をまとめました。権限周りが非常にややこしいです。
2025.12.24

製造ビジネステクノロジー部コネクテッドカーチーム、サーバーサイドエンジニアの木村幸介です。

当エントリは クラスメソッド発 製造業 Advent Calendar 2025の24日目のエントリです。

はじめに

製造業の現場データ活用は、理想の姿がさまざまある一方で、いきなり完璧なデータ基盤を作るのは大変です。
一方で、Excelや各種システムから「まずはCSVで出せる」ことは多いので、そこを起点にして段階的に進められないか、というのが今回の話です。

このポストでは、CSV → Athena → S3 Tables(Iceberg) → Amazon Bedrock Knowledge Base(以下、KB。自然言語→SQL)までを最小構成で扱います。

細かい手順やコマンドは サンプルリポジトリREADME.md にまとめてあるのでそちらをご覧ください。

なお、サンプルとして製造業のQC(品質管理)検査データを題材にしたデータを用意していますが、生成AIが生成した架空のものであり、実在の団体・物品とは関係ありません。

全体像

まずは全体像です。

構成図

流れは次のとおりです。

  1. CSVをS3に置く
  2. AthenaでCSVを外部テーブルとして読めるようにする
  3. AthenaでS3 Tables(Icebergテーブル)へMERGEして、状態更新型のデータとして扱えるようにする
  4. KBから自然言語で問い合わせる(内部ではSQLを組み立てて実行する)

今回の工夫ポイントを補足すると、KB(SQL Knowledge Base)からS3 Tables上のテーブルを参照させる際、Glue Data Catalog側に S3 Tablesカタログを指すResource Link(リソースリンクDB) を作り、KBにはそのResource Link配下のテーブル(例: awsdatacatalog.sample_rl_<stack-name>.<table>)を見せる形にしています。ここが分かると一気に進めやすくなります(本リポジトリではCDKで sample_rl_<stack-name> を作っています)。

なぜこの形にしたか

CSVから始める理由

Excelを含む色々な媒体がCSVエクスポートに対応していて、現場の運用を大きく変えずに始めやすいからです。
「まずはデータを集める」段階ではCSV出力までは詰まりにくいという想定です。

S3 Tablesを使う理由

今回の題材はQC検査データで、日々増えるだけでなく、後から値が補正されたり、マスタの内容が更新されたりします。
IoTの温湿度・圧力のような時系列データは、更新はせずに追記していくパターンが多いと思います。一方で「最新の状態」を見たい場合、追記型のままだと毎回どこかで集計・集約して断面を作る必要が出てきます。
人が見るのが前提のデータだと、そもそも追記型で整っていないことも多いので、最新状態をきちんと持つ側に寄せたいこともあると思います。

今回は、S3 Tables上のIcebergテーブルに対して MERGE で冪等に流し込みできるようにしてあります(同じSQLを何度実行しても同じ状態になる想定)。

KBを使う理由

データを扱う人が全員SQLを書けるとは限りません。
自然言語で「これを知りたい」を投げて、必要に応じてSQLを生成して問い合わせできる入口があると、データ分析のハードルを下げられます。

やってみた

ここからは手順を進めるパートです。詳細は README.md に寄せつつ、つまずきやすいポイントだけ取り上げます。

CDKでデプロイ

まずは依存関係を入れて、CDKをデプロイします。

pnpm install
pnpm cdk deploy

デプロイ後はCloudFormation Outputsに出る値(CSVバケット名、S3 TablesバケットARN、Athena Workgroup名、KB用ロールARNなど)をメモしておきます。

このプロジェクトでは、CDKはS3 Tablesの基盤(TableBucketとNamespace)までを用意し、テーブル作成やスキーマはAthena側のSQLで管理する方針です。

サンプルCSVをアップロード

サンプルCSVをCSVバケットへ置きます。

aws s3 cp sample_csv/ s3://{CsvBucketName}/sample_csv/ --recursive

AthenaでCSVを参照する(外部テーブル作成)

Athenaで athena-queries/01-create-external-tables.sql を実行して、CSVを外部テーブルとして参照できるようにします。
このとき、Workgroupとデータカタログの選択を誤ると、参照先が意図したものにならないため注意が必要です。

Athenaの設定(CSVを拾う)

AthenaでS3 Tablesへ流し込む(スキーマ作成とMERGE)

次に athena-queries/00-initialize-s3-tables-schema.sql を初回のみ実行して、S3 Tables上のIcebergテーブルにスキーマを定義します。
続いて athena-queries/02-merge-to-core-layer.sql を実行して、CSV側のデータをS3 Tablesへ MERGE します。

ここも、Workgroupとデータカタログの選択を誤ると、意図しない参照先になりやすいです。

Athenaの設定(S3 TablesにMERGEする)

権限周り(Lake Formation / Redshift)

Lake FormationやRedshiftに関連した権限周りは、普段このあたりのリソースを触らない筆者にとって、つまずきやすい点でした。

まずLake Formationでの権限付与が必要です。

aws lakeformation grant-permissions \
  --principal "DataLakePrincipalIdentifier=<BedrockKnowledgeBaseRoleArn>" \
  --resource '{"Database":{"CatalogId":"<your-account-id>","Name":"sample_rl_<stack-name>"}}' \
  --permissions "DESCRIBE"
aws lakeformation grant-permissions \
  --principal "DataLakePrincipalIdentifier=<BedrockKnowledgeBaseRoleArn>" \
  --resource '{"Table":{"CatalogId":"<your-account-id>:s3tablescatalog/<table-bucket-name>","DatabaseName":"qc_data","TableWildcard":{}}}' \
  --permissions "SELECT" "DESCRIBE"

特に前者は、KBのサービスロールがResource Link(GlueのリソースリンクDB)を参照できるようにする設定で、見落としやすいポイントです。Resource LinkのデータベースにDESCRIBEを付与します。加えて、S3 Tables側のテーブルに対しても、同じロールにSELECTとDESCRIBEを付与します。

加えて、Query engineとして使っているRedshift Serverless側でも、IAMR:{KBサービスロール} がクエリできるように権限を付与する必要があります。

aws redshift-data execute-statement \
  --workgroup-name <RedshiftWorkgroupName> \
  --database dev \
  --secret-arn <RedshiftAdminSecretArn> \
  --sql 'GRANT USAGE ON DATABASE awsdatacatalog TO "IAMR:<BedrockKnowledgeBaseRoleName>";'

併せてREADME.md の「Lake Formationで権限を付与」「Redshift内でGRANT実行」もご確認ください。

KBの同期

BedrockコンソールでKBを開き、Query engine(Redshift Serverless)の設定を確認してから同期します。

KBの同期ボタン

同期が通ったら、自然言語で問い合わせできる状態になっています。

トラブルシュート

同期やテストで、エラーメッセージに AccessDenied / not authorized / is not authorized to perform のような文言が出る場合は、まずKBのサービスロール(Bedrockが引き受けるロール)の信頼ポリシーとIAMポリシーを見直します。ここが崩れていると、後段の設定が正しくても通りません。

database is empty / no databases found のように「中身が空」を示す文言が出る場合は、KBから見えるカタログが空扱いになっている状態を疑います。Lake FormationでResource LinkにDESCRIBE、S3 TablesのテーブルにSELECT/DESCRIBEが付いているか、加えてRedshift側で IAMR:{KBサービスロール} に必要な GRANT が入っているかを確認します。

自然言語で聞いてみる

「trxの件数を教えて」のように大まかに聞いても、SQLを組み立てて結果が返ります。
さらに、KB側の「説明」や「内包・除外」の設定を調整していくと、用語や言い回しの揺れ(たとえば略称や言い換え)にも対応しやすくなります。

自然言語でクエリできた例

片付け

試し終わったら削除します。

pnpm cdk destroy

おわりに

Glue / Lake Formation / S3 Tables あたりのデータ系スタックは、カタログ管理と権限管理が複数の層で成り立っており、IAMだけ整えても解決しないことがよくあります。
加えて、マネジメントコンソール上だけだと見通しが立てづらかったり、そもそもコンソールから触れない設定が混ざっていたりして、「気軽に試したい」局面でつまずきやすいです。この記事が、同じところで立ち止まったときの手がかりになれば嬉しいです。

この記事をシェアする

FacebookHatena blogX

関連記事