PythonでAmazon S3 Selectを使用するときは最後にデータをデコードしよう

Amazon S3 SelectをPythonで使用していたら処理に失敗するケースに遭遇しました。日本語などを扱う場合は注意が必要そうです。
2021.04.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。サービスグループの武田です。

Amazon S3 Select便利ですよね。私も次のエントリなどを参考にPythonで実装し運用していました。

特に問題もなく稼働していたのですが、ある日次のエラーとともに処理が失敗しました。

UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 64998-64999: unexpected end of data

調査したところ、どうやら結果に含まれるマルチバイト文字が途中で分割された結果、UTF-8として不正な文字コードとなりデコードに失敗したようでした。

ポイントは次の2点。

  1. S3 Selectは結果が一定以上のサイズの場合、複数のRecordsに分割する
  2. RecordsのPayloadはバイト列

つまり、日本語などのマルチバイト文字が結果に含まれる場合、途中で分割されても問題のないようにしておく必要があります。

元のコードは次のものでした。

def execute_s3_select(option):
  res = s3.select_object_content(**option)
  text = ""
  for event in res["Payload"]:
      if "Records" in event:
          raw = event["Records"]["Payload"].decode("utf-8")
          text += raw
  return text

これを次のように修正することで問題は解決できました。

def execute_s3_select(option):
  res = s3.select_object_content(**option)
  return b"".join(
        [event["Records"]["Payload"] for event in res["Payload"] if "Records" in event]
    ).decode("UTF-8")

まとめ

マルチバイト文字の途中で分割されないと発生しない現象のため、しばらくは表面化しませんでした。仕様を知っているとなるほどというバグですね。

どなたかの参考になれば幸いです。