Skip to content

Commit

Permalink
⚡️(api) optimize create/bulk dynamic endpoints number of db queries
Browse files Browse the repository at this point in the history
As the most accessed endpoints, dynamic create/bulk operations needed a
carefull attention to ensure no extra database queries were performed.
We removed 1 extra database query to fetch created object identifier
and 3 extra database queries for bulk endpoints.
  • Loading branch information
jmaupetit committed Dec 17, 2024
1 parent be1fc42 commit 0d31e77
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 6 deletions.
1 change: 1 addition & 0 deletions src/api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to
number of database queries
- CLI: sort groups and operational units alphabetically in the `list-groups`
command
- Decrease the number of database queries for dynamic endpoints

## [0.16.0] - 2024-12-12

Expand Down
20 changes: 14 additions & 6 deletions src/api/qualicharge/api/v1/routers/dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,11 +324,13 @@ async def create_status(
detail="Attached point of charge does not exist",
)
db_status = Status(**status.model_dump(exclude={"id_pdc_itinerance"}))
# Store status id so that we do not need to perform another request
db_status_id = db_status.id
db_status.point_de_charge_id = pdc_id
session.add(db_status)
session.commit()

return DynamiqueItemCreatedResponse(id=db_status.id)
return DynamiqueItemCreatedResponse(id=db_status_id)


