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

В какой-то момент я понял неприятную вещь: если твой канал связи живет по чужому настроению, политической погоде, сбою в чужом облаке или очередной внезапной любви регулятора к кнопке «запретить» — это не твой канал связи. Это аренда с правом внезапного выселения.

Мне эта модель быстро наскучила.

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

Сразу зафиксируем рамки, чтобы не плодить фантомные ожидания:

  • это не убийца Telegram;

  • это не презентация для инвестора с KPI и growth loops;

  • это не проповедь о том, как всем теперь жить.

Это мой личный цифровой бункер, моя песочница, мой учебный полигон и мой инженерный аттракцион. Я строю это прежде всего для себя: как резервный канал связи, как способ не зависеть от чужих решений и как честный highload-эксперимент, в котором можно не рассуждать про real-time в теории, а ловить его за горло руками.

И вот здесь началось смешное.

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

Скриншоты приложения
Скриншоты приложения
Скриншоты приложения
Скриншоты приложения

Что это вообще такое

На текущем этапе это PWA-мессенджер.

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

Почему все равно PWA:

  • мне нужен был короткий путь от идеи до живого клиента;

  • мне нужен был быстрый цикл выката без ритуальных танцев с магазинами приложений;

  • мне нужен был клиент, который можно быстро ломать, чинить, пересобирать и снова кидать в бой;

  • мне нужно было что заработает на разных платформах;

  • мне нравит��я, что PWA живет в браузерной песочнице, а не лезет в телефон как хозяин квартиры.

У PWA есть реальные потолки:

  • нет такой свободы по платформенным API, как у нативного клиента;

  • фон, пуши и некоторые сценарии мобильной жизни работают хуже, чем хотелось бы;

  • нет нормальных VoIP-пушей (звонки работают только когда приложение открыто), нет нормальной интеграции со списком вызовов телефона;

  • по голой производительности натив его обгоняет.

И, да, Service Workers иногда ведут себя как подростки в пубертате, а iOS местами режет фоновую жизнь PWA так, будто лично обиделась на идею веб-клиента. Я в курсе. Выживаем с тем, что есть.

Но для моей задачи PWA дал главное: скорость эволюции. А в экспериментальном проекте это иногда важнее, чем стерильная идеология.

И да, работает эта штука подозрительно хорошо. Лучше, чем я ожидал, если честно.

Зачем вообще писать еще один мессенджер, когда мир и так ими забит

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

Потому что я люблю контролировать инфраструктуру, а не молиться на чужую.

Потому что читать статьи про очереди, ретраи, доставку, порядок сообщений и борьбу с race condition — это теория. А написать свое, увидеть, где оно течет, и потом руками это затыкать — уже ремесло.

Потому что мне скучно.

Да, это тоже важная часть правды. На обычной работе мозг периодически начинает зевать от предсказуемости. А когда ты в одиночку пытаешься собрать живой real-time-сервис, где есть сессии, доставка сообщений, поиск, синхронизация состояния, очереди и weird cases мобильной сети — то зевота быстро заканчивается.

То есть да: это учебный проект. Но из тех учебных проектов, которые в какой-то момент говорят: «всё, детский сад закончился, теперь давай как взрослые».

Где начинаются не разговоры про highload, а настоящая инженерная жизнь

Пока не собираешь такое сам, кажется, что мессенджер — это просто чатик. Ну текстик, ну websocket, ну кнопка «отправить», господи. А потом начинается взрослая часть спектакля.

1. Сообщение должно не просто уйти, а дойти правильно

Самая скучная и самая дорогая ошибка — считать, что «отправил» значит «доставил».

Нет. В реальной жизни между клиентом, сетью, сервером и хранилищем полно мест, где всё может стать интересно:

  • клиент послал пакет, но сеть моргнула;

  • сервер принял, но клиент не получил подтверждение;

  • клиент решил, что ничего не дошло, и отправил повторно;

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

И это только один кусок.

2. Порядок сообщений — штука гораздо более капризная, чем кажется

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

Инженерная реальность куда веселее:

  • локальный optimistic update на offline-first клиенте хочет быть быстрым;

  • серверная истина хочет быть правильной;

  • база хочет жить в своей временной линии;

  • несколько устройств одного пользователя могут в это время смотреть на мир разными глазами.

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

3. Race condition — это когда баг уже произошел, но ты еще не знаешь, где именно тебя унизили

Race conditions — мой любимый жанр инженерного фольклора. Это когда всё прекрасно, пока ты смотришь. И ломается ровно в тот момент, когда отвлекся налить кофе.

Условно:

  • один поток думает, что пользователь в онлайне;

  • второй уже считает, что он отвалился;

  • третий в это время честно пишет новое состояние;

  • четвертый с невинным лицом отдает клиенту вчерашнюю правду.

А потом ты сидишь и объясняешь себе, почему в 99.7% случаев всё идеально, а в оставшихся 0.3% система внезапно начинает разговаривать голосами. А когда еще и пытаешься заставить работать реалтайм систему еще в кластере, то все сложности возводятся в квадрат.

4. Любая «маленькая фича» очень быстро приходит за твоей архитектурой с ножом

Захотел новую механику? Например, необычную резервацию UIN еще до регистрации? На бумаге выглядит забавно. На бэкенде начинается вечеринка:

  • появляется состояние для еще не существующего пользователя;

  • нужно резервировать ресурс так, чтобы его не вымели любопытные и жадные боты;

  • нужно думать о TTL, освобождении, гонках, повторных запросах и кривом клиентском поведении;

  • нужно следить, чтобы веселая фича не превратилась в атаку на собственную логику.

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

5. Поиск и история сообщений сначала кажутся простыми. Потом ты взрослеешь

Пока данных мало, Postgres прощает тебе оптимизм. Потом история растет, индексы начинают намекать, что дружба дружбой, а latency по расписанию, и любой неаккуратный запрос внезапно становится личным конфликтом с CPU.

И тут выясняется, что в реальном мессенджере мало просто «хранить сообщения». Нужно еще:

  • быстро искать;

  • не ломать горячий путь доставки;

  • не превращать сервер в печку под нагрузкой;

  • думать наперед, где потом начнется горизонтальное масштабирование, а где сначала будет боль, потом переписывание, потом снова боль.

Чтобы это не выглядело как очередная литература про «сложности highload», вот живой пример. Это кусок поиска по групповым сообщениям в моем бекенде: с проверкой доступа, выборкой только текстовых сообщений и сортировкой по релевантности.

SELECT  m.id,  m.chat_id,  m.sender_id,  m.content->>'text' AS text,  m.inserted_at,  c.titleFROM group_messages mJOIN chats c  ON c.id = m.chat_id AND c.shard_id = m.shard_idWHERE EXISTS (  SELECT 1  FROM chat_members cm  WHERE cm.chat_id = m.chat_id    AND cm.shard_id = m.shard_id    AND cm.user_id = :user_id)  AND m.content->>'text' IS NOT NULL  AND m.content->>'text' ILIKE :like_queryORDER BY similarity(m.content->>'text', :query) DESC,         m.inserted_at DESCLIMIT :limit;

Под этот запрос у меня лежит не молитва, а вполне конкретная опора: partial GIN index по выражению(content->>'text') gin_trgm_ops. Иначе такая красота очень быстро превращает сервер в отопление стойки за счет тупого перебора JSONB.

А вот так тот же самый функционал очень часто пишут новички — вроде работает, пока данных смешно мало:

SELECT m.id, m.chat_id, m.sender_id, m.content, m.inserted_atFROM group_messages mWHERE m.chat_id IN (  SELECT cm.chat_id  FROM chat_members cm  WHERE cm.user_id = :user_id)  AND m.content::text ILIKE '%' || :query || '%'ORDER BY m.inserted_at DESCLIMIT :limit;

Проблема тут не в эстетике. Проблема в том, что верхний вариант ищет по нужному полю, умеет нормально ранжировать результат и опирается на индекс. Нижний часто уезжает в тяжелый scan по JSONB, ищет по строковому представлению всего объекта и под нагрузкой начинает жечь CPU просто потому, что автору было лень договориться с базой по-хорошему.

На реальном объеме истории в проде разница здесь может быть уже не в процентах, а на порядок и выше. То есть это очень быстро превращается не в «чуть-чуть быстрее», а в 10x+, а дальше всё зависит от размера истории, доли текстовых сообщений, партиций и того, насколько сильно вы любите мучить PostgreSQL.

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

6. Очереди, ретраи и backpressure — это не украшения, а повод спать чуть спокойнее

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

То есть нужны:

  • очереди (Kafka, или что вы там предпочитаете);

  • повторные попытки (retry-логика);

  • backpressure;

  • вменяемая реакция на временную деградацию (graceful degradation);

  • circuit breaker чтобы какой-нибудь маленький и вроде бы некритичный сервис не мог каскадно положить бы всю инфраструктуру;

  • архитектура, которая умеет признавать, что мир не идеален.

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

Интерфейс я подсматривал у Telegram. И да, специально

Я не стал устраивать дизайнерский карнавал ради уникальности. У пользователя уже есть мышечная память. Она дороже моего самолюбия.

Если человек открывает новый мессенджер, он хочет быстро разобраться и написать сообщение. Ему не нужен артхаус-квест «найди кнопку отправки, автор так видит».

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

Моя любимая инженерная шалость: UIN-рулетка до регистрации

Тут я, конечно, немного похулиганил.

Я сделал резервацию UIN еще до этапа регистрации. То есть можно зайти, поймать красивый номер, а уже потом решить, нужен тебе вообще этот зоопарк или нет.

С человеческой стороны это фан, ностальгия и немного ICQ-вайба.

С серверной стороны это пачка вопросов:

  • как хранить состояние для «почти-пользователя»;

  • как не раздать красивые номера слишком щедро;

  • как не дать мимо проходящему ботнету выгрести пул;

  • как освобождать резервы и не плодить мусор;

  • как не застрелить логическую целостность ради красивой игрушки.

