InkVault load test

A report for people who run an XMPP server on a VPS: will the server work correctly with InkVault, how many users can it handle, and what to check in the configuration.

attributes

project
InkVault
repo
https://github.com/tzx1z/inkvault-loadtest
server
Prosody 13.0.7, Lua 5.4
storage
SQLite, delete journal, synchronous FULL
dataset
100 users with 200 records each, 900-byte record ciphertext
rate_limit
10 KB/s per connection
date
2026-10-20

Verdict

A VPS with 1 CPU core, 1 GiB of RAM and a 200 Mbit/s link handles InkVault. For a personal server, a family or a small group of up to about 100 users, the resources are more than enough.

During testing the server made no errors and lost no notifications, even under a load 10 times higher than it could process. This conclusion holds under these conditions:

  • Prosody stores data in SQLite (storage = "sql"), as on the test bench
  • Vaults hold up to 256 records, the default limit
  • The VPS disk is an SSD
  • 0errors in 307,977 operations
  • 0lost notifications about new records
  • 37 ms99% of requests were faster at 100 operations per second
  • 30 MiBof memory used by Prosody with 50 connected clients

How InkVault uses the server

InkVault stores records on your XMPP server using PEP (XEP-0163), the standard mechanism for personal account data. Records are encrypted on the device; the server stores only the ciphertext and service fields: the record ID and the key version. InkVault needs no separate server or database.

Devices connect to Prosody, the server keeps records in a PEP node and an SQLite database, sends all records on connect, accepts one record on save and notifies the owner's other devicesAndroidencrypts recordsWeb clientencrypts recordsTerminalencrypts recordsProsody XMPP serveron your VPSPEP: InkVault vaulturn:inkvault:vault:0up to 256 records by defaultaccess: owner onlywrite + fsyncreadSQLiteprosody.sqlite2Save a recordone record, about 1.8 KB1Connectall records, about 270 KB3Notificationa record from another device
1: on every connection the device downloads all vault records. 2: when something changes, the device sends one record to the server. 3: the server forwards it to the owner's other connected devices. Other users of the server and contacts do not receive the records.

What this means for the server: connecting a device is the most expensive operation, because the server sends all records at once. Saving is one small record and one database transaction with a disk write. Between connections the device sends no requests; changes arrive as notifications.

How it was tested

The load generator behaved like InkVault clients: it connected to Prosody over XMPP with TLS, downloaded vaults, and saved and read records. Record size and format match InkVault.

Test bench: a load generator with 50-500 connections, Prosody 13.0.7, an SQLite database with 100 users of 200 records eachLoad generator50-500 connectionslike InkVault clientsXMPP + TLSProsody 13.0.7one process, one threadrate limit 10 KB/sSQLSQLite100 users200 records each20,000 records, 55 MB
Test bench. The generator and the server ran on the same machine, so network latency to clients is not included in the results.
TestLoadDurationResult
Normal load, 2 runs50 clients, 100 operations per second5 min each100 op/s sustained, 0 errors
Stress500 clients, ramping up to 3,000 operations per second15 minserver limit 260-280 op/s, 0 errors

An operation is one client request: downloading the whole vault (8% of requests), saving a record (20%) or reading one record (72%).

Response time under load

Up to 180 operations per second the response time barely changes. Beyond that, requests start waiting for each other, and at 260-280 operations per second the server reaches its limit: it cannot process more, and only the response time grows.

  • Median: half of requests are faster
  • 1 in 100: the slowest requests
Response time barely changes up to 180 operations per second, then grows; at 340 operations per second the median is 863 ms and the slowest requests take 1,168 ms. A personal server's load is under 1 operation per second.Requests don't waitQueue builds upOverload03006009001,200050100150200250300350Response time, msLoad, operations per secondPersonal server with 100 users: under 1 op/s1,168 ms863 msLoad 20 op/s: median 3.7 ms, 1 in 100: 44 msLoad 60 op/s, processed 59 op/s: median 5.2 ms, 1 in 100: 38 msLoad 100 op/s: median 7.1 ms, 1 in 100: 78 msLoad 140 op/s, processed 138 op/s: median 7.5 ms, 1 in 100: 79 msLoad 180 op/s: median 18.7 ms, 1 in 100: 131 msLoad 220 op/s: median 87 ms, 1 in 100: 219 msLoad 260 op/s, processed 253 op/s: median 79 ms, 1 in 100: 285 msLoad 300 op/s, processed 263 op/s: median 263 ms, 1 in 100: 395 msLoad 340 op/s, processed 274 op/s: median 863 ms, 1 in 100: 1,168 ms
Chart data
Load, op/sProcessed, op/sMedian, ms1 in 100, ms
20203.744
60595.238
1001007.178
1401387.579
18018018.7131
22022087219
26025379285
300263263395
3402748631,168
Response time on the test bench as load increases, stress test with 500 clients. On a slow VPS CPU the curve shifts left, and the limit starts at about 110 operations per second. The load of a personal server stays at the far left of the chart.
  • 0.9 msmedian at 100 operations per second
  • 37 ms99% of requests were faster
  • 117 msslowest request in 5 minutes
  • stableresponse time over 5 minutes

What loads the server

Prosody handles requests from all users one at a time, in a single thread. While a long operation runs, the other requests wait.

Requests wait in a single queue and Prosody runs them one at a time; while a vault download runs, the other requests waitRequests from all users wait their turnloginreadsavemessageProsodynow running:vault download30-100 ms per 256 recordsSQLSQLite
All server requests go through one queue, including regular messages and presence. That is why response time depends on how busy the server is, and not only on how fast a single operation is.

The longest operation is downloading the whole vault when a device connects. In the test it was 8% of requests, yet half of the server's working time.

  • Vault download
  • Saving a record
  • Reading one record
