Scale out¶
One server on one machine keeps everything in its data folder: the collections, the sign-in file, the collection records, the evaluation runs. That is the default, and it is right until one machine is not enough. This page is what has to move off the machine before a second server can run beside the first, what each process keeps for itself, and how ingestion and extraction run as workers of their own.
What must be shared¶
Every server that answers for the same collections has to read the same state. Each row is a setting, and every server gets the same value:
| State | Setting | Shared stores |
|---|---|---|
| The collections: vectors, text and metadata | VECTRIXDB_STORAGE_BACKEND |
azure_search or cosmosdb; see Choose a storage backend |
| The copy of every chunk the collection pages read | VECTRIXDB_CHUNK_STORE |
cosmos://<account>.documents.azure.com/<database>/<container>, or a store of your own passed as chunk_store= |
| Each collection's record: who may search it, who may see it, masking; and the feeds and pages it keeps up with | VECTRIXDB_COLLECTION_STORE |
postgresql://, cosmos:// or dynamodb:// |
| Sign-in: people, sessions, links, codes, keys, failed attempts and each key's requests a minute | VECTRIXDB_SIGNIN_STORE |
postgresql://, cosmos:// or dynamodb:// |
| The sign-in secret | VECTRIXDB_SIGNIN_SECRET |
The same value on every server: authenticator secrets are sealed with a key derived from it |
| The access log | VECTRIXDB_ACCESS_LOG |
stdout for the platform to collect, or a Blob address, append blobs every server writes |
| Search decisions under a policy | VECTRIXDB_AUDIT_STORE |
A Blob address, s3:// or postgresql:// |
| Evaluation runs | VECTRIXDB_EVALUATIONS |
s3://bucket/prefix or a Blob address |
| Knowledge graphs | VECTRIXDB_GRAPH_STORE |
s3://bucket/prefix or a Blob address |
| Kept Markdown | VECTRIXDB_KEEP_SOURCE |
s3://bucket/prefix or a Blob address |
Each key for a store, VECTRIXDB_CHUNK_STORE_KEY,
VECTRIXDB_COLLECTION_STORE_KEY and VECTRIXDB_SIGNIN_STORE_KEY, is a setting of
its own and never part of the address. Left empty, the chunk store and a cloud
collection store are reached as the managed identity. The settings reference has
every one.
What sharing buys:
- A rule set on one server is the rule on all of them. The collection record is read by every server. Reads are cached for a short while, so a search is not a round trip to the store; when the store cannot be read, the copy from a moment ago stands in, and with none the search does not run.
- A sign-in survives a redeploy and works on any server. Anything that must happen once, a code spent or a link used, happens once however many servers are asked at the same moment.
- A key's requests a minute are one count. The count lives in the sign-in store, so three servers behind a load balancer do not give a caller three allowances.
- A source is refreshed by one server at a time. A refresh holds a lease on each source while it reads it, here or on another server, and one that died lets go when the lease runs out.
- The collection pages show what every server wrote. Without a chunk store, each page counts only what the process drawing it wrote, which on a fresh instance is nothing.
vectrixdb check reads the storage backend, the chunk store, the collection
store, the sign-in settings, the access log, the audit store and the evaluation
runs before a start. vectrixdb doctor asks the storage backend's service, the
sign-in store, the audit store, the evaluation runs and the graph store whether
they answer. See Check and doctor.
What each process keeps for itself¶
| Per process | Why it is fine |
|---|---|
| The models, and the line model calls wait in | Each process loads its own and sizes its own line; see Size the server for load |
| The index beside the process | A collection on a store is written to the service and to a local index under the same ids. Each process's local index holds only its own writes, so a search that must see every server's writes asks the store; see Two homes |
| The cache | VECTRIXDB_CACHE_BACKEND=memory, the default, is per process. redis shares one |
The extraction service's jobs, with VECTRIXDB_EXTRACT_JOBS=memory |
Fine for one instance. A restart forgets every job, and a second instance cannot see the first one's |
Replicas and uvicorn workers¶
vectrixdb serve runs one process. There are two ways to run more:
| Replicas | uvicorn --workers |
|
|---|---|---|
| What runs | One server a container or pod, behind a load balancer | Several processes in one container, started by uvicorn |
| Models | One set a replica | One set a worker, so memory is a set times the workers |
| Probes | Each replica has its own /health and /ready |
One container's probe answers for whichever worker takes it |
| On the default storage | Not possible: each replica would have collections of its own | Several processes writing the same collection files, which must not happen: see Threads and processes |
| On shared stores | Supported: the table above | The workers still share one data folder, so anything kept there as files has several writers |
Prefer replicas. The Kubernetes setup in docker/kubernetes runs one replica on
a ReadWriteOnce volume with the Recreate strategy, so two processes never
write the same files. With every row of the shared table set, raise the
replicas and make the strategy RollingUpdate; see
Run it in containers.
An ingestion worker beside the server¶
Ingestion does not have to happen in the server. IngestWorker turns object
events into add_document and delete_document calls, in a function or a
container of its own, and the server lists and searches what it wrote: a server
opens the collections a shared backend holds as they are asked for, including
ones another process made. Ingest when a file lands has
the worker on AWS, on Azure and on a laptop.
What makes it safe to run behind a queue:
| Behaviour | What it gives you |
|---|---|
| Idempotent on the document's bytes | An event delivered twice finds the same bytes already in and does nothing, so retries are safe |
handle_all acts on the last event for each object |
A burst with several events for one file reads it once; the earlier events are answered superseded |
| One index save a batch | A thousand files do not rewrite the index a thousand times |
chunks_per_second= |
A pace on what is written: a burst of files then waits between documents, instead of flooding the store or the model |
| Old chunks are replaced only once new text is there | A failed read leaves the document as it was |
Several workers writing one collection is the same question as several servers: on a store, each keeps its own local index of its own writes; on local files, keep one writer.
Dead letters¶
A file that cannot be read raises ExtractionError by default. On a queue, an
exception is what asks for a retry, and after the queue's last try it moves the
message aside, where a person can find it:
| Queue | Where a file that never reads ends up |
|---|---|
| Amazon SQS | The dead-letter queue you give it |
| Azure Storage Queue under a function app | <queue>-poison, after maxDequeueCount tries, 5 by default |
Put an alert on that queue's length: it is the list of files that would not read. Give the queue a visibility timeout about six times the function's, so a long OCR is not handed to a second worker while the first is still reading.
IngestWorker(..., on_error="record") returns an outcome with
action="failed" and the reason in error instead of raising, for a batch
that should not stop at one bad file. Nothing reaches the dead letter then, so
log or count the failed outcomes yourself.
The extraction service and its queue¶
The extraction service reads scans, recordings, videos and YouTube addresses for
the server; see Run an extraction service. Most of its
calls answer in the request. A long one, youtube_save, answers 202 with a
job at once and does the work later:
VECTRIXDB_EXTRACT_JOBS |
Where the job is kept | Where the work runs |
|---|---|---|
memory, the default |
In the process | Two threads in the same process. A restart forgets every job |
azure |
A record a job in a Blob container, VECTRIXDB_EXTRACT_CONTAINER, extraction by default |
A message on a storage queue, VECTRIXDB_EXTRACT_QUEUE, extract-jobs by default, read by the function app's queue trigger with run_extraction_job |
With azure, any instance answers GET /jobs/{job}, because the record is in
the container and not in anybody's memory, so the service scales out like the
server. Both are made when they are not there. It needs the jobs-azure extra.
A job that fails is recorded as failed with the reason and not raised, so the
queue does not try it again: a video YouTube refused once it refuses again, and
each try is paid for. A message delivered twice runs the job once, since a job
already done or failed is left alone. What reaches the poison queue is a
message the trigger itself could not run.