Skip to content

fix: Asset Issuer caching, e2e tests and metrics, Database Pooler stats caching (#1050, #1051, #1053, #1055) - #1580

Merged
emdevelopa merged 4 commits into
emdevelopa:mainfrom
matteorossi-codes:fix/issues-1050-1051-1053-1055
Sep 25, 2026
Merged

emdevelopa merged 4 commits into
emdevelopa:mainfrom
matteorossi-codes:fix/issues-1050-1051-1053-1055

Conversation

@matteorossi-codes

Copy link
Copy Markdown
Contributor

Summary

Implements four Asset Issuer / Database Pooler issues. One commit per issue.

Changes

#1050 — Asset Issuer query caching (backend/src/lib/asset-issuer.js)

getIssuerStats and getAssetIssuerHealthMetrics re-queried the database on every call. Both are aggregate reads over payment rows that change slowly, so they now sit behind a bounded TTL cache (60s, 500 entries, oldest-first eviction), keyed per issuer and per merchant.

Expired entries are dropped on read rather than only on write, and failed reads are never cached. logAssetIssuerVerification invalidates exactly the issuer and merchant keys a verification write can affect. invalidateQueryCache() with no arguments clears everything; a call that names neither key is a no-op, so a write with no known issuer cannot wipe the cache for other callers.

#1051 — Asset Issuer end-to-end testing (backend/tests/asset-issuer-e2e.test.js)

18 tests driving the module through its public entry points — the AssetIssuerManager facade, the exported singleton and the rate-limit factory — rather than the per-class unit tests already in src/lib/asset-issuer.test.js. Horizon, Postgres, Redis and the logger are mocked, so no network or database is required.

Covers the verification flow, on-chain issuer confirmation, merchant issuer configuration, verification logging, degradation and circuit-breaker recovery, dead-letter handling, and the module surface.

#1053 — Granular metrics for Asset Issuer (backend/src/lib/metrics.js, asset-issuer.js)

Nine new Prometheus series, wired into the module: verification counts and latency, query-cache hit/miss/eviction/invalidation counters and a size gauge, per-query duration, error-recovery outcomes, aggregated circuit-breaker state gauges, and a dead-letter queue size gauge.

Per-issuer and per-transaction detail is deliberately not exported as label values. Issuers, merchants and transaction hashes are unbounded, so labelling by them would let a caller grow series cardinality just by varying an identifier — the same reasoning behind #1058. Verifications are labelled by result and operation type, queries by a fixed query name, and the breaker gauges aggregate across contexts. Per-issuer detail stays available via the existing getCircuitBreakerMetrics() snapshot.

#1055 — Database Pooler stats caching (backend/src/lib/db-pooler-optimized.js)

getPoolerStats() walks the pg pool, the query cache and the rate-limiter window map on every call, so a metrics scrape at the standard interval turns a cheap read into a full state walk per scrape. It is now fronted by a bounded TTL cache (1s, configurable via DB_POOLER_STATS_CACHE_TTL_MS), exposed as getCachedPoolerStats() with invalidatePoolerStatsCache() and getPoolerStatsCacheStats().

getPoolerStats() is deliberately left uncached: callers that need a value guaranteed to reflect the current instant keep reading the live snapshot, and skipCache bypasses the cache for one-off callers. Four new series observe the cache (hits, misses, evictions, size).

Notes

  • asset-issuer.test.js and the new e2e suite now mock ./metrics.js, matching how the rest of the repo isolates the registry. db-pooler-optimized.test.js's existing mock is extended with the four new pooler series, since an unlisted export would be undefined at runtime.
  • The pre-existing Issue #889 and AssetIssuerManager blocks clear the new cache in beforeEach. Those tests assert on mockQueryWithRetry.mock.calls[0] after a single call; once the reads are cached, a leftover entry from an earlier test would serve from cache, issue no query, and leave calls[0] belonging to whichever test ran first.
  • db-pooler-optimized.js and asset-issuer.js are not imported by any non-test file, so this adds no new load-time cost to the running app.

Verification

Code review only. npm test / vitest was not run — the environment has no node_modules and this change installs no dependencies. Every edited file was checked with node --check for syntax.

Two behaviour changes were reasoned through by hand because they interact with existing assertions, and are worth a reviewer's attention:

  1. verifyOperation now records a verification metric on the cache-hit path. The in-flight coalescing path deliberately does not record, so a coalesced call is counted once rather than twice.
  2. getIssuerStats / getAssetIssuerHealthMetrics accept { skipCache }; the existing call sites pass no options, so default behaviour is a cache read then a query on miss.

Closes #1050
Closes #1051
Closes #1053
Closes #1055

@drips-wave

drips-wave Bot commented Sep 25, 2026

Copy link
Copy Markdown

@matteorossi-codes Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@vercel

vercel Bot commented Sep 25, 2026

Copy link
Copy Markdown

@matteorossi-codes is attempting to deploy a commit to the Emmanuel's projects Team on Vercel.

A member of the Team first needs to authorize it.

getIssuerStats and getAssetIssuerHealthMetrics re-queried the database on
every call. Both are aggregate reads over payment rows that change slowly,
so they now sit behind a bounded TTL cache (60s, 500 entries, LRU with
oldest-first eviction), keyed per issuer and per merchant.

Expired entries are dropped on read, failed reads are never cached, and
logAssetIssuerVerification invalidates exactly the issuer and merchant keys
a verification write can affect. invalidateQueryCache() with no arguments
clears everything; a call that names neither key is a no-op, so a write
with no known issuer cannot wipe the cache for other callers.
Adds tests/asset-issuer-e2e.test.js (18 tests) driving the module through
its public entry points -- the AssetIssuerManager facade, the exported
singleton and the rate-limit factory -- rather than the per-class unit tests
already in src/lib/asset-issuer.test.js.

Horizon, Postgres, Redis and the logger are mocked, so the suite needs no
network or database. Covers the verification flow, on-chain issuer
confirmation, merchant issuer configuration, verification logging,
degradation and circuit-breaker recovery, dead-letter handling, and the
module surface.
…lopa#1053)

Adds nine Prometheus series and wires them into the module: verification
counts and latency, query-cache hit/miss/eviction/invalidation counters and
a size gauge, per-query duration, error-recovery outcomes, aggregated
circuit-breaker state gauges, and a dead-letter queue size gauge.

Per-issuer and per-transaction detail is deliberately not exported as label
values. Issuers, merchants and transaction hashes are unbounded, so
labelling by them would let a caller grow the series cardinality just by
varying an identifier. Verifications are labelled by result and operation
type, queries by a fixed query name, and the breaker gauges aggregate
across contexts. Per-issuer detail remains available through the existing
getCircuitBreakerMetrics() snapshot.

Tests assert the new series; the unit and e2e suites now mock ./metrics.js,
matching how the rest of the repo isolates the registry.
getPoolerStats() walks the pg pool, the query cache and the rate-limiter
window map on every call, so a metrics scrape at the standard interval
turns a cheap read into a full state walk per scrape. It is now fronted by
a bounded TTL cache (1s, configurable via DB_POOLER_STATS_CACHE_TTL_MS)
exposed as getCachedPoolerStats(), with invalidatePoolerStatsCache() and
getPoolerStatsCacheStats() alongside it.

getPoolerStats() itself is deliberately left uncached. Callers that need a
value guaranteed to reflect the current instant -- the circuit-breaker and
fallback-mode assertions in the pooler suite among them -- keep reading the
live snapshot, and skipCache bypasses the cache for one-off callers.

Four new series observe the cache: hits, misses, evictions and size. The
existing test mock for ./metrics.js is extended to cover them, since an
unlisted export would be undefined at runtime.
@matteorossi-codes
matteorossi-codes force-pushed the fix/issues-1050-1051-1053-1055 branch from dd65650 to b78977e Compare September 25, 2026 20:53
@emdevelopa
emdevelopa merged commit d5fa7b6 into emdevelopa:main Sep 25, 2026
1 of 2 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

2 participants