Формулировка задачи: https://yadi.sk/i/dA9umaGbQdMNLw
Реализованы следующие обработчики:
- POST /imports
- PATCH /imports/
$import_id
/citizens/$citizen_id
- GET /imports/
$import_id
/citizens - GET /imports/
import_id
/citizens/birthdays - GET /imports/
$import_id
/towns/stat/percentile/age
-
Вам потребуется python 3.7.3
(скорее всего, всё будет работать и на python >= 3.5.3, но я не проверял) -
Убедитесь, что у вас установлен Python модуль
setuptools
- Например, можно запустить интерпретатор Python и выполнить
import setuptools
- Если модуля нет, то установите его, например, командой
sudo apt install python-setuptools
(в Ubuntu)
- Например, можно запустить интерпретатор Python и выполнить
-
Скачайте архив с исходным кодом:
wget https://storage.yandexcloud.net/yandex-backend-school/citizens-0.1.tar.gz
-
Извлеките содержимое архива
tar -xzf citizens-0.1.tar.gz
-
Перейдите в извлеченную директорию
cd citizens-0.1
-
Установите приложение
python3 setup.py install
-
Запустите приложение
python3 -m citizens
-
Сервис будет запущен на порту
8080
на всех интерфейсах (0.0.0.0
) -
Сделайте тестовый запрос и убедись, что сервис работает:
curl -X POST --data '{"citizens":[{"citizen_id": 1,"town": "Москва","street": "Льва Толстого","building": "16к7стр5","apartment": 7,"name": "Иванов Сергей Иванович","birth_date": "17.04.1999","gender": "male","relatives": []}]}' http://0.0.0.0:8080/imports
Предполагается, что docker уже установлен на вашей машине
- Скачайте архив с исходным кодом:
wget https://storage.yandexcloud.net/yandex-backend-school/citizens-0.1.tar.gz
- Извлеките содержимое архива
tar -xzf citizens-0.1.tar.gz
- Перейдите в извлеченную директорию
cd citizens-0.1
- Запустите скрипт
run-with-docker.sh
sh run-with-docker.sh
- Когда образ будет собран, контейнер запустится и сервис будет готов к работе,
вы увидите сообщение
Starting application on 0.0.0.0:8080
. Логи будут писаться в stdout.
-
Установите на сервер необходимое ПО:
-
nginx >= 1.14.0
sudo apt-get install nginx
-
mongodb >= 4.0.12
- Инструкцию можно найти на их сайте:
https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/
Для удобства, приведу здесь список командwget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add -
echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
sudo service mongod start
- Инструкцию можно найти на их сайте:
-
supervisor >= 3.3.1
sudo apt-get install -y supervisor
-
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y python3-venv
-
make
sudo apt-get install make
-
Для удобства, всё то же самое, но одной командой:
wget -qO - https://www.mongodb.org/static/pgp/server-4.0.asc | sudo apt-key add -; \ echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list; \ sudo apt-get update; \ sudo DEBIAN_FRONTEND=noninteractive apt-get install -y mongodb-org nginx supervisor python3-venv make; \ sudo service mongod start
-
-
Скачайте архив с исходным кодом:
wget https://storage.yandexcloud.net/yandex-backend-school/citizens-0.1.tar.gz
-
Извлеките содержимое архива
tar -xzf citizens-0.1.tar.gz
-
Перейдите в извлеченную директорию и выполните команду для установки:
cd citizens-0.1 && make install
- В процессе установки некоторые команды выполняются с
sudo
, поэтому ваш пользователь должен быть в группеsudoers
, а также, вам скорее всего потребуется ввести ваш пароль.
- В процессе установки некоторые команды выполняются с
-
Cервис запущен. Попробуйте сделать тестовый запрос.
-
Если что-то пошло не так, смотрите логи.
- /var/log/nginx/error.log
- /var/log/supervisor/supervisor.log
- ~/citizens-0.0.1/logs/citizens.log
- ~/citizens-0.0.1/logs/supervisor_1_stdout.log (и/или supervisor_2_stdout.log)
Примеры тестовых запросов:
-
curl -X POST --data '{"citizens":[{"citizen_id": 1,"town": "Москва","street": "Льва Толстого","building": "16к7стр5","apartment": 7,"name": "Иванов Сергей Иванович","birth_date": "17.04.1999","gender": "male","relatives": [1]}]}' http://0.0.0.0:8080/imports
-
curl -X GET http://0.0.0.0:8080/imports/1/citizens
-
curl -X GET http://0.0.0.0:8080/imports/1/citizens/birthdays
-
curl -X GET http://0.0.0.0:8080/imports/1/towns/stat/percentile/age
-
curl -X PATCH --data '{"apartment": 777}' http://0.0.0.0:8080/imports/1/citizens/1
- aiohttp 3.5.4
- Асинхронный веб-фреймворк
- aiojobs 0.2.1
- Используется для того, чтобы не отменять задачи в loop-е, в случае, если клиент разорвал соединение не дожидаясь ответа от сервера (особенности aiohttp)
- pymongo 3.8.0
- Ну вроде понятно. Для общения с mongodb
- numpy 1.17.0
- Используется при подсчете перцентилей
В систему установлены:
- python 3.7.3 (собран из исходных файлов, системный python не затронут)
- nginx 1.14.0 (из системных репозиторев)
- mongodb 4.0.12 (из репозитория repo.mongodb.org)
- supervisor 3.3.1 (из системных репозиторев)
Конфигурационные файлы для nginx и supervisor лежат в папке configs.
В /etc/nginx/conf.d и в /etc/supervisor/conf.d созданы символические ссылки на эти файлы.
Для mongodb используется конфиг по умолчанию.
-
Разработка и тестирование велись с использованием Python 3.7.3.
Скорее всего всё будет работать и на более младших версиях Python ( >= 3.5.3), но из-за ограниченности времени, тестирование на других версиях не проводилось. -
Я осознанно не стал использовать библиотеку
marshmallow
для валидации входных данных, потому что в моём тесте (tests/test_marshmallow.py
) кастомная реализация оказалась примерно в 4 раза быстрее.
Хотя, конечно, кода гораздо меньше получается, если использовать marshmallow. -
Вы можете заметить по коммитам, что использовать регулярные выражения для проверки корректности формата даты, я стал использовать также из-за того, что такой способ даёт прирост в скорости на тысячах записей (примерно x1.5) по сравнению со способом "попробовать создать объект Date и посмотреть, будет ли ошибка"
-
Я пробовал "стрелять" яндекс танком по сервису, развернутому на виртуалке в яндекс облаке
- Платформа: Intel Cascade Lake
- vCPU: 4
- Гарантированная доля vCPU: 5%
- RAM 4 ГБ
- HDD 5 ГБ
При таких условиях:
- Кол-во жителей 10000
- Схема нагрузки: const(10, 2m) (10 rps в течение 2х минут)
Вот такие результаты:
Percentiles (all/last 1m/last), ms: . HTTP codes:
100.0% < 4,310.0 4,310.0 2,046.4 . 1,143 +10 97.94% : 200 OK
99.5% < 3,230.0 3,320.0 2,046.4 . 24 +0 2.06% : 201 Created
99.0% < 2,810.0 3,090.0 1,939.5 .
95.0% < 1,585.0 1,820.0 1,512.3 . Net codes:
90.0% < 1,045.0 1,255.0 978.2 . 1,167 +10 100.00% : 0 Success
85.0% < 775.0 925.0 788.5 .
80.0% < 620.0 695.0 697.3 . Average Sizes (all/last), bytes:
75.0% < 525.0 575.0 612.6 . Request: 39,173.5 / 125.1
70.0% < 449.0 485.0 481.0 . Response: 556,885.1 / 496,016.7
60.0% < 327.0 362.0 452.0 .
50.0% < 201.0 210.0 288.3 . Average Times (all/last), ms:
40.0% < 113.0 122.0 111.0 . Overall: 416.61 / 489.28
30.0% < 86.0 91.0 65.0 . Connect: 26.21 / 31.39
20.0% < 68.0 74.0 50.0 . Send: 14.78 / 0.07
10.0% < 50.0 53.0 49.0 . Latency: 109.64 / 89.09
. Receive: 265.99 / 368.73
Cumulative Cases Info:
name count % last net_e http_e avg ms last ms
OVERALL: 1,167 100.00% +10 0 0 416.6 489.2
patch_citizen: 287 24.59% +3 0 0 65.6 54.0
get_all_citizens: 284 24.34% +2 0 0 994.4 1,452.5
get_percentiles: 287 24.59% +2 0 0 98.0 117.5
get_birthdays: 285 24.42% +3 0 0 420.8 529.7
import: 24 2.06% +0 0 0 1,527.0 0.0
- В последний момент решил добавить кеш для GET запросов. Кеш сбрасывается при
получении PATCH запроса на изменение данных жителя.
Не стоит рассматривать это как какой-то общий подход, я понимаю, что если будет больше точек для изменения данных, там везде нужно будет чистить кеш и прочие проблемы. Это решение для конкретной задачи в рамках конкретных условий, чтобы снизить нагрузку на базу.
Решил добавить файловый кеш, потому что он наиболее легок в реализации с учетом того, что приложение состоит из нескольких процессов, нужен разделяемый ресурс. По крайней мере мне так казалось поначалу, что это самый простой кеш. Потом я понял, что есть какое-то количество случаев, которые надо корректно обрабатывать. Например создание файла, в случае, когда два процесса одновременно получат запрос на получение всех жителей, а кеша нет, оба пойдут создавать файл. Я попытался решить некоторые проблемы с конкурентностью, но, думаю, что конкурентный доступ к кешу я реализовал плохо. Сильно сомневался, стоит ли оставлять плохую реализацию, однако, учитывая условия, решил оставить. Жителей максимум 10000, на стенде запущено всего 2 процесса, в условии написано не более 1 запроса в момент времени (насколько я понял, в секунду). При таких условиях вероятность какой-то ошибки с кешом мне показалась достаточно низкой.