Loading…
Scaling Nextdoor’s Datastores: Part 1
NextdoorSlava Markeyev
Summary
Nextdoor relies on a Django backend connected to monolithic PostgreSQL databases and Redis look-aside caches. Migrating to distributed SQL datastores proved impractical because legacy business logic depends heavily on multi-table joins that could not be rewritten. Intermediate mitigations, including read replicas and data partitioning by severing foreign keys, extended infrastructure runways but left primary databases vulnerable to load bottlenecks. Furthermore, look-aside caching and replica usage introduced data staleness risks, atomicity losses across partitioned databases, and inconsistent cache-population behaviors. To address these limitations, Nextdoor initiated an architecture redesign focused on dynamic query routing to replicas, replica-driven cache hydration, time-bounded eventual consistency, and schema-resilient cache serialization.
Context
Nextdoor's Django backend relies heavily on relational PostgreSQL data models with complex multi-table joins. Transitioning to distributed SQL was infeasible due to widespread legacy queries that a small team could not rewrite without halting product development. Over time, primary databases remained bottlenecks and single points of failure despite using Redis look-aside caches, read replicas, and partitioned databases.
Approach / What changed
Nextdoor evaluated the limits of their existing mitigations—severed foreign keys, look-aside caching, and read replicas—and initiated an architecture redesign. Their goals include dynamically routing read queries to replicas, populating caches directly from read replicas, guaranteeing time-bounded eventual cache consistency, and maintaining cache compatibility across schema modifications.
Takeaways
- Adopting distributed SQL datastores can be blocked when entrenched application codebases rely heavily on multi-table joins across monolithic relational models.
- Partitioning databases by severing foreign-key relationships extends scaling runway but can subtly break transactional atomicity during successive writes across databases.
- Querying the primary database during a cache miss does not guarantee that the value written to the cache reflects the most up-to-date data.
Related reading
Nextdoor ·
Scaling Nextdoor’s Datastores: Part 2
Nextdoor encountered scaling issues after adding read replicas when product engineers were initially tasked with deciding whether to route queries to the primary or replica databases. As business logic grew and gained abstraction layers, engineers struggled to track read-after-write consistency constraints across the call stack. To avoid replication lag race conditions, engineers routinely wrapped logic in database transactions, unintentionally directing all queries to the primary node and eroding read replica benefits over several years. The Core-Services team resolved this by injecting custom tracking logic into their Django ORM layer to monitor table writes during web requests and automate routing. They further optimized the system using a timing-based approach that restored replica read eligibility after the p99.9 replication lag elapsed.
Tushar SinglaNextdoor ·
Scaling Nextdoor’s Datastores: Part 4