feat: Remove hidden relays automatically#8402
Conversation
|
There's one open question: #8384 (comment) CI fails due to: #8403 |
921c5df to
1b3f434
Compare
| context | ||
| .sql | ||
| .execute( | ||
| "DELETE FROM transports \ |
There was a problem hiding this comment.
All newline escapes can be removed, see https://github.com/chatmail/core/blob/main/STYLE.md#sql
The query should be checked with EXPLAIN QUERY PLAN to make sure it is not extremely slow with large number of messages.
Removing the transport without adding it to removed_transports means its removal will not be synced. This cleanup likely should be close to what delete_transport does, removing the transport and adding it to removed_transport at the same time. And generating a sync message probably so removal is synced immediately.
There was a problem hiding this comment.
Why should it be synced? If a transport isn't accessible from some device because of e.g. conectivity issues, it shouldn't be removed from other devices. Anyway it will be removed without additional synchronization normally.
Another problem here is how this interacts with DeleteDeviceAfter. Maybe it's better to add some transports.timestamp_rcvd_last column? This way we also don't need to fully scan msgs table, that may take seconds if the table isn't cached in memory, see also #7848 .
There was a problem hiding this comment.
Why should it be synced?
Yeah, since housekeep runs on all devices, maybe syncing it is just a redundant complexity?
There was a problem hiding this comment.
Only transports table is fully scanned, which is not a problem since it's a small table.
There was a problem hiding this comment.
True, there's msgs_index1 ON msgs (rfc724_mid).
But the question about DeleteDeviceAfter remains, it's not even an advanced setting, many users may have it enabled. How can we reason about messages received within 90 days if they are deleted from the device e.g. in 1 day?
There was a problem hiding this comment.
Ah, yes, how this interacts with DeleteDeviceAfter needs to be investigated. Maybe it's even possible to write a test for it.
The interaction is complex: delete_expired_messages() sets the sent_timestamp to 0, but the timestamp (which is called sort_timestamp in the Message struct) is kept, so, maybe it would be enough to look at timestamp instead of sent_timestamp in the SQL query. BUT prune_tombstones() removes rows from the msgs table after 2 days. They are not removed if target!='', but in single-device mode we set target to '' (i.e. mark the message for deletion) right after receiving it (see delete_expired_imap_messages()).
So, the conclusion is that probably this interacts with DeleteDeviceAfter in a bad way. I'm not entirely sure whether we can just ignore this problem, but on first sight we probably do need to explicitly remember when the last message was received on each transport.
There was a problem hiding this comment.
After talking about it with @link2xt, yes we do need to explicitly remember when the last message was received on each transport, e.g. via a column on the transports table.
1b3f434 to
a0c7c59
Compare
Hocuri
left a comment
There was a problem hiding this comment.
Looks good, only some minor things.
| }) | ||
| .await?; | ||
|
|
||
| macro_rules! assert_transport_removed { |
There was a problem hiding this comment.
Can this just be a regular function rather than a macro? Seems easier to read, and only one additional parameter (&TestContext) is required.
There was a problem hiding this comment.
Two additional parameters, as this also needs to take transport_id. Since the macro is pretty simple one, I think the convenience justifies it.
There was a problem hiding this comment.
Generally, in chatmail/core, for readability reasons and for avoiding surprising effects (like that $expr is evaluated in all the places it's copy-pasted to), we only use macros when really needed. I made a PR to explicitly state this in STYLE.md: #8410
There was a problem hiding this comment.
As a general rule #8410 makes sense, but overall i think it's not that important, especially in the test.
495c8ef to
3607f29
Compare
Automatically remove relays that are hidden (`is_published=0`) and haven't been used to receive new messages for over 90 days. Closes: #8384 Signed-off-by: Jagoda Ślązak <jslazak@jslazak.com>
3607f29 to
967d87f
Compare
| sql::update_transport_last_rcvd_timestamp(context, rfc724_mid) | ||
| .await | ||
| .context("Failed to update transport's last_rcvd_timestamp value.")?; | ||
|
|
There was a problem hiding this comment.
receive_imf is only called after downloading a message from the relay where it arrived first. When the message arrives again on another relay, it's not called again. (although we had multiple bugs in this area, so that it's worth testing again whether this actually works when basing more logic on it).
This means that with the PR as-is, transport_last_rcvd_timestamp will only be updated for the fastest transport. This is not exactly what was specified in #8384, but actually it is fine: If an unpublished relay is slower than the others AND all the messages are also received on the other, faster relays, then it's safe to delete the unpublished relay.
It does mean that we can't use the same timestamp to automatically unpublish a relay that seems not to be working. Then again, it's not entirely clear how we'll implement this logic, anyways.
So, the two options now are as follows:
- Document that
last_rcvd_timestampis only updated on the relay via which the message was received first - Move this function call to
fetch_new_msg_batch()
Either of them seems equally fine. @link2xt do you have an opinion?
There was a problem hiding this comment.
I'd leave this logic in receive_imf and also check if the contact is blocked or a contact request. I don't think it's a good idea to allow everyone knowing our public key to control which transports we can remove. We consider the public key as confidential data, but it's still not secret, so we can't absolutely protect from spammers getting know it, particularly if the user is a big channel operator.
| "UPDATE transports | ||
| SET last_rcvd_timestamp=MAX(msgs.timestamp_rcvd, transports.last_rcvd_timestamp) | ||
| FROM transports t1 | ||
| INNER JOIN imap ON t1.id=imap.transport_id | ||
| INNER JOIN msgs ON msgs.rfc724_mid=imap.rfc724_mid | ||
| WHERE msgs.rfc724_mid=?", |
There was a problem hiding this comment.
If I'm not mistaken, this function is called right after receiving the message, which means that msgs.timestamp_rcvd is simply set to the current time? I.e. it would be enough to simply use the current time rather than querying the message's received timestamp?
There was a problem hiding this comment.
that is true, I mainly did that for consistency, so we won't have some weird shift-by-few-seconds in the db, I can change it to just time() call, but this query is fast, it only runs index searches.
There was a problem hiding this comment.
I think that with just time() it will be easier to understand (e.g. it's clear that the logic works even if the message wasn't added to msgs for some reason, e.g. if it's an MDN or another kind of special message) and also we should correct transports.last_rcvd_timestamp in case if the clock were set back
Automatically remove relays that are hidden (
is_published=0) and haven't been used to receive new messages for over 90 days.Closes: #8384