technology
AWS, sized to what you actually run
AWS has around two hundred services and most projects need six of them. The expensive mistake is not choosing the wrong cloud — it is adopting an architecture built for a scale you do not have, then paying for it in both bill and complexity every month afterwards.
Start smaller than the reference architecture
AWS reference diagrams are drawn for the largest plausible customer. Copied onto a product with a few thousand users, they produce a system that costs more to operate than to build.
We start from the load you actually have and the load you can evidence is coming. That usually means a container service, a managed database, object storage, a CDN, and a queue. Not a service mesh, not multi-region active-active, not a bespoke event bus.
Those things are all correct at some scale. The point is to know which scale, and to build the seams that let you add them later without a rewrite.
Cost is an architectural property
The bill is decided by the design, not by the finance team. NAT gateway traffic, cross-availability-zone transfer, and per-request charges on chatty services are the three that surprise people most often, and all three are architecture choices.
We tag resources by environment and workload from the start, so the bill can be read by feature rather than as one number. And we put a budget alarm in place before launch, because the cost of finding out about a runaway process at month end is the entire month.
Where it fits, we will recommend the boring option — a right-sized instance rather than a serverless design that bills per invocation on a predictable, constant workload.
Reproducible, or it does not count
Everything is defined in code. If the environment cannot be recreated from an empty account, you do not have infrastructure — you have a configuration that happens to be running.
That matters most in the worst week you will have. Recovering from a bad change is a deploy when infrastructure is code, and an archaeology exercise when it is not.
Access follows the same rule: roles rather than long-lived keys, least privilege by default, and no credential that lives in a repository.
TypeScript · src/handlers/process-orders.ts
The AWS default that quietly multiplies your bill. A Lambda consuming SQS reports success or failure for the whole batch, so one poison message sends all ten back to the queue and every one of them is processed again — repeatedly, until the message expires. Reporting partial failures returns only the ones that actually failed. It costs a few lines and it is the difference between paying for a batch once and paying for it until someone notices the duplicate side effects.
import type { SQSEvent, SQSBatchResponse } from 'aws-lambda';
// Requires ReportBatchItemFailures on the event source mapping. Without that
// setting this return value is ignored and the behaviour is unchanged, which
// is an easy thing to configure half of.
export async function handler(event: SQSEvent): Promise<SQSBatchResponse> {
const batchItemFailures: { itemIdentifier: string }[] = [];
for (const record of event.Records) {
try {
const order = JSON.parse(record.body) as OrderMessage;
// SQS is at-least-once by design, so this will arrive twice eventually.
// Keyed on the message id, the second delivery is a no-op.
await processOrder(order, { idempotencyKey: record.messageId });
} catch (err) {
// A message that can never succeed should not be retried forever.
// Past the redrive limit it belongs in the dead-letter queue, where
// someone can look at it, rather than back in the main queue.
if (isPermanent(err)) {
await sendToDeadLetter(record, err);
continue; // handled: do not ask for a retry
}
log.error('order.failed', { messageId: record.messageId, err });
batchItemFailures.push({ itemIdentifier: record.messageId });
}
}
// Only the genuinely failed ones go back. Returning nothing here - or
// throwing - redelivers all ten, reprocesses the nine that worked, and
// bills you for each attempt.
return { batchItemFailures };
}Who works on this
Nayemul Saheb
CEO & Software Engineer
Infrastructure, deployment and what it costs to run.
When AWS is more than you need
If you are running one application with predictable traffic and no compliance requirement, a managed platform will cost less in both money and attention. AWS earns its complexity when you need fine-grained control, specific compliance boundaries, or services that genuinely have no equivalent elsewhere. Choosing it because it is the default is how teams end up with a platform engineer they did not budget for.
Common questions
- AWS, Azure, or Google Cloud?
- Whichever your team can operate at three in the morning. The feature gaps between the three are narrower than the gap between a platform your engineers know and one they are learning during an incident. We work across all three and have no commission in the answer.
- Should we go serverless?
- For spiky or unpredictable workloads, yes — you stop paying for idle. For steady, constant load it is frequently more expensive than a right-sized container, and it constrains how you debug. It is a workload-shape decision, not a modernity one.
- Can you reduce our existing AWS bill?
- Usually. The recurring wins are unattached storage volumes, over-provisioned databases, data transfer crossing availability zones unnecessarily, and environments left running out of hours. We start with a tagged breakdown, because you cannot cut what you cannot attribute.
- Do you set up multi-region?
- Only when there is a stated recovery objective that single-region cannot meet. Multi-region roughly doubles operational complexity, and complexity causes outages too. Most teams asking for it need better backups and a tested restore instead.
Related services
- Cloud Application
- DevOps
- Custom Software
Built with
- Docker
- Node.js
- Python
- TypeScript