SDK and CLI patterns

Lesson 4 of 4 in Programming Concepts on the Exam.

Three SDK patterns appear in stems, and all three are places where code that looks like it works is wrong.

Pagination. Almost every AWS list or describe API is paginated: ListObjectsV2 returns up to 1,000 keys, GetPartitions and Scan return a page plus a token. Calling one once and iterating the result is a bug that returns data, raises no error, and is wrong by however much did not fit in one page. This is one of the most common real defects in hand-written data tooling.

The SDK's answer is a paginator: paginator = client.get_paginator("list_objects_v2"), then iterate paginator.paginate(...), which issues the follow-up calls and yields every page. The manual equivalent loops on NextToken / NextContinuationToken / LastEvaluatedKey until it is absent — and getting the terminating condition wrong in either direction gives you a partial result or an infinite loop.

Partial failure inside a successful response. PutRecords, BatchWriteItem, BatchGetItem and SendMessageBatch all return HTTP 200 with a per-item failure list — FailedRecordCount, UnprocessedItems, UnprocessedKeys — because the call succeeded even though some items did not. No retry mode covers this, because from the SDK's point of view nothing failed. The loop that re-submits the unprocessed items is as necessary as the paginator and omitted just as often.

Waiters instead of sleep loops. A waiter polls a describe API until a resource reaches a state — cluster_available, object_exists, job_flow_running — with sensible backoff and a bounded attempt count. It is the correct replacement for while True: sleep(30), which has no timeout, no backoff and no error on a resource that will never arrive.

And the setting that explains behaviour which otherwise looks like magic: the retry mode. standard is the current default — three attempts, a consistent retryable-error set, exponential backoff with jitter. adaptive adds a client-side rate limiter that measures the throttling it is receiving and slows the client before it is rejected, which suits a bulk job and is explicitly not recommended as a default because it will deliberately delay your calls. Configure with max_attempts and retry_mode in the shared config file, the AWS_MAX_ATTEMPTS and AWS_RETRY_MODE environment variables, or per client in code.

A bulk write that is actually correct — every branch here is one people omit

  1. A list of 400,000 items to write

    A backfill, a migration, a reload. The target is a batch API, which is the right choice — and the batch API has a shape that must be handled.

  2. Read the source with a paginator

    Not a single list call. ListObjectsV2 caps at 1,000 keys per page and Scan at 1 MB, so a single call returns a page and looks like a complete answer.

  3. Send a batch

    PutRecords up to 500 records, BatchWriteItem up to 25 items. Larger batches amortise request overhead, which is the whole reason to use them.

  4. HTTP 200?

    A non-2xx response is the easy case — the SDK has already retried it per the retry mode, and what reaches you is a genuine failure to raise.

  5. Any per-item failures in the body?

    This is the branch that gets omitted. FailedRecordCount, UnprocessedItems, UnprocessedKeys — a 200 response can contain failures, because the call succeeded even if some items did not.

  6. Re-submit ONLY the failed items

    With exponential backoff and jitter between attempts, and a bounded attempt count. Re-sending the whole batch duplicates the items that succeeded.

  7. Still failing after the attempt budget?

    A bounded budget is essential: an unbounded retry loop against a permanent failure runs forever and looks like a hung job.

  8. Park the items and alarm

    Write the unwritable items somewhere durable with their error, and raise an alarm. A bulk job that silently dropped 12 items of 400,000 is worse than one that failed, because nobody knows.

  9. Raise — the call genuinely failed

    Let it propagate so the orchestrator sees a failed task and its own retry and catch behaviour applies. Swallowing the error here is how a green run produces a short table.

  10. All items accounted for

    Every item either written or parked, with a count that reconciles against the source. That reconciliation is the only proof the job did what it claimed.

Four SDK mechanisms, and the defect each one prevents. The third row is the one that returns wrong answers without any error.
MechanismReplacesPreventsWhere it appears in a stem

Paginator

one list call, or a hand-written NextToken loop

a silently partial result — the first page looks like the whole answer

"the script reported 1,000 objects but the bucket has 40,000"

Waiter

while True: sleep(30) against a describe call

an unbounded wait with no backoff and no error on a resource that never arrives

"the deployment script hangs when the cluster fails to start"

Partial-failure handling

trusting the HTTP status of a batch call

silent data loss inside a 200 response — no retry mode covers it

"the producer logs success and records are missing downstream"

Retry mode

a hand-rolled retry, or none

a retry storm from synchronised immediate retries; adaptive also slows the client before it is throttled

"throttling rises until almost nothing succeeds"

Key terms: Paginator, SDK retry mode, Exponential backoff with jitter, Multipart upload