mirror of
https://github.com/quay/quay.git
synced 2026-01-26 06:21:37 +03:00
* db: use read replica for selected queries (PROJQUAY-6397) We add a new param `can_use_read_replica` to the `select` query. This allows us to choose which queries we want to send to the read replica. This is useful in cases where the read replica lags behind the primary and some queries need the latest data
121 lines
4.1 KiB
Python
121 lines
4.1 KiB
Python
import os
|
|
import shutil
|
|
|
|
import pytest
|
|
from peewee import OperationalError
|
|
|
|
from data.database import User, configure, db_disallow_replica_use, read_only_config
|
|
from data.readreplica import ReadOnlyModeException
|
|
from test.fixtures import *
|
|
from test.testconfig import FakeTransaction
|
|
|
|
|
|
@pytest.mark.skipif(bool(os.environ.get("TEST_DATABASE_URI")), reason="Testing requires SQLite")
|
|
def test_readreplica(init_db_path, tmpdir_factory):
|
|
primary_file = str(tmpdir_factory.mktemp("data").join("primary.db"))
|
|
replica_file = str(tmpdir_factory.mktemp("data").join("replica.db"))
|
|
|
|
# Copy the initialized database to two different locations.
|
|
shutil.copy2(init_db_path, primary_file)
|
|
shutil.copy2(init_db_path, replica_file)
|
|
|
|
db_config = {
|
|
"DB_URI": "sqlite:///{0}".format(primary_file),
|
|
"DB_READ_REPLICAS": [
|
|
{"DB_URI": "sqlite:///{0}".format(replica_file)},
|
|
],
|
|
"DB_CONNECTION_ARGS": {
|
|
"threadlocals": True,
|
|
"autorollback": True,
|
|
},
|
|
"DB_TRANSACTION_FACTORY": lambda x: FakeTransaction(),
|
|
"FOR_TESTING": True,
|
|
"DATABASE_SECRET_KEY": "anothercrazykey!",
|
|
}
|
|
|
|
# Initialize the DB with the primary and the replica.
|
|
configure(db_config)
|
|
assert not read_only_config.obj.is_readonly
|
|
assert read_only_config.obj.read_replicas
|
|
|
|
# Ensure we can read the data.
|
|
devtable_user = User.get(username="devtable")
|
|
assert devtable_user.username == "devtable"
|
|
|
|
# Configure with a bad primary. Reading should still work since we're hitting the replica.
|
|
db_config["DB_URI"] = "sqlite:///does/not/exist"
|
|
configure(db_config)
|
|
|
|
assert not read_only_config.obj.is_readonly
|
|
assert read_only_config.obj.read_replicas
|
|
|
|
devtable_user = User.get(username="devtable", can_use_read_replica=True)
|
|
assert devtable_user.username == "devtable"
|
|
|
|
# Force us to hit the master and ensure it doesn't work.
|
|
with db_disallow_replica_use():
|
|
with pytest.raises(OperationalError):
|
|
User.get(username="devtable")
|
|
|
|
# Explicitly disallow replica use and ensure it doesn't work.
|
|
with pytest.raises(OperationalError):
|
|
User.get(username="devtable", can_use_read_replica=False)
|
|
|
|
# Default to hitting the master and ensure it doesn't work.
|
|
with pytest.raises(OperationalError):
|
|
User.get(username="devtable")
|
|
|
|
# Test read replica again.
|
|
devtable_user = User.get(username="devtable", can_use_read_replica=True)
|
|
assert devtable_user.username == "devtable"
|
|
|
|
# Try to change some data. This should fail because the primary is broken.
|
|
with pytest.raises(OperationalError):
|
|
devtable_user.email = "newlychanged"
|
|
devtable_user.save()
|
|
|
|
# Fix the primary and try again.
|
|
db_config["DB_URI"] = "sqlite:///{0}".format(primary_file)
|
|
configure(db_config)
|
|
|
|
assert not read_only_config.obj.is_readonly
|
|
assert read_only_config.obj.read_replicas
|
|
|
|
devtable_user.email = "newlychanged"
|
|
devtable_user.save()
|
|
|
|
# Mark the system as readonly.
|
|
db_config["DB_URI"] = "sqlite:///{0}".format(primary_file)
|
|
db_config["REGISTRY_STATE"] = "readonly"
|
|
configure(db_config)
|
|
|
|
assert read_only_config.obj.is_readonly
|
|
assert read_only_config.obj.read_replicas
|
|
|
|
# Ensure all write operations raise a readonly mode exception.
|
|
with pytest.raises(ReadOnlyModeException):
|
|
devtable_user.email = "newlychanged2"
|
|
devtable_user.save()
|
|
|
|
with pytest.raises(ReadOnlyModeException):
|
|
User.create(username="foo")
|
|
|
|
with pytest.raises(ReadOnlyModeException):
|
|
User.delete().where(User.username == "foo").execute()
|
|
|
|
with pytest.raises(ReadOnlyModeException):
|
|
User.update(username="bar").where(User.username == "foo").execute()
|
|
|
|
# Reset the config on the DB, so we don't mess up other tests.
|
|
configure(
|
|
{
|
|
"DB_URI": "sqlite:///{0}".format(primary_file),
|
|
"DB_CONNECTION_ARGS": {
|
|
"threadlocals": True,
|
|
"autorollback": True,
|
|
},
|
|
"DB_TRANSACTION_FACTORY": lambda x: FakeTransaction(),
|
|
"DATABASE_SECRET_KEY": "anothercrazykey!",
|
|
}
|
|
)
|