Chris Collins

Tools · infra

NoSQL capacity and cost

Size NoSQL deployments with the working shown: Cassandra and ScyllaDB nodes from rows, replication and compaction headroom, with node-replacement and repair times; Redis memory per key by encoding (listpack, intset, hashtable, skiplist) with fragmentation, replication buffers and an eviction policy; MongoDB working set, WiredTiger cache and oplog window; Azure Cosmos DB request units, partitions and cost; and a Zipf hot-partition checker.

Added

Works with

How to use it

Five calculators for the questions that come up when a NoSQL store goes into production: how many nodes, how much memory, how many request units, and whether one partition will take more than its share. Each one prints its working line by line — the formula, the numbers that went in and the result — and every assumption is an input you can change. Everything runs in the page.

These are planning estimates. They get the order of magnitude and the shape right (where the headroom goes, which threshold makes memory jump, what a skewed key does) but real numbers come from measuring your own data: the page says which command or metric to check for each.

How to use it

Pick a tab, start from an example (Load an example…) or type your own figures. Number fields accept 2e10, 20,000,000,000 or 20_000_000_000. Results update when a field changes.

Cassandra / Scylla. Describe the data — rows, rows per partition, the payload of a row, its cells and the partition key — and the cluster: replication factor, compaction strategy, the operational headroom you keep, disk per node and the density you are prepared to run. The page works out bytes on disk, the nodes needed by disk and by density (whichever is more, at least RF, rounded up to a multiple of racks), the disk of one node as a bar (data, compaction space, headroom, spare), how long a replacement node takes to stream its data, and whether a full repair cycle finishes inside gc_grace_seconds. The storage-format overheads are under Storage-format overheads.

Redis memory. Give the number of keys, the key length, the value type and its size. The page shows what one key costs, piece by piece — hash-table entry, bucket, key string, value in its encoding, TTL entry — then used_memory, a maxmemory with growth allowance, and the host RAM once fragmentation, the replication backlog, replica output buffers and the snapshot fork’s copy-on-write are added. For hashes, sets and sorted sets a chart shows bytes per element against elements per key, with the jump where the encoding changes. Choose the workload for an eviction policy.

MongoDB. Documents, their size and the hot fraction; indexes as name:key bytes:hot % (an index on an increasing value is hot only at its end). The page adds the hot documents and hot index pages into a working set, works out the RAM whose default WiredTiger cache holds it, and checks a RAM size you give. Below, writes per second and bytes per change give the oplog window for a given or default oplog, and the size needed for the window you want.

Cosmos DB RU. Point reads, writes and queries per second, item size, indexing policy and consistency give the RU/s to provision; items and partition-key values give storage, physical partitions and the size and traffic of the largest logical partition; a price per 100 RU/s-hour (editable) gives monthly cost, provisioned and as an autoscale range.

Hot partitions. Choose Zipf(s) over N keys, or paste key counts (one key count per line, from a sample of your access log). The keys are hashed onto the partitions and the bar chart shows the load on each, hottest first, against the per-partition limit (Cosmos DB, DynamoDB reads or writes, a measured Cassandra node, or your own). Salt the K hottest keys into S shards shows what write sharding does. The advice says whether more partitions can help — they cannot when one key alone is over the limit.

Formulas and assumptions

Cassandra and ScyllaDB.

  • Bytes per row = payload + row overhead + cells × cell overhead; per partition = key + partition overhead. Defaults: 3 bytes per cell, 6 per row, 32 per partition, for the SSTable format of Cassandra 3.0 and later, which stores timestamps, TTLs and lengths as variable-length deltas (Storage engine). The pre-3.0 rule of thumb was 15 bytes per column and 23 per row; enter that for a conservative figure.

  • On disk = raw × compressed fraction × RF.

  • Free space by compaction strategy (Compaction): size-tiered 50% (a compaction may rewrite the largest tier, in the worst case all the data), leveled 10% (small fixed-size SSTables, ~90% of data in the last level), time-window 1 ÷ windows retained (one window is compacted at a time), at least 5%. Plus the operational headroom you set (snapshots, streaming, growth).

  • Nodes = max(⌈total ÷ usable disk per node⌉, ⌈total ÷ target density⌉, RF), rounded up to a multiple of the racks.

  • Replacing a node streams its data at the aggregate rate you give. Each sender is throttled by stream_throughput_outbound (24 MiB/s by default in Cassandra 4) and the total by the network and the new node’s disk; with vnodes many nodes send at once.

  • A full repair cycle (nodetool repair -pr on every node) reads each node’s primary ranges on its RF replicas at once to build Merkle trees: nodes × (data per node ÷ RF) ÷ validation rate ÷ parallel repairs, plus the out-of-sync fraction streamed. It should finish inside gc_grace_seconds (864,000 s, 10 days, by default), or deleted data can reappear (Repair).

  • ScyllaDB uses the same file format family and compaction strategies; it streams and compacts per CPU shard, so the rates and density to enter are higher (ScyllaDB system requirements).

Redis (7.x, 64-bit, the bundled jemalloc built with an 8-byte quantum).

  • Per key: a 24-byte dictEntry; one 8-byte bucket pointer per slot, slots being the next power of two at or above the key count; the key as an SDS string (3-byte header below 256 bytes, 5 below 64 KB, plus the bytes and a NUL); the value; and for keys with a TTL a second entry in the expires dict. Every allocation is rounded up to a jemalloc size class (8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160…). This gives 56 bytes for an empty key with an empty value, as MEMORY USAGE reports.

  • Strings: integers are kept in the object (0–9999 are shared); up to 44 bytes, embstr (object and string in one allocation); longer, raw (a 16-byte object plus the SDS).

  • Collections (Memory optimisation; defaults from redis.conf): a hash stays a listpack while it has at most 128 fields (hash-max-listpack-entries) and every field and value is at most 64 bytes (hash-max-listpack-value); a sorted set the same (zset-max-listpack-entries 128, zset-max-listpack-value 64); a set of integers is an intset up to 512 members (set-max-intset-entries), otherwise a listpack up to 128 members of up to 64 bytes (set-max-listpack-entries, Redis 7.2); lists are quicklists of listpacks of up to 8 KB (list-max-listpack-size -2). A listpack entry is a 1–5-byte header, the bytes and a 1–5-byte back-length; a listpack has a 6-byte header and an end byte. Past the limits: a hashtable (a dict entry, bucket and SDS strings per element) or, for a sorted set, a skiplist plus a dict (a node of 24 + 16 bytes per level, levels drawn with p = ¼). Sorted-set scores in a listpack are taken as 5-byte integers.

  • used_memory adds ~1 MB for an empty instance. RSS ≈ used × mem_fragmentation_ratio (INFO memory).

  • Host RAM ≥ maxmemory × fragmentation + repl-backlog-size + replicas × 256 MB (the hard limit of client-output-buffer-limit replica 256mb 64mb 60) + the pages rewritten while a forked snapshot or AOF rewrite runs (copy-on-write).

  • Eviction policies (Key eviction): allkeys-lru for a pure cache, allkeys-lfu for a skewed long-lived hot set, volatile-lru when cache keys with TTLs share the instance with keys that must stay, volatile-ttl when TTLs say what is worth keeping, noeviction for a primary store, queue or locks.

MongoDB (WiredTiger).

  • Working set = documents × size × hot fraction + Σ indexes × hot fraction, with an index taken as documents × (key bytes + 12 bytes for the RecordId and key format). The cache holds pages uncompressed, so the uncompressed (BSON) size is used. Prefix compression often makes real indexes smaller: check db.collection.stats().indexSizes.

  • WiredTiger’s default cache is the larger of 50% of (RAM − 1 GB) and 256 MB (WiredTiger storage engine), so RAM ≥ 2 × working set + 1 GB, plus about 1 MB per connection. The rest of RAM serves the filesystem cache of compressed data.

  • The default oplog is 5% of free disk, at least 990 MB and at most 50 GB, on 64-bit Linux and Windows (Replica set oplog). Each write adds its change plus ~100 bytes of entry fields; window = oplog size ÷ bytes per hour.

