# Scaling Nextdoor's Datastores: Part 2

DevFeed: [Scaling Nextdoor's Datastores: Part 2](<https://devfeed.tech/articles/scaling-nextdoor-s-datastores-part-2-20341.md>)

Original publisher: [Read original article](<https://engblog.nextdoor.com/scaling-nextdoors-datastores-part-2-513922e4b4b1?source=rss----5e54f11cdfdf---4>)

Author: Tushar Singla

Published: 2025-03-19T15:08:32Z

Content type: article

Language: en

Sources: [Nextdoor](<https://devfeed.tech/sources/nextdoor.md>)

Topics: [Database](<https://devfeed.tech/topics/database.md>), [Replication](<https://devfeed.tech/topics/replication.md>), [Django](<https://devfeed.tech/topics/django.md>), [Object-relational mapping](<https://devfeed.tech/topics/orm.md>), [consistency](<https://devfeed.tech/topics/consistency.md>), [race-condition](<https://devfeed.tech/topics/race-condition.md>), [Transactions](<https://devfeed.tech/topics/transactions.md>)

Tags: [consistency](<https://devfeed.tech/tags/consistency.md>), [database](<https://devfeed.tech/tags/database.md>), [database-scalability](<https://devfeed.tech/tags/database-scalability.md>), [django](<https://devfeed.tech/tags/django.md>), [orm](<https://devfeed.tech/tags/orm.md>), [race-condition](<https://devfeed.tech/tags/race-condition.md>), [rdbms](<https://devfeed.tech/tags/rdbms.md>), [read-replica](<https://devfeed.tech/tags/read-replica.md>), [replication](<https://devfeed.tech/tags/replication.md>), [transactions](<https://devfeed.tech/tags/transactions.md>)

## AI overview

The second installment of Nextdoor's datastore-scaling series examines the consistency problems caused by database read replicas. It describes how routing decisions became obscured by Django ORM abstractions, leading to read-after-write races, and explains how transactions were used as a workaround with negative effects on database load.

## Source excerpt

In the second installment of Nextdoor's "Scaling Nextdoor's Datastores" blog series, the Core-Services team discusses challenges faced after implementing database read replicas. Adding read replicas to an existing database is a very common pattern as applications or products evolve to handle increased demand. Typically, the implementation details are hand waved and it's assumed that this strategy will work. However, that is rarely the case, and we'll dive into some more of the intricacies around the implementation. Initial Attempt When replicas were first introduced in the Nextdoor stack, we gave the product engineers latitude to choose when they wanted to have their query routed to a read replica or to the primary. This was done by leveraging the existing routing mechanism in our ORM, Django. This seemed like the right idea at the time because the product engineers had the most context around consistency requirements within their changes and load characteristics of their product feature. Therefore, they would have the best ability to judge which node to send their query to. However, as our business logic evolved and became more feature-rich, product engineers began to add abstraction layers to help abstract complex operations away from business logic. In this design evolution there is a high frequency read, followed by a low frequency conditional write, followed by a read. The read performed after the write should be routed to the primary, but that may get buried in abstractions and this requirement regressed. The explicit routing decisions engineers made became buried and subsequently created a serious problem for users of these abstractions. If one abstraction method was performing a write and another a read, they could not safely be used together due to read-after-write consistency issues. Due to replication lag between the primary and replica databases, a race condition arises when the application attempts to read data from a replica after performing a write. W