mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-20 20:39:08 +02:00
grpc components encoding with optimisations
This commit is contained in:
@@ -13,8 +13,9 @@ import (
|
||||
|
||||
// Peer capability constants mirror the proto enum values.
|
||||
const (
|
||||
PeerCapabilitySourcePrefixes int32 = 1
|
||||
PeerCapabilityIPv6Overlay int32 = 2
|
||||
PeerCapabilitySourcePrefixes int32 = 1
|
||||
PeerCapabilityIPv6Overlay int32 = 2
|
||||
PeerCapabilityComponentNetworkMap int32 = 3
|
||||
)
|
||||
|
||||
// Peer represents a machine connected to the network.
|
||||
@@ -247,6 +248,14 @@ func (p *Peer) SupportsSourcePrefixes() bool {
|
||||
return p.HasCapability(PeerCapabilitySourcePrefixes)
|
||||
}
|
||||
|
||||
// SupportsComponentNetworkMap reports whether the peer assembles its
|
||||
// NetworkMap from server-shipped components instead of consuming a fully
|
||||
// expanded NetworkMap. Determines whether the network_map controller skips
|
||||
// Calculate() server-side and emits the components envelope.
|
||||
func (p *Peer) SupportsComponentNetworkMap() bool {
|
||||
return p.HasCapability(PeerCapabilityComponentNetworkMap)
|
||||
}
|
||||
|
||||
func capabilitiesEqual(a, b []int32) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
|
||||
@@ -45,6 +45,14 @@ type GroupPeer struct {
|
||||
PeerID string `gorm:"primaryKey"`
|
||||
}
|
||||
|
||||
// HasSeqID reports whether the group has been persisted long enough to have a
|
||||
// per-account sequence id allocated. Wire encoders that key off AccountSeqID
|
||||
// must skip groups that return false here — otherwise multiple unpersisted
|
||||
// groups would collide on id 0.
|
||||
func (g *Group) HasSeqID() bool {
|
||||
return g != nil && g.AccountSeqID != 0
|
||||
}
|
||||
|
||||
func (g *Group) LoadGroupPeers() {
|
||||
g.Peers = make([]string, len(g.GroupPeers))
|
||||
for i, peer := range g.GroupPeers {
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
goproto "google.golang.org/protobuf/proto"
|
||||
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller/cache"
|
||||
mgmtgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
)
|
||||
|
||||
// wireBenchScales mirrors the scales used by networkmap_benchmark_test.go but
|
||||
// trimmed: encoding+marshal are linear, so we don't need the 30k peer extreme
|
||||
// to see the trend.
|
||||
var wireBenchScales = []benchmarkScale{
|
||||
{"100peers_5groups", 100, 5},
|
||||
{"500peers_20groups", 500, 20},
|
||||
{"1000peers_50groups", 1000, 50},
|
||||
{"5000peers_100groups", 5000, 100},
|
||||
}
|
||||
|
||||
// populateAccountSeqIDs assigns deterministic AccountSeqIDs to every group and
|
||||
// policy in the account so that the component encoder can reference them. The
|
||||
// scalableTestAccount fixture builds entities by struct literal and skips this
|
||||
// step, but production paths populate the IDs via the store layer.
|
||||
func populateAccountSeqIDs(account *types.Account) {
|
||||
var nextGroupSeq uint32 = 1
|
||||
for _, g := range account.Groups {
|
||||
g.AccountSeqID = nextGroupSeq
|
||||
nextGroupSeq++
|
||||
}
|
||||
var nextPolicySeq uint32 = 1
|
||||
for _, p := range account.Policies {
|
||||
p.AccountSeqID = nextPolicySeq
|
||||
nextPolicySeq++
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkNetworkMapWireEncode reports per-call ns and the marshaled wire
|
||||
// size for both encoding paths. Run with:
|
||||
//
|
||||
// go test -run=^$ -bench=BenchmarkNetworkMapWireEncode -benchmem ./management/server/types/
|
||||
func BenchmarkNetworkMapWireEncode(b *testing.B) {
|
||||
skipCIBenchmark(b)
|
||||
|
||||
for _, scale := range wireBenchScales {
|
||||
account, validatedPeers := scalableTestAccount(scale.peers, scale.groups)
|
||||
populateAccountSeqIDs(account)
|
||||
|
||||
ctx := context.Background()
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
|
||||
peerID := "peer-0"
|
||||
peer := account.Peers[peerID]
|
||||
|
||||
networkMap := account.GetPeerNetworkMapFromComponents(ctx, peerID, nbdns.CustomZone{}, nil, validatedPeers, resourcePolicies, routers, nil, groupIDToUserIDs)
|
||||
components := account.GetPeerNetworkMapComponents(ctx, peerID, nbdns.CustomZone{}, nil, validatedPeers, resourcePolicies, routers, groupIDToUserIDs)
|
||||
|
||||
dnsCache := &cache.DNSConfigCache{}
|
||||
settings := &types.Settings{}
|
||||
|
||||
// Pre-encode once so the size metric is identical for every run inside
|
||||
// the same scale; the b.Loop call only re-runs encode + Marshal.
|
||||
legacyResp := mgmtgrpc.ToSyncResponse(ctx, nil, nil, nil, peer, nil, nil, networkMap, "netbird.cloud", nil, dnsCache, settings, nil, nil, 0)
|
||||
legacyBytes, err := goproto.Marshal(legacyResp.NetworkMap)
|
||||
if err != nil {
|
||||
b.Fatalf("marshal legacy networkmap: %v", err)
|
||||
}
|
||||
|
||||
envelopeInput := mgmtgrpc.ComponentsEnvelopeInput{
|
||||
Components: components,
|
||||
PeerConfig: legacyResp.NetworkMap.PeerConfig,
|
||||
DNSDomain: "netbird.cloud",
|
||||
}
|
||||
envelope := mgmtgrpc.EncodeNetworkMapEnvelope(envelopeInput)
|
||||
envelopeBytes, err := goproto.Marshal(envelope)
|
||||
if err != nil {
|
||||
b.Fatalf("marshal envelope: %v", err)
|
||||
}
|
||||
|
||||
b.Run(fmt.Sprintf("legacy/%s", scale.name), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ReportMetric(float64(len(legacyBytes)), "bytes/msg")
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
resp := mgmtgrpc.ToSyncResponse(ctx, nil, nil, nil, peer, nil, nil, networkMap, "netbird.cloud", nil, dnsCache, settings, nil, nil, 0)
|
||||
if _, err := goproto.Marshal(resp.NetworkMap); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
b.Run(fmt.Sprintf("components/%s", scale.name), func(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
b.ReportMetric(float64(len(envelopeBytes)), "bytes/msg")
|
||||
b.ResetTimer()
|
||||
for range b.N {
|
||||
env := mgmtgrpc.EncodeNetworkMapEnvelope(envelopeInput)
|
||||
if _, err := goproto.Marshal(env); err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkNetworkMapWireSize is a fast snapshot of the wire size by scale
|
||||
// without a tight encode loop. Run with -bench to see one ns/op + bytes per
|
||||
// scale (treat the timing as informational; the sample is one Marshal per
|
||||
// scale, not the full b.N loop).
|
||||
func BenchmarkNetworkMapWireSize(b *testing.B) {
|
||||
skipCIBenchmark(b)
|
||||
|
||||
for _, scale := range wireBenchScales {
|
||||
account, validatedPeers := scalableTestAccount(scale.peers, scale.groups)
|
||||
populateAccountSeqIDs(account)
|
||||
|
||||
ctx := context.Background()
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
|
||||
peerID := "peer-0"
|
||||
peer := account.Peers[peerID]
|
||||
|
||||
networkMap := account.GetPeerNetworkMapFromComponents(ctx, peerID, nbdns.CustomZone{}, nil, validatedPeers, resourcePolicies, routers, nil, groupIDToUserIDs)
|
||||
components := account.GetPeerNetworkMapComponents(ctx, peerID, nbdns.CustomZone{}, nil, validatedPeers, resourcePolicies, routers, groupIDToUserIDs)
|
||||
|
||||
dnsCache := &cache.DNSConfigCache{}
|
||||
settings := &types.Settings{}
|
||||
|
||||
legacyResp := mgmtgrpc.ToSyncResponse(ctx, nil, nil, nil, peer, nil, nil, networkMap, "netbird.cloud", nil, dnsCache, settings, nil, nil, 0)
|
||||
legacyBytes, _ := goproto.Marshal(legacyResp.NetworkMap)
|
||||
|
||||
env := mgmtgrpc.EncodeNetworkMapEnvelope(mgmtgrpc.ComponentsEnvelopeInput{
|
||||
Components: components,
|
||||
PeerConfig: legacyResp.NetworkMap.PeerConfig,
|
||||
DNSDomain: "netbird.cloud",
|
||||
})
|
||||
envBytes, _ := goproto.Marshal(env)
|
||||
|
||||
b.Run(fmt.Sprintf("size/%s", scale.name), func(b *testing.B) {
|
||||
b.ReportMetric(float64(len(legacyBytes)), "legacy_bytes")
|
||||
b.ReportMetric(float64(len(envBytes)), "components_bytes")
|
||||
ratio := float64(len(envBytes)) / float64(len(legacyBytes))
|
||||
b.ReportMetric(ratio, "components/legacy")
|
||||
for range b.N {
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,13 @@ type Policy struct {
|
||||
SourcePostureChecks []string `gorm:"serializer:json"`
|
||||
}
|
||||
|
||||
// HasSeqID reports whether the policy has been persisted long enough to have
|
||||
// a per-account sequence id allocated. Wire encoders that key off
|
||||
// AccountSeqID must skip policies that return false here.
|
||||
func (p *Policy) HasSeqID() bool {
|
||||
return p != nil && p.AccountSeqID != 0
|
||||
}
|
||||
|
||||
// Copy returns a copy of the policy.
|
||||
func (p *Policy) Copy() *Policy {
|
||||
c := &Policy{
|
||||
|
||||
Reference in New Issue
Block a user