К сожалению, предложенный вами материал не соответствует критериям, предъявляемым модераторами к содержанию и проработанности материалов, проходящих через «Песочницу». Попробуйте прислать на модерацию другую вашу публикацию.
В какой-то момент я понял неприятную вещь: если твой канал связи живет по чужому настроению, политической погоде, сбою в чужом облаке или очередной внезапной любви регулятора к кнопке «запретить» — это не твой канал связи. Это аренда с правом внезапного выселения.
Мне эта модель быстро наскучила.
Поэтому я сделал то, что обычно делают люди с нездоровой смесью инженерной паранойи, скуки и профессиональной деформации: психанул и начал писать свой мессенджер.
Сразу зафиксируем рамки, чтобы не плодить фантомные ожидания:
-
это не убийца 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 так устанавливается). Так приложению жить будет заметно удобнее: платформа позволит заработать пуш-уведомлениям, появится нормальное кеширование, а оффлайн-режим перестанет быть декоративной надписью.
Если хотите просто еще один канал связи — пожалуйста.
Если хотите посмотреть и проверить, насколько крепок мой цифровой эксперимент, — тем более пожалуйста.
Если хотите проверить заодно и собственные навыки на живой системе, которая не притворяется идеальной, — вот, собственно, и весь смысл этой публикации.
Добро пожаловать.
Посмотрим, кто кого утомит первым: вы — мой сервер, я — ваши попытки его удивить, или реальность — нас обоих.
PS:
Меня там можно найти по нику IGRYM или по UIN 10001. Также есть внутренняя группа для первых пользователей (Early Birds, доступна на экране списка чатов через «+» вверху экрана)
Ссылки:
-
Официальный сайт с приложением: https://plumb-app.ru/
-
Телеграм-канал с новостями проекта: https://t.me/plumb_channel
-
Телеграм-группа обсуждения: https://t.me/plumb_group
Автор: igrym


