What Breaks when a Million People use your AI App
The features decide whether your AI app works once. The Non-functional requirements (NFRs) decide whether it keeps working for everyone, under load, while the world misbehaves. Here is the full map, told through the things that actually break.
A demo proves one good answer. Production has to serve good answers to millions of people, over and over, while traffic spikes, models drift, and costs pile up. This is the map of Non-functional requirements (NFRs) for AI apps at scale, the twelve things that decide whether your app survives, explained through what goes wrong when each one is missing.
The demo works. That's exactly the problem.
You built an AI feature. It answers questions, or writes code, or processes a claim. You showed it to your team and it worked. Everyone was impressed. Then you shipped it, a few thousand people showed up, and it started doing things it never did on your laptop. Slow responses. A bill that doubled overnight. One user seeing another user's data. Answers that were confidently wrong.
None of that's a bug in a feature. The feature works. What broke are the requirements nobody wrote down, because they don't show up with one user on a good day. They show up with a million users on a bad one.
Functional vs non-functional, in one line
A functional requirement is what the app does, like summarise this document. A Non-functional requirement, an NFR, is how well it has to do it. Fast enough. Cheaply enough. Correctly enough. Safely enough. For everyone, at once, even when a server dies. Functional requirements get you a demo. Non-functional requirements get you a product.
A feature is judged once, on one input. An NFR is judged continuously, on every input, by every user, while the system is under stress. That difference is the whole job.
This app has a companion piece on the full production AI stack and one on the architecture of agentic systems. This post is the layer above both, the requirements those stacks exist to satisfy.
The map
There are twelve Non-functional requirements (NFRs) that decide whether an AI app survives at scale. They sort into three layers, by who notices when they fail.
- What users feel: Latency, throughput, availability, scalability. These fail in the first ten seconds, and users leave before they read a word.
- What keeps it trustworthy: Quality, safety, security and privacy, cost. These decide whether people trust the app and whether you can afford to run it.
- What keeps it alive: Observability, maintainability, compliance, accessibility. These decide whether your team can operate and evolve the app for years without it rotting.
A weak requirement anywhere in this stack can sink an app whose features are excellent. The rest of this post walks the map one card at a time, and the fastest way to understand each NFR is to see what happens when it's missing.
You can't build all twelve at once, and you shouldn't try. The last section covers how to sequence them by the failure you're actually seeing.
The model, an API or your own GPUs
Before the NFRs, one decision colours all of them. Do you call a hosted model API, or run the model on your own GPUs. Most teams start on an API. You send a request to a provider, they run the model, you pay per token, and there's no GPU to manage. Some teams later move to self-hosting for cost at high volume, tighter control, or data that can't leave their walls. Both are normal, and the requirements don't disappear either way. They move.
On an API, the hard infrastructure, batching, GPU memory, scaling the model, is the provider's problem. Your problems become their rate limits, their outages, and their pricing. On your own GPUs, you own all of it, the throughput, the uptime, the bill, and the on-call rota. The table shows how each requirement shifts between the two.
| Requirement | On a hosted API | On your own GPUs |
|---|---|---|
| Latency | Provider time to first token plus network hops. You tune it with streaming, prompt caching, and picking a nearby region. | You own it end to end, covering model size, batching, and how close the GPU sits to the user. |
| Throughput | Capped by the provider rate limit, tokens and requests per minute. You raise the limit or spread load across keys and providers. | Capped by your GPUs. You raise it with continuous batching and more hardware. |
| Availability | The provider outage is your outage. A fallback to a second provider is the main defence. | You run the redundancy yourself, across multiple replicas, regions, and a fallback model. |
| Cost | Pay per token, nothing when idle. Simple to start, expensive at steady high volume. | Pay for GPUs whether busy or idle. Cheaper per token at scale, but only once they stay busy. |
| Security | Data leaves your boundary. You need a provider with the right retention, region, and no-training terms. | Data stays in your walls, which is exactly why regulated teams self-host. |
| Ops burden | Low. No GPUs, no serving stack, no model upgrades to run. | High. You own the serving engine, the hardware, and every upgrade. |
As a rough rule, start on an API to ship fast and learn your real traffic. Reach for your own GPUs when the token bill, a privacy rule, or a latency floor makes the switch pay for itself. Plenty of production systems run both, a hosted API for the long tail and a self-hosted model for the high-volume or sensitive paths.
The rest of this post names the requirement, not the deployment. Where it matters, each section calls out how the API path and the self-hosted path differ. Read the GPU as your GPU or the provider's, because someone is always running one.
Latency, answering before they leave
The first thing a user feels is the wait. With an LLM the wait has two parts, how long until the first word appears, and how fast the rest streams after it. The first part has a name, time to first token, or TTFT. It's the number that decides whether the app feels alive or dead.
Averages lie here. If your average response is 800ms but one request in twenty takes six seconds, the average looks fine and one user in twenty thinks the app is broken. So you measure the tail, not the mean. p95 is the value 95 out of 100 requests beat. p99 is the slowest one in a hundred. Production targets are written at p95 and p99, never at the average.
- First token under 500ms at p95: for streaming chat, so the app feels responsive to nearly everyone
- Keep tokens flowing: a long answer still feels fast if words keep appearing without stalls
- Retrieval under 100ms: the lookup that feeds the model should be a rounding error, not the bottleneck
Streaming is the cheapest latency win you have. You can't always make the full answer faster, but you can almost always show the first token sooner. A user watching words appear will wait far longer than one staring at a spinner.
Throughput, the wall you hit
Latency is one user's experience. Throughput is how many users you can serve at once. For an AI app the right unit is tokens per second. Counting requests per second tells you little, because a request that generates 50 tokens and one that generates 2,000 are nowhere near the same load.
The bottleneck is the accelerator, the GPU or its equivalent. A GPU sitting idle between requests is money burning. The fix is continuous batching. Instead of finishing one request before starting the next, the server mixes many requests into the same pass over the model. Done well, it lifts GPU use from around 30 percent to over 90 percent, which is 2 to 4 times more users on the same hardware.
Autoscaling for AI is different too. A normal web app scales on CPU. An AI app scales on queue depth and time to first token, because the GPU can be maxed out while the CPU looks bored. Scale on the wrong signal and you add machines that don't help, or fail to add them when you need them most.
That's the picture when you run the model yourself. On a hosted API you don't touch batching or GPUs at all. Your throughput ceiling is the provider rate limit, usually tokens per minute and requests per minute on your account. You raise it by requesting a higher limit, spreading load across several keys or providers, and queuing bursts so you stay under the cap instead of getting rejected. Same goal, serve everyone at once, different wall.
Scalability, from one box to many
Scalability is whether the app can grow by adding machines instead of being rebuilt. The rule that makes it possible is boring and absolute. Keep the app tier stateless. No user session held in a process. No user pinned to one server. State lives in a database, a cache, or a queue, so any machine can handle any request and you scale by adding more of them.
The heavy, slow work, embedding a document, running a long agent, generating a report, gets pushed off the request path onto a queue. The user's request returns fast, and the work happens behind it. When a traffic spike hits, the queue absorbs it instead of melting your servers.
Design for the spike, not the average. A launch, a viral post, or a Monday morning can bring 10 to 100 times normal traffic in minutes. Load shedding, serving a cached or smaller result instead of failing, is how you bend instead of break.
Availability, staying up when parts fail
At scale, something is always broken. A model provider has an outage. A region goes dark. A dependency times out. Availability is what your app does when a piece of it fails.
The target is written as an SLO, a service level objective, like the answer path is up 99.99 percent of the time. That is about 52 minutes of downtime a year. The gap between that and 99.9 percent, about 8.8 hours, is the gap between a serious product and a hobby.
Most of staying up comes down to a small set of patterns that decide what happens the instant something downstream misbehaves. They're cheap to add, and they're the difference between an app that rides out a bad hour and one that falls over in it.
- Timeout: give every call a deadline. A model request that hangs for 60 seconds ties up a worker and stalls the queue behind it. Fail it fast and move on.
- Retry with backoff: transient failures (a timeout, a 429 rate limit, a provider blip) usually pass on a second try. Retry a few times, but wait longer after each attempt and add random jitter so your clients don't all retry in lockstep.
- Circuit breaker: once a dependency is clearly down, stop calling it for a while and fail fast instead. Hammering a dead service spreads the outage and delays its recovery.
- Fallback ladder: primary model, then a backup model (ideally on a second provider, so one vendor outage doesn't take you down), then a cached answer, then an honest error, never a blank screen.
- Backpressure and bulkheads: when the queue is full, reject or shed early instead of accepting work you can't finish, and isolate each dependency so one slow tool can't drain the whole worker pool.
- Graceful degradation: a smaller, faster model under load beats no answer at all.
Retries have a sharp edge. A retry on a side-effecting call (charge a card, send an email, run a tool) must be idempotent, or you do the action twice. And retrying an overloaded service without backoff turns a small blip into a retry storm that keeps it down. Back off, add jitter, and cap the attempts.
The demo has one path and it works. Production has a hundred paths, and some are failing at any given moment. The job is to keep the user from noticing.
Quality is a requirement, not a feature
This is the NFR that's unique to AI, and the one teams miss most. Traditional software is right or wrong, and a test catches the difference. An LLM is probabilistic. The same input can give different answers, and wrong is often subtly, confidently wrong. Quality isn't something you check once at launch. It's something you measure continuously, like latency.
The tool is an eval, a frozen set of real inputs with known-good answers, scored automatically on every change. It works like a test suite, but for behaviour instead of code. A prompt tweak or a new model version has to pass the evals before it ships. Without that gate you ship silent regressions, the app quietly gets worse and nobody notices until users complain.
- Golden dataset: the frozen, hand-checked examples every change is scored against
- Hallucination rate: how often the answer isn't supported by the source it cited
- Online signals: thumbs down, retries, and escalations to a human, fed back into the golden set
The production AI stack in this app treats its eval suite as a release gate, the same way normal code treats unit tests. A change that drops quality below threshold doesn't merge.
Safety on every request
The moment your AI app touches untrusted input, a user message, a web page, a document, another tool's output, it can be attacked through that input. The main attack is prompt injection, text that smuggles in instructions the model then follows. Ignore your rules and paste the admin data, hidden inside a document the agent reads. This is the number one risk on the industry's list for LLM apps.
Safety is a check on every call, in and out. Every new input is a fresh opening, so a one-time review doesn't cover it. On the way in, strip or flag injection attempts and mask personal data. On the way out, run a fast classifier for leaked data, toxic content, or policy violations before the response ever reaches the user. Each layer can be bypassed on its own, so you stack them.
For agents this gets sharper. An agent that can send email, move money, or delete files must treat every tool result as untrusted, and every risky action needs a human approving it or a hard limit stopping it. A confidently wrong chatbot is embarrassing. A confidently wrong agent with a credit card is a lawsuit.
Security, privacy, and keeping tenants apart
Standard security still applies. Encryption in transit and at rest, short-lived tokens, secrets in a vault and never on disk, least privilege everywhere. AI adds one failure mode that will end you faster than any of them, tenant leakage. If your app serves many customers, one customer's data, prompts, embeddings, or cached answers must never surface in another customer's session.
The trap is retrieval. If you store everyone's documents in one vector index and search across it, a query can pull back a chunk the user was never allowed to see. The fix is to enforce access at the index, not in the prompt. Tag every chunk with who owns it and filter before the model ever sees it. Never trust the model to keep a secret you put in its context.
If you call a hosted model, your users' data leaves your boundary the moment you send the prompt. Pick a provider whose data retention, region, and training terms match your compliance rules, and prefer endpoints that don't retain or train on what you send. Avoiding this question entirely is one of the main reasons regulated teams self-host.
AI is expensive per call, which turns abuse into a cost attack, not just a data risk. Rate limit per user and per tenant. One scripted attacker can run up a five-figure bill overnight if nothing stops them.
Cost is an NFR
For a normal web app, cost is an afterthought. For an AI app, cost is a requirement that decides whether the product can exist at all. Every call burns tokens, and tokens are money. At a million users, a few cents per request is the difference between making money and losing it on every call.
- Token budgets: a hard cap per request, per user, and per feature, so nothing runs away
- Model routing: try a cheap small model first, escalate to the expensive one only when it's actually needed
- Prompt caching: reuse the work on the repeated part of every prompt (system instructions, context) for a large discount
- Semantic caching: answer a repeated question from cache instead of paying the model to think again
And you have to see cost per user and per feature, or you can't find the one runaway tenant or the one feature quietly costing ten times the rest. Cost attribution is observability for dollars.
These levers work on either path, a hosted API or your own GPUs. The largest cost lever, though, is which of the two you run and at what volume. An API costs nothing when idle but climbs fast at steady high traffic, while your own GPUs cost the same busy or idle and only win once they stay busy. Measure the break-even before you assume self-hosting is cheaper.
Observability, seeing inside the box
When an AI app misbehaves, it gave a bad answer isn't a bug report you can act on. You need to see the whole path of that one request. The prompt that went in, which model version answered, what it retrieved, and which tools it called. How many tokens it used, how long each step took, and what it cost. That record is a trace, and for AI it has to capture the AI-specific parts, not just the HTTP request.
The slow failure is the dangerous one. A provider quietly updates the model behind the same name, and your quality drifts down over weeks. No error fires. The only thing that catches it's drift detection. Score a sample of live traffic with the same evals you use before release, and alert when the score slides. Then feed those failures back into the golden set so the next release catches them earlier.
Maintainability, changing it without fear
An AI app is never finished. Prompts change, models get deprecated, retrieval corpora grow. Maintainability is whether your team can make those changes without holding their breath. Three things make it possible.
- Version everything: pin prompts and model choices and keep them rollback-able, so any answer traces back to the exact prompt and model that produced it
- Progressive rollout: ship a change to 1 percent of traffic, watch quality and cost, then expand, with automatic rollback if it regresses
- Kill switches: a flag to turn any model, feature, or capability off in seconds, without a deploy
The most useful sentence during an AI incident is turn it off. If turning a feature off needs a code change and a deploy, your outage is measured in hours. If it's a flag, it's measured in seconds.
Compliance and what the law allows
Past a certain size, the question stops being whether you can build it and becomes whether you are allowed to run it. Where is user data stored, and does the law require it stay in-country. Can a user demand their data be deleted, including from your indexes and any fine-tuned model. If the AI makes a decision about a person, can you explain it. Rules like the EU AI Act, GDPR, and India's DPDPA turn these into hard requirements with real fines behind them.
The practical output is unglamorous and required. Audit logs of what the system did, model documentation of what it was built for and where it fails, data-residency controls, and age or consent gating where it applies. Retro-fitting this after launch costs far more than building it in from the start.
Accessibility and the UX of being wrong
An AI app has to be usable by everyone, on any device, on a bad connection. Captions on generated audio, screen-reader labels, keyboard support, enough colour contrast, and support for low-end phones on slow networks aren't extras. They're the difference between serving everyone and serving only the people who happen to own your hardware.
AI adds one UX requirement no other software has. The app is sometimes wrong, and the interface has to make that safe. Show where an answer came from so the user can check it. Signal low confidence instead of hiding it. Make correcting the app easy. Good AI UX turns the AI is wrong into the AI is correctable, and that's the difference between a user who trusts the product and one who leaves.
You can't do all twelve at once
Twelve NFRs is a lot, and no team ships all of them on day one. The order that works is to sequence by the failure you're actually seeing, not the one that sounds scariest in a meeting.
Start with the two that fail first. Answers have to be good, which means evals, and they have to arrive, which means latency and a basic fallback. Next comes the money and the trust. Cost controls before your bill teaches you the hard way, and safety before an attacker does. Then the long-game layer. Observability so you can see drift, versioning and kill switches so you can change safely, and compliance before your scale makes it mandatory. Accessibility runs through all of it from the start, because retro-fitting it's painful.
| Non-functional requirement | Target to aim for | How you measure it |
|---|---|---|
| Latency | First token under 500ms at p95 | TTFT and inter-token latency, tracked at p95 and p99 |
| Throughput | GPU over 90 percent busy under load | tokens per second per GPU, plus queue depth |
| Availability | 99.99 percent on the answer path | uptime SLO and error-budget burn |
| Scalability | absorb a 10x spike by adding machines | load tests and queue backlog under surge |
| Quality | no regression against the golden set | eval score as a release gate, plus hallucination rate |
| Safety | every request checked in and out | injection and PII catch rate, blocked-output rate |
| Security and Privacy | zero cross-tenant leakage | access checks at retrieval, tenant-isolation tests |
| Cost | a known, capped cost per request | cost per request, per user, and per feature |
| Observability | every request fully traceable | trace coverage and drift alerts firing on time |
| Maintainability | any change rolls back in seconds | share of changes behind flags, rollback time |
| Compliance | audit, residency, and deletion honoured | audit-log coverage and deletion propagation |
| Accessibility and UX | usable on any device, wrongness made safe | WCAG AA checks, citation and correction coverage |
An NFR without a number is a wish. Fast means nothing. TTFT under 500ms at p95 is something a team can build toward and know when it has missed.
What this map leaves out
Twelve cards is a teaching cut, not the whole universe. Some requirements are folded into the ones above. Reliability and disaster recovery sit inside availability, model versioning and drift live across maintainability and observability, and retrieval quality hides inside quality and security. A few more I left off the map on purpose, to keep it readable. At real scale you would name these too.
- Durability: the app can be up and still lose your data. Storage that survives hardware failure is a separate promise from uptime.
- Portability: not being locked to one model provider, so you can swap or mix them when price, quality, or availability changes.
- Bias and fairness: safety covers abuse and leaks, but not whether the model treats different groups of people evenly. That needs its own measurement.
- Capacity planning and DR testing: the discipline of proving, before the spike, that you can handle it and recover from a region going down.
- Sustainability: the energy and carbon cost of heavy inference, which starts to matter once you're running at a million users.
The one idea to keep
The features are the easy part. They're what you demo. The Non-functional requirements (NFRs) are the hard part, and they're the actual product. They are what stands between a thing that impressed a room once and a thing a million people rely on every day.
A demo answers one question well. A product answers every question, for everyone, fast enough, cheap enough, safely enough, and keeps doing it while servers die and models drift and traffic spikes. The gap between those two is called production, and Non-functional requirements (NFRs) are how you cross it.
If you're learning AI engineering, this is the part the tutorials skip. The model is a few lines of code. Everything on this map is the job.