Azure Cosmos DB (NoSQL API) — estimates.

  • Microsoft’s published examples (Request Units in Azure Cosmos DB): a point read costs 1 RU at 1 KB, 1.3 RU at 4 KB and 10 RU at 64 KB; a write about 5, 7 and 48 RU, with the default policy of indexing every property. Between those sizes the page interpolates on log(size); above 64 KB in proportion.

  • Strong and bounded-staleness reads cost twice as much (Consistency levels).

  • Writes scale with the number of indexed properties (Indexing policies); the page’s factors for selective (× 0.75) and no indexing (× 0.6) are rough assumptions — measure.

  • Query RU depend on the query: measure the x-ms-request-charge header and enter it.

  • A physical partition serves up to 10,000 RU/s and 50 GB; a logical partition (one partition-key value) holds at most 20 GB (Partitioning). Index storage is taken as 20% of item bytes.

  • Cost: RU/s ÷ 100 × the price per 100 RU/s-hour × 730 hours × regions. The default price ($0.008) is a placeholder for standard provisioned throughput: use your region’s price. Autoscale bills the highest RU/s of each hour at 1.5× the rate, between 10% and 100% of the maximum. Check the result with the Cosmos DB capacity calculator.

Hot partitions.

  • Zipf(s): the key of rank i gets 1/is ÷ H(N, s), where H(N, s) = Σ 1/is; so the top key’s share is 1 ÷ H(N, s). Keys are hashed with FNV-1a and the murmur3 finaliser.

  • Per-partition limits: Cosmos DB 10,000 RU/s per physical partition; DynamoDB 3,000 read and 1,000 write capacity units per partition (partition key design); Cassandra has no fixed limit — a partition is served by its RF replicas, so enter what one node sustains for you (20,000 ops/s is only a placeholder).

  • The shards a hot key needs = ⌈key load ÷ limit⌉.

Limits

  • No growth over time: enter the data size at the horizon you are planning for.

  • Cassandra: no materialised views or secondary indexes (add their tables as rows), no tombstone accumulation, a single data centre; ScyllaDB’s incremental compaction strategy is not modelled.

  • Redis: no modules, streams or Redis Cluster slot overhead; the value is the same for every key; listpack integers other than sorted-set scores are treated as strings; Redis 7.0 still calls listpacks ziplists in some configuration names, and Valkey 8’s embedded keys are smaller than this.

  • MongoDB: no sharded-cluster config servers or mongos; the hot fraction is your judgement — check the cache’s pages read into cache and eviction counters.

  • Cosmos DB: NoSQL API only; no serverless, reserved capacity or multi-region-write pricing differences; RU figures are published examples, not a model of the engine.

  • The hot-partition checker assumes partitions are fixed and keys hashed; range-partitioned stores (Bigtable, HBase) split hot ranges, but a single hot row still cannot be split.

Linking to an example

Links can be written by hand. Add the tab after #: cassandra (the default), redis, mongodb, cosmos or hot; an example opens its own tab. A link’s settings replace this page’s saved settings (a button restores yours); settings not in the link take their defaults, and parameters given with an example override it.

Parameter Meaning Example

example

A preset (list below)

example=redis-sessions

cDb

cassandra or scylla

cDb=scylla

cRows, cRpp

Rows, rows per partition

cRows=2e10&cRpp=8640

cRowB, cCells, cKeyB

Row payload bytes, cells per row, partition-key bytes

cRowB=40&cCells=2

cCellOv, cRowOv, cPartOv

Overhead bytes per cell, row, partition

cCellOv=15&cRowOv=23

cComp

Compressed size, % of raw

cComp=40

cRf

Replication factor

cRf=3

cStrat, cWin

Compaction stcs, lcs or twcs; TWCS windows retained

cStrat=twcs&cWin=30

cOps

Operational headroom, %

cOps=10

cDisk, cTarget

Disk and target data per node, GiB

cDisk=4096&cTarget=2048

cRacks

Racks

cRacks=3

cStream, cVal

Streaming and repair-validation rates, MB/s

cStream=150

cMis, cPar

% out of sync at repair; repairs in parallel

cPar=2

rKeys, rKeyLen

Redis keys, key length

rKeys=1e7&rKeyLen=24

rType

string, hash, set, zset or list

rType=hash

rN, rLen, rInts

Elements per key, element (or value) length, integers (true/false)

rN=129&rLen=24

rTtl

Keys with a TTL, %

rTtl=100

rFrag, rRep, rBacklog

Fragmentation ratio, replicas, backlog MiB

rFrag=1.3

rCow, rGrowth

Copy-on-write %, growth %

rCow=30

rWork

cache, skewed, mixed, ttl or store

rWork=store

mDocs, mDocB, mHot

Documents, bytes each, hot %

mDocs=5e7&mDocB=2048

mIdx

Indexes, name:key bytes:hot % comma-separated

mIdx=_id:12:100,sku:16:100

mConns, mRam

Connections, RAM per node GiB

mRam=64

mWrites, mEntry

Writes per second, bytes per change

mWrites=800

mOplog, mFree, mTarget

Oplog GB (0 = default), free disk GB, target window h

mOplog=50

qReads, qWrites, qQueries, qQRU

Cosmos point reads, writes, queries per second; RU per query

qReads=2000&qWrites=300

qKB

Item size, KB

qKB=4

qIdx

all, selective or none

qIdx=none

qCons

strong, bounded, session, prefix or eventual

qCons=strong

qHead, qRegions

Headroom %, regions

qRegions=2

qItems, qKeys, qHot

Items, partition-key values, largest key’s share %

qKeys=200&qHot=40

qPrice, qStore

$ per 100 RU/s-hour, $ per GB-month

qPrice=0.008

hMode

zipf or paste

hMode=zipf

hKeys, hS

Keys and Zipf exponent

hKeys=1e6&hS=1.2

hText

Pasted key count lines

hText=a%2010%0Ab%205

hParts, hOps

Partitions, total throughput

hParts=32&hOps=120000

hLimit, hLimitV

cosmos, dynamo-read, dynamo-write, cassandra or custom; custom limit

hLimit=custom&hLimitV=5000

hSaltK, hSaltS

Salt the K hottest keys into S shards

hSaltK=10&hSaltS=8

example= Shows

cassandra-timeseries

Sensor readings: 200 billion small rows, time-window compaction, RF 3 over three racks (open)

cassandra-stcs

The same data under size-tiered compaction: half of every disk kept free (open)

scylla-dense

ScyllaDB: denser nodes and faster streaming (open)

redis-sessions

A session cache: 20 million hashes of 12 small fields, each with a TTL (open)

redis-hash-threshold

The same hashes with 129 fields: past hash-max-listpack-entries, memory jumps (open)

redis-leaderboard

Leaderboards: 50,000 sorted sets of 1,000 members (open)

mongo-catalogue

A product catalogue: 50 million 2 KB documents, a fifth of them hot (open)

mongo-oplog-tight

Heavy writes into a default oplog: a window of hours, not days (open)

cosmos-shop

E-commerce orders: 2,000 point reads and 300 writes a second on 2 KB items (open)

cosmos-hot-tenant

Partitioned by tenant, one tenant with 40% of the data: past 20 GB per logical partition (open)

hot-celebrity

A celebrity key: Zipf s = 1.2 over a million users on 32 partitions (open)

hot-salted

The same with the ten hottest keys split into 8 write shards each (open)

hot-uniform

A near-uniform key (s = 0.2): every partition near the mean (open)

Runs entirely in your browser; nothing you type leaves this page.