fix(db): reduce per-request connection bursts and add dbtrace

* Throttle async session-activity writes to once per token per minute
* Add singleflight to session lookups, keystore validation and
  column/row security loads to stop cold-cache stampedes
* Preload security rules in BeforeHandle (restheadspec, resolvespec) so
  they no longer need a second connection while the read tx is open
* Add pkg/dbtrace: opt-in per-request DB call counting and pool logging
  (db_trace.* config, RESOLVESPEC_DB_TRACE_* env), wired into testserver
* Add tests for load dedup, activity throttle and dbtrace
This commit is contained in:
2026-09-30 21:44:28 +02:00
parent 62cc14c02a
commit 3e327d0c78
20 changed files with 544 additions and 87 deletions
+23
View File
@@ -12,6 +12,8 @@ import (
"time"
"github.com/bitechdev/ResolveSpec/pkg/cache"
"github.com/bitechdev/ResolveSpec/pkg/dbtrace"
"golang.org/x/sync/singleflight"
)
// DatabaseKeyStoreOptions configures DatabaseKeyStore.
@@ -51,6 +53,9 @@ type DatabaseKeyStore struct {
capability *dbCapability
cache *cache.Cache
cacheTTL time.Duration
// validateLoads collapses concurrent key lookups for the same key
validateLoads singleflight.Group
}
// NewDatabaseKeyStore creates a DatabaseKeyStore with optional configuration.
@@ -237,6 +242,24 @@ func (ks *DatabaseKeyStore) ValidateKey(ctx context.Context, rawKey string, keyT
}
}
// Concurrent misses for the same key share one database lookup.
v, err, _ := ks.validateLoads.Do(cacheKey+"|"+string(keyType), func() (any, error) {
return ks.validateKeyLoad(ctx, hash, cacheKey, keyType)
})
if err != nil {
return nil, err
}
key, _ := v.(*UserKey)
if key == nil {
return nil, errors.New("invalid or expired key")
}
cp := *key
return &cp, nil
}
// validateKeyLoad validates against the database and fills the cache.
func (ks *DatabaseKeyStore) validateKeyLoad(ctx context.Context, hash, cacheKey string, keyType KeyType) (*UserKey, error) {
dbtrace.Raw(ctx, "keystore.validate")
if !ks.capability.ShouldUseProcedure(ctx, ks.queryMode, ks.getDB(), ks.sqlNames.ValidateKey) {
key, err := ks.validateKeyDirect(ctx, hash, keyType)
if err != nil {