...

пятница, 9 сентября 2016 г.

Строим свой full-stack на JavaScript: Основы

[unable to retrieve full-text content]

Строим свой full-stack на JavaScript



В мире JavaScript очень легко набрать свой стек технологий, используя набор небольших пакетов, каждый из которых решают свою конкретную проблему. И это хорошо, c одной стороны, а с другой стороны, у вас особо нет выбора — фреймворки которые выполняют широкий спектр задач в JavaScript не популярны.


В этом цикле статей я хочу поделиться своим практическим опытом построения JS стека.


Читать дальше →

Централизованное управление политиками резервного копировния

[unable to retrieve full-text content]

Очередная приятная новость от команды разработчиков Microsoft Azure. Если кто-то из читателей, кто уже использует сервис резервного копирования в Azure ARM портале, то они могли заметить, что управление политиками резервного копирования чрезвычайно затруднено. Действительно, вам давалась возможность выбора существующей или создания новой политики, только при добавлении нового сервера в Recovery Services Vault.


Теперь все изменилось. Буквально сегодня команда Microsoft Azure добавила функцию работы с политиками резервного копирования в отдельную оснастку портала Azure Resource Manager.
Читать дальше →

четверг, 8 сентября 2016 г.

[Перевод] Компенсация лагов для оружия в MechWarrior Online

Задержка в сети (лаг) — это реальность, которую нужно учитывать в многопользовательских играх. Сообщениям, передаваемым через Интернет, требуется время, чтобы достичь точки назначения. В зависимости от маршрута и его длины передача этих сообщений может занять довольно долгое время. Это может негативно влиять на процесс игры, особенно в динамичных клиент-серверных играх, таких как FPS. То, что кажется очень простой задачей (стреляй, пытаясь попасть в цель), внезапно становится очень сложным в создании плавного игрового процесса для всех игроков. Думаю, не нужно говорить, что создавать многопользовательские игры сложно, при этом возникает множество проблем, которые разработчики должны решить. В этой статье я расскажу, как система вооружения MechWarrior Online справляется с лагом.

Одна из стратегий решения проблемы лага — уменьшить расстояния и таким образом улучшить маршрут между игроками и игровым сервером, чтобы лаг стал не так заметен. Этого можно достичь, создав систему региональных серверов и подключая игроков только к тем серверам, которые находятся к ним ближе; или же можно обеспечить передачу игрового трафика по маршрутам через определённые сети для оптимальной передачи на серверы. К сожалению, такой способ не всегда возможен из-за высокой стоимости и может применяться при ограниченных объёмах данных; но даже и в этом случае он не всегда срабатывает для игроков, находящихся в отдалённых регионах или имеющих низкое качество Интернет-соединения. MechWarrior Online (MWO) подходит к этой проблеме так же и использует региональные выделенные сервера для минимизации задержек для игроков. Однако этого часто недостаточно для большинства игроков, и нам нужно сделать что-то ещё.

Прогнозирование на стороне клиента


Вы могли слышать выражение «прогнозирование на стороне клиента» в обсуждениях клиент-серверных многопользовательских игр. Это важная концепция, позволяющая обеспечить плавность игрового процесса, особенно в динамичных играх. Без неё даже небольшой лаг может быть очень заметным и раздражающим. Идея прогнозирования на стороне клиента проста: вместо ожидания передачи сервером клиенту результата действия клиент прогнозирует исход этого действия, как будто оно происходит немедленно. Например, если игрок хочет пойти вперёд, мы сразу начинаем перемещать игрока на стороне клиента, а затем отправляем серверу запрос на перемещение. С точки зрения игрока кажется, что он перемещается сразу, однако в реальности для сервера он ещё не переместился. Если прогнозы игрового клиента всегда правильны, то игрок воспринимает это как отсутствие лага. Конечно же, прогнозы не могут быть всегда правильными и эту проблему нужно как-то решать, но мы не будем ею в этой статье. MWO очень сильно зависит от прогнозирования на стороне клиента. И движение, и система вооружения – когда игрок пытается переместить своего меха или выстрелить из оружия, игра реагирует немедленно.

Компенсация лага


Теперь у нас есть клиент, немедленно реагирующий на поведение игрока и устраняющий ощущение лага; однако есть ещё одна проблема. Несмотря на кажущуюся мгновенную реакцию на действия игрока, игровой мир реагирует на них с замедлением. Мы только устранили видимость лага, но информация, получаемая от сервера, всё равно приходит с задержкой. Это означает, что цель, которую видит игрок, на самом деле для сервера находится в другом месте; хуже того, когда игрок стреляет в цель и запрос на стрельбу получает сервер, цель сдвигается ещё дальше из своего исходного положения. Время, необходимое для получения информации и отправки запроса на сервер, называют временем передачи и подтверждения, или пингом. Если у игрока ненулевая латентность, то без компенсации лага игрокам приходилось бы стрелять в цель на упреждение, чтобы попасть в неё; чем выше пинг, тем дальше должно быть упреждение. Вы можете представить, насколько бы это раздражало игроков. Можно проиллюстрировать ситуацию следующими изображениями.

Что видит игрок при лаге

Что видит сервер при лаге

С точки зрения игрока, он стопроцентно попадает в свою цель, но сервер видит, что игрок стреляет совершенно мимо. Чтобы решить эту проблему, нужно каким-то образом компенсировать лаг игрока.

Сейчас вы можете задать вопрос: зачем вообще решать эту проблему? Если клиент прогнозирует всё, может ли клиент просто сообщить серверу, попал игрок в цель или нет? Проще всего ответить «да», этот вариант возможен; однако в нём есть большой изъян. Когда игровому клиенту разрешают сообщать что-то серверу, возникает возможность читерства. При игре на компьютере игровой клиент можно с лёгкостью модифицировать, и хитрый читер изменит игровой клиент таким образом, что тот будет отправлять сообщения о попадании, когда захочет читер, даже если он находится далеко от цели или вообще её не видит. Поэтому передача таких прав клиенту, особенно в отношении нанесения ущерба врагам — это невероятно рискованное предложение. Если многопользовательская игра предназначена для платформ, обеспечивающих какую-нибудь «защиту» игрового клиента, например, для консолей, то это может быть вполне приемлемым решением. Но в мире ПК, если вы даёте клиенту любой контроль над состоянием игры, то он почти наверняка будет использован в свою пользу. Существует множество способов жульничать в бою, но в этой статье мы тоже не будем их рассматривать. Для нас самым безопасным решением будет предоставление полного контроля серверу, именно такой подход и реализован в MWO.

В MWO существуют две категории оружия, и для каждой лаг должен компенсироваться по-своему. Вооружение подразделяется на оружие прямолинейного огня (такое как лазеры и пулемёты) и на артиллерийское оружие (например, ракеты и баллистическое оружие). Работать гораздо проще с прямолинейным оружием, так что давайте начнём с него.

Прямолинейное оружие с компенсацией лага


Прямолинейное вооружение стреляет мгновенно, достигая цели в момент выстрела. Это обеспечивается рейкастингом или трассировкой линий в текущем состоянии игрового мира для определения точки удара оружия. Поэтому для компенсации лага нужно выполнить трассировку линий. Если сервер хранит предыдущие положения и направления целей, то он может использовать данные о пинге игрока для определения местоположения цели в момент выстрела игрока. Это местоположение обозначено синим мехом на картинке ниже. Он выровнен относительно положения экрана стреляющего игрока. MWO обращается к положению этого синего меха в качестве «перемотанного по времени назад» положения цели. Теперь, когда сервер знает, где должна быть цель, он может переместить цель обратно в эту точку, выполнить трассировку линий для определения наличия попадания, а затем поместить цель в её исходную точку. Таким образом сервер делает прогноз игрового клиента точным.

Как сервер видит «перемотанное назад» положение цели

В случае MWO недостаточно просто хранить предыдущие положения и направления. Каждый компонент меха может быть повреждён отдельно, и это очень важная игровая механика, так что необходимо ещё состояние анимации. Оно также должно запоминаться и «перематываться» на сервере. На картинке это видно как разница в цикле движения «прошлого» и «настоящего» меха. Итак, мы разобрались с прямолинейным оружием, теперь давайте найдём решение для артиллерийского вооружения.

Артиллерийское оружие с компенсацией лага


Поведение артиллерии очень сильно отличается от поведения прямолинейного оружия. Они стреляют геометрическими объектами, такими как снаряды или ракеты, эти объекты перемещаются по воздуху, а затем наносят повреждения, сталкиваясь с целью. В этом случае простая «перемотка» не сработает. Если бы мы использовали тот же подход, что и для прямолинейного оружия, и приняли бы, что снаряды двигаются с бесконечной скоростью, то сервер мог некорректно определять, что снаряд мгновенно сталкивался с целью, даже если цель уже могла уйти с его пути до столкновения. Хуже того, игроки намеренно стреляют артиллерийским оружием на упреждение, потому что рассчитывают, где будет снаряд ко времени, когда он долетит до цели; если бы мы применяли такой способ, то игрок с совершенным глазомером на сервере с совершенной «перемоткой» постоянно бы промахивался. Нам нужен другой подход.

Упреждающая стрельба артиллерийским оружием на игровом клиенте

Нам бы хотелось перемотать вперёд снаряд на сервере, чтобы определить его положение, прогнозируемое клиентом. Если мы сделаем прогноз клиента правильным, мы скомпенсируем лаг на игровом клиенте. Чтобы достичь этого, мы разобьём цикл «жизни» снаряда на две части.

Ко времени получения сервером запроса на выстрел снарядом проходит половина времени на передачу и подтверждение игрока. Но нужно помнить, что сервер должен опережать клиент на половину времени на передачу и подтверждение, поскольку обрабатываемый сервером снаряд должен был существовать в течение секунд пинга, чтобы соответствовать всему остальному в мире сервера. В течение этого времени снаряд уже может столкнуться с целью в клиенте, и сервер должен на это реагировать, но давайте пока не будем волноваться об этом. Давайте назовём этот первый этап срока жизни снаряда периодом «перемотки назад». Всё после этой первой части срока жизни снаряда будет точкой, в которой симуляция снаряда сервером и прогнозирование поведения снаряда клиентом должны синхронизироваться. Важно заметить, что синхронизированность в данном контексте не означает, что снаряд должен находиться в одинаковых точках в клиенте и на сервере. Клиент должен отставать от сервера на половину времени на передачу и подтверждение. Поэтому в этом случае синхрозирированность означает, что снаряд находится в том же месте относительно других объектов мира и в клиенте, и на сервере. Назовём этот этап срока жизни снаряда синхронизированным периодом. Работа с первой частью немного сложнее, поэтому давайте сначала займёмся второй.

Предположим, что снаряд имеет простую физику и двигается по прямой с постоянной скоростью. В этом случае в момент получения сервером запроса на выстрел снаряд уже пройдёт в игровом клиенте расстояние, равное его скорости, умноженной на пинг клиента. Если снаряд имеет более сложную физику, например, с учётом трения и гравитации, то всё равно должно быть возможно рассчитать пройденное им расстояние с помощью более сложной формулы.

Траектория снаряда на сервере, вид сверху

Если мы пока проигнорируем период «перемотки назад», сервер может сделать прогноз клиента правильным или синхронизироваться с клиентом, просто создав снаряд на расстоянии, которое он прошёл от обычного положения создания. Другими словами, сервер не будет стрелять снарядом из дула оружия, а вместо этого выстрелит им на нужном расстоянии вдоль его траектории. Если снаряд имеет более сложную физику, серверу может потребоваться рассчитать нужную скорость и ускорение, которое должен будет иметь снаряд в соответствующей точке траектории. Полагая, что снаряд движется детерминированно, он должен теперь перемещаться по синхронизированной между клиентом и сервером траектории. Это довольно просто, но как насчёт периода «перемотки назад»?

Если бы мы всегда создавали снаряды на расстоянии периода «перемотки назад», то снаряды бы могли проходить через цели или даже статические объекты. Довольно просто ограничить прохождение через статические объекты с помощью трассировки линий, но как насчёт подвижных целей? Если цель находится в пределах периода «перемотки назад», то снаряд согласно прогнозу клиента может уже столкнуться с целью, прежде чем запрос дойдёт до сервера. В этом и состоит настоящая проблема со снарядами – сервер должен уметь распознавать такие ситуации, чтобы реализовывать прогнозы клиента.

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

К сожалению, у этого решения есть пара других проблем. Первая заключается в том, что быстро летящие снаряды без какого-нибудь способа непрерывного обнаружения столкновений в каждом цикле смогут проникать через цели в собственном цикле. Вторая состоит в том, что такой тип решения очень затратен для применения на сервере. Количество итераций соотносится с длительностью периода «перемотки назад». Так что если у игроков будут повышенные пинги, обработка каждого выстрела снарядом будет более ресурсоёмкой. В MWO бывает очень много снарядов, и они летят очень быстро; по этим и другим причинам, которые мы не будем здесь рассматривать, такое решение нам не подходило.

Для создания неитеративного решения давайте сначала сделаем некоторые допущения, упрощающие проблему. Мы примем для снарядов следующие допущения: снаряд приблизительно может считаться точкой, так что можно использовать трассирование линий для аппроксимации его траектории; траектория периода «перемотки назад» достаточно хорошо аппроксимируется прямой линией; также можно принять, что снаряды в течение периода «перемотки назад» имеют постоянную скорость. В MWO для снарядов применяется баллистическая физика, поэтому они не всегда движутся по прямым, но относительно мехов они очень малы, движутся очень быстро и не снижают скорости при движении в воздухе, так что эти допущения нам подходят. Также нам нужно принять допущения относительно движения целей. Примем, что движение цели между положением «перемотки назад» и текущим положением на сервере может быть достаточно хорошо аппроксимировано с помощью прямой линии; также примем, что скорость цели в течение этого периода тоже постоянна. Такие допущения могут быть довольно неудобными в некоторых динамичных играх, но к счастью, в MWO мехи поворачиваются, ускоряются и замедляются медленно и плавно, так что эти условия нам подходят.

Приняв эти допущения, мы можем разбить проблему на две части. В первой части мы хотим узнать, возможно ли вообще столкновение с целью. Если столкновение с целью возможно, то мы выполняем расчёт для определения времени, в которое должно случиться столкновение в период «перемотки назад». Во второй части мы используем эту информацию для выполнения обычной «перемотки назад» прямолинейного огня с изменённым значением пинга, чтобы определить, было ли попадание.

Для проверки на наличие возможности попадания, мы сначала определяем положение «перемотанной назад» цели на основании текущего пинга игрока и вектора, представляющего траекторию снаряда в период «перемотки назад». Это похоже на то, что мы делали при «перемотке назад» прямолинейного огня. Мы хотим узнать, пересекаются ли эти два объекта при перемещении по собственным траекториям. Допущения, принятые ранее, позволяют свести эту проблему к трассировке единственной линии, сделав скорость снаряда относительной к стационарной «перемотанной назад» цели. Можно увидеть это на изображении ниже.

Применяя наши допущения, сервер может свести проблему, представленную слева, к проблеме справа, которую гораздо проще решить

Решение этой проблемы аналогично решению проблемы прохождения сквозь объекты, которая упомянута выше. Можно представить, как снаряд перемещается из своего начального положения к концу своего периода «перемотки назад» за один цикл; наличие столкновений во время движения этих объектов между начальной и конечной точкой выполнятся непрерывной проверкой обнаружения столкновений. Важно также заметить, что эта проверка не полностью аналогична обычной трассировке линий. Мы намеренно игнорируем остальную часть мира и рассматриваем только «перемотанную назад» цель и модифицированную траекторию – это очень важная деталь. Если мы не будем игнорировать остальную часть мира, мы можем ошибочно получить результат, сообщающий о невозможности возникновения столкновения, в то время как оно может быть. Например, если игрок стоит рядом со стеной, у нас может получиться ложноотрицательный результат. Можно видеть это на картинке ниже.

Возможная ситуация, в которой объект мира приводит к ошибочному результату при проверке модифицированной траектории

Помните, что эта проверка сообщает нам только о возможности столкновения; она не гарантирует, что столкновение обязательно произойдёт. На пути могут находиться другие движущиеся или статичные объекты. Итак, мы всё ещё хотим выполнять какую-нибудь проверку «перемотки назад» вдоль исходной траектории снаряда, похожую на ту, которую мы делали для оружия прямолинейного огня. Если первая часть этой проверки проходит успешно, она даёт нам точно ту информацию, которая нам нужна для этого. Она сообщает, что столкновение возникло, оно возникло на определённом расстоянии в процентах вдоль пути от «перемотанного назад» положения до текущего положения цели. Поскольку и снаряд, и цель двигаются в течение одного временного промежутка, этот процент равен проценту вдоль модифицированной траектории, где происходит столкновение – иногда он называется «временем попадания». Мы можем использовать время попадания для расчёта значения модифицированного пинга, которое «перематывает назад» цель к этому среднему положению.

Состояния «перемотки назад» снарядов на сервере. Чёрная линия – исходный вектор огня в течение периода «перемотки назад». Синяя линия – модифицированной вектор огня для непрерывной проверки обнаружения столкновений. Чёрный мех – текущее положение меха-цели при выстреле снарядом. Синий мех – «перемотанное назад» положение меха-цели на основании пинга игрока. Жёлтый мех – потенциальное положение для финальной проверки «перемотки назад»

Это среднее положение и есть «перемотанное назад» положение, для которого мы и хотим выполнить проверку «перемотки назад» прямолинейного огня. Мы можем выполнить обычную проверку «перемотки назад» прямолинейного огня с модифицированным значением пинга, чтобы определить, было ли на самом деле попадание. Если попадание было, мы наносим цели соответствующее повреждение и обходимся без создания снаряда. Если попадание отсутствует, мы создаём снаряд в его синхронизированный период, как было описано выше. В случае изображения выше мы видим, что в данной ситуации попадание есть.

Заключение


Оружие с компенсированием лага — это только один из примеров сложных проблем, с которыми обычно сталкивается MWO и другие многопользовательские игры; в этой статье мы показали только самую малую часть. Мы даже не начинали обсуждать эффекты взрывов, трассеры, несоответствия, которые может привнести «перемотка назад» между игроками с лагами и без них, а также способы, которыми сервер может эффективно выполнять всю эту работу для нескольких целей. Наряду с компенсацией лагов оружием есть ещё множество проблем, с которыми приходится иметь дело многопользовательским играм, в том числе ошибки в прогнозировании клиентов, читерство, управление пропускной способностью, управление производительностью сервера и многие другие. Теперь вы можете понять, сколько сил и навыков вкладывается в создание того, что кажется простым элементом многопользовательской игры. Надеюсь, эта статья прольёт немного света на то, насколько может быть сложной разработка многопользовательской игры, и в следующий раз, играя в любимую онлайн-игру, вы достойно оцените всю эту работу.

Комментарии (0)

    Let's block ads! (Why?)

    среда, 7 сентября 2016 г.

    Как устроена MySQL-репликация

    Андрей Аксёнов


    Андрей Аксенов (Sphinx), shodan


    Мой доклад предназначен для тех людей, которые знают слово «репликация», даже знают, что в MySQL она есть, и, возможно, один раз ее настроили, 15 минут потратили и забыли. Больше про нее они не знают ничего.


    Мы немного пройдемся по теории, попытаемся объяснить, как это все работает внутри, а после этого вы с утроенными силами сможете сами нырнуть в документацию.


    Что такое репликация, в принципе? Это копирование изменений. У нас есть одна копия БД, мы хотим с какой-то целью еще одну копию.


    Репликация бывает разных видов. Разные оси сравнения:


    • степень синхронизации изменений (sync, async, semisync);
    • количество серверов записи (M/S, M/M);
    • формат изменений (statement-based (SBR), row-based (RBR), mixed);
    • теоретически, модель передачи изменений (push, pull).

    В докладе не будет:


    Все это есть в Интернете, синтаксис разбирать смысла нет.

    Забавный факт — если немного задуматься, репликация нам теоретически помогает из принципиальных соображений скейлить только чтение. Вот такой несколько неочевидный вывод. Это потому что, если у нас на одну и ту же копию данных надо налить определенное количество изменений, и эта определенная копия данных обслуживается одним и тем же сервером, то этот сервер способен выдержать определенное количество апдейтов в секунду, и больше туда не залить. Способен сервер обновить 1000 записей в секунду, а 2000 — не способен. Что изменится от того, что ты поставишь к этому серверу реплику, неважно, в режиме мастер-слэйв или мастер-мастер? Сумеешь ты на эту реплику налить вторую тысячу апдейтов? Правильный ответ — нет.


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


    Т.е. репликация — это больше про чтение.


    Про синхронизацию.


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


    Здесь нет рефрена «commit закончился успешно, что это значит?». Синхронный commit означает, что у нас локальный и удаленный (хотя бы на одной реплике) закончился, т.е. мы что-то закоммитили на машину, если у нас синхронный режим репликации, то эти изменения успешно закоммитились, они видны для последующих запросов на локальной машине, на удаленной машине (хотя бы на одной) тоже видны. Это означает, что если случилась стандартная внештатная ситуация, т.е. в один и серверов прилетел лом и пробил все насквозь — от процессора до самого винта, то, несмотря на это, данные не только скопированы на некий удаленный сервер, но еще, вдобавок, могут мгновенно, без каких-то дополнительных задержек, участвовать в последующих транзакциях.


    Это все общая терминология, никак совершенно не связанная с MySQL. В любой распределенной системе оно будет устроено так.


    Асинхронный commit — никаких дополнительных гарантий, как повезет.


    Полусинхронный commit — приятное промежуточное решение, это когда у нас локальный commit прошел, про удаленный commit ничего не известно — может, слэйв догнал, а, может, и не догнал, но, по меньшей мере, нам пришло подтверждение, что эти данные куда-то улетели и там приняты и, наверное, записались.


    Про сервера для записи. Какие бывают виды репликации.


    Master-slave classic, изменения все льются на один сервер, после этого копируются на массу реплик.


    Master-master true — когда изменения льются на кучу мастеров одновременно и каким-то образом с одного на другой, с другого на третий и между ними всеми, что порождает и ряд радостей, и ряд автоматических проблем. Понятно, что когда у тебя есть одна «золотая копия» и с нее несколько реплик, которые должны (в идеале — мгновенно) повторять эту «золотую копию», то все сравнительно просто с точки зрения того, как данные туда-сюда гонять и что делать на каждой конкретной копии. С master-master начинается интересная «головная боль», причем, подчеркиваю, не конкретно в случае MySQL, а сугубо теоретическая. Как же быть, если на двух нодах одновременно попытались прогнать одну и ту же транзакцию, которая меняет одни и те же данные, причем, меняет их, для простоты примера, по-разному. Понятно, что одновременно эти два изменения мы применить не можем. На момент, когда мы на одной ноде начинаем что-то изменять, на второй ноде еще пока ничего нет. Конфликт. Одну из транзакций придется откатывать. Вдобавок начинаются отдельные «пляски» со сверкой часов и т.п.


    Любопытный момент — даже вариант, когда у вас в конечном итоге все изменения со всех мастеров должны постепенно распространиться везде, все равно не поможет тому самому write bandwidth. Обидно, но вот так.


    Приятный вариант — под названием«Master-slave + routing запросов». Приятен он тем, что внутри программировать просто, у тебя есть одна основная копия, ты ее реплицируешь на кучу машин. Это намного проще, чем в мастер-мастер среде, когда все равноправны и т.д., но с точки зрения приложения все равно выглядит так, будто у тебя точек записи много. Ты приходишь на любую ноду, она знает, куда тебя зароутить, и успешно роутит. Ну, и чтения масштабируются — вот оно счастье репликации. Читать можно со всех точек все и всегда.


    Теперь ближе к базам данных, «волшебным» форматам statement-based, row-based и т.д. Про формат изменений.


    Что можно делать? Можно передавать сами запросы, а можно передавать только измененные строки. Подчеркиваю — пока мы еще не нырнули в дебри MySQL, этим может заниматься любая СУБД, в которой есть запросы, порождающие большое (или не очень) количество изменений, т.е. обновляющие много данных. Возникает вопрос — а что конкретно будем копировать? Можно сами запросы туда-сюда между нодами гонять, а можно гонять только измененные данные. Интересно, что и так и эдак очень плохо! Можно еще пытаться смешивать.


    Еще один пункт про то, какие бывают репликации. Про модель распространения. Наверное, где-то до сих пор еще не полностью вымерла модель Push-based, когда та нода, которая внесла изменения, та и обязана их рассылать по всем остальным нодам. С точки зрения программирования и отслеживания state'ов это та еще морока. Поэтому рулит Pull-based. Забирать апдейты с той или иной ноды — это намного проще запрограммировать, чем на одной ноде следить за хаотичным кластером своих реплик.


    Некие общие термины ввели. Переходим к тому, как сделали в MySQL.


    MySQL, сам по себе, это некий обман. Есть логический слой под названием MySQL, который занимается всяким общими и изолированными от хранения данных делами — сеть, оптимизатор, кэши и т.д. Конкретный физический слой, который отвечает за хранение данных, лежит на этаж ниже. Есть несколько встроенных, есть ставящиеся плагинами. Но даже встроенные MyISAM, InnoDB и т.д. живут на физическом слое. Плагинная архитектура — это клево, можно подцепить новый движок, но мгновенно возникает некая неоптимальность. В принципе, транзакционные write-ahead log'и (WAL), которые физический слой хранения все равно пишет, было бы хорошо использовать для репликации, и если система знает о том, что есть некий физический уровень, или достаточно хорошо сопряжена с этим физическим уровнем, то можно было бы отдельный лог на логическом уровне не писать, а использовать тот же самый WAL. Но у MySQL это невозможно концептуально, либо, если поменять интерфейс в PSE так, чтобы стало возможно концептуально, то будет очень много работы.


    Репликация реализована на уровне самого MySQL. В этом есть и хорошее — помимо одного лога в виде глубоко внутренних данных движка хранения, есть более-менее логический лог, возможно, на уровне statement'ов, который ведется отдельно от этого движка. А это «лишняя» безопасность и т.д. плюс, поскольку никаких ограничений внутри нет, можно делать всякий креатив типа подмены движка «на лету».


    В веденных терминах в MySQL 4.1 было реализовано: master-slave, pull-based, строго async и строго SBR. Если вы застряли в древней эпохе 4.х, то, наверное, у вас все плохо. Версиям 5.х уже чуть ли не 10 лет — пора бы и обновиться.


    Забавно прослеживать по версиям, как люди наступали на всяческие грабли и, когда сделать уже ничего было нельзя, прикручивали к этим граблям новые грабли, чтобы жизнь была не такая болезненная. Так, в версии 5.1 прикрутили RBR, чтобы компенсировать неизбежные проблемы с SBR, и прикрутили mixed режим. В версии 5.6 прикрутили еще приятных штук: semi-sync, delayed slave, GTID.


    Еще один момент. Поскольку MySQL — это некий общий слой, с одной стороны, и куча pluggable движков, с другой стороны, в том числе, встроенных, там есть с определенного момента божественный NDB cluster, про который рассказывают крутое. Там полностью синхронная мастер-мастер репликация, очень доступная in-memory БД… Но есть один нюанс — как только начинаешь искать людей, которые в продакшене используют NDB cluster, то таких людей находится крайне мало.


    Чем занимается мастер в тот момент, когда вы решили включить репликацию? На мастере происходит довольно мало дополнительных движений. Как обычно, мы по сети принимаем запросы, парсим их, гоняем транзакции, фиксируем их и т.д. Вдобавок к этому, на логическом уровне MySQL мастер начинает вести binary log — файл, не совсем текстовый, в который сыплются все подряд изменения. Также мастер умеет рассылать эти логи по сети. Все это очень просто и, вроде как, работает.


    Чем занимается слэйв? Изменения на слэйв лучше не слать, потому что можно попасть в непонятное. У слэйва чуть больше работы. Помимо того, чтобы вести один дополнительный лог и по запросу его рассылать, еще есть тред, который ходит к удаленному мастеру, возможно, даже не к одному, и качает оттуда binary log'и. Решение «давайте ходить к нескольким удаленным мастерам и с них качать разные логи» неоднозначно. С одной стороны неплохо, а с другой получается мгновенное расхождение. Просто физически копировать файлы по SCP нельзя, уже получается на сервере один лог, в нем свои позиции, локально мы их по сетке тянем, складываем в отдельный лог, еще отдельный тред бегает и пытается проигрывать эти локальные логи. Самое адское, на мой взгляд, заключается в том, что вплоть до версии 5.6 идентификация той или иной транзакции в логе происходила по имени файла и позиции на мастере. Интересное решение.


    Вот путь записи, который простенький insert проходит без репликации:


    Приложение сконнектилось к серверу, положило в таблицу и отбой.


    С репликацией получается несколько дополнительных шагов:


    Приложение-писатель точно так же идет к мастеру, но вдобавок эти данные попадают в том или ином виде в binary log, потом качаются по сети в relay log, потом из relay log'а постепенно реплеются (если нам повезло, и слэйв не лагает, реплеются сразу) в таблицу на слэйве, после этого все доступно в читателе.


    Что конкретно попадает в binary log, зависит от настроек SBR/RBR/mixed. Откуда это все растет? Представим себя базой данных. Нам прилетел простой запрос «обнови одну конкретную запись» — UPDATE users SET x=123 WHERE id=456


    Что записать в binary log? В принципе, все равно, на самом деле. Можем коротенький запрос записать, либо (а он обновил одну запись) можем записать изменение каким-то образом в том или ином формате.


    Другая ситуация. Представим, что нам прилетел тот самый запрос, который сам по себе маленький, а данных меняет много — UPDATE users SET bonus=bonus+100


    Тут эффективный вариант один — писать сам запрос, потому что запрос — ровно 32 байта, а записей он может обновить произвольное количество — 1000, 100 000, 1 000 000, сколько угодно… Неэффективно писать измененные записи в лог.


    А что произойдет, если мы в лог поместим такой нехитрый запрос «давайте отключим всех юзеров, которые не логинились давно» — UPDATE users SET disabled=1 WHERE last_login < UNIX_TIMESTAMP(NOW())-100*86400


    Внезапно наступает ужас. Проблема в том, что если среплицировать идеально сам запрос, то, во-первых, время никогда не синхронно между двумя нодами, кроме этого, за счет того, что путь записи такой длинный, в момент реплея этот «NOW» разойдется-таки. Реплика внезапно расходится с мастером, и все последующие изменения, формально говоря, уже небезопасны, могут привести к чему угодно.


    Вообще говоря, для таких запросов, вне зависимости от количества измененных данных, в идеале надо бы копировать сами строчки. В данном конкретном случае можно сами строчки не копировать, а зафиксировать константу и в лог написать не «NOW», а конкретный timestamp, который был использован мастером на момент репликации.


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


    • мастер многопоточен, а слэйв — нет. Понятно, что если мастер наливает нагрузку в четыре ядра, слэйв эту нагрузку в одно ядро наливать не успевает. Все довольно плохо;
    • состояние слэйва определяется именем позиции в файле мастера. Вдумайтесь — состояние одной ноды в кластере определяется именем файла и позицией в этом файле на другой ноде кластера, с которой может по любым причинам произойти что угодно!
    • «спасительный» RBR. Оказывается, по умолчанию туда пишутся полные before/after row image, т.е. мы изменили одну колонку в пяти-килобайтной строке, оп! — 10 Кб трафика и байтов 20-40 оверхедов на эту строку, потом оп! — едет такая жирная строка предыдущей версии, оп! — едет после этого версия с новыми значениями. Администраторы воют хором! Тем не менее, это просто офигенно с точки зрения некоторых извращенных приложений, например, внешних читалок, которые пытаются подцепиться к серверу MySQL, с него вытягивать данные и делать с ними что-нибудь, например, совать их в полнотекстовый индекс. Насколько это плохо с точки зрения администрирования базы, в которой одно изменение на три байта порождает 10 Кб трафика на винте, а потом 10 Кб трафика по сети на каждого слэйва, настолько же это хорошо для всяких систем типа полнотекстового поиска, как Sphinx, у которых нет локальной копии данных, а MySQL с нуля имплементировать нет никакого желания. В MySQL 5.6 спохватились и сделали binlog_row_image (но по дефолту full, а не minimal или noblob).

    Короче говоря, устроено все не хитро — палка, веревка, один лог, второй лог. И даже в этом логе «детские» болезни довольно забавные:


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


    • прежде всего, не верим дефолтам;
    • внимательно смотрим на настройки, думаем, чего хотим — SBR, RBR и т.д.

    И лучше сразу настроить, чтобы потом не разбирать странный фарш.


    В ситуации «протух лог, разошлась позиция, неизвестно, что происходит» есть определенный инструментарий — смотрим event'ы, пытаемся понять, какая транзакция уже проскочила, какая — нет, можно ли все это дело спасти или восстановить и т.д. Если GTID«ы заранее сумели включить, то жизнь становится проще.


    Другой момент наблюдения за репликацией. Интересно посмотреть, как внутреннее кривое устройство провоцирует не то, что конкуренцию, а создание дополнительных продуктов. „Волшебный“ Tungsten Replicator, говорят, хорошо решает задачу под названием „однопоточный слэйв — это плохо“, а если бы не врожденные сложности, не было бы дополнительного продукта, который позволяет пользоваться этим механизмом, переливать данные в другие системы, с одной стороны, и заодно решать ряд проблем, встроенных в существующую систему, с другой стороны.


    Как обычно, советовать невозможно. Кому-то помогает, кто-то будет сильно плеваться. Но, говорят, есть ситуации, в которых с неизбежным однопоточным лагом хорошо справляется Tungsten. Я уверен, есть еще всякие увлекательные фокусы, но внутренний однопоточный слэйв — это тяжело.


    Что делать, если вы зачем-то использовали реплики как бэкап? Я считаю, надо биться головой об стену, потому что реплика и бэкап — это две разные штуки. Тем не менее, если вы креативные пацаны и используете достаточно новую версию, delayed replication вас спасает, с одной стороны, но с другой стороны, если вы не делаете полноценных бэкапов, вас все равно ничего не спасет.


    Далее еще один элемент креатива. Нетрудно представить ситуацию, когда мастер забил логами весь 10 PB облачный диск или забил рассылкой этих логов всю сеть, при этом 90% этих обновлений нам не нужны, потому что нам интересно реплицировать, например, одну таблицу прицельно или одну базу прицельно, а по умолчанию все валится валом в бинарный лог — все изменения по всем базам, по всем таблицам, по всему. Решение опять поражает своей креативностью. С одной стороны, есть четыре настройки — {binlog|replicate}_{do|ignore}_db, которые позволяют фильтровать на мастере — что запишется в лог, а что проигнорируется. На слэйве, соответственно, позволяет делать то же самое. Т.е. на мастере мы можем отфильтровать то, что попадает в binary log — в эту воронку, которая потом сливается в сеть, а на слэйве, соответственно, мы можем поставить входящий фильтр на то, что прилетает из сети. Или писать на диск только часть данных, а потом на слэйве реплеить, опять же, только часть данных. Внезапно даже в этой нехитрой истории наступает ужас, потому что комбинация — используем одну БД, а апдейтим таблицу в другой БД через интересный синтаксис — она ведет себя как-то… А как конкретно она себя поведет — неизвестно, т.к. разные фильтры срабатывают в разные моменты.


    Встроенных приятных штук под названием „перевыборы мастера, если он внезапно сдох“ нет, надо поднимать руками. Отсутствие инструментов для менеджмента кластера — это, по моему мнению, хорошо — порождает конкуренцию, порождает создание дополнительных продуктов. В самом деле, если бы в обычном MySQL идеально работала очень клевая мастер-мастер репликация, или хотя бы автоматическое поднятие после сбоев, то зачем бы была нужна всякая Galera, Рercona/MariaDB Cluster и т.д.?


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


    Конфигурация №1. Мастер-мастер «на коленке» в стиле MySQL делается вот так:


    Что пугает — сколько в мире идиотов! Погуглите „Мастер-мастер MySQL репликация“ — каждая вторая ссылка вот такая. Ад и холокост.


    Фокус №2 — catch-all slave — поприятнее. Никаких ненужных проверок нет — что с кого прилетает, кому попадает, и что с этим делать. За счет этого можно сделать забавные штуки типа слэйва, на который либо прицельно сливается часть данных с кучи серверов, либо прицельно сливаются все данные со всех серверов — сервер со всеми-всеми бэкапами. Но, повторюсь, репликация есть, т.е. есть некий базовый инструмент, который копирует таблицу А вместо В и все.


    Ну и, наконец, фокус №3 — подменяем всякое. Вспоминаем, что репликация живет на логическом уровне, никак не связанном с физическим уровнем хранения. За счет этого можно крайне интересно чудить. Можно менять движок «на лету» с непонятными целями — вот true story, что, дескать, репликация из InnoDB баз в MyISAM таблицы просто ради того, чтобы полнотекстовый поиск работал хоть как-то. Есть креативный финт под названием „изменение схемы через репликацию“. В чем жир, понимать отказываюсь, но бывают и такие фокусы. Ну и, есть понятный и интересный режим работы под названием „параноидальный апгрейд версии через репликацию“.


    В ходе доклада мы узнали:


    Тем не менее, с этим адом можно жить, если хотя бы примерно понимать, как он устроен.


    Основной посыл в том, что:


    В 2015 году на конференции HighLoad++ Junior Андрей Аксёнов прочитал новую версию своего доклада об устройстве репликации в MySQL. Её мы тоже расшифровали и опубликовали в своём блоге.

    Комментарии (0)

      Let's block ads! (Why?)

      “Зонтичный” мониторинг: перископ для бизнеса

      Решение HPE Operations Bridge в представлении не нуждается. Множество компаний используют его в качестве эффективного инструмента для обработки событийной информации, которая поступает от отдельных компонентов ИТ-инфраструктуры. Однако это решение может быть масштабировано для инфраструктуры в целом — подобно «зонтику», собирающему и анализирующему данные со всех систем, находящихся в пределах его охвата.  



      Для чего же нужен «зонтичный» мониторинг? Если поинтересоваться историей развития информационных технологий, можно увидеть, что разрозненные средства мониторинга появились как ответ на потребности администраторов тех или иных систем. С появлением новых типов ИТ-решений возникала необходимость отслеживать их состояние и работоспособность. Администраторам были необходимы всё более эффективные средства контроля, поэтому утилиты для мониторинга постоянно совершенствовались. А затем и руководители компаний стали отчетливо осознавать, насколько важна роль информационных технологий в организации их бизнеса. Соответственно, у них тоже появилась потребность в инструментах, позволяющих увидеть и оценить не только то, в каком состоянии находятся ИТ-ресурсы в текущий момент времени, но и как их можно будет использовать для реализации новых задач — открытия офисов, разработки продуктов, вывода услуг и т.д.

      Представителям бизнеса нужна в первую очередь предсказуемость: понимание того, какие ИТ-активы имеются в их распоряжении, в какой степени их сотрудники обладают необходимыми знаниями, насколько выгодно сейчас компании с практической точки зрения инвестировать в современные технологии и наращивать компетенции. Когда появились такие вопросы, стало понятно, что отдельные утилиты для мониторинга, о которых мы говорили выше, не помогут дать исчерпывающие ответы. Дополнительные скрипты и надстройки верхнего уровня над техническими утилитами администрирования тоже не позволяли справиться с этой проблемой. Поэтому разработчики начали постепенно наращивать функциональность утилит, добавлять возможности корреляции, выявления и обогащения информации, анализа статистических данных — вплоть до выявления неизвестных проблем и выяснения степени влияния ИТ-метрик на бизнес-метрики. Однако одновременно надо было решить и ещё одну глобальную задачу: интегрировать и консолидировать все разрозненные средства мониторинга, которые уже применялись в компаниях на протяжении многих лет.

      В результате поиска способа решения этой задачи и появились системы «зонтичного» мониторинга ИТ-инфраструктуры, например HPE Operations Bridge, которая, по сути, представляет собой набор компонентов. Если заказчик заинтересован в чётком понимании ситуации и снижении фактора неопределённости для принятия управленческих решений, значит, он уже вполне «дорос» до внедрения «зонтичного» мониторинга и выбора соответствующего решения.  

      Решение HPE Operation Bridge включает в себя несколько ключевых компонентов, или модулей. Остановимся на них поподробнее. Самое главное, что нужно сделать для запуска процесса мониторинга, — собрать информацию. Поэтому сначала расскажем о компоненте, задача которого состоит в консолидации информации, поступающей от различных источников, — Operations Manager I (OMI). В базовом варианте он выполняет свое главное предназначение — консолидирует событийную информацию, приоритизирует, автоматически распределяет по группам и назначает ответственных лиц. Существует множество дополнений к OMI. Одно из них отвечает за обеспечение корреляции. Поддерживается два типа корреляции: поточная (ожидание определенных событий) и топологическая (распространение события по всей топологии инфраструктуры; работает на основе CMDB). Топологическая носит динамический характер, поэтому меняется автоматически, если, например, физическое оборудование или виртуальную среду перемещают из одного хоста в другой. Кроме того, предприятие может создавать собственные корреляционные схемы отдельных участков ИТ-инфраструктуры, а при обнаружении аналогичных участков в целевой сервисно-ресурсной модели эта схема будет автоматически распространяться и на них: на основе их взаимосвязей будет выстроена общая корреляционная схема. Это может быть очень полезно, если у компании имеется разветвлённая территориально-распределённая сеть филиалов, каждый из которых предоставляет одинаковые услуги или продукты. Если схема меняется в одном месте, корреляционные связи будут автоматически перестроены.

      Добавление новых элементов в ИТ-инфраструктуру, находящуюся под «зонтиком», должно осуществляться автоматически, это упростит процесс администрирования. Поэтому следующий модуль HPE Operation Bridge — Monitoring Automation — отвечает за автоматизацию настроек мониторинга и аудита. С его помощью новый сервер или другое оборудование, а также виртуальная среда, добавленная в ИТ-инфраструктуру, смогут автоматически подключиться к периметру мониторинга сервисно-ресурсной модели, после чего на неё будут распространены заданные политики по мониторингу. Таким образом сокращается объём рутинных операций по настройке мониторинга, а предприятие получает полную картину всей схемы мониторинга ИТ-ресурсов. В случае изменения роли сервера в сервисно-ресурсной модели от ненужных политик отказываются и подключают новые. В итоге снижается количество ложных сообщений мониторинга.

      Неотъемлемым элементом мониторинга ИТ-инфраструктуры является формирование отчётности. За это отвечает модуль Operations Bridge Reporter. Он даёт возможность получать сквозную отчётность по всей услуге — начиная от состояния бизнес-процесса до каждого компонента ИТ-инфраструктуры, обеспечивающей его работу. Заказчик может настроить форму предоставления этих отчётов в соответствии со своими предпочтениями. В основе Operations Bridge Reporter лежит СУБД Vertica, специально предназначенная для обработки Больших данных. Таким образом, руководитель компании может самостоятельно определить степень загруженности оборудования и принять решение, стоит ли пополнить парк устройств в случае увеличения объемов продаж или расширения услуг либо достаточно существующих ресурсов. Подобную отчётность можно получать не только по оборудованию, но и по приложениям, сетевой нагрузке и т.д.  

      Визуализация данных не менее важна, чем гибкая отчётность. За нее также отвечает специальный модуль — Business Value Dashboard. Он позволяет создавать информационные панели, на которые можно выводить финансовые, технические и иные материалы. Все они визуализируются и подаются в формате, наиболее подходящем для конкретного пользователя, будь это генеральный директор или рядовой сотрудник. Модуль выпущен всего два года назад, но уже есть интересные сценарии его применения. Так, европейская компания WindPark, специализирующаяся на ветряной энергетике, использует HP Operation Bridge для мониторинга работы ветряных генераторов энергии. Визуализированные данные о силе ветра, а также о количестве вырабатываемой электроэнергии компания получает посредством Business Value Dashboard. Дело в том, что HPE Operation Bridge можно использовать для мониторинга не только ИТ-ресурсов, но и производственных и инженерных систем. Разумеется, она не заменит специальные решения для контроля за инженерными системами, но успешно сопоставит информацию об их работе с данными в бизнес-приложениях.

      Иногда на предприятиях происходят различные инциденты, связанные с работой ИТ-инфраструктуры, которые требуют расследования и выявления причин их возникновения. В таком случае на помощь придет еще один компонент в составе HPE Operation Bridge — Operations Analytics, отвечающий за выявление нештатных ситуаций. Его можно условно назвать «машиной времени», поскольку, используя поступившую в систему информацию, он анализирует её с точки зрения взаимосвязей и выявляет скрытые тренды. К примеру, при неполадках в системе этот модуль способен проанализировать лог-файл, найти данные, свидетельствующие о причинах сбоя, и предоставить визуализированную информацию о взаимосвязи событий, повлекших неисправности. Подобная функция очень полезна при разборе инцидентов, поскольку позволяет определить, что стало первопричиной «падения» системы. Для этого достаточно откатить «бегунок» на информационной панели назад, до начала цепочки неисправностей. Модуль Operations Analytics использует СУБД Vertica, поэтому он может работать с Большими данными.

      Как известно, системные администраторы обязаны регулярно проверять работоспособность ИТ-ресурсов, за которые они несут ответственность. Модуль Operations Orchestration позволяет автоматизировать рутинные операции по проверке информационных систем, он способен полностью заменить ручную проверку или самописные скрипты.

      Помимо перечисленных базовых модулей, HPE Operation Bridge включает в себя еще несколько опциональных дополнений, отвечающих за управление мощностями, сетевой мониторинг, организацию сбора данных с помощью агентов или коллекторов. Последнее необходимо в тех случаях, когда для мониторинга всех имеющихся ИТ-ресурсов заказчик использует только решения компании Hewlett Packard Enterprise. Если на нижнем уровне работают системы для ИТ-мониторинга от сторонних производителей (Zabbix, IBM Tivoli и другие), а HPE Operation Bridge применяется в качестве «зонтика», установка коллекторов не нужна. Дело в том, что HPE Operation Bridge получает из внешних систем, как минимум, топологию, события и метрики производительности.

      Когда HPE Operation Bridge внедряется с нуля, самый оптимальный вариант — использование комплексного решения, обеспечивающего как сбор данных, так и аналитику. В дальнейшем это позволит компании существенно сэкономить, в том числе на обучении, а также снизить риски. По мере развития бизнеса такое решение можно наращивать. Более сложной является задача развертывания подобной системы в организации, где отдельные утилиты для мониторинга ИТ-ресурсов уже были внедрены. Поскольку сотрудники выработали определенные привычки, придется столкнуться с лоббированием интересов, разного рода опасениями или нежеланием перехода на другие решения. В таких случаях успеха можно добиться не с помощью приказа «сверху», а путем мотивирования каждого сотрудника к переходу на новые технологии. В этой связи полезно сравнить возможности и удобство старых и новых инструментов.

      Раньше нередко говорили о том, что бизнес-руководителю нужен «светофор», то есть максимально простое решение. Это не совсем так. Топ-менеджеру необходимо знать несколько вещей: где возникла проблема, кто за неё несет ответственность, работают ли уже над ее устранением и когда будет восстановлена услуга или бизнес-процесс. Все эти задачи поможет решить «зонтичный» мониторинг.

      Комментарии (0)

        Let's block ads! (Why?)

        Аналитика для геймдизайнеров и продюсеров. Часть I

        [unable to retrieve full-text content]

        Зачем нужна аналитика геймдизайнерам, продюсерам и другим сотрудникам, отвечающим за продуктовую составляющую, качество игры и её контент, за успешность на рынке и бизнес-показатели? В этой статье я расскажу, какие показатели статистики принято анализировать для того, чтобы предотвратить возможные проблемы и повысить выручку.


        Читать дальше →

        Не все языки программирования одинаково полезны

        [unable to retrieve full-text content]



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

        По мнению «Википедии», язык программирования — формальная знаковая система, предназначенная для записи компьютерных программ. Язык программирования определяет набор лексических, синтаксических и семантических правил, определяющих внешний вид программы и действия, которые выполнит исполнитель под её управлением.

        Какие-то знаковые системы проще для освоения, какие-то сложнее. Однако среди них есть и весьма причудливые. А есть настолько сложные, что осваивают их только самые хардкорные разработчики. Читать дальше →

        понедельник, 5 сентября 2016 г.

        Что делать с чужими долгами?

        Один из аспектов профессии разработчика — посвящение профанов в особенности процесса разработки ПО.
        С. Макконнелл, Совершенный код

        Цель этой публикации — поделиться опытом работы над проектом со сложной историей и тяжёлым наследием. После ухода из очередного т.н. «стартапа», я решил что хочу попробовать новых ощущений: enterprise, legacy, etc. Для этого взялся за работу над корпоративным приложением для транснационального концерна. Разработка на тот момент шла уже третий год, приложение пережило несколько поколений разработчиков, но стабильного релиза так и не было.

        Полагаю публикация будет полезной:

        • разработчикам принимающим аналогичное решение, чтобы взвесить за и против
        • менеджерам «непростых» проектов, чтобы лучше понять причины и следствия технических проблем
        • и, конечно, просто любопытствующим

        Затрагиваемые в статье вопросы:
        • Низкая компетенция разработчиков, и что с этим можно поделать?
        • Какие аргументы убедительны в глазах заказчика для нефункциональных изменений в проекте?
        • Почему работа аналитиков и QA очень важна с точки зрения разработки в частности и для проекта в целом?


        Input


        Что на входе? На собеседовании первого этапа я был заинтригован следующими подробностями:
        • Используемая версия фреймворка за годы разработки успела устареть
        • Релиза стабильной версии продукта за два года не состоялось
        • Бизнес-сложность системы порой зашкаливает

        На собеседовании второго этапа, с участием проектного менеджера и ведущего разработчика, я получил следующую порцию нюансов:
        • У текущей команды разработки низкий уровень компетенции в используемых фреймворке, языке, тестировании, IoC, объектно-ориентированном дизайне, шаблонах проектирования и архитектуре
        • Предметная область действительно непроста
        • Заказчик убеждён что проблем нет

        К моменту принятия положительного решения об участии в проекте, я не видел ни одного хорошего или надёжного места в нём: абсолютно всё, что мне удалось узнать, являлось признаками проблем. Именно это и подтолкнуло меня к положительному ответу =)

        Резюмируя положение дел на старте, можно выделить следующие балластные категории, которые представляли риск для успеха дальнейшей разработки:

        • Низкая компетенция разработчиков
        • Низкое качество кода (вытекает из предыдущего пункта), большой технический долг
        • Отсутствие налаженной инфраструктуры
        • Отсутствие автоматизированного тестирования
        • Отсутствие культуры приёмочного тестирования
        • Отсутствие культуры версионирования, внесения изменений, деплоя
        • Требования не стандартизованы
        • Менеджерский долг перед заказчиком — сокрытие имеющихся на стороне команды исполнителя проблем, преувеличение успехов, новые обещания к старым долгам

        Общий посыл, который бы я рекомендовал разработчикам, претендующим на роль «спасателя» (в должности сеньора, ведущего разработчика, техлида и т.п.): не врать и не бояться. Если вы застали проект в плачевном состоянии, это не значит, что люди, работавшие в нём до вас, считают так же. Во-первых: текущее состояние проекта во многом плод их усилий, людям психологически сложно воспринимать критику результата своего труда. Во-вторых: скорее всего, уровень команды, допустившей имеющиеся проблемы, не позволяет их разглядеть — для них объективно проблем не существует. В описываемом случае, банальные толстые контроллеры — содержащие всю бизнес-логику — считались приемлемыми. Отсутствие юнит-тестов — не воспринимается как что-то ужасное, если человек просто не знаком с модульным тестированием.

        В подобной ситуации вам неизбежно придётся насаждать свои, лучшие практики в команде. Для этого есть два пути: эволюционный — обучить (в первую очередь собственным примером) имеющиеся кадры. Революционный — искать более компетентных исполнителей, а значит планомерно заменять команду.

        Начинаем с тестов


        Одним из первых моих мероприятий было подключение тестового фреймворка, помощь коллегам в запуске и написании тестов. Эволюция, конечно более гуманный вариант — не надо никого «сливать», тратить время и деньги на подбор более опытных (и вероятно — дорогих) специалистов. И кармически это гораздо полезней — делиться знанием. Но этот долгий путь. Если разработчик, достаточно зрелый, чтобы считать себя готовым к разработке корпоративных приложений за деньги, не освоил тестирование, что-то в его кривой профессионального роста пошло не так. Драма в том, что скорее всего, не выдержав новых требований, многие предпочтут найти новое место работы. Человеку свойственно идти по пути наименьшего сопротивления. В этом случае попытка обучить — пустая трата своего и чужого времени.

        Если у вас нет в запасе лишнего человека-года, что более чем вероятно, для коммерческой разработки, то шансов плавно эволюционировать у малоопытной команды мало. Лучше сразу готовиться формировать новую команду, предъявляя к соискателям требования выше, чем было до этого. Да, увеличивая таким образом бюджет. И это не раздувание бюджета, это компенсация долга, созданного в прошлом менеджером: может в попытке сэкомить, может из-за недостатка опыта.

        Перечисление технологических изменений, которые предстояло произвести, я начал с модульных тестов. Модульные тесты одновременно самая дешёвая во внедрении и сопровождении возможность для поддержания вашего проекта в порядке. И в то же время одна из самых фундаментальных. Начинать с тестов — хорошая привычка. С тестов можно начинать кодирование, использование незнакомой библиотеки или языка, постановку задачи, проверку реализации чего бы-то ни было. Тесты — это способ мышления, при котором вы, прежде чем сделать что-то, формально описываете результат. Если отточить этот навык до должного уровня, то получение любого результата не будет у вас вызывать затруднений, вы забудете про страх белого листа. И это касается не только программирования.

        SVN -> Hg -> Git


        Исторически на проекте использовался SVN. Мержи были делом сложным и ответственным, сопровождавшимся сакраментальным «не пуште пока в транк, сегодня деплоим». Коллеги по департаменту в других проектах использовали Mercurial. Около месяца мы пробовали эту систему контроля версий, но в итоге предпочли переход на хорошо знакомый и популярный Git. Как и ожидалось, большинство соискателей, при формировании новой команды, чаще оказывались знакомы с Git, нежели с двумя другими СКВ. Чем не повод для перехода?

        CI & CD


        Windows Server -> Ubuntu
        Remote Desktop + manual update -> delpoy.sh -> Unix + Docker + TeamCity


        Копии приложения для демонстраций и тестирования находились на Windows Server. Управление сервером и обновление приложений осуществлялось вручную, путём подключения к удалённому рабочему столу. Примерно пол-года мне понадобилось на убеждение менеджера, и, через него, заказчика, что обязательным предусловием выпуска в продакшн, должен стать перевод инфраструктуры на Unix. Параллельно с этим формальным обоснованием, в процессе поиска второго бэкенд-разработчика, я смотрел в сторону кандидатов владеющих администрированием LAMP стека. К счастью, нам удалось найти специалиста с хорошими навыками в Bash и Unix: в итоге он стал в команде на 50% разработчиком и на 50% билд- и интеграционным инженером. К выходу в продакшн у нас был полноценный CI и CD. Привет Rottenwood!

        Это мероприятие, как прочие, не чисто техническое решение. Методологии и концепции разработки влияют на другие процессы, не забывайте об этом. Если менеджер привык руководить командой, для которой подготовка релиза сводиться к «**як-**як и в продакшн!», недостаточно настроить агент в TeamCity. Вам придётся донести до менеджера осознание того, что «нельзя просто взять и «пофиксить» это за пять минут на проде до демо». Да, это будет на первых порах доставлять дискомфорт. Менеджеру понадобиться месяц или больше, чтобы привыкнуть что деплой теперь происходит не по пинку, мгновенно, вместе с падением рабочего продакшена. Теперь это осознанная процедура, которая пусть занимает 10 минут, но гарантировано ничего не уронит, и даст предсказуемый результат на любом из серверов, сколько бы их у вас не было.

        Для заказчика аргументами необходимости выделение на это ресурсов и времени послужили:

        • Снижение простоя в экстренных случаях — благодаря Docker мы можем оперативно развернуться на любой виртуальной машине в любом датацентре.
        • Мы можем поставить приложение вместе с образом и оно может быть запущено на любой машине (такой кейс тоже рассматривался) без участия со стороны команды разработки.
        • Безопасность — изолированность контейнера с приложением от хост-машины, простота и надёжность Unix для сервера. Часть пунктов при прохождении приложением корпоративного security-аудита автоматически закрывалась.
        • Родная среда для используемого приложением стека, шире набор применимых технологий для потенциальных фич. Большая лояльность разработчиков.
        • Ощутимый прирост производительности без дополнительной конфигурации.

        Eclipse / NetBeans / Trial WebStorm / Brackets -> PhpStorm


        Другим важным мероприятием стала организация приобретения для команды лицензии на PhpStorm и настройка единого стиля форматирования в соответствии с PSR. Я считаю любая организация может (и должна) обеспечить работников нормальными орудиями труда. PhpStorm и WebStorm сейчас лидирующие, на мой взгляд, IDE на рынке по поддержке PHP / JavaScript / TypeScript. Хорошая IDE существенно способна повысить как личную эффективность программиста, так и команды в целом — легко внедрить посредством настроек единый code style и разные полезные «примочки» для работы над проектом.

        Devprom + Excel + *.jpg-> Jira


        Этот переход, пожалуй оказался самым эпичным и долгожданным для нас. Исторически, использовался Devprom. Если в двух словах: никому не рекомендую эту систему. Для меня было откровением, что платное ПО может быть настолько низкого качества! Случайным образом система могла зависать, падать, содержала откровенные уязвимости. Каждый апдейт, помимо патча нескольких SQL-инъекций (и добавления новых, судя по частоте обновлений) привносил новшества в расположение элементов GUI, так что привычные уже сценарии использования приходилось осваивать заново.

        Поскольку система управления проектом — краеугольный камень процесса планирования и разработки, использование «забагованного» и нестабильного решения сказывалось на всех остальных процессах: люди ленились заводить баги в трекере (это было сложно и долго), при планировании наполнение бэклога итерации превращалось в пытку на целый день, задачи терялись, инструменты аналитики и оценки было проще не использовать, менеджеры стремились при любой возможности протолкнуть в работу что-то в обход трекера. Таким образом используемый инструмент явно наносил вред нашим процессам, согласитесь это ужасно.

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

        • Работа по картинкам — после тестирования на выходе имеем папку со скриншотами, названными в соответствии с приоритетами. Разработчики весело хватают понравившиеся картинки и «фиксят» их, перекладывая картинки в папочку Done. За попытку создать на каждую картинку задачу в трекере можно было получить по рукам.
        • Email-driven managment: в рассылке между членами команды на протяжении от нескольких дней до недели ходит письмо в таблицей высокоуровневых «фичей». Инициатива использовать доску трекера для оперативного управления разработкой — наказуема.
        • Excel planning — составление бэклога и оценка при помощи легендарного табличного процессора.

        Трекер задач — не менее важен в разработке чем IDE. Это краеугольный камень процесса разработки: в нём должна начинаться и заканчиваться любая активность в проекте. Нет задачи — нет кода. Jira, возможно лучшее из решений для коммерческих организаций на рынке.

        PM-level


        Если разработчик осуществляет оперативное управление в команде, от его решений может зависеть многое. Его выбор определяет успех или провал реализации отдельных частей продукта. Конечно, это наиболее актуально для небольших команд: 2-4 разработчика, тестировщик, аналитик. Пропорционально увеличению количества разных специалистов — вводим архитекторов, администраторов, QA-отдел — надо полагать, степень персональной ответственности отдельного участника процесса снижается.
        Но не забывайте, что есть как минимум два фактора, которые вы, будучи техническим специалистом, на которые у вас нет прямого влияния. При этом от них прямо зависит сама возможность успеха проекта:
        • Общий бюджет проекта и, соответственно, штатное расписание. Нельзя написать конкурентно-способный аналог приложения, если у вас в штате на порядок меньше специалистов. Дальше прототипа, на энтузиазме ваш «стартап» не улетит.
        • Деятельность стоящих перед вами в пищевой цепочке — заказчика и бизнес-аналитика.

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

        Если менеджмент не готов прислушиваться к команде разработки, шансов на восстановление — нет. Вы не можете прямо влиять на решение вышестоящего, или сменить руководство проекта. Но вы должны сделать всё возможное чтобы вас услышали, если не хотите отвечать за чужие ошибки.

        Dev-level


        Если ваш опыт разработчика подсказывает что у проекта есть проблемы, например банальный технический долг, вы можете кинутся грудью на амбразуру рефакторинга… И погибнуть, поскольку остальная команда будет успевать «говнокодить» из своего пулемёта большее количество строк в минуту, чем вы сможете физически отрефакторить и покрыть тестами. Абстрагируйтесь от самой разработки, посмотрите на процесс с высоты птичьего полёта: обзоры кода, защищённые ветки и пул-реквесты, инструменты статистического анализа кода в вашем CI — есть множество инструментов позволяющих предотвратить распространение проблемы. Гораздо важнее устранить причину проблемы, устранение симптомов — дело вторичное. И не факт что у вас хватит времени на второе, с большинством legacy придётся жить ещё долго. Главное предотвратить метастазы.

        Иногда ошибка действительно в генах. Тогда лучший способ — отсечь некачественный генотип от вашей популяции и заменить его свежим образцом с необходимыми доминирующими признаками.

        После года рефакторинга, мне кажется что порой стоит оценивать разработчиков не большому по количеству принесённой пользы (функциональности + соответствующей кодовой базы), а по минимуму оставляемого вреда, в виде технического долга и неподдерживаемого, или не тестируемого кода. Конечно у вашего менеджера будет альтернативный взгляд на реальность. Но реальность разработки такова, что «запилить фичу» практически любой сложности для среднего разработчика не составляет труда. Гораздо важнее в средне- и долгосрочной перспективе чтобы это добавление не ухудшало архитектуру, сопровождалось не хрупкими и понятными тестами, было спроектировано с оглядкой на принципы SOLID. В этом отношении, я предпочту одно senior'a двум middle'ам и двух middle'ов четырём junior'ам. Чем длиннее дистанция, которую предстоит пройти вам и вашему продукту, тем важнее данный тезис.

        Увы, собрать достаточно сильную команду в приемлемые сроки почти никогда не удаётся, даже обладая необходимым бюджетом. Квалифицированных специалистов даже в самых распространённых технологиях и фреймворках катастрофически не хватает. Построение процесса разработки на промышленном уровне поможет вам компенсировать это. Используйте возможные утилиты и техники для анализа и контроля качества кода. Поставив за отлаженный конвейер среднего или начинающего разработчика вы получите более предсказуемый результат и планомерное развитие, чем дав волю «коммитить» в мастер энтузиасту с горящими глазами. Ваша задача должна состоять в организации стабильного процесса и облегчении встраивания в него участников, а не героической ликвидации последствий не менее героических лихих кустарных решений.

        Analytics


        Если вы видите, что бизнес-аналитик скидывает в разработку сырые требования — не включайте фантазию и не начинайте кодирование. Составьте список вопросов и всех конфликтных мест и отправьте ему письмом. Или обсудите вместе над распечаткой все сомнительные моменты. Кодирование же стоит начинать тогда, когда у вас на руках есть настолько определённые требования, в утверждённом аналитиком файле, что задачу по их кодированию можно делегировать любому разработчику. Я считаю что идеально поставленная задача по разработке, кроме ссылки на релевантные требования, не должна содержать никакой «бизнес-логики», в крайнем случае высокоуровневое описание используемых шаблонов проектирования, если назначаемый разработчик ещё не знаком детально с компонентами системы, где предполагается внесение изменений.

        Банальная истина, которой мы ежедневно забываем: люди склонны полагать свои мысли очевидными. Несмотря на средний высокий IQ в нашей отрасли, телепаты, увы, всегда в отпуске. Если вы войдёте в режим телепата и сделаете за аналитика его работу, то вы окажетесь виноваты в следующих грехах:

        1. С вероятностью близкой к единице, задача отнимет у вас времени больше, чем вы планировали. Ведь давая оценку, разработчик всегда озвучивает время на кодирование. Наиболее опытные из нас могут заложить риски отладки, тестирования, сопровождения документации и т.п. Но я сомневаюсь что даже лучшие из разработчиков способны точно прогнозировать время необходимое им на работу в качестве бизнес-аналитика. Аналитик не будет за вас программировать. А за превышенные сроки отвечать вам.
        2. Вы будете ответственны за все ваши фантазии, поскольку попадая в реализацию, они остаются не описанными в требованиях и не согласованными с заказчиком. Потом вам придётся долго убеждать всех, что это «не баг а фича», и в конечном итоге переписать этот код, с появлением реальных требований. Хорошо если вы же. Гораздо хуже наткнутся на недокументированное поведение и загадочный код с инициалами, владельца которых никто на проекте не помнит.
        3. Вы лишаете хорошего парня-аналитика возможности профессионального роста.
        4. Вы увеличиваете энтропию Вселенной, нивелируя выгоду от разделения труда.

        Результаты устных обсуждений и переписки должны фиксироваться в конечных файлах с требованиями. Разработайте пакет документов, необходимых для адекватного планирования разработки и планомерного кодирования. Определите, какие форматы нужны для описания той или иной части системы. К примеру — для каждой экранной формы — может быть востребован следующий набор документов:
        • Файл Visio с описанием функциональности, блок-схемами зависимостей между полями
        • Файл Excel с правилами валидации и описанием типов данных для каждого поля
        • Файл PSD для верстальщика, сопутствующие исходники (шрифты, иконки)

        Такой пакет будет востребован не только на этапе разработки но и на следующих: приёмочное тестирование и разработка пользовательской документации.

        Идеально было бы версионировать эти файлы параллельно с исходным кодом, чтобы понимать актуальное и требуемое состояние системы. К сожалению, с большинством сложных офисным форматов версионирование по аналогии с исходными кодами в Git не применимо.

        Бизнес-анализ — это процесс, который важно формализовать и отладить не меньше чем саму разработку. И поскольку в отношениях разработчик-аналитик вы являетесь своего рода потребителем, ваш долг помочь выстроить эти отношения к обоюдному удовольствию.

        Estimate it


        Если менеджер вынуждает вас давать оценку задачу, по которой не был проведён анализ, ставьте верхнюю из возможных. Я, например, для таких задач использую значения Фибоначчи 13, либо 21, в то время как для нормально спланированных задач максимальное значение 5. Таким образом вы явно отражаете сложность, которая на данном этапе не может быть оценена точнее.

        Другая крайность: установите минимальную оценку. Я использую 1, хотя многие оптимисты склонны давать обещания вроде «это можно сделать за 5 / 10 /15 минут». Да, безусловно есть правки, внесение которых займёт считанные минуты — не считая накладные расходы на взаимодействие с трекером, СКВ, документаций, тестами. Чтобы не огорчать менеджера тем, что «маленький» фикс занимает целый инженерный час, могу порекомендовать связанные мелкие правки объединять в одну задачу.

        QA


        Если вы получаете баг-репорт в виде «Починить форму на странице M» или одного скриншота с большим жирным знаком вопроса на безобидной, всем привычной странице, у вас вряд ли получиться исправить проблему. Формализуйте формат баг-репорта в соответствии с особенностями вашего приложения. Покажите и расскажите всем причастным к тестированию продукта как получить отладочную информацию необходимую для исправления, как формулировать. Не пытайтесь воспроизвести невоспроизводимое.

        Другой нюанс: если в команде нет культуры тестирования, менеджер может полагать что ручное тестирование продукта — дело разработчиков. Ваша миссия показать ему простую арифметику: час разработчика как правило в N-раз дороже часа «ручного» тестировщика. За несколько полных дней тестирования имеющимися в команде разработчиками, легко сжигается зарплата зарплата выделенного тестера. Не забываем о простое разработки.

        Тестирование — это процесс, который должен проходить планомерно и на регулярной основе, а не мероприятие вроде апрельского субботника. Если у вас в организации ещё нет QA, но вы знаете что это такое — приближайте его появление всеми доступными средствами. Пока нет квалифицированного приёмочного тестирования, команда разработки будет оказываться крайней во всех обнаруженных багах. Если тестирование носит нерегулярный характер, то баги будут находиться редко, зато много и не те. Это значит, что лавина аврально обнаруженных багов будет отравлять вам жизнь, и серьёзно вредить всему процессу разработки. В упомянутом проекте, выбивание штатной единицы для QA-специалиста и поиск подходящего кандидата у меня заняло около полутора лет.

        Какие риски несёт нерегулярное и плохо организованное тестирование:

        • Если тестирование проводиться редко, будет казаться что версия ужасно «забагована», кровь-кишки-чума, а-а-а всё пропало, всё бросаем, все бежим на пожар!!1 — это срывает запланированную разработку, порой полностью парализуя её. Риск сорвать следующий дедлайн.
        • Единовременное обнаружение большого количества багов, субъективно будет восприниматься, как то что их критически много в (казалось бы) готовой версии, что подрывает ваш авторитет разработчика.
        • В случае с ручным, эпизодическим, слабо организованным тестированием баг-репорты будут низкого качества — а значит трудно-воспроизводимы, неактуальны, дублированы. Разработчики будут тратить дополнительные усилия и время на попытки их воспроизведение. По моему опыту могу сказать, что, зачастую, воспроизведение бага занимает времени не меньше чем исправление. И вдвойне обидно за потраченное время, если воспроизвести не удалось.
        • Время на исправление большого количества багов сложнее оценить, по сравнению с регулярным ежедневным включением их в план. Если процесс отлажен, то, располагая метриками производительности тестирования и разработки, можно закладывать оценку исправлений в долгосрочный план, параллельно с остальной разработкой.
        • Шансов «пофиксить» все ставшие известными за день до релиза баги в срок будет очень хотеться и менеджеру и молодым разработчикам. Но нет, так не бывает. Релиз будет либо ненадлежащего качества, либо сорван.
        • Специалисты, выполняющие тестирование от случая к случаю (приходящие стажёры, проектные менеджеры, аналитики, сами разработчики (упаси бог!), кофе-леди, бухгалтера и сантехники) по определению: а). низко-эффективны в этой роли, поскольку она для них эпизодическая б). вряд ли будут действовать согласовано по какому-либо плану или сценариям в). дадут гарантировано некачественные отчёты

        Помочь организовать здоровое тестирование — в собственных интересах разработчика.

        Лучшее — главный враг хорошего!


        Если вам повезёт и вы отладите разработку до идеального состояния, это не значит что менеджмент и заказчик автоматически станут счастливы. Во-первых, если PM, например, хронически прогибается под заказчика, беря без ведома команды обязательства по разработке больше чем физически можно успеть, то по мере роста стабильности и производительности разработки, будут расти и обязательства. Во-вторых, всегда найдутся не озвученные раньше проблемы, которые за не имением прочих получат самый высокий приоритет. Здесь схема такая: если раньше команда «факапила» стабильность и новую функциональность, то теперь заказчик может посчитать что приложение медленное и записать это как «эпичный факап», хотя изначально никаких требований относительно этого не существовало. Или вспомнить некую Pet-feature, ломающую всю имеющуюся логику и выставить её как must-have, а любые контр-аргументы посчитать непрофессионализмом и саботажем. Тут уже всё от адекватности заказчика и вашей менеджерской прослойки зависит. В таких ситуация остаётся только последовательно аргументировать, взывать к логике или искать более благодарную организацию.

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

        — было: никакого формализма, **як и в прод, пацаны быстро делали всё.
        — стало: куча формализма, CI/QA-какой-то мешает быстро «релизить», пацаны медленно стали кодить…

        При этом, не имея личного опыта работы в проектах различных размеров и командах, от него ускользает, что между «было» и «стало» находятся:

        1. большой временной период
        2. большой срез функциональности реализованной за это время
        3. не выплаченные долги из того что «было»

        Поэтому, для таких персонажей важно не столько само наведение порядка — для не-разработчика, любые изменения, кроме функциональных, будут эфемерными и необязательными. Важно аргументированное обоснование с наглядными примерами: что, как и почему. Тот самый аспект профессии разработчика, о котором упоминает Макконнелл.

        Комментарии (0)

          Let's block ads! (Why?)

          Интервью с Артёмом Малышевым, который выступит на Moscow Python в октябре

          воскресенье, 4 сентября 2016 г.

          [Из песочницы] Тестируем IVR на Asterisk с помощью… Asterisk

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


          Есть система дистанционного обслуживания – клиент может позвонить в call-центр и проверить/изменить настройки своей учётной записи без участия оператора. Для перехода по меню и управления настройками используются тональные сигналы (DTMF). АТС в свою очередь взаимодействует с ядром основной системы через API, возвращая результаты пользователю в виде голосовых сообщений.


          Задача: настроить автоматизированную проверку системы (правильно отвечает на запросы/выполняет нужные команды).
          Главные требования:


          • максимальная правдоподобность имитации пользователя: т.е. нужно именно звонить и нажимать кнопки, а не вызывать методы API в обход call-центра.


          • работа именно с тем планом набора, в который попадают обычные пользователи; нельзя делать специальный контекст для автоматизированной проверки.

          Для данной статьи упростим наш call-центр и API до безобразия: при звонке в call-центр пользователю доступна единственная услуга (клавиша 1); в ней пользователю предлагается ввести ПИН и в случае его корректности выдается статус учетной записи пользователя (ON/OFF); шаг влево или вправо – выдается сообщение об ошибке. Через API доступно три метода: GET ping (инициализация звонка), GET status (получить статус), POST status (установить статус).


          Блок-схема IVR


          Решать задачу будем с помощью Asterisk. По сути нам нужно собрать аналогичный IVR только от лица клиента: нужно описать машину состояний (ждем приветствие, ждем запроса ПИН и т.д.), и при переходе в каждое из состояний выполнять определённые действия.


          Команды отправлять понятно как – call-центр ожидает тональные сигналы от пользователя – значит можно воспользоваться командой SendDTMF и «нажимать» нужные кнопки от лица клиента.


          А как изменять своё состояние? Да точно так же! Для этого немного модернизируем dialplan нашего боевого IVR'а вызовом незамысловатого макроса в ключевых местах:


          [macro-robot]
          exten => s,1,ExecIf($["${CALLERID(name)}"!="Robot"]?MacroExit())
          same => n,Wait(1)
          same => n,SendDTMF(${ARG1})
          

          В результате в ключевых местах работы IVR, если звонок поступил от робота, в канал будет отправляться выбранная нами DTMF последовательность. Задержка в 1 секунду добавлена, чтобы наш робот успевал перейти в режим ожидания ввода.


          Теперь нам пригодится возможность Asterisk отправлять совершаемый звонок в нужный нам локальный контекст – таким образом замкнём между собой IVR call-центра и нашего робота. В самом простом варианте мы можем использовать call-файл и запускать проверку, периодически копируя этот файл в /var/spool/asterisk/outgoing/.


          Процесс проверки у нас будет такой:


          1. Звоним в call-центр


          2. ждём, пока можно будет выбирать услугу


          3. Нажимаем «1»


          4. Ждём, пока можно будет вводить ПИН


          5. Вводим ПИН


          6. Узнаем состояние


          — При первой проверке вызовом API меняем состояние на противоположное и заново проверяем состояние (переходим в п.3)


          — При второй проверке убеждаемся, что состояние изменилось на противоположное


          8. Если состояние изменилось, считаем проверку успешной


          9. Во всех остальных случаях сообщаем об ошибке


          Ниже я объединил «на одном экране» dialplan'ы обоих Asterisk'ов и показал, как передается управление/изменение состояний:


          Схема взаимодействия


          Текст, а не картинка

          Спрятал в спойлер, т.к. рвёт экран.


                                                      # Call-file
                                                      Channel: SIP/cc_peer/ivr    >----------\
                                                      Callerid: Robot                        |
                                               /--<   Context: robot-test                    |
                                               |      Extension: s                           |
                                               |                                             |
                                               |                                             |
          [robot-test]                         |                                             |          [ivr]
                                               |                                             |
          exten => s,1,NoOp(Wait for init)  <--/                                             \----->    exten => s,1,NoOp(IVR Start)
          same => n,Set(STATUS=)                                                                        same => n,Answer()
          same => n,WaitExten(10)                                                                       same => n,GotoIf($["${CURL(localhost/ping)}"!="PONG"]?err,1)
                                                                            /----------------------<    same => n,Macro(robot,00)
          exten => 00,1,NoOp(Init done)     <------------------------------/                            same => n,Background(welcome)
          same => n,Wait(1)                                                                             same => n(again),Background(press_1_for_status)
          same => n,SendDTMF(1)    ; Press 1 for status   >----------------\                            same => n,WaitExten(5)
          same => n,WaitExten(10)                                           \                           same => n,Goto(err,1)
                                                                             \
          exten => 10,1,NoOp(Status check)  <---------------------------\     \-------------------->    exten => 1,1,NoOp(Status check)     <---------------------------------\
          same => n,Wait(1)                                              \-------------------------<    same => n,Macro(robot,10)                                             |
          same => n,SendDTMF(1234) ; Send pin code        >---------------------------------------->    same => n,Read(PIN,enter_pin,4)                                       |
          same => n,WaitExten(10)                                                                       same => n,Set(STATUS=${CURL(localhost/status?pin=${PIN})})            |
                                                                                                        same => n,GotoIf($["${STATUS}"=="ON"]?on)                             |
          exten => _1[12],1,NoOp(Status)    <--------------------------------------------------\        same => n,GotoIf($["${STATUS}"=="OFF"]?off)                           |
          same => n,ExecIf($[${EXTEN}==11]?MSet(CURRENT=ON,NEW=OFF))                           |        same => n,Goto(err,1)                                                 |
          same => n,ExecIf($[${EXTEN}==12]?MSet(CURRENT=OFF,NEW=ON))                           |---<    same => n(on),Macro(robot,11)                                         |
          same => n,GotoIf($["${STATUS}"==""]?toggle)                                          |        same => n,Background(status_on)                                       |
          same => n,ExecIf($["${STATUS}"=="${CURRENT}"]?System(echo GOOD >> /cc_check.log))    |        same => n,Goto(s,again)                                               |
          same => n,ExecIf($["${STATUS}"!="${CURRENT}"]?System(echo BAD >> /cc_check.log))     \---<    same => n(off),Macro(robot,12)                                        |
          same => n,Hangup()                                                                            same => n,Background(status_off)                                      |
          same => n(toggle),NoOp(Toggle status)                                                         same => n,Goto(s,again)                                               |
          same => n,Set(STATUS=${NEW})                                           /--------------------------------------------------------------------------------------------/
          same => n,Set(RES=${CURL(localhost/status,status=${NEW})})            /                       exten => err,1,NoOp(Error occured)
          same => n,Wait(1)                                                    /            /------<    same => n,Macro(robot,99)
          same => n,SendDTMF(1)   ; Press 1 for status   >--------------------/            /            same => n,Playback(error)
          same => n,WaitExten(10)                                                         /             same => n,Hangup()
                                                                                         /
          exten => i,1,System(echo BAD >> /cc_check.log)    <---------------------------/               [macro-robot]
                                                                                                        exten => s,1,ExecIf($["${CALLERID(name)}"!="Robot"]?MacroExit())
                                                                                                        same => n,Wait(1)
                                                                                                        same => n,SendDTMF(${ARG1})
          

          В нашем примере результат выполнения проверки записывается в файл /cc_check.log. В боевой системе вы конечно же эти результаты будете складывать в свою систему мониторинга.


          В реальной системе простого CURL-запроса к API для проверки всей системы дистанционного обслуживания скорее всего не хватит, поэтому решение можно расширить для контроля звонка робота через AMI. Для этого нужно модифицировать dialplan робота, чтобы он отправлял UserEvent'ы в AMI. Нашу демонстрационную конфигурацию можно изменить следующим образом:


          robot-ami.conf
          [robot-test-ami]
          exten => s,1,UserEvent(CC_ROBOT_WAIT_INIT,RobotId: ${RobotId})
          same => n,Set(STATUS=)
          same => n,WaitExten(10)
          
          exten => 00,1,UserEvent(CC_ROBOT_WAIT_SERVICE,RobotId: ${RobotId})
          same => n,Wait(1)
          same => n,UserEvent(CC_ROBOT_WAIT_SERVICE,RobotId: ${RobotId},Data: Will press 1 now)
          same => n,SendDTMF(1)    ; Press 1 for status
          same => n,WaitExten(10)
          
          exten => 10,1,UserEvent(CC_ROBOT_WAIT_PIN,RobotId: ${RobotId})
          same => n,Wait(1)
          same => n,UserEvent(CC_ROBOT_WAIT_PIN,RobotId: ${RobotId},Data: Will send pin (1234) now)
          same => n,SendDTMF(1234) ; Send pin code
          same => n,WaitExten(10)
          
          exten => _1[12],1,UserEvent(CC_ROBOT_STATUS_CHECK,RobotId: ${RobotId})
          same => n,ExecIf($[${EXTEN}==11]?MSet(CURRENT=ON,NEW=OFF))
          same => n,ExecIf($[${EXTEN}==12]?MSet(CURRENT=OFF,NEW=ON))
          same => n,UserEvent(CC_ROBOT_STATUS_CHECK,RobotId: ${RobotId},Data: Current status is '${CURRENT}')
          same => n,GotoIf($["${STATUS}"==""]?toggle)
          same => n,ExecIf($["${STATUS}"=="${CURRENT}"]?UserEvent(CC_ROBOT_RESULT,RobotId: ${RobotId},Data: GOOD))
          same => n,ExecIf($["${STATUS}"!="${CURRENT}"]?UserEvent(CC_ROBOT_RESULT,RobotId: ${RobotId},Data: BAD))
          same => n,Hangup()
          same => n(toggle),UserEvent(CC_ROBOT_STATUS_CHECK,RobotId: ${RobotId},Data: Need to toggle state)
          same => n,Set(STATUS=${NEW})
          same => n,UserEvent(CC_ROBOT_TOGGLE,RobotId: ${RobotId},Data: ${CURRENT})
          same => n,Wait(2)
          same => n,SendDTMF(1)
          same => n,WaitExten(10)
          
          exten => i,1,UserEvent(CC_ROBOT_RESULT,RobotId: ${RobotId},Data: BAD)
          

          Для взаимодействия с таким dialplan'ом необходимо подключиться к Asterisk'у, с которого инициируется звонок робота, через AMI, сделать Originate и дальше действовать в соответствии с приходящими UserEvent'ами. Пример реализации скрипта нашей demo-проверки на Python:


          test_call.py
          #!/usr/bin/python
          
          import os
          import time
          import string
          import random
          import sys
          import requests
          
          from asterisk.ami import Action, AMIClient
          
          seconds_to_wait = 30
          test_result = 'unknown'
          host = 'localhost' # Asterisk with AMI and test dialplan
          user = 'robot' # AMI user
          password = 'MrRobot' # AMI password
          call_to = 'Local/ivr' # Call-center
          context = { # Robot dialplan context
              "context": "robot-test-ami",
              "extension": "s",
              "priority": 1
          }
          
          def toggle_state(new_state):
              print 'Will try to toggle state to {}'.format(new_state)
              r = requests.post('http://localhost/status', data = {'status':new_state.upper()})
              print 'Done! Actual state now: {}'.format(r.text)
          
          def event_notification(source, event):
              global test_result
              keys = event.keys
              if 'RobotId' in keys:
                  if keys['RobotId'] == robot_id: # it's our RobotId
          
                      if 'Data' in keys:
                          data = keys['Data']
                      else:
                          data = 'unknown'
                      if 'UserEvent' in keys:
                          name = keys['UserEvent']
                      else:
                          name = 'unknown'
          
                      if name.startswith('CC_ROBOT'):
                          print '{}: {}'.format(name, data)
          
                      if name == 'CC_ROBOT_TOGGLE':
                          if data.lower() in ['on','off']:
                              if data.lower() == 'on':
                                  toggle_state('off')
                              else:
                                  toggle_state('on')
                          else:
                              print 'Unknown state {}'.format(data)
          
                      if name == 'CC_ROBOT_RESULT':
                          test_result = data
          
          # Generate uniq RobotId to distinguish events from different robots
          robot_id = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(8))
          print 'Current RobotId: {}'.format(robot_id)
          
          # Action to enable user events
          aEnableEvents  = Action('Events', keys={'EventMask':'user'})
          
          # Action to originate call
          aOriginateCall = Action('Originate',
                                  keys={'Channel':call_to, 'Context':context['context'], 'Exten':context['extension'], 'Priority':context['priority'], 'CallerId':'Robot'},
                                  variables={'RobotId':robot_id}
                                 )
          
          # Init AMI client and try to login
          client = AMIClient(host)
          
          # Register our event listener
          client.add_event_listener(event_notification)
          
          try:
              future = client.login(user, password)
              # This will wait for 1 second or fail
              if future.response.is_error():
                  raise Exception(str(future.response.keys['Message']))
          except Exception as err:
              client.logoff()
              sys.exit('Error: {}'.format(err.message))
          
          print 'Spawned AMI session to: {}'.format(host)
          
          try:
              # Try to enable user events coming
              future = client.send_action(aEnableEvents,None)
              if future.response.is_error():
                  raise Exception(str(future.response.keys['Message']))
              print 'Logged in as {}'.format(user)
          
              # Try to originate call
              future = client.send_action(aOriginateCall,None)
              if future.response.is_error():
                  raise Exception(str(future.response.keys['Message']))
              print 'Originated test call'
          
          except Exception as err:
              client.logoff()
              sys.exit('Error: {}'.format(err.message))
          
          print 'Waiting for events...'
          
          # Wait for events during timelimit interval
          for i in range(seconds_to_wait):
              time.sleep(1)
              # If test_result is changed (via events), then stop waiting
              if test_result != 'unknown':
                  break;
          else:
              client.logoff()
              sys.exit('Error: time limit exceeded')
          
          # Logoff if we still here
          client.logoff()
          
          print 'Test result: {}'.format(test_result)
          

          Действия выполняются аналогичные описанному выше сценарию с call-файлом, но звонок инициируется по команде скрипта и вызов API тоже осуществляется из скрипта при получении события CC_ROBOT_TOGGLE. Результат проверки приходит вместе с событием CC_ROBOT_RESULT.


          В итоге мы решили задачу в рамках требуемых условий: звоним и «нажимаем кнопки», делаем проверки в боевом dialplan'е (выдавая тоны по ходу звонка, если звонит робот).


          Удачного тестирования!

          Комментарии (0)

            Let's block ads! (Why?)