Lewati ke konten utama

OTP Runtime: Redis vs PostgreSQL

Scope: Dokumen ini menjelaskan pemilihan store dari sisi merchant_core_api. Sejak schema iam di-drop dari db_kesles_merchant, persistensi OTP runtime dimiliki oleh auth_service (port 8081, db_kesles_merchant_auth). auth_service punya kedua backend — Redis (internal/service/redis_otp.go) dan PostgreSQL (internal/store/auth_otp.go, tabel auth.otp_challenges + auth.otp_request_locks) — tetapi tidak memakai file bernama postgres_otp_repository.go dan tidak membaca env var OTP_RUNTIME_STORE. OTP_RUNTIME_STORE hanya dibaca oleh merchant_core_api untuk memilih repository mana yang dipakai.

This document compares two approaches for storing runtime OTP data in Kesles Merchant.

Discussion scope:

  • OTP challenge
  • resend cooldown
  • OTP request rate limit
  • attempts increment on failed verification

Current Implementation Status

The currently active implementation uses Redis via:

  • redis_repository.go

A PostgreSQL-based alternative is also available via:

  • postgres_otp_repository.go

This alternative no longer queries any table inside merchant_core_api's own database. Since the iam schema was dropped from db_kesles_merchant, postgres_otp_repository.go now delegates every OTP runtime operation to auth_service over HTTP (authClient.OTPReserveRequest, OTPSaveChallenge, OTPGetChallenge, OTPIncrementAttempts, OTPDeleteChallenge). auth_service (port 8081, db_kesles_merchant_auth) owns the persisted OTP runtime data.

Notes:

  • the active store is selected by the OTP_RUNTIME_STORE env var (default redis)
  • the PostgreSQL path requires auth_service to be configured and reachable

Equivalent Functions

Both Redis and PostgreSQL now have the same functions at the repository level:

  • ReserveRequest(...)
  • SaveChallenge(...)
  • GetChallenge(...)
  • IncrementAttempts(...)
  • DeleteChallenge(...)

Redis

Active file:

  • redis_repository.go

How it works:

  • the cooldown is stored in a TTL key
  • the rate limit is counted via a TTL counter
  • the OTP challenge is stored in a hash per request_id
  • challenge expiry is helped by Redis TTL

Key patterns:

  • auth:otp:cooldown:{phone}
  • auth:otp:rate:{phone}
  • auth:otp:challenge:{request_id}

Pros:

  • fast
  • native TTL
  • well suited for ephemeral data
  • simple cooldown/rate-limit implementation
  • lighter PostgreSQL load

Cons:

  • needs additional Redis infrastructure
  • OTP state is split between PostgreSQL and Redis
  • debugging sometimes requires access to two stores

PostgreSQL

Alternative file:

  • postgres_otp_repository.go

How it works:

  • all five repository functions proxy to auth_service over HTTP via authClient
  • auth_service persists cooldown, request window, request count, and the OTP challenge in its own database (db_kesles_merchant_auth)
  • ReserveRequest(...) calls authClient.OTPReserveRequest(...), which returns a rate-limit error (HTTP 429) when the limit is exceeded
  • the consistency guarantees (transaction and row lock) live inside auth_service, not in merchant_core_api

Pros:

  • a single primary data source in PostgreSQL (owned by auth_service)
  • easier manual audit via SQL on db_kesles_merchant_auth
  • no need for an extra Redis service

Cons:

  • heavier for very ephemeral data
  • expiry cleanup must be designed yourself
  • rate limit and cooldown are more complex
  • more query load on PostgreSQL

Quick Comparison

Redis is more suitable when:

  • OTP traffic is fairly high
  • you want native, fast TTL
  • you accept an additional cache/storage component

PostgreSQL is more suitable when:

  • you want to reduce infrastructure components
  • traffic is still low to medium
  • you accept the trade-off of heavier queries

Practical Recommendation

For the current repo state:

  • keep Redis as the active OTP runtime implementation (default)
  • only use the PostgreSQL OTP runtime if you want simpler infrastructure
  • the PostgreSQL path now requires auth_service to be configured and reachable, since persistence happens there

Steps If You Want To Switch

The minimal steps are:

  1. set OTP_RUNTIME_STORE=postgres (read by selectOTPRepository in internal/httpapi/server.go)
  2. make sure auth_service is configured and reachable (the PostgreSQL path proxies all OTP operations to it)
  3. re-test cooldown, rate limit, and OTP verify