Class PostgresIdempotencyStore

java.lang.Object
com.tnsai.idempotency.PostgresIdempotencyStore
All Implemented Interfaces:
IdempotencyStore

public final class PostgresIdempotencyStore extends Object implements IdempotencyStore
Durable IdempotencyStore backed by a JDBC database (issue #91). Targets PostgreSQL but works with any database whose dialect handles TIMESTAMP WITH TIME ZONE, TEXT, and standard SQL UPDATE / INSERT: H2 in PostgreSQL mode, CockroachDB, any modern Postgres-compatible engine.

Strong durability: an entry stored here survives every kind of process churn that InMemoryIdempotencyStore would lose. The trade-off is one round-trip per get / put -- fine for moderate traffic, prefer RedisIdempotencyStore for hot loops.

Schema

The store expects a table named idempotency_entries (override via the table-name constructor):
 CREATE TABLE idempotency_entries (
     idempotency_key VARCHAR(512) PRIMARY KEY,
     entry_json      TEXT NOT NULL,
     expires_at      TIMESTAMP WITH TIME ZONE NOT NULL
 );
 CREATE INDEX idx_idempotency_expires_at ON idempotency_entries (expires_at);
 

Note that the primary-key column is idempotency_key rather than the bare word key -- key is reserved in many SQL dialects (H2 strictly, MySQL contextually) and quoting it would force dialect-specific identifier escaping.

createTableIfMissing() runs the equivalent DDL for tests / quick-starts. Production deployments typically run schema migration via Flyway / Liquibase and never call that method.

Expiry

Lazy: every get(String) filters on expires_at > now(). Expired rows linger in storage until the operator runs purgeExpired() (manual or scheduled) -- this keeps the hot path single-row and lock-free at the cost of letting the table grow until purged.
Since:
0.10.0 (issue #91 closeout)
  • Field Details

    • DEFAULT_TABLE

      public static final String DEFAULT_TABLE
      Default table name. Override via the two-arg constructor.
      See Also:
  • Constructor Details

    • PostgresIdempotencyStore

      public PostgresIdempotencyStore(DataSource dataSource)
    • PostgresIdempotencyStore

      public PostgresIdempotencyStore(DataSource dataSource, String table)
  • Method Details

    • get

      public Optional<IdempotencyEntry> get(String key)
      Description copied from interface: IdempotencyStore
      Look up a cached entry. Returns empty when no entry exists OR when the entry's TTL has elapsed.
      Specified by:
      get in interface IdempotencyStore
    • put

      public void put(String key, IdempotencyEntry entry, Duration ttl)
      Description copied from interface: IdempotencyStore
      Store an entry under key with the given TTL. Subsequent IdempotencyStore.get(java.lang.String) calls within the TTL window return this entry. Overwrites any prior entry for the same key.
      Specified by:
      put in interface IdempotencyStore
    • invalidate

      public void invalidate(String key)
      Description copied from interface: IdempotencyStore
      Remove the entry for key if any. No-op if the key is not present.
      Specified by:
      invalidate in interface IdempotencyStore
    • size

      public int size()
      Description copied from interface: IdempotencyStore
      Approximate entry count. Intended for monitoring / smoke-testing ("cache has grown to 10M entries, check your TTL"). Implementations may return an eventually-consistent snapshot.
      Specified by:
      size in interface IdempotencyStore
    • purgeExpired

      public int purgeExpired()
      Delete rows whose expires_at is already in the past. Returns the number of rows removed. Run periodically (cron, a scheduled framework job) to keep the table from growing unboundedly.
    • createTableIfMissing

      public void createTableIfMissing()
      Idempotently create the schema if it doesn't exist. Useful for tests and quick-start deployments; production environments typically run schema migration via Flyway / Liquibase and skip this method.