EvApps


Гео и язык канала: Россия, Русский


IT-aутстафферы из Тулы💚
https://evapps.ru/
Здесь пишем про веб- и мобильную разработку
▶️ Наш чат для системных аналитиков: https://t.me/pro_sa_evapps
▶️ Посмотреть, как мы живём: https://vk.com/evapps

Связанные каналы  |  Похожие каналы

Гео и язык канала
Россия, Русский
Статистика
Фильтр публикаций


🎓 Мидл против джуна: как AI сдвинул границу
Раньше спорили, чем мидл отличается от джуна - фреймворками, знанием языка, опытом
AI взял и обнулил половину этого спора
То, что модель теперь пишет за всех, перестало быть отличием вообще
А то, что отличало мидла и раньше, стало единственным, что осталось
Пройдёмся, как сместилась граница

⌨️ Синтаксис больше не в счёт
Знание языка, бойлерплейт, "как написать вот это на питоне" - раньше на этом джун и рос, этим и мерился
Теперь это выдаёт модель за секунды, одинаково хорошо у всех
Граница "кто лучше помнит синтаксис" просто испарилась
Осталось то, что вокруг кода, а не сам код

🧩 Задачу решает модель
Последствия видит человек
Раньше джун писал то, что просили, и оно работало, а мидл заранее прикидывал: что под нагрузкой, что при кривых данных, что сломается у соседей
Сейчас код выдаёт AI - быстро и уверенно
И вопрос "а что оно сломает" никуда не делся, только теперь его должен задать человек, который вычитывает результат
Джун жмёт "принять", мидл читает диф и видит, где рванёт

🛑 «А надо ли это вообще» подорожало
AI генерит код почти бесплатно, и соблазн навалить его вырос в разы - зачем думать, если модель за минуту напишет Мидловое "стоп, а эту штуку вообще надо делать?
может, это решается настройкой или уже где-то есть?" стало ценнее прежнего
Самый дешёвый код по-прежнему тот, который не написали - только теперь его так легко написать, что удержаться труднее

🔍 Теперь весь код - чужой
Раньше джун терялся в легаси, а мидл умел въезжать в чужую кодовую базу и аккуратно её менять
AI сделал чужим вообще весь код: модель пишет много, пишет уверенно, но не знает твой проект
Навык "прочитать незнакомый код и понять, где он врёт" из полезного превратился в обязательный - потому что теперь ты читаешь чужое каждый день

🎯 Ответственность осталась на человеке
Тут AI не поменял ничего - и это главное
Модель написала, а отвечаешь ты
"Оно так сгенерилось" - не ответ ни ревьюеру, ни бизнесу, ни себе в три ночи, когда оно падает
Джун отвечает за "код появился", мидл - за "оно работает в проде и я починю, если что". Эта граница как была, так и осталась, только цена ошибки выросла

Раньше джун набивал шишки как раз на той рутине, которую теперь делает AI - на бойлерплейте, тестах, мелких багах Через эту скучную работу и вырастало чутьё
Модель эту ступеньку убрала, а мидловые навыки суждения требуются сразу
Откуда возьмутся новые мидлы, если нижнюю ступень лестницы забрал AI - вопрос, на который пока ни у кого нет хорошего ответа

А вы как считаете - AI делает мидлов из джунов быстрее или, наоборот, ломает путь наверх? 🤔

#career #ai #dev #programming #junior #softskills


❗️Напоминаем, что уже завтра продет наш вебинар с «Белым кодом» о том, как выбрать шину данных и какая команда нужна для ее внедрения и проверки.

🔥 Регистрация еще идет здесь: https://evapps.timepad.ru/event/4161537/


🪵 Логи, в которые невозможно ничего понять
Прод сломался, открываешь логи - а там каша, по которой не понять, что случилось
Логи есть, а толку ноль
Как пишут обычно и как надо
❌ Плохо:
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 #logging #observability #dev #programming #devops


🧩 Что тут не так?
База 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


🔁 Ретраи, которые добивают лежачий сервис
Сервис-сосед начал тупить, отвечает через раз
Логично же - повторить запрос, ретрай спасёт
Так думает не только твой код, а все сто инстансов сразу
И вот тут ретрай из спасения превращается в оружие против самого сервиса

💥 Retry storm
База или соседний сервис просели под нагрузкой, отвечают с ошибками
Каждый клиент, поймав ошибку, тут же повторяет запрос
Был один поток запросов - стало два, потом три
Сервис, который и так на пределе, получает в разы больше нагрузки именно в тот момент, когда ему плохо
В итоге он ложится окончательно, и ретраи держат его на дне, не давая подняться

⏳ Экспоненциальная задержка
Первое лекарство - не долбить сразу
Не "ошибка - мгновенно повторил", а "подожди и повтори с растущей паузой": 1 секунда, потом 2, потом 4, потом 8
Так ты даёшь соседу передышку вместо того, чтобы добивать лежащего
Плюс всегда ставь: максимум попыток и максимальную задержку, иначе клиенты будут висеть вечно

🎲 Джиттер - без него всё зря
А вот про это забывают
Даже с экспоненциальной задержкой есть засада: если сотня клиентов упала в одну секунду, то и повторят они синхронно - через 1с все разом, через 2с все разом
Получаются волны, которые снова добивают сервис пачками

Лечится джиттером - случайной добавкой к задержке
Не ровно 2 секунды, а 2 плюс-минус случайные миллисекунды
Тогда клиенты размазываются во времени и не бьют залпом
Мелочь, а именно она превращает ретраи из волн в ровный аккуратный поток

🛡 Что ещё
- Ретрай только идемпотентных операций
Повторять "создать платёж" вслепую - привет, двойное списание (об этом был отдельный пост)
- Уважай Retry-After
Если сервис в ответе явно сказал "приходи через 5 секунд" - слушайся, а не долби по своему расписанию
- Circuit breaker
Если сосед стабильно падает, перестань ходить к нему вообще на какое-то время - дай ему встать, а сам отдай заглушку

Ретраи - штука нужная, но наивный "поймал ошибку - сразу повторил" под нагрузкой не лечит
Пауза с ростом, джиттер и потолок попыток - вот что превращает их из проблемы в защиту

Свой сервис заваливали ретраями? 🤔

#backend #reliability #distributed #dev #programming #devops


Хотите не теряться среди сотен откликов?

На hh сейчас бывает ощущение, что до HR просто не докричаться. И небольшое откровение HR: вас правда очень и очень много 😄
Поэтому в EvApps мы решили сделать по-другому и создали закрытый канал EvApps Talent | Projects, где новые проектные заявки будут появляться в первую очередь.

Попасть туда смогут только избранные. Шучу 😄 Но не совсем.
В канал мы приглашаем только тех, кто прошёл наше техническое собеседование, подтвердил навыки из резюме и подходит нам по soft skills.
На технички сейчас в первую очередь зовём Middle/Senior IT-специалистов: разработчиков, аналитиков, специалистов по данным, DevOps, 1С и другим техническим направлениям. Java, .NET и QA сейчас не в фокусе.
Смысл простой: когда появится подходящий проект, вам не придётся снова пробиваться через сотни откликов. Вы увидите заявку в канале, а мы уже будем знать ваш уровень и опыт.

Хотите попробовать попасть внутрь — напишите в личку HR (@olga_panova): «Хочу в закрытый канал» и прикрепите свое актуальное резюме

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




🧩 Что тут не так? Код-загадка
Формат новый - показываю код, ты угадываешь подвох, ниже разбор
Питон, но грабли универсальные
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: "Меньше строк кода - быстрее работает"

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

Миф 2: "Оптимизировать надо везде"
На самом деле почти весь код не на горячем пути - он выполняется редко, и его скорость никого не волнует
Реальные тормоза сидят в паре мест, и найти их можно только профайлером, а не на глаз
Оптимизировать всё подряд - значит усложнять код там, где это не даёт ничего, и раздувать сроки

