Skip to content

perf(agent): cache IP whitelist rules across requests#324

Open
matthv wants to merge 1 commit into
mainfrom
perf/cache-ip-whitelist
Open

perf(agent): cache IP whitelist rules across requests#324
matthv wants to merge 1 commit into
mainfrom
perf/cache-ip-whitelist

Conversation

@matthv

@matthv matthv commented Jul 2, 2026

Copy link
Copy Markdown
Member

Context

ForestAdminAgent::Routes::AbstractAuthenticatedRoute calls Facades::Whitelist.check_ip on every authenticated request, and check_ip builds a fresh IpWhitelist instance each time. IpWhitelist memoized its rules only in instance variables (@rules / @use_ip_whitelist), with no cross-request cache — so every request issued a new HTTPS call to GET /liana/v1/ip-whitelist-rules, opening a new TLS connection each time (no pooling).

Profiling a trivial endpoint (.../relationships/transactions/count) under load showed ~65% of request wall-time in TCPSocket#initialize / Net::HTTP#connect, all originating from IpWhitelist#fetch_rules. On a mini benchmark this made cheap endpoints 3–6× slower than they should be (e.g. a count: ~33 ms with only ~1.7 ms of SQL).

By contrast, permissions are already cached (FileCache, permission_expiration TTL) — 0 permission fetches over the same burst.

Change

IpWhitelist now caches the fetched configuration in a FileCache('ip_whitelist', cache_dir, permission_expiration) via get_or_set, mirroring Permissions:

  • new instances read the cached rules instead of re-hitting the API;
  • TTL = permission_expiration (same as permissions);
  • the HTTP call is isolated in fetch_ip_whitelist_from_api, so failed fetches raise and are not cached.

Measured effect

Same scenario, before → after (median of 7, warm):

endpoint before after
related count ~33 ms ~7 ms
record show ~45 ms ~9 ms
related list (100) ~155 ms ~49 ms

The per-request outbound HTTPS call to the Forest server is gone (first request warms the cache, subsequent ones read from FileCache).

Tests

  • ip_whitelist_spec.rb: added before { described_class.invalidate_cache } (the disk cache otherwise leaks between examples), plus a new test asserting the API is fetched once across two instances.
  • spec_helper.rb: added permission_expiration: 100 to the shared agent config (needed now that the service builds a FileCache with that TTL).
  • .rubocop.yml: ip_whitelist.rb added to the Metrics/ClassLength exclude list, alongside permissions.rb.

Full forest_admin_agent suite: 719 examples, 0 failures. RuboCop clean.

Note: stacked on perf/ar-datasource-join-to-one-same-db (#323) since that is the branch it was validated against; the change is independent and can be retargeted to main once #323 merges.

🤖 Generated with Claude Code

Note

Cache IP whitelist rules across requests using a file-backed cache

  • Adds a file-backed cache (keyed forest.ip_whitelist) to IpWhitelist so the API is called only once per TTL window instead of on every request.
  • Extracts API fetching into fetch_ip_whitelist_from_api, with fetch_rules now reading from cache via get_or_set.
  • Adds IpWhitelist.invalidate_cache class method to manually clear the cached whitelist entry.
  • Test suite gains a before hook calling invalidate_cache for isolation and a new test asserting a single API call across multiple instances.

Macroscope summarized 57fbc5b.

Base automatically changed from perf/ar-datasource-join-to-one-same-db to main July 2, 2026 15:43
@qltysh

qltysh Bot commented Jul 2, 2026

Copy link
Copy Markdown

All good ✅

`Facades::Whitelist.check_ip` runs on every authenticated request and
instantiated a fresh `IpWhitelist` each time, whose rules were memoized
only in instance variables. Every request therefore issued a new HTTPS
call to `GET /liana/v1/ip-whitelist-rules` (new TLS connection, no
pooling) — profiling showed this dominated ~65% of request wall-time.

Cache the fetched configuration in a `FileCache` with the
`permission_expiration` TTL, mirroring `Permissions`. Adds
`IpWhitelist.invalidate_cache` (ready for the SSE cache-invalidation
`TODO`). The API call is isolated in `fetch_ip_whitelist_from_api` so
failures are never cached.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@matthv matthv force-pushed the perf/cache-ip-whitelist branch from 512ad61 to 57fbc5b Compare July 2, 2026 15:48
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