1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

S3Tables を触ってみる

Last updated at Posted at 2025-12-04

やりたいこと

AWS から S3Tables が発表されてしばらく経ちましたが、社内のプロジェクトでデータ基盤を構築することになったのでせっかくなら使ってみるかと思い、簡単に触ってみたので記事にしてみます。

S3 Tables とは

Amazon S3 Tables は、S3 上で分析向けに最適化されたテーブル形式ストレージを提供する新サービス。従来の「ファイル管理中心の S3」とは異なり、データを「テーブル」として管理できるのが最大の特徴。
また、OTF (Open Table Format) である Iceberg や Deltalake などを使えるのもメリット。2025年はじめに東京リージョンでも一般提供が開始されている。

今回やってみること

やることはすごくシンプル。Raw テーブルのデータ (CSV) を Bronze テーブルにロードする。
Raw は Glue DataCatalog Table、Bronze は S3Tables です。

準備

サンプルデータ

サンプルデータとして以下リンク先の Titanic Passengers を使ってみる。

View をクリックして、右上の Export から CSV でダウンロードする。
ファイル名が converted-file.csv になっていて分かりづらいので passengers.csv に変更しておく。

リソース作成

S3 などのリソースは Terraform で作成していく。

main.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~>6.24.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

locals {
  suffix     = "s3tables-sample"
  table_name = "titanic_passengers"
}

resource "aws_s3_bucket" "sample_data" {
  bucket = "sample-data-${local.suffix}"
}

resource "aws_s3_object" "sample_data" {
  bucket = aws_s3_bucket.sample_data.id
  key    = "data/passengers.csv"
  source = "../passengers.csv"  # DL した CSV のパス
}

resource "aws_glue_catalog_database" "raw" {
  name = "raw"
}

resource "aws_glue_catalog_table" "titanic_passengers_raw" {
  database_name = aws_glue_catalog_database.raw.name
  name          = local.table_name

  table_type = "EXTERNAL_TABLE"

  parameters = {
    "EXTERNAL"                  = "TRUE"
    "skip.header.line.count"    = "1"
    "use.null.for.invalid.data" = "true"
  }

  storage_descriptor {
    location      = "s3://${aws_s3_bucket.sample_data.bucket}/data/"
    input_format  = "org.apache.hadoop.mapred.TextInputFormat"
    output_format = "org.apache.hadoop.hive.ql.io.HiveIgnoreKeyTextOutputFormat"

    ser_de_info {
      name                  = "OpenCSVSerDe"
      serialization_library = "org.apache.hadoop.hive.serde2.OpenCSVSerde"

      parameters = {
        "separatorChar" = ","
        "quoteChar"     = "\""
        "escapeChar"    = "\\"
      }
    }

    columns {
      name = "passengerid"
      type = "bigint"
    }
    columns {
      name = "survived"
      type = "int"
    }
    columns {
      name = "pclass"
      type = "int"
    }
    columns {
      name = "name"
      type = "string"
    }
    columns {
      name = "sex"
      type = "string"
    }
    columns {
      name = "age"
      type = "double"
    }
    columns {
      name = "sibsp"
      type = "int"
    }
    columns {
      name = "parch"
      type = "int"
    }
    columns {
      name = "ticket"
      type = "string"
    }
    columns {
      name = "fare"
      type = "double"
    }
    columns {
      name = "cabin"
      type = "string"
    }
    columns {
      name = "embarked"
      type = "string"
    }
  }
}

resource "aws_s3tables_table_bucket" "example" {
  name = "example-bucket-${local.suffix}"
}

resource "aws_s3tables_namespace" "layers" {
  for_each         = toset(["bronze"])
  namespace        = each.value
  table_bucket_arn = aws_s3tables_table_bucket.example.arn
}

