owpengram-server/internal/rpc/account_settings_cache.go
2026-07-24 11:56:59 +08:00

130 lines
3.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package rpc
import (
"context"
"time"
"telesrv/internal/domain"
"telesrv/internal/readmodelcache"
)
const (
accountSettingsCacheMaxEntries = 4096
// accountSettingsCacheTTL is only the lost-notification safety net. Normal
// consistency comes from account_settings read-model notifications; local
// writes update the cached value directly.
accountSettingsCacheTTL = 24 * time.Hour
)
// accountSettingsCache 缓存 userID→AccountSettings避免设置页 4 个 get handler 各查
// 一次同一行N+1。AccountSettings 全值类型,无需深拷贝。
type accountSettingsCache struct {
cache *readmodelcache.Cache[int64, domain.AccountSettings]
}
func newAccountSettingsCache(now func() time.Time) *accountSettingsCache {
return &accountSettingsCache{
cache: readmodelcache.New[int64, domain.AccountSettings](readmodelcache.Config[int64, domain.AccountSettings]{
MaxEntries: accountSettingsCacheMaxEntries,
TTL: accountSettingsCacheTTL,
Now: now,
}),
}
}
func (c *accountSettingsCache) getOrLoad(ctx context.Context, userID int64, load func() (domain.AccountSettings, error)) (domain.AccountSettings, error) {
if c == nil || userID == 0 {
return load()
}
return c.cache.GetOrLoad(ctx, userID, load)
}
func (c *accountSettingsCache) Delete(userID int64) {
if c == nil || userID == 0 {
return
}
c.cache.Invalidate(userID)
}
func (c *accountSettingsCache) Store(userID int64, settings domain.AccountSettings) {
if c == nil || userID == 0 {
return
}
c.cache.Store(userID, settings)
}
func (c *accountSettingsCache) Flush() {
if c == nil {
return
}
c.cache.Flush()
}
// InvalidateAccountSettingsReadModel is called by the shared PostgreSQL
// read-model listener. Router owns this cache, so exposing the invalidation on
// Router keeps store/postgres independent from the RPC package.
func (r *Router) InvalidateAccountSettingsReadModel(userID int64) {
if r == nil || r.accountSettings == nil {
return
}
r.accountSettings.Delete(userID)
}
func (r *Router) FlushAccountSettingsReadModel() {
if r == nil || r.accountSettings == nil {
return
}
r.accountSettings.Flush()
}
func (r *Router) WarmAccountSettingsReadModel(ctx context.Context, userID int64) error {
if r == nil || userID == 0 {
return nil
}
_, err := r.cachedAccountSettings(ctx, userID)
return err
}
type accountSettingsBatchReader interface {
GetAccountSettingsBatch(ctx context.Context, userIDs []int64) (map[int64]domain.AccountSettings, error)
}
func (c *accountSettingsCache) getOrLoadBatch(
ctx context.Context,
userIDs []int64,
svc accountSettingsService,
) (map[int64]domain.AccountSettings, error) {
if len(userIDs) == 0 {
return map[int64]domain.AccountSettings{}, nil
}
return c.cache.GetOrLoadBatch(
ctx,
userIDs,
func(int64) (int64, bool) { return 0, true },
func(ctx context.Context, missing []int64) (map[int64]domain.AccountSettings, error) {
if batch, ok := svc.(accountSettingsBatchReader); ok {
return batch.GetAccountSettingsBatch(ctx, missing)
}
out := make(map[int64]domain.AccountSettings, len(missing))
for _, userID := range missing {
settings, err := svc.GetAccountSettings(ctx, userID)
if err != nil {
return nil, err
}
out[userID] = settings
}
return out, nil
},
)
}
// cachedAccountSettings 取(缓存的)账号单例设置;服务未接通返回默认。
func (r *Router) cachedAccountSettings(ctx context.Context, userID int64) (domain.AccountSettings, error) {
svc, ok := r.accountSettingsSvc()
if !ok {
return domain.DefaultAccountSettings(), nil
}
return r.accountSettings.getOrLoad(ctx, userID, func() (domain.AccountSettings, error) {
return svc.GetAccountSettings(ctx, userID)
})
}