Skip to content

Commit

Permalink
Add caching for lookups from LDP.
Browse files Browse the repository at this point in the history
Also added Instrumentation and a log subscriber
  • Loading branch information
jcoyne committed Jan 22, 2015
1 parent 17af32c commit e4ad57b
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 16 deletions.
2 changes: 2 additions & 0 deletions lib/active_fedora.rb
Expand Up @@ -62,6 +62,8 @@ module ActiveFedora #:nodoc:
autoload :FixityService
autoload :Indexing
autoload :IndexingService
autoload :LdpCache
autoload :LdpCachingService
autoload :LdpResource
autoload :LdpResourceService
autoload :LoadableFromJson
Expand Down
2 changes: 2 additions & 0 deletions lib/active_fedora/base.rb
Expand Up @@ -2,6 +2,7 @@
ENABLE_SOLR_UPDATES = true unless defined?(ENABLE_SOLR_UPDATES)
require 'active_support/descendants_tracker'
require 'active_fedora/errors'
require 'active_fedora/log_subscriber'

module ActiveFedora

Expand All @@ -26,6 +27,7 @@ module ActiveFedora
class Base
extend ActiveModel::Naming
extend ActiveSupport::DescendantsTracker
extend LdpCache::ClassMethods

include Core
include Persistence
Expand Down
46 changes: 46 additions & 0 deletions lib/active_fedora/ldp_cache.rb
@@ -0,0 +1,46 @@
module ActiveFedora
# = Active Fedora Ldp Cache
class LdpCache
module ClassMethods
# Enable the query cache within the block if Active Fedora is configured.
# If it's not, it will execute the given block.
def cache(&block)
service = ActiveFedora.fedora.ldp_resource_service
service.cache(&block)
end

# Disable the query cache within the block if Active Fedora is configured.
# If it's not, it will execute the given block.
def uncached(&block)
ActiveFedora.fedora.ldp_resource_service.uncached(&block)
end
end

def initialize(app)
@app = app
end

def call(env)
ActiveFedora.fedora.ldp_resource_service.enable_cache!

response = @app.call(env)
response[2] = Rack::BodyProxy.new(response[2]) do
reset_cache_settings
end

response
rescue Exception => e

This comment has been minimized.

Copy link
@awead

awead Jan 22, 2015

Contributor

rescue StandardError instead?

This comment has been minimized.

Copy link
@jcoyne

jcoyne Jan 22, 2015

Author Member

This is exactly how it's done in ActiveRecord, so I'm assuming there are some Exceptions that are not StandardErrors we want too. https://github.com/rails/rails/blob/master/activerecord/lib/active_record/query_cache.rb#L42

This comment has been minimized.

Copy link
@awead

awead Jan 22, 2015

Contributor

🆗

reset_cache_settings
raise e
end

private

def reset_cache_settings
ActiveFedora.fedora.ldp_resource_service.clear_cache
ActiveFedora.fedora.ldp_resource_service.disable_cache!
end

end
end

66 changes: 66 additions & 0 deletions lib/active_fedora/ldp_caching_service.rb
@@ -0,0 +1,66 @@
module ActiveFedora
module LdpCachingService

def initialize(*)
super
@cache = {}
@cache_enabled = false
end

def get(klass, id)
if @cache_enabled
cache_resource(id) { super }
else
super
end
end

def update(*)
clear_cache if @cache_enabled
super
end

# Enable the cache within the block.
def cache
old, @cache_enabled = @cache_enabled, true
yield
ensure
@cache_enabled = old
clear_cache unless @cache_enabled
end

def enable_cache!
@cache_enabled = true
end

def disable_cache!
@cache_enabled = false
end

# Disable the query cache within the block.
def uncached
old, @cache_enabled = @cache_enabled, false
yield
ensure
@cache_enabled = old
end

def clear_cache
@cache.clear
end

private

def cache_resource(id, &block)
result =
if @cache.key?(id)
ActiveSupport::Notifications.instrument("ldp.active_fedora",
id: id, name: "CACHE", ldp_service: object_id)
@cache[id]
else
@cache[id] = yield
end
result.dup
end
end
end
42 changes: 30 additions & 12 deletions lib/active_fedora/ldp_resource_service.rb
@@ -1,22 +1,40 @@
module ActiveFedora
class LdpResourceService
module LdpOperations
def get(klass, id)
log(klass, id) do
if id
LdpResource.new(connection, to_uri(klass, id))
else
LdpResource.new(connection, nil, nil, ActiveFedora.fedora.host + ActiveFedora.fedora.base_path)
end
end
end

# TODO break the cache.
def update(change_set, klass, id)
SparqlInsert.new(change_set.changes).execute(to_uri(klass, id))
end


private
def log(klass, id)
ActiveSupport::Notifications.instrument("ldp.active_fedora",
id: id, name: "Load #{klass}", ldp_service: object_id) { yield }
end

def to_uri(klass, id)
klass.id_to_uri(id)
end
end
attr_reader :connection
include LdpOperations
include LdpCachingService

def initialize(conn)
@connection = conn
super()
end

