This article explores Read-Copy-Update (RCU), a concurrency mechanism that dramatically improves read performance in read-heavy, eventually consistent systems by eliminating lock overhead from the read path. It details RCU's three-phase pattern (read, copy, update) and contrasts it with traditional reader-writer locks, highlighting how RCU mitigates cache coherency issues and contention at scale. The discussion provides insights into applying RCU in real-world distributed system architectures.
Read original on InfoQ ArchitectureRead-Copy-Update (RCU) is a synchronization primitive designed to enhance performance in highly concurrent, read-heavy workloads. Unlike traditional lock-based mechanisms, RCU allows readers to access shared data without acquiring any locks, thus avoiding contention and cache coherency overhead that plague reader-writer locks at scale. This lock-free read access comes at the cost of eventual consistency and increased memory usage.
Traditional reader-writer locks, while allowing concurrent reads, introduce significant overhead in high-concurrency environments. Even shared read-lock acquisition involves atomic operations, leading to cache line invalidation across CPU cores. This "cache bouncing" becomes a major bottleneck as core counts increase. Additionally, writers still require exclusive access, causing all readers to wait, potentially leading to thundering herd problems, priority inversion, and convoying if the lock-holding thread is preempted. RCU fundamentally sidesteps these issues by removing locks from the read path entirely.
When to Apply RCU
RCU is best suited for scenarios with a very high read-to-write ratio (e.g., 10:1 or 100:1) and where a brief period of eventual consistency is acceptable for readers. Examples include system configuration updates (Kubernetes, Envoy), DNS servers, and PostgreSQL's MVCC implementation. It is inappropriate for systems requiring strong consistency or immediate access to the latest data due to the potential for stale reads.
pthread_rwlock_t config_lock;
config_t *global_config;
// Reader with traditional lock
void handle_request() {
pthread_rwlock_rdlock(&config_lock); // Acquire read lock
route_t *route = lookup_route(global_config, request_path);
pthread_rwlock_unlock(&config_lock); // Release read lock
// ... forward request ...
}
// Writer with traditional lock
void update_config(config_t *new_config) {
pthread_rwlock_wrlock(&config_lock); // Acquire write lock
global_config = new_config;
pthread_rwlock_unlock(&config_lock);
}
// RCU Reader (conceptual)
void handle_request_rcu() {
rcu_read_lock(); // Mark RCU critical section
route_t *route = rcu_dereference(global_config);
rcu_read_unlock(); // Exit RCU critical section
// route *must not* be used after this point
}