DVA-C03 Optimization: Lambda Concurrency, Memory, Messaging, Caching and Bottlenecks
Optimizing an application on AWS means getting the required speed, scale and reliability for the least cost by tuning how it runs: how much concurrency it gets, how much memory and compute it uses, how it sends and receives messages, what it caches, and where its time actually goes. For the DVA-C03 exam this covers Lambda provisioned and reserved concurrency, SnapStart, EC2 Auto Scaling, thread and connection pools, Lambda memory and architecture, AWS Compute Optimizer, SQS and SNS batching and filtering, CloudFront, ElastiCache and DAX caching, CloudWatch metrics that expose bottlenecks, and AWS AI tools such as Kiro that analyze those metrics. This lesson covers all six official skills for Task 4.3, which now includes the Lambda performance tuning that DVA-C02 placed in Task 1.2.
On this page10 sections
- How Lambda concurrency scales, and why requests get throttled
- Provisioned concurrency auto scaling versus SnapStart
- Scaling EC2 applications with Auto Scaling policies and warm pools
- Thread pools, connection pools and reusing clients
- Optimizing Lambda memory, compute power and architecture
- Rightsizing with AWS Compute Optimizer
- Optimizing SQS and SNS messaging
- Caching at the edge with CloudFront
- Tuning ElastiCache and DAX from their metrics
- Finding bottlenecks with CloudWatch and AWS AI tools
- Choose between reserved concurrency, provisioned concurrency (with Application Auto Scaling) and SnapStart for throttling, burst and cold-start problems
- Scale EC2 applications with target tracking, warm pools and a backlog-per-instance metric, and manage thread, connection and SDK client pools
- Optimize Lambda memory, compute and architecture with Lambda Power Tuning, and rightsize resources with AWS Compute Optimizer
- Reduce SQS and SNS requests and Lambda invocations with long polling, batch APIs, batching windows, SQS buffering and event filtering
- Raise cache efficiency with CloudFront cache and origin request policies, TTLs and versioned file names, and tune ElastiCache and DAX from their metrics
- Detect bottlenecks with CloudWatch percentiles, queue metrics, Lambda Insights and Contributor Insights
- Use Kiro with the AWS CloudWatch MCP server to find optimization opportunities and review its suggestions against the evidence
How Lambda concurrency scales, and why requests get throttled
Lambda concurrency is the number of requests a function is handling at the same moment, and each one needs its own execution environment. Two separate limits decide whether a request gets an environment: the account concurrency quota for the Region (1,000 by default, shared by every function, and raisable through Service Quotas) and the per-function scaling rate, which lets each function add up to 1,000 execution environments every 10 seconds. A request that exceeds either limit is throttled: a synchronous caller such as API Gateway receives a 429 TooManyRequestsException.
That distinction explains a common surprise. A raised account quota does not help a function that must go from idle to several thousand concurrent requests in a few seconds, because the function cannot create environments faster than its scaling rate. When such a burst happens at a predictable time, have the environments ready before it starts by scheduling provisioned concurrency.
| Setting | What it does | Cost | Use it to |
|---|---|---|---|
| Reserved concurrency | Sets aside part of the account quota for one function, and also caps that function at the same number | No charge | Guarantee that a critical function can always reach N concurrent executions, even after other functions are added; or cap a noisy function so it cannot drain the pool |
| Provisioned concurrency | Keeps N environments initialized and ready on a published version or alias | Charged for the configured amount for as long as it is set, used or not | Remove cold-start latency and pre-warm for bursts the scaling rate cannot absorb |
| Account quota increase | Raises the Regional pool shared by all functions | No charge | Run more total concurrency; it does not speed up per-function scaling or protect one function from another |
Capping the noisy function with reserved concurrency protects the API today, but a newly added function can still consume what is left; reserving concurrency on the function that must be protected is the guarantee. Lambda always keeps at least 100 units of unreserved concurrency for functions without a reservation. Reserved concurrency does nothing for cold starts. The basic Lambda configuration settings themselves are covered in the Lambda development lesson for Task 1.2.
Provisioned concurrency auto scaling versus SnapStart
Provisioned concurrency and SnapStart both shorten slow starts, but provisioned concurrency keeps environments running ahead of demand and you pay for them, while SnapStart restores a cached snapshot of an initialized environment only when one is needed. Provisioned concurrency is set on a published version or an alias, never on the unpublished $LATEST.
A fixed provisioned amount is usually wrong for traffic with a daily shape. Two CloudWatch metrics show it: ProvisionedConcurrencySpilloverInvocations counts invocations that found no provisioned environment free and ran on standard (cold-start) environments, and ProvisionedConcurrencyUtilization shows how much of the configured amount is in use. High spillover means too little at peak; low utilization means paying for idle environments. Register the alias as a scalable target in Application Auto Scaling and combine:
- a target tracking policy on the predefined
LambdaProvisionedConcurrencyUtilizationmetric (for example, a target of 0.6), which adds and removes environments as utilization moves; and - scheduled actions that raise the minimum before a ramp you can predict and lower it afterwards, because target tracking reacts to load and needs time to initialize new environments.
Lambda SnapStart takes a snapshot of the memory and disk state after initialization when you publish a version, then resumes new environments from that snapshot. It is available for Java 11 and later, Python 3.12 and later and .NET 8 and later, works only on published versions (and aliases that point to them), and cannot be combined with provisioned concurrency, Amazon EFS or ephemeral storage above 512 MB. For Java there is no extra SnapStart charge. For Python and .NET, a snapshot caching charge (based on memory) is billed for every published SnapStart version for as long as it stays active, whether or not it receives traffic, and each restore is charged; the duration of initialization code and runtime hooks is billed too. That is still different from provisioned concurrency, which bills for the configured number of running environments.
| Need | Better fit |
|---|---|
| Faster startups for spiky, unpredictable traffic without keeping initialized environments running | SnapStart on a published version |
| Consistently low, double-digit-millisecond start latency for a latency-critical path, or pre-warming for a known burst | Provisioned concurrency, scaled by Application Auto Scaling |
| Both on the same version | Not possible: choose one |
Priming makes SnapStart restores faster still. Work that happens only on the first request (class loading, JSON mapper set-up, building SDK request classes) is not in the snapshot unless it ran before the snapshot was taken. Running those code paths in a beforeCheckpoint runtime hook puts them in the snapshot. The afterRestore hook runs after each restore, so work placed there is repeated on every new environment; use it for state that must be unique per environment, such as refreshing credentials, random seeds or connections.
Scaling EC2 applications with Auto Scaling policies and warm pools
Amazon EC2 Auto Scaling adds and removes instances so that capacity follows demand, and the policy type decides how much configuration that takes. A target tracking policy is the simplest: you name a metric and a target value (for example, average CPU utilization of 50%), and Auto Scaling creates and manages the CloudWatch alarms and adjusts capacity in both directions.
| Policy or feature | How it works | Fits |
|---|---|---|
| Target tracking | Keeps a metric near a target value; alarms are managed for you | Most workloads; the least configuration |
| Step scaling | Your own alarms, with different adjustments for different breach sizes | Fine-grained control when you want it |
| Simple scaling | One adjustment per alarm, then a cooldown | Older; one adjustment then a cooldown, so slower to react; AWS recommends target tracking or step scaling instead |
| Scheduled actions | Set capacity at fixed times | Known, repeating patterns |
| Predictive scaling | Forecasts from history and launches ahead of the forecast | Cyclical load; it cannot anticipate unpredictable surges |
| Warm pool | Keeps pre-initialized instances (usually stopped) ready to join the group | Instances that take minutes to boot and load data |
A warm pool answers slow-booting instances: the bootstrap work (downloading reference files, building a local cache) runs once when the warm instance is created, and the stopped instance then starts quickly when the group scales out. Stopped instances in a warm pool cost only their attached storage (and any Elastic IP addresses), so it is cheaper than raising the minimum number of running instances.
Queue workers need a queue-based metric. When workers spend their time waiting on another service, CPU stays low while the SQS backlog grows, so a CPU target never scales out. The documented pattern is a custom backlog per instance metric: ApproximateNumberOfMessagesVisible divided by the number of in-service instances, published to CloudWatch and used by a target tracking policy. The target value is the acceptable latency divided by the average processing time per message: if a message may wait 45 seconds and each takes 0.5 seconds, each instance can carry 90 messages.
Thread pools, connection pools and reusing clients
Thread and connection pools improve concurrency inside one process by reusing a bounded set of expensive resources instead of creating a new one for every request. Unbounded creation looks fast under light load and fails under heavy load.
- Bounded thread pools. Creating a thread per request lets a traffic peak start thousands of threads, each with its own stack, until the JVM runs out of memory. A fixed-size pool from an
ExecutorServicewith a bounded work queue keeps parallelism while capping memory, and the queue (or a rejection policy) applies back-pressure. - Database connection pools. Opening and closing a database connection per request adds TCP, TLS and authentication set-up to every request. A connection pool library in the application keeps connections open and lends them out, without another AWS service. For Lambda, where many short-lived environments each hold connections, Amazon RDS Proxy pools connections on the database side (covered in the Lambda development lesson for Task 1.2).
- SDK HTTP connection pools. Each AWS SDK client keeps its own HTTP connection pool. In boto3 the default
max_pool_connectionsis 10, so when more threads than that share one client, they wait for connections and urllib3 warns that the pool is full and discards the extra connections. Share one client across the threads (boto3 clients are thread-safe) and size its pool to the thread count withbotocore.config.Config(max_pool_connections=...).
import boto3
from botocore.config import Config
s3 = boto3.client("s3", config=Config(max_pool_connections=25)) # 25 worker threads
In Lambda, create SDK clients and database connections outside the handler. Code in the initialization phase runs once per execution environment, and later invocations in the same environment reuse the client and its open connections. Creating a client inside the handler repeats that set-up on every invocation.
Optimizing Lambda memory, compute power and architecture
Lambda has one performance dial, memory, and it also sets compute power: CPU is allocated in proportion to the memory setting. At 1,769 MB a function has the equivalent of one full vCPU, and the maximum of 10,240 MB brings up to 6 vCPUs. Memory runs from 128 MB to 10,240 MB.
- CPU-bound, single-threaded code speeds up with memory until about one vCPU; beyond that a single thread cannot use more.
- CPU-bound, multi-threaded code only runs in parallel when there are several vCPUs. Four worker threads at 768 MB share less than half a vCPU and simply take turns; only enough memory for several vCPUs (about 1,769 MB per vCPU) lets them run at once. Memory is the only setting that changes CPU: ephemeral storage sizes
/tmpdisk and the timeout sets the time limit. - I/O-bound code, which mostly waits on DynamoDB or an HTTP API, rarely gets faster with more memory.
Cost is memory multiplied by duration (GB-seconds) plus a per-request charge. Raising memory can lower cost when duration falls by more than the memory rises. Lowering memory saves money when duration stays the same: a function set to 3,008 MB that peaks at 250 MB and runs just as fast at 1,024 MB costs about a third as much per invocation at 1,024 MB. Find the balance by measurement, not guesswork: the open-source AWS Lambda Power Tuning tool is an AWS Step Functions state machine that invokes a function at several memory sizes with a payload you supply and charts cost against duration. It needs no production history, so it suits a function before launch.
Architecture is the second lever. Functions can run on x86_64 or arm64 (AWS Graviton), and AWS prices arm64 duration lower, for better price performance in many workloads. Pure Python, Node.js or Java code usually moves unchanged, but dependencies with compiled native extensions must be built for arm64: a package built on an x86_64 machine fails to import on arm64 with errors such as invalid ELF header. Rebuild the package with the arm64 wheels (for example, in an arm64 build container or with pip install --platform manylinux2014_aarch64 --only-binary=:all:). Memory and runtime changes do not fix a wrong-architecture binary.
Rightsizing with AWS Compute Optimizer
AWS Compute Optimizer recommends better-sized resources from each resource's actual CloudWatch utilization history. After you opt in, it analyzes EC2 instances, Auto Scaling groups, EBS volumes, Lambda function memory, Amazon ECS services on AWS Fargate (task CPU and memory) and other resource types, and reports over-provisioned, under-provisioned and optimized resources with the projected savings or risk. (Cost Explorer rightsizing recommendations cover EC2 only.)
Two settings decide how good the recommendations are:
| Gap | Fix |
|---|---|
| Memory not analyzed for EC2 (the detail page says so) | EC2 does not report memory to CloudWatch by itself. Install the CloudWatch agent to publish memory utilization (or ingest it from a supported observability tool). |
| The look-back window is shorter than the workload's busiest cycle, so the peak is never seen | Change the rightsizing look-back period preference from the default 14 days to 32 days (no extra charge), or turn on the paid enhanced infrastructure metrics for 93 days on EC2, Auto Scaling groups and RDS. |
Compute Optimizer needs a minimum amount of metric history before it recommends anything, so it cannot size a function that has never run; use Power Tuning there. Review a "smaller instance" recommendation against peaks and memory before acting on it.
Optimizing SQS and SNS messaging
Optimizing messaging means sending fewer, fuller API requests and invoking consumers only for work they will keep, because SQS and SNS charge per request and Lambda charges per invocation.
Amazon SQS
| Setting | What it changes |
|---|---|
ReceiveMessageWaitTimeSeconds (queue) or WaitTimeSeconds (per call), 1–20 seconds | Long polling: the call waits for messages to arrive and returns as soon as one does, so empty responses and their cost drop without delaying messages. 0 means short polling. |
MaxNumberOfMessages, up to 10 | How many messages one receive can return; it does not stop empty receives on a quiet queue. |
SendMessageBatch, DeleteMessageBatch, ChangeMessageVisibilityBatch | Up to 10 entries per request, cutting request count up to tenfold. Check each entry's result: a batch can partly fail. |
DelaySeconds | Hides new messages for a while; it adds latency and saves nothing. |
VisibilityTimeout | How long a received message stays hidden; set it from processing time, not for performance. |
For a Lambda event source mapping on an SQS standard queue, the batch size can go up to 10,000 messages, and a batching window (MaximumBatchingWindowInSeconds, up to 300 seconds) lets Lambda wait to fill a batch. Any batch size above 10 requires a batching window of at least 1 second. When small messages trickle in and invocations carry only one or two, a larger batch size plus a batching window that fits the allowed delay cuts the number of invocations sharply. The batch is invoked when the window ends, the batch size is reached or the payload reaches 6 MB, whichever comes first. FIFO queues keep a maximum batch size of 10 and do not support a batching window.
Amazon SNS fan-out
- Buffer with SQS. An SNS topic invoking Lambda directly invokes it asynchronously, so concurrency follows the publish rate and can overwhelm a downstream database; repeated failures are eventually dropped or dead-lettered. Subscribe an SQS queue to the topic and consume it with an event source mapping whose maximum concurrency caps the pollers' invocations: messages wait in the queue instead of being lost.
- Batch publishing.
PublishBatchsends up to 10 messages to a topic in one request. Each message is still delivered to subscribers individually, so subscribers see no change. - Filter as early as you can. An SNS subscription filter policy stops unwanted messages before they reach the queue, but it is set on the subscription, which belongs to the topic owner. If you own only the queue and the function, add filter criteria to the Lambda event source mapping: Lambda evaluates each message and invokes the function only for matches. For SQS, messages that do not match are deleted from the queue, so filter only on messages you truly want to discard.
{"Filters": [{"Pattern": "{\"body\": {\"status\": [\"SHIPPED\"]}}"}]}
Body filtering works on JSON bodies; with raw message delivery off, the body is the SNS envelope and the original message sits inside its Message string.
Caching at the edge with CloudFront
A CloudFront cache works well when many requests share one cache key and objects stay cached long enough to be reused; the cache policy controls both the cache key and the TTLs.
Cache key versus what the origin receives
Everything in the cache policy's key (query strings, cookies, headers) splits the cache: including all cookies, all query strings or User-Agent creates a separate cached copy per user or device, and the hit ratio collapses. Put only the values that actually change the response in the cache policy. Values the origin needs for other reasons (a session cookie for logging, an authorization header) go in an origin request policy, which forwards them to the origin without adding them to the cache key. Values in the cache key are forwarded automatically.
TTL settings
| Setting | When it applies |
|---|---|
| Minimum TTL | The least time CloudFront caches an object. If it is above 0, CloudFront caches objects for at least that long even when the origin sends Cache-Control: no-cache, no-store or private; browsers still follow the origin's headers. |
| Default TTL | Used only when the origin sends no max-age, s-maxage or Expires. |
| Maximum TTL | Caps how long an origin-supplied max-age or Expires can keep an object cached. |
Releases without invalidations
Invalidations remove objects from edge caches, but they take time, are charged per path beyond a monthly free allowance and do not clear copies already in browsers. The durable pattern for single-page apps is versioned file names: the build adds a content hash to each JS and CSS file name, so those files can be cached for a year (Cache-Control: max-age=31536000, immutable) and a new release simply references new names. The entry point, index.html, is served with no-cache so browsers revalidate it on each load and pick up the new names at once.
CloudFront publishes a CacheHitRate metric (one of the distribution's additional metrics) to confirm a change worked.
Tuning ElastiCache and DAX from their metrics
A cache helps only while it can hold the working set and the requests can be answered from it, and CloudWatch metrics show which of those is failing. Cache patterns themselves (lazy loading, write-through, choosing an engine) are covered in the data stores lesson for Task 1.3; this section is about tuning a cache that already exists.
Amazon ElastiCache for Valkey and Redis OSS
| Metric | What it tells you |
|---|---|
Evictions | Keys removed to make room for new ones; a steady climb means the data no longer fits |
DatabaseMemoryUsagePercentage | Share of available memory in use; near 100% confirms memory pressure |
CacheHitRate | Share of reads answered from the cache; it falls as evictions rise |
EngineCPUUtilization | CPU used by the engine thread; high values point to a compute limit, not a memory one |
When evictions climb, memory sits near 100% and CPU is low, the fix is more memory: scale up to a node type with more RAM, or, for Valkey and Redis OSS, enable cluster mode and add shards so the data set is spread out. High EngineCPUUtilization with spare memory points to a node with more compute or more shards instead. Memcached reports a different metric set (for example, CPUUtilization and BytesUsedForCacheItems) and scales out by adding nodes.
DynamoDB Accelerator (DAX)
DAX is an in-memory, DynamoDB-compatible cache for read-heavy workloads that accept eventually consistent reads, returning cached items in microseconds. Its limits matter:
- Reads with
ConsistentRead=trueand transactional reads (TransactGetItems) are passed straight through to DynamoDB and are not cached. Code that sets strong consistency on every call gains nothing from DAX: if the data may be a few seconds stale, send eventually consistent reads through the DAX client. - Items stay in the item cache for a TTL set in the cluster's parameter group (5 minutes by default).
- Writes go through DAX to DynamoDB (write-through), so DAX does not reduce write cost or write throttling.
Finding bottlenecks with CloudWatch and AWS AI tools
A bottleneck is found by measuring the slow part directly: the right statistic of the right metric, then the component behind it. Setting up alarms, dashboards and tracing is covered in Task 4.2, and log-based root-cause analysis in Task 4.1; this section is about reading the data for optimization.
Metrics that expose bottlenecks
- Percentiles, not averages. An average can look healthy while a small share of requests is many times slower. Graph a percentile such as
p99(or p95/p90) to see the slow tail and track it over time. - Queue lag. For SQS consumers,
ApproximateAgeOfOldestMessageshows how long the oldest message has waited (compare it with the processing deadline) andApproximateNumberOfMessagesVisibleshows the backlog waiting to be received. Both rising together means consumers are falling behind.ApproximateNumberOfMessagesNotVisiblecounts in-flight messages,ApproximateNumberOfMessagesDelayedcounts delayed ones andNumberOfEmptyReceivesmeasures polling waste. - Lambda Insights. The Lambda Insights extension (a layer) adds system metrics such as CPU time, memory used and network for each function, which shows whether a function is CPU-bound, memory-bound or waiting.
- DynamoDB hot keys. Throttling while consumed capacity is well below provisioned capacity usually means a few partition keys get most of the traffic. CloudWatch Contributor Insights for DynamoDB, enabled per table or index, shows the most accessed and most throttled keys with no code changes.
AWS AI tools for optimization
AI coding assistants can find optimization opportunities faster, provided they see real data. Kiro connects to tools through the Model Context Protocol (MCP): adding the open-source AWS CloudWatch MCP server to Kiro's MCP configuration (.kiro/settings/mcp.json in the workspace, or ~/.kiro/settings/mcp.json for the user) lets the agent query metrics, active alarms and CloudWatch Logs Insights itself while it reads the code.
Typical AI-suggested fixes are code-efficiency changes, such as replacing a loop of GetItem calls with one BatchGetItem (up to 100 items or 16 MB per request), and resource changes, such as lowering an over-provisioned memory setting. Accept a suggestion only when it keeps behaviour and the evidence supports it:
BatchGetItemcan return some keys inUnprocessedKeys(for example, when throttled or over the size limit). Those items were not read; they are not missing. Resend them, with exponential backoff, until none remain.- Check a memory or concurrency suggestion against the stated goal. A setting change that does not shorten measured duration or latency only adds cost, and one that keeps duration the same at a lower setting saves money.
Tip. Expect scenario questions in which a developer must make an application faster, cheaper or more scalable under a stated constraint: no paying for idle environments, synchronous responses, a maximum wait, no new service, or no access to a resource another team owns. You need to tell near-identical settings apart (reserved versus provisioned concurrency, SnapStart versus provisioned concurrency, minimum versus default TTL, cache policy versus origin request policy, long polling versus MaxNumberOfMessages, subscription filter policy versus event source mapping filter) and to read CloudWatch metrics such as spillover invocations, Evictions, ApproximateAgeOfOldestMessage and percentiles to name the bottleneck. Several items are multiple response, and AI-tool items ask how to give Kiro live metrics and whether its suggested change keeps the application correct.
- Lambda throttles (429) when the account quota or the per-function scaling rate of 1,000 environments every 10 seconds is exceeded; schedule provisioned concurrency for known bursts.
- Reserved concurrency guarantees and caps a function's concurrency at no charge; provisioned concurrency pre-initializes environments on a version or alias and is billed while configured.
- Scale provisioned concurrency with Application Auto Scaling target tracking on utilization plus scheduled actions; SnapStart restores from a snapshot instead of keeping environments running, cannot be combined with provisioned concurrency, and priming belongs in beforeCheckpoint.
- Lambda CPU scales with memory (one vCPU at 1,769 MB, up to 6 at 10,240 MB); measure the cost–speed balance with Lambda Power Tuning and rebuild native dependencies for arm64.
- Compute Optimizer rightsizes EC2, Lambda and ECS on Fargate from history; EC2 memory needs the CloudWatch agent, and the look-back can be extended to 32 or 93 days.
- Use long polling, 10-entry batch APIs, larger event source mapping batches with a batching window, SNS-to-SQS buffering with maximum concurrency, and event source mapping filters to cut requests and invocations.
- Keep the CloudFront cache key minimal and forward extras with an origin request policy; only a minimum TTL above 0 overrides an origin's no-cache; use hashed file names instead of invalidations.
- Rising Evictions with memory near 100% means scale ElastiCache up or out with shards; DAX does not cache strongly consistent or transactional reads.
- Graph p99 rather than averages, watch ApproximateAgeOfOldestMessage for queue lag, and use Contributor Insights to find DynamoDB hot keys.
- Give Kiro live data through the AWS CloudWatch MCP server, and accept AI suggestions only when they preserve behaviour, such as retrying BatchGetItem UnprocessedKeys.
Frequently asked questions
What is the difference between reserved and provisioned concurrency in AWS Lambda?
Reserved concurrency sets aside part of the Region's account concurrency for one function and also caps that function at the same number; it costs nothing and does not reduce cold starts. Provisioned concurrency keeps a chosen number of execution environments initialized on a published version or alias so requests start without a cold start; it is billed for as long as it is configured. Use reserved concurrency to guarantee or limit capacity, and provisioned concurrency to remove start-up latency or pre-warm for a known burst.
Why does a Lambda function return 429 errors even after the account concurrency quota was raised?
Lambda also limits how fast each function scales: a function can add up to 1,000 execution environments every 10 seconds. A burst that needs more new environments than that rate allows is throttled with 429 errors even though the account quota has room. For a burst at a predictable time, schedule provisioned concurrency so the environments are ready before it starts.
Should I use Lambda SnapStart or provisioned concurrency?
Use SnapStart when you want faster cold starts for Java 11+, Python 3.12+ or .NET 8+ functions without keeping initialized environments running: it restores new environments from a snapshot taken when the version is published. Java pays nothing extra; Python and .NET pay a snapshot caching charge for each active SnapStart version plus a charge per restore. Use provisioned concurrency when a latency-critical path needs consistently minimal start latency or must be pre-warmed for a burst, and scale it with Application Auto Scaling. The two cannot be enabled on the same function version.
Does increasing Lambda memory make a function faster?
It makes CPU-bound functions faster, because Lambda allocates CPU in proportion to memory: one vCPU at 1,769 MB and up to 6 vCPUs at 10,240 MB. Multi-threaded CPU-bound code needs several vCPUs to run in parallel. Functions that mostly wait on databases or APIs usually see little change, so lowering their memory can cut cost with the same duration. Measure with AWS Lambda Power Tuning rather than guessing.
How do I reduce the number of Lambda invocations from an SQS queue?
Raise the event source mapping's batch size (up to 10,000 for a standard queue) and set a batching window (MaximumBatchingWindowInSeconds, up to 300 seconds, and at least 1 second when the batch size is above 10) so Lambda waits to fill each batch. If the function discards many messages, add filter criteria to the event source mapping so it is invoked only for matching messages; non-matching SQS messages are deleted from the queue.
How do I improve a low CloudFront cache hit ratio?
Remove everything from the cache policy's cache key that does not change the response, such as all cookies, unused query strings and the User-Agent header, and forward values the origin still needs with an origin request policy. Set TTLs that let objects stay cached, and use content-hashed file names for static assets so they can be cached for a long time without invalidations. Check the result with the distribution's CacheHitRate metric.
Why does DynamoDB Accelerator (DAX) not reduce read latency for my application?
DAX caches only eventually consistent reads. GetItem or Query calls with ConsistentRead set to true, and transactional reads such as TransactGetItems, are passed through to DynamoDB and never cached, so latency and consumed read capacity stay the same. If the data can be a few seconds stale, send eventually consistent reads through the DAX client. DAX also does not reduce write cost, because writes go through to the table.
How can Kiro help find performance bottlenecks in an AWS application?
Kiro can analyze code and propose efficiency fixes, and when the AWS CloudWatch MCP server is added to its MCP configuration it can query CloudWatch metrics, active alarms and Logs Insights itself instead of relying on data pasted into the chat. Its suggestions, such as batching DynamoDB reads or lowering an over-provisioned memory setting, should be accepted only when they keep the same behaviour and the measured evidence supports them.
Source
This lesson covers the "Troubleshooting and Optimization" domain of the official DVA-C03 exam guide. Vendors revise their guides — check the source for the current version.
- AWS Certified Developer – Associate (DVA-C03) exam guide — Amazon Web Services
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.