Class: ActiveSupport::Cache::CouchbaseStore

Inherits:
Store
  • Object
show all
Includes:
Strategy::LocalCache
Defined in:
lib/active_support/cache/couchbase_store.rb,
/Users/sergey.auseyau/code/couchbase-ruby-client/lib/active_support/cache/couchbase_store.rb
more...

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.

[View source]

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)
[View source]

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.

[View source]

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

[View source]

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

def cluster
  @cluster ||= build_cluster
end

#collectionObject

[View source]

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.

[View source]

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

[View source]

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.

[View source]

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

[View source]

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

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