Skip to content

[bulk] A denied or throttled batch write aborts the job with a raw Spark traceback, and leaves items behind silently #325

Description

@hunterhacker

Found while verifying #324 with a deliberately failing delete. Reproduction is cheap and exact (recipe at the bottom).

What happens

A DynamoDB resource-based policy denying BatchWriteItem for one partition key, then bulk delete over 60 items:

2026-08-28 22:20:35 ERROR TaskSetManager:267 - Task 161 in stage 4.0 failed 4 times; aborting job
exit=1
items remaining: 6            <- 54 of 60 were deleted

From the executor stream:

botocore.exceptions.ClientError: An error occurred (AccessDeniedException) when calling the
BatchWriteItem operation: User: arn:aws:sts::...:assumed-role/AWSGlueServiceRoleBulkDynamoDB-
DdbReadWrite-us-east-1/GlueJobRunnerSession is not authorized to perform ...

Three problems:

  1. The user gets a Spark traceback, not an explanation. Nothing says "you are not allowed to delete from this table". The AccessDeniedException is in an executor stream, which the client discards (_pretty_print_log_event skips _g- streams), so the only thing on the console is Task 161 in stage 4.0 failed 4 times.
  2. The partial delete is silent. 54 items were deleted and 6 were not. The driver's Deleted N items line never prints, because the job aborted first, so nothing states what did or did not happen. A user re-running blind is the best case; a user assuming nothing happened is the likely one.
  3. find's per-item failure reporting cannot see it. batch_writer buffers 25 items and flushes, so the error is raised inside the with block's exit — outside the per-item try. Verified: zero Delete failed for lines were logged, and the M failed count added in [bulk] Bound per-item failure logging, and tell the user the total (closes #319) #324 stayed at zero. That count only covers malformed-record and key-extraction failures, which are rare; it does not cover the failure users will actually hit.

Why it is awkward to fix

A worker-side BulkExecutorError does not reach the driver as a BulkExecutorError — it arrives as a Py4JJavaError wrapping the worker traceback, so root.py's clean-error handler does not catch it, and the client's BulkExecutorError noise suppression never triggers either. Getting a clean message onto the console needs the driver to inspect the wrapped cause, not just a raise in the worker.

Options, roughly in increasing order of work:

  1. Catch at the partition boundary and re-raise with a recognisable shape, then teach the driver (or root.py) to unwrap a Py4JJavaError whose cause carries our marker and print the underlying message. Fixes problem 1.
  2. Report what was done before failing. Read the accumulator on the driver in a finally so Deleted N items, M not attempted prints even on an aborted run. Fixes problem 2.
  3. Decide the policy for a wholesale write failure: abort (today) versus complete-and-report. Aborting is defensible for a permission error, but it must say so; completing with an accurate count risks a successful-looking exit 0 on a run that deleted nothing.

Reproduction

ddb.put_resource_policy(
    ResourceArn=f"arn:aws:dynamodb:us-east-1:{acct}:table/{T}",
    Policy=json.dumps({"Version": "2012-10-17", "Statement": [{
        "Sid": "DenyDeletingOneSpecificItem", "Effect": "Deny",
        "Principal": {"AWS": glue_job_role_arn},
        "Action": ["dynamodb:DeleteItem", "dynamodb:BatchWriteItem"],
        "Resource": f"arn:aws:dynamodb:us-east-1:{acct}:table/{T}",
        "Condition": {"ForAnyValue:StringEquals": {"dynamodb:LeadingKeys": ["k005"]}}}]}))

Then bulk delete --table <T> (PITR must be enabled first — the existing guard fires before any of this). Note the policy denies BatchWriteItem for a request containing the key, so one denied item poisons its whole 25-item batch, which is why 6 items rather than 1 survived: the failing task was retried 4 times and the job aborted while other tasks were still in flight.

Clean up with delete_resource_policy, then wait — DeleteTable returns ResourceInUseException until the policy update settles.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingbulk_executorAll bulk executor tasks

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions