Сообщество - Типичный программист

Типичный программист

1 491 пост 6 730 подписчиков

Популярные теги в сообществе:

IT-шники глупее нотариусов?

Подумал, что IT-шники единственная каста людей, которые наперегонки усердно работают над технологиями, которые уменьшают ценность их труда. Глобально для прогресса это хорошо. Для тех, кто завтра будет иметь доход меньше, чем сегодня - печально.

Юристы или нотариусы и другие бюрократы, которые всячески стремятся, чтобы без них было не обойтись в бытовых ситуациях, наверное, удивляются и смеются.

Придумал новые единицы данных, потому что йобибайт уже не тот. Знакомьтесь: gpybyte, hpybyte, ipybyte

Придумал новые единицы данных. Потому что йобибайт уже не торт.

Встречайте:

🄶🅟🅈🄱 — gpybyte

🄷🅟🅈🄱 — hpybyte

🄸🅟🅈🄱 — ipybyte

Как работают:

1 ipybyte = 2⁹⁰ байт

1 hpybyte = 1000 ipybyte

1 gpybyte = 1000 hpybyte

Почему гибрид? Потому что скучно не бывает.

В цифрах:

gpybyte = 1 237 940 039 285 380 274 899 124 224 000 000 байт

Это 10¹⁸ гигабайт. Терабайт на его фоне — пылинка.

Код на Python:

IPY = 2 ** 90

HPY = 1000 * IPY

GPY = 1000 * HPY

Код на JS:

const IPY = 2n ** 90n;

const HPY = 1000n * IPY;

const GPY = 1000n * HPY;

Всё под лицензией MIT — берите, используйте, форкайте.

Стандарт GIS v1.0 уже существует. GitHub, Gist, сайт — в процессе.

Это эксперимент. Но если залетит — войдёте в историю первыми.

🄶🅟🅈🄱

P.S. Ссылки в комментариях. Подписывайтесь на сообщество, если хотите следить.

Показать полностью
7

Лок в облачном Postgres завис на 5 дней. Виноват не код, а serverless

Если у вас Postgres на Neon (или Supabase, или любом другом serverless), и вы используете "pg_advisory_lock" для координации между репликами - проверьте сегодня. Скорее всего у вас тлеющий баг.

Лок в облачном Postgres завис на 5 дней. Виноват не код, а serverless

Где словил

У меня cron каждые 6 часов запускает функцию: проверить, есть ли пост за сегодня, если нет - сгенерировать, отправить в TG-каналы и в VK. Между двумя репликами на хостинге координация через advisory lock на номер UTC-дня. Учебниковый паттерн. Плюс отдельная retry-функция, которая досылает посты, не ушедшие в каналы с прошлого тика.

И вдруг утром обнаруживаю: retry пятый день подряд логирует одно и то же:

> [blog] retry sweep skipped (lock not acquired)

> [blog] retry sweep skipped (lock not acquired)

Retry-lock висит залоченный кем-то - взять его никто не может, retry никогда не запускается. А посты в каналы не идут. И хвост постов с "channel_posted_at IS NULL" тем временем растёт.

Первое подозрение - lock и unlock на разных клиентах

Перечитал PostgreSQL-доку про Explicit Locking. По смыслу: session-level advisory lock привязан к конкретному соединению и держится до явного освобождения или до конца сессии. Если взял на одном клиенте - отпускать нужно с того же. У меня же везде было "pool().query(...)", а "node-postgres" "pool.query" берёт первое доступное соединение из пула. Между lock и unlock пул мог отдать разные клиенты.

Тут я и налажал. Думал, pool сам разрулит сессионную привязку - он же с обычными запросами разруливает. (Нет).

Фикс - пинить один клиент через всю операцию:

> // retryKey = что-то стабильное на день, чтобы лок был общий

> const client = await pool().connect();

> try {

> const acq = (await client.query(

> 'SELECT pg_try_advisory_lock($1) AS acquired',

> [retryKey],

> )).rows[0]?.acquired;

> if (acq) {

> try {

> await retryTodayChannelPosts();

> } finally {

> await client.query('SELECT pg_advisory_unlock($1)', [retryKey]);

> }

> }

> } finally {

> client.release();

> }

"pool.connect()" отдаёт один клиент, в "finally" возвращаем в пул. Lock и unlock теперь гарантированно через одно соединение. Деплоил с уверенностью, что разобрался.

Это работало два дня.

Второе попадание - Neon обрывает TCP у pinned-клиента

Просыпаюсь утром на третий день - в логах опять то же:

> [blog] retry sweep skipped (lock not acquired)

Снова. Дошло. Между "pg_try_advisory_lock" и "pg_advisory_unlock" у меня внутри блока шёл retry с TG/VK API-вызовами, это десятки секунд. Что происходит за это время:

  1. Я взял клиент A через "pool.connect()".

  2. Сделал "pg_try_advisory_lock" на A, получил "true".

  3. Начал делать retry, дёргаю TG/VK API. Обычно 30-60 секунд, но иногда залипает на 60-120, если TG отвечает медленно. Пока я в API, на клиенте A никто не выполняет SQL. Соединение idle.

  4. Neon - serverless: compute-узлы суспендятся после idle (на Free-тарифе через 5 минут), плюс свои таймауты у proxy-слоя между клиентом и backend-ом. В моём кейсе proxy успевал убить сокет раньше, чем срабатывал compute-suspend - и в особо медленные retry-сеансы unlock уже ехал в мёртвое соединение.

  5. Возвращаюсь к коду, пытаюсь сделать "pg_advisory_unlock" через клиент A.

  6. node-postgres видит, что underlying socket мёртв: клиент A на самом деле труп, "client.query" падает с "Connection terminated unexpectedly". Сам по себе клиент не переподключится - просто мёртв, пул его выбросит при "release()".

  7. Unlock в принципе не выполнился. Старая backend-сессия с локом - на сервере она пока ещё "жива" в смысле, что её процесс выполняется. С её точки зрения TCP клиента просто отвалился, но backend не завершается моментально.

  8. В обычном Postgres зазор "TCP пропал -> backend это понял -> сессия закрылась" зависит от tcp_keepalive: при активности это секунды, на дефолтных Linux-настройках без активности backend может узнать о клиенте через часы. В Neon ещё веселее: между моим клиентом и реальным backend-ом стоит proxy-слой со своими таймаутами. Плюс backend может быть suspended (cold compute) и узнаёт о моей смерти только когда его поднимут обратно. Это могут быть минуты или дольше. Всё это время "pg_try_advisory_lock" для остальных возвращает "false".

Я тогда офигел. Unlock не сработал, лок болтается на сервере без хозяина, реплики приходят, видят "false", разворачиваются. И так всё новые сутки. Никто не может зайти.

Локально Postgres такого не делает - он не serverless, idle-сессии висят сколько угодно. В тестах и подавно: моки соединений не разрывают, CI зелёный. А на проде стабильно ловит раз в 2-3 дня.

Я попробовал keepalive - пинговать клиент "SELECT 1" каждые 10 секунд, чтобы не ушёл в idle. Это снизило частоту до "раз в неделю", но не убрало корень. На любой network blip - то же самое.

Что сработало

Перевести retry на row-level locks через "FOR UPDATE SKIP LOCKED" - вообще без advisory locks.

Логика retry была: "найди в БД сегодняшние посты с "channel_posted_at IS NULL", для каждого попробуй послать, если успешно - проставь дату". Это же ровно то, что Postgres умеет нативно через "FOR UPDATE SKIP LOCKED".

> const client = await pool().connect();

> try {

> while (true) {

> await client.query('BEGIN');

> // в боевом коде ещё фильтр slug LIKE 'YYYY-MM-DD-%' под сегодняшние, опускаю для простоты

> const res = await client.query(

> `SELECT slug, title, content_md, lang FROM blog_posts

> WHERE published = TRUE AND channel_posted_at IS NULL

> ORDER BY created_at LIMIT 1

> FOR UPDATE SKIP LOCKED`,

> );

> if (res.rowCount === 0) {

> await client.query('COMMIT');

> return;

> }

> const post = res.rows[0];

> try {

> const { channelOk } = await publishToChannel(post);

> if (channelOk) {

> await client.query(

> 'UPDATE blog_posts SET channel_posted_at = NOW() WHERE slug = $1',

> [post.slug],

> );

> await client.query('COMMIT');

> } else {

> await client.query('ROLLBACK');

> return; // bail на первой ошибке, не ddos-им сломанный API

> }

> } catch (err) {

> try { await client.query('ROLLBACK'); } catch {}

> return;

> }

> }

> } finally {

> client.release();

> }

Что меняется. Лок выдаётся не сессии, а строке. "FOR UPDATE SKIP LOCKED" захватывает первую незаблокированную, остальные потоки её пропускают и идут к следующей. Если транзакция коммитится - лок отпускается, дата стоит. Если роллбэчится (упали или TG ответил ошибкой) - лок отпускается, дата остаётся "NULL", на следующем тике другая реплика возьмёт ту же строку.

И главное: если коннект умер прямо в середине транзакции, Postgres сам делает rollback на серверной стороне и отпускает все локи в этой транзакции. Транзакционный лок не может остаться "потерянным" как advisory: TCP-сброс, idle-timeout, suspended compute - всё это Postgres обрабатывает через штатный transaction abortion. Гарантирует это сама архитектура движка, не настройки.

Если две реплики одновременно начали retry - они просто будут последовательно брать разные строки благодаря "SKIP LOCKED". На уровне нагрузки безразлично, потому что в день максимум 1-3 поста попадают в retry.

Деплоил, четыре дня тишины. Пока нормас.

Итого

Если у вас в коде стоит "pg_advisory_lock" на serverless Postgres - сегодня хороший день пройти по всем местам и спросить себя: а что тут координируется на самом деле? Часто оказывается, что session-lock не нужен вовсе - хватает "INSERT ... ON CONFLICT" или сравнения состояния. В остальных случаях его можно заменить на "FOR UPDATE SKIP LOCKED" под конкретные строки.

Те случаи, когда session-lock действительно нужен - это long-running worker, который держит соединение всю свою жизнь, не отпуская его в idle. В serverless такого зверя нет.

Что бесило больше всего - баг проявлялся раз в 2-3 дня и локально не воспроизводился вообще. Тесты зелёные, CI зелёный. На проде тлеет. Узнавал глубже только когда читатель в комментариях писал «а где сегодняшний пост». Не лучший способ узнавать.

#postgres #neon #nodejs #грабли

Показать полностью
7

Слезы джуна

Серия Программизм

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

Отличная работа, все прочитано!

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества