bito-code-review[bot] commented on code in PR #44662:
URL: https://github.com/apache/superset/pull/44662#discussion_r4171705923


##########
superset/utils/cache.py:
##########
@@ -54,6 +54,67 @@ def generate_cache_key(values_dict: dict[str, Any], 
key_prefix: str = "") -> str
     return cache_key
 
 
+def exceeds_max_cache_value_size(cache_key: str, value: Any) -> bool:
+    """Check a value against ``DATA_CACHE_MAX_VALUE_SIZE`` before a data-cache 
write.
+
+    This keeps one oversized value from flooding the cache backend (e.g.
+    Redis/Memcached) and evicting many smaller entries. When the serialized
+    (pickled) size of ``value`` is larger than the limit, a WARNING naming the 
key
+    and size is logged and the ``skip_cache_value_too_large`` statsd counter is
+    incremented; the caller must then skip the write. Cache writers use
+    :func:`skip_oversized_cache_value`, which also removes any older value 
stored
+    under the key.
+
+    :returns: ``True`` when the value is too large and must not be cached. 
Always
+        ``False`` when ``DATA_CACHE_MAX_VALUE_SIZE`` is ``None``, in which 
case the
+        value is not serialized and no overhead is incurred.
+    """
+    max_value_size = app.config.get("DATA_CACHE_MAX_VALUE_SIZE")
+    if max_value_size is None:
+        return False
+    value_size = len(pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL))

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Double serialization on cache write</b></div>
   <div id="fix">
   
   With the new default `DATA_CACHE_MAX_VALUE_SIZE = 10 * 1024 * 1024` 
(config.py:1466), `exceeds_max_cache_value_size` now pickles every value 
(cache.py:75) and the caller pickles it AGAIN in `cache.set` (executor.py:1173, 
datasource/api.py:76, cache.py:165). Previously the `None` default skipped the 
measurement pickle entirely. Every data-cache write now serializes twice on the 
hot path — a real regression for large chart/SQL results. Consider reusing the 
measured pickled bytes for the write, or a cheap size pre-check.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #c55fc3</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
tests/unit_tests/sql/execution/test_executor.py:
##########
@@ -3614,11 +3626,133 @@ def test_truncation_survives_cache_round_trip(
             )
         ],
     )
-    cache_set = mocker.patch.object(cache_manager.data_cache, "set")
+    cache_set = enabled_data_cache.set
     executor._store_in_cache(result, "SELECT 1", QueryOptions())
-    mocker.patch.object(
-        cache_manager.data_cache, "get", 
return_value=cache_set.call_args.args[1]
-    )
+    enabled_data_cache.get.return_value = cache_set.call_args.args[1]
     cached = executor._get_from_cache("SELECT 1", QueryOptions())
     assert cached is not None
     assert cached.statements[0].truncated is truncated
+
+
+def _store_in_cache_with_cap(
+    mocker: MockerFixture,
+    database: Database,
+    data_cache: MagicMock,
+    row_count: int,
+) -> tuple[MagicMock, MagicMock, MagicMock]:
+    """Run ``_store_in_cache`` for a ``row_count``-row result under a 1 KB cap.
+
+    :returns: the mocked ``data_cache.set``, stats logger, and cache-module 
logger
+    """
+    from superset_core.queries.types import (
+        QueryResult as QueryResultType,
+        StatementResult,
+    )
+
+    from superset.sql.execution.executor import SQLExecutor
+
+    stats_logger = MagicMock()
+    mocker.patch.dict(
+        current_app.config,
+        {
+            "DATA_CACHE_MAX_VALUE_SIZE": 1024,
+            "STATS_LOGGER": stats_logger,
+            "CACHE_DEFAULT_TIMEOUT": 300,
+        },
+    )
+    mock_cache_set = data_cache.set
+    mock_logger = mocker.patch("superset.utils.cache.logger")
+
+    result = QueryResultType(
+        status=QueryStatus.SUCCESS,
+        statements=[
+            StatementResult(
+                original_sql="SELECT name FROM users",
+                executed_sql="SELECT name FROM users",
+                data=pd.DataFrame(
+                    {"name": [f"user-{i:05d}" for i in range(row_count)]}
+                ),
+                row_count=row_count,
+            )
+        ],
+    )
+    SQLExecutor(database)._store_in_cache(
+        result, "SELECT name FROM users", QueryOptions()
+    )
+    return mock_cache_set, stats_logger, mock_logger
+
+
+def test_store_in_cache_skips_oversized_result(
+    mocker: MockerFixture,
+    database: Database,
+    app_context: None,
+    enabled_data_cache: MagicMock,
+) -> None:
+    """A result larger than ``DATA_CACHE_MAX_VALUE_SIZE`` is not written; the 
skip
+    is logged and counted. A later ``_get_from_cache`` simply misses and the 
query
+    re-runs."""
+    mock_cache_set, stats_logger, mock_logger = _store_in_cache_with_cap(
+        mocker, database, enabled_data_cache, row_count=500
+    )
+
+    mock_cache_set.assert_not_called()
+    # An older result under the same key is removed so it is not served later.
+    enabled_data_cache.delete.assert_called_once()
+    stats_logger.incr.assert_called_once_with("skip_cache_value_too_large")
+    mock_logger.warning.assert_called_once()

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing serialization assertion</b></div>
   <div id="fix">
   
   `test_store_in_cache_skips_oversized_result` asserts 
`cache.set`/`delete`/stats but never that the value was serialized. The skip 
decision in `exceeds_max_cache_value_size` (`superset/utils/cache.py:75`) 
depends on `pickle.dumps`; the sibling NullCache test (3741, 3756) asserts 
this. Without it, a regression that skips serialization entirely would still 
pass this test. ([BITO.md 9460])
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #c55fc3</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to