Performance
Chinaski’s performance is designed around what the workload actually is: an admin with a handful of editors and periodic batch builds. This page covers the levers you can turn: build parallelism, database indexes, cache headers, and the right knobs ordered by impact.
First: build parallelism
Builds are the expensive operation, and their parallelism is configurable:
CHINASKI_JOBSenvironment variable takes highest precedence.- The build_jobs setting (Settings) is next.
- Otherwise the builder uses your CPU count.
- Values are clamped between 1 and 16; a value of 1 builds sequentially without forking: the debugging mode.
Parallel-build architecture: per-language render waves plus per-phase waves (archives built together) so one fully loaded machine runs builds sequentially per worker pool.
Cache headers for the static side
The shipped nginx and Caddy examples nail down cache lifetimes so visitors get fast repeat visits and fresh content isn’t delayed:
- HTML: 5-minute revalidate; content staleness is bounded to the publish cycle.
- sw.js: no-cache, must-revalidate (new service workers push immediately).
- Fonts: year-long immutable caching.
- Other assets: one-day cache.
The application sets its own security headers; the proxy headers are belt-and-braces for responses served before the app could respond.
Databases and indexes
The database ships with performance indexes across pages and posts (active/updated ordering, published+language arcs, the posts category index), applied automatically when the app starts, idempotent on re-run. SQLite runs embedded (no server process, no credentials) in WAL mode with a 64 MiB cache and a 5-second busy timeout. For the expected workload: one active admin session, periodic batch builds, SQLite is the right choice.
Build time levers
- HTML minification is on by default; disabling it (settings) trades page size for build speed (regex-driven minification is a measurable build cost).
- Incremental builds are the default for admin-triggered rebuilds, unchanged pages are reused rather than rewritten; a stalled build falls back to a full build.
- Per-phase timings in every build log: top-level phases and hot-loop leaf counters (template rendering, HTML minify, post-queries) are reported separately; don’t sum the two sections; the verifier output shows where the time actually went.
- Timing history is JSON persisted per build, so trend analysis across your site’s own history is easy (last one: query the newest build_log row’s timings_json).
What the project measures
The benchmarking document ships a reproducible protocol: a fixture DB seeded with a 7,000-post corpus and a timed build (with a flag set for scratch runs). The current documented baseline (three cores): 7,008-article corpus built in ~162.6 s, 9,129 files, 588 MB of output, with template rendering notably not the bottleneck; the dominating costs are minification regex, the link/accessibility validation passes, and the search index. Use the numbers to compare your own baseline rather than as a promise.
The small stuff
- The settings cache in the CMS has a TTL: repeated settings reads are served from memory.
- Webhooks are capped at 8 concurrent sends, so a webhook-heavy build doesn’t starve the request loop.
- Log/audit-table pruning is throttled (once every ~15 minutes) so maintenance work doesn’t have periodic spikes.
- The admin build history and promotion guard run correctness checks in the same time budget as the build; a failed build can’t hang your deploy; forward deploys to the SSH/rsync or S3 targets are immediately the next step.