Почему упоминания ломаются на масштабе
На старте разработки системы упоминаний всё выглядит тривиально: находим в тексте @, тянем список пользователей, подсвечиваем совпадения. Проблемы начинаются, когда аудитория переходит от сотен к десяткам тысяч, а лента — от одного экрана к бесконечной прокрутке. Именно на этом масштабе расходятся три подсистемы, которые в прототипе жили в одной функции: парсинг упоминаний, автодополнение пользователей и подсветка упоминаний.
Первый сбой — рассинхрон подсветки и ссылок. Парсер режет строку по границам слов и юникод-пробелам, а рендер подсвечивает по регулярному выражению с другой логикой границ. В результате имя с дефисом или точкой подсвечивается целиком, а ссылка ведёт на обрезанный ник — пользователь видит одно, переходит на другое. Лечится это единым источником истины: парсер отдаёт не строку, а список диапазонов «начало-конец + идентификатор пользователя», и рендер с построением ссылок работают только по нему.
Второй сбой — задержки автодополнения. Пока запрос уходит на сервер на каждое нажатие, список подсказок приходит с отставанием в несколько символов, и пользователь выбирает не того человека. Решение — локальный индекс частых контактов и дебаунс с отменой устаревших запросов, чтобы ответ на «@ан» не перетирал ответ на «@анна».
Третий сбой — дубли уведомлений об упоминаниях. При росте аудитории одно событие проходит через несколько обработчиков: создание поста, редактирование, репост, — и каждый шлёт своё уведомление. Нужен идемпотентный ключ «получатель + объект + тип события», по которому повторная доставка гасится.
Разбираем, как это закрывается на уровне архитектуры.
Что даёт выстроенная система упоминаний
Скорость автодополнения — первое, что замечает пользователь. Когда список кандидатов появляется после ввода первого символа, а не через паузу, ввод упоминания занимает меньше секунды вместо нескольких. Мы держим индекс пользователей и сущностей в памяти клиента и обновляем его инкрементально, поэтому отклик не зависит от размера справочника — будь в нём сотни или сотни тысяч записей.
Точность распознавания решается на разборе текста, а не на регулярных выражениях. Парсер учитывает границы слов, экранирование, вложенные упоминания и различия раскладок, поэтому «@иван» и «@Иван» ведут к одному профилю, а адрес почты или ник в ссылке не превращаются в ложное упоминание. Доля корректно распознанных сущностей на размеченном наборе сообщений выходит к верхней границе — остаток приходится на опечатки, которые закрывает подсказка «возможно, вы имели в виду».
Нагрузка на поддержку падает как следствие: жалобы «упомянул не того» и «уведомление не пришло» уходят из потока обращений, потому что выбор из подсказки исключает ручной ввод имени. Вовлечённость в обсуждения растёт за счёт того же механизма — человек, которого отметили, получает уведомление с контекстом и переходит в ветку, а не теряет сообщение в ленте.
Мы собираем систему на Kotlin и нативной отрисовке списка подсказок, чтобы поведение совпадало на обеих платформах. Метрики — время отклика подсказки, доля успешных переходов по уведомлению, число обращений по упоминаниям — выносим в общий дашборд, чтобы эффект был виден в динамике, а не на словах.
Подходы к парсингу и автодополнению: сравнение
Есть три рабочие схемы, и выбор между ними определяется не вкусом, а тем, где у приложения бэкенд и насколько живой список пользователей. Если справочник небольшой и меняется редко, разбор прямо в приложении даёт мгновенную реакцию на ввод; если пользователей десятки тысяч и они активно переименовываются, единственный источник правды — сервер. Гибрид закрывает оба случая, но требует аккуратной синхронизации.
| Вариант | Когда подходит | Ограничения |
|---|---|---|
| Парсинг на клиенте | Справочник до нескольких тысяч записей, меняется редко, важна работа без сети | Список устаревает между обновлениями приложения; при росте базы растёт память и время поиска |
| Серверный разбор | Большая живая база, частые переименования, единые правила для всех платформ | Каждое нажатие клавиши — сетевой запрос; при плохом канале подсказки запаздывают, нужен запасной режим |
| Гибридная схема | Справочник большой, но активная часть узкая (переписка идёт с десятком людей) | Сложнее логика слияния: нужно решать, чья версия записи свежее, и чистить устаревшее |
Как выбрать под свой профиль
Ключевой признак — сколько уникальных имён реально попадает в подсказку за сессию. Если это десятки, локальный кэш подсказок поверх серверного разбора снимает почти всю нагрузку: приложение держит недавних собеседников и частые контакты локально, а остальное дотягивает запросом. Такой кэш обновляется по времени жизни и по факту открытия диалога, поэтому переименование подхватывается без перезапуска.
Что ломается на стыке
Главная ошибка — разойтись в правилах разбора: клиент считает упоминанием одну конструкцию, сервер другую, и в уведомлениях появляются битые ссылки. Второй источник сбоев — удалённый пользователь: запись остаётся в кэше, а при отправке превращается в пустую ссылку. Поэтому правила разбора держат в одном месте, а недавно удалённые записи помечают, а не вырезают сразу.
Как мы внедряем систему упоминаний
Работу начинаем с аудита: смотрим, как приложение ведёт себя сейчас при вводе «@» — где срабатывает подсказка, где текст уходит как есть, как хранятся идентификаторы упомянутых сущностей. На этом этапе фиксируем крайние случаи: упоминание в начале строки, после знака препинания, внутри ссылки, вставка из буфера, ввод с аппаратной клавиатуры. Каждый случай получает ожидаемое поведение — это база для приёмки, а не догадки на глаз.
Дальше проектируем схему разбора и собираем её по шагам.
- Аудит текущего поведения. Прогоняем сценарии ввода на живом приложении, собираем расхождения между платформами и фиксируем, какие сущности вообще можно упоминать.
- Схема разбора текста. Определяем грамматику упоминания: допустимые символы, границы токена, правила экранирования. Разбор идёт одним проходом по строке, поэтому ввод длинного текста не тормозит.
- Настройка автодополнения. Подсказка запрашивается с задержкой после последнего нажатия и отменяет предыдущий запрос, если пользователь продолжает печатать. Список ранжируем по совпадению и частоте — релевантное оказывается первым.
- Связывание с данными. Упоминание храним как идентификатор сущности, а не как текст: при переименовании пользователя или канала ссылка остаётся рабочей.
- Проверка сценариев. Прогоняем разбор на подготовленном наборе строк — с эмодзи, смешанными алфавитами, обрывом ввода, удалением символа внутри упоминания. Отдельно проверяем поведение при недоступной сети.
- Нагрузочная проверка. Смотрим, как ведёт себя подсказка при большой базе пользователей и на слабом устройстве: время отклика и потребление памяти.
- Передача документации. Отдаём регламент разбора, описание формата хранения упоминаний и чек-лист приёмки, чтобы команда заказчика могла развивать систему сама.
После передачи остаёмся на поддержке: изменения в правилах разбора или ранжировании проходят через тот же набор проверок, что и первичная реализация.
Артефакты: конфигурации, схемы, политики доступа
После сдачи проекта у вас остаётся не «папка с исходниками», а комплект, по которому система упоминаний живёт и развивается без нас. Каждый артефакт закрывает свой класс задач: разбор текста, подсказки, права, уведомления, эксплуатацию.
- Схема разбора упоминаний — формализованное описание того, как парсер находит сущности в тексте: границы токена, набор допустимых символов, поведение на кириллице и латинице, обработка вложенных и повторных упоминаний. Схема привязана к тест-кейсам, поэтому изменение правил сразу проверяется на регрессиях.
- Конфигурация подсказок — параметры автодополнения: порог срабатывания, размер выдачи, приоритеты сортировки, источники кандидатов (локальный кэш, серверный поиск, недавние контакты). Меняется без пересборки приложения.
- Политики доступа — матрица «кто кого может упоминать»: приватные каналы, закрытые группы, внешние участники, заблокированные пользователи. Правила проверяются и на клиенте, и на сервере, чтобы обход интерфейса не давал доступа к чужим данным.
- Регламент уведомлений — когда упоминание превращается в пуш, как группируются повторные, что происходит при офлайне и как ведётся журнал доставки. Отдельно описаны сценарии, где уведомление намеренно не отправляется.
- Документация по упоминаниям — спецификация формата хранения, контракты серверных методов, описание краевых случаев и инструкция для поддержки: как воспроизвести проблему и что смотреть в логах.
- Схема миграции и отката — порядок выката, обратная совместимость старых версий клиента, план отключения при деградации.
- Набор автотестов — покрытие разбора, подсказок и прав доступа, включая негативные сценарии.
Комплект передаётся с репозиторием и короткой сессией для вашей команды: разбираем, где что лежит и как вносить правки. Опишите задачу — вернёмся с оценкой и планом по вашему приложению.
Кейс: ускорили автодополнение и убрали дубли уведомлений
Клиент — сервис совместной работы с лентой обсуждений. Упоминания работали, но подсказки появлялись с задержкой, а при быстром наборе и правках пользователи получали по два-три уведомления об одном и том же. Разбираем кейс упоминаний: что замеряли, что меняли и какие метрики до/после получили.
Что было до
Автодополнение запрашивало полный список участников при каждом нажатии клавиши и фильтровало его на клиенте. На активных обсуждениях это давало заметный лаг: подсказка отрисовывалась, когда пользователь уже допечатал имя. Уведомления отправлялись на каждое событие ввода — при правке текста или повторном упоминании того же человека в одном сообщении уходило несколько пушей.
Что изменили
- Перенесли фильтрацию на сервер: запрос уходит с префиксом после символа
@, ответ — только релевантные совпадения, ограниченные по количеству. - Добавили кэш частых участников обсуждения, чтобы типовые подсказки отдавались мгновенно.
- Ввели дедупликацию уведомлений по паре «автор — получатель — сообщение»: повторное упоминание в том же сообщении не порождает новый пуш.
- Развели состояния ввода и отправки — уведомление уходит только после того, как сообщение реально сохранено.
Метрики до/после
| Метрика | До | После |
|---|---|---|
| Скорость подсказок | заметная задержка при наборе | подсказка появляется практически сразу |
| Дубли уведомлений | 2-3 пуша на одно упоминание | один пуш на сообщение |
| Ошибочные уведомления | регулярные жалобы | сведены к нулю |
Работу вели наши инженеры на React Native — это позволило переиспользовать логику парсинга между платформами и не дублировать её отдельно под каждую ОС. После релиза число обращений в поддержку по теме уведомлений сократилось, а доля сообщений с упоминаниями выросла: подсказки перестали мешать набору текста.
Чем отличается разбор на клиенте от серверного?
Разница проявляется в момент, когда пользователь набирает символ @ и начинает вводить имя. Клиентский разбор означает, что приложение само находит границы упоминания в тексте и фильтрует список участников по уже загруженному справочнику. Серверный разбор — когда строка уходит на бэкенд, там разбирается, сопоставляется с базой пользователей и возвращается готовый результат. Оба подхода решают одну задачу, но по-разному распределяют нагрузку и задержки.
Клиентский разбор выигрывает в отзывчивости: подсказка появляется за доли секунды, потому что не ждёт сети. Но справочник участников нужно держать в памяти устройства и синхронизировать — при больших командах это заметный вес и риск устаревших данных. Задержки здесь минимальны, зато нагрузка на устройство растёт: разбор текста, фильтрация и отрисовка списка ложатся на процессор телефона, что чувствуется на слабых моделях при длинных сообщениях.
Серверный разбор снимает нагрузку с устройства и всегда работает с актуальным справочником — не нужно ничего кэшировать и синхронизировать. Плата за это — сетевая задержка на каждый запрос и постоянный поток обращений к бэкенду: при активном наборе это десятки запросов в секунду на пользователя. Без грамотного объединения запросов и кэша на стороне сервера нагрузка на инфраструктуру растёт линейно вместе с аудиторией.
На практике чаще выбирают гибрид: разбор и подсветка упоминаний идут на клиенте, а поиск и проверка прав — на сервере. Так задержки остаются в пределах восприятия, устройство не перегружается, а справочник всегда актуален. Стоимость поддержки при этом ниже, чем у чистого серверного варианта: меньше трафика, проще масштабирование, предсказуемая нагрузка на бэкенд. Выбор диктует сценарий — размер аудитории, частота упоминаний и требования к свежести данных.
Частые технические сомнения
Ложные срабатывания почти всегда рождаются на стыке парсера и правил валидации ника, а не в самом поиске совпадений. Мы разбираем строку посимвольно с учётом границ слова и набора допустимых символов, поэтому адрес почты, ссылка или фрагмент кода не превращаются в упоминание. Отдельно ведём список исключений — служебные слова, эмодзи-последовательности, повторные вхождения в одном сообщении: каждое правило закрывается тестом, и доля ошибочных срабатываний держится на уровне статистической погрешности.
Приватность решается на уровне модели доступа, а не косметикой интерфейса. Автодополнение отдаёт только тех пользователей, кого автор сообщения вправе видеть: закрытые команды, скрытые профили и заблокированные аккаунты не попадают в выдачу вовсе. Сервер проверяет право на упоминание повторно в момент отправки — если доступ отозвали между набором и публикацией, упоминание не создаётся, а автор получает понятную ошибку вместо молчаливой потери.
Что будет со старыми версиями приложения, которые ещё в ходу?
Разметку упоминаний проектируем как расширяемый формат с версионированием: старый клиент не знает о новых типах сущностей, но продолжает корректно отображать уже знакомые. Неизвестные конструкции деградируют до обычного текста, а не ломают сообщение. Это позволяет обновлять приложение постепенно, без принудительной миграции всех пользователей в один день.
Не создаст ли автодополнение пиковую нагрузку на сервер?
Поиск идёт с задержкой ввода и отменой предыдущего запроса, результаты кэшируются по префиксу, а частые подсказки отдаются из памяти. При росте числа пользователей добавляются реплики на чтение — выдача остаётся отзывчивой, а основная база не получает лишних обращений.
Каждое из этих сомнений мы закрываем на этапе проектирования, а не после запуска: правила парсинга, модель доступа и формат разметки фиксируются в документации и покрываются автотестами.
Обсудим вашу задачу по упоминаниям
Чтобы оценка была реалистичной, а не «средней по рынку», нам нужно понять вашу модель данных и сценарии использования упоминаний. Разбираем задачу по пунктам: где живут сущности, как устроен поиск по ним, какие ограничения по правам доступа и объёму справочника. Дальше возвращаемся с планом внедрения, сроками по этапам и составом работ — без общих формулировок.
Вот что мы уточняем на первом разговоре:
- Где источник сущностей — пользователи, команды, документы, проекты: одна таблица или несколько разрозненных справочников, как они синхронизируются с бэкендом.
- Как ищется совпадение — по началу строки, по вхождению, по опечаткам; нужна ли латиница рядом с кириллицей и как обрабатываются составные имена.
- Объём и частота — сколько сущностей в справочнике, сколько запросов в минуту, нужна ли выдача с сервера или достаточно локального кэша на устройстве.
- Права доступа — видит ли пользователь только своих коллег или весь справочник; как упоминание ведёт себя, если у получателя нет прав на объект.
- Формат хранения — как упоминание ложится в текст сообщения: идентификатор рядом с отображаемым именем, чтобы переименование не ломало историю.
- Поведение при удалении — что показывать, если упомянутый пользователь или объект больше не существует.
- Платформы и интеграции — нужны ли уведомления получателю, переход по тапу в профиль или карточку, работа в офлайне.
После этого мы фиксируем план внедрения: разметка модели данных, парсер текста, сервис автодополнения, интерфейс подсказок, обработка краевых случаев и тесты на реальных сценариях. Сроки разбиваем по этапам, чтобы вы видели, что и когда получаете на руки.
Опишите задачу — где сейчас хранятся сущности, какие сценарии упоминаний критичны и на каких платформах работает приложение. Вернёмся с оценкой работ, планом внедрения и сроками по каждому этапу.







