Performance Test Plan: Drop — Fintech Payment App
Performance Test Plan: Drop — Fintech Payment App
Project: Drop — Remittance + QR Payments Version: 1.0 Date: 2026-02-23 Author: John (AI Director) Status: Approved Reviewers: Alem Bašić (CEO)
Document History
| Version | Date | Author | Changes |
|---|---|---|---|
| 0.1 | 2026-02-23 | John | Initial performance plan — targets from NFR-P01..P06 + api-benchmarks.test.ts |
1. Performance Testing Objectives
- Validate that Drop 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)
- Determine the SQLite concurrent user limit before migrating to PostgreSQL (target: migrate at 200 concurrent)
- Identify bottlenecks (bcrypt hashing, DB queries, rate limiter) before production release
- Establish a performance baseline for regression comparison in future releases
Reference NFRs: docs/BUSINESS-REQUIREMENTS/non-functional-requirements.md — Section 2 (Performance)
2. Performance Requirements Reference
| Endpoint / Feature | P50 | P90 | P95 | P99 | Error Rate | Notes |
|---|---|---|---|---|---|---|
GET /api/health |
< 20ms | < 50ms | < 100ms | < 200ms | < 0.1% | Health check — fast always |
POST /api/auth/login (bcrypt) |
< 700ms | < 900ms | < 1,000ms | < 1,200ms | < 0.1% | bcrypt 12 rounds; NFR-P03 |
POST /api/auth/register (bcrypt) |
< 700ms | < 900ms | < 1,000ms | < 1,200ms | < 0.1% | Same bcrypt cost |
POST /api/transactions/remittance |
< 200ms | < 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 | — | — | < 10ms | — | 0% | NFR-P04 |
| DB INSERT query | — | — | < 20ms | — | 0% | NFR-P04 |
| 50 concurrent rate limit checks | — | — | — | — | 0% | < 2,000ms total (NFR-P06) |
| Page load (First Contentful Paint) | — | — | < 3,000ms | — | — | NFR-P01 (4G, cold cache) |
| Core Web Vitals LCP | — | — | < 2,500ms | — | — | NFR-P05 |
3. Test Types
3.1 Load Testing — Normal Load Simulation
Objective: Confirm Drop meets SLAs under expected normal load (Norwegian remittance corridor peak traffic) Normal load definition: 200 concurrent users (SQLite MVP limit) / ~20 requests/second Duration: 10 minutes (after 2-minute ramp-up) Pass criteria: All SLA targets in Section 2 met with ≤ 0.1% error rate
3.2 Stress Testing — Beyond Normal Capacity
Objective: Find SQLite concurrent user limit; understand failure behavior to plan PostgreSQL migration Starting point: 50 users, increasing by 25 users every 2 minutes Stop condition: Error rate > 5% or P99 > 3,000ms or service crashes Pass criteria: System fails gracefully with meaningful error messages (429 rate limit, not 500); data integrity maintained; no double-spend under stress
3.3 Spike Testing — Sudden Traffic Surges
Objective: Simulate scenarios like marketing campaigns or media coverage causing sudden user influx Baseline: 20 users Spike to: 100 users (5× baseline) Spike duration: 2 minutes Pass criteria: System recovers to baseline performance within 5 minutes after spike ends; no data corruption
3.4 Endurance / Soak Testing — Sustained Load
Objective: Identify SQLite file lock issues, connection pool exhaustion, memory leaks under sustained load Load level: 50 users (25% of 200 concurrent limit) Duration: 2 hours Metrics to watch: SQLite write queue depth, memory usage trend, response time trend Pass criteria: No upward trend in response time over soak period; no SQLite database locked errors
3.5 Scalability Testing — Phase 1 PostgreSQL Migration
Objective: Verify PostgreSQL migration enables proportional scaling for Phase 1 target (5,000+ users) Test: Step load from 200 to 1,000 concurrent users on PostgreSQL staging Pass criteria: P95 stays under 500ms as concurrent users scale; no P99 explosion Note: PostgreSQL not yet deployed — Phase 1 task. This plan establishes the baseline for comparison.
4. Load Profiles
| Scenario | Virtual Users | Ramp-Up | Hold | Ramp-Down | Think Time | Notes |
|---|---|---|---|---|---|---|
| Smoke (quick sanity) | 5 | 10s | 1min | 10s | 1s | Per-deploy quick check |
| Load (normal SQLite) | 200 | 2min | 10min | 1min | 2s | NFR-S01 MVP target |
| Stress (find limit) | Up to 500 | 2min steps | Until fail | — | 2s | SQLite vs PostgreSQL |
| Spike (traffic burst) | 100 (instant) | 0s | 2min | 0s | 1s | Marketing/media event |
| Soak (stability) | 50 | 1min | 2h | 2min | 3s | Memory leak detection |
Think time simulation: Realistic user think time of 2–5s (Norwegian users reviewing remittance details before confirming)
5. Test Environment
NOTE: Performance tests run against a representative staging environment. SQLite MVP limits are tested on Fly.io staging (1× shared CPU, 256MB RAM). Phase 1 PostgreSQL tests require production-equivalent environment.
| Component | MVP Staging (Fly.io) | Phase 1 Target |
|---|---|---|
| App instances | 1× shared-cpu-1x (256MB) | 2× dedicated-cpu-2x |
| Database | SQLite on persistent volume | PostgreSQL (managed Fly.io) |
| Region | Stockholm (arn) | Stockholm (arn) |
| CDN | None (API only) | None (API) / Vercel (landing) |
Load generator:
- Tool: Vitest benchmarks (
api-benchmarks.test.ts) for micro-benchmarks - Load generator for integration: k6 scripts in
infrastructure/performance/ - Load generator location: Local or CI runner (same region as Fly.io Stockholm)
6. Test Data Requirements
| Data Type | Volume | Generation Method |
|---|---|---|
| Users | 500 | npm run db:seed:perf |
| Recipients (remittance targets) | 200 | Seed script |
| Merchants | 50 | Seed script |
| Transactions (historical) | 10,000 | Bulk insert seed |
| Exchange rates | 6 corridors (fixed) | Always seeded |
Database size at test time: ~50MB SQLite file
Data preparation: npm run db:seed:perf (estimated time: ~2 minutes)
7. Tools & Infrastructure
| Tool | Version | Purpose | Config |
|---|---|---|---|
| Vitest (bench) | 2.x | Micro-benchmarks: bcrypt, DB queries, rate limiter | src/drop-app/__tests__/api-benchmarks.test.ts |
| k6 | Latest | HTTP load testing (concurrent users) | infrastructure/performance/k6-scripts/ |
| Fly.io Metrics | — | Real-time CPU/memory/request metrics | Fly.io dashboard |
| SQLite EXPLAIN QUERY PLAN | — | DB query analysis | Via flyctl ssh console |
Script location: src/drop-app/__tests__/api-benchmarks.test.ts (Vitest benchmarks)
8. Key Metrics to Capture
Response Time
| Metric | Description | Tool |
|---|---|---|
| P50 (median) | Half of requests faster than this | Vitest bench / k6 |
| P90 | 90% of requests faster than this | k6 |
| P95 | 95% of requests faster than this | k6 (NFR gate) |
| P99 | 99% of requests faster than this | k6 |
| Max | Worst single request | k6 |
Throughput
| Metric | Description |
|---|---|
| Requests/second | Total API throughput at peak load |
| Transactions/second | Successful remittance/QR transactions per second |
Error Metrics
| Metric | Target |
|---|---|
| HTTP error rate (5xx) | < 0.1% |
| SQLite database locked errors | 0% (rate limit blocks excess concurrency) |
| Connection timeout rate | < 0.1% |
Resource Utilization (Fly.io Metrics)
| Resource | Warning | Critical |
|---|---|---|
| App CPU | > 70% | > 90% |
| App Memory | > 80% (200MB / 256MB) | > 95% |
| SQLite write queue | > 50ms wait | > 200ms wait |
Database Query Performance (NFR-P04)
| Metric | Target |
|---|---|
| SELECT query P95 | < 10ms |
| INSERT query P95 | < 20ms |
| Slow queries (> 100ms) | 0 per minute under normal load |
9. SLA Targets Per Endpoint (from api-benchmarks.test.ts)
| Endpoint | Method | P95 SLA | Error Rate SLA | Notes |
|---|---|---|---|---|
/api/auth/login |
POST | 1,000ms | < 0.1% | bcrypt 12 rounds |
/api/auth/register |
POST | 1,000ms | < 0.1% | bcrypt 12 rounds |
/api/transactions/remittance |
POST | 500ms | < 0.1% | NFR-P02 |
/api/transactions/qr-payment |
POST | 500ms | < 0.1% | NFR-P02 |
/api/rates |
GET | 100ms | < 0.1% | Cached |
/api/health |
GET | 100ms | < 0.1% | Always fast |
| DB SELECT | — | 10ms | 0% | NFR-P04 |
| DB INSERT | — | 20ms | 0% | NFR-P04 |
| 50 concurrent rate limit | — | 2,000ms total | 0% | NFR-P06 |
10. Baseline Establishment (api-benchmarks.test.ts Results)
Baseline established: Phase 0 MVP on local dev (SQLite in-memory)
| Metric | Baseline Value | Date Recorded | Test |
|---|---|---|---|
| bcrypt hash (register) | ~800ms | 2026-02-23 | api-benchmarks.test.ts |
| bcrypt verify (login) | ~800ms | 2026-02-23 | api-benchmarks.test.ts |
| Rate limit check (50 concurrent) | ~1,800ms | 2026-02-23 | api-benchmarks.test.ts |
| DB SELECT | ~5ms | 2026-02-23 | api-benchmarks.test.ts |
| DB INSERT | ~10ms | 2026-02-23 | api-benchmarks.test.ts |
| SHA-256 hash (reject baseline) | ~1ms | 2026-02-23 | auth.test.ts |
Regression threshold: Alert if any metric degrades > 15% vs baseline
11. Test Execution Schedule
| Run Type | Trigger | Environment | Frequency |
|---|---|---|---|
| Benchmark smoke | Every CI run | Local / CI runner | Per commit |
| api-benchmarks.test.ts full | Every PR | CI | Per PR |
| k6 load test | Release candidate | Fly.io staging | Per release |
| k6 stress test | Before Phase 1 PostgreSQL migration | Fly.io staging | Quarterly |
| k6 soak test | Before Phase 1 launch | Fly.io staging | Pre-launch |
12. Results Analysis Template
Test Run ID: {RUN_ID} Date: {DATE} Tester: Builder Agent + Validator Agent Scenario: Load (normal) / Stress / Spike / Soak Build / Version: v{VERSION}
| Endpoint | P50 (ms) | P90 (ms) | P95 (ms) | P99 (ms) | Error % | RPS | Status vs SLA |
|---|---|---|---|---|---|---|---|
/api/auth/login |
— | — | — | — | — | — | Pass / Fail |
/api/transactions/remittance |
— | — | — | — | — | — | Pass / Fail |
| DB SELECT | — | — | — | — | — | — | Pass / 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
- Recommend PostgreSQL migration at: {TRIGGER} concurrent users (per NFR-S02)
Recommendation: Pass / Fail / Conditional pass with PostgreSQL migration required for Phase 1
13. Bottleneck Identification Process
Drop-specific bottleneck investigation order:
1. Check bcrypt timing → bcrypt > 1,000ms P95 = config issue (rounds too high OR long password pre-check missing)
2. Check SQLite write queue → "database is locked" errors = concurrent write saturation → trigger PostgreSQL migration
3. Check rate 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 metrics → CPU > 90% = scale up instance; Memory > 200MB = Node.js leak check
14. Remediation Tracking
| Issue | Found In | Severity | Root Cause | Fix | Fixed In | Verified |
|---|---|---|---|---|---|---|
| In-memory rate limiter reset on restart | Phase 0 load test | High | In-memory Map → DB-backed | Phase 0.5 security hardening | v0.5.0 | db.test.ts |
| Long password bcrypt DoS (10KB) | Phase 0 security audit | High | No max length before bcrypt | 1,000 char limit in validation | v0.5.0 | validation.test.ts |
Related Documents
Approval
| Role | Name | Date | Signature |
|---|---|---|---|
| Author | John (AI Director) | 2026-02-23 | Approved (AI) |
| QA Lead | Validator Agent | 2026-02-23 | Approved (AI) |
| AI Director (John) | John | 2026-02-23 | Approved |
| CEO (Alem) | Alem Bašić | TBD |