Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Restructure alerts engine to merge new results into cache instead of delete/write #864

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

robbie-sundstrom
Copy link
Contributor

Summary of changes

Asana Ticket: Refactor RTS Alerts Engine

  • On every update to the alerts written to ETS, instead of deleting all and writing all of the new alerts, we now replace them all
  • Added an ETS engine file to handle the replace_all method for reusability

Reviewer Checklist

  • Meets ticket's acceptance criteria
  • Any new or changed functions have typespecs
  • Tests were added for any new functionality (don't just rely on Codecov)
  • If signs.json was changed, there is a follow-up PR to signs_ui to update its dependency on this project
  • This branch was deployed to the staging environment and is currently running with no unexpected increase in warnings, and no errors or crashes (compare on Splunk: staging vs. prod)

@robbie-sundstrom robbie-sundstrom requested a review from a team as a code owner December 30, 2024 21:43
@@ -0,0 +1,34 @@
defmodule Engine.ETS do
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Also thought about naming this module Engine.Cache since ETS is acting as our cache.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Hmm yeah interesting thought - it's true that ETS essentially acts as our cache, although I'm not sure how I feel about abstracting it as such. It could help the cleanliness of some of the engine logic if we abstracted away the direct calls to :ets I suppose. Curious to see if @panentheos has thoughts

Copy link
Collaborator

Choose a reason for hiding this comment

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

I agree with Paul, I'm not sure there's much to be gained from adding new terminology here. Also, we usually use the name Engine to describe GenServer processes that are actively doing something, like polling data repeatedly. Since these are just functions, I'd probably file them under something with "Utilities" in the name, or just add them to one of the existing utility modules if they fit there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha, that distinction for engines makes sense to me! Given the other common use cases you pointed out, I think having this as an ETSUtilities module works well

lib/engine/ets.ex Outdated Show resolved Hide resolved
@panentheos
Copy link
Collaborator

While the delete/insert sequence in the original code is indeed error-prone, ETS does guarantee atomicity for several single operations, including bulk inserts. We leverage that to avoid the problem elsewhere by defining a sentinel "empty" value, overwriting all old values with that, merging the new values, and then writing the whole thing back in one operation. There's been a bit of consolidation of the code for this, and this would be a good opportunity to finish that up. See lib/engine/locations.ex and lib/engine/predictions.ex, which have essentially identical implementations, which could be pulled out as a shared utility function.

@robbie-sundstrom
Copy link
Contributor Author

@panentheos I did pull out the implementations into a shared utility module, but the behavior of the predictions/locations ETS table keys is different than what we want for alerts. Because of that, I kept them as two separate utility functions. I thought about combining them into one with two methods based on the number of inputs, but I ultimately decided that ended up being more complex.

Copy link
Collaborator

@panentheos panentheos left a comment

Choose a reason for hiding this comment

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

This is looking good. See comments for additional recommendations.

@@ -9,6 +9,7 @@ Here's a general overview of the realtime_signs application which should be help

## Development

* If it's your first time using asdf, run `asdf plugin add erlang && asdf plugin add elixir`.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Thanks for keeping this updated and useful!

and then merging new values into the table.
Existing values will be overwritten with the empty_value in ETS.
"""
def write_ets(table, values, empty_value) do
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since this is now more widely used, we could add a typespec to give some early warning if it's called incorrectly.

end

@doc "Writes new key-values to table and removes any existing keys from ETS that aren't in new_entries."
def replace_contents(table, new_entry) when is_tuple(new_entry) do
Copy link
Collaborator

Choose a reason for hiding this comment

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

Now that write_ets is available, we should be able to use that to solve our original problem without needing to add a new function. Looking at the alert code, we're already using :none as the default value when doing lookups, so if we use that as our "empty value" and set the old entries to that, it will have the same effect as deleting them.

Copy link
Contributor Author

@robbie-sundstrom robbie-sundstrom Jan 3, 2025

Choose a reason for hiding this comment

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

Makes sense -- I was thinking of saving space in ETS by deleting old keys, but I don't think that will be a large issue since most of them are re-used. Updated the code with this suggestion.


###################################################################
# write_ets tests
###################################################################
Copy link
Collaborator

Choose a reason for hiding this comment

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

FYI, you can use describe blocks to create logical groupings of tests, which can have their own locally-scoped setup blocks if desired.

Copy link
Contributor

Choose a reason for hiding this comment

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

See ExUnit.Case.describe/2 for documentation on that! Also, when using this to group tests for different functions of a single module, it's conventional to use the function's name as the message. For example

describe "write_ets/3" do
  test "does the thing" do
    # ...
  end
end

These are available unqualified in test modules because of use ExUnit.Case.

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.

4 participants