[B05] ヘッダー無し CSV が混入するとデータ 1 行が静かに欠落し、別テーブル扱いになる(エラー無し)
再現条件
- 同一プレフィックス配下に「ヘッダー有り CSV 9 ファイル」+「ヘッダー無し CSV 1 ファイル」を配置
- Admin UI から
analyze → apply
- ヘッダー無しファイルは 1 行目からデータ行が始まる
観測された挙動
- analyze は成功し 2 つのテーブルに分かれる
visits(9 ファイル)と visits_1(ヘッダー無しの 1 ファイル)に _deduplicate_table_names によるサフィックスで別テーブルとして CREATE
- apply も SUCCEEDED(Step Functions
SUCCEEDED, Admin UI も成功表示)
- ロード後の実行件数:
visits: 9 ファイル × 100,000 行 = 900,000 行(期待値どおり)
visits_1: 1 ファイル 100,000 行 だが 99,999 行(1 行消失)
原因
1. _get_csv_header がヘッダー有無を判定せず、1 行目を常にヘッダーとして扱う
lambda/adminwebbackend/app.py _get_csv_header:
resp = s3.get_object(Bucket=bucket, Key=key, Range="bytes=0-8192")
raw = resp["Body"].read().decode("utf-8", errors="replace")
first_line = raw.split("\n", 1)[0].strip()
...
reader = csv.reader(io.StringIO(first_line), delimiter=delimiter)
headers = next(reader, None)
return headers
- ファイル 1 行目の値(今回の例では
V0000000000,P00039217,2024-01-01 00:00:00,内科,...)がそのまま「ヘッダー」として返る
_group_csv_by_header は ヘッダー文字列が完全一致するファイル同士だけを同一グループにまとめるので、ヘッダー無しの CSV は必ず単独グループに分離される
- Bedrock は 1 行目を「カラム名」として解釈して、適当な英名のカラム定義(
visit_datetime, doctor_name, icd_code, diagnosis, remarks など)を生成してしまう
- 結果、別名のカラムを持つ同一データ内容の別テーブル (
visits_1) が作られる
2. COPY は IGNOREHEADER 1 固定なのでヘッダー無しファイルの 1 行目が失われる
handle_start_build で生成される COPY 文は常に IGNOREHEADER 1 を付けるため、ヘッダー無し CSV ではデータの 1 行目が捨てられ 1 行少なくなる(今回 100,000 → 99,999)。
- Admin UI にはエラーも警告も出ないため、利用者は気づかない
- 検知方法が
select count(*) を手動で突き合わせる以外ない(サイレントなデータ欠損)
期待される挙動
- ヘッダー行が無い CSV が混入したら検知して警告/エラーを上げる。具体的な案:
_get_csv_header が「2 行目以降のレコードと 1 行目のデータ型プロファイルを比較」し、一致度が高ければヘッダー無しと判定
- または Admin UI の analyze プレビュー時点で「このカラム名は実データ値に見えます(例:
V0000000000)」と警告する
_group_csv_by_header で孤立グループ(1 ファイルだけの group)が発生したら analyze 結果にそれを明示
- COPY 側で
IGNOREHEADER 1 を固定せず、analyze の判定結果に応じて 0 か 1 を動的に切り替える
再現手順
- 同一スキーマの CSV を 10 ファイル作成。そのうち 1 ファイル(例:
visits_part4.csv)だけヘッダー行を削除する
- Admin UI から該当プレフィックスで Agent 作成 → analyze → apply
select count(*) でヘッダー無しファイルの行数を確認すると N - 1 行になっていることを観測
参考情報
- 対象スタック:
arn:aws:cloudformation:ap-northeast-1:411521242467:stack/devDwhAgentStack/6619ffe0-3f8b-11f1-a3dc-068ad41190bd
- SFn execution ARN:
arn:aws:states:ap-northeast-1:411521242467:execution:AdminBackendInitWorkflowStateMachine4193125B-ArEyUWKHNQd8:b22592b0-902d-4c64-b447-879c825415f8
- 関連ファイル:
lambda/adminwebbackend/app.py _get_csv_header, _group_csv_by_header; lambda/redshiftinitworkflow/handlers.py COPY 生成箇所
[B05] ヘッダー無し CSV が混入するとデータ 1 行が静かに欠落し、別テーブル扱いになる(エラー無し)
再現条件
analyze→apply観測された挙動
visits(9 ファイル)とvisits_1(ヘッダー無しの 1 ファイル)に_deduplicate_table_namesによるサフィックスで別テーブルとして CREATESUCCEEDED, Admin UI も成功表示)visits: 9 ファイル × 100,000 行 = 900,000 行(期待値どおり)visits_1: 1 ファイル 100,000 行 だが 99,999 行(1 行消失)原因
1.
_get_csv_headerがヘッダー有無を判定せず、1 行目を常にヘッダーとして扱うlambda/adminwebbackend/app.py_get_csv_header:V0000000000,P00039217,2024-01-01 00:00:00,内科,...)がそのまま「ヘッダー」として返る_group_csv_by_headerは ヘッダー文字列が完全一致するファイル同士だけを同一グループにまとめるので、ヘッダー無しの CSV は必ず単独グループに分離されるvisit_datetime,doctor_name,icd_code,diagnosis,remarksなど)を生成してしまうvisits_1) が作られる2. COPY は
IGNOREHEADER 1固定なのでヘッダー無しファイルの 1 行目が失われるhandle_start_buildで生成される COPY 文は常にIGNOREHEADER 1を付けるため、ヘッダー無し CSV ではデータの 1 行目が捨てられ 1 行少なくなる(今回 100,000 → 99,999)。select count(*)を手動で突き合わせる以外ない(サイレントなデータ欠損)期待される挙動
_get_csv_headerが「2 行目以降のレコードと 1 行目のデータ型プロファイルを比較」し、一致度が高ければヘッダー無しと判定V0000000000)」と警告する_group_csv_by_headerで孤立グループ(1 ファイルだけの group)が発生したら analyze 結果にそれを明示IGNOREHEADER 1を固定せず、analyze の判定結果に応じて 0 か 1 を動的に切り替える再現手順
visits_part4.csv)だけヘッダー行を削除するselect count(*)でヘッダー無しファイルの行数を確認すると N - 1 行になっていることを観測参考情報
arn:aws:cloudformation:ap-northeast-1:411521242467:stack/devDwhAgentStack/6619ffe0-3f8b-11f1-a3dc-068ad41190bdarn:aws:states:ap-northeast-1:411521242467:execution:AdminBackendInitWorkflowStateMachine4193125B-ArEyUWKHNQd8:b22592b0-902d-4c64-b447-879c825415f8lambda/adminwebbackend/app.py_get_csv_header,_group_csv_by_header;lambda/redshiftinitworkflow/handlers.pyCOPY 生成箇所