DEV Community

Cover image for DynamoDB vs Google Cloud Spanner
DynoTable
DynoTable

Posted on • Originally published at dynotable.com

DynamoDB vs Google Cloud Spanner

DynamoDB and Google Cloud Spanner both scale horizontally across a distributed cluster, but they make opposite core bets. DynamoDB is a serverless AWS NoSQL key-value and document store designed around known access patterns. Spanner is a globally-distributed relational database that keeps full SQL and strong (externally consistent) transactions even across regions, using Google's TrueTime clock.

Should you use DynamoDB or Spanner?

Choose DynamoDB if you are on AWS and want a serverless NoSQL store with pay-per-request billing and predictable latency for key-based access. Choose Google Cloud Spanner if you need relational SQL, joins, and strong transactional consistency at global scale — a horizontally scalable database that still behaves like a relational one. Relational guarantees at scale versus serverless NoSQL simplicity is the deciding axis.

DynamoDB vs Spanner at a glance

Characteristic DynamoDB Google Cloud Spanner
Data model NoSQL key-value and document; flexible per-item schema, items up to 400 KB Relational — tables, rows, columns, and schemas (also exposes graph, key-value, and search capabilities)
Query language Native API (GetItem, Query, Scan) plus PartiQL, a SQL-compatible language Full SQL in two dialects — GoogleSQL and a PostgreSQL-compatible dialect
Joins & relations No JOIN operator; AWS recommends denormalizing around access patterns Full relational joins, foreign keys, and interleaved tables
Consistency Eventually consistent by default; strongly consistent reads per request (within a Region) External (strong) consistency by default, including across regions, backed by the TrueTime clock
Transactions ACID transactions across multiple items within a Region Globally-consistent ACID transactions, including across regions
Scaling model Automatic partitioning; serverless on-demand or provisioned capacity Horizontal scaling via provisioned compute (nodes / processing units) with autoscaling; storage scales separately
Multi-region Global Tables — multi-Region, active-active replication; eventually consistent between Regions by default, with an optional multi-Region strong consistency (MRSC) mode for three-Region setups Multi-region configurations that preserve strong consistency across regions
Pricing / ops model Pay-per-request (on-demand) or provisioned capacity plus storage; serverless, AWS-only Provisioned compute capacity plus storage (and network); node/processing-unit-based, GCP-only
Best-fit workloads High-scale operational apps with known key access needing consistent low latency Global relational systems needing strong consistency, SQL, and joins at scale

When DynamoDB is the better choice

  • You are on AWS and want serverless NoSQL. On-demand capacity has nothing to provision, scales to traffic, and scales to zero when idle.
  • Your access patterns are known and key-based. DynamoDB's partition/sort-key model gives consistent single-digit-millisecond latency when keys are designed around your reads.
  • You do not need relational joins or global strong consistency. If a denormalized, key-oriented model fits, you avoid paying for guarantees you will not use.
  • You want native AWS integration with IAM, Lambda, and Streams.

When Spanner is the better choice

  • You need relational SQL at global scale. Spanner keeps joins, schemas, and full SQL while scaling horizontally — you do not give up relational semantics to scale out.
  • You need strong consistency across regions. TrueTime-backed external consistency makes multi-region SQL transactions behave as if serialized by default. DynamoDB's Global Tables are eventually consistent between Regions by default, and its opt-in multi-Region strong consistency mode covers strongly consistent reads and writes in a three-Region setup — not Spanner-style globally serialized SQL transactions.
  • Your workload is relational and query-flexible. Ad-hoc joins and evolving queries across normalized tables fit Spanner's model.
  • You are on GCP and want its managed global database with high-availability SLAs.

What a Spanner read-write transaction becomes in DynamoDB

A funds transfer in Spanner is a read-write transaction, and the load-bearing
word is interactive: you read, your code decides, you write, and Spanner holds
the locks across all of it.

-- inside a read-write transaction
SELECT Balance FROM Accounts WHERE AccountId = 'acct-8231';
-- application logic runs here, still inside the transaction
UPDATE Accounts SET Balance = Balance - 250 WHERE AccountId = 'acct-8231';
UPDATE Accounts SET Balance = Balance + 250 WHERE AccountId = 'acct-1104';
INSERT INTO Transfers (TransferId, Source, Target, Amount)
  VALUES ('t-99', 'acct-8231', 'acct-1104', 250);
COMMIT;
Enter fullscreen mode Exit fullscreen mode

If a conflict forces an abort, the Spanner client libraries retry the whole
function for you, raising the transaction's lock priority on each attempt. And
because external consistency is TrueTime-backed, the committed order matches real
time even across regions.

TransactWriteItems is atomic and it is not interactive. Every action is fixed
before the call goes out: up to 100 of them, 4 MB aggregate, all within one
account and Region, no two targeting the same item. Reads belong to a separate
operation, TransactGetItems, which cannot be mixed in.

So the read moves outside the transaction and the guard moves into the writes.
You read the balance and its version, decide in application code, then submit a
transaction that refuses to apply if either fact changed underneath you:

{
  "TransactItems": [
    {
      "Update": {
        "TableName": "accounts",
        "Key": {"PK": {"S": "ACCT#8231"}},
        "UpdateExpression": "SET #b = #b - :amt, #v = #v + :one",
        "ConditionExpression": "#v = :seen AND #b >= :amt",
        "ExpressionAttributeNames": {"#b": "balance", "#v": "version"},
        "ExpressionAttributeValues": {
          ":amt": {"N": "250"},
          ":one": {"N": "1"},
          ":seen": {"N": "7"}
        }
      }
    },
    {
      "Update": {
        "TableName": "accounts",
        "Key": {"PK": {"S": "ACCT#1104"}},
        "UpdateExpression": "SET #b = #b + :amt",
        "ConditionExpression": "attribute_exists(PK)",
        "ExpressionAttributeNames": {"#b": "balance"},
        "ExpressionAttributeValues": {":amt": {"N": "250"}}
      }
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

The version attribute is yours. DynamoDB has no row version, so you increment
one on every write and read it back on every read, in every code path that
touches the item. Miss one path and the guard quietly stops guarding.

The retry loop is yours too. A stale read comes back as
TransactionCanceledException carrying ConditionalCheckFailed in its
CancellationReasons array, ordered to match the actions you sent, and you
re-read, recompute, and resubmit. That is the loop Spanner's client library ran
for you.

The ceilings bite hardest on batch work. A Spanner UPDATE ... WHERE touching
5,000 rows is one statement and one commit; the same change in DynamoDB is at
least 50 TransactWriteItems calls, each atomic on its own and none atomic
together. And since a table in another Region cancels the transaction outright,
Global Tables gives you replication, not a cross-Region transaction.

DynoTable's SQL Workbench covers the read half of that pattern, including the
JOIN and GROUP BY PartiQL lacks, by compiling to Query and Scan against
your real keys. It adds no transaction semantics, because a client cannot.

Working with DynamoDB

If DynamoDB fits, DynoTable is a native desktop client for it on macOS, Windows, and Linux. It reads your standard AWS credential chain, so your data stays in DynamoDB with nothing to migrate. For teams used to Spanner's SQL, its SQL Workbench expresses relational-shaped queries — joins, GROUP BY, aggregates — within DynamoDB's access-pattern rules by compiling them to DynamoDB's Query/Scan, and its AI agent runs on your own AWS Bedrock credentials.

The SQL for DynamoDB and PartiQL vs SQL guides explain what carries across from SQL, and the free DynamoDB Expression Builder generates key conditions, filters, and update expressions in SDK, CLI, and PartiQL form. DynoTable is a closed-source commercial app; this page describes what it does, not how it is built.

FAQ

Is Spanner a NoSQL database like DynamoDB?

No. Spanner is a relational database with full SQL, joins, and schemas that happens to scale horizontally like a distributed system. DynamoDB is a NoSQL key-value and document store with no JOIN operator. Spanner's distinguishing claim is keeping relational, strongly-consistent semantics at global scale.

Does DynamoDB offer global strong consistency like Spanner?

Not in the same way. DynamoDB Global Tables are eventually consistent between Regions by default, and an optional multi-Region strong consistency (MRSC) mode adds strongly consistent reads and writes across a three-Region configuration. Spanner goes further: TrueTime-backed external (strong) consistency applies to full SQL transactions across regions by default.

Can DynamoDB replace Spanner?

Only when you do not need relational SQL or global strong consistency. If your access is key-based and a denormalized model fits, DynamoDB is simpler and serverless. If you rely on joins, ad-hoc SQL, or strongly-consistent cross-region transactions, those are Spanner's purpose — replacing it means remodeling your data around DynamoDB's keys and giving up those guarantees.

Related

References

Last verified 2026-07-13 against the official AWS DynamoDB Developer Guide and Google Cloud Spanner documentation. Google Cloud Spanner is a trademark of Google LLC; referenced here for identification only.

Top comments (0)