Merge lp:~jderose/dmedia/downgrade-unknown into lp:dmedia

Proposed by Jason Gerard DeRose
Status: Merged
Merged at revision: 589
Proposed branch: lp:~jderose/dmedia/downgrade-unknown
Merge into: lp:dmedia
Diff against target: 256 lines (+143/-57)
2 files modified
dmedia/metastore.py (+26/-14)
dmedia/tests/test_metastore.py (+117/-43)
To merge this branch: bzr merge lp:~jderose/dmedia/downgrade-unknown
Reviewer Review Type Date Requested Status
xzcvczx (community) Approve
dmedia Dev Pending
Review via email: mp+150052@code.launchpad.net

Description of the change

For more info, see this bug:
https://bugs.launchpad.net/dmedia/+bug/1131742

This reworks MetaStore.downgrade_by_store_atime() to be driven by the "file/stored" view rather than the "store/atime" view.

Importantly, this change means copies in a store will always be downgraded when the store lacks a corresponding doc in CouchDB. Previously, a missing dmedia/store doc meant these copies never got downgraded by the store atime (although they would eventually get downgraded when the DOWNGRADE_BY_LAST_VERIFIED threshold was reached).

To be a bit more conservative as we test and tune the remaining automation behaviours, I also lowered DOWNGRADE_BY_LAST_VERIFIED from 4 weeks to 2 weeks.

Lastly, the threshold for when files would start to be re-verified was previous hard-coded using the ONE_WEEK constant. Instead, it's now using new VERIFY_THRESHOLD constant (also in the tests), making this parameter easy to tune.

To post a comment you must log in.
Revision history for this message
xzcvczx (xzcvczx) wrote :

Nice

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'dmedia/metastore.py'
2--- dmedia/metastore.py 2013-02-21 13:38:52 +0000
3+++ dmedia/metastore.py 2013-02-22 14:17:19 +0000
4@@ -48,12 +48,13 @@
5
6
7 log = logging.getLogger()
8+
9 DAY = 24 * 60 * 60
10-ONE_WEEK = 7 * DAY
11-
12-DOWNGRADE_BY_STORE_ATIME = 7 * DAY # 1 week
13-DOWNGRADE_BY_NEVER_VERIFIED = 2 * DAY # 48 hours
14-DOWNGRADE_BY_LAST_VERIFIED = 28 * DAY # 4 weeks
15+WEEK = 7 * DAY
16+DOWNGRADE_BY_NEVER_VERIFIED = 2 * DAY
17+VERIFY_THRESHOLD = WEEK
18+DOWNGRADE_BY_STORE_ATIME = WEEK
19+DOWNGRADE_BY_LAST_VERIFIED = 2 * WEEK
20
21
22 class MTimeMismatch(Exception):
23@@ -71,7 +72,7 @@
24 return time.perf_counter() - self.start
25
26 def log(self, msg, *args):
27- log.info('[%.3fs] ' + msg, self.delta, *args)
28+ log.info('[%.3f] ' + msg, self.delta, *args)
29
30
31 def get_dict(d, key):
32@@ -361,13 +362,24 @@
33 if curtime is None:
34 curtime = int(time.time())
35 assert isinstance(curtime, int) and curtime >= 0
36- rows = self.db.view('store', 'atime',
37- endkey=(curtime - DOWNGRADE_BY_STORE_ATIME)
38- )['rows']
39- ids = [row['id'] for row in rows]
40- for store_id in ids:
41- self.downgrade_store(store_id)
42- return ids
43+ threshold = curtime - DOWNGRADE_BY_STORE_ATIME
44+ t = TimeDelta()
45+ result = {}
46+ rows = self.db.view('file', 'stored', reduce=True, group=True)['rows']
47+ for row in rows:
48+ store_id = row['key']
49+ try:
50+ doc = self.db.get(store_id)
51+ atime = doc.get('atime')
52+ if isinstance(atime, int) and atime > threshold:
53+ log.info('Store %s okay at atime %s', store_id, atime)
54+ continue
55+ except NotFound:
56+ log.warning('No doc found for store %s, forcing downgrade')
57+ result[store_id] = self.downgrade_store(store_id)
58+ total = sum(result.values())
59+ t.log('downgraded %d total copies in %d stores', total, len(result))
60+ return result
61
62 def downgrade_store(self, store_id):
63 t = TimeDelta()
64@@ -545,7 +557,7 @@
65
66 def verify_all(self, fs):
67 start = [fs.id, None]
68- end = [fs.id, int(time.time()) - ONE_WEEK]
69+ end = [fs.id, int(time.time()) - VERIFY_THRESHOLD]
70 count = 0
71 t = TimeDelta()
72 log.info('verifying %r', fs)
73
74=== modified file 'dmedia/tests/test_metastore.py'
75--- dmedia/tests/test_metastore.py 2013-02-21 09:31:46 +0000
76+++ dmedia/tests/test_metastore.py 2013-02-22 14:17:19 +0000
77@@ -1019,54 +1019,128 @@
78 for (old, new) in zip(docs, db.get_many(ids)):
79 self.assertEqual(old, new)
80
81- def test_downgrade_by_store_atime(self):
82- db = util.get_db(self.env, True)
83-
84- class Dummy(metastore.MetaStore):
85- def __init__(self, db):
86- super().__init__(db)
87- self._calls = []
88-
89+ def test_downgrade_by_store_atime(self):
90+ class PassThrough(metastore.MetaStore):
91 def downgrade_store(self, store_id):
92 self._calls.append(store_id)
93+ return super().downgrade_store(store_id)
94
95- # Test when empty
96- ms = Dummy(db)
97- self.assertEqual(ms.downgrade_by_store_atime(), [])
98- self.assertEqual(ms._calls, [])
99+ db = util.get_db(self.env, True)
100+ ms = PassThrough(db)
101 curtime = int(time.time())
102- self.assertEqual(ms.downgrade_by_store_atime(curtime), [])
103- self.assertEqual(ms._calls, [])
104-
105- # Test when some need to be downgraded
106 base = curtime - metastore.DOWNGRADE_BY_STORE_ATIME
107- docs = []
108- for i in range(8):
109- doc = {
110- '_id': random_id(),
111- 'type': 'dmedia/store',
112- 'atime': base + i,
113- }
114- docs.append(doc)
115- db.save_many(docs)
116- ids = [doc['_id'] for doc in docs]
117- self.assertEqual(ms.downgrade_by_store_atime(curtime - 1), [])
118+
119+ # Test when empty:
120+ ms._calls = []
121+ self.assertEqual(ms.downgrade_by_store_atime(), {})
122+ self.assertEqual(ms.downgrade_by_store_atime(curtime), {})
123 self.assertEqual(ms._calls, [])
124- for i in range(8):
125- expected = ids[:i+1]
126- self.assertEqual(
127- ms.downgrade_by_store_atime(curtime + i),
128- expected
129- )
130- self.assertEqual(ms._calls, expected)
131- ms._calls = []
132-
133- # Once more with feeling
134- self.assertEqual(
135- ms.downgrade_by_store_atime(curtime),
136- [ids[0]]
137- )
138- self.assertEqual(ms._calls, [ids[0]])
139+
140+ # One store that's missing its doc:
141+ store_id1 = random_id()
142+ ids1 = tuple(random_id() for i in range(17))
143+ docs = [
144+ {
145+ '_id': _id,
146+ 'type': 'dmedia/file',
147+ 'stored': {store_id1: {'copies': 1}},
148+ }
149+ for _id in ids1
150+ ]
151+ db.save_many(docs)
152+
153+ # Another store with an atime old enough to trigger a downgrade:
154+ store_id2 = random_id()
155+ doc2 = {'_id': store_id2, 'atime': base}
156+ db.save(doc2)
157+ ids2 = tuple(random_id() for i in range(18))
158+ docs = [
159+ {
160+ '_id': _id,
161+ 'type': 'dmedia/file',
162+ 'stored': {store_id2: {'copies': 1}},
163+ }
164+ for _id in ids2
165+ ]
166+ db.save_many(docs)
167+
168+ # A store with an atime new enough to be okay:
169+ store_id3 = random_id()
170+ doc3 = {'_id': store_id3, 'atime': base + 1}
171+ db.save(doc3)
172+ ids3 = tuple(random_id() for i in range(19))
173+ docs = [
174+ {
175+ '_id': _id,
176+ 'type': 'dmedia/file',
177+ 'stored': {store_id3: {'copies': 1}},
178+ }
179+ for _id in ids3
180+ ]
181+ db.save_many(docs)
182+
183+ # And finally a store missing its doc['atime']:
184+ store_id4 = random_id()
185+ doc4 = {'_id': store_id4}
186+ db.save(doc4)
187+ ids4 = tuple(random_id() for i in range(20))
188+ docs = [
189+ {
190+ '_id': _id,
191+ 'type': 'dmedia/file',
192+ 'stored': {store_id4: {'copies': 1}},
193+ }
194+ for _id in ids4
195+ ]
196+ db.save_many(docs)
197+
198+ # Test at curtime:
199+ self.assertEqual(ms.downgrade_by_store_atime(curtime),
200+ {store_id1: 17, store_id2: 18, store_id4: 20}
201+ )
202+ self.assertEqual(ms._calls,
203+ sorted([store_id1, store_id2, store_id4])
204+ )
205+ for doc in db.get_many(ids1):
206+ self.assertTrue(doc['_rev'].startswith('2-'))
207+ self.assertEqual(doc['stored'], {store_id1: {'copies': 0}})
208+ for doc in db.get_many(ids2):
209+ self.assertTrue(doc['_rev'].startswith('2-'))
210+ self.assertEqual(doc['stored'], {store_id2: {'copies': 0}})
211+ for doc in db.get_many(ids3):
212+ self.assertTrue(doc['_rev'].startswith('1-'))
213+ self.assertEqual(doc['stored'], {store_id3: {'copies': 1}})
214+ for doc in db.get_many(ids4):
215+ self.assertTrue(doc['_rev'].startswith('2-'))
216+ self.assertEqual(doc['stored'], {store_id4: {'copies': 0}})
217+
218+ # Test at curtime + 1:
219+ ms._calls = []
220+ self.assertEqual(ms.downgrade_by_store_atime(curtime + 1),
221+ {store_id1: 0, store_id2: 0, store_id3: 19, store_id4: 0}
222+ )
223+ self.assertEqual(ms._calls,
224+ sorted([store_id1, store_id2, store_id3, store_id4])
225+ )
226+ for doc in db.get_many(ids1):
227+ self.assertTrue(doc['_rev'].startswith('2-'))
228+ self.assertEqual(doc['stored'], {store_id1: {'copies': 0}})
229+ for doc in db.get_many(ids2):
230+ self.assertTrue(doc['_rev'].startswith('2-'))
231+ self.assertEqual(doc['stored'], {store_id2: {'copies': 0}})
232+ for doc in db.get_many(ids3):
233+ self.assertTrue(doc['_rev'].startswith('2-'))
234+ self.assertEqual(doc['stored'], {store_id3: {'copies': 0}})
235+ for doc in db.get_many(ids4):
236+ self.assertTrue(doc['_rev'].startswith('2-'))
237+ self.assertEqual(doc['stored'], {store_id4: {'copies': 0}})
238+
239+ # Make sure the dmedia/store docs aren't modified:
240+ with self.assertRaises(microfiber.NotFound) as cm:
241+ db.get(store_id1)
242+ self.assertEqual(db.get(store_id2), doc2)
243+ self.assertEqual(db.get(store_id3), doc3)
244+ self.assertEqual(db.get(store_id4), doc4)
245
246 def test_downgrade_store(self):
247 db = util.get_db(self.env, True)
248@@ -1554,7 +1628,7 @@
249 again = docs2[:4]
250 assert len(again) == 4
251 for doc in again:
252- doc['stored'][fs.id]['verified'] -= (metastore.ONE_WEEK + 1)
253+ doc['stored'][fs.id]['verified'] -= (metastore.VERIFY_THRESHOLD + 1)
254 db.save(doc)
255 self.assertEqual(ms.verify_all(fs), 4)
256 self.assertEqual(ms.verify_all(fs), 0)

Subscribers

People subscribed via source and target branches