Факапы случаются и у больших компаний, с тестировщиками и строгими релизными процедурами. В понедельник такой факап случился у нас — мы выкатили версию Яндекс.Навигатора под Андроид с неприятной ошибкой: приложение постоянно держало включённым микрофон и записывало весь звук в локальный файл. В результате быстро забивалось место на устройстве, да и просто это выглядело подозрительно, хотя содержимое файла по сети никуда и никогда не передавалось.
Сейчас ошибка уже исправлена, в сторе версия, которая её не содержит. Мы обнаружили проблему довольно быстро благодаря нашим пользователям и уже через несколько часов после релиза остановили раздачу обновления и оперативно опубликовали исправление.
Мы решили не стесняться, а поделиться с вами опытом, который из этой ситуации извлекли. Возможно, это поможет вам быть лучше. Как обычно, причиной стало сочетание технологических факторов и дискоммуникации между людьми. Подробности — под катом.
Сначала о том, что вообще нового было в этой версии. Чтобы водитель не отвлекался от дороги на взаимодействие с интерфейсом Навигатора, мы решили реализовать основные его сценарии без использования рук. Для этого нам было необходимо встроить в приложение голосовую активацию, которая позволяет вызвать голосовой интерфейс по команде «Яндекс». И сделать так, чтобы человек мог голосом подтвердить или отклонить вопрос от Навигатора. Например, при перестроении маршрута.
У Яндекса есть собственная технология распознавания SpeechKit, которая уже использовалась Навигатором. Но раньше она позволяла приложению только понимать команды после нажатия на кнопку (например, построения маршрута или поиска на карте организации или адреса). В новой версии появилась нужная нам возможность голосовой активации и подтверждения.
Навигатор был самым большим и серьёзным приложением, в котором применялась версия распознавания с голосовым управлением. Естественно, в процессе интеграции с ним сама библиотека развивалась и улучшалась. И, естественно, это происходило множеством итераций с использованием отладочной версии библиотеки. Тут-то и началась путаница.
Когда мы приблизились к желаемому результату, то собрали Нави (так мы называем приложение внутри команды) с финальной версией SpeechKit и начали его проверять для релиза. Тестирование шло хорошо, и мы были готовы к запуску.
Когда финальные тесты уже подходили к концу, мы увидели, что последняя версия библиотеки пишет слишком много логов, и что-то заподозрили. Оказалось, что мы по ошибке снова взяли отладочную версию. Времени оставалось мало, но мы понадеялись на то, что релизная версия библиотеки не должна сильно отличаться от отладочной, и без глубокого тестирования попробовали собрать Навигатор с ней. Увы, приложение стало падать.
Падение происходило в библиотеке, отвечающей за распознавание команд подтверждения маршрута. В ней обработка звука осуществляется параллельно, и для удобства написания параллельного кода и скорости используется небольшой набор примитивных функций с разной платформозависимой реализацией. С каждым тредом, который был создан библиотекой или в котором библиотека была инициализирована, ассоциированы некоторые данные (например, memory pool), и они должны быть инициализированы. Попытка обращения к библиотеке из треда, в котором эта инициализация не была проведена, приводила к падению, поэтому работать с ней нужно было из одного потока. К сожалению, это условие иногда не выполнялось из-за ошибки.
Исправить ошибку было относительно легко, но времени на тестирование с новой версией не оставалось совсем, и мы решили откатиться на отладочную и запускаться с ней.
До запланированного времени запуска оставалось совсем мало времени. Уже был готов следующий релиз, и тянуть не хотелось. При этом мы потратили много усилий на тестирование приложения с девдебажной версией, в результате которого стало понятно, что распознавание с ней вполне работает и единственным его недостатком является только то, что все происходит чуть медленнее. Хотя фикс релизной версии и был тривиальным, его добавление в Нави сулило нам новый процесс тестирования и сдвиг сроков на неизвестный период из-за возможности обнаружения новых баг.
Но у отладочной версии были свои особенности. Мы знали, что она пишет лог ошибок, и это было еще одним небольшим её плюсом, так как позволяло собирать информацию о сбоях на первом запуске. Но логи — это не всё. Большое количество тестовых данных критически важно для достижения максимального качества. Например, нужно понимать, когда библиотека не смогла распознать команду, иметь логи для звука.
Ещё возможность такой записи делалась для того, чтобы наши сотрудники могли поездить с особой сборкой Навигатора и пособирать тестовое окружение в реальных условиях, чтобы обучить на них голосовые технологии. Поэтому в отладочной версии звук записывался на sd-карту. Эта логика включалась define'ами в коде, и в релизной сборке была отключена. Она должна была быть отключена и в дебажной, но нет — был пропущен заголовочный файл, в котором этот макрос определялся.
Естественно, в тест-плане не были описаны запись звука и хранение его на устройстве, потому что эта функциональность не планировалась. А небольшое изменение размера приложения во время тестирования не заметили, так как при каждом вызове приложения из фона файл переписывался заново, что не позволяло семплу вырасти до заметных значений.
Но в процессе движения, когда сессия Навигатора не прекращается достаточно длительное время, семплы могли вырастать до нескольких гигов. И пользователи заметили это уже через несколько часов после запуска. К этому моменту стало ясно, что в продакшн попал ошибочный код, который планировалось использовать исключительно для внутреннего тестирования.
Последствия уже в газетах: www.vedomosti.ru/newspaper/articles/2015/09/08/608063-tainii-navigator-yandeksa.
В результате небольших по отдельности ошибок и допущений мы получили серьёзную проблему, из-за которой некоторые люди, увы, потеряли доверие к Навигатору и даже в целом к Яндексу. Мы все и я лично приносим извинения всем, кого эта проблема затронула.
Мы же для себя разбираем ситуацию в подробностях и пишем план действий, которые нужны, чтобы похожее никогда больше не повторилось. Это как раз тот случай, когда итоговые правила написаны, пусть не кровью, как в авиации, но болью от потери доверия людей и репутации. Сам план не буду приводить здесь, думаю, каждый может составить выводы из ситуации для себя сам.
Сейчас ошибка уже исправлена, в сторе версия, которая её не содержит. Мы обнаружили проблему довольно быстро благодаря нашим пользователям и уже через несколько часов после релиза остановили раздачу обновления и оперативно опубликовали исправление.
Мы решили не стесняться, а поделиться с вами опытом, который из этой ситуации извлекли. Возможно, это поможет вам быть лучше. Как обычно, причиной стало сочетание технологических факторов и дискоммуникации между людьми. Подробности — под катом.
Действие первое. Пролог. Всё идеально подходит
Сначала о том, что вообще нового было в этой версии. Чтобы водитель не отвлекался от дороги на взаимодействие с интерфейсом Навигатора, мы решили реализовать основные его сценарии без использования рук. Для этого нам было необходимо встроить в приложение голосовую активацию, которая позволяет вызвать голосовой интерфейс по команде «Яндекс». И сделать так, чтобы человек мог голосом подтвердить или отклонить вопрос от Навигатора. Например, при перестроении маршрута.
У Яндекса есть собственная технология распознавания SpeechKit, которая уже использовалась Навигатором. Но раньше она позволяла приложению только понимать команды после нажатия на кнопку (например, построения маршрута или поиска на карте организации или адреса). В новой версии появилась нужная нам возможность голосовой активации и подтверждения.
Навигатор был самым большим и серьёзным приложением, в котором применялась версия распознавания с голосовым управлением. Естественно, в процессе интеграции с ним сама библиотека развивалась и улучшалась. И, естественно, это происходило множеством итераций с использованием отладочной версии библиотеки. Тут-то и началась путаница.
Действие второе. Происходит путаница
Когда мы приблизились к желаемому результату, то собрали Нави (так мы называем приложение внутри команды) с финальной версией SpeechKit и начали его проверять для релиза. Тестирование шло хорошо, и мы были готовы к запуску.
Когда финальные тесты уже подходили к концу, мы увидели, что последняя версия библиотеки пишет слишком много логов, и что-то заподозрили. Оказалось, что мы по ошибке снова взяли отладочную версию. Времени оставалось мало, но мы понадеялись на то, что релизная версия библиотеки не должна сильно отличаться от отладочной, и без глубокого тестирования попробовали собрать Навигатор с ней. Увы, приложение стало падать.
Действие третье. Всё идёт не так и мы спешим
Падение происходило в библиотеке, отвечающей за распознавание команд подтверждения маршрута. В ней обработка звука осуществляется параллельно, и для удобства написания параллельного кода и скорости используется небольшой набор примитивных функций с разной платформозависимой реализацией. С каждым тредом, который был создан библиотекой или в котором библиотека была инициализирована, ассоциированы некоторые данные (например, memory pool), и они должны быть инициализированы. Попытка обращения к библиотеке из треда, в котором эта инициализация не была проведена, приводила к падению, поэтому работать с ней нужно было из одного потока. К сожалению, это условие иногда не выполнялось из-за ошибки.
Исправить ошибку было относительно легко, но времени на тестирование с новой версией не оставалось совсем, и мы решили откатиться на отладочную и запускаться с ней.
Действие четвёртое. Предвестник факапа
До запланированного времени запуска оставалось совсем мало времени. Уже был готов следующий релиз, и тянуть не хотелось. При этом мы потратили много усилий на тестирование приложения с девдебажной версией, в результате которого стало понятно, что распознавание с ней вполне работает и единственным его недостатком является только то, что все происходит чуть медленнее. Хотя фикс релизной версии и был тривиальным, его добавление в Нави сулило нам новый процесс тестирования и сдвиг сроков на неизвестный период из-за возможности обнаружения новых баг.
Но у отладочной версии были свои особенности. Мы знали, что она пишет лог ошибок, и это было еще одним небольшим её плюсом, так как позволяло собирать информацию о сбоях на первом запуске. Но логи — это не всё. Большое количество тестовых данных критически важно для достижения максимального качества. Например, нужно понимать, когда библиотека не смогла распознать команду, иметь логи для звука.
Ещё возможность такой записи делалась для того, чтобы наши сотрудники могли поездить с особой сборкой Навигатора и пособирать тестовое окружение в реальных условиях, чтобы обучить на них голосовые технологии. Поэтому в отладочной версии звук записывался на sd-карту. Эта логика включалась define'ами в коде, и в релизной сборке была отключена. Она должна была быть отключена и в дебажной, но нет — был пропущен заголовочный файл, в котором этот макрос определялся.
Действие пятое. Факап
Естественно, в тест-плане не были описаны запись звука и хранение его на устройстве, потому что эта функциональность не планировалась. А небольшое изменение размера приложения во время тестирования не заметили, так как при каждом вызове приложения из фона файл переписывался заново, что не позволяло семплу вырасти до заметных значений.
Но в процессе движения, когда сессия Навигатора не прекращается достаточно длительное время, семплы могли вырастать до нескольких гигов. И пользователи заметили это уже через несколько часов после запуска. К этому моменту стало ясно, что в продакшн попал ошибочный код, который планировалось использовать исключительно для внутреннего тестирования.
Действие шестое. Последствия и раскаяние
Последствия уже в газетах: www.vedomosti.ru/newspaper/articles/2015/09/08/608063-tainii-navigator-yandeksa.
В результате небольших по отдельности ошибок и допущений мы получили серьёзную проблему, из-за которой некоторые люди, увы, потеряли доверие к Навигатору и даже в целом к Яндексу. Мы все и я лично приносим извинения всем, кого эта проблема затронула.
Мы же для себя разбираем ситуацию в подробностях и пишем план действий, которые нужны, чтобы похожее никогда больше не повторилось. Это как раз тот случай, когда итоговые правила написаны, пусть не кровью, как в авиации, но болью от потери доверия людей и репутации. Сам план не буду приводить здесь, думаю, каждый может составить выводы из ситуации для себя сам.