]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
simplify relationship caching options
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 8 Jun 2021 19:43:13 +0000 (15:43 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Wed, 9 Jun 2021 14:01:13 +0000 (10:01 -0400)
Clarified the current purpose of the
:paramref:`_orm.relationship.bake_queries` flag, which in 1.4 is to enable
or disable "lambda caching" of statements within the "lazyload" and
"selectinload" loader strategies; this is separate from the more
foundational SQL query cache that is used for most statements.
Additionally, the lazy loader no longer uses its own cache for many-to-one
SQL queries, which was an implementation quirk that doesn't exist for any
other loader scenario. Finally, the "lru cache" warning that the lazyloader
and selectinloader strategies could emit when handling a wide array of
class/relationship combinations has been removed; based on analysis of some
end-user cases, this warning doesn't suggest any significant issue. While
setting ``bake_queries=False`` for such a relationship will remove this
cache from being used, there's no particular performance gain in this case
as using no caching vs. using a cache that needs to refresh often likely
still wins out on the caching being used side.

Fixes: #6072
Fixes: #6487
Change-Id: Ida61f09b837d3acdafa07344d7d747d7f3ab226a

doc/build/changelog/unreleased_14/6072.rst [new file with mode: 0644]
lib/sqlalchemy/orm/relationships.py
lib/sqlalchemy/orm/strategies.py
test/aaa_profiling/test_orm.py
test/orm/test_cache_key.py
test/orm/test_subquery_relations.py

diff --git a/doc/build/changelog/unreleased_14/6072.rst b/doc/build/changelog/unreleased_14/6072.rst
new file mode 100644 (file)
index 0000000..29e3294
--- /dev/null
@@ -0,0 +1,20 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 6072, 6487
+
+    Clarified the current purpose of the
+    :paramref:`_orm.relationship.bake_queries` flag, which in 1.4 is to enable
+    or disable "lambda caching" of statements within the "lazyload" and
+    "selectinload" loader strategies; this is separate from the more
+    foundational SQL query cache that is used for most statements.
+    Additionally, the lazy loader no longer uses its own cache for many-to-one
+    SQL queries, which was an implementation quirk that doesn't exist for any
+    other loader scenario. Finally, the "lru cache" warning that the lazyloader
+    and selectinloader strategies could emit when handling a wide array of
+    class/relationship combinations has been removed; based on analysis of some
+    end-user cases, this warning doesn't suggest any significant issue. While
+    setting ``bake_queries=False`` for such a relationship will remove this
+    cache from being used, there's no particular performance gain in this case
+    as using no caching vs. using a cache that needs to refresh often likely
+    still wins out on the caching being used side.
+
index 2d1ed6ced8be9487a0602e73feb7a9ca976e3768..89cc37de59e05ee4c5719865d38ae6dd6bf728b1 100644 (file)
@@ -364,20 +364,20 @@ class RelationshipProperty(StrategizedProperty):
                 :ref:`error_qzyx` - usage example
 
         :param bake_queries=True:
-          Use the :class:`.BakedQuery` cache to cache the construction of SQL
-          used in lazy loads.  True by default.   Set to False if the
-          join condition of the relationship has unusual features that
-          might not respond well to statement caching.
-
-          .. versionchanged:: 1.2
-             "Baked" loading is the default implementation for the "select",
-             a.k.a. "lazy" loading strategy for relationships.
-
-          .. versionadded:: 1.0.0
-
-          .. seealso::
+          Enable :ref:`lambda caching <engine_lambda_caching>`_ for loader
+          strategies, if applicable, which adds a performance gain to the
+          construction of SQL constructs used by loader strategies, in addition
+          to the usual SQL statement caching used throughout SQLAlchemy. This
+          parameter currently applies only to the "lazy" and "selectin" loader
+          strategies. There is generally no reason to set this parameter to
+          False.
 
-            :ref:`baked_toplevel`
+          .. versionchanged:: 1.4  Relationship loaders no longer use the
+             previous "baked query" system of query caching.   The "lazy"
+             and "selectin" loaders make use of the "lambda cache" system
+             for the construction of SQL constructs,
+             as well as the usual SQL caching system that is throughout
+             SQLAlchemy as of the 1.4 series.
 
         :param cascade:
           A comma-separated list of cascade rules which determines how
index dca45730c5597d593f39f1823cdcfde389223c9b..4a8ebaabb4ed93c3fa35ae4aac849f1786235ab4 100644 (file)
@@ -547,9 +547,6 @@ class AbstractRelationshipLoader(LoaderStrategy):
         self.target = self.parent_property.target
         self.uselist = self.parent_property.uselist
 
-    def _size_alert(self, lru_cache):
-        util.warn("LRU cache size alert for loader strategy: %s" % self)
-
 
 @log.class_logger
 @relationships.RelationshipProperty.strategy_for(do_nothing=True)
@@ -630,7 +627,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         "_simple_lazy_clause",
         "_raise_always",
         "_raise_on_sql",
-        "_query_cache",
+        "_lambda_cache",
     )
 
     def __init__(self, parent, strategy_key):
@@ -899,12 +896,12 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             for pk in self.mapper.primary_key
         ]
 
-    def _memoized_attr__query_cache(self):
-        # cache is per lazy loader; stores not only cached SQL but also
+    def _memoized_attr__lambda_cache(self):
+        # cache is per lazy loader, and is used for caching of
         # sqlalchemy.sql.lambdas.AnalyzedCode and
         # sqlalchemy.sql.lambdas.AnalyzedFunction objects which are generated
         # from the StatementLambda used.
