Vector Database Sizing Calculator

Size a vector collection: raw vectors, the HNSW or IVF index on top, the payload beside it, replicas, and the build peak. The graph does not shrink when the vectors do, which is what makes quantisation save less than its headline ratio.

Collection
Index
Deployment

vector-sizing.txt

updates as you type

    Wanted a different tool?

    Examples

    Worked setups you can load into the form above. Each one is a decision the generator makes differently, and the reason it makes it.

    A million vectors at float32

    The baseline. 1,536 dimensions at four bytes is 5.72 GiB of vectors against 138 MiB of HNSW links, so the graph is 2% and beneath notice.

    vectors
    1000000
    model
    text-embedding-3-small
    payload-bytes
    0
    index
    hnsw
    m
    16
    precision
    float32
    replicas
    2
    deleted-percent
    0

    The same collection at binary

    The vectors drop 32x to 183 MiB and the graph does not move, so it is now the larger share of the two. The full-precision copies are still there for rescoring, which is why the total barely falls.

    vectors
    1000000
    model
    text-embedding-3-small
    payload-bytes
    0
    index
    hnsw
    m
    16
    precision
    binary
    replicas
    2
    deleted-percent
    0

    Chunk text stored beside the vectors

    Two kilobytes of payload per point against a 384 dimension vector, which is the ordinary shape of a RAG collection and the line every sizing guide leaves out.

    vectors
    2000000
    model
    all-minilm-l6
    payload-bytes
    2048
    index
    hnsw
    m
    16
    precision
    float32
    replicas
    2

    A node sized to the steady state

    It fits at rest and the index build needs half as much again, so the first full rebuild is killed partway through. That rebuild is what you do after losing a replica.

    vectors
    1000000
    model
    custom
    dimensions
    768
    payload-bytes
    0
    index
    hnsw
    m
    16
    precision
    float32
    replicas
    1
    ram-gib
    4

    Common mistakes

    These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

    1. Budgeting quantisation off the vector bytes

      The HNSW graph is about 145 bytes per vector at M=16 and does not change when the vectors do. Next to a float32 vector that is 2%; next to a binary code it is more than the vector, so a 32x saving on the vectors is nothing like 32x on the index.

      Instead:Size the graph separately. If it dominates, lower M, which is the parameter that trades links against recall, and measure recall at your own top-k before committing.

    2. Treating binary quantisation as a replacement for the vectors

      Recall at one bit per component needs a rescoring pass, and rescoring reads the original float32 vectors. They are still stored. The saving is on the hot path rather than on total storage.

      Instead:Budget both copies, and compare against int8, which is usually within a point or two of float32 recall with no rescoring pass at all.

    3. Leaving the payload out of the estimate

      In a retrieval collection the chunk text beside each vector is routinely larger than the vector. Almost every sizing guide counts vectors and index and stops there.

      Instead:Store the chunk id rather than the chunk, and fetch the text by primary key after the search returns. That is one lookup for the largest line on the page.

    4. Sizing the node to the steady state

      An index build holds the graph under construction alongside the data it is reading, at roughly half as much again as the resident size. A node sized to the steady state is one where the first full rebuild is killed partway through, which is exactly when you are rebuilding after losing a replica.

      Instead:Size to the build, or build on a larger node and load the result.

    5. Assuming deleted vectors free their memory

      An HNSW delete marks the node and leaves it in the graph, because removing it would mean repairing every link that pointed at it. It stays resident and is still traversed during search until a segment rebuild.

      Instead:Schedule the compaction or optimizer pass your database offers, and check its threshold rather than trusting the default on a collection that churns.

    Where the memory actually goes

    The vectors are the easy part of the arithmetic and usually not the interesting part of the answer. Four things decide the number, and three of them are missing from most estimates.

    Quantisation does not shrink the graph

    An HNSW node carries its neighbour list, and at M=16 that is about 145 bytes per vector whatever precision the vector is stored at. Next to a 6,144 byte float32 vector it is 2% and beneath notice. Next to a 192 byte binary code it is more than the vector. So the headline ratio on the vector bytes is not the ratio on the index, and the gap is largest exactly where the saving is being claimed.

    1536 dimensions, HNSW M=16
    
    float32   6,144 B vector + 145 B graph   graph is 2%
    int8      1,536 B vector + 145 B graph   graph is 9%
    binary      192 B vector + 145 B graph   graph is 43%

    Binary quantisation keeps the full vectors too

    One bit per component does not retrieve well enough to use on its own. It works as a first pass that returns an oversized candidate set, which is then rescored against the original float32 vectors. Those vectors are still stored. The saving is real and it is a saving on the hot path rather than on total storage, and budgeting it as a 32x reduction in everything is how a collection ends up not fitting.

    The payload is usually the larger half

    In a retrieval collection the chunk text sits beside the vector, and 1 to 4 kB per point against a 1.5 kB vector is ordinary. Almost every sizing guide counts vectors and index and stops. Storing the chunk id instead, and fetching the text by primary key from whatever already holds your documents, removes the largest line on the page for the cost of one lookup after the search returns.

    The build needs more memory than the steady state

    An index build holds the graph under construction alongside the data it is reading, and nothing has been compacted. About half as much again is what this costs in practice, so a node sized to the resident figure is a node where the first full rebuild is killed partway through. This matters most on the day you least want it to, because a rebuild is what you do after losing a replica.

    Deletes are soft, so a churning collection is larger than its live count

    Removing a vector from an HNSW graph would mean repairing every link that pointed at it, so implementations mark the node and leave it. It occupies memory and it is still traversed during search until a segment rebuild removes it. A collection with continuous updates needs that compaction scheduled rather than left to whatever the default threshold is.

    It has to be in RAM, and that is not a gradual limit

    Approximate nearest neighbour search over a graph is random access with no locality. While it fits, queries are fast. Once it does not, every query takes page faults across the whole structure and the latency does not degrade, it falls off a cliff. This is a different failure from a database that gets slower as it grows, and it is why the fit check here is against RAM rather than disk.

    What this cannot see

    It is a model of the data structures, not a measurement of your engine. Every database adds its own per-point overhead: an id map, a payload index, a tombstone bitmap, segment metadata, and pgvector in particular stores vectors in heap tuples with Postgres page overhead on top. Treat this as the floor and measure the real thing once a representative slice is loaded. It also says nothing about recall, which is the other half of every decision here: M, nlist and the precision all trade memory against it, and the only way to know your own number is to run exhaustive search on a held-out query set and compare.