@router.post("/status/bulk", status_code=fa_status.HTTP_201_CREATED, tags=["Status"])
Expand Down Expand Up @@ -365,16 +367,18 @@ async def create_status_bulk(

# Create all statuses
db_statuses = []
db_status_ids = []
for status in statuses:
db_status = Status(**status.model_dump(exclude={"id_pdc_itinerance"}))
db_status_ids.append(db_status.id)
db_status.point_de_charge_id = db_pdcs[status.id_pdc_itinerance]
db_statuses.append(db_status)
session.add_all(db_statuses)
session.commit()

return DynamiqueItemsCreatedResponse(
size=len(db_statuses),
items=[s.id for s in db_statuses],
size=len(db_status_ids),
items=db_status_ids,
)


Expand Down Expand Up @@ -403,11 +407,13 @@ async def create_session(
detail="Attached point of charge does not exist",
)
db_qc_session = QCSession(**session.model_dump(exclude={"id_pdc_itinerance"}))
# Store session id so that we do not need to perform another request
db_qc_session_id = db_qc_session.id
db_qc_session.point_de_charge_id = pdc_id
db_session.add(db_qc_session)
db_session.commit()

return DynamiqueItemCreatedResponse(id=db_qc_session.id)
return DynamiqueItemCreatedResponse(id=db_qc_session_id)


@router.post("/session/bulk", status_code=fa_status.HTTP_201_CREATED, tags=["Session"])
Expand Down Expand Up @@ -444,14 +450,16 @@ async def create_session_bulk(

# Create all statuses
db_qc_sessions = []
db_qc_session_ids = []
for session in sessions:
db_qc_session = QCSession(**session.model_dump(exclude={"id_pdc_itinerance"}))
db_qc_session_ids.append(db_qc_session.id)
db_qc_session.point_de_charge_id = db_pdcs[session.id_pdc_itinerance]
db_qc_sessions.append(db_qc_session)
db_session.add_all(db_qc_sessions)
db_session.commit()

return DynamiqueItemsCreatedResponse(
size=len(db_qc_sessions),
items=[s.id for s in db_qc_sessions],
size=len(db_qc_session_ids),
items=db_qc_session_ids,
)
111 changes: 111 additions & 0 deletions src/api/tests/api/v1/routers/test_dynamic.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from qualicharge.auth.factories import GroupFactory
from qualicharge.auth.schemas import GroupOperationalUnit, ScopesEnum, User
from qualicharge.conf import settings
from qualicharge.db import SAQueryCounter
from qualicharge.factories.dynamic import (
SessionCreateFactory,
StatusCreateFactory,
Expand Down Expand Up @@ -763,6 +764,30 @@ def test_create_status_for_superuser(db_session, client_auth):
assert response.json() == {"id": str(db_status.id)}


def test_create_status_number_of_queries(db_session, client_auth):
"""Test the /status/ create endpoint number of db queries."""
id_pdc_itinerance = "FR911E1111ER1"
qc_status = StatusCreateFactory.build(id_pdc_itinerance=id_pdc_itinerance)

# Create point of charge
save_statique(
db_session, StatiqueFactory.build(id_pdc_itinerance=id_pdc_itinerance)
)

# Create a new status
with SAQueryCounter(db_session.connection()) as counter:
response = client_auth.post(
"/dynamique/status/", json=json.loads(qc_status.model_dump_json())
)
assert response.status_code == status.HTTP_201_CREATED
# We expect 3 database requests:
# 1. select request user
# 2. select point of charge
# 3. insert status
expected = 3
assert counter.count == expected


@pytest.mark.parametrize(
"client_auth",
(
Expand Down Expand Up @@ -938,6 +963,37 @@ def test_create_status_bulk_for_superuser(db_session, client_auth):
assert db_status.etat_prise_type_ef == qc_status.etat_prise_type_ef


def test_create_status_bulk_number_of_queries(db_session, client_auth):
"""Test the /status/bulk create endpoint number of db queries."""
qc_statuses = StatusCreateFactory.batch(3)

# Create points of charge
save_statiques(
db_session,
[
StatiqueFactory.build(id_pdc_itinerance=s.id_pdc_itinerance)
for s in qc_statuses
],
)

# Assert no status exist
assert db_session.exec(select(func.count(Status.id))).one() == 0

# We expect the same answer as one point of charge does not exist
with SAQueryCounter(db_session.connection()) as counter:
response = client_auth.post(
"/dynamique/status/bulk",
json=[json.loads(s.model_dump_json()) for s in qc_statuses],
)
assert response.status_code == status.HTTP_201_CREATED
# We expect 3 database requests:
# 1. select request user
# 2. select points of charge
# 3. insert statuses
expected = 3
assert counter.count == expected


@pytest.mark.parametrize(
"client_auth",
(
Expand Down Expand Up @@ -1178,6 +1234,30 @@ def test_create_session_for_superuser(db_session, client_auth):
assert response.json() == {"id": str(db_qc_session.id)}


def test_create_session_number_of_queries(db_session, client_auth):
"""Test the /session/ create endpoint number of db queries."""
id_pdc_itinerance = "FR911E1111ER1"
qc_session = SessionCreateFactory.build(id_pdc_itinerance=id_pdc_itinerance)

# Create point of charge
save_statique(
db_session, StatiqueFactory.build(id_pdc_itinerance=id_pdc_itinerance)
)

# Create a new status
with SAQueryCounter(db_session.connection()) as counter:
response = client_auth.post(
"/dynamique/session/", json=json.loads(qc_session.model_dump_json())
)
assert response.status_code == status.HTTP_201_CREATED
# We expect 3 database requests:
# 1. select request user
# 2. select point of charge
# 3. insert session
expected = 3
assert counter.count == expected


@pytest.mark.parametrize(
"client_auth",
(
Expand Down Expand Up @@ -1348,6 +1428,37 @@ def test_create_session_bulk_for_superuser(db_session, client_auth):
assert db_qc_session.energy == qc_session.energy


def test_create_session_bulk_number_of_queries(db_session, client_auth):
"""Test the /session/bulk create endpoint number of db queries."""
qc_sessions = SessionCreateFactory.batch(3)

# Create points of charge
save_statiques(
db_session,
[
StatiqueFactory.build(id_pdc_itinerance=s.id_pdc_itinerance)
for s in qc_sessions
],
)

# Assert no session exist
assert db_session.exec(select(func.count(Session.id))).one() == 0

# We expect the same answer as one point of charge does not exist
with SAQueryCounter(db_session.connection()) as counter:
response = client_auth.post(
"/dynamique/session/bulk",
json=[json.loads(s.model_dump_json()) for s in qc_sessions],
)
assert response.status_code == status.HTTP_201_CREATED
# We expect 3 database requests:
# 1. select request user
# 2. select points of charge
# 3. insert sessions
expected = 3
assert counter.count == expected


@pytest.mark.parametrize(
"client_auth",
(
Expand Down

0 comments on commit 0d31e77

Please sign in to comment.