Миф 3: "Кэш всегда ускоряет"
На самом деле кэш добавляет отдельный слой со своими багами - инвалидацией, устареванием, лавинами на протухших ключах
Иногда убрать лишний запрос к базе или повесить индекс даёт тот же выигрыш, но без нового источника проблем
Кэш - это размен скорости на сложность

Миф 4: "Больше потоков - быстрее"
На самом деле только до предела
Потоков больше, чем ядер и ресурсов, - и они начинают толкаться, драться за блокировки и тратить время на переключение
В какой-то момент добавление потоков не ускоряет, а замедляет
Параллелизм помогает ровно до того места, где упираешься в реальное железо

Миф 5: "База медленная, вынесу логику в код"
На самом деле база десятилетиями заточена под работу с данными - фильтрацию, сортировку, агрегацию
Вытащить всё в приложение и крутить руками обычно медленнее, чем один нормальный запрос
Тот же N+1 - это как раз попытка сделать в коде то, что база сделала бы одним движением

Общий смысл под всеми пятью один: производительность - это не про интуицию и красивые приёмы, а про измерения, и использование только там где надо
Не гадай, где медленно - померь профайлером, и часто окажется, что тормозит совсем не там, где ты собирался оптимизировать

А какие мифы вы слышали слышали и сами так думали до определенного момента? 🤔

#performance #backend #optimization #dev #programming #database


🚨 Как перевод денег уронил нам прод
⏰ 19:10

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

⏰ 19:40
Посыпались ошибки, часть переводов падает
В логах - "deadlock detected"
Не таймаут, не отвал базы, а взаимная блокировка
Вылезает только когда переводов много и идут они параллельно

⏰ 19:55
Картина складывается
Перевод от Ани к Боре берёт блокировку на строку Ани, потом тянется за строкой Бори
А в ту же секунду перевод от Бори к Ане блокирует строку Бори и тянется за строкой Ани
Оба ждут друг друга, никто не отпустит

База ловит это сама: видит цикл ожидания, убивает одну из транзакций с ошибкой дедлока
Юзер получает "перевод не прошёл", хотя по сути ничего не сломано

⏰ 20:20
Причина не в переводах, а в порядке
Мы блокировали строки как "сначала отправитель, потом получатель"
Направление у переводов разное, вот встречные пары и встают лицом к лицу

Лочим строки в одном и том же порядке, независимо от того, кто кому платит
Взяли сортировку по id: сначала строка с меньшим id, потом с большим
Теперь две встречные транзакции идут за строками одинаково, и вместо взаимной блокировки одна ждёт другую

🧠 Что забрали с собой
- Дедлок - это про порядок захвата ресурсов, а не про их количество
Двум транзакциям хватает взять два одинаковых замка в разной последовательности
- На стейдже такое не ловится, там нет параллельной нагрузки
Дедлок живёт только там, где реально пересекаются одновременные операции
- Трогаешь в транзакции несколько строк - бери их в детерминированном порядке
По id, по ключу, как угодно, лишь бы одинаково у всех
- И держи в голове: база всё равно иногда будет кидать дедлоки, это норма под нагрузкой
Ретрай на такую ошибку - не костыль, а штатный сценарий

Одна строчка сортировки, а стоила вечера и пачки упавших переводов

А вы ловили дедлоки? На чём - переводы, счётчики, обновление связанных таблиц? 🤔

#backend #database #postgres #concurrency #dev #incident


🗃 Кэш поставили, а он отдаёт старьё
В программировании две сложные вещи - инвалидация кэша и придумывание имён
С кэшем так и вышло
Закэшировать - минута работы, а заставить вовремя отдавать свежее - вот где начинается веселье Разберём, почему кэш врёт и как с этим жить

🎯 Сложность не в том, чтобы закэшировать
Положить данные в кэш легко
Вся жара в другом: когда копия протухла и её пора выкинуть?
Данные в базе поменялись, а в кэше лежит старьё - и юзер видит вчерашний день
Вот это "когда чистить" и есть главная боль

⏳ Вариант 1: TTL, само протухает по времени

Кладёшь данные на N минут, дальше они испаряются, и следующий запрос тянет свежак из базы

Плюс - просто, ничего не забудешь
Минус - есть окно: данные уже поменялись, а TTL ещё не вышел, и юзер видит старое
Для ленты или счётчика лайков норм, минутная задержка никого не убьёт
Для баланса или прав доступа - уже стрёмно

🎯 Вариант 2: сбрасывать кэш при изменении
Поменял данные - сразу выкинул их из кэша (или перезаписал)
Точность идеальная, старья нет

Звучит красиво, но тут прячется главная подстава
Данные меняются не в одном месте
Есть основная ручка апдейта - её помнят
А ещё админка, фоновый джоб, миграция, импорт, соседний сервис - и если хоть один путь поменял данные, но забыл сбросить кэш, получаешь вечно устаревшую запись, которую потом фиг найдёшь
Чем больше мест правит данные, тем легче забыть про одно

💥 И ещё пара граблей
- Лавина (стампед). Популярный ключ протух - и в ту же секунду сотня запросов ломанулась в базу пересчитывать одно и то же
Кэш, который снимал нагрузку, наоборот устраивает базе микро-DDoS
Лечится так: пересчитывает кто-то один, остальные ждут результат
- Сброс из пушки по воробьям. Из страха отдать старьё некоторые на любое изменение чистят пол-кэша
Формально всё свежее, а толку ноль - кэш вечно пустой и не работает

🛠 Как выбирать
- Данные терпят лёгкое устаревание (лента, счётчики, справочники) - бери TTL, просто и без риска забыть
- Устаревание недопустимо (деньги, права, статусы) - сбрасывай явно при записи, но тогда честно найди ВСЕ места, где данные меняются, и сбрось в каждом
Пропустил одно - поймал баг
- Не уверен, что надёжно инвалидируешь - честнее не кэшировать, чем потом ловить призрачные баги со старьём

Прежде чем кэшировать, ответь себе, как будешь это оттуда выкидывать
Нет ответа - не кэшируй

А вас кэш подставлял? 🤔

#backend #performance #architecture #dev #programming #cache


🔌 "Too many connections" - и почему база падает под нагрузкой
Под нагрузкой прилетает FATAL: too many connections, база отказывается принимать запросы, всё стоит
Первая мысль - "надо поднять лимит соединений в базе"
Обычно это лечение симптома, а не причины
А теперь, откуда берётся упор в соединения и почему пул решает это правильно

💰 Соединение к базе - дорогое удовольствие
Каждое подключение к базе - это не бесплатная абстракция
В том же Postgres на каждое соединение заводится отдельный процесс со своей памятью
Их число жёстко ограничено (max_connections), и не просто так: тысяча соединений - это тысяча процессов, которые сжирают память и заставляют базу тратить силы на переключение между ними, а не на работу
Поэтому "просто поднять лимит до 5000" - плохая идея
Ты не уберёшь проблему, а перенесёшь базу из состояния "отказывает" в состояние "еле дышит"

💥 Как упираешься в потолок
Приложение открывает новое соединение на каждый запрос и закрывает после
На малой нагрузке норм
Но под потоком - сто параллельных запросов открывают сто соединений одновременно, тысяча запросов - тысячу Лимит выбивается мгновенно, и новые запросы получают отказ
При этом само соединение ещё и открывается не моментально - на установку уходит время, так что ты платишь дважды

🏊 Пул соединений
Идея простая - не открывать соединение под каждый запрос, а держать наготове небольшой набор уже открытых и переиспользовать их
Запросу нужна база - он берёт свободное соединение из пула, отработал - вернул обратно, не закрывая
Соединений мало, они постоянно в деле, и на установку время не тратится
Пул бывает двух видов:
встроенный в приложение (HikariCP в Java, пулеры в других языках) и внешний - отдельный процесс перед базой (для Postgres это PgBouncer)
Внешний особенно важен, когда инстансов приложения много

⚠️ Ловушка масштабирования

У тебя двадцать инстансов приложения, у каждого свой пул на 20 соединений
Двадцать раз по двадцать — это уже 400 соединений к базе, даже если сейчас тихо
Раскатал автоскейлингом полсотни подов - и снова упёрся в лимит, хотя пулы вроде есть
Вот тут и ставят один общий внешний пулер (PgBouncer) перед базой: приложения ходят в него, а он держит небольшое число реальных соединений к базе

📉 И размер пула - не "чем больше, тем лучше"
Ещё контринтуитивная штука - огромный пул часто медленнее маленького
Если соединений больше, чем база способна реально обрабатывать параллельно (а это упирается в число ядер и диски), они начинают толкаться и мешать друг другу
Небольшой пул, где запросы аккуратно ждут своей очереди, нередко даёт больший throughput, чем раздутый
Так что размер подбирают под возможности базы, а не "на глаз побольше"

too many connections - это почти всегда поставь пул, а под много инстансов — общий внешний пулер, и потолок перестанет быть потолком

А вы на чём ловили «too many connections» — забыли пул, разъехались по инстансам, или крутанули autoscaling? 🤔

#backend #database #postgres #performance #devops #dev


Пока вы наслаждаетесь пятницей, мы напоминаем, что уже завтра стартует одна из наших любимых IT-конференций - "Город IT" в Томске

В этом году наш CEO Альфред Столяров и зам.директора по персоналу ООО "ЦИТ" Евгения Добижа расскажут, почему прекрасная эпоха в IT закончилась - и как нам с этим жить

Приглашаем разработчиков, аналитиков, продактов и проджектов, а также ИТ-руководителей на секцию "Как меняется IT в России — взгляд со стороны бизнеса".

Томск, площадь Ленина, 12А, главный зал
13 сентября 13:30-15:30 по местному времени
Регистрация все еще идет: clck.ru/3Vm5yc


🧠 Почему AI-агент тупеет к концу длинного диалога

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

📏 У модели есть окно, и оно не резиновое
Модель не помнит диалог как человек
На каждый запрос ей заново скармливается весь ваш разговор целиком - вся история сообщений, файлы, что ты кидал, её собственные ответы
Это и есть контекстное окно, и оно ограничено
Чем дольше сессия, тем оно забитее

И проблема не только в том, что "место кончается"
Проблема в том, что происходит с вниманием модели по мере заполнения

🌀 Context rot - почему растёт мусор, падает толк
Когда контекст маленький и по делу - модель держит всё в фокусе
Когда он раздувается до тысяч строк переписки, отладочных логов и десяти версий одного файла - внимание модели размазывается по всей этой каше
Нужные детали тонут среди неактуального

Отсюда знакомые симптомы:
- забывает инструкцию, которую ты дал в начале
- тащит устаревший вариант кода, который вы давно переписали
- повторяет то, что уже делал
- начинает уверенно выдумывать

Есть ещё эффект "потерянного в середине": модель лучше всего держит начало и конец контекста, а то, что застряло где-то посередине длинной простыни, замечает хуже
Так что важная деталь из середины часового диалога легко проходит мимо

💸 Плюс это ещё и дорого
Раз весь диалог гоняется на каждый запрос, то чем он длиннее, тем больше токенов ты жжёшь и тем дольше ждёшь ответ
Раздутый контекст бьёт и по качеству, и по кошельку, и по скорости одновременно

🛠 Что с этим делать
Приёмы простые, но их мало кто применяет:
- Новая задача — новая сессия
Не тащи в свежую фичу контекст, забитый вчерашней отладкой
Чистый чат почти всегда умнее замусоренного
- Подкидывай только релевантное
Не вываливай весь репозиторий "на всякий случай" — дай те два-три файла, что реально нужны
Больше контекста ≠ умнее, часто наоборот
- Делай /compact по ходу
Уперлись в длинный диалог — сделал /compat и с этой выжимкой начинай заново, выкинув простыню
- Держи инструкции ближе к концу
То, что критично прямо сейчас, повтори в свежем сообщении, а не надейся, что модель помнит это из начала

Агент не "тупеет от усталости", он тонет в собственном контексте
Держи контекст чистым и по делу — и модель будет хорошо отрабатывать сколько угодно

