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
- 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.
- Read the source with a paginator
Not a single list call.
ListObjectsV2caps at 1,000 keys per page andScanat 1 MB, so a single call returns a page and looks like a complete answer. - 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
| Mechanism | Replaces | Prevents | Where it appears in a stem |
|---|---|---|---|
one list call, or a hand-written | 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 |
| 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" |
a hand-rolled retry, or none | a retry storm from synchronised immediate retries; | "throttling rises until almost nothing succeeds" |
Key terms: Paginator, SDK retry mode, Exponential backoff with jitter, Multipart upload