Skip to content

Memory limit from the container, streamed exports, trigram search index - #4

Merged
FabioHAraujo merged 1 commit into
mainfrom
perf/memory-export-search
Oct 23, 2026
Merged

FabioHAraujo merged 1 commit into
mainfrom
perf/memory-export-search

Conversation

@FabioHAraujo

Copy link
Copy Markdown
Contributor

A benchmark against Tolgee with the real Platify project (160k keys, 5 languages) showed LowLex losing on text search and on the full ZIP export. Both came from the 256 MB container: the Go heap grew to the limit (4,907 memory.events max during one export) and the kernel throttled the process.

Changes

  • Memory: at start LowLex reads the cgroup limit (v2 or v1) and sets the Go memory limit to 80% of it; GOMEMLIMIT still wins. Logged as memory limit 256 MB: keeping the heap under 204 MB.
  • Exports stream: formats.Write writes each file straight to the response or ZIP entry. Flat and nested JSON are generated incrementally and are byte-identical to encoding/json (new test covers quotes, control characters, U+2028/9, invalid UTF-8, and keys that are both a text and a prefix like a / a.b).
  • Search index: migration 3 adds an FTS5 trigram index (one row per key: name, description, all texts). Triggers only mark keys dirty; the index catches up in the background and searches check dirty keys directly, so results are exact right after a large import. Candidates are confirmed with lowlex_contains, a Unicode case-insensitive substring test (SQLite's LIKE folds ASCII only). One- and two-character terms keep the LIKE scan.

Benchmark on the dev server (same tool, same Tolgee, before → after)

Scenario Tolgee LowLex before LowLex after
Editor page (50 keys, 5 languages) 1.81 s 58 ms 30 ms
Page of 500 keys 2.76 s 184 ms 54 ms
Text search 1.67 s 2.52 s 127 ms
Export one language (14 MB) 3.75 s 2.77 s 1.13 s
Export all languages (ZIP, 22 MB) 16.6 s 19.1 s 6.6 s
10 concurrent clients 13 req/s 140 req/s 154 req/s

LowLex peak RAM during the run: 91 MB. The index adds 54 MB to the Platify database (130 → 184 MB) and took ~65 s to build after the upgrade. Locally (load test, 160k keys): search 31–53 ms once the index is idle.

…ch index

- At start, read the cgroup memory limit and keep the Go heap at 80% of it
  (GOMEMLIMIT still wins). The 256 MB container used to hit its limit thousands
  of times during a large export and get throttled.
- Exports write each file straight to the response: flat and nested JSON are
  generated incrementally (byte-identical to encoding/json, tested), PO,
  Android, iOS and .properties write through a buffer.
- Search: FTS5 trigram index with one row per key (name, description, every
  text). Writes only mark keys dirty; the index catches up in the background,
  and searches check dirty keys directly meanwhile, so results are always exact.
  Matches are confirmed with a Unicode case-insensitive contains (LIKE folds
  ASCII only, so 'AÇÃO' now finds 'ação').
- Migration 3 indexes existing keys on first start.

Platify (160k keys, 5 languages) on the dev server: editor page 30 ms, search
127 ms (was 2.5 s), full ZIP export 6.6 s (was 19 s), 91 MB peak RAM; the
search index adds 54 MB to the database.
@FabioHAraujo
FabioHAraujo merged commit 612cb78 into main Oct 23, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant