Managed Databases
Databases the provider runs for you: managed relational databases, NoSQL options, backups, replicas and when to choose which.
What "managed" means
Running a database server yourself is a lot of work beyond the database itself: installing the engine, applying security patches, taking and testing backups, setting up replication and failover, replacing failed disks, adding capacity. A managed database is a database service in which the cloud provider does that operational work. You choose an engine, a size and some options; the provider hands you a connection endpoint. In service model terms it is platform as a service (PaaS).
| The provider handles | You still own |
|---|---|
| Hardware, operating system and engine installation | The schema: tables, indexes, data types |
| Patching, within a maintenance window you choose | The data itself and its quality |
| Automated backups and their storage | Choosing retention, and testing that a restore works |
| Replication and automatic failover (when enabled) | Query performance: slow queries stay slow |
| Monitoring of the servers, replacing failed parts | Database users, passwords and permissions |
| Scaling options (bigger instance, more storage) | Network access, encryption settings, choosing the size |
The trade-off is control. You usually cannot log in to the operating system, install arbitrary extensions or pick any engine version, and moving a large database to another provider later is real work. For most teams the saved effort is worth it; teams with unusual requirements still run databases on virtual machines.
Managed relational databases
Relational databases store data in tables with a fixed schema and are queried with Structured Query Language (SQL). They are the default choice for business data: orders, customers, invoices, anything with relationships and transactions that must be all-or-nothing. (SQL itself belongs in the Databases & SQL topic.) Each provider offers the common open-source and commercial engines as a managed service:
| Provider | Managed relational services | Engines |
|---|---|---|
| AWS | Amazon RDS (Relational Database Service); Amazon Aurora | PostgreSQL, MySQL, MariaDB, SQL Server, Oracle, Db2; Aurora is compatible with PostgreSQL and MySQL |
| Microsoft Azure | Azure SQL Database, Azure SQL Managed Instance; Azure Database for PostgreSQL and for MySQL | SQL Server engine; PostgreSQL, MySQL |
| Google Cloud | Cloud SQL; AlloyDB; Spanner | PostgreSQL, MySQL, SQL Server; AlloyDB is PostgreSQL-compatible; Spanner is Google's own globally distributed database |
Aurora, AlloyDB and Spanner are provider-built engines: compatible with familiar ones (or with SQL in general) but with the storage and replication redesigned for the cloud. Aurora, for example, keeps six copies of the data spread over three availability zones. That brings performance and resilience, at the price of tying you more closely to one provider.
High availability and read replicas
Two features both involve "a second copy of the database", which makes them easy to confuse. They solve different problems.
A standby for availability
In a multi-AZ deployment (AZ stands for availability zone) the provider keeps a standby copy of the database in a different availability zone and replicates every write to it synchronously: a transaction is confirmed only when both copies have it. If the primary or its zone fails, the provider fails over automatically: the standby becomes the primary and the database endpoint (a Domain Name System (DNS) name) points to it, typically within a minute or two. Applications reconnect to the same name and lose no committed data.
In the classic Amazon RDS setup the standby does not serve any queries; it waits. Azure SQL Database (zone-redundant configuration) and Cloud SQL (high availability configuration) work on the same principle. Some newer designs, such as an RDS Multi-AZ DB cluster, keep standbys that can also serve reads.
Read replicas for scaling reads
A read replica is a read-only copy of the database, updated asynchronously from the primary. Applications send reporting queries, searches and other reads to the replica's own endpoint, so the primary has more capacity for writes. Because replication is asynchronous, a replica is always slightly behind: this replication lag is usually under a second but can grow under heavy load. A user who saves a change and immediately reads it from a replica may not see it yet; reads that must be current go to the primary.
Replicas can live in another zone or even another region, which also helps with disaster recovery: a cross-region replica can be promoted to a standalone database if the primary region is lost. That is a manual decision, and the data lost equals the lag at that moment.
Backups and point-in-time restore
Managed databases take automated backups: a daily
snapshot of the storage plus a continuous copy of the transaction
log. Together they make
point-in-time restore
possible: restoring the database as it was at any moment within the
retention period, down to the second, typically up to a few minutes
ago. That is exactly what you need after someone runs an
UPDATE without a WHERE clause at 14:32:
restore to 14:31.
Points to know before you need it:
- A restore creates a new database instance with a new endpoint; it does not overwrite the existing one. You then copy the lost data back or point the application to the new instance.
- Retention is a setting, commonly a default of 7 days and a maximum of around 35 days at the time of writing. Set it based on your recovery point objective (RPO) and how long a mistake might go unnoticed; the section on RPO and recovery time objective (RTO) in Backup and Recovery explains both.
- Manual snapshots are kept until you delete them, which suits a copy before a risky migration or for long-term retention.
- Keep a copy elsewhere. Backups in the same account and region do not protect against a compromised account or a regional outage. Copy snapshots to another region or a separate backup account, and make sure deleting the database does not silently delete all its backups.
- Test restores. Restore time grows with database size; measure it, so your RTO is based on fact.
NoSQL databases
A NoSQL database ("not only SQL") stores data in a model other than relational tables and usually trades some query flexibility for scale, speed or a better fit with the data's shape. Most cloud NoSQL services are serverless or close to it: you do not choose servers, and they scale to very large volumes. There are four main families.
| Family | Data model | Typical use | Examples |
|---|---|---|---|
| Key-value | A value looked up by its unique key | Sessions, shopping carts, user settings, caches | Amazon DynamoDB, Azure Cosmos DB, in-memory caches such as Redis |
| Document | JavaScript Object Notation (JSON) documents with nested fields; each can differ | Product catalogs, user profiles, content, mobile app data | Azure Cosmos DB, Google Cloud Firestore, Amazon DocumentDB, DynamoDB |
| Wide-column | Rows with many, variable columns, grouped and sorted by key | Time series, sensor data, very large write volumes | Google Cloud Bigtable, Amazon Keyspaces, Cosmos DB for Apache Cassandra |
| Graph | Nodes and the relationships between them | Social networks, recommendations, fraud detection | Amazon Neptune, Cosmos DB for Apache Gremlin |
Azure Cosmos DB appears in several rows because it offers several application programming interfaces (APIs) over one globally distributed service. DynamoDB is both a key-value and a document store.
Relational or NoSQL?
The key difference is when you decide how data will be queried. A relational database lets you ask new questions later: join tables, filter on any column, aggregate. A key-value or document database is designed around access patterns you know in advance; it answers "give me the item with this key" extremely fast at any scale, but a question the key does not answer means reading everything, or adding an index or a second table built for that question.
The example uses Python's built-in sqlite3 for the
relational side and a dictionary as a stand-in for a key-value
store:
import sqlite3
orders = [
(1041, "ana@example.com", "Amsterdam", 59.90),
(1042, "ben@example.com", "Utrecht", 12.50),
(1043, "ana@example.com", "Amsterdam", 24.00),
(1044, "chen@example.com", "Amsterdam", 99.00),
]
# Relational: a table with a schema, queried with SQL.
db = sqlite3.connect(":memory:")
db.execute("CREATE TABLE orders (id INTEGER PRIMARY KEY, customer TEXT, city TEXT, total REAL)")
db.executemany("INSERT INTO orders VALUES (?, ?, ?, ?)", orders)
query = """
SELECT city, COUNT(*), SUM(total) FROM orders
GROUP BY city ORDER BY city
"""
print("SQL: revenue per city")
for city, count, revenue in db.execute(query):
print(f" {city:10} orders: {count} revenue: {revenue:7.2f}")
# Key-value: every item is stored and fetched by its key.
store = {f"order#{oid}": {"customer": c, "city": city, "total": t}
for oid, c, city, t in orders}
print("Key-value: get order#1042 ->", store["order#1042"])
# A question the key does not answer means reading every item.
scanned = 0
amsterdam = 0.0
for item in store.values():
scanned += 1
if item["city"] == "Amsterdam":
amsterdam += item["total"]
print(f"Key-value: Amsterdam revenue {amsterdam:.2f} after scanning {scanned} of {len(store)} items")
SQL: revenue per city
Amsterdam orders: 3 revenue: 182.90
Utrecht orders: 1 revenue: 12.50
Key-value: get order#1042 -> {'customer': 'ben@example.com', 'city': 'Utrecht', 'total': 12.5}
Key-value: Amsterdam revenue 182.90 after scanning 4 of 4 items
With four items the scan is harmless. With four hundred million it is slow and, on a service that bills per item read, expensive. In DynamoDB you would therefore design the keys around the questions ("all orders of one customer" becomes a key on the customer), or add a secondary index. In SQL the same new question is one more query.
| Managed relational | Managed NoSQL | |
|---|---|---|
| Schema | Fixed, enforced by the database | Flexible; the application enforces structure |
| Queries | Any SQL query, joins, aggregates | Fast lookups by key; other queries need planning |
| Transactions | Full, across many tables | Often limited; varies per service |
| Scaling | Mostly up (bigger instance) plus read replicas | Out across many servers, largely automatic |
| Billing | Mostly per instance-hour and storage | Often per request or provisioned throughput, and storage |
| Good default for | Business data, reporting, unknown future questions | Huge scale with known access patterns, flexible documents |
When in doubt, start relational: it is the most flexible and the skills are widely available. Choose NoSQL for a concrete reason.
Cost and sizing
A managed relational database is billed mostly for the time its instances run, whether they are busy or not, plus storage, backup storage beyond a free allowance, and data transfer. Every extra copy — the standby, each replica — is another instance. The prices below are invented round numbers, only to show how the options add up:
# FICTITIOUS round prices for illustration only, not any provider's real prices.
INSTANCE_PER_HOUR = 0.50 # one database instance of the chosen size
STORAGE_PER_GB_MONTH = 0.10 # provisioned storage, charged per copy
HOURS_PER_MONTH = 730
def monthly_cost(storage_gb, multi_az=False, read_replicas=0):
copies = 1 + (1 if multi_az else 0) + read_replicas
instances = INSTANCE_PER_HOUR * HOURS_PER_MONTH * copies
storage = STORAGE_PER_GB_MONTH * storage_gb * copies
return instances + storage
for label, options in [
("Single instance", {}),
("Multi-AZ (standby)", {"multi_az": True}),
("Multi-AZ + 2 read replicas", {"multi_az": True, "read_replicas": 2}),
]:
print(f"{label:28} {monthly_cost(200, **options):8.2f} per month")
Single instance 385.00 per month Multi-AZ (standby) 770.00 per month Multi-AZ + 2 read replicas 1540.00 per month
High availability roughly doubles the bill, which is usually right for production and wasteful for a test database. Real pricing has more parts (Aurora, for example, shares storage between copies and charges for input/output), so use the provider's pricing calculator. Sizing advice:
- Start modestly and watch central processing unit (CPU), memory and storage metrics; scaling up a managed database is a setting, though it may cause a short interruption.
- Turn on storage autoscaling (where offered) so a full disk does not stop the database.
- For steady production databases, reserved capacity lowers the hourly price; see Cloud Costs and FinOps.
- For spiky or rarely used databases, serverless options such as Aurora Serverless or Azure SQL Database serverless scale capacity with demand.
- Stop or delete development databases outside working hours — after taking a snapshot if the data matters. Note that a stopped Amazon RDS instance starts again automatically after seven days.
Securing a managed database
- No public endpoint. Place the database in private subnets and switch off public accessibility. Allow its port only from the application's security group, and reach it for administration through a virtual private network (VPN) or a jump server. Publicly reachable databases are one of the most common causes of data leaks.
- Encryption at rest with the provider's key management service, decided at creation: on Amazon RDS an unencrypted instance cannot simply be switched to encrypted later. Snapshots and replicas inherit the encryption.
- Encryption in transit: require Transport Layer Security (TLS) for client connections.
- Identity-based authentication instead of shared passwords where possible: identity and access management (IAM) database authentication on RDS and Cloud SQL, Microsoft Entra authentication for Azure SQL. Passwords that remain belong in a secrets manager with rotation, not in application code.
- Least privilege inside the database: the application's database user needs to read and write its own tables, not to drop the database or create users. See Identity and Access Management and Cloud Security and Compliance.