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: Send push notifications for expired and expiring positions #1297

Merged

Conversation

klochowicz
Copy link
Contributor

  • Send notification to the user if their position is about to expire.
  • Add unit tests for the logic determining which notification to send.
  • periodically load all relevant positions with their fcm_tokens from the DB and
  • attempt to deliver notifications

coordinator/src/bin/coordinator.rs Outdated Show resolved Hide resolved
) -> Result<()> {
let users = user::all(conn)?;

let positions_with_fcm_tokens = positions::Position::get_all_positions_with_expiry_after(
Copy link
Contributor

Choose a reason for hiding this comment

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

🔧 I this could be nicely done with an inner join!

At least we should only load users which have an expired position.

Copy link
Contributor Author

@klochowicz klochowicz Sep 16, 2023

Choose a reason for hiding this comment

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

I mentioned that in the comment, but I don't think the optimisation is worthwhile at this stage?
🤔 especially if we're doing it very infrequently? (potentially, if we rework according to your other suggestions, we would only do this during the rollover weekend)

how strongly do you feel about it? I'm happy to rework it if you feel it's worth the time investment to optimise it.

coordinator/src/notification_service.rs Outdated Show resolved Hide resolved
coordinator/src/notification_service.rs Outdated Show resolved Hide resolved
coordinator/src/notification_service.rs Outdated Show resolved Hide resolved
@holzeis holzeis self-requested a review September 14, 2023 09:42
holzeis

This comment was marked as resolved.

@klochowicz

This comment has been minimized.

@klochowicz klochowicz dismissed holzeis’s stale review September 14, 2023 16:09

the premise is not true. I am delivering one notification of each.

@holzeis
Copy link
Contributor

holzeis commented Sep 14, 2023

📣 With that logic we are sending the user every 30 minutes a push notification. That's too much!

have you read the code and seen the unit tests? this is certainly not the case!

Fair enough I miss read the condition, but wouldn't it still send 2 notifications as you are checking for 1 hour after but run it ever 30 minutes? Anyways it's surely not as critical as sending a notification non-stop.

If you had something specific in mind, it would be helpful to put it on the ticket, to avoid impedance mismatch at this stage.

I hadn't something specific in mind it was just a proposal after looking over your PR. However we should align it with the rest of the code. We don't need another task that regularly checks if a position has expired we already have that. See expired_positions.

I would actually probably prefer moving with this forward and adding the additional ideas that you've described. They're not either-or, and they can be done in a follow-up.

I've convert the above into a ticket so we can discuss it freely there.

I am not blocking that PR as the the amount of push notifications are less than assumed when asking for changes, however I don't see a rush in bringing this out in anyway possible, we should take the time to get this to a point where we are all happy with it.

Comment on lines 336 to 341
if let Some(fcm_token) = maybe_fcm_token {
Some((p, fcm_token))
} else {
tracing::warn!(?p, "No FCM token for position");
None
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🔧 This can just be a map (or a find_map altogether).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the reason I unfolded is that I wanted to print out the warning instead of just ignoring the ones that didn't have a FCM token.

if you have a solution that does it even neater with that requirement, please share, as I always am keen on improving my iterator-fu 🙏☺️

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair enough. I think you could have done that within the find_map, but this code has diverged so I can't really give it a go myself anymore!

coordinator/src/notification_service.rs Outdated Show resolved Hide resolved
coordinator/src/notification_service.rs Outdated Show resolved Hide resolved
coordinator/src/notification_service.rs Outdated Show resolved Hide resolved
Comment on lines 157 to 158
if position.expiry_timestamp <= now
&& now < position.expiry_timestamp + time::Duration::hours(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

❓ Can we use a Range to define this predicate? It should be more readable if possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

cool idea! I'll look into it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

there's no ranges support for OffsetDateTime unfortunately :(

@klochowicz
Copy link
Contributor Author

📣 With that logic we are sending the user every 30 minutes a push notification. That's too much!

have you read the code and seen the unit tests? this is certainly not the case!

Fair enough I miss read the condition, but wouldn't it still send 2 notifications as you are checking for 1 hour after but run it ever 30 minutes? Anyways it's surely not as critical as sending a notification non-stop.

Yes, you're correct - it would do it twice. I wanted to avoid the situation when e.g. people get it 1 min before the expiry, and can't react.

If you had something specific in mind, it would be helpful to put it on the ticket, to avoid impedance mismatch at this stage.

I hadn't something specific in mind it was just a proposal after looking over your PR. However we should align it with the rest of the code. We don't need another task that regularly checks if a position has expired we already have that. See expired_positions.

This one only checks within particular time window, and I prefer that for separating concerns and concentrating logic of when to send notifications in one place. An infrequent read from a DB is not that costly, readability and testability is preferred IMHO.

I would actually probably prefer moving with this forward and adding the additional ideas that you've described. They're not either-or, and they can be done in a follow-up.
I've convert the above into a ticket so we can discuss it freely there.

I am not blocking that PR as the the amount of push notifications are less than assumed when asking for changes, however I don't see a rush in bringing this out in anyway possible, we should take the time to get this to a point where we are all happy with it.

isn't this the default situation with every review we have that doesn't solve a critical bug in production? 🤓

FYI: The only drawback is that #1211, which I'm currently working on, is depending on it.

@klochowicz klochowicz force-pushed the feat/send-a-notification-when-the-order-is-about-to-expire branch from 21662ee to a07531e Compare September 16, 2023 00:46
@klochowicz klochowicz force-pushed the feat/send-a-notification-when-the-order-is-about-to-expire branch from a07531e to 33a7016 Compare September 18, 2023 22:35
Copy link
Contributor

@bonomat bonomat 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.

coordinator/src/bin/coordinator.rs Outdated Show resolved Hide resolved
Comment on lines 157 to 158
if position.expiry_timestamp <= now
&& now < position.expiry_timestamp + time::Duration::hours(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is my understanding correct that the user will get a notification that his position has expired up to 1h after the position has expired?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, I tried to limit the amount of notifications to not overwhelm the user (cutoff time arbitrarily chosen, it's now with the constant).
the notification itself does not expire on the phone, unless user willingly dismisses it.

Comment on lines 169 to 170
} else if position.expiry_timestamp > now
&& position.expiry_timestamp <= now + time::Duration::hours(1)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is my understanding correct that the user receives this notification and has 1h to come online before his position is expires?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, that's the limitation currently (perhaps a 1h window of delivery e.g. ~12h before it expires would be better!), ie. at the start at the "rollover window".

coordinator/src/db/positions.rs Show resolved Hide resolved
coordinator/src/notifications.rs Show resolved Hide resolved
coordinator/src/notifications.rs Outdated Show resolved Hide resolved
coordinator/src/notifications.rs Outdated Show resolved Hide resolved
@klochowicz klochowicz force-pushed the feat/send-a-notification-when-the-order-is-about-to-expire branch 2 times, most recently from 772c827 to a771df2 Compare September 19, 2023 23:23
@klochowicz
Copy link
Contributor Author

@holzeis I've rebased on your recent work, trying to improve the naming somewhat, so I'd like to hear your opinion on this. I think my naming doesn't cut it perhaps either - so I was thinking maybe feed or user_feed would be best? these are messages that people get when they're connected, in contrast to the notifications that arrive at any time, even if they've have their position closed.
thoughts?

Also, I've moved the window to [13h,12h] before expiry, and adjusted polling interval to be just under 1h (to minimise the doubling of the notifications).

@klochowicz klochowicz force-pushed the feat/send-a-notification-when-the-order-is-about-to-expire branch from a771df2 to 83786a0 Compare September 20, 2023 02:04
Copy link
Contributor

@holzeis holzeis left a comment

Choose a reason for hiding this comment

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

@holzeis I've rebased on your recent work, trying to improve the naming somewhat, so I'd like to hear your opinion on this. I think my naming doesn't cut it perhaps either - so I was thinking maybe feed or user_feed would be best? these are messages that people get when they're connected, in contrast to the notifications that arrive at any time, even if they've have their position closed. thoughts?

Also, I've moved the window to [13h,12h] before expiry, and adjusted polling interval to be just under 1h (to minimise the doubling of the notifications).

I think it already looks good. I am not a the best in naming things either, but I guess we can improve that over time.

What I would have liked to see is a more general purpose notification service, that is managing the user data in a set as we are currently doing it with the authenticated_users. Upon startup, we do anyways always send the fcm token information, so instead of having a REST API we could send it over with the websocket authentication message, store on signin and keep it in memory with our authenticated users. That way the notification service could as well cache that information (However, I do not think that is a huge bottleneck anyways - It's just a thought)

I've left some additional remarks, but they are not blocking! We can also address them in a follow up PR.

@@ -214,7 +219,7 @@ async fn main() -> Result<()> {
notifier.clone(),
network,
);
let _handle = rollover::monitor(pool.clone(), tx_user_feed.clone(), notifier, network);
let _handle = rollover::monitor(pool.clone(), tx_user_feed.clone(), notifier);
Copy link
Contributor

@holzeis holzeis Sep 20, 2023

Choose a reason for hiding this comment

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

🔧 I guess that commit will not compile without the network passed as argument here.

coordinator/src/bin/coordinator.rs Show resolved Hide resolved
pub enum Notification {
Message {
/// Message sent to users via the websocket.
pub enum CoordinatorMessage {
Copy link
Contributor

@holzeis holzeis Sep 20, 2023

Choose a reason for hiding this comment

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

🔧 CoordinatorMessage is not correct here as this also includes messages from the orderbook. How about WebsocketNotification? Inititially, I though that we could as well include the push notification types into this Notifications as well, and include something like a Type e.g. (OnlyIfOnline, Always, OnlyPush) and then the notification service could first try on the websocket and if failed leave a push notification.

I feel like the websocket messages and the push notifcations have somewhat of a relationship, as I might want to inform the user about a missed websocket message.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll change it to WebsocketMessage, thank you fro the suggestion.

I'm not sure whether it would make sense to couple them together (in my mind push notifications are completely separate thing, that user can opt-in into; and also needs FCM key to work locally).

Copy link
Contributor Author

@klochowicz klochowicz Sep 20, 2023

Choose a reason for hiding this comment

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

edit: there was already WebsocketMessage somewhere else, so I ended up with OrderbookMessage (these are after all messages delivered via the orderbook websocket stream).

coordinator/src/notifications.rs Show resolved Hide resolved
coordinator/src/notifications.rs Outdated Show resolved Hide resolved
coordinator/src/orderbook/trading.rs Show resolved Hide resolved
Push notifications are sent in the following time windows:

1. Expiring positions
    - [EXPIRING_THRESHOLD, now]

2. Expired positions:
    - [now, EXPIRED_THRESHOLD]

Here both of the thresholds are set to 1h.

- Add unit tests for the logic determining which notification to send.
- periodically load all relevant positions with their fcm_tokens
     from the DB and attempt to deliver notifications

Notification task runs every 30 mins, so that the user would get 2 of each.
This way they'll likely get it at least 30 mins before the expiry.
When the other party is not online, send a notification that a position has expired.
…xpiry

Create a window [START_OF_EXPIRING_POSITION, END_OF_EXPIRING_POSITION] to better
configure the notification about imminent position expiry.

Configure the window to [13h,12h] before expiry.
Improve documentation and adjust how often we run the check to minimise the
changes of user receiving 2 notifications (it can still happen, but it is
around 3% now (overlapping the time a bit to ensure we don't out miss sending out
the notification).
@klochowicz klochowicz force-pushed the feat/send-a-notification-when-the-order-is-about-to-expire branch from 83786a0 to 8f887fc Compare September 20, 2023 21:52
@klochowicz klochowicz added this pull request to the merge queue Sep 20, 2023
Merged via the queue into main with commit 033ff5e Sep 20, 2023
7 checks passed
@klochowicz klochowicz deleted the feat/send-a-notification-when-the-order-is-about-to-expire branch September 20, 2023 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants