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

feat: adds persistence layer for app state and job results [SBK-363] #45

Merged
merged 50 commits into from
Nov 29, 2023

Conversation

mikeshultz
Copy link
Member

@mikeshultz mikeshultz commented Nov 18, 2023

What I did

Adds persistence for app state and job results. Alters startup/shutdown handlers a bit.

Closes #33

How I did it

Adds persistent storage support of state and results. Stores block numbers the runner sees in an app state collection. Stores handler job results in result collection.

How to verify it

Run it with these defined:

SILVERBACK_PERSISTENCE_CLASS="silverback.persistence:SQLitePersistentStorage"
SQLITE_PATH="/tmp/silverback.db"

Checklist

  • Passes all linting checks (pre-commit and CI jobs)
  • New test cases have been added and are passing
  • Documentation has been updated
  • PR title follows Conventional Commit standard (will be automatically included in the changelog)

@vany365 vany365 changed the title feat: adds persistence layer for app state and job results feat: adds persistence layer for app state and job results [SBK-363] Nov 18, 2023
@mikeshultz mikeshultz self-assigned this Nov 18, 2023
Copy link

linear bot commented Nov 18, 2023

SBK-363 "feat: adds persistence layer for app state and job results" (ApeWorX/silverback #45)

WIP: This is still pretty raw, looking for early feedback. Will leave some personal notes inline.

What I did

Adds persistence for app state and job results

Closes #33

How I did it

Adds mongo support by way of beanie. Stores block numbers the runner sees in an app state collection. Stores handler job results in result collection.

How to verify it

Run it with SILVERBACK_MONGO_URI defined.

Checklist

  • Passes all linting checks (pre-commit and CI jobs)
  • New test cases have been added and are passing
  • Documentation has been updated
  • PR title follows Conventional Commit standard (will be automatically included in the changelog)

ApeWorX/silverback #45 by mikeshultz on GitHub

via LinearSync

@mikeshultz mikeshultz added the enhancement New feature or request label Nov 18, 2023
silverback/application.py Outdated Show resolved Hide resolved
)


class BasePersistentStorage(ABC):
Copy link
Member Author

Choose a reason for hiding this comment

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

Is there anything we need to add for v1? This is bare minimum for what this PR needs but we could future-think here as well for aggregate data or API fetching.

silverback/persistence.py Outdated Show resolved Hide resolved
silverback/persistence.py Outdated Show resolved Hide resolved
silverback/runner.py Outdated Show resolved Hide resolved
@mikeshultz
Copy link
Member Author

Here's what the results docs look like in MongoDB:

[
  {
    _id: ObjectId("65581e0f5a9cc7756a43e890"),
    instance: 'default',
    network: 'ethereum:mainnet',
    handler_id: 'event/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/Transfer(address,address,uint256)',
    block_number: 18595697,
    log_index: 222,
    execution_time: 0,
    return_value: Binary.createFromBase64("gASVMAAAAAAAAABDLIAElSEAAAAAAAAAQx2ABJUSAAAAAAAAAH2UjAZhbW91bnSUSjDZqBxzLpQulC4=", 0),
    created: ISODate("2023-11-18T02:14:39.684Z")
  },
  {
    _id: ObjectId("65581e0f5a9cc7756a43e891"),
    instance: 'default',
    network: 'ethereum:mainnet',
    handler_id: 'block/18595697',
    block_number: 18595697,
    log_index: null,
    execution_time: 0.57,
    return_value: Binary.createFromBase64("gASVGAAAAAAAAABDFIAElQkAAAAAAAAAQwWABEtlLpQulC4=", 0),
    created: ISODate("2023-11-18T02:14:39.841Z")
  }
]

And app state:

[
  {
    _id: ObjectId("65581e0f5a9cc7756a43e88e"),
    instance: 'default',
    network: 'ethereum:mainnet',
    last_block_seen: 18595697,
    last_block_processed: 18595697,
    updated: ISODate("2023-11-18T02:14:39.475Z")
  }
]

