Веб-версияОткрыть в Telegram
EEvApps

EvApps

@evapps_team · канал · в индексе с 2026-07-19
194подписчиков
67средний охват поста
34.5%ER — охват к подписчикам
15постов за 30 дней
E
EvApps
🐌 Почему UUID в первичном ключе тихо душит базу UUID как id — вроде идеально: генерится на клиенте, не угадать, не конфликтует между сервисами Все привыкли лепить его как первичный ключ и не думать А потом база на паре миллионов строк начинает необъяснимо тупить на вставках Разберём, почему так и что с этим делать 🎲 Корень зла — случайность Обычный UUID (v4) полностью случайный Выглядит безобидно, но вот в чём засада: первичный ключ в базе хранится не кучей, а в отсортированном виде (B-tree индекс) Когда id идёт по порядку (обычный автоинкремент 1, 2, 3...), каждая новая строка ложится в конец А случайный UUID лезет каждый раз в новое случайное место в середину индекса Базе приходится раздвигать уже забитые страницы, чтобы впихнуть строку между существующими На потоке вставок таких разрывов тысячи, индекс пухнет и фрагментируется 💾 Мимо кэша Второй удар — по кэшу. База держит в оперативке горячие страницы (buffer pool) Когда вставки идут по порядку, ты всё время работаешь с одним и тем же «хвостом» индекса, он всегда в памяти А со случайным UUID каждая вставка дёргает случайную страницу — сегодня эту, через миг вон ту В память всё не влезает, и база начинает гонять страницы с диска На больших таблицах это разница между «мгновенно» и «чего оно висит» 📏 И просто жирнее UUID — это 16 байт, а bigint — 8 Вроде мелочь, но первичный ключ тащится во все индексы и во все внешние ключи, что на него ссылаются Помножь на миллионы строк и десяток ссылающихся таблиц — набегает ощутимо А если ты ещё и хранишь UUID строкой (varchar(36)) вместо родного типа — это уже 36+ байт, и вообще боль Так делать не надо. 🛠 Что делать Хорошая новость: отказываться от UUID не нужно, надо взять правильный UUIDv7 — это UUID, у которого в начале зашито время создания Он всё так же уникален и генерится на клиенте, но при этом растёт по порядку — новые id всегда больше старых Для базы он ложится так же аккуратно, как автоинкремент: в конец, без разрывов страниц и без прыжков по памяти Забираешь
101 ·
E
EvApps
📄 Почему пагинация тормозит на дальних страницах Пагинация через LIMIT / OFFSET есть почти везде — и работает отлично ровно до тех пор, пока юзер не улистал далеко Первая страница летает, а где-нибудь на 500-й всё еле ползёт Хотя отдаёшь ты те же 20 строк Как сделать, чтобы скорость не зависела от номера страницы 🐌 Что не так с OFFSET: SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 100000; Ты думаешь «дай мне 20 штук со сдвигом» А база понимает это буквально: она проходит первые 100 000 строк, отсчитывает их одну за другой, выбрасывает — и только потом отдаёт следующие 20 То есть чем дальше страница, тем больше строк она перелопачивает впустую На первой странице OFFSET 0, летает На тысячной — прогоняет сотню тысяч строк ради двадцати 🚀 Keyset-пагинация Идея простая: не считать сдвиг, а запоминать, на чём остановились, и просить «дай мне то, что идёт после вот этого» SELECT * FROM posts WHERE created_at < :last_seen_created_at ORDER BY created_at DESC LIMIT 20; Здесь база не отсчитывает ничего — она по индексу сразу прыгает в нужное место и берёт двадцать штук Скорость одинаковая что на первой странице, что на миллионной Ты просто передаёшь с каждой страницей «якорь» последней строки (обычно id или дату), а на следующий запрос отдаёшь его обратно ⚖️ В чём подвох Keyset не панацея, у него есть ограничение: нельзя прыгнуть сразу на «страницу 500» Ты можешь идти только вперёд и назад, от текущего места Для нумерации страниц 1-2-3...500 это не годится Но честно — а где тебе реально нужны номера страниц? В бесконечной ленте, подгрузке по скроллу, выгрузке данных пачками, API с курсором — везде листают последовательно, и keyset там идеален А классический OFFSET оставь для админок и мест, где страниц пара десятков и на скорость плевать 🎭 Бонусная беда OFFSET — он ещё и врёт Про скорость понятно, но есть второй, менее очевидный косяк Пока юзер листает, в таблицу сыплются новые записи Добавили пару строк в начало (а сортировка-то по свежести) — и весь сп
1 · 71 ·
EvApps
🕐 Почему вечно «едут» даты Со временем всё просто — до первого бага У юзера отчёт за «вчера» показывает не то, напоминалка падает на час раньше, а после перевода часов вообще всё разъехалось И самое обидное — локально не повторить, у тебя-то сходится Разберём, обо что тут все спотыкаются и как перестать воевать 🌍 Правило номер один: всё в UTC Главная засада — хранить в базе местное время Пока сервис в одном городе — вроде норм А как появятся юзеры из разных зон (или сервак переедет) — и ты уже сам не понимаешь, что за 14:00 лежит в базе По Москве? По серверу? По юзеру? Фиг знает Как надо: внутри системы всё живёт в UTC — и база, и логика А местное время — это просто «как показать юзеру», его считаешь в самый последний момент Прилетело время от клиента — сразу перегнал в UTC и забыл Отдаёшь наружу — перегнал в зону юзера 🎭 Время без зоны — это мина В коде время бывает двух видов: с зоной и без Без зоны — это голое 2026-08-19 14:00, и непонятно, это 14:00 вообще где Такие штуки нельзя спокойно сравнивать и складывать — рано или поздно бахнет dt = datetime(2026, 8, 19, 14, 0, tzinfo=timezone.utc) 🌗 Перевод часов — отдельная боль Даже если зона одна, есть летнее/зимнее время, и тут два прикола, про которые вспоминают только когда уже горит: — один и тот же час может случиться дважды (когда стрелки крутят назад); — а иногда его не бывает вообще (когда крутят вперёд — час просто исчезает) Поэтому и нельзя держать местное время и думать, что отмотаешь назад 📅 «Вчера» — это когда вообще? Классика аналитики: юзер хочет отчёт за «вчера», а сутки у всех кончаются в разное время У чувака в Москве день закрылся в 21:00 UTC, у другого в Нью-Йорке — совсем в другой момент Режешь сутки по серверному времени — у половины народа «вчера» будет кривым Границы дня надо считать в зоне юзера, и только потом гнать в UTC для запроса 🔢 Ещё пара вещей, чтоб не наступить В Postgres храни момент в timestamptz, а не в timestamp Первый — момент времени, второй — цифры без привязки, и эт
74 ·
E
Фотография
нажмите — покажем
Ранее в сериале: ставка 20% похоронила проекты с окупаемостью в три года, бюджеты ушли в ИБ и ИИ, вакансий −13% при +11% резюме, поиск работы растянулся на 3–5 месяцев, конкурент переехал в Азию, а «мидл» перестал означать «два года стажа». 13 сентября наш CEO Альфред Столяров и Евгения Добижа, зам. директора по персоналу ООО «ЦИТ», выступят на конференции Город IT в Томске с докладом о том, как теперь со всем этим жить. Взгляд сразу с двух сторон: от тех, кто продаёт экспертизу разработки, и от тех, кто её покупает. Обсудим: ✅ три числа, по которым судят команду: стоимость, ошибки, сроки ✅ метрики ценности инженера — Utilization, Cycle Time, Bug Rate, MTTR. Не ощущения тимлида, а цифры ✅ почему разработчик выходит из тени — на демо и в диалог с заказчиком ✅ ИИ как стажёр, который не устаёт: где работает, а где мы обожглись ✅ репутацию вместо «+50% за прыжок» Утешительных прогнозов не будет. Похорон отрасли — тоже. 🤓 Полезнее всего будет разработчикам и аналитикам, руководителям IT-компаний и тем, кто нанимает разработку. 📍 Город IT 2026, 13 сентября Томск, БКЗ филармонии, пл. Ленина, 12А 📎 Регистрация уже вовсю идет здесь
6 · 320 ·
EvApps
Есть новость для тех четырех РП, что сидят в этом чате🤗
85 ·
E
Фотография
нажмите — покажем
Скоро осень - а значит и новый деловой сезон 🍁 Приурочили к его началу новый вебинар для ИТ-руководителей! Точечные интеграции рано или поздно превращаются в клубок, который тормозит развитие. Но нужна ли вам полноценная шина — или можно обойтись без неё? И если нужна, кто её внедрит и будет поддерживать? 29 сентября разберём вопрос с двух сторон — технологии и ресурсы: Сергей Скирдин (CTO «Белый Код») — когда шина действительно нужна, какие ESB-решения есть на российском рынке и как их внедрять Альфред Столяров (CEO EvApps) — как собрать команду под проект: найм, фикс-прайс, аутстафф или гибрид, и как не остаться без экспертизы после сдачи проекта Для CDO, руководителей ИТ и технических лидеров в промышленности, логистике, ритейле и финансах. 29 сентября, 12:00–13:00, онлайн Участие бесплатно, но регистрация обязательна: по итогам вебинара пришлем всем зарегистрировавшимся полезные материалы Зарегистрироваться
3 · 283 ·
E
EvApps
💸 Деньги во float — и почему баланс однажды не сойдётся 0.1 + 0.2 в float даёт не 0.3, а 0.30000000000000004 Само по себе это ерунда, но именно на этом хвосте держится половина багов с деньгами — и вылезают они не там, где хотелось бы Разберём, где именно и как хранить деньги, чтобы потом не искать расхождения по всему проекту 🧮 Погрешность не страшна поштучно — страшна на потоке Одно сложение с хвостом в пятнадцатом знаке погоды не делает Проблема в накоплении: тысячи операций — суммы, проценты, скидки, конвертации — и хвосты потихоньку копятся В какой-то момент SUM по строкам не сходится с общим итогом на копейку И это уже не «ну почти», а расхождение в отчёте, к которому приходит финотдел На копейку в деньгах ответ «это округление float» не принимается 🛠 Как хранить нормально Два рабочих варианта, оба правильные Первый — целые в минимальных единицах Хранишь не 19.99, а 1999 копеек, целым числом Целые складываются и вычитаются идеально точно, никаких двоичных хвостов в принципе На показ юзеру делишь на сотню Просто и надёжно, так живёт большинство платёжек. Второй — специальный денежный тип, который считает в десятичной системе, а не в двоичной В базах это NUMERIC (он же DECIMAL), в языках — готовые классы: BigDecimal в Java, Decimal в C#, тип Decimal из модуля decimal в Python Внутри он хранит число как есть, по десятичным разрядам, поэтому 0.1 + 0.2 там честно даёт 0.3, без хвоста Считает медленнее обычного float, но на денежных объёмах эта разница роли не играет Что выбрать: целые копейки удобнее, когда операции простые (сложить, вычесть, показать) Денежный тип приятнее там, где много процентов, дробей и округлений — он сам держит нужную точность и правила округления Оба варианта рабочие, главное — не float. ➗ Целые копейки не отменяют математику С копейками точно всё, пока не доходит до деления Раскидай 100 рублей на троих: 33.33 + 33.33 + 33.33 = 99.99 Копейка испарилась Обычно остаток отдают кому-то одному (первому, последнему — как договоришься), н
83 ·
E
EvApps
🔒 BEGIN/COMMIT не спасёт так, как ты думаешь Обернул код в транзакцию — и кажется, что теперь всё под защитой, гонки не страшны, данные консистентны На деле транзакция даёт меньше, чем от неё ждут, а самое интересное прячется в уровнях изоляции, которые почти никто не трогает Разберём, что реально гарантирует транзакция и где она молча тебя подведёт 📦 Что даёт транзакция на самом деле Главное, что делает BEGIN ... COMMIT — это атомарность: либо применились все изменения, либо ни одного Списал с одного счёта, зачислил на другой, между ними упало — откатится всё, половина денег нигде не зависнет Это работает и это ценно Но есть второй вопрос, про который забывают: а что транзакция видит, пока рядом крутятся другие? Вот это и есть изоляция — и у неё несколько уровней, от слабого к строгому По умолчанию стоит не самый строгий 🎚 Уровни изоляции по-простому Read Committed Ты видишь только то, что другие уже закоммитили Звучит норм, но засада: два одинаковых SELECT внутри одной твоей транзакции могут вернуть разное — если между ними кто-то успел закоммитить изменение То есть данные под тобой могут поменяться прямо по ходу Repeatable Read Транзакция работает как будто со снимком данных на момент своего старта Сколько раз ни спроси — видишь одно и то же, даже если снаружи уже всё поменяли В MySQL/InnoDB, кстати, это дефолт, а не Read Committed — приятная разница, о которую спотыкаются при переезде между базами Serializable Самый строгий: база ведёт себя так, будто транзакции шли строго по очереди, одна за другой Максимальная защита, но за неё платишь — если база видит, что две транзакции конфликтуют, она откатывает одну с ошибкой сериализации, и ты должен её поймать и повторить 💥 Где это стреляет Списание с баланса из поста про гонки: BEGIN; SELECT balance FROM accounts WHERE id = 1; -- прочитал 100 -- посчитал в коде: 100 - 50 = 50 UPDATE accounts SET balance = 50 WHERE id = 1; COMMIT; Многие думают: «я же в транзакции, всё ок» А вот и нет На дефолтном Read Com
78 ·
E
EvApps
🧩 Микросервисы, которые сделали только хуже Распилили монолит на пятнадцать сервисов - вроде как «сделали по-взрослому» А через полгода деплой стал сложнее, баг тянется через пять сервисов, латентность выросла, и никто уже не держит в голове, как оно всё связано История настолько частая, что пора проговорить: микросервисы решают не ту проблему, которую им обычно приписывают 🎯 Микросервисы - это про команды, а не про код Главное заблуждение: «проект большой, значит пора делить на микросервисы» Но размер кода тут вообще ни при чём Микросервисы нужны, когда у тебя много команд, и им тесно в одном репозитории - они мешают друг другу деплоить, релизы стопорятся, каждый выкат - согласование с пятью отделами Вот тогда распил по сервисам даёт независимость: каждая команда катит своё, когда хочет А если у тебя одна-две команды - ты этой независимости не получишь, зато купишь все минусы распределённой системы И минусы не пустяковые 💥 За что реально платишь Как только сервисы разъехались по сети, вылезает то, чего в монолите не было в принципе: - Вызов функции превращается в сетевой запрос — а сеть падает, тормозит и таймаутит То, что раньше было if, теперь требует ретраев, таймаутов и обработки «а если сосед не ответил» - Транзакции больше не работают как раньше В монолите ты завернул всё в один BEGIN/COMMIT А между сервисами общей транзакции нет - и консистентность приходится собирать руками через саги, компенсации и очереди - Отладка становится квестом Баг проходит через пять сервисов, и чтобы понять, где сломалось, нужен распределённый трейсинг Без него ты просто смотришь в пять разных логов и гадаешь - Версионирование API Поменял формат ответа - и сломал троих соседей, которые про это не знали Всё это - нормальная плата за независимость команд Но если независимости нет, вы просто так усложняете себе жизнь 🧟 Худший вариант - распределённый монолит Самое опасное - распил, после которого сервисы всё равно связаны намертво Как понять, что ты попал именно в это: - сервис
92 ·
E
EvApps
🚀 Как уронить прод одной миграцией Выкатываешь релиз, в нём миграция - вроде добавить колонку, ерунда А прод на сорок секунд встаёт колом, запросы висят, алерты орут Миграции на живой базе - это минное поле, где безобидный ALTER TABLE блокирует всю таблицу Обо что спотыкаются и как катить схему без даунтайма 🔒 Почему прод встаёт Многие операции над таблицей берут блокировку, и пока она держится, все запросы к таблице ждут На маленькой таблице это миллисекунды, а на большой - секунды и десятки секунд, в течение которых сайт фактически лежит Три главных нарушителя: - Добавление колонки с NOT NULL и значением по умолчанию В старых версиях баз это переписывало всю таблицу целиком, с полной блокировкой Чем больше строк - тем дольше стоишь - Создание индекса обычным CREATE INDEX - блокирует запись в таблицу на всё время построения - Переименование или удаление колонки, на которую ещё смотрит работающий код И самое коварное: во время деплоя одновременно крутятся и старая, и новая версия приложения Схема уже поменялась, а половина инстансов ещё живёт по-старому Если миграция ломает старый код — часть юзеров ловит ошибки прямо во время выката 🧩 Главный принцип: expand → migrate → contract Идея в том, чтобы никогда не менять схему одним резким движением, а разбить на шаги, где на каждом и старый, и новый код живут спокойно: Expand — расширяешь схему обратимо: добавляешь новое, ничего не ломая Новая колонка — обязательно nullable, без жёстких констрейнтов Migrate — переносишь данные и переключаешь код на новое Бэкфилл делаешь пачками, а не одним UPDATE на миллион строк (иначе снова блокировка и распухание) Contract — только когда всё переехало и старый код мёртв, убираешь лишнее и навешиваешь констрейнты Между шагами катятся отдельные релизы Да, дольше, зато без даунтайма 🛠 Конкретные приёмы — Индексы строй CREATE INDEX CONCURRENTLY — он не блокирует запись, строится в фоне Медленнее, но прод жив - Колонку добавляй сначала nullable, потом бэкфилли данные пачками, и тол
89 ·
E
EvApps
👀 Код-ревью, которое помогает, а не бесит Ревью обычно скатывается в одну из двух крайностей Либо «LGTM 👍» по диагонали через тридцать секунд — и тогда оно не ловит вообще ничего Либо сорок комментариев про кавычки, отступы и «а я бы назвал переменную иначе» — и тогда автор просто начинает ненавидеть ревью Обе крайности бесполезны Разберём, что делает ревью реально работающим 🎯 Раздели блокеры и придирки Главная беда плохого ревью — всё свалено в кучу Баг в логике и пробел не там лежат в комментариях с одинаковым весом, и автор тонет( переписать всю строку) Раздели явно: - Блокеры - то, что нельзя мержить: баги, дыры в безопасности, сломанная логика, архитектурная ошибка, которую потом дорого разгребать - Придирки - вкусовщина и мелочи Помечай их прямо, например nit: - мол, «на твоё усмотрение, мержу в любом случае» Когда автор видит, где горит, а где просто мнение — он чинит важное и не залипает на ерунде 🤖 Стиль — не человеческая работа Если у тебя в комментариях к PR всплывают отступы, кавычки, порядок импортов и длина строки — это провал процесса, а не ревью Всё это отдаётся линтеру и автоформаттеру, которые гоняются в CI Человек не должен тратить внимание на то, что машина проверит идеально и без обид Освободи ревью для того, что машина не умеет — смысл и решения 💬 Спрашивай, а не приказывай Тон решает «Исправь тут» звучит как приговор и включает у автора защиту «А что будет, если сюда придёт null?» — это вопрос, который ведёт автора к проблеме самого Часто выясняется, что он предусмотрел то, чего ты не заметил — или наоборот, сам натыкается на дыру Ревью — это диалог, а не проверка домашки красной ручкой 📦 Маленькие PR — половина успеха Это уже к автору Гигантский PR на две тысячи строк физически невозможно отревьюить внимательно — глаз замыливается, и ревьюер начинает пролистывать и штамповать «ок» Чем больше диф, тем поверхностнее ревью, это работает железно Режь задачу на маленькие куски — их реально прочитать вдумчиво, и баги ловятся, а не прос
1 · 77 ·
E
EvApps
🧠 Почему AI-агент тупеет к концу длинного диалога Начинаешь сессию с агентом - он бодрый, умный, всё схватывает Через час работы в том же чате он начинает забывать, что ты просил в начале, путаться, повторяться и городить чушь Кажется, что модель "устала" На самом деле ты уперся в то, как устроен контекст - и это чинится, если понимать механику 📏 У модели есть окно, и оно не резиновое Модель не помнит диалог как человек На каждый запрос ей заново скармливается весь ваш разговор целиком - вся история сообщений, файлы, что ты кидал, её собственные ответы Это и есть контекстное окно, и оно ограничено Чем дольше сессия, тем оно забитее И проблема не только в том, что "место кончается" Проблема в том, что происходит с вниманием модели по мере заполнения 🌀 Context rot - почему растёт мусор, падает толк Когда контекст маленький и по делу - модель держит всё в фокусе Когда он раздувается до тысяч строк переписки, отладочных логов и десяти версий одного файла - внимание модели размазывается по всей этой каше Нужные детали тонут среди неактуального Отсюда знакомые симптомы: - забывает инструкцию, которую ты дал в начале - тащит устаревший вариант кода, который вы давно переписали - повторяет то, что уже делал - начинает уверенно выдумывать Есть ещё эффект "потерянного в середине": модель лучше всего держит начало и конец контекста, а то, что застряло где-то посередине длинной простыни, замечает хуже Так что важная деталь из середины часового диалога легко проходит мимо 💸 Плюс это ещё и дорого Раз весь диалог гоняется на каждый запрос, то чем он длиннее, тем больше токенов ты жжёшь и тем дольше ждёшь ответ Раздутый контекст бьёт и по качеству, и по кошельку, и по скорости одновременно 🛠 Что с этим делать Приёмы простые, но их мало кто применяет: - Новая задача — новая сессия Не тащи в свежую фичу контекст, забитый вчерашней отладкой Чистый чат почти всегда умнее замусоренного - Подкидывай только релевантное Не вываливай весь репозиторий "на всякий случай" — дай те два-
73 ·
EvApps
Фотография
нажмите — покажем
Пока вы наслаждаетесь пятницей, мы напоминаем, что уже завтра стартует одна из наших любимых IT-конференций - "Город IT" в Томске В этом году наш CEO Альфред Столяров и зам.директора по персоналу ООО "ЦИТ" Евгения Добижа расскажут, почему прекрасная эпоха в IT закончилась - и как нам с этим жить Приглашаем разработчиков, аналитиков, продактов и проджектов, а также ИТ-руководителей на секцию "Как меняется IT в России — взгляд со стороны бизнеса". Томск, площадь Ленина, 12А, главный зал 13 сентября 13:30-15:30 по местному времени Регистрация все еще идет: clck.ru/3Vm5yc
74 ·
E
🔌 "Too many connections" - и почему база падает под нагрузкой Под нагрузкой прилетает FATAL: too many connections, база отказывается принимать запросы, всё стоит Первая мысль - "надо поднять лимит соединений в базе" Обычно это лечение симптома, а не причины А теперь, откуда берётся упор в соединения и почему пул решает это правильно 💰 Соединение к базе - дорогое удовольствие Каждое подключение к базе - это не бесплатная абстракция В том же Postgres на каждое соединение заводится отдельный процесс со своей памятью Их число жёстко ограничено (max_connections), и не просто так: тысяча соединений - это тысяча процессов, которые сжирают память и заставляют базу тратить силы на переключение между ними, а не на работу Поэтому "просто поднять лимит до 5000" - плохая идея Ты не уберёшь проблему, а перенесёшь базу из состояния "отказывает" в состояние "еле дышит" 💥 Как упираешься в потолок Приложение открывает новое соединение на каждый запрос и закрывает после На малой нагрузке норм Но под потоком - сто параллельных запросов открывают сто соединений одновременно, тысяча запросов - тысячу Лимит выбивается мгновенно, и новые запросы получают отказ При этом само соединение ещё и открывается не моментально - на установку уходит время, так что ты платишь дважды 🏊 Пул соединений Идея простая - не открывать соединение под каждый запрос, а держать наготове небольшой набор уже открытых и переиспользовать их Запросу нужна база - он берёт свободное соединение из пула, отработал - вернул обратно, не закрывая Соединений мало, они постоянно в деле, и на установку время не тратится Пул бывает двух видов: встроенный в приложение (HikariCP в Java, пулеры в других языках) и внешний - отдельный процесс перед базой (для Postgres это PgBouncer) Внешний особенно важен, когда инстансов приложения много ⚠️ Ловушка масштабирования У тебя двадцать инстансов приложения, у каждого свой пул на 20 соединений Двадцать раз по двадцать — это уже 400 соединений к базе, даже если сейчас тихо Раската
79 ·
E
EvApps
🗃 Кэш поставили, а он отдаёт старьё В программировании две сложные вещи - инвалидация кэша и придумывание имён С кэшем так и вышло Закэшировать - минута работы, а заставить вовремя отдавать свежее - вот где начинается веселье Разберём, почему кэш врёт и как с этим жить 🎯 Сложность не в том, чтобы закэшировать Положить данные в кэш легко Вся жара в другом: когда копия протухла и её пора выкинуть? Данные в базе поменялись, а в кэше лежит старьё - и юзер видит вчерашний день Вот это "когда чистить" и есть главная боль ⏳ Вариант 1: TTL, само протухает по времени Кладёшь данные на N минут, дальше они испаряются, и следующий запрос тянет свежак из базы Плюс - просто, ничего не забудешь Минус - есть окно: данные уже поменялись, а TTL ещё не вышел, и юзер видит старое Для ленты или счётчика лайков норм, минутная задержка никого не убьёт Для баланса или прав доступа - уже стрёмно 🎯 Вариант 2: сбрасывать кэш при изменении Поменял данные - сразу выкинул их из кэша (или перезаписал) Точность идеальная, старья нет Звучит красиво, но тут прячется главная подстава Данные меняются не в одном месте Есть основная ручка апдейта - её помнят А ещё админка, фоновый джоб, миграция, импорт, соседний сервис - и если хоть один путь поменял данные, но забыл сбросить кэш, получаешь вечно устаревшую запись, которую потом фиг найдёшь Чем больше мест правит данные, тем легче забыть про одно 💥 И ещё пара граблей - Лавина (стампед). Популярный ключ протух - и в ту же секунду сотня запросов ломанулась в базу пересчитывать одно и то же Кэш, который снимал нагрузку, наоборот устраивает базе микро-DDoS Лечится так: пересчитывает кто-то один, остальные ждут результат - Сброс из пушки по воробьям. Из страха отдать старьё некоторые на любое изменение чистят пол-кэша Формально всё свежее, а толку ноль - кэш вечно пустой и не работает 🛠 Как выбирать - Данные терпят лёгкое устаревание (лента, счётчики, справочники) - бери TTL, просто и без риска забыть - Устаревание недопустимо (деньги, права, стату
82 ·
E
EvApps
🚨 Как перевод денег уронил нам прод ⏰ 19:10 Задеплоили долгожданное - переводы между кошельками юзеров. Списываем с одного, зачисляем на другой, всё в одной транзакции, чтобы деньги не потерялись На стейдже гоняли, всё зелёное ⏰ 19:40 Посыпались ошибки, часть переводов падает В логах - "deadlock detected" Не таймаут, не отвал базы, а взаимная блокировка Вылезает только когда переводов много и идут они параллельно ⏰ 19:55 Картина складывается Перевод от Ани к Боре берёт блокировку на строку Ани, потом тянется за строкой Бори А в ту же секунду перевод от Бори к Ане блокирует строку Бори и тянется за строкой Ани Оба ждут друг друга, никто не отпустит База ловит это сама: видит цикл ожидания, убивает одну из транзакций с ошибкой дедлока Юзер получает "перевод не прошёл", хотя по сути ничего не сломано ⏰ 20:20 Причина не в переводах, а в порядке Мы блокировали строки как "сначала отправитель, потом получатель" Направление у переводов разное, вот встречные пары и встают лицом к лицу Лочим строки в одном и том же порядке, независимо от того, кто кому платит Взяли сортировку по id: сначала строка с меньшим id, потом с большим Теперь две встречные транзакции идут за строками одинаково, и вместо взаимной блокировки одна ждёт другую 🧠 Что забрали с собой - Дедлок - это про порядок захвата ресурсов, а не про их количество Двум транзакциям хватает взять два одинаковых замка в разной последовательности - На стейдже такое не ловится, там нет параллельной нагрузки Дедлок живёт только там, где реально пересекаются одновременные операции - Трогаешь в транзакции несколько строк - бери их в детерминированном порядке По id, по ключу, как угодно, лишь бы одинаково у всех - И держи в голове: база всё равно иногда будет кидать дедлоки, это норма под нагрузкой Ретрай на такую ошибку - не костыль, а штатный сценарий Одна строчка сортировки, а стоила вечера и пачки упавших переводов А вы ловили дедлоки? На чём - переводы, счётчики, обновление связанных таблиц? 🤔 #backend #database #
79 ·
E
EvApps
🎭 Мифы про производительность, в которые верят даже опытные Миф 1: "Меньше строк кода - быстрее работает" На самом деле длина кода и скорость почти не связаны Однострочник с хитрой магией может быть медленнее и в разы хуже читаться, чем пять понятных строк Компилятор и рантайм оптимизируют лучше, когда видят простой код, а не эквилибристику Пиши понятно, а не коротко Миф 2: "Оптимизировать надо везде" На самом деле почти весь код не на горячем пути - он выполняется редко, и его скорость никого не волнует Реальные тормоза сидят в паре мест, и найти их можно только профайлером, а не на глаз Оптимизировать всё подряд - значит усложнять код там, где это не даёт ничего, и раздувать сроки Миф 3: "Кэш всегда ускоряет" На самом деле кэш добавляет отдельный слой со своими багами - инвалидацией, устареванием, лавинами на протухших ключах Иногда убрать лишний запрос к базе или повесить индекс даёт тот же выигрыш, но без нового источника проблем Кэш - это размен скорости на сложность Миф 4: "Больше потоков - быстрее" На самом деле только до предела Потоков больше, чем ядер и ресурсов, - и они начинают толкаться, драться за блокировки и тратить время на переключение В какой-то момент добавление потоков не ускоряет, а замедляет Параллелизм помогает ровно до того места, где упираешься в реальное железо Миф 5: "База медленная, вынесу логику в код" На самом деле база десятилетиями заточена под работу с данными - фильтрацию, сортировку, агрегацию Вытащить всё в приложение и крутить руками обычно медленнее, чем один нормальный запрос Тот же N+1 - это как раз попытка сделать в коде то, что база сделала бы одним движением Общий смысл под всеми пятью один: производительность - это не про интуицию и красивые приёмы, а про измерения, и использование только там где надо Не гадай, где медленно - померь профайлером, и часто окажется, что тормозит совсем не там, где ты собирался оптимизировать А какие мифы вы слышали слышали и сами так думали до определенного момента? 🤔 #performance
76 ·
E
EvApps
🧩 Что тут не так? Код-загадка Формат новый - показываю код, ты угадываешь подвох, ниже разбор Питон, но грабли универсальные def add_item(item, cart=[]): cart.append(item) return cart Функция кладёт товар в корзину Если корзину не передали - создаёт пустую Логично же? Вопрос: что вернёт код, если вызвать функцию три раза подряд для разных юзеров, каждый раз без передачи корзины? add_item("яблоко") # ? add_item("хлеб") # ? add_item("молоко") # ? Интуиция говорит: каждый вызов - новая пустая корзина, значит вернётся ["яблоко"], потом ["хлеб"], потом ["молоко"] . . . А на деле: ["яблоко"] ["яблоко", "хлеб"] ["яблоко", "хлеб", "молоко"] Корзина одна на всех, и товары в неё накапливаются Три разных юзера сложили покупки в общую тележку 🔍 Почему так Значение по умолчанию (этот самый []) создаётся один раз - в момент, когда питон читает определение функции, а не при каждом вызове Дальше все вызовы, которые не передали свой аргумент, работают с одним и тем же списком Он живёт между вызовами и копит всё, что в него положили Особенно весело это выстреливает на проде: локально по одному запросу всё чисто, а под потоком данные разных юзеров начинают протекать друг к другу Ловится потом такое тяжело - код же выглядит правильным 🛠 Как чинить Дефолтом ставим не список, а None, и создаём свежий список внутри: def add_item(item, cart=None): if cart is None: cart = [] cart.append(item) return cart Теперь каждый вызов без корзины получает свою собственную, пустую Изменяемый объект (список, словарь, множество) в значении по умолчанию - почти всегда мина Ставь None и создавай внутри Кто угадал подвох с первого взгляда - press f? 🤔 #python #dev #programming #backend #bugs #квиз
1 · 66 ·
EvApps
Фотография
нажмите — покажем
57 ·
Хотите не теряться среди сотен откликов? На hh сейчас бывает ощущение, что до HR просто не докричаться. И небольшое откровение HR: вас правда очень и очень много 😄 Поэтому в EvApps мы решили сделать по-другому и создали закрытый канал EvApps Talent | Projects, где новые проектные заявки будут появляться в первую очередь. Попасть туда смогут только избранные. Шучу 😄 Но не совсем. В канал мы приглашаем только тех, кто прошёл наше техническое собеседование, подтвердил навыки из резюме и подходит нам по soft skills. На технички сейчас в первую очередь зовём Middle/Senior IT-специалистов: разработчиков, аналитиков, специалистов по данным, DevOps, 1С и другим техническим направлениям. Java, .NET и QA сейчас не в фокусе. Смысл простой: когда появится подходящий проект, вам не придётся снова пробиваться через сотни откликов. Вы увидите заявку в канале, а мы уже будем знать ваш уровень и опыт. Хотите попробовать попасть внутрь — напишите в личку HR (@olga_panova): «Хочу в закрытый канал» и прикрепите свое актуальное резюме Посмотрим резюме, познакомимся и, если профиль подходит под направления, с которыми мы работаем, договоримся о техническом интервью. Возможно, подходящего проекта для вас нет прямо сейчас. Но когда он появится, вам уже не придётся снова конкурировать за внимание среди сотен откликов.💚
1 · 69 ·
E
🔁 Ретраи, которые добивают лежачий сервис Сервис-сосед начал тупить, отвечает через раз Логично же - повторить запрос, ретрай спасёт Так думает не только твой код, а все сто инстансов сразу И вот тут ретрай из спасения превращается в оружие против самого сервиса 💥 Retry storm База или соседний сервис просели под нагрузкой, отвечают с ошибками Каждый клиент, поймав ошибку, тут же повторяет запрос Был один поток запросов - стало два, потом три Сервис, который и так на пределе, получает в разы больше нагрузки именно в тот момент, когда ему плохо В итоге он ложится окончательно, и ретраи держат его на дне, не давая подняться ⏳ Экспоненциальная задержка Первое лекарство - не долбить сразу Не "ошибка - мгновенно повторил", а "подожди и повтори с растущей паузой": 1 секунда, потом 2, потом 4, потом 8 Так ты даёшь соседу передышку вместо того, чтобы добивать лежащего Плюс всегда ставь: максимум попыток и максимальную задержку, иначе клиенты будут висеть вечно 🎲 Джиттер - без него всё зря А вот про это забывают Даже с экспоненциальной задержкой есть засада: если сотня клиентов упала в одну секунду, то и повторят они синхронно - через 1с все разом, через 2с все разом Получаются волны, которые снова добивают сервис пачками Лечится джиттером - случайной добавкой к задержке Не ровно 2 секунды, а 2 плюс-минус случайные миллисекунды Тогда клиенты размазываются во времени и не бьют залпом Мелочь, а именно она превращает ретраи из волн в ровный аккуратный поток 🛡 Что ещё - Ретрай только идемпотентных операций Повторять "создать платёж" вслепую - привет, двойное списание (об этом был отдельный пост) - Уважай Retry-After Если сервис в ответе явно сказал "приходи через 5 секунд" - слушайся, а не долби по своему расписанию - Circuit breaker Если сосед стабильно падает, перестань ходить к нему вообще на какое-то время - дай ему встать, а сам отдай заглушку Ретраи - штука нужная, но наивный "поймал ошибку - сразу повторил" под нагрузкой не лечит Пауза с ростом, джиттер и потолок поп
68 ·
E
EvApps
🧩 Что тут не так? База MySQL, таблица с юзерами, кодировка utf8 Юзер обновляет имя: UPDATE users SET name = 'Аня 🌸' WHERE id = 42; И вместо успеха прилетает: Incorrect string value: '\xF0\x9F\x8C\xB8' for column 'name' Обычные буквы и цифры сохранялись годами, а тут запрос падает на ровном месте Вопрос: почему невинный цветочек ломает вставку? Потому что кодировка utf8 в MySQL - это не полноценный UTF-8 Историческая засада: их utf8 умеет хранить максимум 3 байта на символ А эмодзи (и часть редких иероглифов, старые символы) - это 4 байта Символ не влезает в отведённое место, база отказывается его писать и кидает ошибку То есть колонка принимает буквы, кириллицу, латиницу - всё, что укладывается в 3 байта А как только прилетает 4-байтовый символ, всё ломается И ловится это не сразу: на тесте с обычными именами чисто, а в проде первый же юзер с эмодзи в нике роняет запрос 🛠 Как чинить Нужна кодировка utf8mb4 - вот она и есть настоящий полный UTF-8 с поддержкой 4 байт: ALTER TABLE users CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; И проверь, что на utf8mb4 переведены все три уровня: сама база, таблицы и подключение приложения к базе Если приложение коннектится в старой utf8, эмодзи всё равно потеряются по дороге, даже когда таблица уже правильная В MySQL utf8 - это мина Всегда бери utf8mb4 с самого начала, тогда эмодзи, редкие языки и всякая экзотика не будут ронять тебе вставки В Postgres, к слову, такой засады нет - там UTF8 сразу полный Ловили это? Эмодзи в имени, в сообщении, в названии - что вам роняло базу? 🤔 #mysql #database #backend #dev #programming #bugs
65 ·
EvApps
🪵 Логи, в которые невозможно ничего понять Прод сломался, открываешь логи - а там каша, по которой не понять, что случилось Логи есть, а толку ноль Как пишут обычно и как надо ❌ Плохо: log.info("ошибка") ✅ Хорошо: log.error("платёж отклонён", user_id=42, order_id=1001, reason="insufficient_funds") Логи читает не глаз, а поиск по ним "ошибка" без контекста - мусор, по которому ничего не найдёшь Пиши что случилось плюс ключевые поля: кто, что, почему Такой лог фильтруется и собирается в статистику ❌ Плохо: log.info("готово") log.info("всё ок") ✅ Хорошо: уровни по делу Когда всё подряд на уровне info, в потоке событий тонет важное Раздели: debug для отладки, info для нормальных событий, warning для странного, но терпимого, error для сломанного В проде отключаешь шум и оставляешь важное ❌ Плохо: log.info(f"юзер {user} с картой {card_number} и токеном {token}") ✅ Хорошо: без секретов Пароли, номера карт, токены, персоналку в логи лить нельзя Логи оседают в куче систем, их видят десятки людей, они утекают Маскируй чувствительное или не пиши вовсе - это требование безопасности, а не просто аккуратность ❌ Плохо: Один запрос размазан по логам без связи ✅ Хорошо: сквозной id запроса Когда параллельно летят сотни запросов, их логи перемешаны в один поток Без общего идентификатора не понять, какие строчки относятся к одному запросу Протащи request_id через все логи запроса (а в микросервисах - через все сервисы), и вытащишь всю цепочку одним фильтром 🎯 Простой критерий Лог ты пишешь не себе и не сейчас Его будет читать дежурный в три ночи - возможно, не ты, а тот, кто твой код видит впервые И момент инцидента уже не переиграть: воспроизвести на нём нельзя, лог - единственный след Так что проверяй каждую строчку одним вопросом: хватит ли этого незнакомому человеку, чтобы понять, что сломалось, без похода в исходники? Хватит - лог свою работу сделал Расскажите в комментариях, самые смешные логи, которые вы встречали на проектах и продуктах, где работали? 🤔 #backend #l
48 ·
E
Фотография
нажмите — покажем
❗️Напоминаем, что уже завтра продет наш вебинар с «Белым кодом» о том, как выбрать шину данных и какая команда нужна для ее внедрения и проверки. 🔥 Регистрация еще идет здесь: https://evapps.timepad.ru/event/4161537/
54 ·
E
EvApps
🎓 Мидл против джуна: как AI сдвинул границу Раньше спорили, чем мидл отличается от джуна - фреймворками, знанием языка, опытом AI взял и обнулил половину этого спора То, что модель теперь пишет за всех, перестало быть отличием вообще А то, что отличало мидла и раньше, стало единственным, что осталось Пройдёмся, как сместилась граница ⌨️ Синтаксис больше не в счёт Знание языка, бойлерплейт, "как написать вот это на питоне" - раньше на этом джун и рос, этим и мерился Теперь это выдаёт модель за секунды, одинаково хорошо у всех Граница "кто лучше помнит синтаксис" просто испарилась Осталось то, что вокруг кода, а не сам код 🧩 Задачу решает модель Последствия видит человек Раньше джун писал то, что просили, и оно работало, а мидл заранее прикидывал: что под нагрузкой, что при кривых данных, что сломается у соседей Сейчас код выдаёт AI - быстро и уверенно И вопрос "а что оно сломает" никуда не делся, только теперь его должен задать человек, который вычитывает результат Джун жмёт "принять", мидл читает диф и видит, где рванёт 🛑 «А надо ли это вообще» подорожало AI генерит код почти бесплатно, и соблазн навалить его вырос в разы - зачем думать, если модель за минуту напишет Мидловое "стоп, а эту штуку вообще надо делать? может, это решается настройкой или уже где-то есть?" стало ценнее прежнего Самый дешёвый код по-прежнему тот, который не написали - только теперь его так легко написать, что удержаться труднее 🔍 Теперь весь код - чужой Раньше джун терялся в легаси, а мидл умел въезжать в чужую кодовую базу и аккуратно её менять AI сделал чужим вообще весь код: модель пишет много, пишет уверенно, но не знает твой проект Навык "прочитать незнакомый код и понять, где он врёт" из полезного превратился в обязательный - потому что теперь ты читаешь чужое каждый день 🎯 Ответственность осталась на человеке Тут AI не поменял ничего - и это главное Модель написала, а отвечаешь ты "Оно так сгенерилось" - не ответ ни ревьюеру, ни бизнесу, ни себе в три ночи, когда оно падает
39 ·

Открытая публичная лента из поискового индекса ChatCrawler — «Google по публичному Telegram»; обновляется по мере обхода площадки. Время — UTC.

Только публичный контент, официальный API Telegram. О проекте · Вопросы · Чего мы не делаем · Убрать страницу из выдачи · Каталог · Поиск · Как мы считаем