Skip to content

fix: open MaxMind DB once per process to prevent OOMKill on global middleware - #4

Open
rubarcelk wants to merge 1 commit into
thiagotognoli:mainfrom
rubarcelk:fix/singleton-db-reader-oom
Open

fix: open MaxMind DB once per process to prevent OOMKill on global middleware#4
rubarcelk wants to merge 1 commit into
thiagotognoli:mainfrom
rubarcelk:fix/singleton-db-reader-oom

Conversation

@rubarcelk

Copy link
Copy Markdown

Problem

When this plugin is applied as a global Traefik entrypoint middleware (via entryPoints.websecure.http.middlewares), New() is called once per route at startup. With hundreds of routes, the previous code opened the ~120 MB MaxMind DB file hundreds of times simultaneously, causing a severe memory spike that OOMKilled Traefik pods immediately on startup.

Root cause: factoryLookups() called lib.NewLookupCity/Country/Asn on every New() invocation, and each of those called geoip2.NewCityReaderFromFile() which loads the full database into memory. 280 routes = 280 x 120 MB readers.

Fix

Introduce package-level singletons (singletonCity, singletonCountry, singletonAsn) protected by a sync.Mutex, so each DB file is opened at most once regardless of how many routes reference the middleware. Subsequent New() calls reuse the cached reader at zero extra memory cost.

This matches the pattern used in the parent project traefik-plugins/traefikgeoip2 (package-level var lookup LookupGeoIP2 with if lookup == nil guard).

ResetLookup() is restored to actually nil the singletons — it was previously a no-op stub with commented-out variables.

Testing

Tested against a Kubernetes cluster with ~280 Ingress routes. Previously, Traefik pods OOMKilled within seconds of startup when the global middleware was enabled. After this fix, all pods remain stable with the same configuration.

…ddleware

When this plugin is applied as a global Traefik entrypoint middleware,
New() is called once per route at startup. The previous code opened the
~120 MB MaxMind DB file on every New() call, causing a severe memory
spike that OOMKilled Traefik pods.

The root cause: factoryLookups() called lib.NewLookupCity/Country/Asn on
every invocation, each of which called geoip2.NewCityReaderFromFile() and
loaded the full database into memory. With hundreds of routes, this means
hundreds of ~120 MB readers open simultaneously.

Fix: introduce package-level singletons (singletonCity/Country/Asn)
protected by a sync.Mutex, so each DB file is opened at most once
regardless of how many routes reference the middleware. Subsequent New()
calls reuse the cached reader at no extra memory cost.

This matches the pattern used in the parent project traefik-plugins/traefikgeoip2
(see its package-level var lookup and if lookup == nil guard).

ResetLookup() is restored to actually nil the singletons so tests that
need a fresh reader can call it before New() — it was previously a no-op
stub with commented-out code.

Tested against a Kubernetes cluster with ~280 Ingress routes where global
middleware previously caused immediate OOMKill; all 3 Traefik pods remain
stable after the fix.

Co-authored-by: Cursor <cursoragent@cursor.com>
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