Preparing your learning space...
86% through Production & Reliability tutorials
An FDE's solution rarely stays at one customer. It gets adopted by another team, another department, or a second company — and what worked for one user quietly stops working for ten. This tutorial covers the two directions of scale: growing a solution from one customer to many, and growing a single deployment to handle more usage. They're different problems, and they break in different ways.
"Scaling customer solutions" actually mixes two very different problems:
The first is mostly a design problem — make it configurable, keep customers isolated. The second is a capacity problem — add resources, queue work, optimize hot paths. The tools for each are different, and mixing them up wastes time.
The most common failure in customer work is building each new customer a slightly different version of the same thing. It feels efficient — the code is already 90% there, you just adjust it. But every adjustment becomes a fork that must be maintained forever.
Customer A -> solution A (with A's tweaks) Customer B -> copy of A + B's tweaks Customer C -> copy of A/B + C's tweaks
Fixes don't reach everyone. A bug fixed in Customer B's copy may still live in A and C. One customer asks for a new feature, and now it needs to be built three times. After a handful of customers, every new one costs more than the last instead of less. That's the opposite of scaling.
The fix: build the solution once, and make the differences between customers data, not code.
Solution (one codebase) + Customer A config -> A's behavior + Customer B config -> B's behavior + Customer C config -> C's behavior
The same code, different configuration. A new customer becomes "add a config file and turn it on," not "fork the codebase and start adjusting." This is what turns the FDE's work from custom development into something closer to a product.
# customers/acme/config.yaml
customer: acme
currency: USD
approval_threshold: 5000
notification_email: ops@acme.com
features:
- expense_approvals
- invoice_matching
retention_days: 90
import yaml
def load_customer(customer_id):
with open(f"customers/{customer_id}/config.yaml") as f:
return yaml.safe_load(f)
acme = load_customer("acme")
if acme["approval_threshold"]:
requires_approval = amount > acme["approval_threshold"]
Acme's rule — amounts over $5,000 need approval — lives in a config file, and the code just reads it. When Acme changes the threshold, someone edits YAML; nobody touches the codebase. Features the customer hasn't paid for or isn't ready for stay turned off in their config.
The rule of thumb: the moment you see the same tweak requested twice, turn it into a config option. The third customer is where the pattern pays off.
Configuration is half of multi-customer. The other half is data isolation. Customers sharing one database is fine if every query is scoped to the customer — but "if" is doing heavy lifting there.
customer_id, every query filters on it. Simple, but one wrong query leaks data.SELECT * FROM invoices
WHERE customer_id = :acme_id
AND created_at > NOW() - INTERVAL '30 days';
The SQL looks trivial, but it's exactly where multi-customer systems fail — one unscoped query and customer A sees customer B's invoices. Whatever strategy you pick, test the isolation. A multi-tenant system where tenant A can read tenant B's data is a data breach waiting for an auditor to find it.
Usage scaling starts with numbers. Before buying more hardware, answer three questions:
The failure mode to avoid is discovering your capacity at the moment you need it. A load test that runs the expected peak against your deployment tells you the answer in an afternoon, quietly, instead of during a customer-visible slowdown.
Vertical: one server, bigger server -> 4 GB -> 16 GB RAM Horizontal: more servers, same size -> 1 server -> 4 servers
Vertical scaling — a bigger machine — is easy. Often a settings change. But it has a ceiling, and you're still running a single point of failure. Horizontal scaling — more machines behind a load balancer — has no practical ceiling, and it adds redundancy as a side effect. One box dying no longer takes you down.
The trade-off: horizontal needs your app to be stateless. Any instance has to handle any request, with shared state living in the database or cache. An app that stores sessions in local memory can't spread across servers until that state moves out.
Some work is fine to do immediately. Other work is heavy and spikey. When a request triggers something slow — PDF generation, a bulk import, a report — don't make the user wait. Put the job on a queue and let workers handle it:
User request -> "job queued, we'll email you" (fast, 200 OK) Queue -> workers pick up jobs as capacity allows Worker -> does the heavy work, notifies when done
import json
import boto3
sqs = boto3.client("sqs")
def enqueue_export(customer_id, rows):
sqs.send_message(
QueueUrl="https://sqs.us-east-1.amazonaws.com/123/export-jobs",
MessageBody=json.dumps({"customer": customer_id, "rows": rows}),
)
enqueue_export("acme", 50000)
The queue decouples the request from the work. A spike of 500 exports doesn't slow down normal requests — the queue absorbs the surge, workers process it as capacity allows, and users get results by email. Queues also give you free retries: a job that fails can be retried without the user having to re-submit.
Growing from one customer to many is also a people problem. The solution that one team fought to build needs to be accepted by teams who didn't ask for it. That requires a few things:
The pattern to avoid: growing users faster than you grow onboarding and support. A solution can collapse under its own popularity — more users, more questions, more unhandled feedback — and the champion of the second wave quietly walks away.
Save your progress and earn XP for completing tutorials.
4 questions · Pass with 70%+
1What is the "bespoke trap" in customer solution work?
2What does it mean for an app to be "stateless"?
3What problem do queues solve in a scaling context?
4How does per-customer data isolation usually work in a shared database?
Technology
Forward Deployed Engineer
Lesson group
Production & Reliability
Progress
86% complete