Skip to content

Commit 86f70a8

Browse files
authored
Adds psycopg3 connection pool and make targets for database (#2505)
* Adds psycopg3 connection pool and make targets for database * fix formating and mypy errors
1 parent fd591f6 commit 86f70a8

7 files changed

Lines changed: 141 additions & 9 deletions

File tree

Makefile

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,28 @@ pip-check:
7171
coverage:
7272
pytest --cov --cov-report html
7373

74+
coverage-show-uncovered:
75+
pytest --cov --cov-report term-missing:skip-covered
76+
7477
extract-translations:
7578
pybabel extract --no-location --sort-output --omit-header -o openslides_backend/i18n/messages/template-en.pot openslides_backend
7679

77-
db:
80+
drop-database:
81+
make -C global/meta/dev drop-database
82+
83+
create-database:
84+
make -C global/meta/dev create-database
85+
86+
apply-db-schema:
87+
make -C global/meta/dev apply-db-schema
88+
python cli/create_schema.py
89+
90+
create-database-with-schema:
7891
make -C global/meta/dev create-database-with-schema
7992
python cli/create_schema.py
8093

94+
run-psql:
95+
make -C global/meta/dev run-psql
8196

8297
# Build and run production docker container (not usable inside the docker container)
8398

cli/create_schema.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
from openslides_backend.datastore.shared.postgresql_backend.create_schema import (
22
create_schema,
33
)
4-
from openslides_backend.datastore.writer.services import register_services
54

65

76
def main() -> None:
8-
register_services()
97
create_schema()
108

119

dev/entrypoint.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ set -e
55
source scripts/export_datastore_variables.sh
66
scripts/wait.sh $DATASTORE_WRITER_HOST $DATASTORE_WRITER_PORT
77

8+
printf "\nOpenslides Database:\n"
9+
python cli/create_schema.py
10+
printf "\n"
11+
812
printf "\nMigrations:\n"
913
python openslides_backend/migrations/migrate.py finalize
1014
printf "\n"
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import os
2+
from collections.abc import Callable
3+
4+
import psycopg
5+
import psycopg_pool
6+
7+
from openslides_backend.shared.env import Environment
8+
from openslides_backend.shared.exceptions import DatabaseException
9+
10+
env = Environment(os.environ)
11+
conn_string_without_db = f"host='{env.DATABASE_HOST}' port='{env.DATABASE_PORT}' user='{env.DATABASE_USER}' password='{env.PGPASSWORD}' "
12+
system_conn_pool = psycopg_pool.ConnectionPool(
13+
conninfo=conn_string_without_db + "dbname='postgres'",
14+
connection_class=psycopg.Connection,
15+
kwargs={"autocommit": True, "row_factory": psycopg.rows.dict_row},
16+
min_size=1,
17+
max_size=1,
18+
open=True,
19+
check=psycopg_pool.ConnectionPool.check_connection,
20+
name="ConnPool for dev postgres",
21+
timeout=5.0,
22+
max_waiting=0,
23+
max_lifetime=3600.0,
24+
max_idle=600.0,
25+
reconnect_timeout=300.0,
26+
num_workers=1,
27+
)
28+
os_conn_pool = psycopg_pool.ConnectionPool(
29+
conninfo=conn_string_without_db + f"dbname='{env.DATABASE_NAME}'",
30+
connection_class=psycopg.Connection,
31+
kwargs={"autocommit": True, "row_factory": psycopg.rows.dict_row},
32+
min_size=int(env.DB_POOL_MIN_SIZE),
33+
max_size=int(env.DB_POOL_MAX_SIZE),
34+
open=False,
35+
check=psycopg_pool.ConnectionPool.check_connection,
36+
name="ConnPool for openslides-db",
37+
timeout=float(env.DB_POOL_TIMEOUT),
38+
max_waiting=int(env.DB_POOL_MAX_WAITING),
39+
max_lifetime=float(env.DB_POOL_MAX_LIFETIME),
40+
max_idle=float(env.DB_POOL_MAX_IDLE),
41+
reconnect_timeout=float(env.DB_POOL_RECONNECT_TIMEOUT),
42+
num_workers=int(env.DB_POOL_NUM_WORKERS),
43+
)
44+
45+
46+
def get_unpooled_db_connection(
47+
db_name: str,
48+
autocommit: bool = False,
49+
row_factory: Callable = psycopg.rows.dict_row,
50+
) -> psycopg.Connection:
51+
"""Use for temporary connections, where pooling is not helpfull like tests and other specific DDL-Connections"""
52+
try:
53+
db_connection = psycopg.connect(
54+
f"host='{env.DATABASE_HOST}' port='{env.DATABASE_PORT}' dbname='{db_name}' user='{env.DATABASE_USER}' password='{env.PGPASSWORD}'",
55+
autocommit=autocommit,
56+
row_factory=row_factory,
57+
)
58+
db_connection.isolation_level = psycopg.IsolationLevel.SERIALIZABLE
59+
except psycopg.OperationalError as e:
60+
raise DatabaseException(f"Cannot connect to postgres: {str(e)}")
61+
return db_connection
Lines changed: 41 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,52 @@
11
import os
22

3-
from openslides_backend.datastore.shared.di import injector
4-
from openslides_backend.datastore.shared.postgresql_backend import ConnectionHandler
3+
from psycopg import Connection, sql
4+
5+
from openslides_backend.database.db_connection_handling import (
6+
env,
7+
get_unpooled_db_connection,
8+
)
9+
from openslides_backend.shared.exceptions import DatabaseException
510

611

712
def create_schema() -> None:
813
"""
914
Helper function to write the database schema into the database.
1015
"""
11-
connection_handler = injector.get(ConnectionHandler)
12-
with connection_handler.get_connection_context():
13-
with connection_handler.get_current_connection().cursor() as cursor:
16+
connection: Connection
17+
try:
18+
connection = get_unpooled_db_connection(env.DATABASE_NAME, False)
19+
except DatabaseException:
20+
conn_postgres = get_unpooled_db_connection("postgres", True)
21+
with conn_postgres:
22+
with conn_postgres.cursor() as curs:
23+
curs.execute(
24+
sql.SQL("CREATE DATABASE {db};").format(
25+
db=sql.Identifier(env.DATABASE_NAME),
26+
)
27+
)
28+
print("Database openslides created\n")
29+
connection = get_unpooled_db_connection(env.DATABASE_NAME, False)
30+
with connection:
31+
with connection.cursor() as cursor:
32+
# idempotent key-value-store schema
1433
path = os.path.realpath(
1534
os.path.join(os.getcwd(), os.path.dirname(__file__), "schema.sql")
1635
)
17-
cursor.execute(open(path).read(), [])
36+
cursor.execute(open(path).read())
37+
print("Idempotent key-value-schema applied\n")
38+
39+
# programmatic migrations of schema necessary, only apply if not exists
40+
result = cursor.execute(
41+
sql.SQL(
42+
"SELECT EXISTS (SELECT FROM pg_tables WHERE tablename = %s AND schemaname = %s);"
43+
),
44+
("organization_t", "public"),
45+
).fetchone()
46+
if result and result.get("exists"):
47+
return
48+
path = os.path.realpath(
49+
os.path.join("global", "meta", "dev", "sql", "schema_relational.sql")
50+
)
51+
cursor.execute(open(path).read())
52+
print("Relational schema applied\n")

openslides_backend/shared/env.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,21 @@ class Environment(Env):
5656
"VOTE_PATH": "/internal/vote",
5757
"VOTE_PORT": "9013",
5858
"VOTE_PROTOCOL": "http",
59+
"DATABASE_HOST": "",
60+
"DATABASE_PORT": "5432",
61+
"DATABASE_NAME": "openslides",
62+
"DATABASE_USER": "openslides",
63+
"PGPASSWORD": "openslides",
64+
"DATABASE_PASSWORD_FILE": "",
65+
# psycopg.ConnectionPool attributes with DB_POOL-prefix
66+
"DB_POOL_MIN_SIZE": "4",
67+
"DB_POOL_MAX_SIZE": "4",
68+
"DB_POOL_TIMEOUT": "30",
69+
"DB_POOL_MAX_WAITING": "0",
70+
"DB_POOL_MAX_LIFETIME": "3600",
71+
"DB_POOL_MAX_IDLE": "60",
72+
"DB_POOL_RECONNECT_TIMEOUT": "300",
73+
"DB_POOL_NUM_WORKERS": "3",
5974
}
6075

6176
def __init__(self, os_env: Any, *args: Any, **kwargs: Any) -> None:

openslides_backend/shared/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ class ServiceException(View400Exception):
8484
pass
8585

8686

87+
class DatabaseException(ServiceException):
88+
pass
89+
90+
8791
class DatastoreException(ServiceException):
8892
pass
8993

0 commit comments

Comments
 (0)