resource "aws_s3tables_table" "titanic_passengers_bronze" {
  name             = "titanic_passengers"
  namespace        = aws_s3tables_namespace.layers["bronze"].namespace
  table_bucket_arn = aws_s3tables_table_bucket.example.arn
  format           = "ICEBERG"

  metadata {
    iceberg {
      schema {
        field {
          name     = "passenger_id"
          type     = "long"
          required = true
        }
        field {
          name     = "survived"
          type     = "int"
          required = false
        }
        field {
          name     = "pclass"
          type     = "int"
          required = false
        }
        field {
          name     = "name"
          type     = "string"
          required = false
        }
        field {
          name     = "sex"
          type     = "string"
          required = false
        }
        field {
          name     = "age"
          type     = "double"
          required = false
        }
        field {
          name     = "sibsp"
          type     = "int"
          required = false
        }
        field {
          name     = "parch"
          type     = "int"
          required = false
        }
        field {
          name     = "ticket"
          type     = "string"
          required = false
        }
        field {
          name     = "fare"
          type     = "double"
          required = false
        }
        field {
          name     = "cabin"
          type     = "string"
          required = false
        }
        field {
          name     = "embarked"
          type     = "string"
          required = false
        }
        field {
          name     = "ingest_date"
          type     = "string"
          required = true
        }
      }
    }
  }
}

resource "aws_athena_named_query" "load_to_s3tables" {
  name      = "Load Titanic Passengers to S3Tables"
  workgroup = "primary"
  database  = aws_glue_catalog_database.raw.name
  query     = <<EOF
INSERT INTO "bronze"."titanic_passengers"
SELECT
    passengerid,
    survived,
    pclass,
    name,
    sex,
    age,
    sibsp,
    parch,
    ticket,
    fare,
    cabin,
    embarked,
    date_format(current_timestamp AT TIME ZONE 'Asia/Tokyo', '%Y-%m-%d %H:%i:%s.%f') AS ingest_date
FROM "AwsDataCatalog"."raw"."titanic_passengers"

EOF
}

本当は LakeFormation の設定も Terraform でやりたかったけど未対応みたい。
https://github.com/hashicorp/terraform-provider-aws/pull/43931

sample.tf
    resource "aws_lakeformation_permissions" "athena_raw_database" {
      principal = "arn:aws:iam::{account_id}:user/{user_principal_name}"
      permissions = [
        "ALL",
      ]
      database {
        catalog_id = "{account_id}:s3tablescatalog/${aws_s3tables_table_bucket.example.name}"
        name       = aws_glue_catalog_database.raw.name
      }
    }

terraform apply でリソースを作成すると、各リソースが作成されているはず。

データ処理

Athena にアクセスして、まずは Raw テーブルのデータを確認してみる。

データソース: AwsDataCatalog
カタログ: なし
データベース: raw
を選択すると Terraform で作成しておいたテーブルが表示されているはず。

メニュー (⋮) からテーブルをプレビューしてみる。CSV データがロードされている状態。

image.png

Bronze テーブルにデータをロードしてみる。
上部のタブから「保存したクエリ」を選択すると、Terraform で作成しておいたクエリがあるのでクリック。エディタにクエリが読み込まれる。

image.png

データソース: AwsDataCatalog
カタログ: s3tablescatalog/example-bucket-s3tables-sample
データベース: bronze
を選択して「実行」をクリック。

この時点ではエラーになる。理由は対象のテーブルへのアクセス権がないことなので、LakeFormation でアクセス権の設定をしていく。

アクセス設定

本来は Terraform で権限設定もしたかったが、未対応とのことなので手動で行う。

LakeFormation にアクセスし、「Data Permissions」をクリック。
右上の「Grant」をクリックして設定する。

  • Principals で自身の IAM ユーザを選択する
  • LF-Tags or catalog resources では以下を選択する
    • Named Data Catalog resources を選択
    • Catalogs: {account_id}/s3tablescatalog/example-bucket-s3tables-sample
    • Databases: bronze
    • Tables: titanic_passengers
  • Table permissions では以下を選択
    • Table permissions: Super

元の画面に戻り、bronze へのアクセス設定が1行増えているはず。

再度データ処理

改めて先ほどのクエリを実行してみると、今回は成功するはず。

raw と同様にテーブルをプレビューしてみる。raw のデータが時刻付きでロードされている:tada::tada::tada:

image.png

ということで

非常に簡単ですが S3Tables を使って ETL 的なことをしてみました。
元々はもっと早く記事にする予定だったのですが、業務に追われているうちにアドベントカレンダーの時期になり Iceberg のテーマとして公開しました。
内容的に Iceberg らしさみたいなところはまったくないのですが AWS で S3Tables を使う人の参考になれば嬉しいです!

以上です。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?