Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache LRU removal #14054

Merged
merged 38 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
86da95c
wip lru
memsharded Jun 5, 2023
fdd7fd1
Merge branch 'release/2.0' into feature/lru
memsharded Jun 8, 2023
10677dc
working
memsharded Jun 8, 2023
077a385
wip
memsharded Jun 8, 2023
b05ed35
Merge branch 'release/2.0' into feature/lru
memsharded Jun 9, 2023
62d035e
column order
memsharded Jun 9, 2023
37aadbf
column order
memsharded Jun 9, 2023
712843c
Merge branch 'release/2.0' into feature/lru
memsharded Jun 13, 2023
9254d13
wip
memsharded Jun 13, 2023
da239ed
remove pkg-lists
memsharded Jun 13, 2023
5765352
Merge branch 'release/2.0' into feature/remove_pkg_lists
memsharded Jun 13, 2023
3e44054
add tests
memsharded Jun 13, 2023
c43f69e
wip
memsharded Jun 13, 2023
69f2c66
wip
memsharded Jun 13, 2023
ab718db
wip
memsharded Jun 14, 2023
b393b2e
Merge branch 'release/2.0' into feature/remove_pkg_lists
memsharded Jun 18, 2023
faa2055
Merge branch 'feature/remove_pkg_lists' into feature/lru
memsharded Jun 18, 2023
5e7f476
added 'conan remove -lru' argument
memsharded Jun 18, 2023
8859601
merged release2
memsharded Jun 19, 2023
856ae24
rmove unused imports
memsharded Jun 19, 2023
572e338
Merge branch 'release/2.0' into feature/lru
memsharded Jul 5, 2023
9f30f0e
add migration
memsharded Jul 5, 2023
efffbf2
fix lru migration tests
memsharded Jul 5, 2023
ab64bd6
fixed drop lru column test
memsharded Jul 5, 2023
f61225b
improve docstring lru arg
memsharded Jul 7, 2023
e9c434f
Merge branch 'release/2.0' into feature/lru
memsharded Jul 25, 2023
e5c98ad
wip
memsharded Jul 25, 2023
01392f2
wip
memsharded Jul 25, 2023
c8abe31
wip
memsharded Jul 25, 2023
dea98ea
fix tests
memsharded Jul 25, 2023
bbc2c30
merged release/2.0
memsharded Oct 9, 2023
42aa2f9
wip
memsharded Oct 9, 2023
983bc4b
wip
memsharded Oct 10, 2023
6e6f5ba
more messages
memsharded Oct 10, 2023
929a6f3
Merge branch 'release/2.0' into feature/lru
memsharded Oct 11, 2023
002837d
wip
memsharded Oct 11, 2023
eb641d2
solve conflicts
memsharded Oct 17, 2023
7040e35
fix test
memsharded Oct 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion conan/api/subapi/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from conans.model.package_ref import PkgReference
from conans.model.recipe_ref import RecipeReference
from conans.search.search import get_cache_packages_binary_info, filter_packages
from conans.util.dates import timelimit


