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

Invalid order events treatment #2124

Merged
merged 13 commits into from
Dec 28, 2023
Merged

Conversation

squadgazzz
Copy link
Contributor

@squadgazzz squadgazzz commented Dec 5, 2023

Description

Closes pt2 of the issue #1977:

special treatment for invalid events (maybe only store another invalid event when the order was previously NOT invalid; meaning only store state changes)

Changes

  • Insert Invalid order event if the latest order event label is not Invalid.

How to test

Covered with an e2e test.

@squadgazzz squadgazzz force-pushed the invalid-order-events-treatment branch from 189b96c to 9d11db8 Compare December 6, 2023 13:33
@squadgazzz squadgazzz changed the base branch from store-market-order-events-only to main December 6, 2023 13:34
@squadgazzz squadgazzz marked this pull request as ready for review December 6, 2023 13:37
@squadgazzz squadgazzz requested a review from a team as a code owner December 6, 2023 13:37
Comment on lines +153 to +156
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)
Copy link
Contributor Author

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.

Comment on lines +137 to +139
events_a.len() > 2
&& check_non_consecutive_invalid_events(&events_a)
&& order_b_correct_events
Copy link
Contributor Author

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.

Comment on lines 76 to 90
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'
)
Copy link
Contributor Author

@squadgazzz squadgazzz Dec 7, 2023

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.

Copy link
Contributor

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.

Copy link
Contributor

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.

Copy link
Contributor

@MartinquaXD MartinquaXD Dec 18, 2023

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.

crates/e2e/tests/e2e/tracking_insufficient_funds.rs Outdated Show resolved Hide resolved
crates/e2e/tests/e2e/tracking_insufficient_funds.rs Outdated Show resolved Hide resolved

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)));
Copy link
Contributor

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 OrderUids into different buckets depending on the label.
This can be applied to filtered_orders as well to make the code less verbose.

Comment on lines 76 to 90
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'
)
Copy link
Contributor

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.

@squadgazzz squadgazzz requested review from MartinquaXD and a team and removed request for MartinquaXD December 13, 2023 16:54
Copy link
Contributor

@MartinquaXD MartinquaXD left a 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)?

@squadgazzz squadgazzz marked this pull request as draft December 13, 2023 18:03
@squadgazzz squadgazzz marked this pull request as ready for review December 14, 2023 10:16
@squadgazzz
Copy link
Contributor Author

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 order_events relief PRs are merged, and now it works way faster in staging ~5ms. We can probably skip the purging step.

Copy link
Contributor

@fleupold fleupold left a 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);
Copy link
Contributor

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(
Copy link
Contributor

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.

Copy link
Contributor Author

@squadgazzz squadgazzz Dec 25, 2023

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:

  1. There are around 45% of invalid events where ~99% are subsequent, meaning the table size is expected to be decreased by ~45%(14GB).
  2. 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.

Comment on lines 76 to 90
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'
)
Copy link
Contributor

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
@squadgazzz squadgazzz requested a review from a team December 25, 2023 12:13
Copy link
Contributor

@fleupold fleupold left a 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;
Copy link
Contributor

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)

Copy link
Contributor Author

@squadgazzz squadgazzz Dec 26, 2023

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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 🙂

Copy link
Contributor

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)

crates/database/src/order_events.rs Outdated Show resolved Hide resolved
@squadgazzz squadgazzz enabled auto-merge (squash) December 28, 2023 19:00
@squadgazzz squadgazzz merged commit c509f76 into main Dec 28, 2023
8 checks passed
@squadgazzz squadgazzz deleted the invalid-order-events-treatment branch December 28, 2023 19:04
@github-actions github-actions bot locked and limited conversation to collaborators Dec 28, 2023
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