mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-29 10:09:07 +02:00
Introduce network serial caching in sync fast path, optimize DB reads, and add granular cache invalidation
This commit is contained in:
@@ -30,6 +30,7 @@ import (
|
||||
"github.com/netbirdio/netbird/formatter/hook"
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
||||
"github.com/netbirdio/netbird/management/internals/shared/fastpathcache"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
||||
@@ -111,6 +112,11 @@ type DefaultAccountManager struct {
|
||||
permissionsManager permissions.Manager
|
||||
|
||||
disableDefaultPolicy bool
|
||||
|
||||
// sharedCacheStore is retained so mutation paths can invalidate the
|
||||
// Sync fast-path caches (ExtraSettings, peer-groups) without a circular
|
||||
// dependency on the gRPC server package that owns the read-side wrappers.
|
||||
sharedCacheStore cacheStore.StoreInterface
|
||||
}
|
||||
|
||||
var _ account.Manager = (*DefaultAccountManager)(nil)
|
||||
@@ -250,6 +256,7 @@ func BuildManager(
|
||||
|
||||
am.externalCacheManager = nbcache.NewUserDataCache(sharedCacheStore)
|
||||
am.cacheManager = nbcache.NewAccountUserDataCache(am.loadAccount, sharedCacheStore)
|
||||
am.sharedCacheStore = sharedCacheStore
|
||||
|
||||
if !isNil(am.idpManager) && !IsEmbeddedIdp(am.idpManager) {
|
||||
go func() {
|
||||
@@ -368,6 +375,9 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if extraSettingsChanged {
|
||||
fastpathcache.InvalidateExtraSettings(ctx, am.sharedCacheStore, accountID)
|
||||
}
|
||||
|
||||
am.handleRoutingPeerDNSResolutionSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||
am.handleLazyConnectionSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||
|
||||
@@ -2658,6 +2658,30 @@ func (s *SqlStore) GetAccountNetwork(ctx context.Context, lockStrength LockingSt
|
||||
return accountNetwork.Network, nil
|
||||
}
|
||||
|
||||
// GetAccountNetworkSerial returns only the network.serial column for an
|
||||
// account, avoiding the overhead of materialising the full Network struct
|
||||
// (which carries a JSON-serialised CIDR and other columns). Used by the Sync
|
||||
// fast path to check whether the peer's cached serial still matches without
|
||||
// paying the full-row read cost on contended DBs.
|
||||
func (s *SqlStore) GetAccountNetworkSerial(ctx context.Context, lockStrength LockingStrength, accountID string) (uint64, error) {
|
||||
tx := s.db
|
||||
if lockStrength != LockingStrengthNone {
|
||||
tx = tx.Clauses(clause.Locking{Strength: string(lockStrength)})
|
||||
}
|
||||
|
||||
var serial uint64
|
||||
if err := tx.Model(&types.Account{}).
|
||||
Select("network_serial").
|
||||
Where(idQueryCondition, accountID).
|
||||
Take(&serial).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return 0, status.NewAccountNotFoundError(accountID)
|
||||
}
|
||||
return 0, status.Errorf(status.Internal, "issue getting network serial from store: %s", err)
|
||||
}
|
||||
return serial, nil
|
||||
}
|
||||
|
||||
func (s *SqlStore) GetPeerByPeerPubKey(ctx context.Context, lockStrength LockingStrength, peerKey string) (*nbpeer.Peer, error) {
|
||||
tx := s.db
|
||||
if lockStrength != LockingStrengthNone {
|
||||
|
||||
@@ -188,6 +188,10 @@ type Store interface {
|
||||
GetTakenIPs(ctx context.Context, lockStrength LockingStrength, accountId string) ([]net.IP, error)
|
||||
IncrementNetworkSerial(ctx context.Context, accountId string) error
|
||||
GetAccountNetwork(ctx context.Context, lockStrength LockingStrength, accountId string) (*types.Network, error)
|
||||
// GetAccountNetworkSerial returns only the network.serial column; used by
|
||||
// the Sync fast path to skip the full-row GetAccountNetwork read when all
|
||||
// we need is the serial.
|
||||
GetAccountNetworkSerial(ctx context.Context, lockStrength LockingStrength, accountId string) (uint64, error)
|
||||
|
||||
GetInstallationID() string
|
||||
SaveInstallationID(ctx context.Context, ID string) error
|
||||
|
||||
@@ -1032,6 +1032,21 @@ func (mr *MockStoreMockRecorder) GetAccountNetwork(ctx, lockStrength, accountId
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountNetwork", reflect.TypeOf((*MockStore)(nil).GetAccountNetwork), ctx, lockStrength, accountId)
|
||||
}
|
||||
|
||||
// GetAccountNetworkSerial mocks base method.
|
||||
func (m *MockStore) GetAccountNetworkSerial(ctx context.Context, lockStrength LockingStrength, accountId string) (uint64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetAccountNetworkSerial", ctx, lockStrength, accountId)
|
||||
ret0, _ := ret[0].(uint64)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetAccountNetworkSerial indicates an expected call of GetAccountNetworkSerial.
|
||||
func (mr *MockStoreMockRecorder) GetAccountNetworkSerial(ctx, lockStrength, accountId interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAccountNetworkSerial", reflect.TypeOf((*MockStore)(nil).GetAccountNetworkSerial), ctx, lockStrength, accountId)
|
||||
}
|
||||
|
||||
// GetAccountNetworks mocks base method.
|
||||
func (m *MockStore) GetAccountNetworks(ctx context.Context, lockStrength LockingStrength, accountID string) ([]*types1.Network, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -16,6 +16,7 @@ type GRPCMetrics struct {
|
||||
meter metric.Meter
|
||||
syncRequestsCounter metric.Int64Counter
|
||||
syncRequestsBlockedCounter metric.Int64Counter
|
||||
syncPathCounter metric.Int64Counter
|
||||
loginRequestsCounter metric.Int64Counter
|
||||
loginRequestsBlockedCounter metric.Int64Counter
|
||||
loginRequestHighLatencyCounter metric.Int64Counter
|
||||
@@ -51,6 +52,14 @@ func NewGRPCMetrics(ctx context.Context, meter metric.Meter) (*GRPCMetrics, erro
|
||||
return nil, err
|
||||
}
|
||||
|
||||
syncPathCounter, err := meter.Int64Counter("management.grpc.sync.path.counter",
|
||||
metric.WithUnit("1"),
|
||||
metric.WithDescription("Number of sync requests by the path taken (fast vs slow). Slow-path rows carry a reason label for fast-path misses."),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loginRequestsCounter, err := meter.Int64Counter("management.grpc.login.request.counter",
|
||||
metric.WithUnit("1"),
|
||||
metric.WithDescription("Number of login gRPC requests from the peers to authenticate and receive initial configuration and relay credentials"),
|
||||
@@ -142,6 +151,7 @@ func NewGRPCMetrics(ctx context.Context, meter metric.Meter) (*GRPCMetrics, erro
|
||||
meter: meter,
|
||||
syncRequestsCounter: syncRequestsCounter,
|
||||
syncRequestsBlockedCounter: syncRequestsBlockedCounter,
|
||||
syncPathCounter: syncPathCounter,
|
||||
loginRequestsCounter: loginRequestsCounter,
|
||||
loginRequestsBlockedCounter: loginRequestsBlockedCounter,
|
||||
loginRequestHighLatencyCounter: loginRequestHighLatencyCounter,
|
||||
@@ -173,6 +183,26 @@ func (grpcMetrics *GRPCMetrics) CountSyncRequestBlocked() {
|
||||
grpcMetrics.syncRequestsBlockedCounter.Add(grpcMetrics.ctx, 1)
|
||||
}
|
||||
|
||||
// CountFastPathSync increments the sync-path counter for a Sync that took the
|
||||
// fast path. Used together with CountSlowPathSync to graph the fast-path hit
|
||||
// rate and, via the reason label on the slow-path counts, see where the
|
||||
// misses go (android / cache_miss / serial_mismatch / meta_mismatch / ...).
|
||||
func (grpcMetrics *GRPCMetrics) CountFastPathSync() {
|
||||
grpcMetrics.syncPathCounter.Add(grpcMetrics.ctx, 1, metric.WithAttributes(attribute.String("path", "fast")))
|
||||
}
|
||||
|
||||
// CountSlowPathSync increments the sync-path counter for a Sync that fell
|
||||
// through to the slow path. reason is a short tag describing why the fast
|
||||
// path was skipped; pass "" if the reason is unknown or the Sync never had
|
||||
// a chance to attempt the fast path.
|
||||
func (grpcMetrics *GRPCMetrics) CountSlowPathSync(reason string) {
|
||||
attrs := []attribute.KeyValue{attribute.String("path", "slow")}
|
||||
if reason != "" {
|
||||
attrs = append(attrs, attribute.String("reason", reason))
|
||||
}
|
||||
grpcMetrics.syncPathCounter.Add(grpcMetrics.ctx, 1, metric.WithAttributes(attrs...))
|
||||
}
|
||||
|
||||
// CountGetKeyRequest counts the number of gRPC get server key requests coming to the gRPC API
|
||||
func (grpcMetrics *GRPCMetrics) CountGetKeyRequest() {
|
||||
grpcMetrics.getKeyRequestsCounter.Add(grpcMetrics.ctx, 1)
|
||||
|
||||
Reference in New Issue
Block a user