class ListAPI:
Expand Down Expand Up @@ -84,13 +85,18 @@ def filter_packages_configurations(pkg_configurations, query):
"""
return filter_packages(query, pkg_configurations)

def select(self, pattern, package_query=None, remote=None):
def select(self, pattern, package_query=None, remote=None, lru=None):
if package_query and pattern.package_id and "*" not in pattern.package_id:
raise ConanException("Cannot specify '-p' package queries, "
"if 'package_id' is not a pattern")
if remote and lru:
raise ConanException("'--lru' cannot be used in remotes, only in cache")

select_bundle = PackagesList()
# Avoid doing a ``search`` of recipes if it is an exact ref and it will be used later
search_ref = pattern.search_ref
app = ConanApp(self.conan_api.cache_folder)
limit_time = timelimit(lru) if lru else None
if search_ref:
refs = self.conan_api.search.recipes(search_ref, remote=remote)
refs = pattern.filter_versions(refs)
Expand All @@ -114,6 +120,10 @@ def select(self, pattern, package_query=None, remote=None):
rrevs = self.recipe_revisions(r, remote)
rrevs = pattern.filter_rrevs(rrevs)
rrevs = list(reversed(rrevs)) # Order older revisions first

if lru and pattern.package_id is None: # Filter LRUs
rrevs = [r for r in rrevs if app.cache.get_recipe_lru(r) < limit_time]

select_bundle.add_refs(rrevs)

if pattern.package_id is None: # Stop if not displaying binaries
Expand Down Expand Up @@ -148,6 +158,9 @@ def select(self, pattern, package_query=None, remote=None):
new_prefs.extend(prevs)
prefs = new_prefs

if lru: # Filter LRUs
prefs = [r for r in prefs if app.cache.get_package_lru(r) < limit_time]

select_bundle.add_prefs(rrev, prefs)
select_bundle.add_configurations(packages)
return select_bundle
11 changes: 10 additions & 1 deletion conan/cli/commands/list.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,15 +108,23 @@ def list(conan_api: ConanAPI, parser, *args):
parser.add_argument("-g", "--graph", help="Graph json file")
parser.add_argument("-gb", "--graph-binaries", help="Which binaries are listed", action="append")
parser.add_argument("-gr", "--graph-recipes", help="Which recipes are listed", action="append")
parser.add_argument('--lru', default=None, action=OnceArgument,
help="List recipes and binaries that have not been recently used. Use a"
" time limit like --lru=5d (days) or --lru=4w (weeks),"
" h (hours), m(minutes)")

args = parser.parse_args(*args)

if args.pattern is None and args.graph is None:
raise ConanException("Missing pattern or graph json file")
if args.pattern and args.graph:
raise ConanException("Cannot define both the pattern and the graph json file")
if args.graph and args.lru:
raise ConanException("Cannot define lru when loading a graph json file")
if (args.graph_recipes or args.graph_binaries) and not args.graph:
raise ConanException("--graph-recipes and --graph-binaries require a --graph input")
if args.remote and args.lru:
raise ConanException("'--lru' cannot be used in remotes, only in cache")

if args.graph:
graphfile = make_abs_path(args.graph)
Expand All @@ -127,7 +135,8 @@ def list(conan_api: ConanAPI, parser, *args):
pkglist = MultiPackagesList()
if args.cache or not args.remote:
try:
cache_list = conan_api.list.select(ref_pattern, args.package_query, remote=None)
cache_list = conan_api.list.select(ref_pattern, args.package_query, remote=None,
lru=args.lru)
except Exception as e:
pkglist.add_error("Local Cache", str(e))
else:
Expand Down
10 changes: 9 additions & 1 deletion conan/cli/commands/remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ def remove(conan_api: ConanAPI, parser, *args):
parser.add_argument('-r', '--remote', action=OnceArgument,
help='Will remove from the specified remote')
parser.add_argument("-l", "--list", help="Package list file")
parser.add_argument('--lru', default=None, action=OnceArgument,
help="Remove recipes and binaries that have not been recently used. Use a"
" time limit like --lru=5d (days) or --lru=4w (weeks),"
" h (hours), m(minutes)")
parser.add_argument("--dry-run", default=False, action="store_true",
help="Do not remove any items, only print those which would be removed")
args = parser.parse_args(*args)
Expand All @@ -61,6 +65,10 @@ def remove(conan_api: ConanAPI, parser, *args):
raise ConanException("Cannot define both the pattern and the package list file")
if args.package_query and args.list:
raise ConanException("Cannot define package-query and the package list file")
if args.remote and args.lru:
raise ConanException("'--lru' cannot be used in remotes, only in cache")
if args.list and args.lru:
raise ConanException("'--lru' cannot be used with input package list")

ui = UserInput(conan_api.config.get("core:non_interactive"))
remote = conan_api.remotes.get(args.remote) if args.remote else None
Expand All @@ -80,7 +88,7 @@ def confirmation(message):
ref_pattern = ListPattern(args.pattern, rrev="*", prev="*")
if ref_pattern.package_id is None and args.package_query is not None:
raise ConanException('--package-query supplied but the pattern does not match packages')
package_list = conan_api.list.select(ref_pattern, args.package_query, remote)
package_list = conan_api.list.select(ref_pattern, args.package_query, remote, lru=args.lru)
multi_package_list = MultiPackagesList()
multi_package_list.add(cache_name, package_list)

Expand Down
12 changes: 12 additions & 0 deletions conan/internal/cache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,3 +232,15 @@ def assign_rrev(self, layout: RecipeLayout):
# This was exported before, making it latest again, update timestamp
ref = layout.reference
self._db.update_recipe_timestamp(ref)

def get_recipe_lru(self, ref):
return self._db.get_recipe_lru(ref)

def update_recipe_lru(self, ref):
self._db.update_recipe_lru(ref)

def get_package_lru(self, pref):
return self._db.get_package_lru(pref)

def update_package_lru(self, pref):
self._db.update_package_lru(pref)
12 changes: 12 additions & 0 deletions conan/internal/cache/db/cache_database.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ def update_recipe_timestamp(self, ref):
def update_package_timestamp(self, pref: PkgReference, path: str, build_id: str):
self._packages.update_timestamp(pref, path=path, build_id=build_id)

def get_recipe_lru(self, ref):
return self._recipes.get_recipe(ref)["lru"]

def get_package_lru(self, pref: PkgReference):
return self._packages.get(pref)["lru"]

def update_recipe_lru(self, ref):
self._recipes.update_lru(ref)

def update_package_lru(self, pref):
self._packages.update_lru(pref)

def remove_recipe(self, ref: RecipeReference):
# Removing the recipe must remove all the package binaries too from DB
self._recipes.remove(ref)
Expand Down
30 changes: 24 additions & 6 deletions conan/internal/cache/db/packages_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from conans.errors import ConanReferenceDoesNotExistInDB, ConanReferenceAlreadyExistsInDB
from conans.model.package_ref import PkgReference
from conans.model.recipe_ref import RecipeReference
from conans.util.dates import timestamp_now


class PackagesDBTable(BaseDbTable):
Expand All @@ -14,7 +15,8 @@ class PackagesDBTable(BaseDbTable):
('prev', str, True),
('path', str, False, None, True),
('timestamp', float),
('build_id', str, True)]
('build_id', str, True),
('lru', int)]
unique_together = ('reference', 'rrev', 'pkgid', 'prev')

@staticmethod
Expand All @@ -26,6 +28,7 @@ def _as_dict(row):
"pref": pref,
"build_id": row.build_id,
"path": row.path,
"lru": row.lru
}

def _where_clause(self, pref: PkgReference):
Expand Down Expand Up @@ -73,12 +76,13 @@ def create(self, path, pref: PkgReference, build_id):
# are saved with the temporary uuid one, we don't want to consider these
# not yet built packages for search and so on
placeholders = ', '.join(['?' for _ in range(len(self.columns))])
lru = timestamp_now()
with self.db_connection() as conn:
try:
conn.execute(f'INSERT INTO {self.table_name} '
f'VALUES ({placeholders})',
[str(pref.ref), pref.ref.revision, pref.package_id, pref.revision,
path, pref.timestamp, build_id])
f'VALUES ({placeholders})',
[str(pref.ref), pref.ref.revision, pref.package_id, pref.revision,
path, pref.timestamp, build_id, lru])
except sqlite3.IntegrityError:
raise ConanReferenceAlreadyExistsInDB(f"Reference '{repr(pref)}' already exists")

Expand All @@ -96,6 +100,18 @@ def update_timestamp(self, pref: PkgReference, path: str, build_id: str):
except sqlite3.IntegrityError:
raise ConanReferenceAlreadyExistsInDB(f"Reference '{repr(pref)}' already exists")

def update_lru(self, pref):
assert pref.revision is not None
# TODO: InstallGraph is dropping the pref.timestamp, cannot be checked here yet
# assert pref.timestamp is not None, f"PREF _TIMESSTAMP IS NONE {repr(pref)}"
where_clause = self._where_clause(pref)
lru = timestamp_now()
query = f"UPDATE {self.table_name} " \
f'SET {self.columns.lru} = "{lru}" ' \
f"WHERE {where_clause};"
with self.db_connection() as conn:
conn.execute(query)

def remove_build_id(self, pref):
where_clause = self._where_clause(pref)
query = f"UPDATE {self.table_name} " \
Expand Down Expand Up @@ -133,7 +149,8 @@ def get_package_revisions_references(self, pref: PkgReference, only_latest_prev=
f'{self.columns.prev}, ' \
f'{self.columns.path}, ' \
f'MAX({self.columns.timestamp}), ' \
f'{self.columns.build_id} ' \
f'{self.columns.build_id}, ' \
f'{self.columns.lru} ' \
f'FROM {self.table_name} ' \
f'WHERE {self.columns.rrev} = "{pref.ref.revision}" ' \
f'AND {self.columns.reference} = "{str(pref.ref)}" ' \
Expand Down Expand Up @@ -165,7 +182,8 @@ def get_package_references(self, ref: RecipeReference, only_latest_prev=True):
f'{self.columns.prev}, ' \
f'{self.columns.path}, ' \
f'MAX({self.columns.timestamp}), ' \
f'{self.columns.build_id} ' \
f'{self.columns.build_id}, ' \
f'{self.columns.lru} ' \
f'FROM {self.table_name} ' \
f'WHERE {self.columns.rrev} = "{ref.revision}" ' \
f'AND {self.columns.reference} = "{str(ref)}" ' \
Expand Down
26 changes: 22 additions & 4 deletions conan/internal/cache/db/recipes_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@
from conan.internal.cache.db.table import BaseDbTable
from conans.errors import ConanReferenceDoesNotExistInDB, ConanReferenceAlreadyExistsInDB
from conans.model.recipe_ref import RecipeReference
from conans.util.dates import timestamp_now


class RecipesDBTable(BaseDbTable):
table_name = 'recipes'
columns_description = [('reference', str),
('rrev', str),
('path', str, False, None, True),
('timestamp', float)]
('timestamp', float),
('lru', int)]
unique_together = ('reference', 'rrev')

@staticmethod
Expand All @@ -21,6 +23,7 @@ def _as_dict(row):
return {
"ref": ref,
"path": row.path,
"lru": row.lru,
}

def _where_clause(self, ref):
Expand All @@ -38,11 +41,12 @@ def create(self, path, ref: RecipeReference):
assert ref.revision is not None
assert ref.timestamp is not None
placeholders = ', '.join(['?' for _ in range(len(self.columns))])
lru = timestamp_now()
with self.db_connection() as conn:
try:
conn.execute(f'INSERT INTO {self.table_name} '
f'VALUES ({placeholders})',
[str(ref), ref.revision, path, ref.timestamp])
[str(ref), ref.revision, path, ref.timestamp, lru])
except sqlite3.IntegrityError:
raise ConanReferenceAlreadyExistsInDB(f"Reference '{repr(ref)}' already exists")

Expand All @@ -56,6 +60,17 @@ def update_timestamp(self, ref: RecipeReference):
with self.db_connection() as conn:
conn.execute(query)

def update_lru(self, ref):
assert ref.revision is not None
assert ref.timestamp is not None
where_clause = self._where_clause(ref)
lru = timestamp_now()
query = f"UPDATE {self.table_name} " \
f'SET {self.columns.lru} = "{lru}" ' \
f"WHERE {where_clause};"
with self.db_connection() as conn:
conn.execute(query)

def remove(self, ref: RecipeReference):
where_clause = self._where_clause(ref)
query = f"DELETE FROM {self.table_name} " \
Expand All @@ -68,9 +83,11 @@ def all_references(self):
query = f'SELECT DISTINCT {self.columns.reference}, ' \
f'{self.columns.rrev}, ' \
f'{self.columns.path} ,' \
f'{self.columns.timestamp} ' \
f'{self.columns.timestamp}, ' \
f'{self.columns.lru} ' \
f'FROM {self.table_name} ' \
f'ORDER BY {self.columns.timestamp} DESC'

with self.db_connection() as conn:
r = conn.execute(query)
result = [self._as_dict(self.row_type(*row)) for row in r.fetchall()]
Expand All @@ -92,7 +109,8 @@ def get_latest_recipe(self, ref: RecipeReference):
query = f'SELECT {self.columns.reference}, ' \
f'{self.columns.rrev}, ' \
f'{self.columns.path}, ' \
f'MAX({self.columns.timestamp}) ' \
f'MAX({self.columns.timestamp}), ' \
f'{self.columns.lru} ' \
f'FROM {self.table_name} ' \
f'WHERE {self.columns.reference} = "{str(ref)}" ' \
f'GROUP BY {self.columns.reference} ' # OTHERWISE IT FAILS THE MAX()
Expand Down
12 changes: 12 additions & 0 deletions conans/client/cache/cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,18 @@ def get_matching_build_id(self, ref, build_id):
def get_latest_package_reference(self, pref):
return self._data_cache.get_latest_package_reference(pref)

def get_recipe_lru(self, ref):
return self._data_cache.get_recipe_lru(ref)

def update_recipe_lru(self, ref):
self._data_cache.update_recipe_lru(ref)

def get_package_lru(self, pref):
return self._data_cache.get_package_lru(pref)

def update_package_lru(self, pref):
self._data_cache.update_package_lru(pref)

@property
def store(self):
return self._store_folder
Expand Down
2 changes: 2 additions & 0 deletions conans/client/graph/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ def _get_recipe(self, reference, remotes, update, check_update):
conanfile_path = recipe_layout.conanfile()
return conanfile_path, status, remote, new_ref

self._cache.update_recipe_lru(ref)

# TODO: cache2.0: check with new --update flows
conanfile_path = recipe_layout.conanfile()

Expand Down
1 change: 1 addition & 0 deletions conans/client/installer.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ def _handle_package(self, package, install_reference, handled_count, total_count
elif package.binary == BINARY_CACHE:
node = package.nodes[0]
pref = node.pref
self._cache.update_package_lru(pref)
assert node.prev, "PREV for %s is None" % str(pref)
node.conanfile.output.success(f'Already installed! ({handled_count} of {total_count})')

Expand Down
Loading