2025, Sep 27 13:16

Polars, pyarrow и S3: как исправили запись Parquet в v1.33

Разбираем, почему запись партиционированного Parquet в S3 с polars и pyarrow давала ACCESS_DENIED и мусор на диске, и как это исправлено в polars v1.33+.

Запись партиционированных файлов Parquet в Amazon S3 из polars может вести себя неожиданно — всё зависит от выбранного бэкенда. На практике проявляются два симптома: если отключить движок pyarrow, файлы сначала подготавливаются локально перед загрузкой и эти локальные артефакты не удаляются автоматически; если включить pyarrow, загрузка может завершиться ошибкой ACCESS_DENIED с упоминанием анонимных multipart‑загрузок. Ниже — краткий разбор проблемы и текущий статус исправления.

Воспроизведение в коде

В примере ниже DataFrame записывается в S3 с явной передачей AWS‑учётных данных через storage_options. Переключение бэкенда меняет результат.

aws_sess = ...  # сессия boto3
creds = aws_sess.get_credentials()
io_opts = {
    "aws_access_key_id": creds.access_key,
    "aws_secret_access_key": creds.secret_key,
    "aws_session_token": creds.token,
    "aws_region": "eu-west-1",
}
s3_uri = f"s3://{bucket_name}/{prefix_path}"
# объект polars DataFrame
tbl.write_parquet(
    s3_uri,
    partition_by=part_cols,
    storage_options=io_opts,
    use_pyarrow=...  # True | False — поведение отличается
)

Что происходит на самом деле

При use_pyarrow=False polars создаёт локальные копии данных перед загрузкой в S3, и эти файлы не очищаются автоматически. При use_pyarrow=True запись может упасть с ошибкой вроде «Uploading to <file> FAILED with error When initiating multiple part upload for key <directory> in bucket <bucket>: AWS Error ACCESS_DENIED during CreateMultipartUpload operation: Anonymous users cannot initiate multipart uploads. Please authenticate.» Такое поведение было известной проблемой, затрагивавшей и pl.DataFrame.write_parquet, и pl.LazyFrame.sink_parquet. Обсуждение публично отслеживается в polars#23114 для sink_parquet и polars#23221 для write_parquet.

Решение и обновлённое поведение

Исправление уже доступно. Начиная с polars v1.33, описанные проблемы больше не возникают, и pl.LazyFrame.sink_parquet, и pl.DataFrame.write_parquet работают как ожидается при записи партиционированного Parquet в S3.

Если у вас версия ниже v1.33, поведение можно воспроизвести приведённым выше сниппетом. На v1.33 и новее тот же путь вызова работает как задумано. Например:

aws_sess = ...  # сессия boto3
creds = aws_sess.get_credentials()
io_opts = {
    "aws_access_key_id": creds.access_key,
    "aws_secret_access_key": creds.secret_key,
    "aws_session_token": creds.token,
    "aws_region": "eu-west-1",
}
s3_uri = f"s3://{bucket_name}/{prefix_path}"
# объект polars DataFrame
tbl.write_parquet(
    s3_uri,
    partition_by=part_cols,
    storage_options=io_opts,
    use_pyarrow=True  # работает как ожидается на v1.33+
)

Почему это важно

Партиционированные дата-лейки чувствительны к корректности и согласованности при записи. Локальная подготовка, оставляющая файлы, расходует диск и усложняет эксплуатацию. Срыв multipart‑загрузок в S3 останавливает конвейеры и вносит неопределённость в мониторинг. Осознание, что поведение связано с конкретными версиями polars, помогает быстро диагностировать симптомы и убрать лишние обходные решения.

Практические выводы

Если при записи партиционированного Parquet в S3 вы видите либо остатки локальной подготовки, либо ошибку ACCESS_DENIED при multipart‑загрузке, проверьте свою версию polars. Обновление до v1.33 и новее решает проблему как для «жадного» пути записи, так и для «ленивого» sink‑пути. За фоном и историей обратитесь к публичным тредам polars#23114 и polars#23221.

Короче говоря, полагайтесь на исправленное поведение в v1.33+ и держите код записи данных простым и явным в части storage_options и партиционирования. Так ваши нагрузки в S3 останутся предсказуемыми, а операционные издержки — ниже.

Статья основана на вопросе на StackOverflow от FBruzzesi и ответе от FBruzzesi.