perf(group): reduce websocket fanout serialization
This commit is contained in:
+94
-1
@@ -650,12 +650,20 @@ func (t *Topic) runLocal(hub *Hub) {
|
||||
|
||||
// handleClientMsg is the top-level handler of messages received by the topic from sessions.
|
||||
func (t *Topic) handleClientMsg(msg *ClientComMessage) {
|
||||
started := time.Now()
|
||||
kind := "other"
|
||||
if t.cat == types.TopicCatP2P {
|
||||
kind = "p2p"
|
||||
} else if t.cat == types.TopicCatGrp {
|
||||
kind = "group"
|
||||
}
|
||||
operation := "other"
|
||||
if msg.Pub != nil {
|
||||
operation = "publish"
|
||||
} else if msg.Note != nil {
|
||||
operation = "note"
|
||||
}
|
||||
defer immetrics.ObserveTopicProcess(kind, t.metricRole(), operation, started)
|
||||
immetrics.ObserveTopicQueueWait(kind, msg.queuedAt)
|
||||
msg.queuedAt = time.Time{}
|
||||
if msg.TenantID.IsZero() {
|
||||
@@ -1958,9 +1966,18 @@ func (t *Topic) handlePresence(msg *ServerComMessage) {
|
||||
|
||||
// broadcastToSessions writes message to attached sessions.
|
||||
func (t *Topic) broadcastToSessions(msg *ServerComMessage) {
|
||||
started := time.Now()
|
||||
kind := t.metricKind()
|
||||
role := t.metricRole()
|
||||
websocketTargets, multiplexTargets, otherTargets := 0, 0, 0
|
||||
defer func() {
|
||||
immetrics.ObserveTopicBroadcast(kind, role, started, websocketTargets, multiplexTargets, otherTargets)
|
||||
}()
|
||||
msg.TenantID = t.tenantID
|
||||
// List of sessions to be dropped.
|
||||
var dropSessions []*Session
|
||||
var sharedPayload []byte
|
||||
sharedPayloadReady := false
|
||||
// Broadcast the message. Only {data}, {pres}, {info} are broadcastable.
|
||||
// {meta} and {ctrl} are sent to the session only
|
||||
for sess, pssd := range t.sessions {
|
||||
@@ -2027,7 +2044,41 @@ func (t *Topic) broadcastToSessions(msg *ServerComMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
// Make a copy of msg since messages sent to sessions differ.
|
||||
if sess.proto == WEBSOCK {
|
||||
websocketTargets++
|
||||
} else if sess.isMultiplex() {
|
||||
multiplexTargets++
|
||||
} else {
|
||||
otherTargets++
|
||||
}
|
||||
|
||||
// Ordinary group messages are identical for every local WebSocket subscriber.
|
||||
// Serialize once and reuse immutable bytes to avoid O(recipients) JSON work.
|
||||
if t.canShareSerializedBroadcast(msg, sess, pssd) {
|
||||
if !sharedPayloadReady {
|
||||
var err error
|
||||
sharedPayload, err = json.Marshal(msg)
|
||||
if err != nil {
|
||||
immetrics.IncTopicSharedSerialization(kind, "error")
|
||||
logs.Warn.Printf("topic[%s]: shared broadcast serialization failed: %v", t.name, err)
|
||||
sharedPayload = nil
|
||||
} else {
|
||||
immetrics.IncTopicSharedSerialization(kind, "serialized")
|
||||
}
|
||||
sharedPayloadReady = true
|
||||
}
|
||||
if sharedPayload != nil {
|
||||
if sess.queueOutSharedBytes(sharedPayload) {
|
||||
continue
|
||||
}
|
||||
immetrics.IncTopicSharedSerialization(kind, "full")
|
||||
logs.Warn.Printf("topic[%s]: connection stuck, detaching - %s", t.name, sess.sid)
|
||||
dropSessions = append(dropSessions, sess)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Make a copy of msg since messages sent to sessions may differ.
|
||||
msgCopy := msg.copy()
|
||||
// Topic name may be different depending on the user to which the `sess` belongs.
|
||||
t.prepareBroadcastableMessage(msgCopy, pssd.uid, pssd.isChanSub)
|
||||
@@ -2046,6 +2097,48 @@ func (t *Topic) broadcastToSessions(msg *ServerComMessage) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Topic) metricKind() string {
|
||||
switch t.cat {
|
||||
case types.TopicCatGrp:
|
||||
return "group"
|
||||
case types.TopicCatP2P:
|
||||
return "p2p"
|
||||
default:
|
||||
return "other"
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Topic) metricRole() string {
|
||||
if t.isProxy {
|
||||
return "proxy"
|
||||
}
|
||||
return "master"
|
||||
}
|
||||
|
||||
func serverMessageType(msg *ServerComMessage) string {
|
||||
switch {
|
||||
case msg == nil:
|
||||
return "unknown"
|
||||
case msg.Data != nil:
|
||||
return "data"
|
||||
case msg.Ctrl != nil:
|
||||
return "ctrl"
|
||||
case msg.Pres != nil:
|
||||
return "pres"
|
||||
case msg.Info != nil:
|
||||
return "info"
|
||||
case msg.Meta != nil:
|
||||
return "meta"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Topic) canShareSerializedBroadcast(msg *ServerComMessage, sess *Session, pssd perSessionData) bool {
|
||||
return t.cat == types.TopicCatGrp && !t.isChan && sess.proto == WEBSOCK && !pssd.isChanSub &&
|
||||
(msg.Data != nil || msg.Pres != nil || msg.Info != nil)
|
||||
}
|
||||
|
||||
// subscriptionReply generates a response to a subscription request
|
||||
func (t *Topic) subscriptionReply(asChan bool, msg *ClientComMessage) error {
|
||||
// The topic is already initialized by the Hub
|
||||
|
||||
Reference in New Issue
Block a user