Mapping Services Across Cloud Providers
A translation table for the eight things a backend needs from a cloud — with the column that matters most being what the analogy gets wrong.
The requirement, the obvious build, and why it breaks
Every lesson starts where the work starts: someone asked for something, and the first implementation that comes to mind has a problem.
My service needs compute, storage, a database, a cache and a queue. What is the equivalent of each on the provider we actually use?
The architecture was designed on one provider and has to be built on another, or the team is reading documentation written for a provider they do not use.
Learn the mapping — S3 is Cloud Storage is Blob Storage, SQS is Pub/Sub is Service Bus — and substitute names. The concepts are the same everywhere; only the SDK changes.
A design that relies on a queue where each message is consumed once by one worker is ported to a topic-and-subscription service, where every subscription gets its own copy — and the same job now runs three times (Job Idempotency).
- A design that relies on a queue where each message is consumed once by one worker is ported to a topic-and-subscription service, where every subscription gets its own copy — and the same job now runs three times (Job Idempotency).
- A function-based service sized against a platform that runs one request per execution environment is ported to one that runs many concurrent requests per instance, and the connection-pool arithmetic that governed it is silently invalidated (Connection Pools).
- Code written against a managed Postgres reached directly by host and port is ported to a platform whose expected access path is a connector or proxy sidecar, and the "identical Postgres" needs a different connection strategy entirely.
- A presigned-upload flow is ported to a provider whose delegated-access token has a different scope, lifetime and revocation model, and the security review that passed on one does not apply to the other (Presigned URLs).
- Ordering, delivery and retry guarantees are assumed to transfer, and the assumption fails only under load — which is when the duplicates and reorderings that were always allowed start actually happening.
What is actually happening
- Providers converge on categories — run a container, store an object, run a query — and diverge on semantics: concurrency model, delivery guarantee, consistency, identity, quota shape and failure behaviour.
- The category is what a diagram shows. The semantics are what your code depends on. A mapping table transfers the first and quietly loses the second.
- The categories a backend actually needs are small in number: a machine, a managed container runtime, an orchestrator, a function runtime, object storage, a relational database, a cache and a queue. Almost everything else is a variation.
- Within one provider there are often several products in the same category with materially different semantics — a simple queue and a full message broker sit side by side, and picking the wrong one is a bigger error than picking the wrong provider.
- The application-visible differences cluster in four places: how concurrency reaches your process, how identity and credentials are obtained, what the delivery or consistency guarantee is, and what the quota or limit is measured in.
- This page is a lookup table for orientation, and orientation only. Every row needs verification against current provider documentation before it becomes a design.
The table, and the column that matters
Read the last column first. The three provider columns tell you where to look; the fourth tells you what will surprise you when you get there. If you only remember one thing from this lesson, it should be that these are not exact 1:1 equivalents — they are the nearest product in the same category, with different semantics underneath.
The rows are chosen to be the eight things almost every backend needs. Depth on any of them belongs in Cloud & Infrastructure; what follows is the application engineer's orientation map.
| Need | AWS | GCP | Azure | What the mapping gets wrong |
|---|---|---|---|---|
| A machine | EC2 | Compute Engine | Virtual Machines | Instance families, local disk behaviour and preemptible/spot reclamation notice differ, which changes how much warning your process gets before termination. |
| Managed containers | ECS / Fargate, App Runner | Cloud Run | Container Apps / Container Instances | The concurrency model differs sharply: some run many concurrent requests per instance, others fewer or one. That single property decides your pool sizing and whether in-process caching helps. |
| Kubernetes | EKS | GKE | AKS | The Kubernetes API is genuinely portable; the surrounding integration is not — ingress controllers, load-balancer provisioning, storage classes, node autoscaling and workload identity all differ. |
| Functions | Lambda | Cloud Run functions / Cloud Functions | Azure Functions | Concurrency per execution environment, maximum execution duration, and whether work may continue after the response differ. See the worked example below. |
| Object storage | S3 | Cloud Storage | Blob Storage | The naming hierarchy, delegated-access tokens (presigned URL vs SAS), versioning semantics, storage-class transitions and multipart-upload rules all differ, and delegated access is a security decision. |
| Managed Postgres | RDS for PostgreSQL, Aurora PostgreSQL | Cloud SQL for PostgreSQL, AlloyDB | Database for PostgreSQL (Flexible Server) | Postgres-compatible is not Postgres: replication and failover behaviour, connection-handling (built-in pooler or a separate proxy product), extension availability and major-version support all vary. |
| Cache | ElastiCache, MemoryDB | Memorystore | Azure Cache for Redis / Managed Redis | Persistence and durability differ by product: some are strictly a cache, one is a durable in-memory database. Whether you may treat a miss as fatal depends on which. |
| Queue | SQS (+ EventBridge) | Pub/Sub, Cloud Tasks | Storage Queues, Service Bus | The largest semantic gap on this page: point-to-point queue vs topic/subscription fan-out, ordering guarantees, dead-lettering, and per-message vs per-subscription acknowledgement. See below. |
Where the analogy actively misleads: two worked examples
Queue is not queue. A point-to-point queue delivers each message to one consumer, which acknowledges it or lets a visibility timeout return it. A topic-and-subscription service delivers a copy to *every subscription*, and each subscription acknowledges independently. Porting "we have one queue and three worker deployments" from the first model to the second by creating a topic and three subscriptions changes the meaning completely: instead of the three workers sharing the load, each one now receives every message, and every job runs three times. The correct translation is one subscription with three consumers, and nothing in the name tells you that. In the other direction, a design that relied on fan-out to independent consumers has no direct equivalent in a single point-to-point queue and needs a topic in front of several queues. On a third provider you may find two products in the same category — a minimal queue and a full broker with sessions, ordering and transactions — where choosing the wrong one is a larger mistake than choosing the wrong provider.
Function is not function. One widely used function platform runs a single request per execution environment at a time, so 200 concurrent requests means up to 200 environments, each with its own process, its own cold-start risk and its own database connection. A container-based request platform on another provider defaults to serving many concurrent requests per instance, so the same 200 concurrent requests may be handled by a handful of instances sharing a handful of pools. The application code can be nearly identical; the pressure on the database differs by an order of magnitude, and a design that was safe on one becomes a connection-exhaustion incident on the other (Serverless Backends).
A third, quieter example: a "managed Postgres" that is a Postgres-compatible engine with a different storage and replication architecture will accept your SQL and behave differently on failover, on replica lag and on a handful of query plans. That is not a defect — it is a different product with a compatible interface, and the compatibility is exactly what makes the difference easy to miss.
// Source design (point-to-point queue): // orders-queue -> 3 worker instances // each message handled once, by one worker // "Ported" to a topic/subscription service: // topic: orders // subscription: worker-a // subscription: worker-b // subscription: worker-c // // Every message is now delivered to all three. // Every order is processed three times.
// Required semantics, written down first: // - each message processed by exactly one consumer // - at-least-once delivery, so handlers are idempotent // - ack deadline > slowest handler (else redelivery // races the original) // - failed messages land in a dead-letter after N tries // Correct mapping: // topic: orders // subscription: order-workers <-- ONE // 3 consumers pulling from that one subscription // dead-letter policy configured explicitly
The unit of delivery differs between the two models: a queue fans out to consumers, a subscription fans out to subscriptions. Writing the required semantics down first — one consumer per message, at-least-once, ack deadline, dead-lettering — makes the correct mapping mechanical, and makes it obvious that the naive one triples every side effect.
How to port without porting the assumptions
The reliable method is not a bigger table. It is to convert your dependency on a service into a short list of guarantees, and then verify each guarantee individually. Most of the list is the same four questions for every category.
This is also the right way to choose between two products from the same provider, which is a more frequent decision than choosing between providers.
What am I actually depending on, and have I checked it?
when Any compute service.
cost Decides pool size, connection count, cold-start frequency and whether in-process cache is useful (Connection Pools).
when Any queue, stream, or replicated store.
cost Decides whether handlers must be idempotent and whether reads can be stale (At-Least-Once Delivery).
when Every service.
cost Decides secret handling, rotation and the audit story (Secrets Are Not Configuration).
when Every service, before load testing.
cost Per-account vs per-region vs per-resource quotas fail differently and are found late (Rate Limiting).
when Databases, caches, queues.
cost Failover time, throttling response and partial availability decide your timeout and retry policy (Timeouts).
when Anything that will hold years of data.
cost Data egress and re-architecture, which are strategic rather than technical (Egress: Moving Data Costs Money, Not Just Storing It in Cloud & Infrastructure).
How to build it
Most important first.
- Design against the category and the semantics you need, stated explicitly: "a queue with at-least-once delivery, per-message acknowledgement, and a visibility timeout longer than our slowest handler" is portable; "SQS" is not.
- Write down the three or four guarantees your design depends on before choosing the product, then check them one at a time against the candidate. That check is the actual porting work.
- Isolate provider SDKs behind a narrow internal interface — the operations you use, not the whole surface — so a substitution is a module rather than a search-and-replace (The Repository Layer).
- Treat concurrency model as a first-class property of a compute product, because it determines connection counts, pool sizing and whether an in-process cache is useful at all.
- Assume nothing about identity. Prefer the platform's workload identity over static keys on every provider, and expect the mechanism to differ completely between them (Secrets Are Not Configuration).
- Verify limits in the units the provider uses: some quotas are per-account, some per-region, some per-resource; some are hard and some are soft. A design that fits one shape may not fit another.
- Where the depth of a managed service matters — replication topology, failover behaviour, storage classes — go to Cloud & Infrastructure rather than reasoning from the name (Managed Databases, Direct Uploads and Signed Authorization).
What can go wrong
- Porting a design by name and discovering the semantic difference in production, under load, where duplicate delivery and reordering actually manifest.
- Assuming a "compatible" database engine is the same engine. Compatibility covers the wire protocol and most SQL; it does not cover replication behaviour, failover timing, extension availability or performance characteristics.
- Building a portability abstraction over every provider feature, which costs more than the migration it was insuring against and hides the semantics it was supposed to expose.
- Reading a blog post written for another provider and inheriting its architecture along with its assumptions.
- Assuming region and zone semantics match. What is guaranteed to be failure-independent differs, and multi-zone designs do not port on the strength of the words alone (Regions and Availability Zones).
- Identity models differ more than anything else on this list: role assumption, service-account impersonation and managed identity are three distinct mechanisms with different scoping, expiry and audit behaviour. A least-privilege policy does not translate line by line (The IAM Model in Cloud & Infrastructure).
- Delegated object access — presigned URL versus shared access signature — differs in what can be scoped, how long it lasts and whether it can be revoked before expiry. Re-do the threat model, do not port it (Presigned URLs).
- Default network exposure differs. A resource that is private by default on one provider may be reachable by default on another; verify rather than assume (Public Exposure, Read With Context in Cloud & Infrastructure).
- Encryption defaults and key custody differ per service. "Encrypted at rest" is true almost everywhere and means different things about who holds the key.
- "These services are equivalent." They are not. Every row in the table below is an approximation, and the last column exists because the approximation fails in ways that change application code.
- "A Postgres-compatible service is Postgres." Wire and SQL compatibility is not behavioural equivalence. Replication, failover, extensions and query performance can all differ.
- "Serverless functions work the same everywhere." Their concurrency models differ, and concurrency model is the property that decides connection pressure, cold-start frequency and whether in-process caching helps at all.
- "Multi-cloud means picking portable services." It means operating everything twice. The portability of the services is the smallest part of the cost.
- "The mapping is stable." Providers rename, replace and re-position products continuously. Verify against current documentation; treat any table, including this one, as a starting point.
Operating it
- Instrument your own client calls — attempts, retries, latency, throttles — rather than relying only on provider metrics, so behaviour is comparable across providers in your own terms.
- Alert on throttling and quota errors as a distinct class. Every provider has them, they surface differently, and they are not ordinary 5xx (An Error Taxonomy That Maps Cause to Response).
- Record which managed service and which region served each dependency call, so cross-region and cross-provider latency is visible rather than inferred (Cross-Region Latency Is Physics, Not Configuration in Observability & Performance).
- Quota shape becomes the binding constraint before capacity does. Concurrency ceilings, connections per instance, messages in flight and requests per second per resource are the numbers that stop a design, and they are provider-specific.
- At 100x, differences in a service's scaling granularity matter: a service that scales in fixed instances behaves differently from one that scales per request, both in latency shape and in what your database sees.
- Multi-provider deployment multiplies operational surface rather than dividing risk, and is a strategic decision rather than a technical default (Multi-Cloud, Taught Cautiously in Cloud & Infrastructure).
- Managed services remove operational work and add coupling. That trade is usually correct and should be made knowingly, with the semantics you depend on written down.
- An abstraction layer buys optionality and costs clarity: it hides exactly the semantic differences that make the abstraction leaky.
- Choosing the lowest-common-denominator feature set across providers gives portability and gives up the specific capability that made the managed service worth using.
Where this applies
Backend advice is context-sensitive. These labels say what each claim is specific to, and where a different stack or scale would differ.
- CLOUD-SPECIFICEvery claim on this page is provider-specific by construction, and provider product lines change. Treat the table as orientation and verify each row against current documentation before designing on it.
- SIMPLIFIEDOne product per category per provider, where most providers offer several with materially different semantics — a simple queue and a full broker, a container platform with per-request scaling and one with fixed tasks. The simplification is exactly where the misleading part lives.
- GENERALThe method — specify the semantics you depend on, then check them one at a time — is provider-independent and is the only part of this lesson with a long shelf life.
Where the depth lives
This domain teaches the application-side mechanism and hands the rest off.