-        return util.LRUCache(30, size_alert=self._size_alert)
+        return util.LRUCache(30)
 
     @util.preload_module("sqlalchemy.orm.strategy_options")
     def _emit_lazyload(
@@ -923,7 +920,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
             .set_label_style(LABEL_STYLE_TABLENAME_PLUS_COL)
             ._set_compile_options(ORMCompileState.default_compile_options),
             global_track_bound_values=False,
-            lambda_cache=self._query_cache,
+            lambda_cache=self._lambda_cache,
             track_on=(self,),
         )
 
@@ -979,11 +976,7 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
                 self._invoke_raise_load(state, passive, "raise_on_sql")
 
             return loading.load_on_pk_identity(
-                session,
-                stmt,
-                primary_key_identity,
-                load_options=load_options,
-                execution_options={"compiled_cache": self._query_cache},
+                session, stmt, primary_key_identity, load_options=load_options
             )
 
         if self._order_by:
@@ -1018,8 +1011,6 @@ class LazyLoader(AbstractRelationshipLoader, util.MemoizedSlots):
         execution_options = {
             "_sa_orm_load_options": load_options,
         }
-        if not self.parent_property.bake_queries:
-            execution_options["compiled_cache"] = None
 
         if self.key in state.dict:
             return attributes.ATTR_WAS_SET
@@ -1592,10 +1583,7 @@ class SubqueryLoader(PostLoader):
 
             q = self.subq
             assert q.session is None
-            if "compiled_cache" in self.execution_options:
-                q = q.execution_options(
-                    compiled_cache=self.execution_options["compiled_cache"]
-                )
+
             q = q.with_session(self.session)
 
             if self.load_options._populate_existing:
@@ -2626,7 +2614,7 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
         "_parent_alias",
         "_query_info",
         "_fallback_query_info",
-        "_query_cache",
+        "_lambda_cache",
     )
 
     query_info = collections.namedtuple(
@@ -2730,8 +2718,12 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
             (("lazy", "select"),)
         ).init_class_attribute(mapper)
 
-    def _memoized_attr__query_cache(self):
-        return util.LRUCache(30, size_alert=self._size_alert)
+    def _memoized_attr__lambda_cache(self):
+        # cache is per lazy loader, and is used for caching of
+        # sqlalchemy.sql.lambdas.AnalyzedCode and
+        # sqlalchemy.sql.lambdas.AnalyzedFunction objects which are generated
+        # from the StatementLambda used.
+        return util.LRUCache(30)
 
     def create_row_processor(
         self,
@@ -2879,7 +2871,7 @@ class SelectInLoader(PostLoader, util.MemoizedSlots):
                     "plugin_subject": effective_entity,
                 }
             ),
-            lambda_cache=self._query_cache,
+            lambda_cache=self._lambda_cache,
             global_track_bound_values=False,
             track_on=(self, effective_entity) + (tuple(pk_cols),),
         )
index 356ea252d8d9ff55d32f9ae6531e2615e3b0d458..7de1811aed7694ddb48bc2b458d016d3bf4fc8a0 100644 (file)
@@ -155,7 +155,7 @@ class MergeTest(NoCache, fixtures.MappedTest):
         self.assert_sql_count(testing.db, go2, 2)
 
 
-class LoadManyToOneFromIdentityTest(NoCache, fixtures.MappedTest):
+class LoadManyToOneFromIdentityTest(fixtures.MappedTest):
 
     """test overhead associated with many-to-one fetches.
 
index 390fc514d3acf68f8dbba3e4ef74568a722107f4..67f2d0230616d62191a40216173d8a49fb15b365 100644 (file)
@@ -712,9 +712,9 @@ class RoundTripTest(QueryTest, AssertsCompiledSQL):
         sess = Session(connection)
 
         with mock.patch(
-            "sqlalchemy.orm.strategies.LazyLoader._query_cache", cache
+            "sqlalchemy.orm.strategies.LazyLoader._lambda_cache", cache
         ), mock.patch(
-            "sqlalchemy.orm.strategies.SelectInLoader._query_cache", cache
+            "sqlalchemy.orm.strategies.SelectInLoader._lambda_cache", cache
         ):
 
             def go():
index 96106b49df4b580d6d78238e7de06549bcfe67b6..512ec0fe2bffb340fb3b7b0b94d26fef6e1158ac 100644 (file)
@@ -89,47 +89,6 @@ class EagerTest(_fixtures.FixtureTest, testing.AssertsCompiledSQL):
 
         self.assert_sql_count(testing.db, go, 2)
 
-    def test_query_is_cached(self):
-        users, Address, addresses, User = (
-            self.tables.users,
-            self.classes.Address,
-            self.tables.addresses,
-            self.classes.User,
-        )
-
-        mapper(
-            User,
-            users,
-            properties={
-                "addresses": relationship(
-                    mapper(Address, addresses),
-                    lazy="subquery",
-                    order_by=Address.id,
-                )
-            },
-        )
-        query_cache = {}
-        sess = fixture_session()
-
-        def go():
-            sess.close()
-
-            stmt = select(User).filter(User.id == 7)
-
-            sess.execute(
-                stmt, execution_options={"compiled_cache": query_cache}
-            ).one()
-
-        for i in range(3):
-            go()
-
-        qclen = len(query_cache)
-
-        for i in range(5):
-            go()
-
-        eq_(len(query_cache), qclen)
-
     def test_params_arent_cached(self):
         users, Address, addresses, User = (
             self.tables.users,