0
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?

Athena Iceberg + dbt で mismatched input '<' が出たら — Hive/Trino 型混在の罠

0
Last updated at Posted at 2026-04-06

dbt-athena で Athena Iceberg テーブルに対して incremental + merge を使い、スキーマ変更を伴う運用をしていると、複合型(struct / array<struct>)のカラムで mismatched input '<'TYPE_MISMATCH のエラーが発生することがあります。

この記事では、このエラーの根本原因を adapter のソースコードレベルで特定し、最小構成で再現する方法を紹介します。2つの異なるバグパターンを整理し、どの条件で発生するかを明確にします。

実際に DynamoDB からの取り込みパイプラインで本番障害としてこのエラーに遭遇し、dbt-athena-community の 1.9.4 と 1.10.0 の両方で再現検証を行いました。


dbt-athena で Athena Iceberg テーブルに対して incremental + merge を使い、スキーマ変更を伴う運用をしていると、複合型(struct / array<struct>)のカラムで mismatched input '<'TYPE_MISMATCH のエラーが発生することがあります。

この記事では、このエラーの根本原因を adapter のソースコードレベルで特定し、最小構成で再現する方法を紹介します。2つの異なるバグパターンを整理し、どの条件で発生するかを明確にします。

実際に DynamoDB からの取り込みパイプラインで本番障害としてこのエラーに遭遇し、dbt-athena-community の 1.9.4 と 1.10.0 の両方で再現検証を行いました。


TL;DR

  • dbt-athena の schema evolution(on_schema_change='sync_all_columns''append_new_columns')で、struct / array<struct> を含むカラムに2つのバグがあります
  • バグA: struct 内に timestamp 型フィールドがあると、ALTER TABLE で struct 全体が timestamp に誤変換される → MERGE 時に TYPE_MISMATCH
  • バグB: 既存カラムの型が変わると(フィールド追加など)、UPDATE の CAST に Hive 形式 struct<...> が混入する → mismatched input '<'
  • dbt-athena-community 1.9.4 / 1.10.0 の両方で再現し、執筆時点(2026年4月)では未修正です

1. 何が起きたか

DynamoDB のデータを Glue Crawler 経由で Athena Iceberg テーブルに取り込み、dbt で incremental + merge の運用をしていました。DynamoDB の JSON は marshalized 形式で、各カラムが struct<S:string>struct<M:struct<...>> のようなネストした複合型になります。

ある日、DynamoDB 側のデータにフィールドが追加され、Glue Crawler がスキーマを更新しました。dbt の on_schema_change='sync_all_columns' が schema evolution を実行しようとしたところ、以下のエラーが発生しました。

An error occurred (InvalidRequestException) when calling the StartQueryExecution operation:
line 2:112: mismatched input '<'. Expecting: '(', ')', 'ARRAY'

dbt --debug で生成された SQL を確認すると、UPDATE 文に以下のような CAST が含まれていました。

update ... set user_profile__dbt_alter = cast(user_profile as struct<m:struct<address:struct<...>>>);

Athena の DML(UPDATE / MERGE)は Trino エンジンで実行されるため、型名には row(...) 形式を使う必要があります。しかし、ここでは Hive 形式の struct<...> が使われてしまっていました。


2. なぜ起きるか — Hive 形式と Trino 形式の混在

Athena には DDL と DML で異なる型構文が使われるという仕様があります。

文脈 エンジン 複合型の書き方
DDL(CREATE TABLE, ALTER TABLE) Hive 互換 struct<name:string, value:int>
DML(SELECT, UPDATE, MERGE) Trino row(name varchar, value integer)

dbt-athena は、テーブルのスキーマ情報を boto3glue_client.get_table() で取得します。この API は Hive 形式で型を返します。

{
  "Name": "complex_col",
  "Type": "struct<event_time:timestamp,detail:string>"
}

一方、information_schema.columns から取得すると Trino 形式になります。

row(event_time timestamp(6), detail varchar)

根本原因: adapter のソースコード

バグの起点は impl.pyget_columns_in_relation です。Glue API から取得した Hive 形式の型文字列を、そのまま AthenaColumn.dtype に格納しています。

# impl.py L1383-1388
return [
    AthenaColumn(column=c["Name"], dtype=c["Type"], table_type=table_type)
    for c in columns + partition_keys
]

この dtype(Hive 形式)は DDL と DML の両方で使われますが、DML 文脈では Trino 形式への変換が必要です。adapter には ddl_data_typedml_data_type という2つの変換マクロが存在するものの、schema evolution のコードパスではこの変換が正しく行われていません。


3. 最小再現プロジェクトの設計

本番のデータは複雑な DynamoDB marshalized JSON で、カラム数も多いため、最小構成での再現プロジェクトを作成しました。

