Going Live Properly: What We Found (9 October 2026, revised the same day)
What this is. Before the first real deployment, the founder asked for a careful look at
how serious companies run their servers, and at eight specific worries: search, old data,
logs, outages and restarts, load balancers / Redis / Lambda, fixed addresses and remote
access, cookies and sign-in, backups, and video checks. This document is the answer, in
plain words. Its companion, PRODUCTION-PLAN-2026-10-09.md, is the step-by-step plan.
Revised after the founder's review. The first draft recommended a setup that costs about $360 a month. The founder's answer: that is a bill for a company with revenue, not a pilot. This version keeps the same facts and the same research, and sizes every recommendation to what 10,000 users actually need, at about $50 to $70 a month. Every finding and every recommendation from the research stays in this document, so it is the one complete record: what we do now is marked as such, and the bigger pieces are kept in full, each with its price and the exact measurement that would justify it later (§6, §7, §10, §11 and the table in §13).
Nothing in either document has been built. Nothing starts until the founder and CTO approve.
How prices were checked. Every Amazon price was read on 9 October 2026 from Amazon's own pricing pages or from Amazon's own machine-readable price list, the files the pricing pages themselves load. All links are in §15 at the end, grouped by section, so the text stays readable. Prices are for US East (N. Virginia) in US dollars; "a month" means 730 hours.
The numbers we planned for. Up to 10,000 users in the first pilot, 2,000 to 3,000 of them on the site at the same time at peak, schools in and around Pittsburgh.
1. The short version
| Question | Answer in one line |
|---|---|
| Do we need Elasticsearch? | No. Our Postgres search was measured at 1 million profiles; the pilot has 10,000. A production OpenSearch cluster costs 5 to 8 times the whole database. We re-run the benchmark at pilot size and set a clear trigger (§3). |
| Move old data out of Postgres into S3? | Yes, as a monthly job, once the founder sets retention numbers. At pilot size the database stays small; the archive is cheap hygiene plus a legal record (§4). |
| Full production logging? | Yes, and it costs about $4 a month. One place for all logs, a request id on every line, alarms to a phone. The free allowances cover most of it (§5). |
| Deploy with no outage? | Yes, on the one server we already planned. The new version starts beside the old one, is health-checked, then takes over through Caddy's graceful reload. No load balancer needed (§6). |
| Load balancer, Redis, Lambda? | None now. Each has a written trigger and a known price for later (§7). |
| Fixed address (Elastic IP)? SSH? | Elastic IP yes ($3.65 a month), because the API is one server with its own name. SSH no: Amazon's logged browser session instead, as docs/27 already decided (§8). |
| Cookies and sign-in? | Serve the API through the same front door as the site. CloudFront already sits in front of the site; routing /api through it too costs nothing extra and means the sign-in cookie never has to be shared across subdomains (§9). |
| Backups? | Restore to any minute in the last 35 days (free within allowance), a snapshot before every migration, a weekly off-site copy, and a tested restore drill (§10). |
| Video checks? | Keep what is built. A person watches every video; the free speech model stays, moved to run at low priority. Amazon's speech and frame checks are costed and parked until video volume is real (§11). |
| Which Amazon region? | US East (N. Virginia), as decided. Pittsburgh's internet routes reach it in about 5 ms versus about 25 ms to Ohio (§2). |
| What does it cost? | About $50 a month at the start, about $67 a month at 1,000 active students, from credits (§12). |
2. Where everything runs: one region, and which one
The rule. Everything lives in one Amazon region: server, database, files, email, AI, backups. The only exceptions are Amazon's "global" services that have no region at all: CloudFront, Route 53, IAM, and the certificate CloudFront uses, which Amazon requires to be issued in N. Virginia whatever region the rest lives in. If AWS Organizations is switched on (it is free), a one-line policy can refuse any action outside the region; it is optional for a single account that one person administers.
Which region. Two candidates are close to Pittsburgh.
| US East (N. Virginia) | US East (Ohio) | |
|---|---|---|
| Straight-line distance from Pittsburgh | 265 km (Ashburn) | 261 km (Columbus) |
| Measured network round trip from Pittsburgh (city to city) | 5.5 ms to Washington DC | 25.6 ms to Columbus |
| Our AI model (gpt-oss-safeguard-20b) available in-region? | Yes | Yes |
| Every other service we use available? | Yes | Yes |
| Default picture-check speed (Rekognition) | 50 a second | 5 a second (can be raised on request) |
| Known weakness | Busiest region; had a large outage on 20 Oct 2025 | Fewer specialised features |
| Price differences | Essentially none |
Recommendation: keep N. Virginia, as docs/27 decided. Pittsburgh's internet paths run down the Washington corridor, so N. Virginia is in practice five times "closer" than Ohio.
3. Search: what we have, and whether we need Elasticsearch
What the search does today
- Matching. A name is matched two ways inside Postgres: by the start of each word ("ma al" finds Maya Alvarez) using a full-text index, and, if that finds nothing, by letter-triplet similarity ("Mya Alvarz" still finds her) using a trigram index. Accents are folded, so typing "jose" finds José. School, grade, interests, skills and 1st-degree connections are filters with their own indexes.
- Who may be found is decided inside the query itself, so ranking and paging only ever see people the viewer is allowed to see. A test holds this equal to the profile rules.
- Ranking is LinkedIn's order: how well the name matches, then your connections, then your school, then shared interests. Pages use a stable cursor, so results never repeat.
- The typing box waits 200 ms after the last keystroke before asking the server. For the first one or two letters it searches only your own circle (connections and schoolmates), because "m" matches a tenth of the network. From the third letter it searches everyone.
- Protections already built. Per account: 240 typing requests and 60 full searches a minute. The server runs at most 6 searches at once; the rest wait up to 3 seconds and then get a polite "busy, try again". Any single search is cut off at 2 seconds. None of this can take the rest of the app down with it.
How it measured
On a laptop with Postgres given 2 processors and 4 GB, filled with one million made-up profiles: a full name 3 ms; a typo 5 ms; two letters 122 ms; one letter 244 ms; typing box median 35 ms with six people at once; and under a storm of 10,000 searches from 1,000 users the rest of the app stayed at 9 ms. Roughly 20 to 25 searches a second per database processor at that size.
"2,000 to 3,000 people searching at the same time, at different typing speeds"
The typing pause is what protects the server, not the search engine. Industry guidance is to wait 200 to 300 ms after typing stops (Algolia calls 200 ms the preferred delay; Twitter's typeahead library defaults to 300 ms). A fast typist therefore generates one request per pause, not one per key. Working it through:
| Scenario (3,000 people online) | Requests a second |
|---|---|
| Typical peak: 2% typing at once, about 2 requests a second each | about 120 |
| Everyone who is typing hits the 4-a-second cap | about 240 |
| Heavy: 10% typing at once | 600 to 1,200 |
Our measured capacity, at 1 million profiles, is about 40 to 50 searches a second on a 2-processor database. That would not be enough for the heavy case. But the pilot has 10,000 profiles, one hundred times fewer, and the cost of a search falls with the number of names that match. The honest position is: at 10,000 profiles even the smallest database should answer well over a hundred searches a second, and this is the one number we must measure, not assume. The plan re-runs the existing benchmark at pilot size on the chosen database class before launch; if it falls short, the database is one click bigger (the next size up is about $12 a month more).
What we add regardless, because it is free:
- the browser cancels a stale request when a newer keystroke arrives;
- a watch on search latency and database load, with an alarm.
Elasticsearch (Amazon OpenSearch): what it would cost and when it is right
Amazon's own guidance for a production cluster is three data nodes plus three dedicated "master" nodes across three data centres, and it warns not to use the smallest nodes for production.
| Setup | Monthly |
|---|---|
| Amazon's recommended minimum (3 × r6g.large data + 3 × r6g.large master) | about $735 |
| Cheapest production shape that supports a standby (3 × m6g.large + 3 × m7g.medium) | about $433 |
| OpenSearch Serverless, production minimum (2 OCUs at $0.24 an hour) | about $350 |
| OpenSearch Serverless, dev/test minimum (1 OCU) | about $175 |
| A single small dev node (not for production) | about $27.50 |
| For comparison: our whole database | $14 to $47 |
Every engineering write-up we read agrees on the shape of the trade: Postgres search is right for small to medium tables with no extra infrastructure, and Elasticsearch gives excellent search at the price of operational overhead, delayed freshness and "two sources of truth" (ParadeDB, Neon, Crunchy Data and GitLab all say a version of this). OpenSearch Serverless also refreshes about every 10 seconds, so a student who just joined would not be instantly findable. Amazon CloudSearch, the older option, is closed to new customers.
Recommendation. Keep Postgres. The code already isolates search in one module so OpenSearch can replace it later without touching the rest. Triggers for adding it: profiles in the millions, or a relevance need that word-prefix plus trigram cannot meet. Traffic alone at pilot size is not a reason; a bigger database or a read-only copy buys more search capacity first, for a fraction of the price.
4. Old data: moving it out of Postgres into S3
What grows, and how fast
The CTO is right that most growth is not the content itself. Our own measurement (docs/27 §16) found a chat message costs about 1.4 KB in the database, and two thirds of that is the moderation record and its log line, not the message. The tables that grow:
| Table | What it holds | Can it be moved out? |
|---|---|---|
Check records (content_reviews) | Every automatic check: a copy of the text, the AI's answer, the reason | Yes, once decided and old, unless tied to a safety case or appeal |
Who-did-what (audit_log) | Append-only record of every staff and system action | Copy out, never delete. Legal record |
| Notifications | "You have a new message" and the like | Delete after 90 days, as LinkedIn does |
Trial-rule hits (rule_watch_hits) | What a trial rule would have caught | Yes, after the rule is promoted or dropped |
| Messages, posts, comments | What people wrote | No. People read old threads; docs/10 says messages are never deleted and no retention number exists yet. That is a founder-and-lawyer decision |
| Profile views | Who looked at your profile | Already deleted after 90 days |
| Job queue tables (pg-boss) | The background job queue | Yes; finished jobs are archived and purged on timers we set explicitly |
| Media originals in S3 | Uploaded photos and videos, kept twice when approved | Lifecycle rules, respecting the evidence hold |
Size reality check. At 10,000 users writing 100 checked items a month each, that is about 1 million check records a month, about 1 GB. After a year, a few GB. The smallest database handles that; the next size up is a click. So moving data out is cost hygiene and legal structure, not a performance rescue, at this scale.
How it is done, the standard way
- A monthly archive job inside the app: for a closed month, select the rows that are
safe to move (decided, older than N days, not referenced by any safety case, appeal,
grievance, legal order or account action), write them to a compressed file in a locked
archive bucket, read the file back and check the row count, then delete the rows in
small batches. Amazon's database can write straight to S3 from SQL (the
aws_s3extension, supported on RDS Postgres 16 and 17). - Still searchable without loading back. Amazon Athena reads those files in place at $5 per TB scanned, so a legal question about a message from 14 months ago is one query, not a restore. At our sizes a query costs a fraction of a cent.
- Three storage tiers, by age.
| Tier | Price per GB-month | Minimum stay | Use |
|---|---|---|---|
| S3 Standard | $0.023 | none | the last 90 days of archives, media |
| S3 Glacier Instant Retrieval | $0.004 | 90 days | archives older than 90 days (still instantly readable) |
| S3 Glacier Deep Archive | $0.00099 | 180 days | archives older than a year; hours to retrieve |
| For comparison: database disk (RDS gp3) | $0.115 |
Moving a file between tiers costs $0.02 to $0.05 per 1,000 files, so archives are
written as a few large files per month, not millions of tiny ones.
4. Locked, not just stored. The archive and evidence buckets use S3 Object Lock in
compliance mode: nobody, including the root account, can shorten the lock or delete a
file before its date. Versioning stays on. The feature itself is free. This is how "we
never delete anything" is made true rather than hoped.
5. Partitioning later, not now. Splitting big tables by month (pg_partman, supported
on RDS) makes removing a month a one-step operation. It changes primary keys, so it is a
real schema change; the trigger is about 1 million rows in one table, which the pilot
reaches only in year two.
What we must keep, whatever the archive rules say (from docs/22 and docs/27): anything tied to a safety case, 1 year minimum and never deletable by a reviewer; removed content, 180 days; intimate-image removal requests, 3 years. And what we must not keep too long: the US children's privacy rule (COPPA, amended January 2025, full compliance due one year after its April 2025 publication) requires keeping a child's data "only for as long as reasonably necessary" and says outright that it cannot be retained indefinitely. So "never delete" applies to logs, audit and legal records; a retention number for messages and for dormant accounts is a founder decision the plan needs before the archive job ships.
5. Logs: knowing what broke, when, why, and how far it spread
Today
The API writes JSON log lines to the server's system journal; 16 files still use plain
console.log; no request carries an id, so one user's failing click cannot be followed
across lines. Caddy writes a web access log to a file on the box. There is no central
log store, no alarm, no dashboard. The "who did what" audit trail in the database is good
and stays.
The production standard, right-sized
| Question in an incident | What answers it | Cost |
|---|---|---|
| What broke? | Application logs: one JSON line per request with a request id, route, status, duration, a hashed user id, and the error. Secrets, cookies and emails are masked at the logger. Shipped from the server to CloudWatch Logs by Amazon's free agent | First 5 GB a month free, then $0.50 per GB; storage $0.03 per GB-month |
| When, and for whom? | The same request id returned to the browser and stamped by CloudFront, so a student can quote it and we can find their exact click. A deploy line with the git version, so "did the 14:02 deploy cause this?" is a filter, not a guess | free |
| Why? | Database slow-query log and Database Insights (query-level view, 7 days free); Caddy's access log; CloudFront's log | free |
| Blast radius? | A handful of metrics from the logs: error rate, latency, Bedrock errors, moderation queue age, "busy" count, video job time. The server's and database's own metrics are free. One dashboard (3 free) | 10 custom metrics free |
| Is it still happening? | A Route 53 health check on the API from outside, every 30 seconds, independent of our server | $1.50 a month |
| Who is told? | Up to 10 alarms (free) to an email-and-text topic, each with a runbook link | first 1,000 emails free |
| What did Amazon itself do? | CloudTrail (first copy free) and the AWS Health dashboard | free |
Total: about $4 a month, almost all of it logs above the free 5 GB. Logs are kept 90 days in CloudWatch and exported monthly to the locked archive bucket, where the Glacier tiers hold them for years for pennies.
Not now, and why: X-Ray tracing ($5 per million traces) answers questions a single server rarely raises; CloudWatch Synthetics ($10 a month for a 5-minute canary) is replaced by the $1.50 health check; GuardDuty and Security Hub (roughly $10 to $15 a month together for a small account) are listed in §13 with their trigger.
Containing damage before it spreads
- Timeouts and limits on every dependency, already built: Bedrock 20 s, database wait 5 s, search 2 s, posting 10 a minute.
- Switches, not deploys. Video uploads, mentor messaging and (later) the automatic picture check each get an off switch that takes effect without a release.
- Fail closed on safety, already built: if the AI is down, every item waits for a person; nothing publishes unchecked.
- Background work at low priority. ffmpeg and the speech model already run "niced" so the pages students load win the processors. The plan adds a memory cap for them.
- A written incident routine. Who is paged, where the runbooks are, a status page people can read, and a blameless write-up after every incident, the Google SRE practice.
6. Deploys and restarts with no outage
Today
scripts/deploy.sh pulls the code, builds, migrates, and restarts one systemd service. The
API is down for the seconds the restart takes; Caddy holds requests for up to 10 s to hide
most of it. If the new code fails its health check the script restores the old code.
The right-sized fix: two versions on one server, for a minute
- The deploy script builds the new version into its own folder and starts it on a second port beside the running one.
- It waits until the new version answers its readiness check (database reachable, job queue connected).
- Caddy is told to send traffic to the new port. Caddy reloads its configuration without dropping a connection; in-flight requests finish on the old process.
- The old process is told to stop; the app already finishes its work and exits cleanly on that signal.
- If the new version never becomes ready, nothing changes: the old one keeps serving and the script reports the failure. Rollback is "start the previous folder".
No load balancer, no second server, no new monthly cost. The database rule stays: migrations only add, so old and new code run together for the minute of overlap.
What still causes a short outage, and how it is handled. A kernel update or a hardware retirement needs the whole server to restart. That is done in a planned window at night with a notice banner, about once a month, and EC2's free "auto-recover" setting restarts the server on another machine if the hardware fails. These are the two cases a load balancer with a second copy would remove. Their price is about $45 a month for the balancer plus a second server; the trigger for paying it is in §13.
Why not containers on Fargate now. The first draft recommended two app copies on Fargate behind a load balancer (about $86 for the copies plus $20 for the balancer plus network costs). It is the right shape once a short monthly restart window is unacceptable or more than one server is needed; for 10,000 users on a pilot it buys very little over the design above.
7. Load balancers, Redis and Lambda: when each is right
| Tool | What it is | Right now? | When it becomes right | Cost when needed |
|---|---|---|---|---|
| Load balancer (ALB) | Spreads traffic over several app copies, checks their health, ends HTTPS | No. One server; Caddy ends HTTPS for free | A second server (either for traffic or for restart-free maintenance) | $0.0225 an hour (about $16.40 a month) + $0.008 per capacity unit-hour |
| Redis / Valkey (ElastiCache) | A fast shared memory store | No. Sessions and jobs are in Postgres by design (docs/04). With one process, rate-limit counters need no sharing | A second app process (shared rate limits), or a measured hot spot such as feed pages served from cache | ElastiCache Serverless for Valkey: $0.084 per GB-hour, minimum 100 MB, about $6.13 a month at the floor |
| Lambda | Run a small function on an event, pay per run | No for the app or workers: our workers run continuously and video jobs can run longer than Lambda's 15-minute limit | Glue jobs: react to a new file in S3, handle an email bounce, a nightly export, a webhook | $0.20 per million requests plus duration; 1 million requests and 400,000 GB-seconds free a month |
| SQS / EventBridge | Queues and schedules | No; pg-boss in Postgres already does this | When workers move to another machine or Lambda | SQS $0.40 per million requests (1 million free); EventBridge Scheduler 14 million invocations free |
8. Fixed addresses (Elastic IP) and remote access (SSH)
Elastic IP: what it is for. A server's public address is released every time it is stopped and a new one assigned when it starts. An Elastic IP is a fixed one, so the API's name keeps working across restarts. Amazon now charges $0.005 an hour (about $3.65 a month) for every public IPv4 address, fixed or not, used or idle. The server needs exactly one; it costs the same whether it is fixed or not, so fixed it is. A side benefit: if a school's firewall ever asks us for an address to allow-list, we have one.
SSH: do we need it? No, and docs/27 already chose not to open port 22. Production practice is Amazon's Session Manager: a shell in the browser through Amazon's own agent, with no open port, no key to lose, access controlled by the same login as the console, and every session can be recorded. It is free on EC2.
When would we actually open a shell? For deploys (the script runs there), for the one-off ops scripts (giving a staff member a role, creating a school page), and for an emergency investigation. The plan asks that every emergency session is noted in the incident record.
9. Cookies, sign-in and role-based access
Today
Sign-in is a long random token in an HttpOnly cookie, stored only as a hash in the database (a database leak leaks no live sessions), marked Secure in production, SameSite Lax, valid 30 days, revoked on password reset and when staff suspend an account. Writes need a second anti-forgery cookie echoed back in a header. Roles (student, mentor, educator, staff hats) are checked on the server for every route. This is a sound design. Two things need attention for the live site:
- The planned shared cookie. Because the site would be
thnkrr.comand the APIapi.thnkrr.com, docs/27 sets the cookies for.thnkrr.com. A cookie with a Domain like that is sent to every subdomain:media.,www., and any future one. If any subdomain is ever compromised or misconfigured, it can read or overwrite the sign-in cookie. The anti-forgery cookie is also unsigned, which OWASP rates below a signed one. - No idle timeout. A session lives 30 days from sign-in even if untouched. OWASP recommends both an idle limit and an absolute limit, shorter for privileged users.
The fix: one front door, at no extra cost
CloudFront already sits in front of the web files (docs/27). Add two routes to that same
distribution: /api/* goes to the server (the /in/* and /school/* pages already do).
CloudFront's first 1 TB and 10 million requests a month are free, so this adds nothing to
the bill. Then:
- cookies are first-party and host-only, using the
__Host-prefix, which browsers only accept with Secure, Path=/ and no Domain, so no subdomain can ever see them; - no cross-origin requests, so CORS goes away;
- the anti-forgery token is signed with the server secret;
- sessions get an idle limit (proposed 14 days for members, 12 hours for staff) and keep the 30-day absolute limit;
- CloudFront is told never to cache
/api/*and to pass every header and cookie through (Amazon's managed "CachingDisabled" and "AllViewerExceptHostHeader" policies); - the server accepts traffic only from CloudFront (Amazon publishes CloudFront's address list for exactly this), so nobody can go around the front door;
- security headers (HSTS, a content security policy, no sniffing) are added at the edge.
One code consequence: the web app's page routes (/messages, /search, /network and
others) share names with API routes, so the API moves under an /api prefix. A small change.
Firewall (AWS WAF): optional, about $8 a month. Amazon's managed rule sets block common attack patterns in front of CloudFront for $5 a month plus $1 per rule group plus $0.60 per million requests. Shield Standard, the baseline DDoS protection, is free and already on. The plan lists WAF as a switch to turn on the week real students arrive, if the founder wants it; the app's own rate limits and input checks are the first line either way.
Account-level access
Root locked with MFA and never used; a named login per person through IAM Identity Center with MFA; the server wears a role with only the permissions it needs (already designed); CloudTrail on (free). App secrets live in the one locked file on the server as docs/27 says, with a copy in the password manager; Amazon's Parameter Store (free for standard parameters) holds them too so a rebuilt server can fetch them. Secrets Manager ($0.40 per secret a month with automatic rotation) is parked until there is more than one server to keep in sync.
10. Backups: restore to any minute, keep forever, prove it works
| Layer | What it gives | Price |
|---|---|---|
| RDS automated backups, retention set to the maximum 35 days | Restore the database to any minute in the last 35 days (logs are shipped every 5 minutes) | Free up to the database's own size; $0.095 per GB-month beyond |
| A manual snapshot before every migration | Already the rule in deploy.sh; snapshots are incremental | free within allowance |
| Monthly snapshots kept for years, taken by AWS Backup into a vault with Vault Lock | A copy nobody can delete early, root included | Vault Lock is free; storage at the RDS rate above, small because snapshots are incremental |
Weekly off-site copy of a logical dump (pg_dump), encrypted, to a second company's storage | A restore that does not depend on Amazon at all. Already specified in PROVIDER-SWAP-PLAN-2026-10-09.md (P2); not repeated here | about $6 per TB-month there |
| S3 versioning + Object Lock on evidence and archive buckets | An accidental or malicious delete is recoverable, or impossible | no feature charge |
| Restore drill | Proof. Restore last night's backup to a temporary database, run the test suite against it, time it, write the number down. Once before the first school, then quarterly | staff time; a temporary database for an hour costs cents |
Not now: a live standby database (Multi-AZ) doubles the database price for automatic failover in 60 to 120 seconds. At pilot size a failed database is restored from backup in tens of minutes instead; the trigger for paying for the standby is in §13.
On "we never delete anything". The rules above make records, logs and backups effectively permanent. Two deliberate exceptions, both already law or product rules: a removed intimate image or child-abuse image must truly go from every public copy (the hashed fingerprint is kept so it cannot return), and children's data must not be kept longer than its purpose (COPPA). Those exceptions belong in the retention policy the plan asks the founder to sign off.
On the "one region" rule and backups. Amazon's textbook advice is a backup copy in a second region. The off-site copy to a second company (P2 in the provider-swap plan) answers the same risk without a second Amazon region.
11. Video checks
Today (D-69, docs/23)
- The file is rebuilt with ffmpeg (hidden data stripped), in SD and HD.
- A poster frame and a 30-frame storyboard (one frame every N seconds, tiled) are made for the reviewer.
- If there is audio, whisper.cpp with the "base" model transcribes it on the server at low priority. The transcript and the author's captions go through the same text check as every post, so a spoken phone number is caught like a typed one.
- A person watches every video before it goes live. There is no automatic picture check yet. Videos are processed one at a time.
What we found
- Whisper "base" is the least accurate size. In a published benchmark across four English test sets, Whisper base averaged 9.5% word error, Whisper large 5.7%, and Amazon Transcribe 4.3% (benchmark by Picovoice, a speech vendor). Whisper is also the one engine a 2024 academic study found inventing whole sentences on about 1% of clips, most often around long pauses and music. Our code drops Whisper's silence markers but cannot catch an invented sentence. At pilot volume this is acceptable because a person watches every video anyway; the transcript is an aid, not the gate.
- The industry pattern is frames + speech + people. Amazon's design-pattern guidance describes sampling frames through the picture check, with audio transcribed and sent to the text check, and people for the rest. One frame a second is a common default.
- Nothing automatic detects known child-abuse material. Amazon says so plainly for Rekognition. That remains the hash-matching programmes the founder must apply to.
The options, costed for a 1.5-minute average video
| A. Today | B. Amazon end to end | C. Our frames + Amazon checks | |
|---|---|---|---|
| Speech to text | whisper.cpp base on the server | Amazon Transcribe $0.006 a minute + Toxicity Detection $0.0036 a minute | Transcribe, or whisper |
| Picture check | none | Rekognition Video moderation, $0.10 per minute of video | ffmpeg takes 1 frame a second; Rekognition Image moderation $0.001 per frame |
| Human watch | every video | every video | every video |
| Amazon cost, 300 videos a month | about $0.04 | about $49 | about $54 to $58 |
| Amazon cost, 1,000 videos a month | about $0.13 | about $165 | about $117 to $131 |
Recommendation: A for the pilot. The human watch is the gate and is already built. Two free improvements: cap the memory ffmpeg and whisper may use so a long video can never starve the API, and try the "small" Whisper model (about 2 GB of memory while running) if the transcripts prove too rough. Triggers for B or C: reviewer time on video passes about an hour a day, or a missed spoken violation shows the transcript matters more than expected. Option C is the cheaper one at volume and what Amazon's own guidance describes.
12. What it costs
All prices us-east-1, a month of 730 hours. Usage lines (Bedrock, SES, S3 growth) are docs/27's estimates.
| Line | At the start (about 20 students) | At 1,000 active students | Notes |
|---|---|---|---|
| Server: EC2 t4g.medium $24.53 + 30 GB disk $2.40 + Elastic IP $3.65 | $30.58 | $30.58 | As docs/27 decided; 2 processors, 4 GB |
| Database: RDS db.t4g.micro $11.68 + 20 GB disk $2.30 | $13.98 | $13.98 | One click to db.t4g.small (+$11.68) if the search benchmark says so |
| Backups | $0 | $0 | Within the free allowance; AWS Backup vault lock free |
| Web + media (S3, CloudFront) | ≈ $0.10 | ≈ $1 to $14 | Grows with photos and videos (docs/27 §8) |
| Logs, metrics, alarms, health check | ≈ $2 | ≈ $4 | Logs above 5 GB free; Route 53 health check $1.50 |
| DNS (Route 53 zone + queries) | ≈ $1 | ≈ $1 | Certificates free |
| Bedrock writing check | $0.31 | $15.50 | docs/27 estimate |
| Email (SES) | $0.01 | $0.50 | $0.10 per 1,000 |
| CloudTrail, Budgets, Parameter Store, Session Manager, Shield Standard | $0 | $0 | Free |
| Total, about | $48 | $67 | |
| Optional: WAF managed rules | +$8 | +$8 | Switch on the week real students arrive, if wanted |
| Optional: GuardDuty after its 30-day trial | +$3 to $4 | +$3 to $4 |
Credits. About $5,000 (docs/18). At $50 to $70 a month they last six years or more. Activate credits cover Bedrock third-party models but never Marketplace purchases or upfront reservations.
13. Parked, with the measurement that would justify each
| Piece | Monthly | Switch it on when |
|---|---|---|
| Bigger database (db.t4g.small / medium) | +$12 / +$35 | The pilot-size search benchmark misses its marks, or database CPU sits above 70% at peak |
| Load balancer + a second server copy | about $45 | A monthly night-time restart window is no longer acceptable, or one server's CPU is regularly above 70% |
| Live standby database (Multi-AZ) | +$12 (micro) to +$47 (medium) | A database outage of tens of minutes would be unacceptable (paying schools, contractual uptime) |
| Containers on Fargate + infrastructure as code | about +$80 and a few days' work | A second environment (staging) or a second server exists, so hand-managed servers start to drift |
| WAF managed rules | $8 | Real students arrive, or the first attack pattern shows in the logs |
| GuardDuty, Security Hub, Inspector | $10 to $15 | A school or insurer asks for a security attestation, or revenue exists |
| Secrets Manager | $2 | More than one server needs the same secrets |
| Valkey cache | from $6 | A second app process (shared rate limits) or a measured hot spot |
| Amazon Transcribe (+ toxicity) | $14 per 1,000 videos | Reviewer time on video passes about an hour a day, or a spoken violation is missed |
| Automatic frame check (Rekognition) | $90 to $108 per 1,000 videos | Same trigger as above; C is the cheaper shape at volume |
| OpenSearch | $350 to $735 | Profiles in the millions, or relevance needs Postgres cannot meet |
| Monthly table partitioning | engineering time | A table passes about 1 million rows |
| Lambda, SQS, EventBridge | pennies | An event-driven need appears (S3 hooks, SES bounces, exports) |
| X-Ray tracing | $5 per million traces | More than one service is in the request path |
| Second Amazon region or account | varies | A regulator or school requires it; otherwise the off-site copy (P2) covers the risk |
14. What we could not verify
Said plainly so nobody plans on it:
- Amazon's pricing web pages draw their tables with scripts, so most prices were read from Amazon's own price-list files behind those pages, not from the rendered page.
- Rekognition Video's rounding rule for part-minutes is not published.
- No direct Pittsburgh-to-AWS latency measurement exists; the city-to-city figures are a proxy.
- Whisper's accuracy per model size comes from a vendor benchmark, not OpenAI.
- Cost Anomaly Detection's "free" status has no explicit pricing page.
- Whether CloudWatch's agent reads the system journal directly was not checked; the plan has the app write its log file, which the agent certainly reads.
15. Sources
All read on 9 October 2026. Price-list files are Amazon's own machine-readable prices,
published at pricing.us-east-1.amazonaws.com and b0.p.awsstatic.com.
§2 Region
- Bedrock model availability by region: https://docs.aws.amazon.com/bedrock/latest/userguide/models-region-compatibility.html
- Amazon's regional services list (data feed behind the page): https://api.regional-table.region-services.aws.a2z.com/index.json
- Rekognition default quotas by region: https://docs.aws.amazon.com/general/latest/gr/rekognition.html
- Pittsburgh round-trip times: https://wondernetwork.com/pings/Pittsburgh
- Distances: https://www.distancecalculator.net/from-pittsburgh-to-ashburn and https://www.distancecalculator.net/from-pittsburgh-to-columbus
- Certificates for CloudFront must be in N. Virginia: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/cnames-and-https-requirements.html
- Region restriction policy example: https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps_examples.html
- Amazon's account of the 20 October 2025 outage: https://aws.amazon.com/message/101925/
§3 Search
- OpenSearch pricing page: https://aws.amazon.com/opensearch-service/pricing/
- OpenSearch price-list file: https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonES/current/us-east-1/index.json
- OpenSearch production best practices: https://docs.aws.amazon.com/opensearch-service/latest/developerguide/bp.html
- OpenSearch Multi-AZ with standby: https://docs.aws.amazon.com/opensearch-service/latest/developerguide/managedomains-multiaz.html
- OpenSearch Serverless minimums: https://aws.amazon.com/about-aws/whats-new/2024/06/amazon-opensearch-serverless-entry-cost-half-collection-types/
- OpenSearch Serverless refresh delay: https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-overview.html
- CloudSearch closed to new customers: https://docs.aws.amazon.com/cloudsearch/latest/developerguide/what-is-cloudsearch.html
- Algolia on debounce timing: https://www.algolia.com/doc/ui-libraries/autocomplete/guides/debouncing-sources/
- Twitter typeahead defaults: https://github.com/twitter/typeahead.js/blob/master/doc/bloodhound.md
- LinkedIn's typeahead (Cleo): https://www.linkedin.com/blog/engineering/open-source/cleo-open-source-technology-behind-linkedins-typeahead-search
- Postgres index docs: https://www.postgresql.org/docs/current/gin.html and https://www.postgresql.org/docs/current/pgtrgm.html
- Postgres-vs-Elasticsearch write-ups: https://paradedb.com/blog/elasticsearch-vs-postgres , https://neon.com/blog/postgres-full-text-search-vs-elasticsearch , https://www.crunchydata.com/blog/postgres-full-text-search-a-search-engine-in-a-database , https://about.gitlab.com/blog/fast-search-using-postgresql-trigram/
- Read replica billing: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ReadRepl.html
- RDS instance prices: https://aws.amazon.com/rds/postgresql/pricing/ and https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonRDS/current/us-east-1/index.csv
§4 Old data
- S3 pricing page: https://aws.amazon.com/s3/pricing/
- S3 price-list file: https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonS3/current/us-east-1/index.json
- Athena pricing: https://aws.amazon.com/athena/pricing/
- S3 Object Lock (compliance mode): https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html
- S3 Versioning billing: https://docs.aws.amazon.com/AmazonS3/latest/userguide/Versioning.html
- RDS extensions (aws_s3, pg_partman, pg_trgm and others): https://docs.aws.amazon.com/AmazonRDS/latest/PostgreSQLReleaseNotes/postgresql-extensions.html
- Export from RDS straight to S3: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/postgresql-s3-export.html
- COPPA 2025 amendments (retention): https://www.ftc.gov/news-events/news/press-releases/2025/01/ftc-finalizes-changes-childrens-privacy-rule-limiting-companies-ability-monetize-kids-data
§5 Logs
- CloudWatch pricing: https://aws.amazon.com/cloudwatch/pricing/
- CloudWatch log classes: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CloudWatch_Logs_Log_Classes.html
- Log retention per group: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/Working-with-log-groups-and-streams.html
- CloudWatch agent on EC2: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html
- Fastify logging (masking) and request ids: https://fastify.dev/docs/latest/Reference/Logging/ and https://fastify.dev/docs/latest/Reference/Server/
- Route 53 health check pricing: https://aws.amazon.com/route53/pricing/
- X-Ray prices: https://b0.p.awsstatic.com/pricing/2.0/meteredUnitMaps/xray/USD/current/xray.json
- SNS pricing: https://aws.amazon.com/sns/pricing/
- CloudTrail pricing: https://aws.amazon.com/cloudtrail/pricing/
- Database Insights cost and free tier: https://aws.amazon.com/rds/performance-insights/pricing/ and https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PerfInsights.Overview.cost.html
- GuardDuty pricing: https://aws.amazon.com/guardduty/pricing/
- Blameless postmortems: https://sre.google/sre-book/postmortem-culture/
§6 Deploys
- Expand and contract migrations: https://martinfowler.com/bliki/ParallelChange.html
- ECS rolling deploys and circuit breaker (the parked option): https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-type-ecs.html and https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-circuit-breaker.html
- Load balancer health checks and draining (parked): https://docs.aws.amazon.com/elasticloadbalancing/latest/application/target-group-health-checks.html
- Two Availability Zones as Amazon's baseline (parked): https://docs.aws.amazon.com/wellarchitected/latest/reliability-pillar/rel_fault_isolation_multiaz_region_system.html
- Fargate pricing (parked): https://aws.amazon.com/fargate/pricing/
- Load balancer pricing (parked): https://aws.amazon.com/elasticloadbalancing/pricing/
- EC2 prices: https://aws.amazon.com/ec2/pricing/on-demand/ and https://b0.p.awsstatic.com/pricing/2.0/meteredUnitMaps/ec2/USD/current/ec2-ondemand-without-sec-sel/US%20East%20(N.%20Virginia)/Linux/index.json
- EBS prices: https://aws.amazon.com/ebs/pricing/
- GitHub deploys without stored keys: https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services
§7 Redis, Lambda, queues
- ElastiCache pricing: https://aws.amazon.com/elasticache/pricing/ and https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonElastiCache/current/us-east-1/index.csv
- Lambda pricing: https://aws.amazon.com/lambda/pricing/ ; limits: https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html
- SQS pricing: https://aws.amazon.com/sqs/pricing/ ; EventBridge pricing: https://aws.amazon.com/eventbridge/pricing/
- Rate limiting across several copies needs a shared store: https://github.com/fastify/fastify-rate-limit
- pg-boss: https://github.com/timgit/pg-boss
§8 Addresses and access
- Public IPv4 charge: https://aws.amazon.com/vpc/pricing/ and https://aws.amazon.com/blogs/aws/new-aws-public-ipv4-address-charge-public-ip-insights/
- A server's public address is released on stop: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html
- Session Manager: https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager.html and pricing https://aws.amazon.com/systems-manager/pricing/
§9 Cookies and access
- OWASP session management: https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- OWASP anti-forgery (signed double-submit): https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
- Cookie Domain and the
__Host-prefix: https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Set-Cookie - CloudFront managed cache and origin-request policies: https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-cache-policies.html and https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/using-managed-origin-request-policies.html
- CloudFront pay-as-you-go pricing: https://aws.amazon.com/cloudfront/pricing/pay-as-you-go/
- WAF pricing: https://aws.amazon.com/waf/pricing/ ; managed rule groups: https://docs.aws.amazon.com/waf/latest/developerguide/aws-managed-rule-groups-baseline.html
- Shield pricing: https://aws.amazon.com/shield/pricing/
- Secrets Manager pricing: https://aws.amazon.com/secrets-manager/pricing/ ; Parameter Store pricing: https://aws.amazon.com/systems-manager/pricing/
- ACM pricing: https://aws.amazon.com/certificate-manager/pricing/
- AWS Startup Security Baseline: https://docs.aws.amazon.com/prescriptive-guidance/latest/aws-startup-security-baseline/introduction.html
§10 Backups
- Restore to any point in time; logs every 5 minutes: https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_PIT.html
- Retention "from 0 to 35" days: https://docs.aws.amazon.com/AmazonRDS/latest/APIReference/API_ModifyDBInstance.html
- Backup storage allowance: https://aws.amazon.com/rds/faqs/
- Multi-AZ failover (parked): https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.Failover.html
- AWS Backup Vault Lock: https://docs.aws.amazon.com/aws-backup/latest/devguide/vault-lock.html ; AWS Backup pricing: https://aws.amazon.com/backup/pricing/
§11 Video
- Rekognition pricing: https://aws.amazon.com/rekognition/pricing/ and https://b0.p.awsstatic.com/pricing/2.0/meteredUnitMaps/rekognition/USD/current/rekognition.json
- Rekognition video API and limits: https://docs.aws.amazon.com/rekognition/latest/dg/api-video.html and https://docs.aws.amazon.com/rekognition/latest/dg/limits.html
- Rekognition moderation categories and the CSAM caveat: https://docs.aws.amazon.com/rekognition/latest/dg/moderation-api.html
- Transcribe pricing: https://aws.amazon.com/transcribe/pricing/ ; toxicity detection: https://docs.aws.amazon.com/transcribe/latest/dg/toxicity.html
- Bedrock pricing: https://aws.amazon.com/bedrock/pricing/ and https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrock/current/us-east-1/index.csv
- Whisper model sizes: https://github.com/openai/whisper
- Accuracy benchmark (Picovoice, a vendor): https://github.com/Picovoice/speech-to-text-benchmark
- Whisper hallucination study ("Careless Whisper", 2024): https://arxiv.org/html/2402.08021v2
- Amazon's content-moderation reference design: https://docs.aws.amazon.com/solutions/content-moderation-on-aws/
- Amazon's moderation design patterns (frame sampling): https://aws.amazon.com/blogs/machine-learning/content-moderation-design-patterns-with-aws-managed-ai-services/
- Known-CSAM video matching programmes: https://protectingchildren.google/tools-for-partners/ and https://github.com/facebook/ThreatExchange/tree/main/vpdq
§12 and §13 Other prices and terms
- Inspector pricing: https://aws.amazon.com/inspector/pricing/
- Security Hub and Config pricing: https://aws.amazon.com/security-hub/pricing/ and https://aws.amazon.com/config/pricing/
- Support plans: https://aws.amazon.com/premiumsupport/pricing/ and https://docs.aws.amazon.com/awssupport/latest/user/support-plans-eos.html
- Postgres version support dates on RDS: https://docs.aws.amazon.com/AmazonRDS/latest/PostgreSQLReleaseNotes/postgresql-release-calendar.html
- Route 53 pricing: https://aws.amazon.com/route53/pricing/
- SES pricing: https://aws.amazon.com/ses/pricing/ ; Gmail sender rules: https://support.google.com/a/answer/81126
- Budgets pricing: https://aws.amazon.com/aws-cost-management/aws-budgets/pricing/
- Credit terms: https://aws.amazon.com/activate/terms/ and https://aws.amazon.com/awscredits/