Skip to content

feat: scale improvements — O(1) reporting, Redis idempotency cache, centi-cent accrual#12

Merged
0-sayed merged 16 commits intomainfrom
feat/improvement
Apr 12, 2026
Merged

feat: scale improvements — O(1) reporting, Redis idempotency cache, centi-cent accrual#12
0-sayed merged 16 commits intomainfrom
feat/improvement

Conversation

@0-sayed
Copy link
Copy Markdown
Owner

@0-sayed 0-sayed commented Apr 12, 2026

Summary

  • Reporting at scale — replaces the O(n) ledger GROUP BY scan with an O(1) ledger_totals materialized table. Running totals are maintained transactionally on every deposit and purchase, so report generation is a single 4-row SELECT regardless of ledger size.
  • Idempotency latency — adds a global RedisModule (ioredis) and a Redis SETNX edge cache in PurchasesService. New requests set a 'processing' sentinel; completed purchases are cached as JSON. Redis unavailability falls through to the DB path without error. Sentinel is deleted on any transaction failure so clients can retry.
  • Fractional cent fairness — introduces wallets.fractional_balance (integer centi-cents) to eliminate systematic author under-payment. The 70% royalty remainder accumulates per-author and is swept into the integer balance whenever it crosses 100 centi-cents.

ADRs

  • ADR-008: Running Totals for Reporting
  • ADR-009: Redis Idempotency Cache
  • ADR-010: Centi-cent Fractional Accrual (supersedes ADR-005)

Test Plan

  • pnpm test — 57 unit tests passing
  • pnpm typecheck — no errors
  • pnpm lint — clean
  • Server smoke test: health, deposit, purchase, idempotency replay, report queue + poll all return correct responses
  • Schema migration applies cleanly via pnpm db:migrate

0-sayed added 13 commits April 12, 2026 06:38
Adds fractional_balance integer column to wallets (with non-negative CHECK)
to support centi-cent royalty accrual. Adds ledger_totals table keyed by
ledger_type for O(1) report lookups. Migration includes backfill from
existing ledger rows.
Adds a @global() NestJS module that provisions a REDIS_CLIENT (ioredis)
injection token, reading REDIS_HOST/REDIS_PORT from ConfigService.
Used as the edge cache for idempotency checks in PurchasesService (Task 7).

Also adds redis.module.ts to knip entry array so REDIS_CLIENT export is
recognized as an infrastructure token, not dead code.
Short-circuits repeated requests by checking Redis before the DB query.
On cache miss or Redis unavailability, falls through to the existing DB path.
Clear the 'processing' sentinel in the catch block of the DB transaction
so clients can retry after failures (insufficient funds, DB errors, etc.)
instead of receiving a permanent 409 for 24 hours.
- Skip royalty_author and royalty_platform ledger_totals upserts when
  the respective amount is 0, mirroring the existing ledger entry filter
- Skip platform wallet UPDATE when platformCut is 0 to avoid spurious
  updatedAt touches when no money moves to the platform
- Wrap JSON.parse of Redis cached value in try/catch; malformed values
  fall through to the DB path instead of throwing a 500
- Add comment explaining why all transaction errors clear the Redis
  sentinel (including business-rule failures like 402/403/404)
- Fix reports.processor.spec.ts groupBy assertion from toBeUndefined()
  tautology to not.toHaveBeenCalled() — the old assertion always passed
  regardless of implementation
@0-sayed
Copy link
Copy Markdown
Owner Author

0-sayed commented Apr 12, 2026

@coderabbitai review
@codoki review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

@0-sayed Sure, I'll review the changes in this PR right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

gemini-code-assist[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 12, 2026

Walkthrough

This pull request introduces a centi-cent fractional royalty accrual system, Redis-backed idempotency caching for purchases, and pre-aggregated financial reporting. New database schema includes a ledger_totals table and wallets.fractional_balance column. Architecture decision records document the centi-cent model (ADR-010), Redis caching strategy (ADR-009), and running-totals reporting (ADR-008). The PurchasesService now routes idempotency checks through Redis before database fallback, splits royalty amounts into whole cents and centi-cent remainders, and maintains accumulated fractions per author. ReportsProcessor reads pre-computed totals from ledger_totals instead of scanning and aggregating the ledger table. Tests are updated to verify centi-cent accrual behavior and Redis idempotency flows.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the three main features: O(1) reporting via ledger_totals, Redis idempotency caching, and centi-cent fractional accrual for royalties.
Description check ✅ Passed The description clearly relates to the changeset, covering all three major features with concrete details on implementation and test coverage expectations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

coderabbitai[bot]

This comment was marked as resolved.

0-sayed added 2 commits April 12, 2026 08:38
- RedisModule: implement OnModuleDestroy to close the ioredis client on
  shutdown, preventing TCP connection leaks (matches DbModule pattern)

- PurchasesService: fix stale 'processing' sentinel and blind DEL bugs:
  - track sentinelSet flag so only the request that set the sentinel
    deletes it (prevents clobbering another request's cached result)
  - wrap DB idempotency check in try/catch with conditional sentinel
    cleanup so errors before the transaction don't leave stale sentinels
  - skip sentinel DEL on PG_UNIQUE_VIOLATION (concurrent winner may have
    already cached the completed-purchase JSON)
  - add 3 targeted tests covering all three sentinel-ownership scenarios

- schema: add wallets_fractional_balance_lt_100 CHECK constraint and
  generate migration 0002_stormy_sasquatch.sql

- deps: update @nestjs/core, @nestjs/common, @nestjs/platform-express,
  @nestjs/swagger, @nestjs/testing to 11.1.18 and drizzle-orm to 0.45.2;
  add pnpm overrides for handlebars, flatted, lodash, smol-toml,
  serialize-javascript, multer, picomatch, and brace-expansion to
  resolve all 30 moderate+ audit vulnerabilities

- knip: remove @nestjs/bullmq and bullmq from ignoreDependencies now
  that updated packages resolve them correctly
Move the wallets_fractional_balance_lt_100 CHECK constraint from the
separate migration (0002_stormy_sasquatch) into the original migration
(0001_modern_rogue) alongside the fractional_balance column and its
non-negative check. No functional change — both constraints are applied
atomically in a single migration step now.
Copy link
Copy Markdown

@devin-ai-integration devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 1 new potential issue.

View 10 additional findings in Devin Review.

Open in Devin Review

Comment thread src/purchases/purchases.service.ts
When redis.set fails after a successful DB commit or on a DB cold-start
hit, the 'processing' sentinel was silently left in Redis for 24h.
Subsequent idempotent replays would hit the stale sentinel and get a
false 409 'still in flight' instead of the completed purchase result.

Fix: in both catch blocks, delete the sentinel when sentinelSet=true so
retries fall through to the DB path rather than hitting stale state.
@0-sayed 0-sayed merged commit 82f3d51 into main Apr 12, 2026
8 checks passed
@0-sayed 0-sayed deleted the feat/improvement branch April 12, 2026 07:08
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.

2 participants