VPS Recommendations
How to Choose a VPS for RAG Vector Databases (2026): Qdrant, pgvector, and Milvus Configuration and Cost Comparison
Qdrant, pgvector, or Milvus—which should you choose? This article breaks down VPS configurations by vector dimensions, indexes, memory, and disk, gives the applicable boundaries for 2-core 4GB, 4-core 8GB, and 8-core 16GB, and provides a Qdrant Docker deployment process with API Key, Caddy HTTPS, and snapshot backups.

If you already have PostgreSQL and vectors are only part of your business data, start with pgvector; if you want to split vector retrieval into a separate service, Qdrant is more convenient; only when a single machine truly cannot hold the data and the team is ready to maintain more components should you consider Milvus Distributed.
This matters more than "which one has the highest benchmark score." What a vector database ultimately consumes is not a software name, but memory, disk, index build time, and your operational effort.
| Option | Projects it suits better | Operational complexity | Suggested VPS starting point | Main cost |
|---|---|---|---|---|
| pgvector | Already using PostgreSQL, need transactions, JOINs, and vector retrieval | Low | 2-core 4GB for validation; production better starts at 4-core 8GB | Vector indexes compete with business tables for memory and I/O |
| Qdrant | RAG, semantic search, need an independent API and payload filtering | Medium | 2-core 4GB for small-scale validation; 4-core 8GB is better for formal use | One more service to maintain; must back up and authenticate separately |
| Milvus Standalone | Data and query pressure clearly growing, but not yet wanting a cluster | Medium-high | Official minimum 8GB; 16GB memory recommended | Components and resource usage higher than lightweight options |
| Milvus Distributed | Single-machine capacity or throughput has become a clear bottleneck | High | Not recommended to squeeze into a cheap VPS | Requires multiple nodes, object storage, and more mature monitoring and operations |
If you just need a vector store for Dify, AnythingLLM, or your own RAG application, I would choose between pgvector and Qdrant first. Milvus is fine, but "it might be very large later" is not sufficient reason to deploy a distributed system now.
The site already has a complete Dify private RAG deployment tutorial and an AnythingLLM private knowledge base tutorial. This article only addresses the layer beneath them: how vectors are actually stored, how to buy the machine, and how to run the service securely.
"How much memory do 1 million vectors need" has no universal answer independent of the model. You need to know at least:
- How many dimensions the vectors have;
- Whether using float32, half precision, or quantized data;
- Whether using exact search, HNSW, or IVFFlat;
- How much text, tags, and business payload each record also carries;
- Whether there are replicas, WAL, snapshots, and concurrent queries;
- How much working memory index building needs.
Taking float32 raw vectors as an example, start with a minimal estimate:
原始向量字节数 ≈ 向量数量 × 维度 × 4
The official pgvector README is more precise: vector occupies 4 × dimensions + 8 bytes per entry. Assuming 1536 dimensions:
10 万条:100000 × (4 × 1536 + 8) ≈ 587 MiB
100 万条:1000000 × (4 × 1536 + 8) ≈ 5.73 GiB
This is only the vectors themselves, not including PostgreSQL row overhead, HNSW graphs, payload, index build memory, cache, and the operating system. By this point you can see: a 4GB VPS can do functional validation, but you cannot promise it will stably hold 1 million 1536-dimensional HNSW entries just because "the raw vectors are only a few GB."
If the model outputs 768 dimensions, the raw data is roughly halved; switching to half precision can also shrink the working set, but brings trade-offs in precision and implementation. Calculate the raw volume with your own embedding dimensions first, then leave headroom for indexes, business fields, backups, and growth—this is more reliable than copying someone else's "capacity table."
pgvector is a PostgreSQL extension. Its advantage is not "always fastest," but that you can put vectors and relational data such as users, documents, permissions, and orders in the same transaction and backup system.
The current official README supports exact nearest-neighbor search as well as two approximate indexes, HNSW and IVFFlat. HNSW usually offers a better speed-recall trade-off, but builds more slowly and uses more memory; IVFFlat builds more lightly, but its parameters and data distribution affect recall more.
Cases where pgvector fits well:
- You already have a stable PostgreSQL;
- Vector retrieval needs to filter together with many relational conditions;
- Data scale is temporarily small and the team doesn't want to maintain another database;
- You need PostgreSQL transactions, JOINs, WAL, PITR, and existing monitoring tools.
Its pitfalls are also direct: business queries, autovacuum, backups, and HNSW index building all compete for the same database resources. The official documentation also warns that HNSW graphs build faster when they fit in maintenance_work_mem, but setting this value too large may exhaust server memory.
When a small team already has PostgreSQL, pgvector is often the lowest-cost first step; once vector retrieval truly becomes an independent workload, splitting to Qdrant is easier than maintaining three or four component sets from the start.
Qdrant provides independent REST and gRPC interfaces, with vectors, payload, and filtering designed around retrieval scenarios. For RAG knowledge bases, its boundary is clearer than "stuffing another extension into the business database": an application failure doesn't necessarily drag down the vector database, and scaling the vector database doesn't require touching the main business database first.
Qdrant suits these cases:
- Multiple applications share one vector service;
- Payload filtering is part of the retrieval chain;
- You want an independent API to manage collections, points, and snapshots;
- You may later upgrade memory, disk, or split nodes separately.
The cost is that you must handle API Key, TLS, snapshots, upgrades, and monitoring yourself. The self-hosted Qdrant version is not automatically secure after installation, and port 6333 should not be exposed directly to the public internet.
Milvus has deployment modes such as Lite, Standalone, and Distributed. Standalone can run on a single machine; Distributed targets large-scale scenarios beyond single-machine capacity, with significantly increased component and operational complexity.
In Milvus's current official hardware recommendations, Standalone requires a minimum of 8GB and recommends 16GB memory; Cluster requires a minimum of 32GB and recommends 128GB, and disks should be at least SATA SSD, with NVMe recommended. These numbers already show that Milvus is not a peer replacement on a 2-core 4GB small VPS.
When is it worth considering?
- Single-machine resources have been confirmed as a bottleneck through monitoring;
- Data scale, writes, and query volume continue to grow;
- The team can maintain object storage, monitoring, backups, and multi-component upgrades;
- The benefits of distribution outweigh the extra machine and labor costs.
If the project is just starting and you're only worried about "100 million entries in the future," first record data growth, query concurrency, and latency targets. Without these data, distribution is just paying the operations tax in advance.
Below are conservative starting tiers, not capacity guarantees. Different dimensions, index parameters, payload, concurrency, and CPU platforms will spread results widely.
Suitable for pgvector validation when PostgreSQL already exists, or low-concurrency Qdrant testing. The system, Docker, and monitoring themselves also use memory—don't count all 4GB toward indexes.
This tier is not suitable for Milvus Standalone, nor for building high-dimensional HNSW in large batches. When memory approaches the limit, Swap can only prevent processes from exiting immediately; it cannot keep retrieval latency stable.
If you want to run an independent Qdrant, take regular snapshots, and leave resources for the system and reverse proxy, 8GB is much more comfortable than 4GB. Projects with existing PostgreSQL + pgvector can also leave room for shared buffers, queries, and index maintenance.
But 8GB is still not a "million-vector pass." As calculated earlier, 1 million 1536-dimensional float32 raw vectors alone are close to 5.73GiB, not counting indexes and business data.
At 16GB, you can seriously test larger Qdrant collections, pgvector HNSW, or deploy Standalone per Milvus's official recommendations. Whether to stay single-machine depends on your failure recovery goals: if the database and indexes are all on one machine, upgrades, disk failures, and accidental deletion will still affect the service together.
If you're still hesitating between 1-core 1GB, 2-core 2GB, and 2-core 4GB, first read this VPS configuration selection guide and factor in the baseline overhead of the system, application, and database together.
Don't declare the machine sufficient just because queries run on an empty collection. What really fills memory and disk is batch import, index building, filtered queries, and snapshots happening at the same time.
Prepare a batch of data that represents real business: don't casually simplify vector dimensions, text length, tag counts, or filter conditions. First record the data directory size and process memory without indexes, then create the HNSW or IVFFlat index you plan to launch with, and compare disk, memory peaks, and build time before and after indexing.
Then replay real queries: include not only ordinary nearest-neighbor search but also the tenant, permission, time, and category filters the business will use. Testing only single-request average latency is not very meaningful; at minimum observe peak concurrency, P95/P99 latency, error rates, and recall changes at the same time.
Finally, do a failure drill: create a snapshot, transfer the copy elsewhere, delete the test collection, and restore according to the documentation. If recovery time already exceeds the downtime window the business can accept, then even if daily queries are fast, this configuration cannot be considered a pass.
Record the vector count, dimensions, index parameters, disk usage, memory peaks, and query results for each round. Repeat once when data doubles—this is more useful for judging whether to add memory, change disks, or split into an independent service than copying someone else's benchmarks.
Common purchasing priorities for vector retrieval are:
- Memory headroom: index building and queries both need working sets; running long-term at 95% memory is dangerous.
- SSD/NVMe random I/O: data, WAL, snapshots, and indexes all read and write disk; looking only at capacity and not latency easily leads to pitfalls.
- CPU model: index building and distance computation are CPU-intensive; older shared multi-core may not be faster than newer platforms with fewer cores.
- Backup egress: snapshots must be transferred to another machine or object storage; outbound traffic and speed both affect recovery time.
- Data center location: keep the vector database as close as possible to the application that calls it; don't make every retrieval cross continents.
Don't count only the VPS in monthly costs:
真实月成本 = VPS + 异地备份/对象存储 + 快照空间 + 出站流量 + 维护时间
pgvector may save one service but concentrates risk in the main database; Qdrant adds one container but has clear boundaries; Milvus Distributed can scale horizontally but also raises machine count and operational costs together.
The following assumes Ubuntu/Debian, Docker Compose already installed, and Caddy installed on the host. The example pins Qdrant v1.18.3 released on July 17, 2026; do not use latest long-term in production.
Create directories:
sudo mkdir -p /opt/qdrant/{storage,snapshots}
sudo chown -R "$USER":"$USER" /opt/qdrant
cd /opt/qdrant
Generate an API Key and tighten permissions on .env:
umask 077
printf 'QDRANT_API_KEY=%s\n' "$(openssl rand -hex 32)" > .env
Create compose.yaml:
services:
qdrant:
image: qdrant/qdrant:v1.18.3
container_name: qdrant
restart: unless-stopped
ports:
- "127.0.0.1:6333:6333"
environment:
QDRANT__SERVICE__API_KEY: "${QDRANT_API_KEY}"
volumes:
- ./storage:/qdrant/storage
- ./snapshots:/qdrant/snapshots
Here 6333 is deliberately bound to 127.0.0.1. It cannot be accessed directly from outside; only through Caddy on the host. If you don't use gRPC, you don't need to expose 6334.
Start and check:
docker compose pull
docker compose up -d
docker compose ps
set -a
. ./.env
set +a
curl -fsS -H "api-key: ${QDRANT_API_KEY}" http://127.0.0.1:6333/collections
If it returns the collection list, the container and authentication are working. If it returns 401, first confirm .env is loaded, the container environment variable exists, and check docker compose logs qdrant.
First point the A/AAAA records of vector.example.com to the VPS, and ensure the firewall only opens SSH, 80, and 443. 6333/tcp does not need public exposure.
Host Caddyfile:
vector.example.com {
reverse_proxy 127.0.0.1:6333
}
Check and load:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Caddy will apply for and renew certificates for a valid domain. Clients must still include Qdrant's api-key request header when accessing:
curl -fsS \
-H "api-key: ${QDRANT_API_KEY}" \
https://vector.example.com/collections
Replace vector.example.com with your own domain. Do not write the API Key into the Caddyfile, Git repository, or frontend JavaScript; it should be kept in server-side environment variables or a secret management system.
Assuming the collection is named docs, create a snapshot:
curl -fsS -X POST \
-H "api-key: ${QDRANT_API_KEY}" \
http://127.0.0.1:6333/collections/docs/snapshots
List existing snapshots:
curl -fsS \
-H "api-key: ${QDRANT_API_KEY}" \
http://127.0.0.1:6333/collections/docs/snapshots
The snapshot file is still in the host's /opt/qdrant/snapshots directory. When the machine's disk fails, same-disk snapshots may be lost too, so copy them regularly to object storage or another cloud provider.
Before restoring, read the Qdrant Snapshot documentation corresponding to the current version and validate on a test collection. The API form for uploading a local snapshot is as follows:
curl -fsS -X POST \
-H "api-key: ${QDRANT_API_KEY}" \
-F "snapshot=@/path/to/docs.snapshot" \
"http://127.0.0.1:6333/collections/docs/snapshots/upload?priority=snapshot"
/path/to/docs.snapshot is the real file path you retrieved from off-site backup. Restoring affects the target collection; don't try it on the production database the first time.
Snapshots, images, and whole-machine backups are not the same thing; you can design a recovery process by referring to the differences between VPS snapshots, backups, and images.
Production upgrades should do at least these steps:
- Read the target version's release notes and compatibility notes;
- Create a collection snapshot and copy it off-site;
- Record the current image version and compose file;
- Change the image tag to an explicit version before pulling;
- After startup, check health, collection count, and business queries;
- Keep the old configuration and backups that can be rolled back.
docker compose pull
docker compose up -d
docker compose ps
docker compose logs --tail=100 qdrant
Database upgrades differ from ordinary web containers. Data formats, index rebuilds, and recovery time must all be validated in advance; you cannot consider the upgrade successful just because the container is running.
Observe continuously during business peaks first, not just a single top:
# 容器 CPU、内存和网络
docker stats qdrant
# 磁盘空间和 inode
df -h /opt/qdrant
df -i /opt/qdrant
# Qdrant 日志
docker compose logs --since=1h qdrant
# 本地健康检查
curl -fsS http://127.0.0.1:6333/healthz
# Prometheus 格式指标,需要 API Key 时保留请求头
curl -fsS -H "api-key: ${QDRANT_API_KEY}" http://127.0.0.1:6333/metrics
These signals indicate it's time to scale up or split:
- Normal memory approaches the limit, continuous swapping begins, or OOM occurs;
- Business queries time out noticeably during index building;
- Remaining disk space is insufficient to hold data, WAL, and snapshots at the same time;
- Random I/O latency keeps rising;
- Backup windows get longer and longer, and recovery drills cannot complete within the target time;
- The price of vertical single-machine upgrades is already close to multi-node or managed options.
Don't just look at vector count. Stress test with real embeddings, real filter conditions, and concurrency levels, record P95/P99 latency, recall changes, memory peaks, and index build time, then decide whether to add memory, switch to NVMe, do quantization, or split services.
It can do learning, functional validation, and low-concurrency small projects, with the default premise of single node, low concurrency, and real data already stress-tested, but there is no fixed vector count guarantee. 1536-dimensional float32 data grows quickly, and HNSW, payload, and snapshots will continue to take space. You must stress test with your own data before formal use.
If you already have PostgreSQL and need transactions and JOINs, choose pgvector first; if you want vector retrieval to be an independent service, or shared by multiple applications, choose Qdrant. The two are not simply a performance contest, but different system boundaries.
When single-machine bottlenecks have been confirmed by monitoring and stress testing, data and concurrency truly need distributed scaling, and the team can maintain more components. For just an internal knowledge base, Milvus Distributed is often too heavy.
Not recommended. Prefer letting the service go through the same private network, Docker network, VPN, or a reverse proxy with TLS and authentication. Security groups should only allow trusted sources, and API Keys should be rotated regularly.
No. Same-disk snapshots share disk failures, accidental deletion, and account risks with the original machine. Keep at least one copy in a different failure domain and do recovery drills regularly.
If the project is already in PostgreSQL and vector scale is still small, start with pgvector; if you want to provide an independent interface for multiple RAG applications, 4-core 8GB Qdrant is an easier starting point to manage; if you're sure you need more single-machine resources, evaluate 8-core 16GB+ and Milvus Standalone; only after single-machine bottlenecks are confirmed should you discuss Milvus Distributed.
When buying a VPS, don't squeeze memory too tight just to save a little monthly fee. What vector databases fear most is not average load, but index building, batch import, snapshots, and business queries colliding at the same time. Leaving headroom, pulling 6333 back to localhost, and copying snapshots out are more useful than chasing a benchmark table detached from your dataset.