Memos

Performance Tuning

Focus on the parts of a Memos deployment that most affect real-world performance.

Memos is lightweight by default, so performance work should stay targeted. Start with storage, database choice, and deployment shape before reaching for low-level tuning.

Built-in caching

Memos maintains an in-memory cache for frequently accessed data. The cache runs entirely in-process with no external dependency:

Cached resourceTTLMax items
Instance settings10 min
Users10 min1000 total
User settings10 min

Cache entries are invalidated immediately on write operations and automatically expired after their TTL.

The database-backed ACCESS setting bypasses the ordinary settings cache so another replica observes an access-policy change on its next policy check. Deployment-file overrides remain fixed until that process restarts.

SQLite tuning

Memos applies these SQLite pragmas by default:

PragmaDefault valueEffect
journal_modeWALWrite-Ahead Logging; allows concurrent reads during writes
busy_timeout10000 (10 s)Waits up to 10 s for a write lock before returning an error
foreign_keys0Disabled for flexibility
mmap_size0Memory-mapping disabled to avoid OOM on some systems

For higher write loads, you can extend the defaults through the DSN:

export MEMOS_DSN="/path/to/memos.db?_pragma=cache_size(-64000)&_pragma=synchronous(NORMAL)&_pragma=temp_store(MEMORY)"

SQLite limitations to keep in mind:

DimensionRecommendation
Concurrent writers1 (serialized through WAL)
Concurrent readersUnlimited
Practical DB sizeUp to ~100 GB
When to migrateHigh concurrent writes or multi-server write patterns

Live updates and write contention

In 0.31, browser tabs coordinate one SSE connection through a visible tab instead of opening redundant streams. Keep reverse proxies compatible with long-lived event streams and avoid response buffering on the SSE route.

SQLite memo mutations begin with BEGIN IMMEDIATE, allowing concurrent writers to wait for the write lock earlier in the transaction. Writes are still serialized; this reduces intermittent SQLITE_BUSY failures without making SQLite a multi-writer database.

First-order decisions

  • SQLite is fine for many single-instance deployments
  • move to MySQL or PostgreSQL when operational needs justify it
  • choose the attachment backend based on file volume and backup expectations
  • keep the instance behind a reverse proxy in production

Where to look first

  • database latency or lock contention
  • slow storage volumes
  • oversized attachment handling
  • under-provisioned container or pod resources

Tuning advice

  • measure first
  • tune the database only after confirming the bottleneck
  • keep caching and proxy behavior simple unless usage data says otherwise
  • prefer structural fixes such as better storage placement before micro-tuning runtime flags

On this page