performance: add a verification file to the database (#14693)

Reading the database repeatedly can be quite slow.  We need a way to speed
up Spack when it reads the DB multiple times, but the DB has not been
modified between reads (which is nearly all the time).

- [x] Add a file containing a unique uuid that is regenerated at database
    write time. Use this uuid to suppress re-parsing the database
    contents if we know a previous uuid and the uuid has not changed.

- [x] Fix mutable_database fixture so that it resets the last seen
    verifier when it resets.

- [x] Enable not rereading the database immediately after a write. Make
    the tests reset the last seen verifier in between tests that use the
    database fixture.

- [x] make presence of uuid module optional
This commit is contained in:
Andrew W Elble 2020-03-20 20:05:51 -04:00 committed by Todd Gamblin
parent 9b5805a5cd
commit 6b559912c1
3 changed files with 52 additions and 3 deletions

View File

@ -26,6 +26,12 @@
import socket import socket
import sys import sys
import time import time
try:
import uuid
_use_uuid = True
except ImportError:
_use_uuid = False
pass
import llnl.util.tty as tty import llnl.util.tty as tty
import six import six
@ -294,6 +300,7 @@ def __init__(self, root, db_dir=None, upstream_dbs=None,
# Set up layout of database files within the db dir # Set up layout of database files within the db dir
self._index_path = os.path.join(self._db_dir, 'index.json') self._index_path = os.path.join(self._db_dir, 'index.json')
self._verifier_path = os.path.join(self._db_dir, 'index_verifier')
self._lock_path = os.path.join(self._db_dir, 'lock') self._lock_path = os.path.join(self._db_dir, 'lock')
# This is for other classes to use to lock prefix directories. # This is for other classes to use to lock prefix directories.
@ -315,6 +322,7 @@ def __init__(self, root, db_dir=None, upstream_dbs=None,
mkdirp(self._failure_dir) mkdirp(self._failure_dir)
self.is_upstream = is_upstream self.is_upstream = is_upstream
self.last_seen_verifier = ''
# initialize rest of state. # initialize rest of state.
self.db_lock_timeout = ( self.db_lock_timeout = (
@ -913,6 +921,11 @@ def _write(self, type, value, traceback):
with open(temp_file, 'w') as f: with open(temp_file, 'w') as f:
self._write_to_file(f) self._write_to_file(f)
os.rename(temp_file, self._index_path) os.rename(temp_file, self._index_path)
if _use_uuid:
with open(self._verifier_path, 'w') as f:
new_verifier = str(uuid.uuid4())
f.write(new_verifier)
self.last_seen_verifier = new_verifier
except BaseException as e: except BaseException as e:
tty.debug(e) tty.debug(e)
# Clean up temp file if something goes wrong. # Clean up temp file if something goes wrong.
@ -928,6 +941,16 @@ def _read(self):
write lock. write lock.
""" """
if os.path.isfile(self._index_path): if os.path.isfile(self._index_path):
current_verifier = ''
if _use_uuid:
try:
with open(self._verifier_path, 'r') as f:
current_verifier = f.read()
except BaseException:
pass
if ((current_verifier != self.last_seen_verifier) or
(current_verifier == '')):
self.last_seen_verifier = current_verifier
# Read from file if a database exists # Read from file if a database exists
self._read_from_file(self._index_path) self._read_from_file(self._index_path)
return return

View File

@ -525,6 +525,8 @@ def database(mock_store, mock_packages, config):
"""This activates the mock store, packages, AND config.""" """This activates the mock store, packages, AND config."""
with use_store(mock_store): with use_store(mock_store):
yield mock_store.db yield mock_store.db
# Force reading the database again between tests
mock_store.db.last_seen_verifier = ''
@pytest.fixture(scope='function') @pytest.fixture(scope='function')

View File

@ -13,6 +13,12 @@
import os import os
import pytest import pytest
import json import json
try:
import uuid
_use_uuid = True
except ImportError:
_use_uuid = False
pass
import llnl.util.lock as lk import llnl.util.lock as lk
from llnl.util.tty.colify import colify from llnl.util.tty.colify import colify
@ -469,6 +475,21 @@ def test_015_write_and_read(mutable_database):
assert new_rec.installed == rec.installed assert new_rec.installed == rec.installed
def test_017_write_and_read_without_uuid(mutable_database, monkeypatch):
monkeypatch.setattr(spack.database, '_use_uuid', False)
# write and read DB
with spack.store.db.write_transaction():
specs = spack.store.db.query()
recs = [spack.store.db.get_record(s) for s in specs]
for spec, rec in zip(specs, recs):
new_rec = spack.store.db.get_record(spec)
assert new_rec.ref_count == rec.ref_count
assert new_rec.spec == rec.spec
assert new_rec.path == rec.path
assert new_rec.installed == rec.installed
def test_020_db_sanity(database): def test_020_db_sanity(database):
"""Make sure query() returns what's actually in the db.""" """Make sure query() returns what's actually in the db."""
_check_db_sanity(database) _check_db_sanity(database)
@ -703,6 +724,9 @@ def test_old_external_entries_prefix(mutable_database):
with open(spack.store.db._index_path, 'w') as f: with open(spack.store.db._index_path, 'w') as f:
f.write(json.dumps(db_obj)) f.write(json.dumps(db_obj))
if _use_uuid:
with open(spack.store.db._verifier_path, 'w') as f:
f.write(str(uuid.uuid4()))
record = spack.store.db.get_record(s) record = spack.store.db.get_record(s)