Class: ActiveSupport::Cache::CouchbaseStore

Inherits:
Store
  • Object
show all
Includes:
Strategy::LocalCache
Defined in:
lib/active_support/cache/couchbase_store.rb

Overview

A cache store implementation which stores data in Couchbase: 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"
}

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.



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/active_support/cache/couchbase_store.rb', line 51

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)
  @last_mutation_token = nil
end

Class Method Details

.supports_cache_versioning?Boolean

Advertise cache versioning support.

Returns:

  • (Boolean)


45
46
47
# File 'lib/active_support/cache/couchbase_store.rb', line 45

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.



158
159
160
161
162
163
164
165
166
167
168
# File 'lib/active_support/cache/couchbase_store.rb', line 158

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
      operation_options.consistent_with(::Couchbase::MutationState.new(@last_mutation_token)) if @last_mutation_token
      cluster.query("DELETE FROM #{scope_qualifier}", operation_options)
    end
  end
end

#clusterObject



78
79
80
# File 'lib/active_support/cache/couchbase_store.rb', line 78

def cluster
  @cluster ||= build_cluster
end

#collectionObject



74
75
76
# File 'lib/active_support/cache/couchbase_store.rb', line 74

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.



140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/active_support/cache/couchbase_store.rb', line 140

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)
      )
      @last_mutation_token = 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 docs.couchbase.com/server/current/n1ql/n1ql-language-reference/patternmatchingfun.html#section_regex_matches

Because the operation performed on query engine, and it might take time to propagate changes from key/value engine to the indexer. Therefore the keys, that were created a moment ago might not be deleted.

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



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/active_support/cache/couchbase_store.rb', line 96

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)
  operation_options.consistent_with(::Couchbase::MutationState.new(@last_mutation_token)) if @last_mutation_token
  begin
    result = cluster.query("DELETE FROM #{scope_qualifier} cache WHERE REGEXP_MATCHES(META(cache).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.



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/active_support/cache/couchbase_store.rb', line 120

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)
      )
      @last_mutation_token = res.mutation_token
      res.content
    end
  end
end

#inspectObject



82
83
84
# File 'lib/active_support/cache/couchbase_store.rb', line 82

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