Multi-Database Support
Ryx supports routing queries across multiple databases, allowing you to separate read and write workloads, split data across different servers, or use a dedicated database for specific models.
Also see: PostgreSQL Multi-Schema for schema-per-tenant isolation within a single database.
Configurationโ
To enable multi-database support, provide a dictionary of URLs to ryx_core.setup instead of a single string. Each key in the dictionary serves as an alias for that database pool.
from ryx import ryx_core
# Configure multiple databases
urls = {
"default": "postgresql://user:pass@localhost/main_db",
"users": "postgresql://user:pass@localhost/user_db",
"logs": "sqlite::memory:",
}
await ryx_core.setup(urls)
Routing Strategiesโ
Ryx resolves which database to use for a query in the following order of priority:
- Explicit Routing: Using
.using(alias)on a QuerySet. - Dynamic Router: Using a configured
BaseRouter. - Model Metadata: Using the
databaseoption inModel.Meta. - Default: Falling back to the
'default'alias.
1. Explicit Routingโ
You can force a query to run on a specific database using the .using() method. This is useful for one-off queries or manual routing.
# Read from the 'users' database
users = await User.objects.using("users").all()
# Write to the 'logs' database
await Log.objects.using("logs").create(message="System boot")
2. Model-Level Routingโ
You can assign a model to a specific database by default using the database option in its Meta class.
class Log(Model):
message = CharField()
class Meta:
database = "logs"
Any query on Log will now use the logs database unless overridden by .using().
3. Dynamic Routing (The Router)โ
For more complex logic (e.g., routing based on the environment, user, or model type), you can implement a custom router by inheriting from BaseRouter.
from ryx.router import BaseRouter, set_router
class MyProjectRouter(BaseRouter):
def db_for_read(self, model, **hints):
if model.__name__ == "User":
return "users"
return None # Fallback to default
def db_for_write(self, model, **hints):
if model.__name__ == "User":
return "users"
return None
# Activate the router globally
set_router(MyProjectRouter())
Utility Functionsโ
# List all configured database aliases
aliases = ryx.list_aliases()
# โ ["default", "users", "logs"]
# Check connection status
is_up = ryx.is_connected("default") # โ bool
# Get pool stats
stats = ryx.pool_stats("default")
# โ {"size": 5, "idle": 3}
# Detect backend type
backend = ryx.get_backend("default")
# โ "postgres" | "mysql" | "sqlite"
Multi-Database Transactionsโ
Transactions in Ryx are tied to a specific database connection. To start a transaction on a non-default database, pass the alias to the transaction() context manager.
import ryx
async with Ryx.transaction(alias="users"):
await User.objects.create(name="Alice")
await User.objects.create(name="Bob")
# If an exception occurs, only changes to 'users' DB are rolled back.
Nesting and Multiple Databasesโ
- If you start a transaction on a database that already has an active transaction on the current task, Ryx creates a SAVEPOINT.
- If you start a transaction on a different database while another is active, Ryx starts a new independent transaction for that database.