{"id":"l-aws-3-3","courseId":"aws-cloud-foundations","lang":"en","kind":"lesson","summary":"Choose between Amazon RDS and Amazon DynamoDB from a workload’s access patterns, and set each one up with backups, encryption and private access from the start.","html":"<p>Lantern’s old server had one PostgreSQL database for everything: events, venues, organisers and every RSVP. During the festival spike, RSVP writes queued up behind a slow report query and the whole site stalled. On AWS you can give each kind of data the database that suits it. The two you will meet first are <strong>Amazon RDS</strong> and <strong>Amazon DynamoDB</strong>, and they differ in almost every way that matters.</p>\n<h2 id=\"sec-1\" tabindex=\"-1\">Amazon RDS: your SQL database, run for you</h2>\n<p><strong>Amazon Relational Database Service</strong> runs a relational engine on infrastructure AWS manages. Engines include PostgreSQL, MySQL, MariaDB, Oracle, SQL Server and Db2, plus <strong>Amazon Aurora</strong>, AWS’s own MySQL- and PostgreSQL-compatible engine.</p>\n<p>AWS takes on the chores Lantern used to do at 2 a.m.: provisioning, OS and engine patching in a maintenance window you choose, <strong>automated backups</strong> with point-in-time recovery, and <strong>Multi-AZ</strong> deployments with a synchronous standby in another Availability Zone that takes over automatically. You still own the schema, indexes, queries, database users, the instance size, and who can connect.</p>\n<p>Here is how Lantern creates its events database, with the safe choices spelled out:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"0\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> rds</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create-db-instance</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --db-instance-identifier</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> lantern-events</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --engine</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> postgres</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --db-instance-class</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> db.t4g.micro</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --allocated-storage</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 20</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --storage-encrypted</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --master-username</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> lantern_admin</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --manage-master-user-password</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --backup-retention-period</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> 7</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --multi-az</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --no-publicly-accessible</span></span></code></pre></div>\n<p><code dir=\"ltr\">--manage-master-user-password</code> stores the admin password in AWS Secrets Manager instead of your shell history. <code dir=\"ltr\">--no-publicly-accessible</code> keeps the database off the internet; next lesson puts it in a private subnet. And because Lantern’s existing app speaks SQL, migrating is a <code dir=\"ltr\">pg_dump</code> and a restore, with the queries unchanged:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"sql\" data-index=\"1\"><div class=\"code-head\"><span class=\"code-title\">SQL</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">SELECT</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> v</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, date_trunc(</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">'month'</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">starts_at</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">count</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">(</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">*</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">) </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">AS</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> events</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">FROM</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> events e</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">JOIN</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\"> venues v </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ON</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> v</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">id</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> =</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> e</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">venue_id</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">GROUP BY</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> v</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">.</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">name</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">month</span></span>\n<span class=\"line\"><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">ORDER BY</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> month</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> DESC</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">, events </span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\">DESC</span><span style=\"color:#1F2328;--shiki-dark:#E6EDF3\">;</span></span></code></pre></div>\n<p><strong>How RDS bills:</strong> per <strong>instance-hour</strong> (with Savings Plans or reserved instances for steady use), plus storage per GB-month, backups beyond a free allowance, and data transfer. A Multi-AZ deployment roughly doubles the instance cost because there are two instances. Crucially, the instance bills <strong>all month whether busy or idle</strong>. Aurora Serverless v2 can scale capacity with load and pause when idle, which suits spiky or development databases.</p>\n<h2 id=\"sec-2\" tabindex=\"-1\">Amazon DynamoDB: design the keys, get speed at any scale</h2>\n<p><strong>DynamoDB</strong> is a serverless key-value and document database. There is no instance to size: you create a <strong>table</strong>, choose a <strong>primary key</strong>, and read and write <strong>items</strong> (JSON-like documents) with consistent single-digit-millisecond latency, at ten requests a day or tens of thousands a second.</p>\n<p>The catch is that you design the keys around the questions you will ask. For RSVPs, Lantern’s questions are “who is coming to event X?” and “has this person already RSVPed to X?”. That gives a <strong>partition key</strong> of <code dir=\"ltr\">eventId</code> and a <strong>sort key</strong> of <code dir=\"ltr\">email</code>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"2\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> dynamodb</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> create-table</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --table-name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> lantern-rsvps</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --attribute-definitions</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> AttributeName=eventId,AttributeType=S</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">                          AttributeName=email,AttributeType=S</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --key-schema</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> AttributeName=eventId,KeyType=HASH</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\">               AttributeName=email,KeyType=RANGE</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --billing-mode</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> PAY_PER_REQUEST</span></span></code></pre></div>\n<p>Counting the RSVPs for one event is then a cheap <strong>Query</strong>:</p>\n<div class=\"code-block\" dir=\"ltr\" data-lang=\"bash\" data-index=\"3\"><div class=\"code-head\"><span class=\"code-title\">Terminal</span><span class=\"code-actions\"><button type=\"button\" class=\"code-btn\" data-action=\"copy\" aria-label=\"Copy code\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><rect width=\"14\" height=\"14\" x=\"8\" y=\"8\" rx=\"2\" ry=\"2\"/><path d=\"M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2\"/></svg><span>Copy</span></button></span></div><pre class=\"shiki\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#953800;--shiki-dark:#FFA657\">aws</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> dynamodb</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> query</span><span style=\"color:#0550AE;--shiki-dark:#79C0FF\"> --table-name</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> lantern-rsvps</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --key-condition-expression</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> \"eventId = :e\"</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --expression-attribute-values</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> '{\":e\": {\"S\": \"evt-2026-street-food\"}}'</span><span style=\"color:#CF222E;--shiki-dark:#FF7B72\"> \\</span></span>\n<span class=\"line\"><span style=\"color:#0550AE;--shiki-dark:#79C0FF\">  --select</span><span style=\"color:#0A3069;--shiki-dark:#A5D6FF\"> COUNT</span></span></code></pre></div>\n<p><strong>How DynamoDB bills:</strong> in <strong>on-demand</strong> mode (<code dir=\"ltr\">PAY_PER_REQUEST</code>), per read and write request plus storage per GB-month, and nothing for idle time. <strong>Provisioned</strong> mode, with auto scaling, can be cheaper for steady, predictable traffic. Start on-demand; switch only when the bill shows steady load. Point-in-time recovery is a setting you turn on per table, and you should.</p>\n<aside class=\"callout callout-mistake\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><path d=\"m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3\"/><path d=\"M12 9v4\"/><path d=\"M12 17h.01\"/></svg><span><span class=\"callout-kind\">Common mistake</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Scanning to find a few items</span></p>\n<p>A <code dir=\"ltr\">Scan</code> with a filter reads every item in the table and then throws most away, and you pay for every item read. It works in testing with 50 RSVPs and becomes slow and expensive with 500,000. If a hot path needs a question your keys can’t answer with <code dir=\"ltr\">Query</code> or <code dir=\"ltr\">GetItem</code>, add a global secondary index for it rather than scanning.</p>\n</aside>\n<h2 id=\"sec-3\" tabindex=\"-1\">Choosing</h2>\n<div class=\"table-wrap\" role=\"region\" aria-label=\"Scrollable table\" tabindex=\"0\"><table><thead>\n<tr>\n<th>Question</th>\n<th>RDS</th>\n<th>DynamoDB</th>\n</tr>\n</thead>\n<tbody>\n<tr>\n<td>Are the questions varied or ad hoc?</td>\n<td>Yes: SQL handles new ones</td>\n<td>No: keys answer known ones</td>\n</tr>\n<tr>\n<td>Need joins and multi-table constraints?</td>\n<td>Yes</td>\n<td>Rarely; you denormalise</td>\n</tr>\n<tr>\n<td>Traffic spiky, with idle time?</td>\n<td>Pays for idle hours</td>\n<td>Pays per request</td>\n</tr>\n<tr>\n<td>Existing SQL code to keep?</td>\n<td>Keep it</td>\n<td>Rewrite data access</td>\n</tr>\n<tr>\n<td>Ops you still do</td>\n<td>Sizing, connections, upgrades</td>\n<td>Key design</td>\n</tr>\n</tbody>\n</table></div><p>Lantern ends up with <strong>both</strong>: events, venues and organisers in RDS for PostgreSQL, where reports and the existing admin pages work unchanged; RSVPs in DynamoDB, where a viral event’s write spike costs a few cents of requests instead of stalling the shared database.</p>\n<aside class=\"callout callout-why\" role=\"note\"><p class=\"callout-title\"><svg class=\"icon \" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\" focusable=\"false\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></svg><span><span class=\"callout-kind\">Why this matters</span><span class=\"callout-sep\" aria-hidden=\"true\"> · </span>Security is the same job in both</span></p>\n<p>Encrypt at rest (on by default for DynamoDB, a flag for RDS), keep the database unreachable from the internet, and grant the app’s role only the actions it needs, such as <code dir=\"ltr\">dynamodb:PutItem</code> and <code dir=\"ltr\">dynamodb:Query</code> on one table ARN.</p>\n</aside>\n<p>The database is private, but private from <em>what</em>? Next lesson builds the network Lantern’s pieces live in.</p>\n","toc":[{"id":"sec-1","text":"Amazon RDS: your SQL database, run for you","level":2},{"id":"sec-2","text":"Amazon DynamoDB: design the keys, get speed at any scale","level":2},{"id":"sec-3","text":"Choosing","level":2}],"takeaways":["Amazon RDS runs a relational engine such as PostgreSQL for you, handling patching, backups and Multi-AZ failover, while you own the schema, queries and connections.","Amazon DynamoDB is a serverless key-value and document database where you design the keys around the questions you will ask.","RDS bills mostly per instance-hour, whether busy or idle; DynamoDB on-demand bills per request and per gigabyte stored.","Use a DynamoDB Query on keys for hot paths, because a Scan reads and bills the whole table.","Many real systems use both, relational data in RDS and high-volume simple lookups in DynamoDB."],"further":[{"title":"What is Amazon RDS?","url":"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Welcome.html"},{"title":"What is Amazon DynamoDB?","url":"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html"},{"title":"DynamoDB throughput capacity (on-demand and provisioned)","url":"https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/capacity-mode.html"},{"title":"Configuring and managing a Multi-AZ deployment for Amazon RDS","url":"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Concepts.MultiAZ.html"}],"quiz":[{"q":"<p>Lantern’s treasurer wants ad-hoc reports such as “events per venue per month, with organiser names”, and the questions change every quarter. Which database fits?</p>","options":[{"text":"DynamoDB, because it is serverless and scales automatically.","why":"Scaling is not the problem here. DynamoDB answers questions you designed keys for; new ad-hoc joins mean new indexes or exports each time."},{"text":"RDS for PostgreSQL, because SQL joins and aggregates answer new questions without redesigning the data.","why":"Correct. Relational databases shine when the questions are varied and change over time."},{"text":"S3 Standard, because reports are files.","why":"The report output can live in S3, but S3 is not a database and cannot join or aggregate rows on its own."}],"answer":1},{"q":"<p>The RSVP table’s partition key is <code dir=\"ltr\">eventId</code> and sort key is <code dir=\"ltr\">email</code>. Which call returns every RSVP for one event efficiently?</p>","options":[{"text":"A Scan with a filter on <code dir=\"ltr\">eventId</code>.","why":"A Scan reads every item in the table and applies the filter afterwards, so you pay for reading the whole table."},{"text":"A GetItem with only <code dir=\"ltr\">eventId</code>.","why":"GetItem needs the full primary key, partition key and sort key, and returns a single item."},{"text":"A Query with the key condition <code dir=\"ltr\">eventId = :e</code>.","why":"Correct. A Query reads only the items under that partition key, so cost grows with the RSVPs for that event, not the table."}],"answer":2},{"q":"<p>Which RDS setting protects Lantern’s events database from the loss of one Availability Zone?</p>","options":[{"text":"A Multi-AZ deployment, which keeps a standby in another AZ and fails over automatically.","why":"Correct. RDS replicates to the standby synchronously and switches over when the primary’s AZ has a problem."},{"text":"Automated backups with a seven-day retention period.","why":"Backups let you restore to a point in time, but restoring creates a new instance and takes a while. They are not automatic failover."},{"text":"A larger instance class.","why":"More CPU and memory help with load, but a bigger instance in one AZ still goes down with that AZ."},{"text":"Storage encryption.","why":"Encryption protects data at rest from being read. It does nothing for availability."}],"answer":0}],"exercise":{"kind":"choice","multiple":true,"options":["rsvps","reports","ticket-lookup","relational-admin","rate-limit"],"correct":["rsvps","ticket-lookup","rate-limit"],"title":"Which data belongs in DynamoDB?","prompt":"<p>Lantern is splitting its single PostgreSQL database. For each feature, decide whether Amazon DynamoDB is a <strong>good fit</strong>. Select every feature that should use DynamoDB; the rest stay in Amazon RDS for PostgreSQL.</p>\n","hints":["DynamoDB is strongest when you can say in advance exactly which key every request will use.","Varied, changing questions and joins across several tables point to SQL.","Features with short-lived data can use DynamoDB’s Time to Live (TTL) to delete items automatically at no extra cost."],"explanation":"<p>RSVPs, ticket lookups and rate-limit counters all have one known access pattern each, keyed by an ID, and they see sudden bursts of traffic, which is where DynamoDB’s per-request pricing and consistent latency shine. Rate-limit counters also expire, and TTL deletes them for you (usually within a day or two of expiry, so the code still compares the expiry time itself). The treasurer’s reports ask new questions every quarter with joins and aggregates, and the admin pages rely on foreign keys across organisers, venues and events; both are relational work that the existing SQL already does well in RDS.</p>\n","checkLabels":{},"optionText":{"rsvps":{"text":"RSVPs: write one per person per event, list them by event, check for a duplicate.","why":"Good fit. Every request uses <code dir=\"ltr\">eventId</code>, plus <code dir=\"ltr\">email</code> for the duplicate check, and writes spike when an event goes viral."},"reports":{"text":"Quarterly reports that the treasurer redesigns each time, joining events, venues and organisers.","why":"Not a good fit. Ad-hoc questions with joins are exactly what SQL handles and DynamoDB keys do not."},"ticket-lookup":{"text":"Checking a ticket by its ticket ID at the festival gate, thousands of times per minute.","why":"Good fit. A single-key <code dir=\"ltr\">GetItem</code> at high volume with low latency is DynamoDB’s core strength."},"relational-admin":{"text":"The admin pages, which edit organisers, venues and events with foreign keys between them.","why":"Not a good fit. Multi-table relationships and constraints are relational work, and the existing SQL code keeps working in RDS."},"rate-limit":{"text":"Per-IP counters that limit RSVP spam, each expiring after ten minutes.","why":"Good fit. One key per IP address, very frequent small writes, and TTL removes expired counters automatically."}}},"words":708,"readMinutes":3}