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.



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

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)


47
48
49
# File 'lib/active_support/cache/couchbase_store.rb', line 47

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.



161
162
163
164
165
166
167
168
169
170
171
# File 'lib/active_support/cache/couchbase_store.rb', line 161

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



80
81
82
# File 'lib/active_support/cache/couchbase_store.rb', line 80

def cluster
  @cluster ||= build_cluster
end

#collectionObject



76
77
78
# File 'lib/active_support/cache/couchbase_store.rb', line 76

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.



143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/active_support/cache/couchbase_store.rb', line 143

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



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

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_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.



123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/active_support/cache/couchbase_store.rb', line 123

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



84
85
86
# File 'lib/active_support/cache/couchbase_store.rb', line 84

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