С практической точки зрения миру, возможно, и не нужна эта фича. С инженерной — она просто слишком вкусная, чтобы я прошел мимо.

Про безопасность — без дешевой магии и без сказок для инвестора

Нет, я не изобретал свою криптографию. Я вообще считаю, что желание «сейчас быстренько придумаю свой защищенный протокол» должно автоматически активировать тревожную сирену. Да, создать защищенный протокол возможно, но это сложнее и дольше чем кажется из-за необходимости учета множества edge-cases; и в реальности сделанные на коленке протоколы чаще ненадежны, и, что еще хуже, об их уязвимостях могут знать лишь немногие пронырливые и порой не самые добросовестные люди.

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

  • транспорт закрыт современным TLS 1.3 (который на текущий момент, март 2026, взломать прямым криптографическим методом практически невозможно);

  • клиент живет как PWA в браузерной песочнице, какая уже сама по себе имеет ряд уровней защиты;

  • поверх этого я стараюсь не творить откровенной дичи;

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

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

Если вам нужна зрелая крепость для деликатной переписки — берите зрелые решения, которым вы уже доверяете.

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

Почему я вообще тащу это именно на Хабр

Потому что Хабр — это место, где тексту мало быть наглым. Здесь за дерзость прощают только одно: если под ней есть мясо.

А у меня как раз тот случай, когда мне интереснее не «собрать лайки», а получить нормальную техническую реакцию:

  • где архитектура выглядит спорно;

  • где логика доставки сообщений может начать чудить;

  • где PWA упрется в реальные ограничения платформы;

  • где резервация UIN создает лишнюю поверхность атаки;

  • где под нагрузкой начнет хрустеть то, что в одиночных тестах ведет себя прилично;

  • где в протоколе, порядке событий, ретраях или кешировании я недооценил крайний случай.

Мне не нужен хор в духе «вау, круто».

Мне нужен ваш внутренний вредный сеньор. Тот самый тип, который открывает статью, хмыкает, а потом начинает мысленно разбирать чужую систему на части.

Вот ему я и пишу.

Что я от вас хочу

По сути — честной драки, но умной.

  • Хотите проверить, как это переживает плохую сеть — отлично.

  • Хотите посмотреть, как выглядит поведение в edge-cases — вообще прекрасно.

  • Хотите понять, насколько легко читается логика протокола — давайте.

  • Хотите найти баг в порядке сообщений, в кеше, в подтверждениях, в резервации UIN, в клиентском поведении — тем более.

Только давайте без детского жанра «я что-то молча сломал и ушел сиять в закат». Если найдете слабое место, баг, странность, дыру, подозрительный сценарий или красивый способ заставить систему вести себя не так, как я планировал, — пишите.

Мне это полезнее, чем аплодисменты.

И да: я не заявляю, что построил новый и единственно верный мессенджер. Я заявляю другое. Я собрал свой рабочий велосипед, уже довольно злой и живой, и мне интересно, где именно Хабр попробует вставить ему отвертку в спицы, и на какой секунде этот велосипед упадет.

Что можно делать прямо сейчас

  • Если вы параноик или устали от мейнстрима и навязанных решений — держите это как запасной канал связи.

  • Если вы соскучились по временам красивых UIN — приходите ловить UIN-номер.

  • Если вы любите sniff, разбор трафика и сетевые игры — смотрите что идет по проводу.

  • Если у вас профессиональная привычка первым делом искать edge-cases — вот, собственно, ради вас всё это и принесено.

И если что-то положите, вскроете или красиво разберете — пришлите детали. Я не обидчивый. Я, строго говоря, именно ради этого и открыл двери.

Богатые логи в бета-версии
Богатые логи в бета-версии

Что дальше

Планы без ��орпоративной шелухи, но вполне понятные:

  • дальше допиливаю PWA-версию;

  • нативные Android и iOS-клиенты — в планах, но пока не в приоритете;

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

  • идея с SDK / библиотекой для кастомных клиентов мне нравится отдельно: если люди захотят писать свои оболочки, это будет уже совсем красивый уровень игры.

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

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

Я делал его в первую очередь для себя. Но он уже дорос до стадии, когда его интересно не только строить, но и показывать наружу.

Если будете пробовать — лучше сразу добавить его на домашний экран (PWA так устанавливается). Так приложению жить будет заметно удобнее: платформа позволит заработать пуш-уведомлениям, появится нормальное кеширование, а оффлайн-режим перестанет быть декоративной надписью.

Установка PWA на Android и iOS
Установка PWA на Android и iOS

Если хотите просто еще один канал связи — пожалуйста.

Если хотите посмотреть и проверить, насколько крепок мой цифровой эксперимент, — тем более пожалуйста.

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

Добро пожаловать.

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

PS:

Меня там можно найти по нику IGRYM или по UIN 10001. Также есть внутренняя группа для первых пользователей (Early Birds, доступна на экране списка чатов через «+» вверху экрана)

Ссылки: