Best Practice for Using Read Replica in Frappe/ERPNext

Hi everyone,

Iโ€™ve been working with read replicas in Frappe/ERPNext to reduce query load on the primary database. While setting this up, I learned something important from the community and docs that Iโ€™d like to share here for clarity.

:point_right: In Frappe, every request runs inside a transaction. Because of this, we cannot automatically route some queries to the replica โ€” doing so could lead to inconsistent results, especially if the replica lags behind the primary.

:key: The recommended approach is:

  • Identify top-level functions or reports that are read-only (e.g., fetching invoices, generating reports).
  • Add the decorator @frappe.read_only() to those functions.
  • This ensures those queries will run on the replica safely, without affecting write operations.

Example:

@frappe.read_only()
def fetch_data(filters):
    return frappe.db.get_all("Sales Invoice", filters=filters, fields=["name", "grand_total"])

This way, you can offload heavy read queries to the replica where itโ€™s safe, while keeping write-related operations on the primary.

As a reminder, do follow the official guide to correctly configure read replica in Frappe/ERPNext settings:
https://docs.frappe.io/framework/user/en/guides/database-settings/setup-read-from-secondary-db

Hope this helps others who are trying to optimize with replicas! :rocket:

3 Likes