Удобный инструмент — Newrelic


Проблема мониторинга состояния приложения актуально для всех маломальски работающих систем. Каждый ее решает сам в меру своих сил. Чаще всего это какой — то самодел на основе Nagios, Zabbix или Cacti. Но основная сложность использования данных систем заключается в том, что если брать данные системы «из коробки» то они будут отслеживать состояние сервера и его сервисов, а не самого приложения. Придется писать плагины для системы которые будут экспортировать данные о состоянии приложения. Многие этого не делают, по причине отсутствия времени или навыков.

Решение есть. Оно не бесплатное но достаточно надежное и универсальное. И это решение собственно и есть NewRelic. Кстати, сразу о финансовой составляющей вопроса. Тут дело не такое простое. Дело в том, что релик позволяет мониторить как ваше приложение так и сам сервер. Все что относится к серверу — мониторится бесплатно. Единственно ограничение это время хранения данных на серверах newrelic. В бесплатном варианте — это только 24 часа. Это означает что вы не сможете посмотреть исторические данные которые превышают данный предел. Есть триал «напопробовать» — он равнозначен PRO версии и кажется он составляет 30 дней.

Так что умеет эта чудо система за свои почти 150 баксов в месяц. Умеет многое:

  • параметры производительности системы — как серверной части так и клиентской. Тоесть вы можете видеть на что у вас тратяться миллисекунды на сервере и на клиенте.
    Screen Shot 2014-03-07 at 14.14.52
  • Детализировать и трейсить любые views котороые у вас вызывают сомнения
    Screen Shot 2014-03-07 at 14.19.39
  • Вести учет всех ошибок и инциндентов которые происходили в приложении
  • Просматривать данные о запросах к БД и видеть к какой таблице какая вьюшка относится.
  • Делать X-Ray любой вьюшки. Это по сути профилирование отдельной views, которое производится с реальными клиентами (нагрузкой) в реальном времени. Тоесть вы видите что и как тормозит при эксплуатации вашего кода. Очень мощный инструмент для отладки произодительности.
  • Есть система RUM для конечными клиентами
  • Гора всяких отчетов и прочее прочее прочее…

И это только по приложению. Есть еще серверный монитор который показывает состояние серверов с отчетами по использованию памяти, дисков, процессов и прочими прелестями. Но надо отметить — в этом случае newrelic несколько проигрывает тому — же zabbix, но ядреные бородатые и свитерастые сисадмины пусть юзают что им хочется. Newrelic инструмент для разработчика.

Программист: «работа сервера мешает работе моего приложения»
Системный администратор: «работа приложения мешает работе моего сервера»
:)

Ко всему прочему весь этот мониториг можно расширить узкоспециализированными плагинами, например слежение за PostgreSQL или Nginx. Плагинов не то что прям много — но хватает для работы. Никто опять таки не мешает писать свои.

Теперь про установку. Она неимеоверно простая (для питонячего приложения, руби, ява дотнет и прочие — не знаю, но думаю не сложнее).

  1. Регистрируем аккаунт и получаем ключик лицензии. Он как раз и является привязкой ваших серверов и приложений к аккаунту на релике.
  2. На сервере выполняем: pip install newrelic
  3. После установки появляется команда newrelic-admin. Запускаем ее с ключем  newrelic-admin generate-config _ВАШ_LICENCE_KEY_ _путь куда положить файл_
  4. Система создаст вас конфиг по указанному пути. Можете его «потыкать палочкой» но обычно ничего там править не надо.
  5. Далее, (в случае если использется UWSGI) надо запустить UWSGI приложениче через newrelic-admin. Я это делаю так:
    NEW_RELIC_CONFIG_FILE=/path/to/newrelic.ini
    export NEW_RELIC_CONFIG_FILE
    newrelic-admin run-program /usr/bin/uwsgi —ini /code/app.ini

    естественно пути менять под себя.

  6. Собственно все. Приложение подключено и через 5-10 минут появятся данные в вашем аккаунте.

Для работы плагинов newrelic  используется newrelic_plugin_agent. Тут еще все проще. Так-же ставится через pip install newrelic_plugin_agent. У меня после установки конфиг лежал в /opt/newrelic_plugin_agent/newrelic_plugin_agent.cfg — тут настраиваются все параметры относящиеся к работе плагинов. Обязательно надо в этом файле прописать ваш ключик в соответствующее место. Подключение стандартный плагинов — это просто расскомментирование их настроек в этом файле. Для запуска просто вызываем  newrelic_plugin_agent -с /path/to/newrelic/plugin/agent/config

В качестве резюме — мы используем эту систему уже более полугда, и свои 150$/mo она отрабатывает легко. Однако надо заметить, что эта чудо система не всесильна. И сидение с отладчиком и кучей терминалов никто не отменит. Однако для повседневного использования — однозначнй мастхев.

 

 

Полнотекстовый поиск без проблем


Часто, для подсобных нужд, бывает нужно использовать хоть плохенький но все-таки полнотекстовый поиск по БД. Понятно что, «серьезные пацаны» используют для этого всякие Sphinx  и ElasticSearch, но у этих штук есть одна премерзкая особенность: их надо настраивать, загонять в них данные и вообще мониторить их потребности. Да и если у вас в базе всего полсотни тысяч записей — использовать эти «махины» не сильно то удобно. Особенно если не хотите выходить за пределы Django.

Выход есть и он достаточно простой. Любая современная база данных (Mongo — ага, lol) имеет в себе встроенный полнотекстовый поиск. Не шибко умный, зато «изкоробки». Так как MySQL последние несколько лет не работал (и слава богу) расскажу на примере PostgreSQL. Начнем с версии. Вообще для постгреса использование свежих версий не «прихоть админа», а реальная необходимость. Связано это с достаточно активной деятельностью направленной на улучшение работы и вводу новых фич в движек. Так что я советую проводить эксперименты, да и вообще работать с самой свежей версией СУБД.

Итак, приступим.

Допустим у нас есть какая то Django-модель:

class Element(models.Model):
    class Meta:
        db_table = 'products'
    parent = models.ForeignKey(Section, null=True, blank=True)
    title = models.CharField(max_length=200)
    description = models.TextField(null=True, blank=True)
    article = models.CharField(max_length=200, null=True, blank=True)

    def __unicode__(self):
        return "%s/%s" % (self.parent.title, self.title)

И допустим мы хотели бы сделать так, чтобы по полям title и description происходил полнотекстовый поиск.

Для начала разберем как это делается на чистом SQL:

SELECT *, ts_rank(to_tsvector('russian', title||desctription), to_tsquery('russian', 'шампунь|против|перхоти')) as rank FROM "Products"
WHERE to_tsvector('russian', title||desctription) @@ to_tsquery('russian', 'шампунь|против|перхоти')
ORDER BY rank DESC LIMIT 20 OFFSET 0;

В этом запросе главными командами являются to_tsvector и to_tsquery. Первая команда переводит строки которые лежат в БД в дикт вида: слово:вес,…

Наглядно:
Screen Shot 2014-02-16 at 20.08.21

Функция to_tsquery нормализует введенные слова и приводит их к типу tsquery. Так-же есть функция plainto_tsquery, которая принимает на вход просто строку и приводит ее так-же как to_tsquery, но без необходимости заранее разбивать фразу. Надо заметить, что если в случае с to_tsquery можно самим указать логическое условие, я данном случае ИЛИ (|), то plainto_tsquery разбивает строку с условием И (&).

Так-же в запросе участвует оператор @@ которые как раз сравнивает tsvector и tsquery. Вообще есть достаточно много функций и операторов для полнотекстового поиска, так что курим.

Все это будет работать из коробки, даже с не настроенными индексами (про них ниже). Но. Мы то ленивые, писать сырые запросы не хотим и вообще считаем это «низкосистемным злом» (sarcasm).

Добрый дядя Джангонафт облегчил задачу интеграции FTS в Django. Для этого устанавливаем модуль

pip install djorm-ext-pgfulltext

и несколько изменяем код нашей модельки:

from djorm_pgfulltext.models import SearchManager
from djorm_pgfulltext.fields import VectorField
from django.db import models

class Element(models.Model):
    class Meta:
        db_table = 'products'

    parent = models.ForeignKey(GroupSection, null=True, blank=True)
    title = models.CharField(max_length=200)
    description = models.TextField(null=True, blank=True)
    article = models.CharField(max_length=200, null=True, blank=True)

search_index = VectorField()
    def __unicode__(self):
        return "%s/%s" % (self.parent.title, self.title)

    search_manager = SearchManager(
        fields=('title', 'description'),
        config='pg_catalog.russian',
        search_field='search_index',
        auto_update_search_field=True
    )

По сути все изменение заключается в том, что мы добавили search_index — которое является тем самым tsvector для записи в БД и добавили новый менеджер запросов в конструктор которого передали следующие параметры:

  • fields — массив полей из которых будет строиться tsvector,
  • config — указывает postgresql с каким словарем мы хотим работать,
  • search_field — поле в которм у нас лежат данные которые являются уже подготовленным tsveсtor, собранный из указанных в fields полей,
  • auto_update_search_field — флаг который заставляет пересоздаваться search_field при изменении записи.

Если взглянуть на структуру таблицы, то мы увидим одно дополнительное поле — search_index, в котором уже лежит tsvector. Это сделано для оптимизации, Postgres умеет работать с уже подготовленными векторами и не тратить в пустую ресурсы на выполнение to_tsvector(‘russian’, title||desctription) для каждой строки БД.

Осталось понять как этот полнотекстовый запрос собственно сделать в нашем коде. Тут проще не бывает:

elements = Element.search_manager.search(query)

где query — это просто строка текста которую мы хотим найти. Естественно к результатам работы этого менеджера можно применять filter и все остальное.

Чтобы все работало побыстрее, нужно создать GIN индекс. Создается он так:

CREATE INDEX fts_tsvector_gin_idx ON products USING GIN(search_index)

Собственно и все, что я хотел рассказать я рассказал. Ну если только добавить пару слов о качестве встроенного полнотекстового поиска PostgreSQL. Тут все не особо объективно. Если речь идет о каком то примитивном каталоге товаров — то качество вполне сносное, вполне можно использовать в продакшене. Если же говорить о более серьезных вещах — таких как поиск текста по предложению — то лучше идти в сторону Elasticsearch+Russina morphology или Сфинкса.

Вообще есть неплохое сравнение FTS движков.

Что такое сигналы (signals) в Django


telephone-exchange

Что такое сигнал в Django Framework ?

На бытовом уровне это система (диспетчер сигналов) которая обрабатывает некоторые виды событий которые генерирует система. По сути система сигналов разделяется на два компонента:

  • sender  — компонент посылающий сигнал;
  • receiver — компонент отвечающий за обработку сигнала.

Опять же, пример на бытовом уровне.

Допустим у нас есть три модели:

class UserProfile(models.Model):
    nick = models.CharField(max_length = 30)
    score = models.FloatField(default = 0)

class Post(models.Model):
    user_profile = models.ForeignKey(UserProfile)
    text = models.TextFiled()

class UserAvatar(models.Model):
    user_profile = models.ForeignKey(UserProfile)
    image = models.UrlField()

И допустим у нас есть следующее условие: при каждом добавлении записи Post или UserAvatar должно обновляться поле UserProfile.score увеличиваясь на единицу.

Самый простой и первый приходящий в голову способ — это перегрузить методы save моделей Post и UserAvatar. Но пионэры не ищут простых путей, так что я заюзаю систему сигналов джанги, да и к тому-же зачем засорять лишней логикой save да еще и в двух моделях.

Вообще, сигналы, условно можно разделить на два типа:

Встроенные разделяются на несколько подгрупп:

  • Сигналы моделей. Данная группа самая обширная и, наверное, самая часто используемая. Она состоит из следующих сигналов:
    • pre_init, post_init — посылаются соответственно до и после вызова метода  __init__ модели;
    • pre_save, post_save — соответственно посылаются до и после вызова метода save модели;
    • pre_delete, post_delete — по аналогии, до и после удаления объекта (вызов метода delete);
    • m2m_changed — отправляется при изменении объектов m2m (ManyToMany) связи;
    • class_prepared — отправляется после регистрации модели в Djange. Как главсит документация  it’s not generally used in third-party applications.
  • Менеджментные сигналы (я не знаю как из нормально перевести на русский язык)
    • pre_syncdb, post_syncdb — вызваются до и после запуска команды manage.py syncdb
  • Request/response сигналы (аналогично, фантазия на перевод не работает)
    • request_started — посылается когда Django начинает обработку request;
    • request_finished — посылается после завершения обработки request;
  • Тестовые сигналы — запускаются только когда запущены юнит тесты, группа состоит из 2-х сигналов setting_changed, template_rendered
  • Сигналы генерируемые движком работы с БД (не путать с ORM). Содержит только сигнал connection_created который запускается после создания соединения с БД.

Для нашего примера, нам вполне хватит одного сигнала post_save.  Почему именно его ? Потому что нам важно сделать инкрементацию поля score  после сохранения моделей UserAvatar или Post, данный сигнал как раз за это и отвечает.

Сам сигнал «испускается» в кишках ORM Django (точнее за это отвечает Signal Dispatcher), наша задача только реализовать приемник (ни или обработчик сигнала) данного типа. Для этого в Django есть декоратор reciever(signal, **kwargs).

Вот код нашего обработчика:

from django.db.models.signals import post_save
from django.dispatch import receiver
@receiver(post_save, sender = Post)
@receiver(post_save, sender = UserAvatar)
def add_score(instance, **kwargs):
    profile = instance.user_profile
    profile.score += 1
    profile.save()

Как видно, я использовал два декоратора для сигнала post_save от разных моделей. В метод add_save, который после «оборачивания» декоратором receiver стал приемником событий post_save, передается собственно объект который вызвал это событие. В нашем случае это могут быть  объекты класса UserAvatar и Post.  Так как обе модели имеют поле user_profile, то инкрментация поля score не представляет сложности (иначе бы пришлось проверять тип пришедшего объекта и делать условия, но имхо проще сделать 2 разных сигнала).

Кроме встроенных сигналов есть возможность создавать свои сигналы. Но это тема следующей заметки.

С сигналами связано два неприятных момента с которыми можно столкнуться при излишне активном использовании:

  • Рекурсивный вызов сигналов. Когда один сигнал может запустить другой который в свою очередь вызовет первый. Тут важно внимательно следить за вызовами;
  • «Размазывание» логики. Из за того что встроенные сигналы явным образом нигде не вызываются, то очень легко забыть, что например, сохранение какого либо объекта запускает череду сигналов. Из за этого бывают непредвиденные и сложно отлаживаемые баги (все вроде работает, ничего не падает а результат не тот что ожидается)

И последнее. Сигналы удобно отслеживать через  Django Debug Toolbar. Для этого в настройках DEBUG_TOOLBAR_PANELS  нужно добавить ‘debug_toolbar.panels.signals.SignalDebugPanel’

Как убрать help_text при ренеринге формы с ManyToMany полями.


Наверное многих раздражает при выводе Django формы с ManyToMany полями текст

Удерживайте "Control" (или "Command" на Mac) для выбора нескольких значений

или

Hold down "Control", or "Command" on a Mac, to select more than one.

Особенно он раздражает если на данный контрол вешается что нибудь похожее на Chosen, получается некрасиво. Так вот убрать этот текст можно простой правкой модели:


class Foo(models.Model):
...
bar = models.ManyToMany(Bar, blank=True, null=True)
bar.help_text = ''
...

Вот и все, больше эта надпись не будет раздражать.

Балдеющие от адреналина и зомбированные шаблонами


Книга с таим название привлекает внимание. Что говорить, только ради названия я ее и купил.Авторы книги — группа консультантов которые помогают компаниям в менеджмента проектов.

Основная цель данной книги показать основные поведенческие паттерны присущие многим компаниям по входящим ПО.
Слово поведенческие основное. Вы не встретите в книге никакого упоминания про паттерн синглетон или фабрика. Эта книга не про эти шаблоны. Более того, эта книга не для разработчиков вообще.  Ее цель показать 86 причин (по сути моделей поведения) хреновой работы команд, неудач проектов и косяки менеджеров ПО. Естественно все излагается в контексте возможностей как сделать софт лучше, процесс разработки более гладким, а нервы целыми.

В книга будет полезна менеджерам  и аналитикам проектов. Книга поднимает основную проблему современного мира создания ПО — как балансировать в проектном треугольнике так чтобы не стать в конечном итоге козлом отпущения, когда сроки уже перевалили за дату второго дедлайна, бабки кончились а у разработчиков нет никакого желания или возможностей что то исправить.

В кнге нет готовых решений в виде: делать надо так и так, думаю просто не позволяет объем. Но каждая глава имеет ценность, и над каждой стоит задуматься и спроецировать описанную модель на свою компанию или свою должность в этой компании. В любом случае что то полезное извлечь получится.

Перевод совсем не шлакрвый, хотя как я заметил у издателя Символ+ совсем шлаковых не бывает (ну ладно, почти не бывает)
Одним словом рекомендую для прочтения всем менеджерам проектов не осилившим pm book :-)

ISBN: 978-5-93286-160-8

Джоел о программировании. Джоел. И снова о программировании


Вместо предисловия: где то с месяц до нового 2012 года я заказал у books.ru кучу книжек по программированию, архитектуре и менеджменту. Хочется сделать небольшие обзорчики по мере прочтения книг. Начну с двух книг Джоела Спольских, потом по мере прочтения буду выкладывать обзорчики других.

Итак «Джоэл о программировании» и «Джоэл. И снова о программировании».

Что можно сказать об этих двух книжках…

Они очень хорошо написаны. Основное и главное достоинство книг Джоэла — прмая и доступная для всех мысль и хороший слог. Собственно во второй книге автор об этом и говорит «хорошему программисту надо уметь хорошо излагать свои мысли».

Надо понимать, что это не книги в общем-то, это наборы статей из блога joelonsoftware.com. Именно по этому автор затрагивает очень большое количество аспектов менеджмента и разработки ПО. Статьи кнечно сгруппированы по направлению, но не ищите в них развернутых материалов по какойто то теме.

Скажу прямо, мне книжки очень понравились, они достаточно интересно написаны, поднимаются  распространенные и сложные темы и в книгах есть ответы на некоторые вопросы. Да и стиль узложения автора неподражаем.

Чего не стоит искать в книгах, так это самого программирования. Это не учбники информатики, это больше методологии по менеджменту времени, качеству ПО по рекомендации подбору персоналу. Все это сдобрено хорошими историями из опыта автора, так что читать их очень легко даже если вы не профессинал в какой то области.

Те кто любит сухие факты, книги наверное не понравятся. В них (особенно во второй больше) достаточно много воды и спорных идей, которые автор выдвигает как неприложные истины. Особенно это касается вопросов связанных с подбором персонала, у меня при прочтении некоторых глав, посвященных этой тематики, выворачивало наизнанку. Если у Джоел с таким рвением подбирает себе разработчиков и если его продукт так хорош, то почему так мало распространен.

Особо хочется отметить перевод — он на удивление хорош. Хотя и есть некоторые ляпы, но смысл от них материал не меняет.
Можно по разному относится к персоне автора, но книги, имхо, обязательны к прочтению. Проведите аналогию с товарищем А. Лебедевым, так вот Спольских такойже Лебедев но от программирования. Интересная и неоднозначная личнось с более чем 20 летним опытом разработки и менеджмента ПО. В любом случае, что то новое узнаете. А если не узнаете то хотябы повесилитесь.

ISBN: 5-93286-063-4 (первая книга)

ISBN: 978-5-93286-144-8 (вторая книга)

MySQL функции GROUP_CONCAT и ее аналог в PostgreSQL


В MySQL есть одна интересная функция, о которой не все знают, но от этого менее интересной и полезной она не становится. Имя этой функции GROUP_CONCAT.

Занимается она тем, что склеивает через сепаратор значения из определенного столбца таблицы. Лучше всего это показать на примере.

Допустим, что у нас есть таблица со следующей структурой:

CREATE TABLE `section_firms`(

  `sid` int(11) NOT NULL,
  `fid` int(11) NOT NULL,
);

В таблице хранятся соотношения одной сущности (sid) к другой (fid), в данном случае many-to-many.


Нам нужно в результате запроса получить все fid со всеми связанными с ней sid. Как раз для этого и существует функция GROUP_CONCAT. Она возмет все строчки указанного столбца и склит их в одну строку. Запрос для этого будет выглядит так:

SELECT fid, GROUP_CONCAT(sid) as sid
FROM section_firms
GROUP BY fid

Результат будет таким:

Как видим функция «свернула» столбец в строку.

А как обстоят дела с ней в PostgreSQL ? А никак. (Как мне правильно указал Alexander Korotkov — я ошибаюсь по поводу PostgreSQL, функция такая есть) Это специфичная только для MySQL  функция. Но можно написать аналогичную процедуру для PostgreSQL, чуть сложнее конечно, но можно положить в библиотеку (а еще лучше в свой шаблон базы данных) и таскать из проекта в проект.

Функция собственно:

create aggregate array_accum (
sfunc = array_append,
basetype = anyelement,
stype = anyarray,
initcond = '{}'
);

Пример запроса с этой процедурой:

select fid, array_to_string(array_accum(sid), ',')
from section_firms
group by fid;

Небольшое замечание, процедура возвращает не строку а массив значений, для того чтобы превратить в строку нужно результат работы процедуры скормить стандартной процедуре array_to_string, которая склеит массив с указанным разделителем.

Более подробно про реализацию под PostgreSQL можно почитать тут.

Надеюсь, что данная информация сможет быть интересной.