GlobalCache: load LatestEventsByType on startup

This commit is contained in:
David Robertson 2023-06-01 16:02:57 +01:00
parent 747062c23b
commit da118624d9
No known key found for this signature in database
GPG Key ID: 903ECE108A39DEDD
3 changed files with 42 additions and 15 deletions

View File

@ -6,6 +6,13 @@ import (
"strings"
)
// EventMetadata holds timing information about an event, to be used when sorting room
// lists by recency.
type EventMetadata struct {
NID int64
Timestamp uint64
}
// RoomMetadata holds room-scoped data. It is primarily used in two places:
// - in the caches.GlobalCache, to hold the latest version of data that is consistent
// between all users in the room; and
@ -21,15 +28,15 @@ type RoomMetadata struct {
CanonicalAlias string
JoinCount int
InviteCount int
// LastMessageTimestamp is the origin_server_ts of the event most recently seen in
// this room. Because events arrive at the upstream homeserver out-of-order (and
// because origin_server_ts is an untrusted event field), this timestamp can
// _decrease_ as new events come in.
// TODO removeme
LastMessageTimestamp uint64
Encrypted bool
PredecessorRoomID *string
UpgradedRoomID *string
RoomType *string
// LatestEventsByType tracks timing information for the latest event in the room,
// grouped by event type.
LatestEventsByType map[string]EventMetadata
Encrypted bool
PredecessorRoomID *string
UpgradedRoomID *string
RoomType *string
// if this room is a space, which rooms are m.space.child state events. This is the same for all users hence is global.
ChildSpaceRooms map[string]struct{}
// The latest m.typing ephemeral event for this room.
@ -38,8 +45,9 @@ type RoomMetadata struct {
func NewRoomMetadata(roomID string) *RoomMetadata {
return &RoomMetadata{
RoomID: roomID,
ChildSpaceRooms: make(map[string]struct{}),
RoomID: roomID,
LatestEventsByType: make(map[string]EventMetadata),
ChildSpaceRooms: make(map[string]struct{}),
}
}

View File

@ -328,10 +328,20 @@ func (t *EventTable) SelectLatestEventsBetween(txn *sqlx.Tx, roomID string, lowe
return events, err
}
func (t *EventTable) selectLatestEventInAllRooms(txn *sqlx.Tx) ([]Event, error) {
func (t *EventTable) selectLatestEventByTypeInAllRooms(txn *sqlx.Tx) ([]Event, error) {
result := []Event{}
// TODO: this query ends up doing a sequential scan on the events table. We have
// an index on (event_type, room_id, event_nid) so I'm a little surprised that PG
// decides to do so. Can we do something better here? Ideas:
// - Find a better query for selecting the newest event of each type in a room.
// - At present we only care about the _timestamps_ of these events. Perhaps we
// could store those in the DB (and even in an index) as a column and select
// those, to avoid having to parse the event bodies.
// - We could have the application maintain a `latest_events` table so that the
// rows can be directly read. Assuming a mostly-static set of event types, reads
// are then linear in the number of rooms.
rows, err := txn.Query(
`SELECT room_id, event FROM syncv3_events WHERE event_nid in (SELECT MAX(event_nid) FROM syncv3_events GROUP BY room_id)`,
`SELECT room_id, event_nid, event FROM syncv3_events WHERE event_nid in (SELECT MAX(event_nid) FROM syncv3_events GROUP BY room_id, event_type)`,
)
if err != nil {
return nil, err
@ -339,7 +349,7 @@ func (t *EventTable) selectLatestEventInAllRooms(txn *sqlx.Tx) ([]Event, error)
defer rows.Close()
for rows.Next() {
var ev Event
if err := rows.Scan(&ev.RoomID, &ev.JSON); err != nil {
if err := rows.Scan(&ev.RoomID, &ev.NID, &ev.JSON); err != nil {
return nil, err
}
result = append(result, ev)

View File

@ -182,13 +182,22 @@ func (s *Storage) MetadataForAllRooms(txn *sqlx.Tx, tempTableName string, result
}
// work out latest timestamps
events, err := s.accumulator.eventsTable.selectLatestEventInAllRooms(txn)
events, err := s.accumulator.eventsTable.selectLatestEventByTypeInAllRooms(txn)
if err != nil {
return err
}
for _, ev := range events {
metadata := result[ev.RoomID]
metadata, ok := result[ev.RoomID]
metadata.LastMessageTimestamp = gjson.ParseBytes(ev.JSON).Get("origin_server_ts").Uint()
if !ok {
metadata = *internal.NewRoomMetadata(ev.RoomID)
}
parsed := gjson.ParseBytes(ev.JSON)
eventMetadata := internal.EventMetadata{
NID: ev.NID,
Timestamp: parsed.Get("origin_server_ts").Uint(),
}
metadata.LatestEventsByType[parsed.Get("type").Str] = eventMetadata
// it's possible the latest event is a brand new room not caught by the first SELECT for joined
// rooms e.g when you're invited to a room so we need to make sure to set the metadata again here
metadata.RoomID = ev.RoomID