再現に必要な条件は以下の通りです。

  • materialized='incremental' + incremental_strategy='merge'
  • table_type='iceberg'
  • on_schema_change='sync_all_columns'(または 'append_new_columns'
  • Phase 1 でシンプルなテーブルを作成し、Phase 2 でスキーマを変更して再実行

比較軸として、以下のバリエーションを用意しました。

TC 列の変更内容 意図
TC-01 string 列追加 ベースライン(成功するはず)
TC-02 struct<timestamp, string> 追加 バグA 再現
TC-02b struct<string, int> 追加 timestamp 無しで比較
TC-02e array<struct> のフィールド増加 バグB 再現(DynamoDB パターン)
TC-02f struct 内の型変更 int→struct バグB 再現(型昇格パターン)
TC-05 struct 追加、ignore モード schema evolution を走らせない比較

4. 再現結果 — 2つのバグパターン

dbt-athena-community 1.9.4 と 1.10.0 の両方で、全く同じ結果が得られました。

バグA: ALTER TABLE での struct → timestamp 誤変換

発生条件: struct 内に timestamp 型のフィールドが含まれる新規カラムの追加

原因は ddl_data_type マクロの 19-22行目 です。

-- ddl_data_type マクロ(抜粋)
{%- if table_type == 'iceberg' -%}
  {%- if 'timestamp' in data_type -%}
      {% set data_type = 'timestamp' -%}
  {%- endif -%}
{%- endif -%}

Iceberg テーブルの場合、'timestamp' in data_type という部分一致で型を timestamp に置き換えています。struct<event_time:timestamp,detail:string> のように文字列中に timestamp が含まれていると、struct 全体が timestamp に化けてしまいます。

debug ログで確認すると、ALTER TABLE が以下のように生成されていました。

-- Glue から取得した型: struct<event_time:timestamp,detail:string>
-- 生成された ALTER TABLE:
alter table `repro_dbt_athena_struct`.`tc02_top_level_struct_add`
  add columns (complex_col timestamp)

その結果、MERGE 文で src 側の row(event_time timestamp(6), detail varchar) と target 側の timestamp(6) が不一致となり TYPE_MISMATCH が発生します。

TYPE_MISMATCH: line 7:7: MERGE table column types don't match for MERGE case 0,
SET expressions: Table: [varchar, timestamp(6)],
Expressions: [varchar, row(event_time timestamp(6), detail varchar)]

興味深いことに、struct<name:string,value:int> のように timestamp を含まない struct ではこの問題は起きません(TC-02b で確認済み)。

バグB: DML の CAST に Hive 形式 struct<...> が混入

発生条件: 既存カラムの複合型が変更される場合(フィールド追加、型変更)

原因は athena__alter_column_type マクロの 76-78行目 です。

-- on_schema_change.sql(抜粋)
{%- set update_query -%}
  update {{ relation.render_pure() }}
    set {{ tmp_column }} = cast({{ column_name }} as {{ new_column_type }});
{%- endset -%}

DDL 用の ALTER TABLE(72行目)では ddl_data_type() で変換した new_ddl_data_type が使われますが、DML 用の UPDATE(77行目)では new_column_type(Glue 由来の Hive 形式)がそのまま CAST に埋め込まれています。dml_data_type() を経由していません。

-- ALTER TABLE (DDL) — Hive 形式で正しい
alter table ... add columns(items__dbt_alter array<struct<k:string,v:string,priority:int>>);

-- UPDATE (DML) — Hive 形式が混入してエラー
update ... set items__dbt_alter = cast(items as array(struct<k:string,v:string,priority:int>));

DDL の ALTER TABLE は Hive 形式の struct<...> で正しく動作しますが、DML の UPDATE CAST にもそのまま struct<...> が使われてしまい、Athena が mismatched input '<' を返します。

結果一覧

TC v1.9.4 v1.10.0 バグパターン
TC-01 (string追加) SUCCESS SUCCESS
TC-02 (struct<ts,string>) FAIL FAIL A: TYPE_MISMATCH
TC-02b (struct<string,int>) SUCCESS SUCCESS
TC-02e (array<struct>フィールド増) FAIL FAIL B: mismatched input '<'
TC-02f (int→struct型変更) FAIL FAIL B: mismatched input '<'
TC-05 (ignore) SUCCESS SUCCESS

5. 現時点での回避策と今後

執筆時点(2026年4月)では、dbt-athena-community の 1.9.4 / 1.10.0 の両方でこのバグが存在します。

dbt-adapters リポジトリの Issue #1433 で報告されており、triage:product ラベルが付いて Product チームのキューにありますが、修正 PR やコメントはまだありません。

回避策として考えられるもの:

  • on_schema_change='ignore' を使い、schema evolution を dbt に任せない
  • スキーマ変更が必要な場合は、手動で ALTER TABLE を実行してから dbt run する
  • athena__alter_column_type マクロをプロジェクト内でオーバーライドし、UPDATE の CAST で dml_data_type() を経由させる

修正に必要な変更:

adapter 側で必要な修正は大きく2点です。

  1. バグA: ddl_data_type マクロ'timestamp' in data_type を、struct 内の timestamp にマッチしないよう修正する(例: 正規表現でトップレベルの型のみを対象にする)
  2. バグB: athena__alter_column_type マクロ の UPDATE CAST で new_column_type を直接使わず、dml_data_type() で Trino 形式に変換する。ただし、現在の dml_data_type()struct<...>row(...) の変換を行わないため、この変換ロジック自体の追加も必要です

環境情報

項目
dbt-athena-community 1.9.4 / 1.10.0
dbt-core 1.11.7
Python 3.11.6
AWS リージョン ap-northeast-1
Athena Engine Version 3
テーブルフォーマット Iceberg

関連リンク


Appendix: 再現用 dbt モデル SQL

以下は最小再現に使用した dbt モデルの SQL です。すべて materialized='incremental' + incremental_strategy='merge' + table_type='iceberg' + on_schema_change='sync_all_columns' で設定しています。

phase 変数で Phase 1(初期テーブル作成、--full-refresh)と Phase 2(スキーマ変更、incremental 実行)を切り替えます。

TC-02: struct<timestamp, string> 追加(バグA 再現)

-- Phase 1: シンプルなテーブル
select
  cast(id as integer) as id,
  cast(payload as varchar) as payload
from {{ ref('base_items_seed') }}

-- Phase 2: struct 列を追加
select
  cast(id as integer) as id,
  cast(payload as varchar) as payload,
  cast(
    row(cast(current_timestamp as timestamp(6)), 'detail_value')
    as row(event_time timestamp(6), detail varchar)
  ) as complex_col
from {{ ref('base_items_seed') }}

結果: Phase 2 で ALTER TABLE が add columns (complex_col timestamp) を生成し、struct 全体が timestamp に誤変換される。MERGE 時に TYPE_MISMATCH

TC-02b: struct<string, int> 追加(対照群 — 成功)

-- Phase 2: timestamp を含まない struct 列を追加
select
  cast(id as integer) as id,
  cast(payload as varchar) as payload,
  cast(
    row('some_name', 42)
    as row(name varchar, value integer)
  ) as simple_struct_col
from {{ ref('base_items_seed') }}

結果: SUCCESS。struct<name:string,value:int>timestamp が含まれないため、バグA は発生しない。

TC-02e: array<struct> のフィールド増加(バグB 再現)

-- Phase 1: 2フィールドの struct を持つ array
select
  cast(id as integer) as id,
  cast(payload as varchar) as payload,
  cast(
    array[row('key1', 'val1'), row('key2', 'val2')]
    as array(row(k varchar, v varchar))
  ) as items
from {{ ref('base_items_seed') }}

-- Phase 2: struct に新フィールド priority が追加
select
  cast(id as integer) as id,
  cast(payload as varchar) as payload,
  cast(
    array[row('key1', 'val1', 1), row('key2', 'val2', 2)]
    as array(row(k varchar, v varchar, priority integer))
  ) as items
from {{ ref('base_items_seed') }}

結果: Phase 2 で UPDATE の CAST に cast(items as array(struct<k:string,v:string,priority:int>)) が生成される。Hive 形式の struct<...> が DML に混入し、mismatched input '<'

TC-02f: struct 内の型変更 int→struct(バグB 再現)

-- Phase 1: score は integer
select
  cast(id as integer) as id,
  cast(payload as varchar) as payload,
  cast(
    row(100, 'good')
    as row(score integer, label varchar)
  ) as detail
from {{ ref('base_items_seed') }}

-- Phase 2: score が integer → struct に変わる
select
  cast(id as integer) as id,
  cast(payload as varchar) as payload,
  cast(
    row(row(100, 0.95e0), 'good')
    as row(score row(raw integer, normalized double), label varchar)
  ) as detail
from {{ ref('base_items_seed') }}

結果: Phase 2 で UPDATE の CAST に cast(detail as struct<score:struct<raw:int,normalized:double>,label:string>) が生成される。mismatched input '<'

再現手順(まとめ)

# 前提: dbt-athena-community 1.9.4 or 1.10.0, Athena Iceberg 対応の S3 + Glue DB

# Phase 1: 初期テーブル作成
dbt seed --full-refresh
dbt run --full-refresh --select tc02_top_level_struct_add tc02b_struct_no_timestamp \
    tc02e_array_struct_field_evolve tc02f_field_type_change --vars "{phase: 'initial'}"

# Phase 2: スキーマ変更を伴う incremental 実行
dbt --debug run --select tc02_top_level_struct_add --vars "{phase: 'evolved'}"
# → TYPE_MISMATCH (バグA)

dbt --debug run --select tc02b_struct_no_timestamp --vars "{phase: 'evolved'}"
# → SUCCESS (対照群)

dbt --debug run --select tc02e_array_struct_field_evolve --vars "{phase: 'evolved'}"
# → mismatched input '<' (バグB)

dbt --debug run --select tc02f_field_type_change --vars "{phase: 'evolved'}"
# → mismatched input '<' (バグB)
0
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
0
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?