AWS Glue 5.0に移行して6ヶ月、コストと落とし穴の現実

「Glue、そろそろ限界かも」と感じていたら読んでほしい。Spark 3.5・Ray統合・Flex実行モードで実際に何が変わったか、踏んだ罠も含めてチームの本音を書きました。

AWS Glue 5.0でデータパイプラインを刷新して気づいた、移行の現実と本当の恩恵

去年の秋ごろ、うちのチームで長年使っていたGlue 4.0ベースのパイプラインがじわじわと限界を迎えはじめた。特に問題だったのは、日次で動かしている大規模なS3→Redshift間のETLが、データ量の増加に比例してコストが跳ね上がっていたこと。当時は「Glue自体の限界では?」「EMR Serverlessに移行すべきでは?」という議論もチームで出てたんだけど、ちょうどGlue 5.0がGAになったタイミングで一度試してみることにした。

結論から言うと、移行は思ったより順調だったし、コスト面・パフォーマンス面どちらも改善できた。ただ、ハマったポイントも当然あって、特にFlexible実行モードとRay統合まわりは最初ちょっと苦労した。その実体験をそのまま書いていく。


Glue 5.0で何が変わったか、実際に使って感じたこと

Glue 5.0のリリースノートを読んだとき、正直「アップデートっぽい変更が多いな」という印象だった。ところが実際に動かしてみると、地味だけど効いてくる変更がいくつかあった。

主要な変更点をまとめるとこうなる:

項目Glue 4.0Glue 5.0
Sparkバージョン3.33.5
Pythonバージョン3.103.11
Rayサポートなしネイティブ統合
G.1Xワーカー4vCPU4vCPU
G.2Xワーカー8vCPU8vCPU
G.4Xワーカー16vCPU16vCPU
G.8Xワーカーなし32vCPU(新設)
Flex実行制限ありより安定
カタログ統合Glue Data CatalogGlue Data Catalog + Lake Formation強化

一番効いたのはSpark 3.5への対応だ。Adaptive Query Execution(AQE)の改善が積み重なっていて、特にjoinのスキュー処理が全然違う。うちのパイプラインでは一部のテーブルが特定のキーに偏るデータ分布をしていて、Glue 4.0時代はexecutorの偏りによるタスク遅延が慢性的な問題だった。Glue 5.0 + Spark 3.5に移行したあと、この問題がかなり自然に解消されたのはマジで助かった。

Spark周りの最適化についてはApache Spark 2026年最新動向の記事でも詳しく書いているので、AQEの詳細はそちらも参照してほしい。


実際のパイプライン構成とAWS構成図

うちのチームで運用しているパイプラインの構成はこんな感じ。S3に積まれたRAWデータを変換して、Redshiftのデータウェアハウスに流し込む、わりとオーソドックスなレイクハウス構成だ。

graph TB
    subgraph Sources["データソース"]
        APP["アプリケーションDB\n(Aurora PostgreSQL)"]
        LOG["アプリログ\n(Kinesis Firehose)"]
        EXT["外部API\nWebhook/連携"]
    end

    subgraph S3Layers["S3 データレイク"]
        subgraph Raw["Raw Layer"]
            S3R[("s3://raw-bucket")]
        end
        subgraph Processed["Processed Layer"]
            S3P[("s3://processed-bucket\nIceberg形式")]
        end
        subgraph Curated["Curated Layer"]
            S3C[("s3://curated-bucket")]
        end
    end

    subgraph GlueLayer["AWS Glue 5.0 Processing"]
        GC["Glue Data Catalog\n+ Lake Formation"]
        subgraph Jobs["Glue Jobs"]
            J1["Ingestion Job\nG.1X × 10workers"]
            J2["Transform Job\nG.4X × 5workers"]
            J3["Aggregation Job\nRay Runtime"]
        end
        GW["Glue Workflows\n+ Triggers"]
        EB["EventBridge\nScheduler"]
    end

    subgraph Serving["Serving Layer"]
        RS[("Redshift Serverless")]
        QS["QuickSight"]
        API["内部API\n(Lambda + RDS)"]        
    end

    subgraph Monitoring["モニタリング"]
        CW["CloudWatch\nLogs + Metrics"]
        SNS["SNS\nアラート通知"]
    end

    APP -->|Aurora Zero-ETL| RS
    APP -->|DMS CDC| S3R
    LOG --> S3R
    EXT --> S3R

    S3R --> J1
    J1 --> S3P
    S3P --> J2
    J2 --> S3C
    S3C --> J3
    J3 --> RS

    GC -.->|メタデータ管理| J1
    GC -.->|メタデータ管理| J2
    GW --> J1
    GW --> J2
    GW --> J3
    EB --> GW

    RS --> QS
    RS --> API

    J1 -.->|ログ| CW
    J2 -.->|ログ| CW
    J3 -.->|ログ| CW
    CW --> SNS

