Class: ActiveSupport::Cache::CouchbaseStore

Inherits:
Store
  • Object
show all
Includes:
Strategy::LocalCache
Defined in:
lib/active_support/cache/couchbase_store.rb,
/home/runner/work/couchbase-ruby-client/couchbase-ruby-client/lib/active_support/cache/couchbase_store.rb

Overview

A cache store implementation which stores data in Couchbase: https://couchbase.com

  • Local cache. Hot in-memory primary cache within block/middleware scope.
  • read_multi and write_multi support.
  • delete_matched support using N1QL queries.
  • clear for erasing whole collection (optionally can flush the bucket).

To use this store, add the select it in application config

config.cache_store = :couchbase_store, {
  connection_string: "couchbase://localhost",
  username: "app_cache_user",
  password: "s3cret",
  bucket: "app_cache"
}

delete_matched and clear query with consistent_with the store's writes. By default that covers the last write only, which leaves earlier writes to other vBuckets out of the query until the index catches up. With mutation_tracking: :all the store keeps the newest mutation token of every vBucket it wrote to, so both see every earlier write. It also accepts an object that responds to record(*tokens) and mutation_state.

Defined Under Namespace

Classes: AllMutations, LastMutation

Constant Summary collapse

MAX_KEY_BYTESIZE =
250
DEFAULT_ERROR_HANDLER =
lambda do |method:, returning:, exception:, logger: CouchbaseStore.logger|
  logger&.error { "CouchbaseStore: #{method} failed, returned #{returning.inspect}: #{exception.class}: #{exception.message}" }
end

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options = nil) ⇒ CouchbaseStore

Returns a new instance of CouchbaseStore.



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/active_support/cache/couchbase_store.rb', line 105

def initialize(options = nil)
  super
  @error_handler = @options.delete(:error_handler) { DEFAULT_ERROR_HANDLER }
  @couchbase_options = {}
  @couchbase_options[:connection_string] =
    @options.delete(:connection_string) do
      raise ArgumentError, "Missing connection string for Couchbase cache store. Use :connection_string in the store options"
    end
  @couchbase_options[:username] =
    @options.delete(:username) do
      raise ArgumentError, "Missing username for Couchbase cache store. Use :username in the store options"
    end
  @couchbase_options[:password] =
    @options.delete(:password) do
      raise ArgumentError, "Missing password for Couchbase cache store. Use :password in the store options"
    end
  @couchbase_options[:bucket] =
    @options.delete(:bucket) { raise ArgumentError, "Missing bucket for Couchbase cache store. Use :bucket in the store options" }
  @couchbase_options[:scope] = @options.delete(:scope) if @options.key?(:scope)
  @couchbase_options[:collection] = @options.delete(:collection) if @options.key?(:collection)
  @mutations =
    case (tracking = @options.delete(:mutation_tracking) { :last })
    when :last, "last" then LastMutation.new
    when :all, "all" then AllMutations.new
    else
      unless tracking.respond_to?(:record) && tracking.respond_to?(:mutation_state)
        raise ArgumentError, "mutation_tracking must be :last, :all, or respond to #record and #mutation_state"
      end

      tracking
    end
end

Class Method Details

.supports_cache_versioning? ⇒ Boolean

Advertise cache versioning support.

Returns:

  • (Boolean)


99
100
101
# File 'lib/active_support/cache/couchbase_store.rb', line 99

def self.supports_cache_versioning?
  true
end

Instance Method Details

#clear(use_flush: false, **_options) ⇒ Object

Clears the entire cache. Be careful with this method since it could affect other processes if shared cache is being used.

When use_flush option set to true it will flush the bucket. Otherwise, it uses N1QL query and relies on default index.



225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/active_support/cache/couchbase_store.rb', line 225

def clear(use_flush: false, **_options)
  failsafe(:clear) do
    if use_flush
      cluster.buckets.flush_bucket(@couchbase_options[:bucket_name])
    else
      operation_options = ::Couchbase::Options::Query.new
      mutation_state = @mutations.mutation_state
      operation_options.consistent_with(mutation_state) if mutation_state
      cluster.query("DELETE FROM #{scope_qualifier}", operation_options)
    end
  end
end

#cluster ⇒ Object



142
143
144
# File 'lib/active_support/cache/couchbase_store.rb', line 142

def cluster
  @cluster ||= build_cluster
end

#collection ⇒ Object



138
139
140
# File 'lib/active_support/cache/couchbase_store.rb', line 138

def collection
  @collection ||= build_collection
end

#decrement(name, amount = 1, expires_in: nil, initial: nil, **_options) ⇒ Object

Decrements an integer value in the cache.

Note that it uses binary collection interface, therefore will fail if the value is not represented as ASCII-encoded number using :raw.

Note that the counter represented by non-negative number on the server, and it will not cycle to maximum integer when decrementing zero.



207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/active_support/cache/couchbase_store.rb', line 207

def decrement(name, amount = 1, expires_in: nil, initial: nil, **_options)
  instrument :decrement, name, amount: amount do
    failsafe :decrement do
      key = normalize_key(name, options)
      res = collection.binary.decrement(
        key, ::Couchbase::Options::Decrement(delta: amount, expiry: expires_in, initial: initial)
      )
      @mutations.record(res.mutation_token)
      res.content
    end
  end
end

#delete_matched(matcher, _options = nil) ⇒ Object

Deletes all entries with keys matching the regular expression.

The matcher must be valid pattern for N1QL REGEXP_MATCHES function. More info at https://docs.couchbase.com/server/current/n1ql/n1ql-language-reference/patternmatchingfun.html#section_regex_matches

The query is consistent with the writes this store recorded, as selected by mutation_tracking. With the default :last, keys written shortly before the last write to other vBuckets might not be deleted. Writes by other processes are not covered.

Also this method assumes, that primary index created on the target bucket



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# File 'lib/active_support/cache/couchbase_store.rb', line 161

def delete_matched(matcher, _options = nil)
  pattern =
    case matcher
    when Regexp
      matcher.inspect[1..-2]
    when String
      matcher.tr("?", ".").gsub("*", ".*")
    else
      raise NotImplementedError, "Unable to convert #{matcher.inspect} to Regexp pattern"
    end
  operation_options = ::Couchbase::Options::Query(named_parameters: {"pattern" => pattern}, metrics: true)
  mutation_state = @mutations.mutation_state
  operation_options.consistent_with(mutation_state) if mutation_state
  begin
    result = cluster.query("DELETE FROM #{scope_qualifier} cache_store_ WHERE REGEXP_MATCHES(META(cache_store_).id, $pattern)",
                           operation_options)
    result..metrics.mutation_count
  rescue ::Couchbase::Error::ParsingFailure, ::Couchbase::Error::ServiceNotAvailable
    raise NotImplementedError, "The server does not support delete_matched operation"
  end
end

#increment(name, amount = 1, expires_in: nil, initial: nil, **options) ⇒ Object

Increments an integer value in the cache.

Note that it uses binary collection interface, therefore will fail if the value is not represented as ASCII-encoded number using :raw.



187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/active_support/cache/couchbase_store.rb', line 187

def increment(name, amount = 1, expires_in: nil, initial: nil, **options)
  instrument :increment, name, amount: amount do
    failsafe :increment do
      key = normalize_key(name, options)
      res = collection.binary.increment(
        key, ::Couchbase::Options::Increment(delta: amount, expiry: expires_in, initial: initial)
      )
      @mutations.record(res.mutation_token)
      res.content
    end
  end
end

#inspect ⇒ Object



146
147
148
# File 'lib/active_support/cache/couchbase_store.rb', line 146

def inspect
  "#<#{self.class} options=#{options.inspect} collection=#{@collection.inspect}>"
end