Skip to main content

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:

  1. Explicit Routing: Using .using(alias) on a QuerySet.
  2. Dynamic Router: Using a configured BaseRouter.
  3. Model Metadata: Using the database option in Model.Meta.
  4. 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.