Треды, или цепочки писем в почте, — одна из фич, на которые у гиков и массовой аудитории полярные взгляды. Гики активно ими пользуются; обычные пользователи, как показывают наши опросы, относятся к ним скорее настороженно. Во-первых, непривычно, во-вторых, люди опасаются, что не смогут сориентироваться в цепочках. Когда мы реализовывали треды в веб-версии Почты Mail.Ru, мы помнили об этом челлендже — и нашли, как нам кажется, максимально удобный и интуитивно понятный алгоритм группировки, который будет удобен и гикам, и менее продвинутым юзерам. За основу в работе над мобильными тредами мы взяли систему, разработанную большой Почтой, так как мы не хотели запутать пользователей и делать разную логику. Наша задача с точки зрения продукта заключалась в том, чтобы и веб-треды, и мобильные треды работали для пользователя одинаково. Но многие вещи пришлось переделывать с учетом офлайн-работы. О том, как мы сделали в Android-приложении Почты Mail.Ru цепочки, где письма не теряются даже при сбоях в сети, я расскажу в этой статье (о том, как сделали то же самое в iOS-приложении, расскажем в одном из следующих постов).



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

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

Техническая сторона синхронизации


Итак, с подходом мы определились, можно было приступать к реализации. Логика группировки писем в цепочки разрабатывалась еще для большой веб-версии Почты. Так что нам не пришлось изобретать велосипед: в Android-приложении мы просто реализовали тот же самый алгоритм. На Хабре есть детальный пост о том, как он разрабатывался и чем отличается от алгоритмов, которые используют другие почтовые службы, поэтому подробно описывать его в сегодняшней статье я не буду.

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

Мы начали искать причину такой медлительности. Изначально мы предполагали, что больше всего времени занимали SQL-операции, потому что они зависели от количества писем: при выполнении операции с каждым из них приходилось обновлять треды, счетчики, папки, само письмо и так далее. Однако в действительности самым узким местом оказалось копирование чрезмерно сложных индексов, по которым мы потом выбирали данные из памяти. Оно занимало 70-80% времени из этих 20 секунд. Мы убрали копирование этих индексов, а также перестройку индексов на каждую операцию, сделали буферизацию и update после завершения операции в конце транзакции. В результате вместо 20 секунд у нас получилось всего порядка 20-50 мс на операцию перестроения индексов. Кроме того, мы убрали из перестроений индексов данные, которые не изменяются согласно нашей бизнес-логике.

У нас есть достаточно простые и быстрые индексы, например, по какому-то конкретному значению: flagged, unflagged, read, unread, по значению папки, по аккаунту. Если значение вариантов состояний конечно, индексы перестраиваются быстро. А текстовые индексы, основанные на префиксных деревьях, перестраиваются долго: они и по объему занимаемой памяти большие, и в целом эта структура достаточно громоздка. Она позволяет ускорить процесс поиска, но при этом очень сложная и долго меняется. Это стандартные проблемы разных структур и алгоритмов обработки данных. Там, где мы хотим выиграть на поиске, мы, естественно, теряем на изменении этих данных.

Сначала мы пробовали делать разные реализации текстовых индексов. Ощутимой разницы не заметили и выбрали те, которые были быстрее по нашим замерам. Но желаемых показателей по времени мы все еще не достигали. Тогда мы решили зайти с другой стороны. Такие данные, как тема письма, отправитель и т.д. во время операций с письмом не меняются. Значит можно просто отказаться от перестроения сложных индексов и ограничиться теми, что отвечают за параметры flagged, unflagged и т. д., то есть быстрыми. Мы отказались от всех лишних операций, оптимизировали то, от чего отказаться нельзя, и сразу на 50-60% ускорили синхронизацию.

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

В итоге пользователь видит быстрый отклик независимо от того, какого рода операцию он совершал и над какими данными. В самом начале мы ставили для себя планку в 100 мс, но уперлись уже в ограничения самого Android: механизм транзакций довольно медленный, а если не использовать его, то мы получаем ошибку промежуточного состояния. Пришлось скорректировать требования. Мы планировали, чтобы с точки зрения пользователя операция занимала не больше 200-500 миллисекунд. Скорее всего, в следующей версии Android мы все же добьемся того, чтобы для пользователя операции выполнялись практически мгновенно.

Интерфейс


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



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

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

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

Отдельно стоит рассказать про тулбар, относящийся ко всему треду. Здесь у Android-почты больше расхождений с веб-версией. В последней тулбар — это большая панель, позволяющая совершать операции с верхним письмом треда в папке. В Android-приложении Почты Mail.Ru работа ведется либо с целой цепочкой, либо с конкретными письмами, выделенными пользователем. Есть только одно исключение: когда идет работа с тредом, есть возможность ответить на последнее письмо в этой цепочке. Это один из достаточно частых и важных кейсов, поэтому мы вынесли эту кнопку из чтения письма и включили ее в инструменты работы с тредом. Кнопки «Ответить» и «Переслать», которые можно назвать главными action buttons для треда, применяются именно к последнему письму цепочки.

Взаимодействие с Android Wear и Android Auto


Мы изначально реализовали поддержку Android Wear и Android Auto, поэтому для взаимодействия Android-Почты с часами и магнитолой не пришлось делать ничего дополнительно. Если пользователь включил в настройках почты группировку писем, уведомления на часах тоже будут группироваться по тредам. Если группировку писем отключить, то все будет работать по-старому, и уведомления группироваться не будут. На магнитоле под управлением Android Auto уведомления также группируются по тредам.

На наш взгляд, с поставленными задачами мы справились. Но, как всегда, мы очень ждем от вас фидбека. Тестируйте треды на Android и рассказывайте — удобно ли вам?

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


  1. Zeliret
    24.12.2015 13:03
    +2

    А как происходит непосредственная синхронизация данных на телефоне и на сервере?


    1. rus1f1kat0r
      25.12.2015 17:38

      в общем виде алгоритм такой:
      1. отправляем исходящие изменения (отметки писем, перемещения и т.д.)
      2. обрабатываем ошибки, если есть (откатываем либо повторяем попытку позже)
      3. проверяем статус по ящику, если нужно обновляем данные в смежных контейнерах (папки, треды и т.д.)


  1. Temmokan
    24.12.2015 13:51

    Маловато IT-сленга. Надо бы так: "Когда мы имплементировали треды в веб-версии Почты Mail.Ru, мы помнили об этом челлендже — и нашли, как нам кажется, максимально удобный и интуитивно понятный алгоритм групинга, который будет удобен и гикам, и менее продвинутым юзерам."

    А если серьёзнее — как поступаете, когда в пределах цепочки у письма внезапно меняется тема (такое тоже бывает)?


    1. z3apa3a
      24.12.2015 15:36

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


      1. rus1f1kat0r
        25.12.2015 17:41

        Все верно, считается, что это новый тред за исключением стандартных префиксов (Re: Fwd: и т.д.)
        В целом это решение должно быть консистентно среди всех платформ, на которых наша почта работает.


  1. Daedmen
    29.12.2015 01:54

    А как разруливаете конфликты при синхронизациях?


  1. DonAlPAtino
    29.12.2015 09:55

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


    1. rus1f1kat0r
      29.12.2015 10:11

      Спасибо за предложение. Я согласен, мы в принципе так и делаем и уже достаточно давно:
      1. При обработке письма с другого клиента, мы скрываем уведомление об этом письме.
      2. Корзина и спам не синхронизируются.

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