What a process holds
Measured with
moss 1.19.0 and Python 3.12 on Linux (arm64, glibc), with the moss-minilm model
and chunks of 800 to 1,200 characters of English text with four metadata fields. Memory is RSS,
which is what a container’s memory limit counts. Your text, chunk size and metadata change the cost
per chunk, so check these figures against your own data.
- The embedding model loads once per process for each model, with the first load that needs it, and every client and session in the process shares it. Opening it briefly holds 2.2 to 2.5 times its steady size.
- A load briefly needs about twice the index’s size, for the download and parse buffers. A refresh needs about three times, because the old copy keeps serving queries until the new version replaces it.
- Memory grows linearly with the number of loaded chunks, and with the number of indexes. Each index keeps its own keyword vocabulary, so many small indexes cost 1.5 to 2 times more per chunk than one large index with the same chunks. Non-Latin text, such as Hindi, roughly doubled the cost per chunk in our test.
- Processes share nothing. Each process holds its own model and its own copy of every index it
loads, even when they use the same
cache_path. The disk cache saves downloads, not memory. - Text query time stays flat. With 50 or 500 indexes loaded, a text query on one of them took about 6 ms at the median and 9 to 10 ms at p95, embedding included.
Estimate a process
Plan each process for the larger of two numbers:- About 380 MiB plus the indexes loading at the same time, the peak while the embedding model loads. It happens once, with the first load in the process.
- 186 MiB + 7 KB per loaded chunk + twice the largest index, the steady memory plus room to load
or refresh the largest index. The steady term already counts the old copy that a refresh keeps
serving, so twice the index on top covers the refresh’s total of about three times. An index of
nchunks takes aboutn × 7 KB.
Example: one process holding 500 knowledge bases
Example: one process holding 500 knowledge bases
500 knowledge bases of 200 chunks each, all loaded in one process:
- Loaded chunks: 500 × 200 = 100,000, so 100,000 × 7 KB ≈ 668 MiB.
- Largest index: 200 × 7 KB ≈ 1.3 MiB, twice that ≈ 3 MiB.
- Steady estimate: 186 + 668 + 3 ≈ 857 MiB, above the 380 MiB model peak.
Example: 20 concurrent voice calls on one host
Example: 20 concurrent voice calls on one host
Each call loads 3 knowledge bases of 2,000 chunks each.With one call per process, as in the default LiveKit setup:
- Per process: 6,000 chunks × 7 KB ≈ 40 MiB, and the largest index ≈ 13 MiB, so 186 + 40 + 2 × 13 ≈ 253 MiB steady.
- The model load sets the peak instead: about 380 MiB, plus the 40 MiB of indexes that load alongside it, so about 420 MiB.
- 20 calls ≈ 20 × 420 MiB ≈ 8.2 GiB for Moss alone.
- 60 indexes × 2,000 chunks = 120,000 chunks × 7 KB ≈ 801 MiB.
- 186 + 801 + 2 × 13 ≈ 1,014 MiB steady for all 20 calls, and less when calls share knowledge bases.
- If all 60 loads overlap the first model load, the peak is about 380 + 801 ≈ 1,181 MiB, so plan about 1.2 GiB for the process.
Many indexes
One index per knowledge base
Give each knowledge base, tenant or agent its own index, and load the ones a request needs. Query time does not depend on how many indexes are loaded, and multi-index search queries several of them in one call. One large index filtered per tenant is a poor fit:- A filtered query still scores every vector in the index, then drops the ones the filter excludes, so every tenant pays for the size of all tenants.
- Every edit rewrites the whole index. A new version is rebuilt in full, and every process that refreshes it downloads the whole index again.
- Every process holds every tenant’s data, whether or not it serves them.
Load on first use, concurrently
Load an index when a request first needs it, with acache_path on persistent storage so a reload
reads it from disk when the cloud copy is unchanged. To load several, start the loads together so
their round trips to the cloud overlap.
Unload idle indexes
A process that serves many knowledge bases over time keeps every index it loads until you unload it. Track when each index was last queried, and unload the ones that have gone idle withunload_index /
unloadIndex (or unload_indexes / unloadIndexes for several). The next request that needs one
loads it again, from the disk cache when the cloud copy is unchanged.
On Linux, glibc keeps freed memory inside the process, so RSS does not drop right after an unload.
Later loads in the same process reuse that memory. To return it to the operating system, call
malloc_trim after unloading:
Refresh sparingly
Withauto_refresh, each poll makes three requests per index. 500 indexes at the default interval of
600 seconds make 150 requests a minute; at 10 seconds they make 9,000. Keep the default interval or a
longer one when a process holds many indexes, and refresh the indexes you know have changed by loading
them again.
Each poll also counts as session time, so a long-running process with auto_refresh on is billed for
session minutes around the clock, even when it serves no calls.
Related
Storage & Persistence
Disk cache, offline loads and auto-refresh.
Releasing Resources
Close clients and sessions to free memory.