-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
CCIP-2971 Optimize token/gas prices database interactions #14074
Conversation
core/services/ocr2/plugins/ccip/internal/ccipdb/price_service.go
Outdated
Show resolved
Hide resolved
7481fab
to
3b503f5
Compare
|
||
CREATE TABLE ccip.observed_token_prices | ||
( | ||
chain_selector NUMERIC(20, 0) NOT NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jobId is not relevant with upsert_approach, so I did remove it entirely
2f478fa
to
ee784ad
Compare
ee784ad
to
e19f814
Compare
1b087fa
to
9619292
Compare
9619292
to
0284c31
Compare
0284c31
to
6e45fa0
Compare
6e45fa0
to
7d75de7
Compare
7d75de7
to
6ceb3e2
Compare
yeh would be ideal, alternatively we can ignore the |
Ouch, that's the problem here. We need a single source of truth and unfortunately, PRs touching db migrations have to be done in chainlink repo first. Not sure what's the best approach here, @asoliman92 any suggestions? We can either cherry-pick ccip changes to chainlink and resolve conflicts in this PR or merge that PR and resolve conflicts on ccip level |
I've done the cherry-pick approach while doing the initial porting from CCIP to CL. It worked well at the time and I think it should be okay with your PR. I'm actually more interested in merging CL to CCIP and fixing the conflicts as we'll need to do this someway or another once V1.5 is ready. But this might be outside the scope of your PR. |
d20c2d7
to
7dd61a9
Compare
7dd61a9
to
8f9bf8e
Compare
core/services/ccip/orm.go
Outdated
|
||
_, err := o.ds.ExecContext(ctx, stmt, destChainSelector, expireSec) | ||
return err | ||
tokensToIgnore := mapset.NewThreadUnsafeSet[string](dbTokensToIgnore...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the ThreadUnsafeSet
intentional? I think it won't cause issues as it's locally created but seeing Unsafe
does an alert so making extra sure that we're not missing something :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1, is there a reason to use NewThreadUnsafeSet
vs alternatives?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, mapset default implementation is threadsafe which adds locking. It's not needed here, because we use that set only within this function. Locking comes with overhead so I've decided to use the implementation that doesn't have any locks under hood. However, if it raises questions I can either use locking implementation or just use a regular Go map
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
// NewThreadUnsafeSet creates and returns a new set with the given elements.
// Operations on the resulting set are not thread-safe.
Co-authored-by: Abdelrahman Soliman (Boda) <2677789+asoliman92@users.noreply.github.com>
9c0fa6d
to
0cb98b5
Compare
Quality Gate failedFailed conditions See analysis details on SonarQube Catch issues before they fail your Quality Gate with our IDE extension SonarLint |
Reference https://smartcontract-it.atlassian.net/browse/CCIP-2971
Problem
Leader lane implementation inserts every price of every token/gas to the
observed_token_prices
andobserved_gas_prices
tables. It also evicts stale elements by removing them from the database, so all background workers (per lane) keep inserting token prices and deleting stale ones. Then leader lane worker is responsible for querying that table to pick only the relevant data (latest update per token)It makes the entire process very db-heavy and inefficient because with every round we keep inserting new prices and evicting stale ones, which leads to:
Solution
Get rid of deleting logic (simplify the code) and use upsert instead of insert. This will keep the table rather small - as many rows as tokens per chain. The table doesn’t grow in time so we don’t need the entire deletion logic - simplify the codebase, and reduce database connection usage by node. Token price updates only update the row in the database instead of creating a new row with every update. The huge benefit is also lightweight lookup (searching token prices by leader lane), which will be extremely small because the table is small.
Don’t insert if it’s not necessary. Multiple jobs might have the same tokens, before fetching the token price we can check which tokens didn’t get an update recently (to be defined what “recently” means in that case) and update only the ones that require it. Therefore we don’t flood the table with upsert requests.
Tests
Go bench results
Keep inserting random prices to the table. Please see
Benchmark_UpsertsTheSameTokenPrices
CCIP baseline test
Before
After