1
0
mirror of https://github.com/redis/go-redis.git synced 2025-12-02 06:22:31 +03:00

try to cache time if for non-critical calculation

This commit is contained in:
Nedyalko Dyakov
2025-10-27 07:30:09 +02:00
parent bec09a52b6
commit 55c502dde4
2 changed files with 46 additions and 6 deletions

View File

@@ -18,6 +18,35 @@ import (
var noDeadline = time.Time{}
// Global time cache updated every 50ms by background goroutine.
// This avoids expensive time.Now() syscalls in hot paths like getEffectiveReadTimeout.
// Max staleness: 50ms, which is acceptable for timeout deadline checks (timeouts are typically 3-30 seconds).
var globalTimeCache struct {
nowNs atomic.Int64
}
func init() {
// Initialize immediately
globalTimeCache.nowNs.Store(time.Now().UnixNano())
// Start background updater
go func() {
ticker := time.NewTicker(50 * time.Millisecond)
defer ticker.Stop()
for range ticker.C {
globalTimeCache.nowNs.Store(time.Now().UnixNano())
}
}()
}
// getCachedTimeNs returns the current time in nanoseconds from the global cache.
// This is updated every 50ms by a background goroutine, avoiding expensive syscalls.
// Max staleness: 50ms.
func getCachedTimeNs() int64 {
return globalTimeCache.nowNs.Load()
}
// Global atomic counter for connection IDs
var connIDCounter uint64
@@ -458,7 +487,8 @@ func (cn *Conn) getEffectiveReadTimeout(normalTimeout time.Duration) time.Durati
return time.Duration(readTimeoutNs)
}
nowNs := time.Now().UnixNano()
// Use cached time to avoid expensive syscall (max 50ms staleness is acceptable for timeout checks)
nowNs := getCachedTimeNs()
// Check if deadline has passed
if nowNs < deadlineNs {
// Deadline is in the future, use relaxed timeout
@@ -491,7 +521,8 @@ func (cn *Conn) getEffectiveWriteTimeout(normalTimeout time.Duration) time.Durat
return time.Duration(writeTimeoutNs)
}
nowNs := time.Now().UnixNano()
// Use cached time to avoid expensive syscall (max 50ms staleness is acceptable for timeout checks)
nowNs := getCachedTimeNs()
// Check if deadline has passed
if nowNs < deadlineNs {
// Deadline is in the future, use relaxed timeout
@@ -843,8 +874,10 @@ func (cn *Conn) MaybeHasData() bool {
// deadline computes the effective deadline time based on context and timeout.
// It updates the usedAt timestamp to now.
// Uses cached time to avoid expensive syscall (max 50ms staleness is acceptable for deadline calculation).
func (cn *Conn) deadline(ctx context.Context, timeout time.Duration) time.Time {
tm := time.Now()
// Use cached time for deadline calculation (called 2x per command: read + write)
tm := time.Unix(0, getCachedTimeNs())
cn.SetUsedAt(tm)
if timeout > 0 {

View File

@@ -27,6 +27,12 @@ var (
// ErrConnUnusableTimeout is returned when a connection is not usable and we timed out trying to mark it as unusable.
ErrConnUnusableTimeout = errors.New("redis: timed out trying to mark connection as unusable")
// errHookRequestedRemoval is returned when a hook requests connection removal.
errHookRequestedRemoval = errors.New("hook requested removal")
// errConnNotPooled is returned when trying to return a non-pooled connection to the pool.
errConnNotPooled = errors.New("connection not pooled")
// popAttempts is the maximum number of attempts to find a usable connection
// when popping from the idle connection pool. This handles cases where connections
// are temporarily marked as unusable (e.g., during maintenanceNotifications upgrades or network issues).
@@ -446,7 +452,8 @@ func (p *ConnPool) getConn(ctx context.Context) (*Conn, error) {
return nil, err
}
now := time.Now()
// Use cached time for health checks (max 50ms staleness is acceptable)
now := time.Unix(0, getCachedTimeNs())
attempts := 0
// Lock-free atomic read - no mutex overhead!
@@ -661,12 +668,12 @@ func (p *ConnPool) putConn(ctx context.Context, cn *Conn, freeTurn bool) {
// Combine all removal checks into one - reduces branches
if shouldRemove || !shouldPool {
p.removeConnInternal(ctx, cn, errors.New("hook requested removal"), freeTurn)
p.removeConnInternal(ctx, cn, errHookRequestedRemoval, freeTurn)
return
}
if !cn.pooled {
p.removeConnInternal(ctx, cn, errors.New("connection not pooled"), freeTurn)
p.removeConnInternal(ctx, cn, errConnNotPooled, freeTurn)
return
}