merged from gramsrv upstream
This commit is contained in:
parent
79c64ee916
commit
21a0856587
651 changed files with 54774 additions and 4590 deletions
349
internal/store/postgres/message_send_hotpath.go
Normal file
349
internal/store/postgres/message_send_hotpath.go
Normal file
|
|
@ -0,0 +1,349 @@
|
|||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"telesrv/internal/domain"
|
||||
"telesrv/internal/store"
|
||||
"telesrv/internal/store/postgres/sqlcgen"
|
||||
)
|
||||
|
||||
// plainPrivateSendHotPath is a semantic classifier. Requests with additional
|
||||
// durable projections keep using the complete aggregate transaction.
|
||||
func plainPrivateSendHotPath(req domain.SendPrivateTextRequest, hooks privateSendTxHooks) bool {
|
||||
return hooks.before == nil && hooks.projectMedia == nil && hooks.afterAllocate == nil && hooks.after == nil &&
|
||||
len(req.Entities) == 0 && req.Media.IsZero() && req.ReplyMarkup.IsZero() && req.RichMessage.IsZero() &&
|
||||
req.ReplyTo == nil && req.Forward == nil && !req.Silent && !req.NoForwards &&
|
||||
req.ViaBotID == 0 && req.GroupedID == 0 && req.Effect == 0 && req.BusinessAutomationKind == ""
|
||||
}
|
||||
|
||||
type plainPrivateSendProjection struct {
|
||||
Sender domain.Message
|
||||
Recipient domain.Message
|
||||
}
|
||||
|
||||
// createPlainPrivateMessage folds the dialog/default TTL lookup into the
|
||||
// logical-message insert. A random-id conflict returns pgx.ErrNoRows and is
|
||||
// resolved through the immutable replay receipt by the caller.
|
||||
func createPlainPrivateMessage(
|
||||
ctx context.Context,
|
||||
tx pgx.Tx,
|
||||
req domain.SendPrivateTextRequest,
|
||||
requestFingerprint []byte,
|
||||
deliverRecipient bool,
|
||||
) (sqlcgen.CreatePrivateMessageRow, error) {
|
||||
row := tx.QueryRow(ctx, `
|
||||
WITH effective_ttl AS MATERIALIZED (
|
||||
SELECT CASE
|
||||
WHEN $7::int <> 0 THEN $7::int
|
||||
ELSE GREATEST(COALESCE((
|
||||
SELECT COALESCE(NULLIF(d.ttl_period, 0), u.default_history_ttl_period, 0)::int
|
||||
FROM users u
|
||||
LEFT JOIN dialogs d
|
||||
ON d.user_id = u.id
|
||||
AND d.peer_type = 'user'
|
||||
AND d.peer_id = $2::bigint
|
||||
WHERE u.id = $1::bigint
|
||||
), 0), 0)
|
||||
END AS ttl_period
|
||||
), inserted AS (
|
||||
INSERT INTO private_messages (
|
||||
sender_user_id, recipient_user_id, random_id, request_fingerprint,
|
||||
recipient_delivered, message_date, ttl_period, expires_at, body
|
||||
)
|
||||
SELECT
|
||||
$1::bigint, $2::bigint, $3::bigint, $4::bytea,
|
||||
$5::boolean, $6::int, ttl_period,
|
||||
CASE WHEN ttl_period > 0 THEN ($6::int + ttl_period)::int ELSE 0 END,
|
||||
$8::text
|
||||
FROM effective_ttl
|
||||
ON CONFLICT (sender_user_id, random_id) WHERE random_id <> 0 DO NOTHING
|
||||
RETURNING
|
||||
id, sender_user_id, recipient_user_id, random_id, message_date,
|
||||
ttl_period, expires_at, edit_date, body, entities::text AS entities_json
|
||||
)
|
||||
SELECT
|
||||
id, sender_user_id, recipient_user_id, random_id, message_date,
|
||||
ttl_period, expires_at, edit_date, body, entities_json
|
||||
FROM inserted`,
|
||||
req.SenderUserID,
|
||||
req.RecipientUserID,
|
||||
req.RandomID,
|
||||
requestFingerprint,
|
||||
deliverRecipient,
|
||||
req.Date,
|
||||
req.TTLPeriod,
|
||||
req.Message,
|
||||
)
|
||||
var result sqlcgen.CreatePrivateMessageRow
|
||||
err := row.Scan(
|
||||
&result.ID,
|
||||
&result.SenderUserID,
|
||||
&result.RecipientUserID,
|
||||
&result.RandomID,
|
||||
&result.MessageDate,
|
||||
&result.TtlPeriod,
|
||||
&result.ExpiresAt,
|
||||
&result.EditDate,
|
||||
&result.Body,
|
||||
&result.EntitiesJson,
|
||||
)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// persistPlainPrivateSendProjection reserves account PTS and writes both box
|
||||
// projections, dialogs, update events, durable dispatch rows and the immutable
|
||||
// replay receipt in one statement. The caller owns the transaction and holds
|
||||
// the ordered user advisory locks.
|
||||
func persistPlainPrivateSendProjection(
|
||||
ctx context.Context,
|
||||
tx pgx.Tx,
|
||||
req domain.SendPrivateTextRequest,
|
||||
privateMessageID int64,
|
||||
senderBoxID, recipientBoxID int,
|
||||
ttlPeriod, expiresAt int,
|
||||
) (plainPrivateSendProjection, error) {
|
||||
selfMessage := req.SenderUserID == req.RecipientUserID
|
||||
deliverRecipient := !selfMessage && !req.RecipientBlocked
|
||||
savedPeer := domain.Peer{}
|
||||
if selfMessage {
|
||||
savedPeer = domain.SavedPeerForSelfChat(req.SenderUserID, nil)
|
||||
}
|
||||
senderTemplate := domain.Message{
|
||||
ID: senderBoxID,
|
||||
UID: privateMessageID,
|
||||
OwnerUserID: req.SenderUserID,
|
||||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: req.RecipientUserID},
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: req.SenderUserID},
|
||||
Date: req.Date,
|
||||
Out: true,
|
||||
Body: req.Message,
|
||||
Pts: 1,
|
||||
TTLPeriod: ttlPeriod,
|
||||
ExpiresAt: expiresAt,
|
||||
SavedPeer: savedPeer,
|
||||
RandomID: req.RandomID,
|
||||
}
|
||||
originUserID := req.OriginUserID
|
||||
if originUserID == 0 {
|
||||
originUserID = req.SenderUserID
|
||||
}
|
||||
senderExcludeAuthKeyID, senderExcludeSessionID := int64(0), int64(0)
|
||||
if originUserID == req.SenderUserID {
|
||||
senderExcludeAuthKeyID = authKeyIDToInt64(req.OriginAuthKeyID)
|
||||
senderExcludeSessionID = req.OriginSessionID
|
||||
}
|
||||
recipientExcludeAuthKeyID, recipientExcludeSessionID := int64(0), int64(0)
|
||||
if deliverRecipient && originUserID == req.RecipientUserID {
|
||||
recipientExcludeAuthKeyID = authKeyIDToInt64(req.OriginAuthKeyID)
|
||||
recipientExcludeSessionID = req.OriginSessionID
|
||||
}
|
||||
if (senderExcludeAuthKeyID != 0) != (senderExcludeSessionID != 0) ||
|
||||
(recipientExcludeAuthKeyID != 0) != (recipientExcludeSessionID != 0) {
|
||||
return plainPrivateSendProjection{}, errInvalidDispatchOutboxExclusionPair
|
||||
}
|
||||
|
||||
receiptRecipientBoxID := recipientBoxID
|
||||
if selfMessage {
|
||||
receiptRecipientBoxID = senderBoxID
|
||||
}
|
||||
senderSnapshotTemplate, err := store.EncodePrivateSendSnapshot(senderTemplate)
|
||||
if err != nil {
|
||||
return plainPrivateSendProjection{}, err
|
||||
}
|
||||
expectedRows := 1
|
||||
if deliverRecipient {
|
||||
expectedRows = 2
|
||||
}
|
||||
|
||||
var boxRows, dialogRows, eventRows, dispatchRows, receiptRows, senderPts, recipientPts int
|
||||
err = tx.QueryRow(ctx, `
|
||||
WITH box_seed (
|
||||
owner_user_id, box_id, peer_id, outgoing,
|
||||
saved_peer_type, saved_peer_id, exclude_auth_key_id, exclude_session_id
|
||||
) AS MATERIALIZED (
|
||||
VALUES
|
||||
($1::bigint, $2::int, $3::bigint, true,
|
||||
$4::text, $5::bigint, $6::bigint, $7::bigint),
|
||||
($3::bigint, $8::int, $1::bigint, false,
|
||||
''::text, 0::bigint, $9::bigint, $10::bigint)
|
||||
), watermark_rows AS (
|
||||
INSERT INTO user_update_watermarks (user_id, contiguous_pts)
|
||||
SELECT owner_user_id, 1
|
||||
FROM box_seed
|
||||
WHERE box_id > 0
|
||||
GROUP BY owner_user_id
|
||||
ORDER BY owner_user_id
|
||||
ON CONFLICT (user_id) DO UPDATE
|
||||
SET contiguous_pts = user_update_watermarks.contiguous_pts + 1,
|
||||
updated_at = now()
|
||||
RETURNING user_id, contiguous_pts
|
||||
), box_input AS MATERIALIZED (
|
||||
SELECT seed.*, watermark_rows.contiguous_pts AS pts
|
||||
FROM box_seed seed
|
||||
JOIN watermark_rows ON watermark_rows.user_id = seed.owner_user_id
|
||||
WHERE seed.box_id > 0
|
||||
), boxes AS (
|
||||
INSERT INTO message_boxes (
|
||||
owner_user_id, box_id, private_message_id, message_sender_id,
|
||||
peer_type, peer_id, from_user_id, message_date, ttl_period,
|
||||
expires_at, outgoing, body, pts, saved_peer_type, saved_peer_id
|
||||
)
|
||||
SELECT
|
||||
i.owner_user_id, i.box_id, $11::bigint, $1::bigint,
|
||||
'user', i.peer_id, $1::bigint, $12::int, $13::int,
|
||||
$14::int, i.outgoing, $15::text, i.pts,
|
||||
i.saved_peer_type, i.saved_peer_id
|
||||
FROM box_input i
|
||||
WHERE i.box_id > 0 AND i.pts > 0
|
||||
RETURNING owner_user_id, box_id, peer_id, outgoing, pts
|
||||
), dialog_rows AS (
|
||||
INSERT INTO dialogs (
|
||||
user_id, peer_type, peer_id, top_message_id, top_message_date, unread_count
|
||||
)
|
||||
SELECT
|
||||
b.owner_user_id, 'user', b.peer_id, b.box_id, $12::int,
|
||||
CASE WHEN b.outgoing THEN 0 ELSE 1 END
|
||||
FROM boxes b
|
||||
ON CONFLICT (user_id, peer_type, peer_id) DO UPDATE SET
|
||||
top_message_id = CASE
|
||||
WHEN EXCLUDED.unread_count = 0 THEN EXCLUDED.top_message_id
|
||||
ELSE GREATEST(dialogs.top_message_id, EXCLUDED.top_message_id)
|
||||
END,
|
||||
top_message_date = CASE
|
||||
WHEN EXCLUDED.unread_count = 0 THEN EXCLUDED.top_message_date
|
||||
WHEN EXCLUDED.top_message_id >= dialogs.top_message_id THEN EXCLUDED.top_message_date
|
||||
ELSE dialogs.top_message_date
|
||||
END,
|
||||
unread_count = CASE
|
||||
WHEN EXCLUDED.unread_count = 0 THEN dialogs.unread_count
|
||||
ELSE (
|
||||
SELECT COUNT(*)::int
|
||||
FROM message_boxes m
|
||||
WHERE m.owner_user_id = dialogs.user_id
|
||||
AND m.peer_type = dialogs.peer_type
|
||||
AND m.peer_id = dialogs.peer_id
|
||||
AND NOT m.deleted
|
||||
AND NOT m.outgoing
|
||||
AND m.box_id > dialogs.read_inbox_max_id
|
||||
AND m.box_id <= GREATEST(dialogs.top_message_id, EXCLUDED.top_message_id)
|
||||
) + CASE
|
||||
-- Data-modifying CTE effects are not visible through a base-table
|
||||
-- rescan in the same statement; account explicitly for this box.
|
||||
WHEN EXCLUDED.top_message_id > dialogs.read_inbox_max_id THEN 1
|
||||
ELSE 0
|
||||
END
|
||||
END,
|
||||
unread_mark = CASE
|
||||
WHEN EXCLUDED.unread_count = 0 THEN false
|
||||
ELSE dialogs.unread_mark
|
||||
END,
|
||||
updated_at = now()
|
||||
RETURNING user_id
|
||||
), event_rows AS (
|
||||
INSERT INTO user_update_events (
|
||||
user_id, pts, pts_count, date, event_type,
|
||||
message_box_id, peer_type, peer_id
|
||||
)
|
||||
SELECT
|
||||
b.owner_user_id, b.pts, 1, $12::int, 'new_message',
|
||||
b.box_id, 'user', b.peer_id
|
||||
FROM boxes b
|
||||
RETURNING user_id, pts
|
||||
), dispatch_rows AS (
|
||||
INSERT INTO dispatch_outbox (
|
||||
target_user_id, pts, event_type, exclude_auth_key_id, exclude_session_id
|
||||
)
|
||||
SELECT
|
||||
e.user_id, e.pts, 'new_message', i.exclude_auth_key_id, i.exclude_session_id
|
||||
FROM event_rows e
|
||||
JOIN box_input i ON i.owner_user_id = e.user_id AND i.pts = e.pts
|
||||
ON CONFLICT DO NOTHING
|
||||
RETURNING target_user_id
|
||||
), receipt_rows AS (
|
||||
UPDATE private_messages
|
||||
SET sender_box_id = $2::int,
|
||||
sender_pts = (SELECT pts FROM boxes WHERE outgoing),
|
||||
recipient_box_id = $16::int,
|
||||
recipient_pts = CASE
|
||||
WHEN $16::int = 0 THEN 0
|
||||
WHEN $3::bigint = $1::bigint THEN (SELECT pts FROM boxes WHERE outgoing)
|
||||
ELSE (SELECT pts FROM boxes WHERE NOT outgoing)
|
||||
END,
|
||||
sender_snapshot = jsonb_set(
|
||||
$17::jsonb,
|
||||
ARRAY['message', 'Pts'],
|
||||
to_jsonb((SELECT pts FROM boxes WHERE outgoing)),
|
||||
false
|
||||
)
|
||||
WHERE sender_user_id = $1::bigint
|
||||
AND id = $11::bigint
|
||||
AND sender_box_id = 0
|
||||
AND sender_pts = 0
|
||||
AND sender_snapshot = '{}'::jsonb
|
||||
AND (SELECT COUNT(*) FROM boxes) = $18::int
|
||||
RETURNING id
|
||||
)
|
||||
SELECT
|
||||
(SELECT COUNT(*) FROM boxes)::int,
|
||||
(SELECT COUNT(*) FROM dialog_rows)::int,
|
||||
(SELECT COUNT(*) FROM event_rows)::int,
|
||||
(SELECT COUNT(*) FROM dispatch_rows)::int,
|
||||
(SELECT COUNT(*) FROM receipt_rows)::int,
|
||||
(SELECT pts FROM boxes WHERE outgoing)::int,
|
||||
COALESCE((SELECT pts FROM boxes WHERE NOT outgoing), 0)::int`,
|
||||
req.SenderUserID,
|
||||
senderBoxID,
|
||||
req.RecipientUserID,
|
||||
string(savedPeer.Type),
|
||||
savedPeer.ID,
|
||||
senderExcludeAuthKeyID,
|
||||
senderExcludeSessionID,
|
||||
recipientBoxID,
|
||||
recipientExcludeAuthKeyID,
|
||||
recipientExcludeSessionID,
|
||||
privateMessageID,
|
||||
req.Date,
|
||||
ttlPeriod,
|
||||
expiresAt,
|
||||
req.Message,
|
||||
receiptRecipientBoxID,
|
||||
senderSnapshotTemplate,
|
||||
expectedRows,
|
||||
).Scan(&boxRows, &dialogRows, &eventRows, &dispatchRows, &receiptRows, &senderPts, &recipientPts)
|
||||
if err != nil {
|
||||
return plainPrivateSendProjection{}, fmt.Errorf("persist plain private send projection: %w", err)
|
||||
}
|
||||
if boxRows != expectedRows || dialogRows != expectedRows || eventRows != expectedRows ||
|
||||
dispatchRows != expectedRows || receiptRows != 1 || senderPts <= 0 || (deliverRecipient && recipientPts <= 0) {
|
||||
return plainPrivateSendProjection{}, fmt.Errorf(
|
||||
"persist plain private send projection: incomplete rows boxes=%d dialogs=%d events=%d dispatch=%d receipt=%d pts=%d/%d want=%d/%d/%d/%d/1",
|
||||
boxRows, dialogRows, eventRows, dispatchRows, receiptRows, senderPts, recipientPts,
|
||||
expectedRows, expectedRows, expectedRows, expectedRows,
|
||||
)
|
||||
}
|
||||
sender := senderTemplate
|
||||
sender.Pts = senderPts
|
||||
recipient := domain.Message{}
|
||||
if selfMessage {
|
||||
recipient = sender
|
||||
} else if deliverRecipient {
|
||||
recipient = domain.Message{
|
||||
ID: recipientBoxID,
|
||||
UID: privateMessageID,
|
||||
OwnerUserID: req.RecipientUserID,
|
||||
Peer: domain.Peer{Type: domain.PeerTypeUser, ID: req.SenderUserID},
|
||||
From: domain.Peer{Type: domain.PeerTypeUser, ID: req.SenderUserID},
|
||||
Date: req.Date,
|
||||
Body: req.Message,
|
||||
Pts: recipientPts,
|
||||
TTLPeriod: ttlPeriod,
|
||||
ExpiresAt: expiresAt,
|
||||
RandomID: req.RandomID,
|
||||
}
|
||||
}
|
||||
return plainPrivateSendProjection{Sender: sender, Recipient: recipient}, nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue