-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enable DB replica #1104
Enable DB replica #1104
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,14 +14,24 @@ def create_app(): | |
return Flask(__name__) | ||
|
||
|
||
def configure_app(app, *blueprints, database_interface=None, **mysql_kwargs): | ||
def configure_app( | ||
app, *blueprints, database_interface=None, replica_socket=None, **mysql_kwargs | ||
): | ||
"""Register blueprints. Configure the app's database interface. `mysql_kwargs` are | ||
forwarded. | ||
""" | ||
for blueprint in blueprints: | ||
app.register_blueprint(blueprint) | ||
|
||
app.config["DATABASE"] = database_interface or create_db_interface(**mysql_kwargs) | ||
|
||
if replica_socket or mysql_kwargs: | ||
# In deployed environment: replica_socket is set | ||
# In integration tests: connect to same host/port as primary database | ||
# In endpoint tests, no replica connection is used | ||
mysql_kwargs["unix_socket"] = replica_socket | ||
app.config["DATABASE_REPLICA"] = create_db_interface(**mysql_kwargs) | ||
|
||
db.init_app(app) | ||
|
||
|
||
|
@@ -53,7 +63,10 @@ def before_sentry_send(event, hint): | |
return | ||
return event | ||
|
||
# dsn/environment/release: reading SENTRY_* environment variables set in CircleCI | ||
# The SDK requires the parameters dns, and optionally, environment and release for | ||
# initialization. In the deployed GAE environments they are read from the | ||
# environment variables `SENTRY_*`. Since in local or CI testing environments these | ||
# variables don't exist, the SDK is not effective which is desired. | ||
sentry_sdk.init( | ||
integrations=[FlaskIntegration(), AriadneIntegration()], | ||
traces_sample_rate=float(os.getenv("SENTRY_TRACES_SAMPLE_RATE", 0.0)), | ||
|
@@ -65,11 +78,15 @@ def before_sentry_send(event, hint): | |
configure_app( | ||
app, | ||
*blueprints, | ||
host=os.environ["MYSQL_HOST"], | ||
port=int(os.environ["MYSQL_PORT"]), | ||
# always used | ||
user=os.environ["MYSQL_USER"], | ||
password=os.environ["MYSQL_PASSWORD"], | ||
database=os.environ["MYSQL_DB"], | ||
# used for connecting to development / CI testing DB | ||
host=os.getenv("MYSQL_HOST"), | ||
port=int(os.getenv("MYSQL_PORT", 0)), | ||
# used for connecting to Google Cloud from GAE | ||
unix_socket=os.getenv("MYSQL_SOCKET"), | ||
replica_socket=os.getenv("MYSQL_REPLICA_SOCKET"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to remember to set this variable in the contexts in circleci There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I already set it as a project-wide variable |
||
) | ||
return app |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,61 @@ | ||
from peewee import MySQLDatabase | ||
from playhouse.flask_utils import FlaskDB # type: ignore | ||
|
||
db = FlaskDB() | ||
|
||
class DatabaseManager(FlaskDB): | ||
"""Custom class to glue Flask and Peewee together. | ||
If configured accordingly, connect to a database replica, and use it in all SELECT | ||
SQL queries. | ||
""" | ||
|
||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.replica = None | ||
|
||
def init_app(self, app): | ||
self.replica = app.config.get("DATABASE_REPLICA") # expecting peewee.Database | ||
super().init_app(app) | ||
|
||
def connect_db(self): | ||
super().connect_db() | ||
if self.replica: | ||
self.replica.connect() | ||
|
||
def close_db(self, exc): | ||
super().close_db(exc) | ||
if self.replica and not self.replica.is_closed(): | ||
self.replica.close() | ||
|
||
def get_model_class(self): | ||
"""Whenever a database model (representing a table) is defined, it must derive | ||
from `db.Model` which calls this method. | ||
""" | ||
if self.database is None: | ||
raise RuntimeError("Database must be initialized.") | ||
|
||
class BaseModel(self.base_model_class): | ||
@classmethod | ||
def select(cls, *args, **kwargs): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. elegant |
||
if self.replica: | ||
with cls.bind_ctx(self.replica): | ||
return super().select(*args, **kwargs) | ||
return super().select(*args, **kwargs) | ||
|
||
class Meta: | ||
database = self.database | ||
|
||
return BaseModel | ||
|
||
|
||
db = DatabaseManager() | ||
|
||
|
||
def create_db_interface(**mysql_kwargs): | ||
"""Create MySQL database interface using given connection parameters. `mysql_kwargs` | ||
are validated to not be None and forwarded to `pymysql.connect`. | ||
Configure primary keys to be unsigned integer. | ||
""" | ||
for field in ["host", "port", "user", "password", "database"]: | ||
for field in ["user", "password", "database"]: | ||
if mysql_kwargs.get(field) is None: | ||
raise ValueError( | ||
f"Field '{field}' for database configuration must not be None" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
#!/bin/bash | ||
|
||
layout virtualenv .venv | ||
export MYSQL_PORT=32000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
weird that the socket is needed here if you are using a socket
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you mean? weird that the port is needed? It's because the connection to the DB for integration tests is via TCP. Socket will only be effective in deployed environments.