Reduce the pressure on the db for API post email requests.

Instead of saving the email notification to the db add it to a queue to save later.
This is an attempt to alleviate pressure on the db from the api requests.
This initial PR is to trial it see if we see improvement in the api performance an a reduction in queue pool errors. If we are happy with this we could remove the hard coding of the service id.

In a nutshell:
 - If POST /v2/notification/email is from our high volume service (hard coded for now) then create a notification to send to a queue to persist the notification to the db.
 - create a save_api_email task to persist the notification
 - return the notification
 - New worker app to process the save_api_email tasks.
This commit is contained in:
Rebecca Law
2020-03-25 07:59:05 +00:00
parent 544537791d
commit a13bcc6697
8 changed files with 182 additions and 12 deletions

View File

@@ -30,7 +30,7 @@ from app.celery.tasks import (
s3,
send_inbound_sms_to_service,
process_returned_letters_list,
)
save_api_email)
from app.config import QueueNames
from app.dao import jobs_dao, service_email_reply_to_dao, service_sms_sender_dao
from app.models import (
@@ -44,8 +44,8 @@ from app.models import (
JOB_STATUS_IN_PROGRESS,
LETTER_TYPE,
SMS_TYPE,
ReturnedLetter
)
ReturnedLetter,
NOTIFICATION_CREATED)
from tests.app import load_example_csv
@@ -60,8 +60,8 @@ from tests.app.db import (
create_user,
create_reply_to_email,
create_service_with_defined_sms_sender,
create_notification_history
)
create_notification_history,
create_api_key)
from tests.conftest import set_config_values
@@ -1664,3 +1664,37 @@ def test_process_returned_letters_populates_returned_letters_table(
returned_letters = ReturnedLetter.query.all()
assert len(returned_letters) == 2
@freeze_time('2020-03-25 14:30')
def test_save_api_email(sample_email_template, mocker):
mock_send_email_to_provider = mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
api_key = create_api_key(service=sample_email_template.service)
data = {
"id": str(uuid.uuid4()),
"template_id": str(sample_email_template.id),
"template_version": sample_email_template.version,
"to": "jane.citizen@example.com",
"service_id": str(sample_email_template.service_id),
"personalisation": None,
"notification_type": sample_email_template.template_type,
"api_key_id": str(api_key.id),
"key_type": api_key.key_type,
"client_reference": 'our email',
"reply_to_text": "our.email@gov.uk",
"document_download_count": 0,
"status": NOTIFICATION_CREATED,
"created_at": datetime.utcnow().strftime(DATETIME_FORMAT),
}
encrypted = encryption.encrypt(
data
)
assert len(Notification.query.all()) == 0
save_api_email(encrypted)
notifications = Notification.query.all()
assert len(notifications) == 1
assert str(notifications[0].id) == data['id']
assert notifications[0].created_at == datetime(2020, 3, 25, 14, 30)
mock_send_email_to_provider.assert_called_once_with([data['id']], queue=QueueNames.SEND_EMAIL)

View File

@@ -491,11 +491,6 @@ def sample_notification_history(notify_db, notify_db_session, sample_template):
return notification_history
@pytest.fixture(scope='function')
def mock_encryption(mocker):
return mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
@pytest.fixture(scope='function')
def sample_invited_user(notify_db_session):
service = create_service(check_if_service_exists=True)

View File

@@ -1,4 +1,5 @@
import uuid
from unittest import mock
from unittest.mock import call
import pytest
@@ -951,3 +952,28 @@ def test_post_email_notification_when_data_is_empty_returns_400(
assert error_msg == 'phone_number is a required property'
else:
assert error_msg == 'email_address is a required property'
def test_post_notifications_saves_email_to_queue(client, notify_db_session, mocker):
save_email_task = mocker.patch("app.celery.tasks.save_api_email.apply_async")
service = create_service(service_id='539d63a1-701d-400d-ab11-f3ee2319d4d4', service_name='high volume service')
template = create_template(service=service, content='((message))', template_type=EMAIL_TYPE)
data = {
"email_address": "joe.citizen@example.com",
"template_id": template.id,
"personalisation": {"message": "Dear citizen, have a nice day"}
}
response = client.post(
path='/v2/notifications/email',
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), create_authorization_header(service_id=service.id)]
)
json_resp = response.get_json()
assert response.status_code == 201
assert json_resp['id']
assert json_resp['content']['body'] == "Dear citizen, have a nice day"
assert json_resp['template']['id'] == str(template.id)
save_email_task.assert_called_once_with([mock.ANY], queue='save-api-email')