Skip to content

Commit ba0fa6a

Browse files
authored
Merge branch 'main' into selective-snapshot-hydration
2 parents 11ed15d + c1cc16b commit ba0fa6a

3 files changed

Lines changed: 75 additions & 16 deletions

File tree

‎sqlmesh/core/model/cache.py‎

Lines changed: 47 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -112,22 +112,48 @@ def with_optimized_query(self, model: Model, name: t.Optional[str] = None) -> bo
112112
cache_entry = self._file_cache.get(name)
113113
if cache_entry:
114114
try:
115-
# If the optimized rendered query is None, then there are likely adapter calls in the query
116-
# that prevent us from rendering it at load time. This means that we can safely set the
117-
# unoptimized cache to None as well to prevent attempts to render it downstream.
118-
optimized = cache_entry.optimized_rendered_query is not None
119-
model._query_renderer.update_cache(
120-
cache_entry.optimized_rendered_query,
121-
cache_entry.renderer_violations,
122-
optimized=optimized,
123-
)
115+
self.with_optimized_query_entry(model, cache_entry)
124116
return True
125117
except Exception as ex:
126118
logger.warning("Failed to load a cache entry '%s': %s", name, ex)
127119

128120
self._put(name, model)
129121
return False
130122

123+
@staticmethod
124+
def with_optimized_query_entry(model: Model, cache_entry: OptimizedQueryCacheEntry) -> None:
125+
"""Adds an already-decoded optimized query cache entry to a model."""
126+
if not isinstance(model, SqlModel):
127+
return
128+
129+
# If the optimized rendered query is None, then there are likely adapter calls in the
130+
# query that prevent us from rendering it at load time. This means that we can safely
131+
# set the unoptimized cache to None as well to prevent attempts to render it downstream.
132+
optimized = cache_entry.optimized_rendered_query is not None
133+
model._query_renderer.update_cache(
134+
cache_entry.optimized_rendered_query,
135+
cache_entry.renderer_violations,
136+
optimized=optimized,
137+
)
138+
139+
def get_or_create_entry(
140+
self, model: Model, name: t.Optional[str] = None
141+
) -> t.Optional[OptimizedQueryCacheEntry]:
142+
"""Returns one decoded cache entry, creating it when necessary."""
143+
if not isinstance(model, SqlModel):
144+
return None
145+
146+
name = self._entry_name(model) if name is None else name
147+
cache_entry = self._file_cache.get(name)
148+
if cache_entry:
149+
try:
150+
self.with_optimized_query_entry(model, cache_entry)
151+
return cache_entry
152+
except Exception as ex:
153+
logger.warning("Failed to load a cache entry '%s': %s", name, ex)
154+
155+
return self._put(name, model)
156+
131157
def put(self, model: Model) -> t.Optional[str]:
132158
if not isinstance(model, SqlModel):
133159
return None
@@ -140,14 +166,15 @@ def put(self, model: Model) -> t.Optional[str]:
140166
self._put(name, model)
141167
return name
142168

143-
def _put(self, name: str, model: SqlModel) -> None:
169+
def _put(self, name: str, model: SqlModel) -> OptimizedQueryCacheEntry:
144170
optimized_query = model.render_query()
145171

146172
new_entry = OptimizedQueryCacheEntry(
147173
optimized_rendered_query=optimized_query,
148174
renderer_violations=model.violated_rules_for_query,
149175
)
150176
self._file_cache.put(name, value=new_entry)
177+
return new_entry
151178

152179
@staticmethod
153180
def _entry_name(model: SqlModel) -> str:
@@ -197,7 +224,13 @@ def load_optimized_query(
197224

198225
def load_optimized_query_and_mapping(
199226
model: Model, mapping: t.Dict
200-
) -> t.Tuple[str, t.Optional[str], str, str, t.Dict]:
227+
) -> t.Tuple[
228+
str,
229+
t.Optional[OptimizedQueryCacheEntry],
230+
str,
231+
str,
232+
t.Dict,
233+
]:
201234
assert _optimized_query_cache
202235

203236
schema = MappingSchema(normalize=False)
@@ -207,13 +240,13 @@ def load_optimized_query_and_mapping(
207240

208241
if isinstance(model, SqlModel):
209242
entry_name = _optimized_query_cache._entry_name(model)
210-
_optimized_query_cache.with_optimized_query(model, entry_name)
243+
cache_entry = _optimized_query_cache.get_or_create_entry(model, entry_name)
211244
else:
212-
entry_name = None
245+
cache_entry = None
213246

214247
return (
215248
model.fqn,
216-
entry_name,
249+
cache_entry,
217250
model.data_hash,
218251
model.metadata_hash,
219252
model.mapping_schema,

‎sqlmesh/core/model/schema.py‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,16 @@ def process_models(completed_model: t.Optional[Model] = None) -> None:
8787
for future in as_completed(futures):
8888
try:
8989
futures.remove(future)
90-
fqn, entry_name, data_hash, metadata_hash, mapping_schema = future.result()
90+
fqn, cache_entry, data_hash, metadata_hash, mapping_schema = future.result()
9191
model = models[fqn]
9292
model._data_hash = data_hash
9393
model._metadata_hash = metadata_hash
9494
if model.mapping_schema != mapping_schema:
9595
model.set_mapping_schema(mapping_schema)
96-
optimized_query_cache.with_optimized_query(model, entry_name)
96+
if cache_entry is not None:
97+
# The worker has already decompressed and decoded this entry. Passing
98+
# it back avoids a second gzip/pickle read in the parent process.
99+
optimized_query_cache.with_optimized_query_entry(model, cache_entry)
97100
_update_schema_with_model(schema, model)
98101
process_models(completed_model=model)
99102
except Exception as ex:

‎tests/utils/test_cache.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,29 @@ def test_optimized_query_cache(tmp_path: Path, mocker: MockerFixture):
7878
assert model._query_renderer._optimized_cache is not None
7979

8080

81+
def test_optimized_query_cache_reuses_decoded_entry(tmp_path: Path, mocker: MockerFixture):
82+
model = SqlModel(
83+
name="test_model",
84+
query=parse_one("SELECT a FROM tbl"),
85+
mapping_schema={"tbl": {"a": "int"}},
86+
)
87+
cache = OptimizedQueryCache(tmp_path)
88+
assert not cache.with_optimized_query(model)
89+
90+
get_mock = mocker.spy(cache._file_cache, "get")
91+
cache_entry = cache.get_or_create_entry(model)
92+
assert cache_entry is not None
93+
assert get_mock.call_count == 1
94+
95+
model._query_renderer._cache = []
96+
model._query_renderer._optimized_cache = None
97+
cache.with_optimized_query_entry(model, cache_entry)
98+
99+
# Applying the entry in the parent process must not read and decompress it again.
100+
assert get_mock.call_count == 1
101+
assert model._query_renderer._optimized_cache is not None
102+
103+
81104
def test_optimized_query_cache_missing_rendered_query(tmp_path: Path, mocker: MockerFixture):
82105
model = SqlModel(
83106
name="test_model",

0 commit comments

Comments
 (0)