А вы как работаете с агентом — льёте всё в один длинный чат или дробите на чистые сессии? 🤔

#ai #llm #dev #programming #productivity #tools


👀 Код-ревью, которое помогает, а не бесит
Ревью обычно скатывается в одну из двух крайностей
Либо «LGTM 👍» по диагонали через тридцать секунд — и тогда оно не ловит вообще ничего
Либо сорок комментариев про кавычки, отступы и «а я бы назвал переменную иначе» — и тогда автор просто начинает ненавидеть ревью
Обе крайности бесполезны
Разберём, что делает ревью реально работающим

🎯 Раздели блокеры и придирки

Главная беда плохого ревью — всё свалено в кучу
Баг в логике и пробел не там лежат в комментариях с одинаковым весом, и автор тонет( переписать всю строку)
Раздели явно:
- Блокеры - то, что нельзя мержить:
баги, дыры в безопасности, сломанная логика, архитектурная ошибка, которую потом дорого разгребать
- Придирки - вкусовщина и мелочи
Помечай их прямо, например nit: - мол, «на твоё усмотрение, мержу в любом случае»
Когда автор видит, где горит, а где просто мнение — он чинит важное и не залипает на ерунде

🤖 Стиль — не человеческая работа
Если у тебя в комментариях к PR всплывают отступы, кавычки, порядок импортов и длина строки — это провал процесса, а не ревью
Всё это отдаётся линтеру и автоформаттеру, которые гоняются в CI
Человек не должен тратить внимание на то, что машина проверит идеально и без обид
Освободи ревью для того, что машина не умеет — смысл и решения

💬 Спрашивай, а не приказывай
Тон решает
«Исправь тут» звучит как приговор и включает у автора защиту
«А что будет, если сюда придёт null?» — это вопрос, который ведёт автора к проблеме самого
Часто выясняется, что он предусмотрел то, чего ты не заметил — или наоборот, сам натыкается на дыру
Ревью — это диалог, а не проверка домашки красной ручкой

📦 Маленькие PR — половина успеха
Это уже к автору
Гигантский PR на две тысячи строк физически невозможно отревьюить внимательно — глаз замыливается, и ревьюер начинает пролистывать и штамповать «ок»
Чем больше диф, тем поверхностнее ревью, это работает железно
Режь задачу на маленькие куски — их реально прочитать вдумчиво, и баги ловятся, а не проскакивают
Ну и главное, ради чего всё это
Ты ревьюишь не «к чему бы придраться», а отвечаешь на один вопрос: понимаю ли я это решение и готов ли поддерживать его завтра, когда автор будет в отпуске
Если да — мержишь
Если нет — разбираешься, пока не да
Всё остальное — шум

А у вас ревью — это про поиск багов и понимание, или больше про вкусовщину и «поставь пробел»? 🤔

#career #teamwork #codereview #dev #programming #softskills


🚀 Как уронить прод одной миграцией
Выкатываешь релиз, в нём миграция - вроде добавить колонку, ерунда
А прод на сорок секунд встаёт колом, запросы висят, алерты орут
Миграции на живой базе - это минное поле, где безобидный ALTER TABLE блокирует всю таблицу
Обо что спотыкаются и как катить схему без даунтайма

🔒 Почему прод встаёт
Многие операции над таблицей берут блокировку, и пока она держится, все запросы к таблице ждут
На маленькой таблице это миллисекунды, а на большой - секунды и десятки секунд, в течение которых сайт фактически лежит
Три главных нарушителя:
- Добавление колонки с NOT NULL и значением по умолчанию
В старых версиях баз это переписывало всю таблицу целиком, с полной блокировкой
Чем больше строк - тем дольше стоишь
- Создание индекса обычным CREATE INDEX - блокирует запись в таблицу на всё время построения
- Переименование или удаление колонки, на которую ещё смотрит работающий код
И самое коварное: во время деплоя одновременно крутятся и старая, и новая версия приложения
Схема уже поменялась, а половина инстансов ещё живёт по-старому
Если миграция ломает старый код — часть юзеров ловит ошибки прямо во время выката

🧩 Главный принцип: expand → migrate → contract
Идея в том, чтобы никогда не менять схему одним резким движением, а разбить на шаги, где на каждом и старый, и новый код живут спокойно:
Expand — расширяешь схему обратимо: добавляешь новое, ничего не ломая
Новая колонка — обязательно nullable, без жёстких констрейнтов
Migrate — переносишь данные и переключаешь код на новое
Бэкфилл делаешь пачками, а не одним UPDATE на миллион строк (иначе снова блокировка и распухание)
Contract — только когда всё переехало и старый код мёртв, убираешь лишнее и навешиваешь констрейнты
Между шагами катятся отдельные релизы
Да, дольше, зато без даунтайма

🛠 Конкретные приёмы — Индексы строй
CREATE INDEX CONCURRENTLY — он не блокирует запись, строится в фоне
Медленнее, но прод жив
- Колонку добавляй сначала nullable, потом бэкфилли данные пачками, и только потом, отдельным шагом, вешай NOT NULL.
- Колонки не переименовывай на живой базе
Добавь новую, дублируй запись в обе, переведи чтение на новую, старую убери потом
Прямой RENAME мгновенно рассинхронит схему и работающий код
- Удаление - тоже в два захода: сперва перестань использовать в коде, выкати, убедись что ничего не отвалилось, и только следующим релизом дропай из базы
Общая мысль: код всегда должен уметь работать и со старой, и с новой схемой одновременно — потому что в момент деплоя ровно так и происходит
Пляши от этого, и миграции перестанут ронять прод

А вас миграция когда-нибудь роняла на проде? На чём именно поймали — индекс, NOT NULL, переименование? 🤔

#backend #database #postgres #devops #dev #sql


🧩 Микросервисы, которые сделали только хуже

Распилили монолит на пятнадцать сервисов - вроде как «сделали по-взрослому»
А через полгода деплой стал сложнее, баг тянется через пять сервисов, латентность выросла, и никто уже не держит в голове, как оно всё связано
История настолько частая, что пора проговорить: микросервисы решают не ту проблему, которую им обычно приписывают

🎯 Микросервисы - это про команды, а не про код

Главное заблуждение: «проект большой, значит пора делить на микросервисы»
Но размер кода тут вообще ни при чём
Микросервисы нужны, когда у тебя много команд, и им тесно в одном репозитории - они мешают друг другу деплоить, релизы стопорятся, каждый выкат - согласование с пятью отделами
Вот тогда распил по сервисам даёт независимость: каждая команда катит своё, когда хочет

А если у тебя одна-две команды - ты этой независимости не получишь, зато купишь все минусы распределённой системы
И минусы не пустяковые

💥 За что реально платишь
Как только сервисы разъехались по сети, вылезает то, чего в монолите не было в принципе:
- Вызов функции превращается в сетевой запрос — а сеть падает, тормозит и таймаутит
То, что раньше было if, теперь требует ретраев, таймаутов и обработки «а если сосед не ответил»
- Транзакции больше не работают как раньше
В монолите ты завернул всё в один BEGIN/COMMIT
А между сервисами общей транзакции нет - и консистентность приходится собирать руками через саги, компенсации и очереди
- Отладка становится квестом
Баг проходит через пять сервисов, и чтобы понять, где сломалось, нужен распределённый трейсинг
Без него ты просто смотришь в пять разных логов и гадаешь
- Версионирование API
Поменял формат ответа - и сломал троих соседей, которые про это не знали

Всё это - нормальная плата за независимость команд
Но если независимости нет, вы просто так усложняете себе жизнь

🧟 Худший вариант - распределённый монолит

Самое опасное - распил, после которого сервисы всё равно связаны намертво
Как понять, что ты попал именно в это:
- сервисы деплоятся только все вместе, по одному никак
- они лезут в одну общую базу и её таблицы
- один запрос юзера - это синхронная цепочка из пяти сервисов, и если падает любой, падает всё

Это худшее из двух миров: сложность распределённой системы и жёсткость монолита одновременно


