python meetup

Post on 23-Jan-2018

187 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Оптимизация работы веб сервера с

базой данных на примере Django

Одесса Python meetup

Кто я?

Евгений Гетманский

Разработка веб сайтов на протяжении более пяти лет

Работаю тимлидом в Steelkiwi inc.

Часть 1

Что такое оптимизация и зачем она нужна?

Требования к проектам

Требования к проектам

Функциональные требования

Требования к проектам

Функциональные требования

Соблюдение стандартов

Требования к проектам

Функциональные требования

Соблюдение стандартов

Безопасность

Требования к проектам

Функциональные требования

Соблюдение стандартов

Безопасность

Скорость работы

504 Gateway Timeout Error

Увеличение таймаута

Асинхронная обработка данных

Оптимизация Python кода

Оптимизация запросов к базе данных

Часть 2

Оптимизация работы с базой данных

Когда нужно

оптимизировать

Бывает ли преждевременная оптимизация

Когда нужно

оптимизировать

Бывает ли преждевременная оптимизация

Анализ требований и проектирование до старта проекта

Когда нужно

оптимизировать

Бывает ли преждевременная оптимизация

Анализ требований и проектирование до старта проекта

Во время написания кода

Когда нужно

оптимизировать

Бывает ли преждевременная оптимизация

Анализ требований и проектирование до старта проекта

Во время написания кода

Когда уже не работает

Приемы и примеры кода

Как в Django писать оптимизированный код

Немного кодаclass Category(models.Model):

name = models.CharField(max_length=50)

class Product(models.Model):

name = models.CharField(max_length=50)

category = models.ForeignKey(Category,

related_name='products')

brand = models.ForeignKey(Brand, related_name='products')

class CartItem(models.Model):

product = models.ForeignKey(Product)

class Brand(models.Model):

title = models.CharField(max_length=50)

Использование ID

связанного объекта

используйте

product.category_id

вместо

product.category.id

Использование ID связанного

объекта

for item in CartItem.objects.filter(

product__category_id__in={

p.category.id for p in products}):

# Do something

Использование ID связанного

объекта

for item in CartItem.objects.filter(

product__category_id__in={

p.category_id for p in products}):

# Do something

Бывает и такое

if parent.invoices.filter(status=Invoice.PAID).count():

return

parent.invoices.filter(status=Invoice.PAID).order_by(

'payed_period_end').last()

return None

Проверки

if queryset

len(queryset)

count

exists

Используем select_related

for product in Product.objects.select_related('category'):

data.append(

{'product': product.name,

'category': product.category.name})

Используем select_related

Что это и зачем нужно?

Что происходит когда выполняется?

Как это помогает оптимизировать работу сервера

Используем prefetch_related

for category in Category.objects.prefetch_related('products'):

data.append({'category': category,

'products': category.products.all()})

Используем prefetch_related

Что это и зачем нужно?

Что происходит когда выполняется?

Как это помогает оптимизировать работу сервера

А что можно добавить?

Используем prefetch_related

prefetch = Prefetch('products',

queryset=Product.objects.select_related('brand'))

for category in Category.objects.only('name').prefetch_related(prefetch):

data.append({'category': category.name,

'products': [(product.name, product.brand.name) for

product in category.products.all()]})

Неужели все так хорошо или

есть что то еще?

Join на таблице в 300000 записей

Иногда лучше проверить

Django debug toolbar и debug panel

Создание записей

Все те же грабли с циклами

А что же делать?

Используем bulk_create

parsed_data = parse_data(data)

batch_size = 100

items = []

for row in parsed_data:

items.append(Item(**row))

if len(items) >= batch_size:

Item.objects.bulk_create(items)

items = []

if items:

Item.objects.bulk_create(items)

Редактирование записей

Вы не поверите, но тут опять все хорошо пока нет циклов

bulk_update?

Нет, но есть кое что получше - F object

Вот это чаще всего не хорошо

for product in category.products.all():