Vault download: 8% of requests and 50% of server time; saving: 20% of requests and 35% of time; reading one record: 72% of requests and 15% of timeShare of requestsShare of requests: vault download 8%8%Share of requests: saving a record 20%20%Share of requests: reading one record 72%72%Share of server timeShare of server time: vault download 50%50%Share of server time: saving a record 35%35%Share of server time: reading one record 15%15%
Chart data
OperationShare of requestsShare of server time
Vault download8%50%
Saving a record20%35%
Reading one record72%15%
Share of requests and share of server time by operation type at 100 operations per second. Server time is estimated from the execution time of each operation.

Download time grows with the number of records. If the limit is raised to several thousand records, every connection of such a user will hold up the whole server for seconds.

Vault download time: 256 records take 28-95 ms, 1,000 records 0.11-0.37 s, 10,000 records 1.1-3.7 s10 ms100 ms1 s10 s256 recordsdefault256 records: 28 ms on a fast CPU, 95 ms on a slow CPU28-95 ms1,000 records1,000 records: 110 ms on a fast CPU, 370 ms on a slow CPU0.11-0.37 s10,000 records10,000 records: 1,100 ms on a fast CPU, 3,700 ms on a slow CPU1.1-3.7 s
Chart data
RecordsFast CPUSlow CPU
25628 ms95 ms
1,000110 ms370 ms
10,0001,100 ms3,700 ms
Server time for one vault download, by number of records. The lower bound was measured on the test bench CPU; the upper bound corresponds to a slow VPS CPU. The scale is logarithmic: each division is 10 times the previous one.

Are the VPS resources enough

Estimate for a VPS with 1 core, 1 GiB of RAM and a 200 Mbit/s link. 100 InkVault users, each with 2-3 devices, 10-30 connections and about 5 saves per day, up to 256 records.

  • CPU: up to 4 min per day

    The server spends 30 seconds to 4 minutes a day on InkVault requests, less than 0.3% of the time. A second core is not needed: Prosody runs in a single thread.

  • Memory: 250-400 MB of 1 GiB

    System 150-250 MB, Prosody 30-60 MB, database cache about 70 MB. More than half of the memory stays free.

  • Disk: about 70 MB database

    2.8 KB per record including indexes. One save writes up to 400 KB to disk: database pages and the transaction journal. For an SSD in personal use this volume is negligible.

  • Network: about 4 s after a restart

    In normal operation InkVault uses a fraction of a percent of the link. After a server restart, 300 devices download about 105 MB, which takes about 4 seconds on a 200 Mbit/s link.

  • After a restart: 7-22 s

    This is how long it takes for all 300 devices to download their vaults again. The server processes the downloads one by one, so during these seconds it responds more slowly than usual. For 500 users it takes from 35 seconds to 2 minutes.

How many users such a VPS can handle

  • 1-100 users

    Suitable, with a large margin.

  • 100-500 users

    Probably suitable. Test on your VPS: a lot depends on its CPU speed.

  • More than 500 users

    Not tested. A separate test on the target server is needed.

What happens under overload

In the stress test the server received 10 times more requests than it could process.

  • 3,000operations per second requested
  • 264operations per second processed
  • 0errors and lost notifications
  • about 2 sresponse time instead of milliseconds

The server did not fail and lost no data: all requests completed, all notifications were delivered, and responses slowed down. After the load dropped, the accumulated queue was processed. A personal server's load is hundreds of times lower than this level.

What to check on your server

Settings are in prosody.cfg.lua. Restart Prosody after changing them.

SQLite storage

The conclusions of this report apply to SQLite storage. By default Prosody stores data in files (internal); that option was not tested. To switch to SQLite:

storage = "sql"
sql = { driver = "SQLite3", database = "prosody.sqlite" }

This requires the LuaDBI package for SQLite: lua-dbi-sqlite3 on Debian and Ubuntu. Existing data can be moved with prosody-migrator.

Vault record limit

The limit is set by pep_max_items. The default of 256 matches InkVault's default vault limit. If you need more records, raise the limit:

pep_max_items = 10000

PEP module

The pep module is part of the standard Prosody configuration and is usually already enabled in modules_enabled. Testing was done on Prosody 13.0.7.

Disk speed

Save speed depends on how fast the disk confirms a write. A check for experienced users; the file is created next to the Prosody database and removed after the test:

  1. fio --name=fsync --filename=/var/lib/prosody/fio-test --rw=write --bs=4k --size=16m --fsync=1 --ioengine=sync
  2. rm /var/lib/prosody/fio-test

If 99.00th in the sync percentiles block is 3000 usec or less, the disk is fine.

Rate limit

If the server limits connection speed (the limits module, for example 10 KB/s), the first import of 256 records takes about 50 seconds. This is the limit at work, not an error.

Backup

The InkVault vault lives in the Prosody database. You can back it up without stopping the server:

  1. sqlite3 /var/lib/prosody/prosody.sqlite ".backup '/var/backups/prosody.sqlite'"

The database path depends on your installation.

SQLite and PostgreSQL

All conclusions in this report come from a test bench with SQLite and 100 users. SQLite is enough for a small server.

PostgreSQL is worth choosing for a server with hundreds of users or more. In a reference measurement on the same bench, the server wrote 4-5 times less to disk per save with PostgreSQL. Response time and CPU load were close to SQLite: Prosody runs queries to any database one at a time in its own thread, so the choice of database has little effect on the number of requests per second.

Reference measurement, 100 op/sSQLitePostgreSQL
Disk writes per save394 KB85 KB
Prosody CPU43%43%
Response time: median / 1 in 1006.3 / 77 ms7.3 / 159 ms

InkVault project