Skip to content

Conversation

@vazarkevych
Copy link
Collaborator

This PR implements two-level caching for GrowthBook feature data in the Python SDK:

On initialization, GrowthBook loads features from persistent file-based cache (if available and not expired).
The loaded data is stored in the in-memory cache and used for feature evaluations.
No network request is made unless the cached data is expired or missing.

class FeatureRepository(object):
def __init__(self) -> None:
self.cache: AbstractFeatureCache = InMemoryFeatureCache()
self.in_memory_cache: AbstractFeatureCache = InMemoryFeatureCache()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will violate public interface of the class.

instead of squeezing more caches into single repo and forcing user to use (and setup) both, you can create another AbstractFeatureCache class (and name it something like LayeredFeatureCache) which should be able to combine as many caches user wants.

that aside I don't think that feature flag service needs multi-level cache, because flag should be as consistent as possible between processes and allowing multiple caches to serve data will make cache more variable.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that squeezing multiple caches directly into FeatureRepository isn't ideal. You're right, the LayeredFeatureCache approach is much cleaner and more flexible.

We've actually implemented LayeredFeatureCache as part of this. It allows us to combine different caching strategies (like in-memory and an external cache) under a single, consistent interface.

self.cache: Dict[str, CacheEntry] = {}

def _get_base_path(self) -> Path:
base_path = Path("./GrowthBook-Cache")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not configurable? looks like general directory but only this class knows about it for some reason

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

class FileFeatureCache(AbstractPersistentFeatureCache):
def __init__(self, cache_file: str):
self.cache_file = cache_file
self.cache: Dict[str, CacheEntry] = {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use protected variables? otherwise someone might use them as public API and we will stuck with them forever.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


class AbstractPersistentFeatureCache(AbstractFeatureCache):
@abstractmethod
def load(self) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like implementation detail which could be hidden and called from constructor or other methods

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Volodymyr Nazarkevych and others added 7 commits July 22, 2025 16:23
# Conflicts:
#	growthbook/growthbook.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants