Chronicler
Performance & models

Performance and memory

Give the stack more RAM, and know which knob actually changes anything.

Chronicler is slow for one of three reasons, in this order of likelihood:

  1. There's no GPU, or there is one and it isn't being used.
  2. Docker doesn't have enough memory, so the model reloads constantly.
  3. The model is too big for the machine and is spilling to disk.

Fix them in that order. GPU acceleration covers the first, this page covers the rest.

1. Give Docker more memory

This is the setting people mean when they say "allocate more RAM", and it lives in Docker, not in Chronicler. Nothing in the compose file can raise it.

Docker Desktop with the WSL2 backend takes memory through WSL, and by default it will take up to half the machine. To set it explicitly, create or edit %UserProfile%\.wslconfig:

.wslconfig
[wsl2]
memory=16GB
processors=8
swap=8GB

Then, in an admin PowerShell:

PowerShell
wsl --shutdown

Restart Docker Desktop. Leave the host at least 4–8 GB - starving Windows to feed the model makes everything worse.

wsl --shutdown stops your containers. Do it when nobody is using the server.

Check what Docker actually got:

PowerShell
docker info --format "{{.MemTotal}}"

Under 4 GiB and the backend will be killed as soon as a model loads.

2. Tune the model runtime

These go in the .env next to your compose file. Each is optional - leave it out and Ollama's own default applies. Run docker compose up -d after a change.

SettingDefaultWhat it does
OLLAMA_KEEP_ALIVE96hHow long the model stays loaded after a question.
OLLAMA_NUM_PARALLELautomaticHow many questions one model answers at once.
OLLAMA_MAX_LOADED_MODELSautomaticHow many different models stay in memory.
OLLAMA_CONTEXT_LENGTH16384Default context for clients that don't ask for one.

OLLAMA_KEEP_ALIVE - the one worth knowing about

Ollama's own default unloads the model five minutes after the last question, and the next question pays the full cold load again: one to three minutes on a CPU, several seconds on a GPU. Chronicler ships 96h (4 days) instead, so a server left running answers the second question as fast as the first.

The cost is exactly the model's memory (3–20 GB depending on which one), held whether anyone is asking or not. On a shared machine that has other work to do, lower it:

.env
OLLAMA_KEEP_ALIVE=2h

-1 goes the other way and keeps the model resident permanently.

OLLAMA_NUM_PARALLEL - for several people at once

Each parallel slot gets its own context memory, on top of the model weights. Doubling it roughly doubles the per-context memory. With one or two users, leave it alone; a spare 1 also frees memory on a small machine.

.env
OLLAMA_NUM_PARALLEL=4

Only useful with memory to spare. If the machine is already tight, more parallel slots make everything slower, not faster.

OLLAMA_MAX_LOADED_MODELS

Chronicler generates with a single model, so leaving this alone is right for almost everyone. Setting it to 1 on a small machine guarantees a second model never sneaks into memory.

OLLAMA_CONTEXT_LENGTH

Raising this does not give chat a bigger context window. The Chronicler backend asks for its own context size on every request, and that value is fixed in the image. This setting only affects other clients talking to the same Ollama.

Included for completeness. Changing it is almost never the fix you're looking for.

3. Tune the database

Also in .env. The defaults are Postgres' own, which are famously conservative.

SettingDefaultWhat it does
POSTGRES_SHARED_BUFFERS128MBDatabase cache. The one that matters for search across a big library.
POSTGRES_WORK_MEM4MBMemory per sort. Raise in small steps.
.env
POSTGRES_SHARED_BUFFERS=2GB
POSTGRES_WORK_MEM=32MB

A common rule of thumb for shared_buffers is 25% of the memory you're willing to give the database. work_mem is allocated per sort, not per connection, so a large value times many concurrent queries is how servers run out of memory - 32 MB is generous, 1 GB is a mistake.

Only worth touching once you have thousands of documents. Below that, the default is fine and the model is your bottleneck.

4. Right-size the model

A model that doesn't fit is the worst case: it spills to disk and each answer takes minutes. Match the model to the memory you actually have - see Models. Smaller and resident beats bigger and swapping, every time.

Worked examples

Laptop, no GPU, 16 GB

Docker: 8 GB. Model: gemma4:e2b.

.env
OLLAMA_KEEP_ALIVE=30m
OLLAMA_NUM_PARALLEL=1

Office server, no GPU, 32 GB

Docker: 24 GB. Model: gemma4:e4b.

.env
OLLAMA_KEEP_ALIVE=-1
OLLAMA_NUM_PARALLEL=2
POSTGRES_SHARED_BUFFERS=2GB
POSTGRES_WORK_MEM=16MB

Workstation, 24 GB NVIDIA

COMPOSE_PROFILES=nvidia. Model: gemma4:26b.

.env
OLLAMA_KEEP_ALIVE=-1
OLLAMA_NUM_PARALLEL=4
POSTGRES_SHARED_BUFFERS=4GB
POSTGRES_WORK_MEM=32MB

Check what you changed

PowerShell
docker compose config | Select-String "OLLAMA_" -Context 0,6
docker exec chronicler-ollama env | Select-String "OLLAMA"
docker exec chronicler-postgres psql -U chronicler -d chronicler_app -c "show shared_buffers;"
docker stats --no-stream

A setting you left out of .env won't appear in the container at all. That's correct - it means the runtime's own default is in force.

Speed that isn't about memory

  • Scope your questions. A conversation pointed at three documents is far faster than one pointed at everything.
  • Index during quiet hours. Bulk uploads, especially scanned ones, compete with chat for the same CPU.
  • Use an SSD. Loading a 20 GB model off a spinning disk is a bad minute.