product.priority += 1

product.save()

А это уже неплохо

category.products.update(field=F('priority')+1)

Читайте документацию

https://docs.djangoproject.com/en/1.10/topics/db/optimization/

Часть 3

Немного примеров из жизни

Пересчет рейтинга

Приложение по работе с данными о авиакомпаниях, самолетах и

поиске маршрутов

Решения

увеличить таймаут

увеличить таймаут

прикручивать асинхронную обработку?

что делать???

Загрузка данных из CSV

много записей и много проверок

А теперь можно задавать вопросы

Немного ссылок

https://www.facebook.com/evgeniy.getmansky

http://steelkiwi.com

https://github.com/steelkiwi/meetup.demo

Шаблон проекта.

Использование Vagrant, VirtualEnv и

Ansible provisioner. Зачем это

необходимо?

О себе

1. Степанов Александр

2. Python Team Lead in SteelKiwi Inc.

3. Участие в 15+ проектах

4. AngularJS, BackBoneJS

5. Ionic Framework (не вздумайте)

Что мы хотели бы получить?

1. Скорость инициализации

2. Удобен при командной разработке

3. Прост с точки зрения деплоя

4. Легко масштабируется

5. Приятная структура

6. Имеет изолированное окружение

Какие мы знаем шаблоны?

- Default django template

- Two Scoops of Django

- Cookiecutter Django

Default django template

$ django-admin.py startproject myproject

myproject/manage.pymyproject/

__init__.pysettings.pyurls.pywsgi.py

myapp/

$ python manage.py startapp myapp

Django two scoops

$ django-admin.py startproject myproject2 --template=https://github.com/twoscoops/django-twoscoops-project/archive/develop.zip

myproject_2/docs/requirements/myproject_2/

manage.pystatic/templates/myproject_2/

__init__.pysettings/__init__.pylocal.pybase.pyproduction.pytest.py

urls.pywsgi.py

Сookiecutter django

$ pip install "cookiecutter>=1.4.0"

$ cookiecutter https://github.com/pydanny/cookiecutter-django

myproject_3/manage.pyconfig/

__init__.pysettings/

__init__.pylocal.pycommon.pyproduction.pytest.py

urls.pywsgi.py

requirements/myproject_3/static/templates/myapp/

Сравнение структуры

myproject_3/manage.pyconfig/

__init__.pysettings/

__init__.pylocal.pycommon.pyproduction.pytest.py

urls.pywsgi.py

requirements/base.txtlocal.txtproduction.txttest.txt

myproject_3/static/templates/myapp/

myproject_2/docs/requirements/

base.txtlocal.txtproduction.txttest.txt

myproject_2/manage.py

static/templates/myproject_2/

__init__.pysettings/

__init__.pylocal.pybase.pyproduction.pyTest.py

urls.pywsgi.py

myproject/manage.py

myproject/__init__.pysettings.pyurls.pyWsgi.py

myapp/

Default Django Two Scoops of Django Cookiecutter Django

Что мы сделали?

1. Только необходимые настройки

2. Изменили скрипт инициализации проекта

3. Разные database backend

4. Единый файл настроек

5. Переменные окружения по умолчанию

Скрипт инициализации проекта

{

"project_name":

"project_name",

"repo_name": "project_name",

"python_version": ["2", "3"],

"use_postgresql": "y",

"use_postgis": "n",

"use_mysql": "n",

"use_celery": "n",

"use_allauth": "n",

"use_redis": "n",

"use_rabbitmq": "n",

"use_supervisor": "n",

"use_apache": "n",

"use_nginx": "n"

}

Единый файл settings.py

