-
Notifications
You must be signed in to change notification settings - Fork 93
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
Invalid order events treatment #2124
Conversation
189b96c
to
9d11db8
Compare
events_a.last().map(|o| o.label) == Some(OrderEventLabel::Traded) | ||
&& events_b.last().map(|o| o.label) == Some(OrderEventLabel::Traded) | ||
&& check_non_consecutive_invalid_events(&events_a) | ||
&& check_non_consecutive_invalid_events(&events_b) |
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.
This test doesn't pass on the main
branch.
events_a.len() > 2 | ||
&& check_non_consecutive_invalid_events(&events_a) | ||
&& order_b_correct_events |
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.
This test doesn't pass on the main
branch.
crates/database/src/order_events.rs
Outdated
const QUERY: &str = r#" | ||
WITH cte AS ( | ||
SELECT label | ||
FROM order_events | ||
WHERE order_uid = $1 | ||
ORDER BY timestamp DESC | ||
LIMIT 1 | ||
) | ||
INSERT INTO order_events (order_uid, timestamp, label) | ||
SELECT $1, $2, $3 | ||
WHERE NOT EXISTS ( | ||
SELECT 1 | ||
FROM cte | ||
WHERE label = 'invalid' | ||
) |
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.
In staging, this query takes around 60ms(after other PRs are merged) ~5ms.
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.
This is concerning because that doesn't bode well for prod. 😬
Currently we discard around 520 orders from every auction because the owner does not have enough balance. If we go down this path we should probably spawn a background task for this insert as well.
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.
Can you execute the query like this in prod to test? Not sure if this helps, but we could add another boolean column in the table "latest" and then for every insert first update the latest record to be false. An index on this column could make lookups much faster.
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.
According to the query planner the query used in this PR is constant time because we already have a sorted index on (order_uid, timestamp)
.
It simply scans the index for the given uid
backwards and because we are only interested in the last row we only do a single iteration during the scan.
|
||
let orders = filter_dust_orders(orders, &balances, self.ethflow_contract_address); | ||
let removed = counter.checkpoint("dust_order", &orders); | ||
order_events.extend(removed.into_iter().map(|o| (o, OrderEventLabel::Filtered))); | ||
filtered_order_events.extend(removed.into_iter().map(|o| (o, OrderEventLabel::Filtered))); |
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.
I like your change to collect OrderUid
s into different buckets depending on the label.
This can be applied to filtered_orders
as well to make the code less verbose.
crates/database/src/order_events.rs
Outdated
const QUERY: &str = r#" | ||
WITH cte AS ( | ||
SELECT label | ||
FROM order_events | ||
WHERE order_uid = $1 | ||
ORDER BY timestamp DESC | ||
LIMIT 1 | ||
) | ||
INSERT INTO order_events (order_uid, timestamp, label) | ||
SELECT $1, $2, $3 | ||
WHERE NOT EXISTS ( | ||
SELECT 1 | ||
FROM cte | ||
WHERE label = 'invalid' | ||
) |
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.
This is concerning because that doesn't bode well for prod. 😬
Currently we discard around 520 orders from every auction because the owner does not have enough balance. If we go down this path we should probably spawn a background task for this insert as well.
Co-authored-by: Martin Beckmann <[email protected]>
Co-authored-by: Martin Beckmann <[email protected]>
# Conflicts: # crates/database/src/order_events.rs
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.
Code looks good. 👍
I'm still a bit worried about the 60ms per order event insert.
Maybe we should consider purging the databases from all duplicated Invalid
events before merging (the assumption is that this should speed up the query)?
Tested the query once again after the |
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.
LGTM
|
||
let orders = filter_mispriced_limit_orders(orders, &prices, &self.limit_order_price_factor); | ||
let removed = counter.checkpoint("out_of_market", &orders); | ||
order_events.extend(removed.into_iter().map(|o| (o, OrderEventLabel::Filtered))); | ||
filtered_order_events.extend(removed); |
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.
Oh wow, the code is quite lavish with the reasons for which it excludes orders. Would love to see all of those go away (and be done in the driver eventually). This has nothing to with this PR of course.
let db = self.database.clone(); | ||
tokio::spawn(async move { | ||
db.store_invalid_order_events(&invalid_order_uids).await; | ||
db.store_order_events( |
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.
Shouldn't we also store just state changes for Filtered
orders? What's the point of seeing multiple Filtered events in a row? This would make the insert code more consistent.
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.
Could not analyze prod data since SQL queries are a bit complex to find occurrences of subsequent invalid/filtered
labels within a single order. Staging data was used to calculate the ratio of subsequent events with the same label, while prod data was used to determine the ratio between events with different labels:
- There are around 45% of
invalid
events where ~99% are subsequent, meaning the table size is expected to be decreased by ~45%(14GB). - Around 22% of
filtered
events will remain in the table and ~70% are subsequent. So, approximately ~15% could be additionally removed(2.5GB) from the remaining data.
In summary: 45% or reduction with invalid
retreatment only and 55% including filtered
.
crates/database/src/order_events.rs
Outdated
const QUERY: &str = r#" | ||
WITH cte AS ( | ||
SELECT label | ||
FROM order_events | ||
WHERE order_uid = $1 | ||
ORDER BY timestamp DESC | ||
LIMIT 1 | ||
) | ||
INSERT INTO order_events (order_uid, timestamp, label) | ||
SELECT $1, $2, $3 | ||
WHERE NOT EXISTS ( | ||
SELECT 1 | ||
FROM cte | ||
WHERE label = 'invalid' | ||
) |
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.
Can you execute the query like this in prod to test? Not sure if this helps, but we could add another boolean column in the table "latest" and then for every insert first update the latest record to be false. An index on this column could make lookups much faster.
# Conflicts: # crates/autopilot/src/solvable_orders.rs # crates/e2e/tests/e2e/tracking_insufficient_funds.rs
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.
Looks good. I'd still slightly prefer if all order events are handled the same way, but not blocking this PR.
@@ -231,9 +232,23 @@ impl SolvableOrdersCache { | |||
prices, | |||
}; | |||
let removed = counter.record(&auction.orders); | |||
order_events.extend(removed.into_iter().map(|o| (o, OrderEventLabel::Filtered))); | |||
|
|||
self.database.store_order_events(&order_events).await; |
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.
Do we still need the old method (I feel like all order events should only be updated if non-subsequent). If so, the method could also be called more neutral (ie, keep store_order_events
but make it only store non subsequent events)
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.
It was already mentioned here: #1977 (comment). And I think it was additionally discussed with @MartinquaXD. Filtering other labels such as Ready
, Considered
extends the time period between the events. If that won't harm metrics/debugging, we can apply this logic to all the events.
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.
Filtering other labels such as Ready, Considered extends the time period between the events.
Do you see any way in which this could make a difference? If I understand correctly ready, considered, considered, traded
would become ready, considered, traded
which seems fine. To me this metric should allow to track the state machine for the lifecycle of an order.
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.
@MartinquaXD mentioned that the current metric where order_evets
table is used is going to be improved. But I don't have enough context in which way.
Currently, we have something like ready->ready->ready->traded
where we can clearly see the time difference between the latest ready
and traded
events. With filtering ready
events, the difference becomes bigger. For some of the subsequent labels, we can only update their timestamps. But for the invalid
label, for example, we care about the initial timestamp rather than the latest one. Anyways, would need more context from Martin 🙂
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.
I'm pretty sure we always care about the initial timestamp (for ready -> ready -> traded, we should measure our performance from when your order first became ready).
Given Martin is off until the 8th feel free to merge either with or without the unification (but please follow up before closing the issue)
Description
Closes pt2 of the issue #1977:
Changes
Invalid
order event if the latest order event label is notInvalid
.How to test
Covered with an e2e test.