This class manages the cache. It is initialized by the Store and front ends the database behind the Store.
# File lib/storage/cache.rb, line 112 def initialize(db) @cwidth = options['cache_width'] @cdepth = options['cache_depth'] @db = db @st = CacheStats.new @cache = Array.new(@cwidth) {Array.new} @cwidth.times do |i| @cdepth.times do |j| @cache[i] << CacheEntry.new @st.inc(:cache_size) end end end
delete an object
# File lib/storage/cache.rb, line 254 def delete(oid) return nil if oid.nil? @st.inc(:deletes) hv = hash(oid) # mark dead in cache @cache[hv].each do |ce| next if ce.oid != oid ce.mark_dead break end # delete it from the database @db.delete(oid.to_s) end
gets an object
oid
integer object id to retrieve
return
a reference to the object or nil if none exists
# File lib/storage/cache.rb, line 151 def get(oid) return nil if oid.nil? @st.inc(:reads) hv = hash(oid) # search the cache @cache[hv].each do |ce| if ce.oid == oid @st.inc(:cache_read_hits) # need to try putting hot hit to the head of the list? return ce.obj end end # cache miss - search the database if @db.has_key? oid.to_s ret = Utility.decode(@db[oid.to_s]) else @st.inc(:database_read_fails) return nil end @st.inc(:database_reads) # get and remove the last entry off this list ch = @cache[hv].pop # if its dirty we write it to the database if ch.dirty? && !ch.dead? @db[ch.oid.to_s] = Utility.encode(ch.obj) @st.inc(:database_writes) if ch.noswap? # here we have a problem we can't use this # first push it back onto the list @cache[hv].unshift ch # get ourselves a brand new ch = CacheEntry.new # problem solved # the depth of any list chains will be cache_depth + # noswap entries end end # assign our new object to the cache entry ch.obj = ret ch.oid = oid ch.clean! # push it to the head of the list @cache[hv].unshift ch ret end
Our simple hash algoritm. Our database keys are sequentially assigned integers, so… I don’t know what would be better.
# File lib/storage/cache.rb, line 144 def hash(oid) oid % @cwidth end
report on the cache map see cmd_dumpcache
# File lib/storage/cache.rb, line 130 def inspect str = "cache width:#{@cwidth} cache depth:#{@cdepth}\n" @cwidth.times do |i| @cache[i].each_with_index do |ce,j| next if ce.dead? str << "cache map [#{i}][#{j}] => " str << "oid #{ce.oid} object_id #{ce.obj.object_id} dirty? #{ce.dirty?} noswap? #{ce.noswap?}\n" end end str end
This is provided to traverse the entire object chain. This thrashes the cache violently. See Store#each. One needs to question the design of routines that use Store#each above.
# File lib/storage/cache.rb, line 359 def keys kys = [] @cwidth.times do |i| @cache[i].each {|ce| kys << ce.oid if !ce.oid.nil? } end kys end
Mark an object in the cache as nonswappable
oid
is the object id
return
undefined
# File lib/storage/cache.rb, line 319 def makenoswap(oid) return nil if oid.nil? @st.inc(:cache_noswap) hv = hash(oid) @cache[hv].each do |ce| next if ce.oid != oid ce.noswap! return end # This would indicate we've tried marking it noswap before we did a put. # A logic error @st.inc(:cache_noswap_misses) log.debug "Marking object nonswappable before put - #{oid}" end
Mark an object in the cache as swappable
oid
is the object id
return
undefined
# File lib/storage/cache.rb, line 339 def makeswap(oid) return nil if oid.nil? @st.inc(:cache_swap) hv = hash(oid) @cache[hv].each do |ce| next if ce.oid != oid ce.swap! return end # This would indicate we've tried marking it swap before we did a put. # A logic error @st.inc(:cache_swap_misses) log.debug "Marking object swappable before put - #{oid}" end
deliberately mark an object in the cache as dirty see properties.
# File lib/storage/cache.rb, line 271 def mark(oid) return nil if oid.nil? @st.inc(:cache_marks) hv = hash(oid) @cache[hv].each do |ce| next if ce.oid != oid ce.dirty! return end # this would indicate we've tried marking it dirty before we did a put. @st.inc(:cache_mark_misses) log.debug "Marking object dirty before put - #{oid}" end
puts an object
obj
The integer object id to retrieve
return
undefined
# File lib/storage/cache.rb, line 203 def put(obj) return nil if obj.nil? @st.inc(:writes) hv = hash(obj.id) # search the cache @cache[hv].each do |ce| next if ce.oid != obj.id @st.inc(:cache_write_hits) if ce.dirty? if obj.object_id != ce.obj.object_id # be safe # Be extra careful here. It's possible we could have two objects # with the same object id but with different Ruby object_ids. # Most likely this is a bug, but we should handle it here.. ce.obj = obj log.warn "Duplicate object id's in cache!" log.warn "insert - #{obj.inspect}" log.warn "cache - #{obj.inspect}" end # need to try putting hot hit to the head of the list? ce.dirty! return end # get and remove the last entry off this list ch = @cache[hv].pop # if its dirty we write it to the database if ch.dirty? && !ch.dead? # errors possible - check in store module @db[ch.oid.to_s] = Utility.encode(ch.obj) @st.inc(:database_writes) if ch.noswap? # here we have a problem we can't use this # first push it back onto the list @cache[hv].unshift ch # get ourselves a brand new ch = CacheEntry.new # problem solved # the depth of any list chains will be cache_depth + # noswap entries end end # assign our new object to the cache entry ch.obj = obj ch.oid = obj.id ch.dirty! # push it to the head of the list @cache[hv].unshift ch return end
produce a report - see cmd_stats
# File lib/storage/cache.rb, line 368 def stats str = "----------* Cache Statistics *----------\n" @st.each do |k,v| str << sprintf("%25-25s : %d\n",k.to_s.gsub(%r_/,' '),v) end str << "----------* *----------\n" end
syncronize the entire cache with the database called by save.
# File lib/storage/cache.rb, line 289 def sync @st.inc(:cache_syncs) # search the list chains for dirty objects and write them out. @cwidth.times do |i| @cache[i].each do |ce| if ce.dirty? @db[ce.oid.to_s] = Utility.encode(ce.obj) @st.inc(:database_writes) ce.clean! end end end end
syncronize a specific list chain not yet called but included for possible performance enhancement
# File lib/storage/cache.rb, line 305 def sync_chain(i) @st.inc(:chain_syncs) @cache[i].each do |ce| if ce.dirty? @db[ce.oid.to_s] = Utility.encode(ce.obj) @st.inc(:database_writes) ce.clean! end end end