この構成のポイントは、IcebergテーブルフォーマットをProcessed Layerに採用していること。Glue 5.0はIceberg v2のサポートが強化されていて、タイムトラベルクエリやスキーマ進化が格段に使いやすくなっている。データ品質管理の観点で言えばデータ品質管理2026年版の記事でも触れているような「いつのデータに問題があったか」を追いかける作業が劇的に楽になった。個人的に、これは地味に一番うれしい変更だったかもしれない。


移行でハマったポイントと解決策

ここが正直に書きたいところ。Glue 5.0は「バージョンアップすれば動く」とは言えない部分もある。

ライブラリの互換性問題

Glue 4.0からの移行で一番最初にハマったのは、カスタムライブラリのPython 3.10→3.11互換性問題だった。うちのパイプラインでは社内製の型変換ユーティリティをwheelファイルでGlueジョブに配布していたんだけど、3.11でbehaviorが微妙に変わっている箇所があってサイレントに値が変わっていた。テストを通過したのに本番で数値がずれてることに気づいたときは、正直かなり焦った。

# Glue 5.0 での設定例(--additional-python-modules を使う場合)
import sys
from awsglue.transforms import *
from awsglue.utils import getResolvedOptions
from pyspark.context import SparkContext
from awsglue.context import GlueContext
from awsglue.job import Job
from awsglue.dynamicframe import DynamicFrame

args = getResolvedOptions(sys.argv, [
    'JOB_NAME',
    'source_bucket',
    'target_bucket',
    'database_name',
    'table_name'
])

sc = SparkContext()
glueContext = GlueContext(sc)
spark = glueContext.spark_session
job = Job(glueContext)
job.init(args['JOB_NAME'], args)

# Iceberg設定 (Glue 5.0 では catalog_id を明示する)
spark.conf.set("spark.sql.extensions", 
    "org.apache.iceberg.spark.extensions.IcebergSparkSessionExtensions")
spark.conf.set("spark.sql.catalog.glue_catalog", 
    "org.apache.iceberg.aws.glue.GlueCatalog")
spark.conf.set("spark.sql.catalog.glue_catalog.warehouse", 
    f"s3://{args['target_bucket']}/warehouse")

# AQE設定 (Spark 3.5 で効果大)
spark.conf.set("spark.sql.adaptive.enabled", "true")
spark.conf.set("spark.sql.adaptive.coalescePartitions.enabled", "true")
spark.conf.set("spark.sql.adaptive.skewJoin.enabled", "true")
spark.conf.set("spark.sql.adaptive.skewJoin.skewedPartitionThresholdInBytes", "256MB")

# Glue Data Catalogからソース読み込み
datasource = glueContext.create_dynamic_frame.from_catalog(
    database=args['database_name'],
    table_name=args['table_name'],
    transformation_ctx="datasource"
)

df = datasource.toDF()

# 変換処理
df_transformed = df.filter(df['event_date'] >= '2025-01-01') \
    .withColumn('processed_at', 
        spark.sql("SELECT current_timestamp()").collect()[0][0]) \
    .dropDuplicates(['user_id', 'event_date', 'event_type'])

# Icebergテーブルへの書き込み
df_transformed.writeTo(
    f"glue_catalog.{args['database_name']}.{args['table_name']}_processed"
).option("write.target-file-size-bytes", str(128 * 1024 * 1024)) \
 .tableProperty("write.merge.mode", "merge-on-read") \
 .overwritePartitions()

job.commit()

このコードで重要なのは、AQEの設定を明示的に入れているところ。Glue 5.0でもデフォルト設定のままだと恩恵を受けにくいケースがあった。skewedPartitionThresholdInBytesは環境によって調整が必要だけど、うちは256MBにしたらスキューが大幅に改善した。