🛠 Как по уму
Начинай с монолита — но аккуратного, с чёткими внутренними модулями и границами
Внутри одного процесса проводишь линии там, где логика реально разделяется
Когда (и если) команда вырастет или какой-то кусок реально упрётся в нагрузку и потребует отдельного масштабирования — вот тогда отрезаешь его в сервис по уже готовому шву
Резать по живому, «на всякий случай», заранее — почти всегда преждевременно

Микросервисы — это инструмент под конкретную боль: много команд, независимые релизы, части системы с разной нагрузкой
Нет этой боли — модульный монолит сделает то же самое

А у вас микросервисы реально по делу — или распилили, потому что «так модно», и теперь мучаетесь? 🤔

#architecture #backend #microservices #dev #programming #system


🔒 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 Committed две такие транзакции спокойно выполнятся параллельно, обе прочитают 100, обе запишут 50 — одно списание потерялось
Транзакция тут не помогла, потому что проблема не в атомарности, а в изоляции

🛠 Что с этим делать
Три рабочих пути, по ситуации:
Считать атомарно в самой базе, не таща значение в код:
UPDATE accounts SET balance = balance - 50
WHERE id = 1 AND balance >= 50;
Заблокировать строку на время работы через SELECT ... FOR UPDATE — тогда вторая транзакция подождёт, пока не закоммитишь
Поднять уровень до Serializable — база сама поймает конфликт, но тогда готовь ретрай на ошибку сериализации

⏱️ И держи транзакции короткими
Пока транзакция открыта, она держит блокировки и мешает другим
Открыл транзакцию, а внутри пошёл в чужой API или задумался на пользовательском вводе — и вот уже полбазы стоит в очереди за твоими строками
В транзакции должно быть только то, что реально должно примениться разом, и ничего медленного

А вы уровень изоляции вообще трогали — или живёте на дефолте и не паритесь? 🤔

#database #postgres #backend #sql #dev #programming


💸 Деньги во 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
Копейка испарилась
Обычно остаток отдают кому-то одному (первому, последнему — как договоришься), но сумма кусков обязана сойтись с целым
То же с процентами и кэшбэком: реши заранее, как округляешь — вверх, вниз или по-банковски к чётному — и держи это в одном месте

💱 Множитель ×100 — тоже ловушка
Работает, пока валюта одна
Появилась вторая — и ломается: у иены копеек нет вообще (множитель 1), у динара Бахрейна три знака (×1000)
Поэтому сумму держим в паре с валютой (amount + currency), а множитель тянем из справочника, а не хардкодим * 100 по всему проекту
Иначе на другой валюте всё может разъехаться в сто раз, и хорошо если это рано заметят

🌐 Когда отдаешь суммы - за этим тоже надо следить

Внутри всё правильно — а потом отдал наружу 19.99 числом в JSON, и на том конце парсер прочитал его обратно как float с хвостом
Гоняй между сервисами строкой ("19.99") или целыми единицами (1999) плюс валюта
Тогда на обоих концах сумма останется той же, что была
Короче: деньги — это целые копейки или decimal, всегда с валютой рядом, и точность нельзя терять ни при делении, ни на границе API
Float оставь в покое— в кошельках ему не место.

#backend #database #dev #programming #fintech #sql


Скоро осень - а значит и новый деловой сезон 🍁
Приурочили к его началу новый вебинар для ИТ-руководителей!

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

29 сентября разберём вопрос с двух сторон — технологии и ресурсы:

Сергей Скирдин (CTO «Белый Код») — когда шина действительно нужна, какие ESB-решения есть на российском рынке и как их внедрять
Альфред Столяров (CEO EvApps) — как собрать команду под проект: найм, фикс-прайс, аутстафф или гибрид, и как не остаться без экспертизы после сдачи проекта

Для CDO, руководителей ИТ и технических лидеров в промышленности, логистике, ритейле и финансах.

29 сентября, 12:00–13:00, онлайн

Участие бесплатно, но регистрация обязательна: по итогам вебинара пришлем всем зарегистрировавшимся полезные материалы

Зарегистрироваться

Показано 20 последних публикаций.