·
7 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
0.9.0 (2026-07-31)
This release introduces a runtime-controlled, Redis-backed write path for the fingerprint, locker, hash-table, and vault tables. When enabled, writes are first persisted to Redis and asynchronously replayed to Postgres by the drainer, while reads consult Redis first and fall back to Postgres on misses. This decouples request handling from the Postgres write path and supports low-downtime database maintenance. It also adds new operation-level metrics across the vault.
Features
- entity: Add explicit POST /entity provisioning endpoint (#172) (
c5485d9) by @ShankarSinghC - kv:
- KV framework introduction + fingerprint table movement for kv (#177) (
d3c0a14) by @su-shivanshmathur - Add locker kv support (#182) (
22207b0) by @hrithikesh026 - Add hash table kv support (#183) (
6c16b80) by @hrithikesh026 - Add vault kv support (#181) (
e437e03) by @hrithikesh026 - Add reverse lookup kv support (#180) (
7e91be4) by @hrithikesh026 - Validate runtime storage transitions (#187) (
b64cf2f) by @hrithikesh026
- KV framework introduction + fingerprint table movement for kv (#177) (
- storage: Introduce reverse_lookup table (#174) (
3c1a9a5) by @hrithikesh026 - Allow reads from read replica for merchant and entity tables based on runtime config (#171) (
9260ba2) by @SanchithHegde - Introduce metrics for all major operations (#184) (
1aad793) by @SanchithHegde
Refactors
- deps: Use metrics_utils to setup metrics pipeline (#190) (
5d2103b) by @SanchithHegde - storage: Split storage into single-query primitives with a domain composition layer (#170) (
45f44c2) by @ShankarSinghC - Change hash_table primary key to data_hash (#176) (
400a1f1) by @hrithikesh026
Miscellaneous Tasks
- kv: Refactor wrapper delete behaviour (#188) (
ae5f055) by @hrithikesh026 - migrations: Add migration to create hash ID based locker index (#179) (
1bb0741) by @SanchithHegde - runtime_config: Add support for optional static headers in runtime config requests (#178) (
77c10a7) by @su-shivanshmathur
Database Migrations
CREATE TABLE IF NOT EXISTS reverse_lookup (
lookup_id VARCHAR NOT NULL PRIMARY KEY,
secondary_key VARCHAR NOT NULL,
partition_key VARCHAR NOT NULL,
source VARCHAR NOT NULL,
updated_by VARCHAR(32) NOT NULL
);
-- Add `updated_by` to the KV-participating tables.
-- Tracks which storage backend wrote a row (`postgres_only` or `redis_kv`).
ALTER TABLE fingerprint ADD COLUMN IF NOT EXISTS updated_by VARCHAR(32);
ALTER TABLE hash_table ADD COLUMN IF NOT EXISTS updated_by VARCHAR(32);
ALTER TABLE locker ADD COLUMN IF NOT EXISTS updated_by VARCHAR(32);
ALTER TABLE vault ADD COLUMN IF NOT EXISTS updated_by VARCHAR(32);
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS hash_table_hash_id_key_idx ON hash_table (hash_id);
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS hash_table_data_hash_pkey_idx ON hash_table (data_hash);
SET LOCAL lock_timeout = '2s';
SET LOCAL statement_timeout = '5s';
ALTER TABLE hash_table
DROP CONSTRAINT hash_table_pkey,
DROP CONSTRAINT hash_table_data_hash_key,
ADD CONSTRAINT hash_table_pkey PRIMARY KEY USING INDEX hash_table_data_hash_pkey_idx,
ADD CONSTRAINT hash_table_hash_id_key UNIQUE USING INDEX hash_table_hash_id_key_idx;
CREATE UNIQUE INDEX CONCURRENTLY IF NOT EXISTS locker_hash_id_merchant_id_customer_id_idx ON locker (hash_id, merchant_id, customer_id);Configuration Changes
diff --git a/config/config.example.toml b/config/config.example.toml
index 7d73d5aaa9ec..867d6c66ca72 100644
--- a/config/config.example.toml
+++ b/config/config.example.toml
@@ -16,28 +16,37 @@ duration = 60 # duration to rate limit the delete api (in sec)
tti = 7200 # Idle time after a get/insert of a cache entry to free the cache (in secs)
max_capacity = 5000 # Max capacity of a single table cache
[database]
username = "sam" # username for the database
password = "damn" # password of the database
host = "localhost" # the host where the database is hosted on
port = 5432 # the port of the database
dbname = "locker" # the name of the database where the cards are stored
+# Optional read replica
+# [read_replica]
+# username = "sam" # username for the read replica database
+# password = "damn" # password of the read replica database
+# host = "localhost" # the host where the read replica is hosted on
+# port = 5432 # the port of the read replica database
+# dbname = "locker" # the name of the read replica database
+# pool_size = 10 # optional: pool size for the read replica connection pool
+
[secrets]
locker_private_key = "" # the locker private key to used used and the private key present with the tenant
[tenant_secrets]
# configure master_key and public_key for each tenant
# master_key - used for database encryption this could be aes encrypted by key custodian
# public_key - used for signature verification and encryption of response payload which is sent back to tenant
-hyperswitch = { master_key = "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", public_key = "", schema = "public" }
+hyperswitch = { master_key = "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308", public_key = "", schema = "public", redis_key_prefix = "hyperswitch" }
# To protect secret/sensitive values like:
# - database.passwod
# - secrets.master_key
# - secrets.tenant_public_key
# - secrets.locker_private_key
#
# Following possible encryption schemes are used, of of them are mutually exclusive, the sections are:
# - aws_kms (AWS KMS Symmetric Encryption)
# - hashi_corp_vault (HashiCorp Vault Secrets Engine KV version 2)
@@ -97,26 +106,65 @@ stream_read_count = 1 # entries read per stream read
auto_pipeline = true # automatically pipeline commands
broadcast_channel_capacity = 32 # pub/sub broadcast channel capacity
disable_auto_backpressure = false # disable automatic backpressure
max_in_flight_commands = 5000 # max in-flight commands before backpressure (0 = disabled)
default_command_timeout = 30 # command timeout (seconds)
unresponsive_timeout = 10 # mark a connection unresponsive after (seconds)
unresponsive_check_interval = 2 # how often to check for unresponsiveness (seconds)
max_feed_count = 200 # max commands fed to the server at once
max_failure_threshold_seconds = 5 # max seconds Redis may be unreachable before it's failed
+# KV (write-through Redis) configuration — global drainer settings (only used with the `kv` feature)
+[kv]
+drainer_stream_suffix = "DRAINER_STREAM" # suffix for the drainer stream name
+drainer_num_partitions = 16 # number of shard partitions (must be > 0)
+ttl_for_kv = 900 # TTL for KV entries in Redis (seconds)
+
+# KV enablement is controlled SOLELY at runtime via the runtime-config endpoint (see
+# [runtime_config] below). There is no TOML fallback — a single source of truth avoids drift.
+# When the endpoint is disabled or unreachable the service fails closed to "disabled"
+# (all reads/writes go to Postgres). The endpoint returns the config object as its `value`:
+# {"enable_kv": "disabled" | "enabled" | "soft_kill", "use_replica": true | false}
+# "enabled" → write-through Redis (writes → Redis + drainer; reads → Redis-first)
+# "soft_kill" → gradual rollout (inserts → Postgres, reads → Redis-first)
+
+# Per-tenant `redis_key_prefix` (under `[tenant_secrets.<id>]`) MUST be non-empty
+# and unique across tenants whenever KV is enabled, otherwise the service fails to
+# start. The drainer must consume the prefixed
+# `{prefix}:{shard_N}_DRAINER_STREAM` for each tenant.
+
# Metrics configuration: export metrics for monitoring.
# - disabled: no metrics exported
# - otlp: configure endpoint, endpoint_timeout_secs, metrics_export_interval_secs
# - prometheus: configure host, port (scraped at /metrics on a separate HTTP server)
[metrics]
mode = "otlp" # Options: "disabled", "otlp", "prometheus"
# When mode is "otlp", configure endpoint, endpoint_timeout_secs and metrics_export_interval_secs:
endpoint = "http://localhost:4317" # OTLP gRPC endpoint
endpoint_timeout_secs = 10 # OTLP exporter connection timeout (seconds)
metrics_export_interval_secs = 60 # How often to export metrics (seconds)
+background_metrics_collection_interval_secs = 15 # How often to collect metrics in the background (in secs)
# When mode is "prometheus", configure host and port:
# host = "127.0.0.1" # IP address the Prometheus HTTP server binds to
# port = 9090 # Port the Prometheus HTTP server listens on
+# background_metrics_collection_interval_secs = 15 # How often to collect metrics in the background (in secs)
+
+# Runtime configuration endpoint
+# [runtime_config]
+# mode = "enabled"
+# refresh_interval_seconds = 30
+
+# Endpoint needs to be specified if mode is "enabled"
+# [runtime_config.endpoint]
+# base_url = "http://localhost:8090/configs"
+# api_key = "test_admin"
+# path = ""
+#
+# The endpoint at `{base_url}{path}` returns `{"key": "...", "value": "<config json string>"}`,
+# where `value` is a JSON string holding the flat config object, e.g.:
+# {"key":"locker.configs","value":"{\"enable_kv\":\"enabled\",\"use_replica\":false}"}
+# Optional static headers sent on every runtime config pull request (omit entirely to send none)
+# [runtime_config.endpoint.headers]
+# accept = "application/json"Full Changelog: v0.8.0...v0.9.0