問題点標準的なSQLステートメントで「Run Query」を実行し、S3からクリーンなデータセットが取得できると期待していたところ、Athenaが停止し、次のような厄介なエラーをスローすることがあります。
HIVE_CURSOR_ERROR: Row is not a valid JSON object
このエラーは紛らわしいものです。VS Codeでファイルを開くと構文は完璧にハイライトされており、何が悪いのかと不思議に思うかもしれません。多くの場合、問題はJSONの構文そのものではなく、S3上でのデータの物理的な配置にあります。Athenaのデフォルトのパーサーは、ファイルの構造に対して非常に厳格です。
要約:手っ取り早い解決策Athenaは標準的なJSONファイルを読み取れません。JSON Lines (ndjson)形式である必要があります。データがプリティプリント(複数行にインデント)されていたり、JSON配列([]を使用)でラップされている場合、クエリは即座に失敗します。
- オブジェクトをフラット化する: 各レコードは必ず1行に収める必要があります。- ラッパーを削除する: 開始の
[と終了の]ブラケットを取り除きます。- カンマを整理する: 標準的な配列でオブジェクトを区切るために使われるカンマを削除します。### jqを使用して瞬時にファイルを変換するjqがインストールされている場合は、次のコマンドを使用して、プリティプリントされたファイルをAthenaが要求するコンパクトな形式に変換できます。
jq -c . input.json > output.jsonl
なぜこれが起こるのかデフォルトのHive JSON SerDe (Serializer/Deserializer)は、ストリーミングリーダーです。500MBのファイル全体をメモリにロードして1つのドキュメントとして解析することはありません。代わりにファイルを1行ずつスキャンし、各改行文字を新しい独立したレコードの開始として扱います。
次のよくあるスニペットを考えてみましょう。
{
"id": 101,
"status": "active"
}
Athenaは最初の行 { を読み取ります。孤立した開始中括弧は完全なJSONオブジェクトではないため、パーサーはパニックを起こしクエリを終了させます。ファイルが配列を示す [ で始まっている場合も同様のことが起こります。
よくあるシナリオと修正方法### 1. プリティプリントされたJSONこれが最も頻繁に発生する原因です。インデントによってログは人間にとって読みやすくなりますが、Athenaにとっては致命的です。これらのオブジェクトを1行の文字列にフラット化する必要があります。
誤り:
{
"user_id": 5001,
"action": "click"
}
{
"user_id": 5002,
"action": "view"
}
正しい例 (JSON Lines):
{"user_id": 5001, "action": "click"}
{"user_id": 5002, "action": "view"}
2. 単一の不正な形式のレコード数百万行のデータセットであっても、引用符が1つ欠けていたり、エスケープされていない特殊文字が1つあるだけでクエリはクラッシュします。たった1バイトの迷い込みによって、100GBのクエリが99%の進捗で失敗するのを見たことがあります。
プロのヒント: 不審な行を調査するには JSON Formatter & Validator を使用してください。私はToolCraftを開いたままにして、問題のある行を素早く貼り付けて検証できるようにしています。これはブラウザ内でローカルに動作するため、機密性の高いログデータがマシンから外部に出ることはありません。
3. OpenX SerDeによる猶予のある失敗データパイプラインが乱雑で、クエリ全体を失敗させるよりも数行の不正なデータを無視したい場合は、OpenX SerDeに切り替えてください。これにはエラーをスキップするための特定のフラグが含まれています。
次のように CREATE TABLE 文を更新します。
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
'ignore.malformed.json' = 'true'
)
これによりクエリは完了するようになりますが、不正な形式の行は結果の中で NULL として表示されることに注意してください。
一括フォーマット用のPythonスクリプト修正が必要なファイルがS3上に数千ある場合、このPythonスクリプトは標準的なJSON配列をAthenaが必要とする行区切り形式に変換します。
import json
def repair_athena_json(input_file, output_file):
with open(input_file, 'r') as f:
data = json.load(f)
with open(output_file, 'w') as f:
# 配列を反復処理するか、単一のオブジェクトを処理するようにする
records = data if isinstance(data, list) else [data]
for entry in records:
f.write(json.dumps(entry) + '\n')
# 使用方法
repair_athena_json('logs_june.json', 'logs_fixed.jsonl')
最終チェックリスト- テストクエリを実行する: SELECT * FROM table LIMIT 10 を使用します。最初の数行がロードされれば、基本的なフォーマットはおそらく正しいです。- NULLを監査する: ignore.malformed.json フラグを使用した場合は、どの程度のデータが失われているかを確認するためにカウントを実行します: ```
SELECT count(*) FROM your_table WHERE primary_id IS NULL;
```- エンコーディングを確認する: ファイルをBOM(バイト順マーク)なしの UTF-8 で保存してください。ファイルの先頭にある隠し文字は、即座に HIVE_CURSOR_ERROR を引き起こす可能性があります。## 予防策- ロガーを設定する: Pythonを使用している場合は python-json-logger に切り替えてください。Node.jsの場合は pino を使用します。どちらもデフォルトで ndjson 出力をサポートしています。- Parquetを採用する: 本番環境ではJSONはコストがかかります。AWS Glueを使用してデータをParquetに変換してください。Parquetはクエリが高速で、ストレージコストが安く、テキスト解析エラーを完全に回避できます。

