Class: Pgtk::Stash

Inherits:
Object
  • Object
show all
Defined in:
lib/pgtk/stash.rb

Overview

Database query cache implementation.

Provides a caching layer for PostgreSQL queries, automatically invalidating the cache when tables are modified. Read queries are cached while write queries bypass the cache and invalidate related cached entries.

Thread-safe with read-write locking.

The implementation is very naive! Use it at your own risk.

Author

Yegor Bugayenko ([email protected])

Copyright

Copyright (c) 2019-2026 Yegor Bugayenko

License

MIT

Examples:

Basic usage

pool = Pgtk::Pool.new(...)
stash = Pgtk::Stash.new(pool, cap: 1000, refill: 30)
stash.start!
result = stash.exec('SELECT * FROM users WHERE id = $1', [42])

Instance Method Summary collapse

Constructor Details

#initialize(pool, stash: { queries: {}, tables: {}, table_mod: {}, table_inflight: {} }, loog: Loog::NULL, entrance: Concurrent::ReentrantReadWriteLock.new, refill: 16, delay: 0, maxqueue: 128, threads: 4, cap: 10_000, capping: 60, retire: 15 * 60, retirement: 60) ⇒ Stash

Initialize a new Stash with query caching.

Set any of the intervals to nil to disable the cron.

Parameters:

  • pool (Object) —

    The underlying connection pool

  • stash (Hash) (defaults to: { queries: {}, tables: {}, table_mod: {}, table_inflight: {} }) —

    Internal cache structure

  • refill (Float) (defaults to: 16) —

    Interval in seconds between background refill tasks

  • delay (Float) (defaults to: 0) —

    A pause in seconds before making a refill

  • maxqueue (Integer) (defaults to: 128) —

    Maximum number of refilling tasks in the thread pool queue

  • threads (Integer) (defaults to: 4) —

    Number of worker threads for cache refilling

  • cap (Integer) (defaults to: 10_000) —

    Maximum number of cached query results to retain

  • capping (Float) (defaults to: 60) —

    Interval in seconds between cache cap enforcement tasks

  • retire (Integer) (defaults to: 15 * 60) —

    Maximum age in seconds to keep a query in cache

  • retirement (Float) (defaults to: 60) —

    Interval in seconds between retirement tasks

  • loog (Loog) (defaults to: Loog::NULL) —

    Logger instance

  • entrance (Concurrent::ReentrantReadWriteLock) (defaults to: Concurrent::ReentrantReadWriteLock.new) —

    Read-write lock for thread-safe access



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/pgtk/stash.rb', line 70

def initialize(
  pool,
  stash: { queries: {}, tables: {}, table_mod: {}, table_inflight: {} },
  loog: Loog::NULL,
  entrance: Concurrent::ReentrantReadWriteLock.new,
  refill: 16,
  delay: 0,
  maxqueue: 128,
  threads: 4,
  cap: 10_000,
  capping: 60,
  retire: 15 * 60,
  retirement: 60
)
  @pool = pool
  @stash = stash
  @stash[:table_mod] ||= {}
  unless @stash[:table_inflight].default_proc
    @stash[:table_inflight] = Hash.new { |h, k| h[k] = Concurrent::AtomicFixnum.new(0) }
  end
  @loog = loog
  @entrance = entrance
  @refill = refill
  @delay = delay
  @maxqueue = maxqueue
  @threads = threads
  @cap = cap
  @capping = capping
  @retire = retire
  @retirement = retirement
end

Instance Method Details

#dump ⇒ String

Convert internal state into text.

Returns:

  • (String) —

    Multi-line text representation of the current cache state



120
121
122
123
124
# File 'lib/pgtk/stash.rb', line 120

def dump
  @entrance.with_read_lock do
    body(queries)
  end
end

#exec(query, params = [], result = 0) ⇒ PG::Result

Execute a SQL query with optional caching.

Parameters:

  • query (String, Array<String>) —

    The SQL query

  • params (Array) (defaults to: []) —

    Query parameters

  • result (Integer) (defaults to: 0) —

    Result format code

Returns:

  • (PG::Result) —

    Query result object



132
133
134
135
136
137
138
139
140
141
# File 'lib/pgtk/stash.rb', line 132

def exec(query, params = [], result = 0)
  pure = (query.is_a?(Array) ? query.join(' ') : query).gsub(/\s+/, ' ').strip
  if MODS_RE.match?(pure) || (WITH_RE.match?(pure) && ALTS_RE.match?(pure))
    modify(pure, params, result)
  elsif /(^|\s)pg_[a-z_]+\(/.match?(pure)
    @pool.exec(pure, params, result)
  else
    select(pure, params, result)
  end
end

#session {|Pgtk::Stash| ... } ⇒ Object

Run statements on a single connection, without a transaction.

Yields:

Returns:

  • (Object) —

    The result of the block



157
158
159
160
161
# File 'lib/pgtk/stash.rb', line 157

def session
  @pool.session do |t|
    yield(Pgtk::Stash.new(t, stash: @stash, loog: @loog, entrance: @entrance))
  end
end

#start! ⇒ void

This method returns an undefined value.

Start the connection pool and launch background cache management tasks.



105
106
107
108
109
# File 'lib/pgtk/stash.rb', line 105

def start!
  @pool.start!
  cascade!
  launch!
end

#transaction {|Pgtk::Stash| ... } ⇒ Object

Execute a database transaction.

Yields:

  • (Pgtk::Stash) —

    A stash connected to the transaction

Returns:

  • (Object) —

    The result of the block



147
148
149
150
151
# File 'lib/pgtk/stash.rb', line 147

def transaction
  @pool.transaction do |t|
    yield(Pgtk::Stash.new(t, stash: @stash, loog: @loog, entrance: @entrance))
  end
end

#version ⇒ String

Get the PostgreSQL server version.

Returns:

  • (String) —

    Version string of the database server



113
114
115
# File 'lib/pgtk/stash.rb', line 113

def version
  @pool.version
end