История одного бага

Раз в год-два у меня стабильно в работе встречаются какие-то невероятные детективные истории, после которых удивляешься как вообще это всё раньше не навернулось. Хочется поделиться интересным кейсом. 🔎

Пользователи репортят: долго запускается приложение, выглядит как вечное ожидание на сплэш скрине, минуту например. И нет никакого сообщения о том, что всё зависло, как это бывает при ANR. Просто как будто в фоне долго что-то выполняется.

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

У нас на сплэше выполняется несколько обязательных запросов. Бэк показывает свои метрики, что запросы отрабатывают в пределах нормы, это вычёркивает их из списка подозреваемых. Один из запросов подкачивает строки, для того чтобы позже подменять их в рантайме. После получения ответа пишем их в локальную базу, чтобы постоянно в интернет не ходить. Ходим в интернет не чаще, чем раз в сутки. Обновляем в ежедневной WorkManager джобе.

Звучит всё разумно. До тех пор пока взгляд у меня не падает на Dao с запросами в базу: Insert, Select All. Смотрю на описание таблицы, вижу автогенерируемый id, а ключ строки не уникальный. Проверяю теорию, ага, в табличке лежит несколько десятков тысяч строк вместо двух. Копаю дальше, понимаю, что раз в день мы стабильно добавляем в таблицу по паре тысяч записей, не удаляя предыдущие. Select All на старте приложения пытается достать все, делает из них мапу, с которой мы уже работаем, не замечая никаких проблем.

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

Поправили этот баг просто, сделав primary key из ключа и переписав логику перезаписи таблицы. Снизили количество ANR в два раза, количество крашей почти в полтора, оценки поднимаются вверх. Все в восторге.

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

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