From 2386b91755bc58d6e6cff6109612c1cace924a70 Mon Sep 17 00:00:00 2001 From: Haw Loeung Date: Sun, 5 Jul 2026 21:56:35 +1000 Subject: [PATCH] Reduce Mattermost API calls by caching channels --- bridge/mattermost/mattermost.go | 22 +- .../matterbridge/matterclient/channels.go | 217 ++++++++---------- .../matterbridge/matterclient/matterclient.go | 81 ++++++- 3 files changed, 173 insertions(+), 147 deletions(-) diff --git a/bridge/mattermost/mattermost.go b/bridge/mattermost/mattermost.go index 97a3181a..801560dc 100644 --- a/bridge/mattermost/mattermost.go +++ b/bridge/mattermost/mattermost.go @@ -582,28 +582,14 @@ func (m *Mattermost) GetChannels() []*bridge.ChannelInfo { func (m *Mattermost) GetChannel(channelID string) (*bridge.ChannelInfo, error) { if channelID == "" || strings.HasPrefix(channelID, "&") || channelID == m.mc.User.Nickname || channelID == m.mc.User.Username { - return nil, errors.New("channel not found") - } - - for _, channel := range m.GetChannels() { - if channel.ID == channelID { - return channel, nil - } - } - - m.UpdateChannels() - - for _, channel := range m.GetChannels() { - if channel.ID == channelID { - return channel, nil - } + return nil, errors.New("invalid channel id") } - // Fallback if it's not found in the cache. - mmchannel, _, err := m.mc.Client.GetChannel(channelID, "") - if err != nil { + mmchannel := m.mc.GetChannel(channelID) + if mmchannel == nil { return nil, errors.New("channel not found") } + return &bridge.ChannelInfo{ Name: mmchannel.Name, ID: mmchannel.Id, diff --git a/vendor/github.com/matterbridge/matterclient/channels.go b/vendor/github.com/matterbridge/matterclient/channels.go index a881874d..98dca0a0 100644 --- a/vendor/github.com/matterbridge/matterclient/channels.go +++ b/vendor/github.com/matterbridge/matterclient/channels.go @@ -8,18 +8,36 @@ import ( "github.com/mattermost/mattermost-server/v6/model" ) +func (m *Client) GetChannel(channelID string) *model.Channel { + m.Users.mu.RLock() + ch, exists := m.Users.channelData[channelID] + m.Users.mu.RUnlock() + + if exists { + return ch + } + + mmchannel, _, err := m.Client.GetChannel(channelID, "") + if err != nil { + return nil + } + + m.Users.mu.Lock() + m.Users.channelData[channelID] = mmchannel + m.Users.mu.Unlock() + + return mmchannel +} + // GetChannels returns all channels we're members off func (m *Client) GetChannels() []*model.Channel { - m.RLock() - defer m.RUnlock() + m.Users.mu.RLock() + defer m.Users.mu.RUnlock() var channels []*model.Channel - // our primary team channels first - channels = append(channels, m.Team.Channels...) - - for _, t := range m.OtherTeams { - if t.ID != m.Team.ID { - channels = append(channels, t.Channels...) + for id := range m.Users.joinedChannels { + if ch, exists := m.Users.channelData[id]; exists { + channels = append(channels, ch) } } @@ -27,17 +45,9 @@ func (m *Client) GetChannels() []*model.Channel { } func (m *Client) GetChannelHeader(channelID string) string { - m.RLock() - defer m.RUnlock() - - for _, t := range m.OtherTeams { - for _, channel := range append(t.Channels, t.MoreChannels...) { - if channel.Id == channelID { - return channel.Header - } - } + if ch := m.GetChannel(channelID); ch != nil { + return ch.Header } - return "" } @@ -53,18 +63,16 @@ func getNormalisedName(channel *model.Channel) string { } func (m *Client) GetChannelID(name string, teamID string) string { - m.RLock() - defer m.RUnlock() - if teamID != "" { return m.getChannelIDTeam(name, teamID) } - for _, t := range m.OtherTeams { - for _, channel := range append(t.Channels, t.MoreChannels...) { - if getNormalisedName(channel) == name { - return channel.Id - } + m.Users.mu.RLock() + defer m.Users.mu.RUnlock() + + for _, ch := range m.Users.channelData { + if getNormalisedName(ch) == name { + return ch.Id } } @@ -72,15 +80,14 @@ func (m *Client) GetChannelID(name string, teamID string) string { } func (m *Client) getChannelIDTeam(name string, teamID string) string { - for _, t := range m.OtherTeams { - if t.ID == teamID { - for _, channel := range append(t.Channels, t.MoreChannels...) { - if getNormalisedName(channel) == name { - return channel.Id - } - } + m.Users.mu.RLock() + for _, ch := range m.Users.channelData { + if ch.TeamId == teamID && getNormalisedName(ch) == name { + m.Users.mu.RUnlock() + return ch.Id } } + m.Users.mu.RUnlock() // Fallback if it's not found in the t.Channels or t.MoreChannels cache. // This also let's us join private channels. @@ -89,64 +96,27 @@ func (m *Client) getChannelIDTeam(name string, teamID string) string { return "" } + m.Users.mu.Lock() + if m.Users.channelData == nil { + m.Users.channelData = make(map[string]*model.Channel) + } + m.Users.channelData[channel.Id] = channel + m.Users.mu.Unlock() + return channel.Id } func (m *Client) GetChannelName(channelID string) string { - m.RLock() - defer m.RUnlock() - - for _, t := range m.OtherTeams { - if t == nil { - continue - } - - for _, channel := range append(t.Channels, t.MoreChannels...) { - if channel.Id == channelID { - return getNormalisedName(channel) - } - } + if ch := m.GetChannel(channelID); ch != nil { + return getNormalisedName(ch) } - return "" } func (m *Client) GetChannelTeamID(id string) string { - m.RLock() - defer m.RUnlock() - - if m.Team != nil { - for _, channel := range m.Team.Channels { - if channel.Id == id { - return channel.TeamId - } - } - - for _, channel := range m.Team.MoreChannels { - if channel.Id == id { - return channel.TeamId - } - } - } - - for _, t := range m.OtherTeams { - if m.Team != nil && t.ID == m.Team.ID { - continue - } - - for _, channel := range t.Channels { - if channel.Id == id { - return channel.TeamId - } - } - - for _, channel := range t.MoreChannels { - if channel.Id == id { - return channel.TeamId - } - } + if ch := m.GetChannel(id); ch != nil { + return ch.TeamId } - return "" } @@ -216,14 +186,16 @@ func (m *Client) GetLastViewedAt(channelID string) int64 { } } -// GetMoreChannels returns existing channels where we're not a member off. +// GetMoreChannels returns existing channels where we're not a member of. func (m *Client) GetMoreChannels() []*model.Channel { - m.RLock() - defer m.RUnlock() + m.Users.mu.RLock() + defer m.Users.mu.RUnlock() var channels []*model.Channel - for _, t := range m.OtherTeams { - channels = append(channels, t.MoreChannels...) + for id, ch := range m.Users.channelData { + if _, joined := m.Users.joinedChannels[id]; !joined { + channels = append(channels, ch) + } } return channels @@ -231,44 +203,23 @@ func (m *Client) GetMoreChannels() []*model.Channel { // GetTeamFromChannel returns teamId belonging to channel (DM channels have no teamId). func (m *Client) GetTeamFromChannel(channelID string) string { - m.RLock() - defer m.RUnlock() - - var channels []*model.Channel - - for _, t := range m.OtherTeams { - channels = append(channels, t.Channels...) - - if t.MoreChannels != nil { - channels = append(channels, t.MoreChannels...) + if ch := m.GetChannel(channelID); ch != nil { + if ch.Type == model.ChannelTypeGroup { + return "G" } - - for _, c := range channels { - if c.Id == channelID { - if c.Type == model.ChannelTypeGroup { - return "G" - } - - return t.ID - } - } - - channels = nil + return ch.TeamId } - return "" } func (m *Client) JoinChannel(channelID string) error { - m.RLock() - defer m.RUnlock() - - for _, c := range m.Team.Channels { - if c.Id == channelID { - m.logger.Debug("Not joining ", channelID, " already joined.") + m.Users.mu.RLock() + _, joined := m.Users.joinedChannels[channelID] + m.Users.mu.RUnlock() - return nil - } + if joined { + m.logger.Debug("Not joining ", channelID, " already joined.") + return nil } m.logger.Debug("Joining ", channelID) @@ -278,6 +229,13 @@ func (m *Client) JoinChannel(channelID string) error { return err } + m.Users.mu.Lock() + if m.Users.joinedChannels == nil { + m.Users.joinedChannels = make(map[string]struct{}) + } + m.Users.joinedChannels[channelID] = struct{}{} + m.Users.mu.Unlock() + return nil } @@ -296,7 +254,6 @@ func (m *Client) UpdateChannelsTeam(teamID string) error { resp *model.Response err error ) - const batchSize = 200 mmchannels := make([]*model.Channel, 0, batchSize) @@ -305,7 +262,6 @@ func (m *Client) UpdateChannelsTeam(teamID string) error { if err == nil { break } - if err = m.HandleRatelimit("GetChannelsForTeamForUser", resp); err != nil { return err } @@ -321,23 +277,34 @@ func (m *Client) UpdateChannelsTeam(teamID string) error { } continue } - moreChannels = append(moreChannels, channels...) - if len(channels) < batchSize { break } idx++ } - m.Lock() - defer m.Unlock() + m.Users.mu.Lock() + if m.Users.channelData == nil { + m.Users.channelData = make(map[string]*model.Channel) + m.Users.joinedChannels = make(map[string]struct{}) + } + + for _, ch := range mmchannels { + m.Users.channelData[ch.Id] = ch + m.Users.joinedChannels[ch.Id] = struct{}{} + } + for _, ch := range moreChannels { + m.Users.channelData[ch.Id] = ch + } + m.Users.mu.Unlock() + m.Users.lastUpdated.Store(time.Now().Unix()) + m.Lock() if team, exists := m.OtherTeams[teamID]; exists { - team.Channels = mmchannels - team.MoreChannels = moreChannels team.LastChannelSync = time.Now() } + m.Unlock() return nil } diff --git a/vendor/github.com/matterbridge/matterclient/matterclient.go b/vendor/github.com/matterbridge/matterclient/matterclient.go index ff58b546..75346044 100644 --- a/vendor/github.com/matterbridge/matterclient/matterclient.go +++ b/vendor/github.com/matterbridge/matterclient/matterclient.go @@ -44,14 +44,15 @@ type UsersCache struct { teams map[string]map[string]struct{} statuses map[string]string + channelData map[string]*model.Channel + joinedChannels map[string]struct{} + lastUpdated atomic.Int64 } type Team struct { Team *model.Team ID string - Channels []*model.Channel - MoreChannels []*model.Channel LastUserSync time.Time LastChannelSync time.Time @@ -126,6 +127,9 @@ func New(login string, pass string, team string, server string, mfatoken string) channels: make(map[string]map[string]struct{}), teams: make(map[string]map[string]struct{}), statuses: make(map[string]string), + + channelData: make(map[string]*model.Channel), + joinedChannels: make(map[string]struct{}), }, rootLogger: rootLogger, lruCache: cache, @@ -456,9 +460,41 @@ func (m *Client) initUserChannels() error { return err } + m.RLock() + teams := make([]*Team, 0, len(m.OtherTeams)) for _, t := range m.OtherTeams { - m.logger.Debugf("found %d channels for user in team %s", len(t.Channels), t.Team.Name) - m.logger.Debugf("found %d public channels in team %s", len(t.MoreChannels), t.Team.Name) + teams = append(teams, t) + } + m.RUnlock() + + m.Users.mu.RLock() + defer m.Users.mu.RUnlock() + + var dmCount int + for id, ch := range m.Users.channelData { + if ch.TeamId == "" { + if _, joined := m.Users.joinedChannels[id]; joined { + dmCount++ + } + } + } + m.logger.Debugf("found %d direct/group message channels", dmCount) + + for _, t := range teams { + var joinedCount, publicCount int + + for id, ch := range m.Users.channelData { + if ch.TeamId == t.ID { + if _, joined := m.Users.joinedChannels[id]; joined { + joinedCount++ + } else { + publicCount++ + } + } + } + + m.logger.Debugf("found %d channels for user in team %s", joinedCount, t.Team.Name) + m.logger.Debugf("found %d public channels in team team %s", publicCount, t.Team.Name) } return nil @@ -984,5 +1020,42 @@ func (m *Client) maintainUsersCache(event *model.WebSocketEvent) { } } } + + case model.WebsocketEventChannelCreated, model.WebsocketEventDirectAdded: + if channelStr, ok := event.GetData()["channel"].(string); ok { + channel := &model.Channel{} + if err := json.Unmarshal([]byte(channelStr), channel); err == nil { + m.Users.mu.Lock() + m.Users.channelData[channel.Id] = channel + if channel.Type == model.ChannelTypeDirect || channel.Type == model.ChannelTypeGroup { + m.Users.joinedChannels[channel.Id] = struct{}{} + } + m.Users.mu.Unlock() + m.Users.lastUpdated.Store(time.Now().Unix()) + } + } else if channelID, ok := event.GetData()["channel_id"].(string); ok && channelID != "" { + m.GetChannel(channelID) + } + + case model.WebsocketEventChannelUpdated: + if channelStr, ok := event.GetData()["channel"].(string); ok { + channel := &model.Channel{} + if err := json.Unmarshal([]byte(channelStr), channel); err == nil { + m.Users.mu.Lock() + m.Users.channelData[channel.Id] = channel + m.Users.mu.Unlock() + m.Users.lastUpdated.Store(time.Now().Unix()) + } + } + + case model.WebsocketEventChannelDeleted: + if channelID, ok := event.GetData()["channel_id"].(string); ok && channelID != "" { + m.Users.mu.Lock() + delete(m.Users.channelData, channelID) + delete(m.Users.joinedChannels, channelID) + delete(m.Users.channels, channelID) + m.Users.mu.Unlock() + m.Users.lastUpdated.Store(time.Now().Unix()) + } } }