Commit d1c6a2c8 authored by Guido van Rossum's avatar Guido van Rossum

Cache policy improvement:

When load() finds a hit in the non-current file, copy it to the
current file (except when this would cause a cache flip).

It is hoped that this improves the cache performance.

(Now merged into the trunk!  Jeremy & Jim want this in the ZEO and ZODB3 beta2 releases.)
parent 5cf04dac
...@@ -99,7 +99,7 @@ with '\0\0\0\0'. ...@@ -99,7 +99,7 @@ with '\0\0\0\0'.
If var is not writable, then temporary files are used for If var is not writable, then temporary files are used for
file 0 and file 1. file 0 and file 1.
$Id: ClientCache.py,v 1.36 2002/09/13 20:59:28 gvanrossum Exp $ $Id: ClientCache.py,v 1.37 2002/09/18 21:22:58 gvanrossum Exp $
""" """
import os import os
...@@ -303,6 +303,8 @@ class ClientCache: ...@@ -303,6 +303,8 @@ class ClientCache:
if dlen: if dlen:
data = read(dlen) data = read(dlen)
self._trace(0x2A, oid, version, h[19:], dlen) self._trace(0x2A, oid, version, h[19:], dlen)
if (p < 0) != self._current:
self._copytocurrent(ap, tlen, dlen, vlen, h, data)
return data, h[19:] return data, h[19:]
else: else:
self._trace(0x26, oid, version) self._trace(0x26, oid, version)
...@@ -317,6 +319,9 @@ class ClientCache: ...@@ -317,6 +319,9 @@ class ClientCache:
seek(ap+27) seek(ap+27)
data = read(dlen) data = read(dlen)
self._trace(0x2C, oid, version, h[19:], dlen) self._trace(0x2C, oid, version, h[19:], dlen)
if (p < 0) != self._current:
self._copytocurrent(ap, tlen, dlen, vlen, h,
data, vheader)
return data, h[19:] return data, h[19:]
else: else:
self._trace(0x28, oid, version) self._trace(0x28, oid, version)
...@@ -326,10 +331,71 @@ class ClientCache: ...@@ -326,10 +331,71 @@ class ClientCache:
vdata = read(vdlen) vdata = read(vdlen)
vserial = read(8) vserial = read(8)
self._trace(0x2E, oid, version, vserial, vdlen) self._trace(0x2E, oid, version, vserial, vdlen)
if (p < 0) != self._current:
self._copytocurrent(ap, tlen, dlen, vlen, h,
None, vheader, vdata, vserial)
return vdata, vserial return vdata, vserial
finally: finally:
self._release() self._release()
def _copytocurrent(self, pos, tlen, dlen, vlen, header,
data=None, vheader=None, vdata=None, vserial=None):
"""Copy a cache hit from the non-current file to the current file.
Arguments are the file position in the non-current file,
record length, data length, version string length, header, and
optionally parts of the record that have already been read.
"""
if self._pos + tlen > self._limit:
return # Don't let this cause a cache flip
assert len(header) == 27
if header[8] == 'n':
# Rewrite the header to drop the version data.
# This shortens the record.
tlen = 31 + dlen
vlen = 0
# (oid:8, status:1, tlen:4, vlen:2, dlen:4, serial:8)
header = header[:9] + pack(">IHI", tlen, vlen, dlen) + header[-8:]
else:
assert header[8] == 'v'
f = self._f[not self._current]
if data is None:
f.seek(pos+27)
data = f.read(dlen)
if len(data) != dlen:
return
l = [header, data]
if vlen:
assert vheader is not None
l.append(vheader)
assert (vdata is None) == (vserial is None)
if vdata is None:
vdlen = unpack(">I", vheader[-4:])[0]
f.seek(pos+27+dlen+vlen+4)
vdata = f.read(vdlen)
if len(vdata) != vdlen:
return
vserial = f.read(8)
if len(vserial) != 8:
return
l.append(vdata)
l.append(vserial)
else:
assert None is vheader is vdata is vserial
l.append(header[9:13]) # copy of tlen
g = self._f[self._current]
g.seek(self._pos)
g.writelines(l)
assert g.tell() == self._pos + tlen
oid = header[:8]
if self._current:
self._index[oid] = - self._pos
else:
self._index[oid] = self._pos
self._pos += tlen
self._trace(0x6A, header[:8], vlen and vheader[:-4] or '',
vlen and vserial or header[-8:], dlen)
def update(self, oid, serial, version, data): def update(self, oid, serial, version, data):
self._acquire() self._acquire()
try: try:
...@@ -476,7 +542,7 @@ class ClientCache: ...@@ -476,7 +542,7 @@ class ClientCache:
l.append(stlen) l.append(stlen)
f = self._f[self._current] f = self._f[self._current]
f.seek(self._pos) f.seek(self._pos)
f.write("".join(l)) f.writelines(l) # write all list elements
if self._current: if self._current:
self._index[oid] = - self._pos self._index[oid] = - self._pos
......
...@@ -364,6 +364,8 @@ explain = { ...@@ -364,6 +364,8 @@ explain = {
0x5A: "store (non-version data present)", 0x5A: "store (non-version data present)",
0x5C: "store (only version data present)", 0x5C: "store (only version data present)",
0x6A: "_copytocurrent",
0x70: "checkSize (cache flip)", 0x70: "checkSize (cache flip)",
} }
......
...@@ -164,6 +164,103 @@ class ClientCacheTests(unittest.TestCase): ...@@ -164,6 +164,103 @@ class ClientCacheTests(unittest.TestCase):
self.assertNotEqual(cache._index.get(oid2), None) self.assertNotEqual(cache._index.get(oid2), None)
self.assertEqual(cache._index.get(oid), None) self.assertEqual(cache._index.get(oid), None)
def testCopyToCurrent(self):
# - write some objects to cache file 0
# - force a flip
# - write some objects to cache file 1
# - load some objects that are in cache file 0
# - load the same objects, making sure they are now in file 1
# - write some more objects
# - force another flip
# - load the same objects again
# - make sure they are now in file 0 again
cache = self.cache
# Create some objects
oid1 = 'abcdefgh'
data1 = '1234' * 100
serial1 = 'ABCDEFGH'
oid2 = 'bcdefghi'
data2 = '2345' * 200
serial2 = 'BCDEFGHI'
version2 = 'myversion'
nonversion = 'nada'
vdata2 = '5432' * 250
vserial2 = 'IHGFEDCB'
oid3 = 'cdefghij'
data3 = '3456' * 300
serial3 = 'CDEFGHIJ'
# Store them in the cache
cache.store(oid1, data1, serial1, '', '', '')
cache.store(oid2, data2, serial2, version2, vdata2, vserial2)
cache.store(oid3, data3, serial3, '', '', '')
# Verify that they are in file 0
self.assert_(None is not cache._index.get(oid1) > 0)
self.assert_(None is not cache._index.get(oid2) > 0)
self.assert_(None is not cache._index.get(oid3) > 0)
# Load them and verify that the loads return correct data
self.assertEqual(cache.load(oid1, ''), (data1, serial1))
self.assertEqual(cache.load(oid2, ''), (data2, serial2))
self.assertEqual(cache.load(oid2, nonversion), (data2, serial2))
self.assertEqual(cache.load(oid2, version2), (vdata2, vserial2))
self.assertEqual(cache.load(oid3, ''), (data3, serial3))
# Verify that they are still in file 0
self.assert_(None is not cache._index.get(oid1) > 0)
self.assert_(None is not cache._index.get(oid2) > 0)
self.assert_(None is not cache._index.get(oid3) > 0)
# Cause a cache flip
cache.checkSize(10*self.cachesize)
# Load o1, o2, o4 again and verify that the loads return correct data
self.assertEqual(cache.load(oid1, ''), (data1, serial1))
self.assertEqual(cache.load(oid2, version2), (vdata2, vserial2))
self.assertEqual(cache.load(oid2, nonversion), (data2, serial2))
self.assertEqual(cache.load(oid2, ''), (data2, serial2))
# Verify that o1, o2, 04 are now in file 1, o3 still in file 0
self.assert_(None is not cache._index.get(oid1) < 0)
self.assert_(None is not cache._index.get(oid2) < 0)
self.assert_(None is not cache._index.get(oid3) > 0)
# Cause another cache flip
cache.checkSize(10*self.cachesize)
# Load o1 and o2 again and verify that the loads return correct data
self.assertEqual(cache.load(oid1, ''), (data1, serial1))
self.assertEqual(cache.load(oid2, nonversion), (data2, serial2))
self.assertEqual(cache.load(oid2, version2), (vdata2, vserial2))
self.assertEqual(cache.load(oid2, ''), (data2, serial2))
# Verify that o1 and o2 are now back in file 0, o3 is lost
self.assert_(None is not cache._index.get(oid1) > 0)
self.assert_(None is not cache._index.get(oid2) > 0)
self.assert_(None is cache._index.get(oid3))
# Invalidate version data for o2
cache.invalidate(oid2, nonversion)
self.assertEqual(cache.load(oid2, ''), (data2, serial2))
self.assertEqual(cache.load(oid2, nonversion), None)
self.assertEqual(cache.load(oid2, version2), None)
# Cause another cache flip
cache.checkSize(10*self.cachesize)
# Load o1 and o2 again and verify that the loads return correct data
self.assertEqual(cache.load(oid1, ''), (data1, serial1))
self.assertEqual(cache.load(oid2, version2), None)
self.assertEqual(cache.load(oid2, nonversion), None)
self.assertEqual(cache.load(oid2, ''), (data2, serial2))
# Verify that o1 and o2 are now in file 1
self.assert_(None is not cache._index.get(oid1) < 0)
self.assert_(None is not cache._index.get(oid2) < 0)
class PersistentClientCacheTests(unittest.TestCase): class PersistentClientCacheTests(unittest.TestCase):
def setUp(self): def setUp(self):
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment