Skip to content

perf: reuse Liquid engines and hoist constant regexes in MJML converter#381

Open
Amahdip wants to merge 1 commit into
Notifuse:mainfrom
Amahdip:perf/reuse-liquid-engine
Open

perf: reuse Liquid engines and hoist constant regexes in MJML converter#381
Amahdip wants to merge 1 commit into
Notifuse:mainfrom
Amahdip:perf/reuse-liquid-engine

Conversation

@Amahdip

@Amahdip Amahdip commented Jul 4, 2026

Copy link
Copy Markdown

Problem

processLiquidContent creates a new SecureLiquidEngine — a fresh liquid.Environment plus full standard-tag registration — for every block of every render. cleanLiquidTemplate and camelToKebab also recompile constant regexes per call. All three run per-block during email rendering, so a broadcast pays this cost thousands of times.

Fix

Reuse engines through a sync.Pool and hoist the regexes to package level.

A pool is used instead of a shared instance deliberately: liquidgo's Environment.CreateStrainer reads and writes strainerTemplateClassCache without a lock during rendering, so a single engine must not be shared across goroutines. Verified with go test -race ./pkg/notifuse_mjml/.

Results

Benchmark Before After
ProcessLiquidContent 28,424 ns/op · 338 allocs/op 10,537 ns/op · 120 allocs/op
ProcessLiquidContentParallel 17,504 ns/op 2,418 ns/op
CamelToKebab 624 ns/op · 17 allocs/op 231 ns/op · 5 allocs/op

processLiquidContent created a new SecureLiquidEngine (fresh environment
+ standard tag registration) for every block on every render, and
cleanLiquidTemplate/camelToKebab recompiled constant regexes per call.
All three run per-block during email rendering, so a broadcast pays
this cost thousands of times.

Engines are now reused through a sync.Pool. A pool is used instead of a
shared instance because liquid.Environment caches strainer classes in an
unsynchronized map during rendering, so a single engine is not safe for
concurrent use. Verified with go test -race.

BenchmarkProcessLiquidContent: 28424 ns/op -> 10537 ns/op (-63%),
338 allocs/op -> 120 allocs/op
BenchmarkProcessLiquidContentParallel: 17504 ns/op -> 2418 ns/op (-86%)
BenchmarkCamelToKebab: 624 ns/op -> 231 ns/op (-63%)
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