Flex実行モードの落とし穴

コスト削減目的でFlex実行モードを使ったら、ジョブの起動レイテンシが最大15分程度になることがあった。バッチ処理なら許容範囲だけど、SLA的に「データが揃ってから30分以内に集計完了」という要件があるパイプラインには使えない。これは最初の設計で考慮していなかったので、わりと痛い目を見た。

どのジョブにFlex実行を使うかは、以下の基準で判断するようにした:

判定対象ジョブの特徴具体例
✅ Flex OKSLAがゆるい・後続の待機なしデータレイクの定期コンパクション、月次集計、バックフィル処理
❌ Flex NGSLA 30分以内・後続ジョブが待機時間単位の準リアルタイム集計、依存関係のあるETL

Ray統合で機械学習特徴量パイプラインが劇的に楽になった

Glue 5.0の目玉機能の一つがRayのネイティブサポート。うちのチームでは機械学習チームが特徴量エンジニアリングをSageMakerで行っていたんだけど、前処理パイプラインとGlue ETLの境界が曖昧になっていた問題があった。

Ray on Glueを使うと、SparkとRayを同一ジョブ内で使い分けられる。大規模なJoin/AggregationはSparkで、機械学習的な前処理(正規化、エンコーディング、欠損値補完)はRayで、という構成が自然に書ける。これ、実際に動いたとき「なんでもっと早くなかったんだろう」と思ったくらいしっくりきた。

# Glue 5.0 Ray統合の実装例
import ray
from ray.data import from_spark

# SparkでデータをETL済みDataFrameとして取得
spark_df = spark.sql("""
    SELECT 
        user_id,
        feature_a,
        feature_b,
        COALESCE(feature_c, 0) as feature_c,
        label
    FROM glue_catalog.ml_database.training_data
    WHERE partition_date >= '2026-01-01'
""")

# RayのDatasetに変換
ray.init()
ray_dataset = from_spark(spark_df)

# Rayで特徴量の前処理(分散処理)
def preprocess_batch(batch):
    import numpy as np
    from sklearn.preprocessing import StandardScaler
    
    scaler = StandardScaler()
    features = ['feature_a', 'feature_b', 'feature_c']
    
    batch_array = batch[features].values
    batch[features] = scaler.fit_transform(batch_array)
    
    return batch

processed_dataset = ray_dataset.map_batches(
    preprocess_batch,
    batch_format="pandas",
    batch_size=10000
)

# S3に書き出し
processed_dataset.write_parquet(
    f"s3://{args['target_bucket']}/ml-features/"
)

MLOpsとデータエンジニアリングの境界をシームレスにつなぐ方法を探していたので、Glue 5.0 + Rayはその一つの答えになっているなと感じている。


パフォーマンス改善の実測値

Glue 4.0から5.0への移行前後で、主要なジョブの実行時間とコストを計測した。

xychart-beta
    title "主要パイプラインの実行時間比較(分)"
    x-axis ["Ingestion Job", "Transform Job", "Aggregation Job", "Full Pipeline"]
    y-axis "実行時間(分)" 0 --> 120
    bar [95, 68, 45, 110]
    line [58, 42, 28, 72]

棒グラフがGlue 4.0、折れ線がGlue 5.0だ。Transform Jobが最も改善した。これはAQEのskewJoin最適化が一番効いた部分で、以前は特定のpartitionが他の10倍以上のデータを持っていてボトルネックになっていたやつ。設定を数行追加するだけでこれだけ変わるなら、もっと早くやっておけばよかったと正直思った。

コスト面はさらに顕著だった:

xychart-beta
    title "月次Glueジョブコスト推移(万円)"
    x-axis ["2025-10", "2025-11", "2025-12", "2026-01", "2026-02", "2026-03"]
    y-axis "コスト(万円)" 0 --> 50
    line [42, 43, 41, 28, 24, 22]

2025年12月にGlue 5.0移行を完了させ、翌月から一気に下がっている。Flex実行モードの活用とG.4Xワーカーへの見直し(G.2X × 10 → G.4X × 5 に変更)が効いた。ワーカー数を減らしてもスループットが落ちなかったのは、Spark 3.5のAQEのおかげだと思っている。

バッチ処理の設計原則についてはバッチ処理設計のスケーラブルなシステム構築ガイドが参考になるので、パイプライン設計から見直したい場合は合わせて読んでみてほしい。


Glue Data Catalog + Lake Formationとの連携強化

Glue 5.0でもう一つ効いているのが、Lake Formationとの連携強化だ。列レベルのアクセス制御やRow-levelフィルタリングが、より細かく設定できるようになっている。

うちのチームでは、個人情報を含むテーブルに対して、チームごとにアクセスできる列を制限している。これをLake Formationで管理することで、Glueジョブ側でフィルタリングロジックを書かなくてよくなった。コードがシンプルになるのはもちろん、「ロジックをジョブに書いていたせいでアクセス制御が属人化する」問題がそもそも起きなくなるのが個人的にはありがたかった。

# Lake Formation 列レベル制御を有効にした読み込み
# Glue 5.0 では `lakeformation_governed` オプションが改善
datasource_pii = glueContext.create_dynamic_frame.from_catalog(
    database="production_db",
    table_name="user_events",
    transformation_ctx="datasource_pii",
    additional_options={
        "catalogPartitionPredicate": "event_date >= '2026-01-01'",
        # Lake Formation のデータフィルタリングを有効化
        "lakeformationGovernedTable": "true"
    }
)

この設定を入れることで、Lake Formation側で定義したアクセス権限がGlueジョブ実行時にも適用される。以前はジョブの中でロール判定ロジックを書いていたんだけど、その必要がなくなってコードがかなりシンプルになった。

ちなみに、データカタログの運用全般についてはデータカタログ完全ガイド2026で詳しく扱っているので、メタデータ管理から整理したい方は参考に。


正直、まだ検証中な部分もある

Glue 5.0が完璧かというと、まだ正直迷っているところもある。

G.8Xワーカーの使いどころが一つ。32vCPUの大型ワーカーはメモリ集約型のジョブに向いているはずで、試しにAggregation Jobに使ってみたんだけど、今のところ「G.4X × ある程度の台数」と比べてコスト効率で有意な差が出ていない。データ量がさらに増えれば変わるかもしれないけど、現状は判断保留中だ。

Ray統合の本番安定性も、正直まだ手放しには薦められない。特徴量パイプラインでは使い始めているけど、メモリ管理まわりでたまに謎の挙動をすることがある。Rayのバージョンが上がるたびに微妙にAPIが変わるのも地味にしんどい。本番で使うなら、まずは非クリティカルなパイプラインから試すのをおすすめしたい。

皆さんはGlue 5.0をすでに使ってますか?特にRay統合まわりの知見があれば、ぜひ聞いてみたい。


まとめ

Glue 5.0への移行を6ヶ月運用してわかったことを振り返ると、こんな感じになる。

観点まとめ
Spark 3.5 AQEデータスキューが問題だったパイプラインは移行だけで30〜40%改善できる可能性あり。設定の明示指定を忘れずに
Flex実行モードコスト削減効果は本物。ただしSLAがある処理には向かない。バックフィルやコンパクションに絞ると安全
Ray統合SparkとRayを同一ジョブで使えるのはML特徴量パイプラインを持つチームに大きなメリット。本番安定性は引き続き観察中
Lake Formation連携列レベルアクセス制御をGlue側のロジックに埋め込まなくてよくなった。コードが薄くなって管理しやすい
コスト移行前後で約45%削減。ワーカータイプ見直し+Flex実行の組み合わせが効いている

次のアクションとしては、まずGlue 4.0で動いているジョブのAQE設定を確認することから始めると良いと思う。設定を入れるだけで改善するケースが多い。その後、Flex実行モードの適用候補を洗い出して、小さなジョブで試していく順番が無難だ。Glue 5.0への移行自体はマネジメントコンソールかCLIでバージョン指定を変えるだけなので、ライブラリ互換性のテストを丁寧にやれば大きなリスクはない。

U

Untanbaby

ソフトウェアエンジニア|AWS / クラウドアーキテクチャ / DevOps

10年以上のIT実務経験をもとに、現場で使える技術情報を発信しています。 記事の誤りや改善点があればお問い合わせからお気軽にご連絡ください。

関連記事