Performance & Cost Best Practices
These are the defaults every SDK-heavy service should set before scaling, not discover from a slow endpoint or a surprise invoice.
Search across all documentation pages
These are the defaults every SDK-heavy service should set before scaling, not discover from a slow endpoint or a surprise invoice.
Walk the list once now, then use it as a review checklist before shipping any code that constructs clients, loops over items, or runs calls in parallel.
max_pool_connections (boto3) or an Agent({ maxSockets }) on NodeHttpHandler (SDK v3) to roughly the number of concurrent in-flight calls on that client.Build the client once; call it many times.
# --- Python (boto3) ---
import boto3
from botocore.config import Config
cfg = Config(max_pool_connections=25)
s3 = boto3.client("s3", config=cfg) # module scope, reused everywhere// --- TypeScript (AWS SDK v3) ---
import { S3Client } from "@aws-sdk/client-s3";
import { NodeHttpHandler } from "@smithy/node-http-handler";
import { Agent } from "node:https";
const s3 = new S3Client({
requestHandler: new NodeHttpHandler({ httpsAgent: new Agent({ maxSockets: 25 }) }),
}); // module scope, reused everywhereSendMessageBatch (SQS), BatchWriteItem (DynamoDB), and DeleteObjects (S3) each replace many single-item calls with one.BatchWriteItem, 1,000 for DeleteObjects - exceeding the limit is a validation error, not a partial batch.Failed, UnprocessedItems, and Errors fields can carry partial failures even when the call itself succeeds.UnprocessedItems often means the partition was momentarily throttled.TransactWriteItems instead when items must all succeed or all fail together.Batch the writes; check the partial-failure list every time.
# --- Python (boto3) ---
resp = ddb.batch_write_item(RequestItems=request_items)
unprocessed = resp.get("UnprocessedItems", {}) # always check this// --- TypeScript (AWS SDK v3) ---
const resp = await ddb.send(new BatchWriteItemCommand({ RequestItems: requestItems }));
const unprocessed = resp.UnprocessedItems ?? {}; // always check thisasyncio.Semaphore with aioboto3, or a worker-pool pattern, in Python; use p-limit or a small helper in TypeScript.Cap in-flight calls; let retries handle the rest.
# --- Python (boto3) ---
sem = asyncio.Semaphore(10) # at most 10 concurrent calls// --- TypeScript (AWS SDK v3) ---
const limit = pLimit(10); // at most 10 concurrent callsWaitTimeSeconds on SQS ReceiveMessage, or use waiters, instead of a tight poll loop.Wait for data instead of asking for it repeatedly.
# --- Python (boto3) ---
resp = sqs.receive_message(QueueUrl=queue_url, WaitTimeSeconds=20)// --- TypeScript (AWS SDK v3) ---
const resp = await sqs.send(new ReceiveMessageCommand({ QueueUrl: queueUrl, WaitTimeSeconds: 20 }));Ramp traffic up and down; watch tail latency and throttling, not just the average.
# --- Python (boto3) ---
# locustfile.py: wait_time models think time, not max throughput
from locust import HttpUser, task, between
class ServiceUser(HttpUser):
wait_time = between(1, 3)
@task
def call_endpoint(self):
self.client.get("/sessions/user-123")// k6 script targeting a Node/SDK v3-backed endpoint
export const options = {
stages: [
{ duration: "2m", target: 50 },
{ duration: "5m", target: 50 },
{ duration: "2m", target: 0 },
],
};Stack versions: This page was written for boto3 1.43.x (Python 3.10+) and the AWS SDK for JavaScript v3 (Node.js 18+).
Reviewed by Chris St. John·Last updated Jul 23, 2026