implement performance mode for existing state size check (#4392)

This commit is contained in:
benedikt-bartscher 2024-11-22 01:33:47 +01:00 committed by GitHub
parent b5e4b02d9c
commit c13cec3d8a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 37 additions and 9 deletions

View File

@ -454,6 +454,14 @@ class PathExistsFlag:
ExistingPath = Annotated[Path, PathExistsFlag] ExistingPath = Annotated[Path, PathExistsFlag]
class PerformanceMode(enum.Enum):
"""Performance mode for the app."""
WARN = "warn"
RAISE = "raise"
OFF = "off"
class EnvironmentVariables: class EnvironmentVariables:
"""Environment variables class to instantiate environment variables.""" """Environment variables class to instantiate environment variables."""
@ -550,6 +558,12 @@ class EnvironmentVariables:
# Whether to check for outdated package versions. # Whether to check for outdated package versions.
REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True) REFLEX_CHECK_LATEST_VERSION: EnvVar[bool] = env_var(True)
# In which performance mode to run the app.
REFLEX_PERF_MODE: EnvVar[Optional[PerformanceMode]] = env_var(PerformanceMode.WARN)
# The maximum size of the reflex state in kilobytes.
REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000)
environment = EnvironmentVariables() environment = EnvironmentVariables()

View File

@ -43,7 +43,7 @@ from sqlalchemy.orm import DeclarativeBase
from typing_extensions import Self from typing_extensions import Self
from reflex import event from reflex import event
from reflex.config import get_config from reflex.config import PerformanceMode, get_config
from reflex.istate.data import RouterData from reflex.istate.data import RouterData
from reflex.istate.storage import ClientStorageBase from reflex.istate.storage import ClientStorageBase
from reflex.model import Model from reflex.model import Model
@ -90,6 +90,7 @@ from reflex.utils.exceptions import (
ReflexRuntimeError, ReflexRuntimeError,
SetUndefinedStateVarError, SetUndefinedStateVarError,
StateSchemaMismatchError, StateSchemaMismatchError,
StateTooLargeError,
) )
from reflex.utils.exec import is_testing_env from reflex.utils.exec import is_testing_env
from reflex.utils.serializers import serializer from reflex.utils.serializers import serializer
@ -110,10 +111,11 @@ Delta = Dict[str, Any]
var = computed_var var = computed_var
# If the state is this large, it's considered a performance issue. if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
TOO_LARGE_SERIALIZED_STATE = 100 * 1024 # 100kb # If the state is this large, it's considered a performance issue.
# Only warn about each state class size once. TOO_LARGE_SERIALIZED_STATE = environment.REFLEX_STATE_SIZE_LIMIT.get() * 1024
_WARNED_ABOUT_STATE_SIZE: Set[str] = set() # Only warn about each state class size once.
_WARNED_ABOUT_STATE_SIZE: Set[str] = set()
# Errors caught during pickling of state # Errors caught during pickling of state
HANDLED_PICKLE_ERRORS = ( HANDLED_PICKLE_ERRORS = (
@ -2097,7 +2099,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
state["__dict__"].pop(inherited_var_name, None) state["__dict__"].pop(inherited_var_name, None)
return state return state
def _warn_if_too_large( def _check_state_size(
self, self,
pickle_state_size: int, pickle_state_size: int,
): ):
@ -2105,6 +2107,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
Args: Args:
pickle_state_size: The size of the pickled state. pickle_state_size: The size of the pickled state.
Raises:
StateTooLargeError: If the state is too large.
""" """
state_full_name = self.get_full_name() state_full_name = self.get_full_name()
if ( if (
@ -2112,10 +2117,14 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
and pickle_state_size > TOO_LARGE_SERIALIZED_STATE and pickle_state_size > TOO_LARGE_SERIALIZED_STATE
and self.substates and self.substates
): ):
console.warn( msg = (
f"State {state_full_name} serializes to {pickle_state_size} bytes " f"State {state_full_name} serializes to {pickle_state_size} bytes "
"which may present performance issues. Consider reducing the size of this state." + "which may present performance issues. Consider reducing the size of this state."
) )
if environment.REFLEX_PERF_MODE.get() == PerformanceMode.WARN:
console.warn(msg)
elif environment.REFLEX_PERF_MODE.get() == PerformanceMode.RAISE:
raise StateTooLargeError(msg)
_WARNED_ABOUT_STATE_SIZE.add(state_full_name) _WARNED_ABOUT_STATE_SIZE.add(state_full_name)
@classmethod @classmethod
@ -2157,7 +2166,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow):
""" """
try: try:
pickle_state = pickle.dumps((self._to_schema(), self)) pickle_state = pickle.dumps((self._to_schema(), self))
self._warn_if_too_large(len(pickle_state)) if environment.REFLEX_PERF_MODE.get() != PerformanceMode.OFF:
self._check_state_size(len(pickle_state))
return pickle_state return pickle_state
except HANDLED_PICKLE_ERRORS as og_pickle_error: except HANDLED_PICKLE_ERRORS as og_pickle_error:
error = ( error = (

View File

@ -151,6 +151,10 @@ class InvalidPropValueError(ReflexError):
"""Raised when a prop value is invalid.""" """Raised when a prop value is invalid."""
class StateTooLargeError(ReflexError):
"""Raised when the state is too large to be serialized."""
class SystemPackageMissingError(ReflexError): class SystemPackageMissingError(ReflexError):
"""Raised when a system package is missing.""" """Raised when a system package is missing."""