merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
216
internal/rpc/story_sparse_projection_cache.go
Normal file
216
internal/rpc/story_sparse_projection_cache.go
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
package rpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
appreadmodel "telesrv/internal/app/readmodel"
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/readmodelcache"
|
||||
"telesrv/internal/store"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultStoryActivePeerCacheMaxEntries = 1_000_000
|
||||
defaultStoryHiddenListCacheMaxEntries = 100_000
|
||||
defaultStoryHiddenListCacheMaxBytes = 64 << 20
|
||||
missingStoryReadModelHash = int64(-1)
|
||||
)
|
||||
|
||||
var errStorySparseProjectionUnavailable = errors.New("story sparse projection read model unavailable")
|
||||
|
||||
type storySparseProjectionProvider interface {
|
||||
ActiveStoryPeerExpirations(ctx context.Context, peers []domain.Peer, now int) (map[domain.Peer]int, error)
|
||||
ListHiddenStoryPeers(ctx context.Context, viewerUserID int64) ([]domain.Peer, error)
|
||||
}
|
||||
|
||||
type activeStoryPeerFact struct {
|
||||
maxExpireAt int
|
||||
}
|
||||
|
||||
type hiddenStoryPeerSet map[domain.Peer]struct{}
|
||||
|
||||
// storySparseProjectionCache avoids a viewer×peer negative matrix. Active
|
||||
// existence is shared by peer; hidden preferences are stored once per viewer.
|
||||
type storySparseProjectionCache struct {
|
||||
versions store.ReadModelVersionStore
|
||||
active *readmodelcache.Cache[domain.Peer, activeStoryPeerFact]
|
||||
hidden *readmodelcache.Cache[int64, hiddenStoryPeerSet]
|
||||
}
|
||||
|
||||
func newStorySparseProjectionCache(
|
||||
versions store.ReadModelVersionStore,
|
||||
activeMaxEntries int,
|
||||
hiddenMaxEntries int,
|
||||
hiddenMaxBytes int64,
|
||||
) *storySparseProjectionCache {
|
||||
if versions == nil {
|
||||
return nil
|
||||
}
|
||||
if activeMaxEntries <= 0 {
|
||||
activeMaxEntries = defaultStoryActivePeerCacheMaxEntries
|
||||
}
|
||||
if hiddenMaxEntries <= 0 {
|
||||
hiddenMaxEntries = defaultStoryHiddenListCacheMaxEntries
|
||||
}
|
||||
if hiddenMaxBytes <= 0 {
|
||||
hiddenMaxBytes = defaultStoryHiddenListCacheMaxBytes
|
||||
}
|
||||
return &storySparseProjectionCache{
|
||||
versions: versions,
|
||||
active: readmodelcache.New[domain.Peer, activeStoryPeerFact](readmodelcache.Config[domain.Peer, activeStoryPeerFact]{
|
||||
MaxEntries: activeMaxEntries,
|
||||
}),
|
||||
hidden: readmodelcache.New[int64, hiddenStoryPeerSet](readmodelcache.Config[int64, hiddenStoryPeerSet]{
|
||||
MaxEntries: hiddenMaxEntries,
|
||||
MaxWeight: hiddenMaxBytes,
|
||||
Weight: func(value hiddenStoryPeerSet) int64 {
|
||||
return 64 + int64(len(value))*32
|
||||
},
|
||||
Clone: cloneHiddenStoryPeerSet,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *storySparseProjectionCache) activePeers(
|
||||
ctx context.Context,
|
||||
provider storySparseProjectionProvider,
|
||||
peers []domain.Peer,
|
||||
now int,
|
||||
) ([]domain.Peer, error) {
|
||||
if c == nil || c.versions == nil || c.active == nil || provider == nil {
|
||||
return nil, errStorySparseProjectionUnavailable
|
||||
}
|
||||
peers = uniqueStoryProjectionPeers(peers)
|
||||
if len(peers) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
for _, peer := range peers {
|
||||
if fact, ok := c.active.Peek(peer); ok && fact.maxExpireAt > 0 && fact.maxExpireAt <= now {
|
||||
c.active.Invalidate(peer)
|
||||
}
|
||||
}
|
||||
keys := make([]store.ReadModelKey, 0, len(peers))
|
||||
for _, peer := range peers {
|
||||
keys = append(keys, store.ReadModelKey{
|
||||
Model: appreadmodel.ModelStoryPeer, PeerType: peer.Type, PeerID: peer.ID,
|
||||
})
|
||||
}
|
||||
hashes, err := c.versions.ReadModelHashes(ctx, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
peerHashes := make(map[domain.Peer]int64, len(peers))
|
||||
for _, key := range keys {
|
||||
hash := hashes[key]
|
||||
if hash == 0 {
|
||||
hash = missingStoryReadModelHash
|
||||
}
|
||||
peerHashes[domain.Peer{Type: key.PeerType, ID: key.PeerID}] = hash
|
||||
}
|
||||
values, err := c.active.GetOrLoadBatch(ctx, peers,
|
||||
func(peer domain.Peer) (int64, bool) { return peerHashes[peer], true },
|
||||
func(ctx context.Context, missing []domain.Peer) (map[domain.Peer]activeStoryPeerFact, error) {
|
||||
expirations, err := provider.ActiveStoryPeerExpirations(ctx, missing, now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make(map[domain.Peer]activeStoryPeerFact, len(missing))
|
||||
for _, peer := range missing {
|
||||
out[peer] = activeStoryPeerFact{maxExpireAt: expirations[peer]}
|
||||
}
|
||||
return out, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make([]domain.Peer, 0, len(peers))
|
||||
for _, peer := range peers {
|
||||
if values[peer].maxExpireAt > now {
|
||||
out = append(out, peer)
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *storySparseProjectionCache) hiddenPeers(
|
||||
ctx context.Context,
|
||||
provider storySparseProjectionProvider,
|
||||
viewerUserID int64,
|
||||
) (hiddenStoryPeerSet, error) {
|
||||
if c == nil || c.versions == nil || c.hidden == nil || provider == nil || viewerUserID == 0 {
|
||||
return nil, errStorySparseProjectionUnavailable
|
||||
}
|
||||
key := store.ReadModelKey{
|
||||
Model: appreadmodel.ModelStoryHiddenList,
|
||||
OwnerUserID: viewerUserID,
|
||||
PeerType: domain.PeerTypeUser,
|
||||
PeerID: viewerUserID,
|
||||
}
|
||||
hash, _, err := c.versions.ReadModelHash(ctx, key.Model, key.OwnerUserID, key.PeerType, key.PeerID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hash == 0 {
|
||||
hash = missingStoryReadModelHash
|
||||
}
|
||||
return c.hidden.GetOrLoadVersioned(ctx, viewerUserID, hash, func() (hiddenStoryPeerSet, error) {
|
||||
peers, err := provider.ListHiddenStoryPeers(ctx, viewerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make(hiddenStoryPeerSet, len(peers))
|
||||
for _, peer := range peers {
|
||||
if peer.ID != 0 {
|
||||
out[peer] = struct{}{}
|
||||
}
|
||||
}
|
||||
return out, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *storySparseProjectionCache) DeletePeer(peer domain.Peer) {
|
||||
if c != nil && peer.ID != 0 {
|
||||
c.active.Invalidate(peer)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *storySparseProjectionCache) DeleteViewer(viewerUserID int64) {
|
||||
if c != nil && viewerUserID != 0 {
|
||||
c.hidden.Invalidate(viewerUserID)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *storySparseProjectionCache) Flush() {
|
||||
if c != nil {
|
||||
c.active.Flush()
|
||||
c.hidden.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func cloneHiddenStoryPeerSet(in hiddenStoryPeerSet) hiddenStoryPeerSet {
|
||||
if in == nil {
|
||||
return hiddenStoryPeerSet{}
|
||||
}
|
||||
out := make(hiddenStoryPeerSet, len(in))
|
||||
for peer := range in {
|
||||
out[peer] = struct{}{}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func uniqueStoryProjectionPeers(peers []domain.Peer) []domain.Peer {
|
||||
out := make([]domain.Peer, 0, len(peers))
|
||||
seen := make(map[domain.Peer]struct{}, len(peers))
|
||||
for _, peer := range peers {
|
||||
if peer.ID == 0 || (peer.Type != domain.PeerTypeUser && peer.Type != domain.PeerTypeChannel) {
|
||||
continue
|
||||
}
|
||||
if _, ok := seen[peer]; ok {
|
||||
continue
|
||||
}
|
||||
seen[peer] = struct{}{}
|
||||
out = append(out, peer)
|
||||
}
|
||||
return out
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue