From: matz@... (Yukihiro Matsumoto) Date: 2001-04-11T11:56:03+09:00 Subject: [ruby-talk:13602] Re: persistent ruby? Hi, In message "[ruby-talk:13595] Re: persistent ruby?" on 01/04/10, Anders Bengtsson writes: |Does anyone know how ObjectDBM compares to PStore? It uses DBM, read/write cache, plus it allows nested transactions. So theoretically, it must be far better than PStore, but I'm not sure because I haven't used it. PStore has been enough for my tasks. |When I looked for a persistence mechanism I was a bit |dissapointed by PStore, since it works by writing the |entire object hierarchy to disk for each transaction. PStore is just a proof-of-concept chunk of code. Remember it's implemented in just 143 lines of code (remarkable, isn't it?). Thus avoiding unnecessary file update and saving each root data entry into dbm or any other database would improve its performance dramatically. Here's the version which avoids unnecessary update using MD5. This will be standard PStore in 1.7. # # How to use: # # db = PStore.new("/tmp/foo") # db.transaction do # p db.roots # ary = db["root"] = [1,2,3,4] # ary[0] = [1,1.5] # end # db.transaction do # p db["root"] # end require "ftools" require "md5" class PStore class Error < StandardError end def initialize(file) dir = File::dirname(file) unless File::directory? dir raise PStore::Error, format("directory %s does not exist", dir) end unless File::writable? dir raise PStore::Error, format("directory %s not writable", dir) end if File::exist? file and not File::readable? file raise PStore::Error, format("file %s not readable", file) end @transaction = false @filename = file @abort = false end def in_transaction raise PStore::Error, "not in transaction" unless @transaction end private :in_transaction def [](name) in_transaction value = @table[name] if value == nil raise PStore::Error, format("undefined root name `%s'", name) end value end def []=(name, value) in_transaction @table[name] = value end def delete(name) in_transaction @table.delete name end def roots in_transaction @table.keys end def root?(name) in_transaction @table.key? name end def path @filename end def commit @abort = false throw :pstore_abort_transaction end def abort @abort = true throw :pstore_abort_transaction end def transaction raise PStore::Error, "nested transaction" if @transaction begin @transaction = true value = nil backup = @filename+"~" begin file = File::open(@filename, "r+") orig = true rescue Errno::ENOENT file = File::open(@filename, "w+") end file.flock(File::LOCK_EX) if orig content = file.read @table = Marshal::load(content) size = content.size md5 = MD5.new(content).digest content = nil # unreference huge data else @table = {} end begin catch(:pstore_abort_transaction) do value = yield(self) end rescue Exception @abort = true raise ensure unless @abort file.rewind content = Marshal::dump(@table) if !md5 || size != content.size || md5 != MD5.new(content).digest File::copy @filename, backup begin file.write(content) file.truncate(file.pos) content = nil # unreference huge data rescue File::rename backup, @filename if File::exist?(backup) raise end end end @abort = false end ensure @table = nil @transaction = false file.close end value end end if __FILE__ == $0 db = PStore.new("/tmp/foo") db.transaction do p db.roots ary = db["root"] = [1,2,3,4] ary[1] = [1,1.5] end 1000.times do db.transaction do db["root"][0] += 1 p db["root"][0] end end db.transaction do p db["root"] end end --- matz.