def get(klass, id)
if id
LdpResource.new(connection, to_uri(klass, id))
else
LdpResource.new(connection, nil, nil, ActiveFedora.fedora.host + ActiveFedora.fedora.base_path)
end
end

private
def to_uri(klass, id)
klass.id_to_uri(id)
end
end
end
38 changes: 38 additions & 0 deletions lib/active_fedora/log_subscriber.rb
@@ -0,0 +1,38 @@
module ActiveFedora
class LogSubscriber < ActiveSupport::LogSubscriber

def initialize
super
@odd = false
end

def ldp(event)
return unless logger.debug?

payload = event.payload

name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
id = payload[:id] || "[no id]"

if odd?
name = color(name, CYAN, true)
id = color(id, nil, true)
else
name = color(name, MAGENTA, true)
end

debug " #{name} #{id} Service: #{payload[:ldp_service]}"
end

def odd?
@odd = !@odd
end

def logger
ActiveFedora::Base.logger
end
end
end

ActiveFedora::LogSubscriber.attach_to :active_fedora

4 changes: 1 addition & 3 deletions lib/active_fedora/persistence.rb
Expand Up @@ -152,16 +152,14 @@ def update_record(options = {})
end

def refresh
# TODO break the cache
@ldp_source = build_ldp_resource(id)
@resource = nil
end

# TODO break the cache.
def execute_sparql_update
change_set = ChangeSet.new(self, self.resource, self.changed_attributes.keys)
return true if change_set.empty?
SparqlInsert.new(change_set.changes).execute(uri)
ActiveFedora.fedora.ldp_resource_service.update(change_set, self.class, id)
end

# Override to tie in an ID minting service
Expand Down
3 changes: 3 additions & 0 deletions lib/active_fedora/railtie.rb
@@ -1,6 +1,9 @@
module ActiveFedora
class Railtie < Rails::Railtie

config.app_middleware.insert_after "::ActionDispatch::Callbacks",
"ActiveFedora::LdpCache"

initializer 'active_fedora.autoload', before: :set_autoload_paths do |app|
app.config.autoload_paths << 'app/models/datastreams'
end
Expand Down
58 changes: 58 additions & 0 deletions spec/integration/caching_spec.rb
@@ -0,0 +1,58 @@
require 'spec_helper'

describe "Caching" do
before do
class TestClass < ActiveFedora::Base
property :title, predicate: ::RDF::DC.title
end
end

after { Object.send(:remove_const, :TestClass) }

let!(:object) { TestClass.create(id: '123') }

describe "#cache" do
it "should find records in the cache" do
expect(ActiveFedora::LdpResource).to receive(:new).once.and_call_original
ActiveFedora::Base.cache do
TestClass.find(object.id)
TestClass.find(object.id)
end
end

it "should clear the cache at the end of the block" do
expect(ActiveFedora::LdpResource).to receive(:new).twice.and_call_original
ActiveFedora::Base.cache do
TestClass.find(object.id)
end
ActiveFedora::Base.cache do
TestClass.find(object.id)
end
end

context "an update" do
it "should flush the cache" do
expect(ActiveFedora::LdpResource).to receive(:new).twice.and_call_original
ActiveFedora::Base.cache do
TestClass.find(object.id)
object.title= ['foo']
object.save!
TestClass.find(object.id)
end
end
end
end

describe "#uncached" do
it "should not use the cache" do
expect(ActiveFedora::LdpResource).to receive(:new).twice.and_call_original
ActiveFedora::Base.cache do
TestClass.find(object.id)
ActiveFedora::Base.uncached do
TestClass.find(object.id)
end
TestClass.find(object.id)
end
end
end
end
1 change: 1 addition & 0 deletions spec/spec_helper.rb
Expand Up @@ -20,6 +20,7 @@

ActiveFedora::Base.logger = Logger.new(STDERR)
ActiveFedora::Base.logger.level = Logger::WARN
# require 'http_logger'

This comment has been minimized.

Copy link
@tpendragon

tpendragon Jan 22, 2015

Contributor

Should be deleted.

This comment has been minimized.

Copy link
@jcoyne

jcoyne Jan 22, 2015

Author Member

terrellt: it's required now that the LDP gem doesn't require it: samvera/ldp@f2872cd

This comment has been minimized.

Copy link
@tpendragon

tpendragon Jan 22, 2015

Contributor

But it's commented out..

This comment has been minimized.

Copy link
@jcoyne

jcoyne Jan 22, 2015

Author Member

But it's helpful to be able to just uncomment all 4 of those lines on when debugging.

# HttpLogger.logger = Logger.new(STDOUT)
# HttpLogger.ignore = [/localhost:8983\/solr/]
# HttpLogger.colorize = false
Expand Down
2 changes: 1 addition & 1 deletion spec/unit/logger_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'

describe ActiveFedora::Base do
let(:logger1) { double }
let(:logger1) { double(debug?: false) }

before do
@initial_logger = ActiveFedora::Base.logger
Expand Down

0 comments on commit e4ad57b

Please sign in to comment.