@@ -30,8 +30,38 @@ class QueryExistingContents(Stage):
3030
3131 This stage drains all available items from `self._in_q` and batches everything into one large
3232 call to the db for efficiency.
33+
34+ When `repo_version` is provided, content from that version is cached in memory on first
35+ access (per content type) so that repeat syncs can resolve most items without per-batch
36+ database queries.
37+
38+ Plugins with expensive fields that are not needed during sync can pass
39+ `deferred_fields` - a mapping of content model class to a list of field names
40+ to exclude from queries via Django's `defer()`.
3341 """
3442
43+ def __init__ (self , repo_version = None , deferred_fields = None , * args , ** kwargs ):
44+ super ().__init__ (* args , ** kwargs )
45+ self ._repo_version = repo_version
46+ self ._deferred_fields = deferred_fields or {}
47+ self ._content_cache = {}
48+
49+ def _ensure_type_cache (self , model_type ):
50+ """
51+ Populate the cache for model_type from repo_version on first access.
52+
53+ Args:
54+ model_type: The content model class to cache.
55+ """
56+ if model_type not in self ._content_cache and self ._repo_version is not None :
57+ deferred = self ._deferred_fields .get (model_type , ())
58+ cache = {}
59+ for content in self ._repo_version .get_content (
60+ model_type .objects .defer (* deferred )
61+ ).iterator ():
62+ cache [content .natural_key ()] = content
63+ self ._content_cache [model_type ] = cache
64+
3565 async def run (self ):
3666 """
3767 The coroutine for this stage.
@@ -42,25 +72,46 @@ async def run(self):
4272 async for batch in self .batches ():
4373 content_q_by_type = defaultdict (lambda : Q (pk__in = []))
4474 d_content_by_nat_key = defaultdict (list )
75+ cache_hits_by_type = defaultdict (set )
76+
4577 for d_content in batch :
4678 if d_content .content ._state .adding :
4779 model_type = type (d_content .content )
48- unit_q = d_content .content .q ()
49- content_q_by_type [model_type ] = content_q_by_type [model_type ] | unit_q
50- d_content_by_nat_key [d_content .content .natural_key ()].append (d_content )
51-
80+ nat_key = d_content .content .natural_key ()
81+ await sync_to_async (self ._ensure_type_cache )(model_type )
82+ cached = self ._content_cache .get (model_type , {}).get (nat_key )
83+ if cached is not None :
84+ d_content .content = cached
85+ cache_hits_by_type [model_type ].add (cached .pk )
86+ else :
87+ unit_q = d_content .content .q ()
88+ content_q_by_type [model_type ] = content_q_by_type [model_type ] | unit_q
89+ d_content_by_nat_key [nat_key ].append (d_content )
90+
91+ db_results_by_type = defaultdict (list )
5292 for model_type , content_q in content_q_by_type .items ():
53- try :
54- await sync_to_async (model_type .objects .filter (content_q ).touch )()
55- except AttributeError :
56- raise TypeError (
57- "Plugins which declare custom ORM managers on their content classes "
58- "should have those managers inherit from "
59- "pulpcore.plugin.models.ContentManager."
60- )
93+ deferred = self ._deferred_fields .get (model_type , ())
6194 async for result in sync_to_async_iterable (
62- model_type .objects .filter (content_q ).iterator ()
95+ model_type .objects .filter (content_q ).defer ( * deferred ). iterator ()
6396 ):
97+ db_results_by_type [model_type ].append (result )
98+
99+ all_types = set (cache_hits_by_type .keys ()) | set (db_results_by_type .keys ())
100+ for model_type in all_types :
101+ pks = cache_hits_by_type .get (model_type , set ())
102+ pks |= {r .pk for r in db_results_by_type .get (model_type , [])}
103+ if pks :
104+ try :
105+ await sync_to_async (model_type .objects .filter (pk__in = pks ).touch )()
106+ except AttributeError :
107+ raise TypeError (
108+ "Plugins which declare custom ORM managers on their content classes "
109+ "should have those managers inherit from "
110+ "pulpcore.plugin.models.ContentManager."
111+ )
112+
113+ for model_type , results in db_results_by_type .items ():
114+ for result in results :
64115 for d_content in d_content_by_nat_key [result .natural_key ()]:
65116 d_content .content = result
66117
0 commit comments