Skip to content
This repository has been archived by the owner on Nov 8, 2024. It is now read-only.

FF-3143 feat: add caching logger for assignment and bandit events #68

Merged
merged 7 commits into from
Sep 4, 2024

Conversation

rasendubi
Copy link
Collaborator


labels: mergeable

Fixes: https://linear.app/eppo/issue/FF-3143/%5Bpython%5D-deduplication-cache-for-assignment-and-bandit-events

Motivation and Context

This allows deduplicating assignments and bandit actions before they are logged to client storage.

Description

This PR adds a caching logger adapter that optionally accepts caches for assignment and bandit. Cache implementation can be any MutableMapping:

  • Any instance of Cache from cachetools (with any configuration)
  • dict for non-evicting (unbound) cache (not recommended)

If cache is not provided for assignment or bandits, that type of events is not cached.

This design decouples eppoclient completely from caching implementation and the user may use any of the great caching implementation out there that fit their needs.

How has this been tested?

Added new test cases.

@rasendubi rasendubi requested review from aarsilv and schmit August 30, 2024 14:00
@rasendubi rasendubi force-pushed the ff-3143-assignment-cache branch 2 times, most recently from ff71c4f to 5682b87 Compare August 30, 2024 14:04
@rasendubi
Copy link
Collaborator Author

rasendubi commented Aug 30, 2024

run-tests-with-tox in CI seems to be broken because of https://github.com/Eppo-exp/python-sdk/pull/65/files#r1738745786

I've checked that it passes locally (on all available python versions):

  py38: OK (6.07=setup[5.08]+cmd[0.98] seconds)
  py39: OK (4.23=setup[3.24]+cmd[1.00] seconds)
  py310: OK (4.09=setup[3.14]+cmd[0.94] seconds)
  py311: OK (3.99=setup[3.05]+cmd[0.94] seconds)
  py312: OK (4.49=setup[3.50]+cmd[0.99] seconds)
  congratulations :) (23.02 seconds)

UPD: fixed in #69


@staticmethod
def __bandit_cache_keyvalue(event: Dict) -> Tuple[Tuple, Tuple]:
key = (event["flagKey"], event["subject"])
Copy link
Collaborator Author

@rasendubi rasendubi Aug 30, 2024

Choose a reason for hiding this comment

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

Noticed that our event format is somewhat inconsistent between SDKs.

In Python, Go, PHP, .NET, Rust, and Ruby:

  • assignment event uses "featureFlag"
  • bandit event uses "flagKey"

Javascript and Java SDK use "featureFlag" for both events.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, it's just a factor of evolution. We're trying to move (slowly) more towards explicit keys, such as flagKey. However, it's also nice to have fields be analogously named between the two events (hence JavaScript and Java maintaining featureFlag). I think you're just seeing the personal preferences of the authors manifest in the various SDKs.

BanditEvaluator.evaluate_bandit() always returns a result, so `is
None` checks were unnecessary. Furthermore, `evaluation` fields are
accessed without this guard a couple of lines below.
There is no reason for AssignmentLogger to be a pydantic model because
it's not a model at all (it's behavior, not data).

Remove inheritance from the base model for AssignmentLogger and allow
arbitrary types on Config instead. Also exclude assignment_logger from
pydantic serialization.

This refactor makes it easier to work with AssignmentLogger.
Especially when mocking it, as pydantic models are not easily mockable.
@rasendubi rasendubi force-pushed the ff-3143-assignment-cache branch from 5682b87 to 9b3599a Compare August 30, 2024 14:39
Copy link
Contributor

@schmit schmit left a comment

Choose a reason for hiding this comment

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

👌 very clean implementation, love it!

Copy link
Contributor

@aarsilv aarsilv left a comment

Choose a reason for hiding this comment

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

Great stuff 💪

Comment on lines +131 to +134
# cache 1024 least recently used assignments
assignment_cache=cachetools.LRUCache(maxsize=1024),
# cache bandit assignment for no longer than 10 minutes
bandit_cache=cachetools.TTLCache(maxsize=2048, ttl=600),
Copy link
Contributor

Choose a reason for hiding this comment

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

fantastic example code


@staticmethod
def __bandit_cache_keyvalue(event: Dict) -> Tuple[Tuple, Tuple]:
key = (event["flagKey"], event["subject"])
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, it's just a factor of evolution. We're trying to move (slowly) more towards explicit keys, such as flagKey. However, it's also nice to have fields be analogously named between the two events (hence JavaScript and Java maintaining featureFlag). I think you're just seeing the personal preferences of the authors manifest in the various SDKs.

assert inner.log_bandit_action.call_count == 1


def test_bandit_flip_flop():
Copy link
Contributor

Choose a reason for hiding this comment

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

❤️

assert inner.log_bandit_action.call_count == 3


def mk_assignment_event(
Copy link
Contributor

Choose a reason for hiding this comment

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

super minor, but consider spelling out mock (instead of mk), I don't think the few characters saved is worth the extra cognitive step for developers reading the code to get what is going on

Copy link
Collaborator Author

@rasendubi rasendubi Sep 4, 2024

Choose a reason for hiding this comment

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

mk here actually stands for "make" (as in mkdir). Thought it's quite widely understood but it's probably just me coming from C background and writing too much makefiles (.mk) in the past 😅

I'll spell it out

@rasendubi rasendubi merged commit 12583d6 into main Sep 4, 2024
4 checks passed
@rasendubi rasendubi deleted the ff-3143-assignment-cache branch September 4, 2024 12:26
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants