Skip to main content

Performance Test Plan

Performance Test Plan: Drop — Fintech Payment AppPlan

Project: Drop — Remittance + QR Payments{{PROJECT_NAME}} Version: 1.0{{VERSION}} Date: 2026-02-23{{DATE}} Author: John (AI Director){{AUTHOR}} Status: Draft | In Review | Approved Reviewers: Alem Bašić (CEO){{REVIEWERS}}

Document History

Version Date Author Changes
0.1 2026-02-23{{DATE}} John{{AUTHOR}} Initial performance plan — targets from NFR-P01..P06 + api-benchmarks.test.tsdraft

1. Performance Testing Objectives

  1. Validate that Drop{{PROJECT_NAME}} meets the performance SLAs defined in the NFR document under normal operating conditions (200 concurrent users on SQLite MVP; 5,000+ on PostgreSQL Phase 1)
  2. Determine the SQLitemaximum concurrentcapacity userthe limitsystem can handle before migratingperformance to PostgreSQL (target: migrate at 200 concurrent)degrades
  3. Identify bottlenecks (bcryptdatabase, hashing,application, DB queries, rate limiter)infrastructure) before production release
  4. Establish a performance baseline for regression comparison in future releases

Reference NFRs: docs/BUSINESS-REQUIREMENTS/non-functional-requirements.md{{NFR_DOC_LINK}} — Section 2 (Performance)


2. Performance Requirements Reference

rounds;NFR-P03
Endpoint / Feature P50 P90 P95 P99 Error Rate NotesThroughput
GET /api/health (homepage) < 20ms{{P50}}ms < 50ms{{P90}}ms < 100ms{{P95}}ms < 200ms{{P99}}ms < 0.1%{{ERR}}% Health{{RPS}} check — fast alwaysreq/s
POST /api/auth/login (bcrypt) < 700ms{{P50}}ms < 900ms{{P90}}ms < 1,000ms{{P95}}ms < 1,200ms{{P99}}ms < 0.1%{{ERR}}% bcrypt{{RPS}} 12req/s
GET /api/{{RESOURCE}}< {{P50}}ms< {{P90}}ms< {{P95}}ms< {{P99}}ms< {{ERR}}%{{RPS}} req/s
POST /api/auth/register{{RESOURCE}} (bcrypt) < 700ms{{P50}}ms < 900ms{{P90}}ms < 1,000ms{{P95}}ms < 1,200ms{{P99}}ms < 0.1%{{ERR}}% Same{{RPS}} bcrypt costreq/s
POSTDatabase /api/transactions/remittancequery (P95) < 200ms{{DB_P95}}ms < 350ms < 500ms < 800ms < 0.1% NFR-P02
POST /api/transactions/qr-payment< 200ms< 350ms< 500ms< 800ms< 0.1%NFR-P02
GET /api/rates< 20ms< 50ms< 100ms< 200ms< 0.1%Cached / fast
GET /api/transactions< 50ms< 100ms< 200ms< 400ms< 0.1%NFR-P02
DB SELECT query< 10ms0%NFR-P04
DB INSERT query< 20ms0%NFR-P04
50 concurrent rate limit checks0%< 2,000ms total (NFR-P06)
Page load (First Contentful Paint) < 3,000ms{{FCP}}ms NFR-P01 (4G, cold cache)
Core Web Vitals LCP< 2,500msNFR-P05

3. Test Types

3.1 Load Testing — Normal Load Simulation

Objective: Confirm Dropthe system meets SLAs under expected normal load (Norwegian remittance corridor peak traffic) Normal load definition: 200{{NORMAL_USERS}} concurrent users (SQLite MVP limit) / ~20{{NORMAL_RPS}} requests/second Duration: 10{{LOAD_DURATION}} minutes (after 2-minute ramp-up) Pass criteria: All SLA targets in Section 2 met with ≤ 0.1%{{LOAD_ERROR_RATE}}% error rate

3.2 Stress Testing — Beyond Normal Capacity

Objective: Find SQLitethe concurrentmaximum usercapacity limit;and understand failure behavior to plan PostgreSQL migration Starting point: 50{{STRESS_START_USERS}} users, increasing by 25{{STRESS_INCREMENT}} users every 2 minutes{{STRESS_STEP}}min Stop condition: Error rate > 5% or P99 > 3,000ms{{STRESS_STOP_ERROR}}% or service crashes Pass criteria: System fails gracefully with(circuit breakers, meaningful error messages (429 rate limit, not 500);messages), data integrity maintained; no double-spend under stressmaintained

3.3 Spike Testing — Sudden Traffic Surges

Objective: SimulateVerify scenariossystem like marketing campaigns or media coverage causinghandles sudden usertraffic influxspikes without crashing Baseline: 20{{SPIKE_BASELINE}} users Spike to: 100{{SPIKE_PEAK}} users ({{SPIKE_MULTIPLIER}}× baseline) Spike duration: 2{{SPIKE_DURATION}} minutes Pass criteria: System recovers to baseline performance within 5{{SPIKE_RECOVERY}} minutes after spike ends; no data corruptionends

3.4 Endurance / Soak Testing — Sustained Load

Objective: Identify SQLite file lock issues, connection pool exhaustion, memoryresource leaks and degradation under sustained load Load level: 50{{SOAK_USERS}} users (25%{{SOAK_PERCENT}}% of 200normal concurrent limit)load) Duration: 2{{SOAK_DURATION}} hours Metrics to watch: SQLite write queue depth, memoryMemory usage trend, response time trendtrend, disk space, database connection pool Pass criteria: No upward trend in memory/response time over the soak period; no SQLite database locked errorsperiod

3.5 Scalability Testing — PhaseIncreasing 1Load

PostgreSQL Migration

Objective: Verify PostgreSQLlinear migration(or enables proportionalbetter) scaling foras Phaseinfrastructure 1 target (5,000+ users)grows Test: Step load from 200{{SCALE_START}} to 1,000 concurrent{{SCALE_MAX}} users onwhile PostgreSQLscaling stagingfrom {{MIN_INSTANCES}} to {{MAX_INSTANCES}} instances Pass criteria: P95Throughput staysincreases underproportionally 500ms(≥ as{{SCALE_EFFICIENCY}}% concurrentefficiency) userswith scale;added no P99 explosion Note: PostgreSQL not yet deployed — Phase 1 task. This plan establishes the baseline for comparison.instances


4. Load Profiles

Scenario Virtual Users Ramp-Up Hold Ramp-Down Think Time NotesIterations
Smoke (quick sanity) 5{{SMOKE_VU}} 10s{{SMOKE_RAMP}}s 1min{{SMOKE_HOLD}}min 10s30s 1s Per-deploy quick check1
Load (normal SQLite)normal) 200{{LOAD_VU}} 2min{{LOAD_RAMP}}min 10min{{LOAD_HOLD}}min 1min5min 2s{{THINK}}s NFR-S01 MVP target
Stress (find limit) Up{{STRESS_VU}} to 500(increasing) 2min steps{{STRESS_RAMP}}min Until failfailure 2s{{THINK}}s SQLite vs PostgreSQL
Spike (traffic burst) 100{{SPIKE_VU}} (instant) 0s 2min{{SPIKE_HOLD}}min 0s 1s{{THINK}}s Marketing/media event
Soak (stability) 50{{SOAK_VU}} 1min{{SOAK_RAMP}}min 2h{{SOAK_HOLD}}h 2min10min 3s{{THINK}}s Memory leak detection

Think time simulation: RealisticRandom between {{THINK_MIN}}s and {{THINK_MAX}}s (realistic user think time of 2–5s (Norwegian users reviewing remittance details before confirming)behavior)


5. Test Environment

NOTE:CRITICAL: Performance tests run againston a representativededicated stagingenvironment environment.with SQLitethe MVPsame limitsinfrastructure aresizing testedas on Fly.io staging (1× shared CPU, 256MB RAM). Phase 1 PostgreSQL tests require production-equivalent environment.production.

Component MVPTest Staging (Fly.io)Environment Phase 1 TargetProduction
App instances {{TEST_APP_INSTANCES}} shared-cpu-1x× (256MB){{TEST_INSTANCE_TYPE}} {{PROD_APP_INSTANCES}} dedicated-cpu-2x× {{PROD_INSTANCE_TYPE}}
Database SQLite on persistent volume{{TEST_DB_SPEC}} PostgreSQL (managed Fly.io){{PROD_DB_SPEC}}
RegionCache Stockholm (arn){{TEST_CACHE_SPEC}} Stockholm (arn){{PROD_CACHE_SPEC}}
CDN NoneDisabled (APIdirect only)hit) None (API) / Vercel (landing)Enabled

Load generator:generator infrastructure:

  • Tool: Vitest{{PERF_TOOL}} benchmarks (api-benchmarks.test.ts) for micro-benchmarks
  • Load generator for integration: k6 scripts in infrastructure/performance/
  • Load generator location: Local or CI runner{{LG_LOCATION}} (same region as Fly.ioapp, Stockholm)separate VPC)
  • Load generator sizing: {{LG_SPEC}}

6. Test Data Requirements

anonymizedproduction
Data Type Volume Generation Method
Users 500{{USER_COUNT}} npmScript run+ db:seed:perffactory
Recipients (remittance targets){{DATA_TYPE_1}} 200{{VOLUME}} SeedBulk import script
Merchants{{DATA_TYPE_2}} 50{{VOLUME}} SeedBulk import script
TransactionsSearch (historical)index 10,000Production-sized BulkSeeded insertfrom seed
Exchange rates6 corridors (fixed)Always seededdata

Database size at test time: ~50MB SQLite file{{DB_SIZE}}GB Data preparation: npmbash run db:seed:perfscripts/perf-seed.sh (estimated time: ~2 minutes){{SEED_TIME}}min)


7. Tools & Infrastructure

Tool Version Purpose Config
Vitest (bench){{PERF_TOOL}} 2.x{{VERSION}} Micro-benchmarks:Load bcrypt, DB queries, rate limitergeneration src/drop-app/__tests__/api-benchmarks.test.tsperf-tests/
k6{{MONITOR_TOOL}} Latest{{VERSION}} HTTPReal-time loadmetrics testingduring (concurrent users)test infrastructure/performance/k6-scripts/Dashboard link
Fly.io Metrics{{APM_TOOL}} {{VERSION}} Real-timeApplication CPU/memory/requestperformance metricsprofiling Fly.ioDashboard dashboardlink
SQLite EXPLAIN QUERY PLAN{{DB_MONITOR}} {{VERSION}} DBDatabase query analysis ViaDashboard flyctl ssh consolelink

Script location: src/drop-app/__tests__/api-benchmarks.test.ts{{PERF_SCRIPT_PATH}} (Vitest benchmarks)


8. Key Metrics to Capture

Response Time

Metric Description Tool
P50 (median) Half of requests faster than this Vitest bench / k6{{TOOL}}
P90 90% of requests faster than this k6{{TOOL}}
P95 95% of requests faster than this k6 (NFR gate){{TOOL}}
P99 99% of requests faster than this k6{{TOOL}}
Max Worst single request k6{{TOOL}}

Throughput

persecond
Metric Description
Requests/second Total API throughput at peak load
Transactions/second Successful remittance/QRbusiness transactionstransactions/second
Data transferredTotal MB/s in + out

Error Metrics

Metric Target
HTTP error rate (5xx) < 0.1%{{HTTP_ERR}}%
SQLiteTimeout database locked errorsrate 0%< (rate limit blocks excess concurrency){{TIMEOUT_ERR}}%
Connection timeoutrefused rate < 0.1%0%

Resource Utilization (Fly.io Metrics)

wait
Resource Warning Critical
App CPU > 70%{{CPU_WARN}}% > 90%{{CPU_CRIT}}%
App Memory > 80% (200MB / 256MB){{MEM_WARN}}% > 95%{{MEM_CRIT}}%
SQLiteDB write queueCPU > 50ms wait{{DB_CPU_WARN}}% > 200ms{{DB_CPU_CRIT}}%
DB Connections> {{DB_CONN_WARN}}% of pool> {{DB_CONN_CRIT}}%
Cache hit ratio< {{CACHE_HIT}}%< {{CACHE_CRIT}}%

Database Query Performance (NFR-P04)

Metric Target
SELECTAverage query P95time < 10ms
INSERT query P95< 20ms{{AVG_QUERY}}ms
Slow queries (> 100ms){{SLOW_THRESHOLD}}ms)< {{SLOW_COUNT}} per minute
Deadlocks 0 per minute under normal load

9. SLA Targets Per Endpoint

(from api-benchmarks.test.ts)
Endpoint Method P95 SLA Error Rate SLA Notes
/GET{{P95}}ms< {{ERR}}%Static or cached
/api/auth/login POST 1,000ms{{P95}}ms < 0.1%{{ERR}}% bcryptAuth 12critical roundspath
/api/auth/register{{RESOURCE}} POSTGET 1,000ms{{P95}}ms < 0.1%{{ERR}}% bcrypt 12 rounds{{NOTE}}
/api/transactions/remittance{{RESOURCE}} POST 500ms{{P95}}ms < 0.1%{{ERR}}% NFR-P02
/api/transactions/qr-paymentPOST500ms< 0.1%NFR-P02
/api/ratesGET100ms< 0.1%Cached
/api/healthGET100ms< 0.1%Always fast
DB SELECT10ms0%NFR-P04
DB INSERT20ms0%NFR-P04
50 concurrent rate limit2,000ms total0%NFR-P06{{NOTE}}

10. Baseline Establishment

(api-benchmarks.test.ts Results)

Baseline established:criteria: PhaseSystem 0in MVPstable onstate, localseeded devwith (SQLiteproduction-realistic in-memory)data, running load test scenario for {{BASELINE_DURATION}} minutes

Baseline metrics to record:

Metric Baseline Value Date Recorded Test
bcryptP95 hashlatency (register)key endpoints) ~800msTBD 2026-02-23api-benchmarks.test.ts{{DATE}}
bcryptThroughput verifyat (login)normal load ~800msTBD 2026-02-23api-benchmarks.test.ts{{DATE}}
RateError limitrate checkat (50normal concurrent)load ~1,800msTBD 2026-02-23api-benchmarks.test.ts{{DATE}}
DBCPU SELECTutilization at normal load ~5msTBD 2026-02-23api-benchmarks.test.ts{{DATE}}
DBMemory INSERTutilization at normal load ~10msTBD 2026-02-23api-benchmarks.test.ts
SHA-256 hash (reject baseline)~1ms2026-02-23auth.test.ts{{DATE}}

Regression threshold: Alert if any metric degrades > 15%{{REGRESSION_THRESHOLD}}% vs baseline


11. Test Execution Schedule

Run Type Trigger Environment Frequency
Benchmark smokeSmoke Every CI rundeployment Local / CI runnerStaging Per commitdeploy
api-benchmarks.test.tsLoad fullEvery PRCIPer PR
k6 load test(baseline) Release candidate Fly.io stagingProduction-sized Per release
k6 stress testStress BeforeMajor Phasefeature 1 PostgreSQL migrationreleases Fly.io stagingProduction-sized Quarterly
k6 soak testSoak Before Phasemajor 1 launchreleases Fly.io stagingProduction-sized Pre-launchPer release
ScalabilityInfrastructure changesProduction-sizedAs needed

12. Results Analysis Template

Test Run ID: {{RUN_ID}} Date: {{DATE}} Tester: Builder Agent + Validator Agent{{TESTER}} Scenario: Load (normal) / Stress / Spike / Soak{{SCENARIO}} Build / Version: v{{{VERSION}}

Endpoint P50 (ms) P90 (ms) P95 (ms) P99 (ms) Error % RPS Status vs SLA
/api/auth/login{{ENDPOINT}} Pass / Fail
/api/transactions/remittancePass / Fail
DB SELECTPass / Fail

Summary:

  • Peak concurrent users: {{PEAK_USERS}}
  • Peak throughput: {{PEAK_RPS}} req/s
  • Test duration: {{DURATION}} min
  • Total requests: {{TOTAL_REQUESTS}}
  • Total errors: {{TOTAL_ERRORS}}

Notable findings:

  • SQLite concurrent user limit reached at: {LIMIT} users{FINDING_1}}
  • Recommend{{FINDING_2}}
  • PostgreSQL
migration

Comparison at:to baseline:

  • {TRIGGER} concurrent users (per NFR-S02){COMPARISON}}

Recommendation: Pass / Fail / Conditional pass with PostgreSQL migration required for Phase 1{{CONDITIONS}}


13. Bottleneck Identification Process

Drop-specific bottleneck investigation order:

1. Check bcryptapplication timingerror ratebcryptIs >the 1,000msapp P95returning =errors, configor issuejust (rounds too high OR long password pre-check missing)slow?
2. Check SQLiteP99 writevs queueP50 spread"databaseLarge is locked" errorsspread = concurrentoutlier writerequests saturation(DB queries, triggerlocks, PostgreSQLGC migrationpauses)
3. Check rateCPU limiter DB table → rate_limit_requests table query slow = add index or migrate to Redis
4. Check transaction lock → SELECT FOR UPDATE timeout = deadlock in double-spend prevention
5. Check mock BaaS response → mock service timeout = CI/staging environment issue
6. Check Fly.io metricssaturation → CPU > 90%80% sustained = scalecompute upbottleneck
instance;4. Check memory → Memory >growing 200MBduring test = Node.js leak check/ GC pressure
5. Check DB metrics → Slow queries, high connections, deadlocks
6. Check cache hit rate → Low cache hit = DB overloaded unnecessarily
7. Check external calls → Third-party API latency or rate limiting
8. Check network → Bandwidth saturation, packet loss

14. Remediation Tracking

Issue Found In Severity Root Cause Fix Fixed In Verified
In-memory rate limiter reset on restart{{ISSUE}} Phase 0 load test{{SCENARIO}} High{{SEVERITY}} In-memory Map → DB-backed{{CAUSE}} Phase 0.5 security hardening{{FIX}} v0.5.0{{VERSION}} db.test.ts
Long password bcrypt DoS (10KB)Phase 0 security auditHighNo max length before bcrypt1,000 char limit in validationv0.5.0validation.test.ts{{DATE}}


Approval

Role Name Date Signature
Author John (AI Director) 2026-02-23 Approved (AI)
QA LeadReviewer Validator Agent 2026-02-23 Approved (AI)
AI Director (John)Approver John 2026-02-23Approved
CEO (Alem)Alem BašićTBD