Skip to content

Commit 1094a25

Browse files
authored
Merge pull request #428 from alexhsamuel/feature/vacuum-program
vacuum program
2 parents 0ad4333 + ba2537f commit 1094a25

File tree

7 files changed

+117
-14
lines changed

7 files changed

+117
-14
lines changed

docs/programs.rst

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ This job produces a run once a minute, which appends the stats to a dated file:
197197
Archive
198198
^^^^^^^
199199

200-
A `apsis.program.interal.archive` program moves data pertaining to older runs
201-
out of the Apsis database file, into a separate archive file. Keeping the main
202-
Apsis database file from growing too large can avoid performance degredation.
200+
An `ArchiveProgram` program moves data pertaining to older runs out of the Apsis
201+
database file, into a separate archive file. Keeping the main Apsis database
202+
file from growing too large can avoid performance degredation.
203203

204204
The archive program retires a run from Apsis's memory before archiving it. The
205205
run is no longer visible through any UI. A run that is not completed cannot be
@@ -235,3 +235,12 @@ columns from the main database file that contains run data. The archive file
235235
cannot be used directly by Apsis, but may be useful for historical analysis and
236236
forensics.
237237

238+
239+
Vacuum
240+
^^^^^^
241+
242+
A `VacuumProgram` run vacuums (defragments and frees unused pages from) the
243+
Apsis database file. The program blocks Apsis while it is running; schedule it
244+
to run only during times the scheduler is otherwise quiet.
245+
246+

python/apsis/program/internal/archive.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from ..base import _InternalProgram, ProgramRunning, ProgramSuccess
66
from apsis.lib.json import check_schema, nkey
77
from apsis.lib.parse import parse_duration
8+
from apsis.lib.timing import Timer
89
from apsis.runs import template_expand
910

1011
log = logging.getLogger(__name__)
@@ -104,9 +105,13 @@ async def wait(self, apsis):
104105

105106
row_counts = {}
106107
meta = {
107-
"run count" : 0,
108-
"run_ids" : [],
109-
"row counts": row_counts
108+
"run count" : 0,
109+
"run_ids" : [],
110+
"row counts" : row_counts,
111+
"time": {
112+
"get runs" : 0,
113+
"archive runs" : 0,
114+
}
110115
}
111116

112117
count = self.__count
@@ -115,31 +120,32 @@ async def wait(self, apsis):
115120
count if self.__chunk_size is None
116121
else min(count, self.__chunk_size)
117122
)
118-
run_ids = db.get_archive_run_ids(
119-
before =ora.now() - self.__age,
120-
count =chunk,
121-
)
123+
with Timer() as timer:
124+
run_ids = db.get_archive_run_ids(
125+
before =ora.now() - self.__age,
126+
count =chunk,
127+
)
128+
meta["time"]["get runs"] += timer.elapsed
122129
count -= chunk
123130

124131
# Make sure all runs are retired; else skip them.
125132
run_ids = [ r for r in run_ids if apsis.run_store.retire(r) ]
126133

127134
if len(run_ids) > 0:
128135
# Archive these runs.
129-
chunk_row_counts = db.archive(self.__path, run_ids)
136+
with Timer() as timer:
137+
chunk_row_counts = db.archive(self.__path, run_ids)
130138
# Accumulate metadata.
131139
meta["run count"] += len(run_ids)
132140
meta["run_ids"].append(run_ids)
133141
for key, value in chunk_row_counts.items():
134142
row_counts[key] = row_counts.get(key, 0) + value
143+
meta["time"]["archive runs"] += timer.elapsed
135144

136145
if count > 0 and self.__chunk_sleep is not None:
137146
# Yield to the event loop.
138147
await asyncio.sleep(self.__chunk_sleep)
139148

140-
# Also vacuum to free space.
141-
db.vacuum()
142-
143149
return ProgramSuccess(meta=meta)
144150

145151

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import logging
2+
3+
from ..base import _InternalProgram, ProgramRunning, ProgramSuccess
4+
from apsis.lib.json import check_schema
5+
from apsis.lib.timing import Timer
6+
7+
log = logging.getLogger(__name__)
8+
9+
#-------------------------------------------------------------------------------
10+
11+
class VacuumProgram(_InternalProgram):
12+
"""
13+
A program that defragments the Apsis database.
14+
15+
This program runs within the Apsis process, and blocks all other activities
16+
while it runs.
17+
"""
18+
19+
def __str__(self):
20+
return "vacuum database"
21+
22+
23+
def bind(self, args):
24+
return self
25+
26+
27+
def to_jso(self):
28+
return {
29+
**super().to_jso()
30+
}
31+
32+
33+
@classmethod
34+
def from_jso(cls, jso):
35+
with check_schema(jso) as pop:
36+
pass
37+
return cls()
38+
39+
40+
async def start(self, run_id, apsis):
41+
return ProgramRunning({}), self.wait(apsis)
42+
43+
44+
async def wait(self, apsis):
45+
# FIXME: Private attributes.
46+
db = apsis._Apsis__db
47+
48+
with Timer() as timer:
49+
db.vacuum()
50+
51+
meta = {
52+
"time": timer.elapsed,
53+
}
54+
return ProgramSuccess(meta=meta)
55+
56+
57+

test/int/test_archive.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,23 @@ def test_clean_up_jobs(tmp_path):
219219
assert job_ids == {job_id2, archive_job_id}
220220

221221

222+
def test_vacuum():
223+
with closing(ApsisService()) as inst:
224+
inst.create_db()
225+
inst.write_cfg()
226+
inst.start_serve()
227+
inst.wait_for_serve()
228+
229+
client = inst.client
230+
231+
res = client.schedule_adhoc("now", {
232+
"program": {
233+
"type": "apsis.program.internal.vacuum.VacuumProgram",
234+
},
235+
})
236+
res = inst.wait_run(res["run_id"])
237+
assert res["state"] == "success"
238+
meta = res["meta"]["program"]
239+
assert meta["time"] > 0
240+
241+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
*.db
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
params: [label, age]
2+
3+
program:
4+
type: apsis.program.internal.archive.ArchiveProgram
5+
age: "{{ age }}"
6+
path: "/home/alex/dev/apsis/test/manual/procstar/archive/{{ label }}.db"
7+
count: 1000000
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
program:
2+
type: apsis.program.internal.vacuum.VacuumProgram
3+

0 commit comments

Comments
 (0)