From d7fd75e48767f765ac060435387d07bc97371f4f Mon Sep 17 00:00:00 2001 From: onysd Date: Fri, 31 Jul 2026 22:33:08 +0300 Subject: [PATCH] fixes for loading issues --- internal/rpc/deps.go | 5 ++ internal/rpc/phone_rpc_test.go | 12 +++++ internal/rpc/users.go | 78 +++++++++++++---------------- internal/store/postgres/user_pts.go | 13 ++--- 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/internal/rpc/deps.go b/internal/rpc/deps.go index d8100e8b..f3b1bef1 100644 --- a/internal/rpc/deps.go +++ b/internal/rpc/deps.go @@ -424,6 +424,11 @@ type PrivacyService interface { SetRules(ctx context.Context, ownerUserID int64, key domain.PrivacyKey, rules []domain.PrivacyRule) (domain.PrivacyRules, error) AddAllowUser(ctx context.Context, ownerUserID int64, key domain.PrivacyKey, targetUserID int64) (domain.PrivacyRules, bool, error) CanSee(ctx context.Context, ownerUserID, viewerUserID int64, key domain.PrivacyKey) (bool, error) + // CanSeeBatch evaluates multiple keys for one (owner, viewer) pair with a + // single ListPrivacyRules + GetReverseContacts round trip, equivalent to + // one CanSee call per key. Used where a single projection (e.g. + // getFullUser) otherwise needs several keys checked at once. + CanSeeBatch(ctx context.Context, ownerUserIDs []int64, viewerUserID int64, keys []domain.PrivacyKey) (map[int64]map[domain.PrivacyKey]bool, error) } // HelpService 抽象启动配置与国家区号目录。 diff --git a/internal/rpc/phone_rpc_test.go b/internal/rpc/phone_rpc_test.go index 92410b0f..9c622df2 100644 --- a/internal/rpc/phone_rpc_test.go +++ b/internal/rpc/phone_rpc_test.go @@ -102,6 +102,18 @@ func (p stubPrivacy) CanSee(_ context.Context, _, _ int64, key domain.PrivacyKey return !p.deny[key], nil } +func (p stubPrivacy) CanSeeBatch(_ context.Context, ownerUserIDs []int64, _ int64, keys []domain.PrivacyKey) (map[int64]map[domain.PrivacyKey]bool, error) { + out := make(map[int64]map[domain.PrivacyKey]bool, len(ownerUserIDs)) + for _, owner := range ownerUserIDs { + m := make(map[domain.PrivacyKey]bool, len(keys)) + for _, k := range keys { + m[k] = !p.deny[k] + } + out[owner] = m + } + return out, nil +} + type phoneFixture struct { t *testing.T ctx context.Context diff --git a/internal/rpc/users.go b/internal/rpc/users.go index 2da02569..0d5126c5 100644 --- a/internal/rpc/users.go +++ b/internal/rpc/users.go @@ -237,15 +237,40 @@ func applyContactNoteToUserFull(user domain.User, full *tg.UserFull) bool { } func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int64, u domain.User) (tg.UserFull, error) { - about := u.About - if r.deps.Privacy != nil && u.ID != currentUserID { - allowed, err := r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyAbout) + // One CanSeeBatch call replaces what used to be up to six sequential + // CanSee calls (about/phone_call/phone_p2p/profile_photo/saved_music/ + // birthday) below and in fillUserFullPhotos -- each CanSee cost two + // queries (rules + contacts), so this alone was ~12 of the ~15 db + // queries getFullUser used to make. + var privacyVisibility map[domain.PrivacyKey]bool + if r.deps.Privacy != nil && currentUserID != 0 && u.ID != 0 && u.ID != currentUserID { + batch, err := r.deps.Privacy.CanSeeBatch(ctx, []int64{u.ID}, currentUserID, []domain.PrivacyKey{ + domain.PrivacyKeyAbout, + domain.PrivacyKeyPhoneCall, + domain.PrivacyKeyPhoneP2P, + domain.PrivacyKeyProfilePhoto, + domain.PrivacyKeySavedMusic, + domain.PrivacyKeyBirthday, + }) if err != nil { return tg.UserFull{}, internalErr() } - if !allowed { - about = "" + privacyVisibility = batch[u.ID] + } + // canSee mirrors CanSee's own fail-open semantics: self is always + // visible, and an unset map means the batch call above was skipped + // (privacy service absent, or self) -- both cases default to visible, + // exactly like every "if r.deps.Privacy != nil" guard did individually. + canSee := func(key domain.PrivacyKey) bool { + if u.ID == currentUserID || privacyVisibility == nil { + return true } + return privacyVisibility[key] + } + + about := u.About + if u.ID != currentUserID && !canSee(domain.PrivacyKeyAbout) { + about = "" } // Surface the scam/fake warning to other viewers (never to the account // itself), non-destructively over the projected About. @@ -261,19 +286,8 @@ func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int6 // 通话入口:客户端不见 phone_calls_available=true 不显示通话按钮(P1 前置项)。 // phone_calls_private 标记对端禁 P2P(p2p_allowed 真值在通话确认时另行计算)。 if !u.Bot && u.ID != currentUserID { - callsAllowed, p2pAllowed := true, true - if r.deps.Privacy != nil { - allowed, err := r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyPhoneCall) - if err != nil { - return tg.UserFull{}, internalErr() - } - callsAllowed = allowed - allowed, err = r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyPhoneP2P) - if err != nil { - return tg.UserFull{}, internalErr() - } - p2pAllowed = allowed - } + callsAllowed := canSee(domain.PrivacyKeyPhoneCall) + p2pAllowed := canSee(domain.PrivacyKeyPhoneP2P) full.PhoneCallsAvailable = callsAllowed full.VideoCallsAvailable = callsAllowed full.PhoneCallsPrivate = !p2pAllowed @@ -302,15 +316,11 @@ func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int6 break } } - if err := r.fillUserFullPhotos(ctx, currentUserID, u.ID, &full); err != nil { + if err := r.fillUserFullPhotos(ctx, currentUserID, u.ID, &full, canSee(domain.PrivacyKeyProfilePhoto)); err != nil { return tg.UserFull{}, err } if r.deps.Account != nil { - allowed, err := r.canSeeSavedMusic(ctx, currentUserID, u.ID) - if err != nil { - return tg.UserFull{}, err - } - if allowed { + if canSee(domain.PrivacyKeySavedMusic) { music, err := r.deps.Account.ListSavedMusic(ctx, u.ID, 0, 1) if err != nil { return tg.UserFull{}, internalErr() @@ -382,15 +392,7 @@ func (r *Router) buildUserFullProjection(ctx context.Context, currentUserID int6 // 生日(account.updateBirthday):落 userFull.birthday,按 PrivacyKeyBirthday 对他人裁剪, // 本人恒可见。 if u.Birthday.IsSet() { - birthdayVisible := true - if r.deps.Privacy != nil && u.ID != currentUserID { - allowed, err := r.deps.Privacy.CanSee(ctx, u.ID, currentUserID, domain.PrivacyKeyBirthday) - if err != nil { - return tg.UserFull{}, internalErr() - } - birthdayVisible = allowed - } - if birthdayVisible { + if canSee(domain.PrivacyKeyBirthday) { full.SetBirthday(tgBirthday(u.Birthday)) } } @@ -570,7 +572,7 @@ func savedMusicDocumentIDs(docs []domain.Document) []int64 { return ids } -func (r *Router) fillUserFullPhotos(ctx context.Context, viewerUserID, ownerUserID int64, full *tg.UserFull) error { +func (r *Router) fillUserFullPhotos(ctx context.Context, viewerUserID, ownerUserID int64, full *tg.UserFull, profileAllowed bool) error { if r.deps.Files == nil || full == nil || ownerUserID == 0 { return nil } @@ -602,14 +604,6 @@ func (r *Router) fillUserFullPhotos(ctx context.Context, viewerUserID, ownerUser } } } - profileAllowed := true - if r.deps.Privacy != nil { - var err error - profileAllowed, err = r.deps.Privacy.CanSee(ctx, ownerUserID, viewerUserID, domain.PrivacyKeyProfilePhoto) - if err != nil { - return internalErr() - } - } if profileAllowed { if photo, found, err := r.deps.Files.CurrentProfilePhotoKind(ctx, domain.PeerTypeUser, ownerUserID, domain.ProfilePhotoKindProfile); err != nil { return internalErr() diff --git a/internal/store/postgres/user_pts.go b/internal/store/postgres/user_pts.go index b1d41fb0..49b60bec 100644 --- a/internal/store/postgres/user_pts.go +++ b/internal/store/postgres/user_pts.go @@ -30,15 +30,16 @@ ON CONFLICT (user_id) DO NOTHING`, userID) func reserveUserPts(ctx context.Context, db sqlcgen.DBTX, userID int64, count int) (int, error) { count = normalizePtsCount(count) - if err := ensureUserUpdateWatermark(ctx, db, userID); err != nil { - return 0, err - } + // Single upsert instead of ensure-insert-then-update: this runs on every + // message send/update event, and the ensure step was a no-op round trip + // for every user past their first-ever pts allocation. var pts int if err := db.QueryRow(ctx, ` -UPDATE user_update_watermarks -SET contiguous_pts = contiguous_pts + $2, +INSERT INTO user_update_watermarks (user_id, contiguous_pts) +VALUES ($1, $2) +ON CONFLICT (user_id) DO UPDATE +SET contiguous_pts = user_update_watermarks.contiguous_pts + $2, updated_at = now() -WHERE user_id = $1 RETURNING contiguous_pts`, userID, count).Scan(&pts); err != nil { return 0, fmt.Errorf("reserve user pts: %w", err) }