snapshot of https://github.com/makeplane/plane.git @ d868349a55de79979eb8b98701bf1702c67ad634 (2026-09-15)
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
Some checks failed
CodeQL / Analyze (javascript) (push) Failing after 16s
CodeQL / Analyze (python) (push) Failing after 3s
i18n sync check / check:sync (push) Failing after 3s
React Doctor / react-doctor (push) Failing after 16s
Branch Build CE / Build-Push Admin Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Admin Manifest (push) Has been cancelled
Branch Build CE / Build-Push Web Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Web Manifest (push) Has been cancelled
Branch Build CE / Build-Push Space Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Proxy Manifest (push) Has been cancelled
Branch Build CE / Merge Space Manifest (push) Has been cancelled
Branch Build CE / Prepare AIO Assets (push) Has been cancelled
Branch Build CE / Build Setup (push) Has been cancelled
Branch Build CE / Build-Push Live Collaboration Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge Live Collaboration Manifest (push) Has been cancelled
Branch Build CE / Build-Push API Server Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge API Server Manifest (push) Has been cancelled
Branch Build CE / Build-Push Proxy Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Build-Push AIO Docker Image (${{ matrix.arch }}) (push) Has been cancelled
Branch Build CE / Merge AIO Manifest (push) Has been cancelled
Branch Build CE / Upload Build Assets (push) Has been cancelled
Branch Build CE / Build Release (push) Has been cancelled
This commit is contained in:
25
apps/api/plane/utils/core/__init__.py
Normal file
25
apps/api/plane/utils/core/__init__.py
Normal file
@@ -0,0 +1,25 @@
|
||||
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# See the LICENSE file for details.
|
||||
|
||||
"""
|
||||
Core utilities for Plane database routing and request scoping.
|
||||
This package contains essential components for managing read replica routing
|
||||
and request-scoped context in the Plane application.
|
||||
"""
|
||||
|
||||
from .dbrouters import ReadReplicaRouter
|
||||
from .mixins import ReadReplicaControlMixin
|
||||
from .request_scope import (
|
||||
set_use_read_replica,
|
||||
should_use_read_replica,
|
||||
clear_read_replica_context,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ReadReplicaRouter",
|
||||
"ReadReplicaControlMixin",
|
||||
"set_use_read_replica",
|
||||
"should_use_read_replica",
|
||||
"clear_read_replica_context",
|
||||
]
|
||||
75
apps/api/plane/utils/core/dbrouters.py
Normal file
75
apps/api/plane/utils/core/dbrouters.py
Normal file
@@ -0,0 +1,75 @@
|
||||
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# See the LICENSE file for details.
|
||||
|
||||
"""
|
||||
Database router for read replica selection.
|
||||
This router determines which database to use for read/write operations
|
||||
based on the request context set by the ReadReplicaRoutingMiddleware.
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Type
|
||||
|
||||
from django.db import models
|
||||
|
||||
from .request_scope import should_use_read_replica
|
||||
|
||||
logger = logging.getLogger("plane.db")
|
||||
|
||||
|
||||
class ReadReplicaRouter:
|
||||
"""
|
||||
Database router that directs read operations to replica when appropriate.
|
||||
This router works in conjunction with ReadReplicaRoutingMiddleware to:
|
||||
- Route read operations to replica database when request context allows
|
||||
- Always route write operations to primary database
|
||||
- Ensure migrations only run on primary database
|
||||
"""
|
||||
|
||||
def db_for_read(self, model: Type[models.Model], **hints) -> str:
|
||||
"""
|
||||
Determine which database to use for read operations.
|
||||
Args:
|
||||
model: The Django model class being queried
|
||||
**hints: Additional routing hints
|
||||
Returns:
|
||||
str: Database alias ('replica' or 'default')
|
||||
"""
|
||||
if should_use_read_replica():
|
||||
logger.debug(f"Routing read for {model._meta.label} to replica database")
|
||||
return "replica"
|
||||
else:
|
||||
logger.debug(f"Routing read for {model._meta.label} to primary database")
|
||||
return "default"
|
||||
|
||||
def db_for_write(self, model: Type[models.Model], **hints) -> str:
|
||||
"""
|
||||
Determine which database to use for write operations.
|
||||
All write operations always go to the primary database to ensure
|
||||
data consistency and avoid replication lag issues.
|
||||
Args:
|
||||
model: The Django model class being written to
|
||||
**hints: Additional routing hints
|
||||
Returns:
|
||||
str: Always returns 'default' (primary database)
|
||||
"""
|
||||
logger.debug(f"Routing write for {model._meta.label} to primary database")
|
||||
return "default"
|
||||
|
||||
def allow_migrate(self, db: str, app_label: str, model_name: str = None, **hints) -> bool:
|
||||
"""
|
||||
Ensure migrations only run on the primary database.
|
||||
Args:
|
||||
db: Database alias
|
||||
app_label: Application label
|
||||
model_name: Model name (optional)
|
||||
**hints: Additional routing hints
|
||||
Returns:
|
||||
bool: True if migration is allowed on this database
|
||||
"""
|
||||
# Only allow migrations on the primary database
|
||||
allowed = db == "default"
|
||||
if not allowed:
|
||||
logger.debug(f"Blocking migration for {app_label} on {db} database")
|
||||
return allowed
|
||||
15
apps/api/plane/utils/core/mixins/__init__.py
Normal file
15
apps/api/plane/utils/core/mixins/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# See the LICENSE file for details.
|
||||
|
||||
"""
|
||||
Core mixins for read replica functionality.
|
||||
This package provides mixins for different aspects of read replica management
|
||||
in Django and Django REST Framework applications.
|
||||
"""
|
||||
|
||||
from .view import ReadReplicaControlMixin
|
||||
|
||||
__all__ = [
|
||||
"ReadReplicaControlMixin",
|
||||
]
|
||||
24
apps/api/plane/utils/core/mixins/view.py
Normal file
24
apps/api/plane/utils/core/mixins/view.py
Normal file
@@ -0,0 +1,24 @@
|
||||
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# See the LICENSE file for details.
|
||||
|
||||
"""
|
||||
Mixins for Django REST Framework views.
|
||||
"""
|
||||
|
||||
|
||||
class ReadReplicaControlMixin:
|
||||
"""
|
||||
Mixin to control read replica usage in DRF views.
|
||||
Set use_read_replica = True/False to route read operations to
|
||||
replica/primary database. Works with ReadReplicaRoutingMiddleware.
|
||||
Usage:
|
||||
class MyViewSet(ReadReplicaControlMixin, ModelViewSet):
|
||||
use_read_replica = True # Use replica for GET requests
|
||||
Note:
|
||||
- Only affects GET, HEAD, OPTIONS requests
|
||||
- Write operations always use primary database
|
||||
- Defaults to True for safe replica usage
|
||||
"""
|
||||
|
||||
use_read_replica: bool = True
|
||||
76
apps/api/plane/utils/core/request_scope.py
Normal file
76
apps/api/plane/utils/core/request_scope.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) 2023-present Plane Software, Inc. and contributors
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
# See the LICENSE file for details.
|
||||
|
||||
"""
|
||||
Database routing utilities for read replica selection.
|
||||
This module provides request-scoped context management for database routing,
|
||||
specifically for determining when to use read replicas vs primary database.
|
||||
Used in conjunction with middleware and DRF views that set use_read_replica=True.
|
||||
The context is maintained per request to ensure proper isolation between
|
||||
concurrent requests in async environments.
|
||||
"""
|
||||
|
||||
from asgiref.local import Local
|
||||
|
||||
__all__ = [
|
||||
"set_use_read_replica",
|
||||
"should_use_read_replica",
|
||||
"clear_read_replica_context",
|
||||
]
|
||||
|
||||
# Request-scoped context storage for database routing preferences
|
||||
# Uses asgiref.local.Local which provides ContextVar under the hood
|
||||
# This ensures proper context isolation per request in async environments
|
||||
_db_routing_context = Local()
|
||||
|
||||
|
||||
def set_use_read_replica(use_replica: bool) -> None:
|
||||
"""
|
||||
Mark the current request context to use read replica database.
|
||||
This function sets a request-scoped flag that determines database routing.
|
||||
The context is isolated per request to ensure thread safety in async environments.
|
||||
This function is typically called from:
|
||||
- Middleware that detects read-only operations
|
||||
- DRF views with use_read_replica=True attribute
|
||||
- API endpoints that only perform read operations
|
||||
Args:
|
||||
use_replica (bool): True to route database queries to read replica,
|
||||
False to use primary database
|
||||
Note:
|
||||
The context is automatically isolated per request and should be
|
||||
cleared at the end of each request using clear_read_replica_context().
|
||||
"""
|
||||
_db_routing_context.use_read_replica = bool(use_replica)
|
||||
|
||||
|
||||
def should_use_read_replica() -> bool:
|
||||
"""
|
||||
Check if the current request should use read replica database.
|
||||
This function reads the request-scoped context to determine database routing.
|
||||
It's called by the database router to decide which connection to use.
|
||||
Returns:
|
||||
bool: True if queries should be routed to read replica,
|
||||
False if they should use primary database (default)
|
||||
Note:
|
||||
Returns False by default if no context is set for the current request.
|
||||
The context is automatically isolated per request.
|
||||
"""
|
||||
return getattr(_db_routing_context, "use_read_replica", False)
|
||||
|
||||
|
||||
def clear_read_replica_context() -> None:
|
||||
"""
|
||||
Clear the read replica context for the current request.
|
||||
This function should be called at the end of each request to ensure
|
||||
that context doesn't leak between requests. Typically called from
|
||||
middleware during request cleanup.
|
||||
This is important for:
|
||||
- Preventing context leakage between requests
|
||||
- Ensuring clean state for each new request
|
||||
- Proper memory management in long-running processes
|
||||
"""
|
||||
try:
|
||||
delattr(_db_routing_context, "use_read_replica")
|
||||
except AttributeError:
|
||||
pass
|
||||
Reference in New Issue
Block a user