Build1 publisher3 min readPublished
Six pragmas and a context manager: the vector store that fits in 2GB of RAM
A dev.to writeup argues tuned SQLite replaces pgvector or Pinecone on a $5 VPS. The connection discipline is the real lesson; the headline count of millions of vectors is not evidenced.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- A dev.to post describes running a vector search engine and document vectorizing pipeline (the "Harvest" pipeline) for NGP 4.5, the NetGlyph Knowledge Protocol, on a cheap virtual machine with 2GB of RAM and no swap space, using SQLite.
- The post asserts that heavy solutions such as pgvector, Pinecone or Milvus would crash from an out-of-memory error on a 2GB RAM machine before they finished initialising.
- An AI agent called "Hermes", responsible for auto-importing data, had a save_vector_to_db function that called sqlite3.connect(self.db_path) directly inside an argument list to run a strftime query for the current formatted time, and left that connection open.
- Every hanging connection held a file descriptor open, and after processing a stream of 2,258 documents the operating system ran out of file descriptors and memory.
- The post states that the kernel's OOM-Killer would terminate the process before it could process the first hundred documents.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
Someone building NGP 4.5, which the writeup calls the NetGlyph Knowledge Protocol, has published the SQLite configuration used to run vector search and a document vectorizing pipeline on a cheap 2GB VPS with no swap space [1]. The reason to read it is not the pragma list but the ordering of the failures: the first thing that fell over was not the database's capacity, it was the application leaking file descriptors [4].
The framing is familiar. The post argues that pgvector, Pinecone or Milvus would be killed by the out-of-memory reaper on a 2GB machine before initialisation even finished, so the project chose embedded SQLite instead [2]. That is an assertion, not a benchmark, and nothing in the text measures those alternatives.
The diagnosis is more useful than the framing. An auto-import agent called Hermes had a save path that opened a brand new connection with sqlite3.connect inside an argument list, purely so it could ask SQL for a formatted timestamp via strftime, and then left that connection open [3]. Each one held a file descriptor. After a stream of 2,258 documents, the operating system ran out of descriptors and memory [4]. The post also says the kernel OOM killer would terminate the process before the first hundred documents were processed [5], which does not reconcile with the 2,258 figure [6]; read both as illustration rather than measurement.
The fix was to stop asking the database for things the runtime already knows: time.time() or a native datetime string instead of a SQL round trip, and idiomatic `with sqlite3.connect(...) as conn:` blocks in place of hand-managed handles [7]. The post states that the context manager guarantees a commit or rollback and closes the descriptor even if the transaction fails [8]. That is the load-bearing assertion in the whole piece, and it is the one to verify against your own Python version before you build a pipeline on top of it.
Then the tuning. WAL journalling, synchronous=NORMAL, temp_store=MEMORY, and busy_timeout=5000 to avoid deadlocks under concurrent writers [9]. Two settings do the memory work: mmap_size set to 268435456 bytes, or 256MB, in place of what the author describes as a 32GB default [10], and cache_size set to -131072, the negative form that expresses the page cache strictly in KiB, capping it at 128MB [11]. Those two caps together account for about 384MB, roughly 19 percent of the 2GB box [12], and the mmap change alone is a factor of 128 reduction from the default the author cites [13].
Where the claims outrun the evidence: the post reports hundreds of transactions per second, no descriptor leaks, and flat memory within a negligible margin [14], with no numbers behind any of the three. The headline promises millions of vectors on a $5 VPS [15], but the text carries no vector count, no index structure, and no query latency [16]. The similarity engine is named LossySpinBosonEngine and the excerpt never says how it searches [17], which is the part that decides whether SQLite is doing a brute-force scan or something with an index.
Worth watching if you are sizing this yourself: descriptor count and resident set size under sustained ingest, not at startup; and whether the 384MB of caps still holds once your working set exceeds them and SQLite starts evicting pages instead of mapping them.