env = environ.Env(

DJANGO_DEBUG=(bool, False),

DJANGO_SECRET_KEY=(str, 'CHANGEME!!!8l=tkv+t@)ilij-w=hogt8qgs-zf(i3or-k7w_d(7_1=&l1##l'),

DJANGO_ADMINS=(list, []),

DJANGO_ALLOWED_HOSTS=(list, []),

DJANGO_STATIC_ROOT=(str, str(APPS_DIR('staticfiles'))),

DJANGO_MEDIA_ROOT=(str, str(APPS_DIR('media'))),

DJANGO_DATABASE_URL=(str, 'postgres:///myproject4'),

DJANGO_EMAIL_URL=(environ.Env.email_url_config, 'consolemail://'),

DJANGO_DEFAULT_FROM_EMAIL=(str, 'admin@example.com'),

DJANGO_EMAIL_BACKEND=(str, 'django.core.mail.backends.smtp.EmailBackend'),

DJANGO_SERVER_EMAIL=(str, 'root@localhost.com'),

DJANGO_CELERY_BROKER_URL=(str, 'redis://localhost:6379/0'),

DJANGO_CELERY_BACKEND=(str, 'redis://localhost:6379/0'),

DJANGO_CELERY_ALWAYS_EAGER=(bool, False),

DJANGO_USE_DEBUG_TOOLBAR=(bool, False),

DJANGO_TEST_RUN=(bool, False),

)

DJANGO DATABASE URL

Engine URL

PostgreSQL postgres://USER:PASSWORD@HOST:PORT/NAME

PostGIS postgis://USER:PASSWORD@HOST:PORT/NAME

MySQL mysql://USER:PASSWORD@HOST:PORT/NAME

MySQL (GIS) mysqlgis://USER:PASSWORD@HOST:PORT/NAME

SQLite sqlite:///PATH

DJANGO EMAIL URL

SMTP: smtp://

SMTP+SSL: smtp+ssl://

SMTP+TLS: smtp+tls://

Console mail: consolemail://

File mail: filemail://

LocMem mail: memorymail://

Dummy mail: dummymail://

# Example

smtp+tls://user:SuperSecretPassword@smtp_server.com:587`

А как же локальная разработка и тесты?

if env.bool('DJANGO_USE_DEBUG_TOOLBAR'):

MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware', )

INSTALLED_APPS += ('debug_toolbar', )

DEBUG_TOOLBAR_CONFIG = {

'DISABLE_PANELS': [

'debug_toolbar.panels.redirects.RedirectsPanel',

],

'SHOW_TEMPLATE_CONTEXT': True,

}

if env.bool('DJANGO_TEST_RUN'):

pass

Virtual environment

$ pip install virtualenv

$ cd my_project_folder

$ virtualenv venv

$ source venv/bin/activate

$ pip install requests

$ pip install -r requirements.txt

Версия Python$ virtualenv -p /usr/bin/python2.7 venv

Установка пакетов

Vagrant

Vagrant это cli для VirtalBox, VMWare, Amazon EC2, LXC

Преимущества

1. Установка утилит в изолированное окружение

2. Уникальное окружение в контексте проекта

3. Поддержка в любой операционной системеПочему не Docker

1. См. Пункт 3 преимуществ

2. Изолированное окружение, повторяющее реальную рабочую среду

Настройки Vagrant1. Используемый box

2. Маппинг портов и папок

3. Способ наполнения изолированного окружения

4. Используемые ресурсы#### Box

develop.vm.box = "develop"develop.vm.box_url = "http://vagrant.steel.kiwi/vagrant/develop/"

#### Networkdevelop.vm.network :forwarded_port, guest: 8000, host: 8000develop.vm.network :private_network, ip: '192.168.80.50'develop.vm.synced_folder ".", "/vagrant", type: "nfs"

#### ansible_provisiondevelop.vm.provision "ansible" do |ansible|ansible.verbose = "v"ansible.playbook = "ansible/vagrant.yml"end

#### system resourcesdevelop.vm.hostname = "develop"

develop.vm.provider "virtualbox" do |v|v.memory = 1024v.cpus = 1

Как наполнить окружение?

#!/bin/bash

echo "deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main" >>

/etc/apt/sources.list.d/pgdg.list

wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | \

apt-key add -

apt-get update

apt-get install -y postgresql-9.4 postgresql-contrib libgeos-dev

Ansible ubuntu/trusty64---

- name: Configure the PostgreSQL APT key

apt_key: url=https://www.postgresql.org/media/keys/ACCC4CF8.asc state=present

- name: Configure the PostgreSQL APT repositories

apt_repository: repo="deb http://apt.postgresql.org/pub/repos/apt/ trusty-pgdg main"

state=present

- name: Update PostgreSQL Cache

apt: update_cache=yes

- name: Install PostgreSQL

apt: name={{ item }} force=yes state=installed

with_items:

- postgresql-9.4

- postgresql-contrib-9.4

- python-psycopg2

tags: packages

Ansible steelkiwi.box---

- name: enable postgresql

service: >

name=postgresql

enabled=yes

runlevel=default

state=started

tags: postgresql

- name: Create database

postgresql_db: >

login_user={{ db_user }}

name={{ db_name }}

owner={{ db_user }}

encoding='UTF-8'

lc_collate='en_US.UTF-8'

lc_ctype='en_US.UTF-8'

state=present

become: yes

become_user: vagrant

tags: create_db

В чем же преимущество Ansible?

Всё работает через SSH;

Написан на Python;

YAML;

Хорошая, постоянно обновляемая документация;

Последовательное обновление узлов.

Variables---

project_name: myproject4

application_name: myproject4

# Database settings.

db_user: "vagrant"

db_name: "{{ application_name }}"

db_password: ""

# Application settings.

virtualenv_path: "/home/vagrant/venv"

project_path: "/vagrant"

requirements_file: "{{ project_path }}/requirements/local.txt"

django_settings_path: "{{ project_path }}/config"

django_settings: "config.settings"

#Python version - change if you needed

python_version: 3

Связь Variables c Vagrant playbook# vars/vagrant.ymlmysql: noapache: nonginx: nopostgresql: truerabbitmq: noredis: truesupervisor: noapp: true

# vagrant.yml

vars_files:- vars/vagrant.yml

roles:- { role: mysql, when: "mysql == true" }- { role: apache, when: "apache == true" }- { role: nginx, when: "nginx == true" }- { role: postgresql, when: "postgresql == true" }- { role: rabbitmq, when: "rabbitmq == true" }- { role: redis, when: "redis == true" }- { role: supervisor, when: "supervisor == true" }- { role: app, when: "app == true" }

Ansible & Django

---

- name: Install packages required by the Django app inside virtualenv

pip: >

virtualenv={{ virtualenv_path }}

requirements={{ requirements_file }}

- name: Run Django database migrations

django_manage: >

command=migrate

app_path="{{ project_path }}"

virtualenv="{{ virtualenv_path }}"

settings="{{ django_settings }}"

when: run_django_db_migrations is defined and run_django_db_migrations

become: yes

become_user: vagrant

tags: django.migrate

Старт нового проекта

$ cookiecutter https://github.com/steelkiwi/django-template.git

$ vagrant up

$ vagrant ssh

$ cd /vagrant

$ cp env.example ./config/.env

$ python manage.py runserver 0.0.0.0:8000

А если проект уже в разработке?

$ git clone https://github.com/steelkiwi/old_poject.git

$ vagrant up

$ vagrant ssh

$ cd /vagrant

$ cp env.example ./config/.env

$ python manage.py runserver 0.0.0.0:8000

Выводы

1. Получили легко настраиваемый и гибкий шаблон проекта

2. Добавили возможность использовать разные database backend and

python version

3. Каждый проект находится в изолированном окружение

4. Одинаковая среда разработки

5. Добавили удобный способ наполнения и разворачивания проекта.

Useful links

https://github.com/steelkiwi/django-template

https://www.vagrantup.com/

https://www.ansible.com/

https://github.com/kennethreitz/dj-database-url

https://github.com/joke2k/django-environ

top related