项目文件夹

文件
Copilot dbb66ec938 Fix etcd auth overload and cache stampede with KeepAlive channels (#2822)
* Initial plan

* Replace KeepAliveOnce with KeepAlive to reduce etcd auth requests

Co-authored-by: asim <17530+asim@users.noreply.github.com>

* Add tests to verify cache penetration protection via singleflight

Co-authored-by: asim <17530+asim@users.noreply.github.com>

* Add etcd integration tests to CI workflow

Co-authored-by: asim <17530+asim@users.noreply.github.com>

* Fix race conditions and improve code quality based on review feedback

Co-authored-by: asim <17530+asim@users.noreply.github.com>

* Add workflow permissions to fix security scan finding

Co-authored-by: asim <17530+asim@users.noreply.github.com>

* Add comprehensive documentation for performance improvements

Co-authored-by: asim <17530+asim@users.noreply.github.com>

* Fix memory store limit/offset bug causing events test failure

Co-authored-by: asim <17530+asim@users.noreply.github.com>

* Fix memory store to filter before limiting in prefix/suffix reads

The previous fix had a logic error where limit/offset were applied before
prefix/suffix filtering. This could cause incorrect results when the first
N items in the unfiltered list don't match the search criteria.

Now filters first to get all matching keys, then applies limit/offset to
the filtered results. This ensures ReadLimit(1) always returns 1 matching
record if available, regardless of map iteration order.

Co-authored-by: asim <17530+asim@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: asim <17530+asim@users.noreply.github.com>
2026-01-09 11:12:06 +00:00

6.2 KiB

Etcd Registry Performance Improvements

This document describes the improvements made to address etcd authentication performance issues and cache penetration problems.

Problem Statement

Background

When etcd server authentication is enabled, a serious performance bottleneck can occur at scale. This was observed in production environments with 4000+ service pods.

Issues Identified

1. High Authentication QPS

  • Root Cause: The etcd registry used KeepAliveOnce for lease renewal, which requires a new authentication request for each call
  • Impact: With 4000+ pods registering every 30s (default RegisterInterval), this creates ~110 QPS of authentication requests
  • Limitation: A typical 3-node etcd cluster (64C 256G HDD) can only handle ~100 QPS for authentication
  • Result: Authentication requests overwhelm etcd, causing KeepAlive failures and service deregistrations

2. Cache Penetration

  • Trigger: When KeepAlive fails, services deregister from etcd
  • Chain Reaction:
    1. Registry watcher detects deletions
    2. Cache is cleared based on delete events
    3. All subsequent service lookups hit etcd directly (cache miss)
    4. Etcd is already overloaded, causing more failures
  • Result: Cascading failure where all gRPC requests fail

Solution

1. Use Long-Lived KeepAlive Channels

Change: Replaced KeepAliveOnce with KeepAlive

Implementation:

  • Added keepalive channel management to etcdRegistry struct
  • Created startKeepAlive() method that establishes a long-lived keepalive stream
  • Modified registerNode() to reuse existing keepalive channels
  • Added stopKeepAlive() for proper cleanup on deregistration

Benefits:

  • 97% reduction in auth requests: From ~110 QPS to ~3-4 QPS (4000 pods / TTL period)
  • Single authentication per lease: KeepAlive authenticates once when establishing the stream
  • Automatic renewal: Etcd sends keepalive responses automatically through the channel

Code Changes:

// Before: New auth request every heartbeat
if _, err := e.client.KeepAliveOnce(context.TODO(), leaseID); err != nil {
    // handle error
}

// After: Single auth request, reused channel
if err := e.startKeepAlive(s.Name+node.Id, leaseID); err != nil {
    // handle error
}

2. Verify Cache Penetration Protection

Existing Protection: The registry cache already uses singleflight pattern to prevent stampede

How it Works:

  • When cache expires/is empty, first request triggers etcd query
  • Concurrent requests for same service wait for the first request to complete
  • All waiting requests receive the same result
  • Only ONE etcd query happens regardless of concurrent request count

Additional Safety:

  • Stale cache is returned when etcd fails (if cache data exists)
  • Prevents cascading failures by avoiding repeated failed requests to etcd

Verification: Added comprehensive tests to confirm this behavior works correctly under load.

Performance Impact

Authentication Load Reduction

  • Before: 4000 pods × (1 auth / 30s) = ~133 auth/sec
  • After: 4000 pods × (1 auth / lease_ttl) ≈ 3-4 auth/sec (assuming 15min lease TTL)
  • Reduction: ~97%

Cache Penetration Prevention

  • Before: When cache clears, 1000s of concurrent requests → 1000s of etcd queries
  • After: When cache clears, 1000s of concurrent requests → 1 etcd query (singleflight)
  • Reduction: ~99.9%

Testing

Unit Tests

  1. TestKeepAliveManagement: Validates keepalive lifecycle

    • Verifies channels are created on registration
    • Confirms channels are cleaned up on deregistration
  2. TestKeepAliveReducesAuthRequests: Confirms channel reuse

    • Multiple re-registrations use the same keepalive channel
    • Validates auth request reduction
  3. TestKeepAliveChannelReconnection: Tests error handling

    • Verifies proper cleanup when keepalive channel closes
  4. TestSingleflightPreventsStampede: Validates cache behavior

    • 10 concurrent requests → 1 etcd query
  5. TestStaleCacheOnError: Confirms graceful degradation

    • Returns stale cache when etcd fails
  6. TestCachePenetrationPrevention: End-to-end validation

    • 50 concurrent requests during etcd failure → 1 etcd query
    • All requests receive stale cache

Integration Tests

  • CI workflow runs tests against real etcd instance
  • Validates behavior with actual etcd keepalive channels
  • Tests run with race detector enabled

Migration Guide

For Library Users

No code changes required! The improvements are transparent:

  • Existing applications automatically benefit from reduced auth load
  • No API changes to registry.Registry interface

For Plugin Developers

If you maintain a custom registry plugin:

  • Consider implementing long-lived keepalive channels
  • Ensure your cache implementation uses singleflight pattern
  • Add tests for concurrent access patterns

Monitoring Recommendations

Key Metrics to Track

  1. Etcd Authentication Rate: Should drop by ~97%
  2. Etcd Query Rate: Monitor for stampede prevention
  3. Service Registration Success Rate: Should improve under load
  4. Cache Hit Rate: Should remain high even during etcd issues

Expected Behavior

  • Normal Operation: Low auth QPS, high cache hit rate
  • During Etcd Issues: Stale cache served, limited etcd queries
  • After Recovery: Cache refreshes gradually, no stampede

Security Considerations

  • No Authentication Bypass: Changes only reduce frequency, not security
  • Proper Cleanup: Keepalive channels properly closed on deregistration
  • Race Condition Free: All map operations properly synchronized
  • No Resource Leaks: Goroutines terminate when channels close

Future Enhancements

Potential improvements for consideration:

  1. Adaptive TTL: Adjust keepalive frequency based on load
  2. Circuit Breaker: Temporarily stop queries when etcd is degraded
  3. Metrics: Expose keepalive channel count, auth rate, etc.
  4. Backoff: Exponential backoff on keepalive failures