summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/testing/util.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2022-11-10 17:01:58 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2022-11-11 15:25:35 -0500
commit1d8833a9c1ada64cfc716109a8836b32cb8a9bd6 (patch)
treeae5d2f6adfb578c43603b0141c27fad520c70e4f /lib/sqlalchemy/testing/util.py
parente3a8d198917f4246365e09fa975d55c64082cd2e (diff)
downloadsqlalchemy-1d8833a9c1ada64cfc716109a8836b32cb8a9bd6.tar.gz
ensure anon_map is passed for most annotated traversals
We can cache the annotated cache key for Table, but for selectables it's not safe, as it fails to pass the anon_map along and creates many redudant structures in observed test scenario. It is likely safe for a Column that's mapped to a Table also, however this is not implemented here. Will have to see if that part needs adjusting. Fixed critical memory issue identified in cache key generation, where for very large and complex ORM statements that make use of lots of ORM aliases with subqueries, cache key generation could produce excessively large keys that were orders of magnitude bigger than the statement itself. Much thanks to Rollo Konig Brock for their very patient, long term help in finally identifying this issue. Also within TypeEngine objects, when we generate elements for instance variables, skip the None elements at least. this also saves on tuple complexity. Fixes: #8790 Change-Id: I448ddbfb45ae0a648815be8dad4faad7d1977427
Diffstat (limited to 'lib/sqlalchemy/testing/util.py')
-rw-r--r--lib/sqlalchemy/testing/util.py63
1 files changed, 63 insertions, 0 deletions
diff --git a/lib/sqlalchemy/testing/util.py b/lib/sqlalchemy/testing/util.py
index 6fd42af70..74b1ca992 100644
--- a/lib/sqlalchemy/testing/util.py
+++ b/lib/sqlalchemy/testing/util.py
@@ -9,10 +9,13 @@
from __future__ import annotations
+from collections import deque
import decimal
import gc
+from itertools import chain
import random
import sys
+from sys import getsizeof
import types
from . import config
@@ -459,3 +462,63 @@ def teardown_events(event_cls):
event_cls._clear()
return decorate
+
+
+def total_size(o):
+ """Returns the approximate memory footprint an object and all of its
+ contents.
+
+ source: https://code.activestate.com/recipes/577504/
+
+
+ """
+
+ def dict_handler(d):
+ return chain.from_iterable(d.items())
+
+ all_handlers = {
+ tuple: iter,
+ list: iter,
+ deque: iter,
+ dict: dict_handler,
+ set: iter,
+ frozenset: iter,
+ }
+ seen = set() # track which object id's have already been seen
+ default_size = getsizeof(0) # estimate sizeof object without __sizeof__
+
+ def sizeof(o):
+ if id(o) in seen: # do not double count the same object
+ return 0
+ seen.add(id(o))
+ s = getsizeof(o, default_size)
+
+ for typ, handler in all_handlers.items():
+ if isinstance(o, typ):
+ s += sum(map(sizeof, handler(o)))
+ break
+ return s
+
+ return sizeof(o)
+
+
+def count_cache_key_tuples(tup):
+ """given a cache key tuple, counts how many instances of actual
+ tuples are found.
+
+ used to alert large jumps in cache key complexity.
+
+ """
+ stack = [tup]
+
+ sentinel = object()
+ num_elements = 0
+
+ while stack:
+ elem = stack.pop(0)
+ if elem is sentinel:
+ num_elements += 1
+ elif isinstance(elem, tuple):
+ if elem:
+ stack = list(elem) + [sentinel] + stack
+ return num_elements