silverback/runner.py Outdated Show resolved Hide resolved
silverback/application.py Outdated Show resolved Hide resolved
silverback/persistence.py Outdated Show resolved Hide resolved
silverback/persistence.py Outdated Show resolved Hide resolved
@mikeshultz mikeshultz marked this pull request as ready for review November 27, 2023 21:37
mikeshultz and others added 2 commits November 27, 2023 14:48
Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com>
docs/userguides/development.md Show resolved Hide resolved
silverback/application.py Outdated Show resolved Hide resolved
def do_something_on_startup(startup_state):
... # Reprocess missed events or blocks
"""
return self.broker.task(task_name="silverback_startup")
Copy link
Member

Choose a reason for hiding this comment

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

what are the pros/cons of moving the startup/shutdown handlers into separate tasks from the worker startup/shutdown events

Copy link
Member Author

Choose a reason for hiding this comment

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

They cannot be combined. They're very separate things.

Having on_startup be a worker event means unexpected duplication of execution. Having a separate task also allows us to feed the handler useful information, like last_block_*. This is basically what we need for ApePay.

Renaming the previous handlers to be explicit worker event handlers implies their actual usage and is clearer, IMO. They're two distinct things.

The only real con is we're changing up behavior here. Anyone setting up worker state may need to refactor.

Copy link
Member

Choose a reason for hiding this comment

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

They cannot be combined. They're very separate things.

Having on_startup be a worker event means unexpected duplication of execution. Having a separate task also allows us to feed the handler useful information, like last_block_*. This is basically what we need for ApePay.

Renaming the previous handlers to be explicit worker event handlers implies their actual usage and is clearer, IMO. They're two distinct things.

The only real con is we're changing up behavior here. Anyone setting up worker state may need to refactor.

but won't this mean that only one worker will handle this event on startup? vs. the worker startup event is executed on every worker as it's set up?

Copy link
Member Author

@mikeshultz mikeshultz Nov 27, 2023

Choose a reason for hiding this comment

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

yes. on_startup will only run once upon runner startup. on_worker_startup will run on every worker startup.

silverback/middlewares.py Outdated Show resolved Hide resolved
examples/redis/main.py Outdated Show resolved Hide resolved
examples/memory/main.py Outdated Show resolved Hide resolved
silverback/types.py Outdated Show resolved Hide resolved
silverback/persistence.py Outdated Show resolved Hide resolved
docs/userguides/development.md Outdated Show resolved Hide resolved

silverback run "example:app" \
--network :mainnet:alchemy \
--runner "silverback.runner:WebsocketRunner"
Copy link
Member

Choose a reason for hiding this comment

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

question(non-blocking): should we standardize runner to client?

Copy link
Member Author

Choose a reason for hiding this comment

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

Hard question, tbh. Would it be right to say "choose your client" to the user who wants to say use the WebsocketRunner?

docs/userguides/development.md Outdated Show resolved Hide resolved
)

try:
await self.persistence.add_result(handler_result)
Copy link
Member

Choose a reason for hiding this comment

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

random question: does the middleware act on the client or the worker?

Copy link
Member Author

Choose a reason for hiding this comment

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

Both. pre_send and post_send are client side, and pre_execute, post_execute, and post_save are worker-side. It never comes back to the client after processing, though.

Copy link
Member

@fubuloubu fubuloubu left a comment

Choose a reason for hiding this comment

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

suggestion: we find a different name rather than persistence as it's a little awkward and not a good indicator of it's purpose

maybe data_store or metrics

@@ -17,6 +18,9 @@ class Settings(BaseSettings, ManagerAccessMixin):
testing or deployment purposes. Defaults to a working in-memory broker.
"""

# A unique identifier for this silverback instance
INSTANCE: str = "default"
Copy link
Member

Choose a reason for hiding this comment

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

What's the idea behind this?

Copy link
Member Author

Choose a reason for hiding this comment

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

The ability for multiple Silverback instances to use a single persistence backend. For ApePay, I'm setting it to apepay-sepolia, and we may have others for other networks. Otherwise, they'd step on each-others toes and confusingly share state.

silverback/types.py Outdated Show resolved Hide resolved
mikeshultz and others added 3 commits November 28, 2023 16:51
Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com>
Co-authored-by: El De-dog-lo <3859395+fubuloubu@users.noreply.github.com>
@mikeshultz
Copy link
Member Author

suggestion: we find a different name rather than persistence as it's a little awkward and not a good indicator of it's purpose

maybe data_store or metrics

I don't know. data_store is also clumsy since it's multiple words, and metrics doesn't really cover its use. Could just call it "store", "storage", or "state". You tell me.

@mikeshultz mikeshultz merged commit 4426342 into main Nov 29, 2023
25 of 26 checks passed
@mikeshultz mikeshultz deleted the feat/persistence branch November 29, 2023 21:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[SBK-251] Store the last successfully processed block for fast restart
2 participants