этогик // DevOps, Infrastructure, Productivity
Ir al canal en Telegram
🚀Блог DevOps-инженера. Вместе растем в хард- и софт скиллах, разбираемся как жить и работать продуктивно, ищем баланс во всем 🌿 📽 yt: https://www.youtube.com/@etogeek 🌍 site: https://etogeek.dev 🌿 chat: @etogeekchat Подробнее в закрепе 🦾
Mostrar más4 359
Suscriptores
Sin datos24 horas
+97 días
+930 días
Archivo de publicaciones
Чтобы немного подвести итог серии постов про бекапы (раз и два), расскажу как я ими управляю в своей инфрастркутуре.
Основная цель такого поста не показать единственный способ "как правильно" (такого нет), а поделиться моим опытом, из которого вы можете подцепить какую-нибудь идею и реализовать у себя.
В общем, конечно же, я делаю это через ansible. У меня есть довольно сильно параметризируемая роль
backups. В директории files я складываю bash-скрипты в сыром виде. Можно их там разбить на под-директории, чтобы было больше порядка.
В инвентори у нужных хостов я описываю бекап по следующей структуре:
backups: # список - можем описать несколько бекапов
- name: billing_db_backup
user: root # под каким юзером запускаем
scripts:
- script: billing_db_backup.sh # название скрипта из files
script_dest: /etc/billing/scripts # куда положить скрипт
schedule: { hour: 2, minute: 30 } # cron-расписание
job: /etc/billing/scripts/billing_db_backup.sh # cron-команда
create_dir: # если нужно создать директории
- path: /etc/billing/scripts
mode: '0750'
- path: /var/lib/billing/backups
mode: '0700'
env:
PGPASSWORD: >-
{{ длинный и некрасивый lookup, чтобы забрать пароль из Infisical }}
Что роль сделает:
- создаст директории
- скопирует скрипт как есть
- срендерит env файлик с паролем из Infisical (в скрипт надо добавить . env)
- добавит расписание в cron
Почему скрипты в files, а не в templates? Потому что некоторые скрипты могут конфликтовать с рендером Jinja который используется в шаблонах. Надежнее их положить в files в том виде, в котором они есть и попадут на машину.Мониторинг бекапов
В прошлый раз я говорил про PITR-бекапы для PostgreSQL, теперь хочу немного рассказать про мониторинг бекапов.
Проблематика следующая - мы сделали бекап-скрипт, закинули его в crontab и забыли. Как будем проверять, что скрипт работает и бекапы создаются регулярно?
Переключатель мертвеца - это некая система требующая постоянной активации, чтобы основная система продолжала работать. Например, педалька у высотного погрузчика, которую человек должен всегда нажимать, чтобы двигать погрузчик.
Аналогичной штукой воспользуемся и с бекапами - на каждое успешное выполнение скрипта будем отправлять "пинг" в систему. Если пинг не придет в ожидаемое временное окно - получим алерт. Что может быть надежнее?
Таких сервисов полно, я использую healthchecks.io. Раньше пользовался бесплатной версией, потом развернул self-hosted, чтобы обойти лимиты на хелсчеки.
Интеграция простейшая: внутрь скрипта, либо через pipe в cron-команде добавляем curl-запрос типа:
curl -fsS -m 10 --retry 5 -o /dev/null https://healthckecs.io/ping/efe3-da-ffdsfsd-qqsdfsf
Этот сервис так же умеет замерять время выполнения скрипта (отправляем пинг в начале и в конце), а так же записывать лог ошибок (через POST запрос).
Другие примеры: cronitor, sentry, тысячи их. Гуглите cronjobs monitoring.Про бекапы. Хочу немного поделиться своим опытом в нескольких постах. Это больше информация для новичков, на которой я хочу и в дальнейшем фокусироваться, если честно. Синьоры-помидоры, вы итак это всё знаете (надеюсь).
Что первое приходит в голову, когда мы говорим про бекапы баз данных?
Ну, делать дамп раз в сутки через cron-таску и загружать его в отдельное хранилище. Есть говорить про PostgreSQL, то стандартный
pg_dump достаточен для этого. А восстанавливаемся через pg_restore.
Отлично подходит для небольших инстансов, всяких селф-хостед сервисов. Например, у вас развернуты self-hosted Metabase, Infisical, Grafana. База весит немного, дамп еще меньше - рестор быстрый.
Проблем тут две:
- мы теряем до 24 часов данных, в зависимости от того, как давно был снят последний бекап
- при росте базы данных, особенно если это БД продукта, время рестора из дампа растет в прогрессии
Рестор ведь можно распараллелить - запустить в несколько потоков, почему растет время рестора?
Дамп базы под капотом - это инструкции как собрать базу заново - схема, COPY/INSERT-ы, CREATE INDEX, и тд. То есть при ресторе сначала все данные наливаются с нуля из SQL, а потом начинается создание индексов. На большой базе индексы могут создаваться долго, а главное - они жрут очень много ресурсов (проц+диск).
Плюс дампов - можно ресторить отдельные таблицы, ресторить рядом с другой БД внутри одного инстанса, сам дамп можно "прочитать".
Что делать с потерей данных, если последний дамп был давно? Надо снимать бекапы чаще :)
На прошлой неделе настраивал Point in time recovery (PITR) в PostgreSQL. Что это такое:
- раз в какое-то время снимаем полный бекап инстанса (ну например так же раз в сутки) - важно понимать, что мы бекапим не транзакционный дамп, а именно файлы БД.
- можно фулл-бекап снимать раз в неделю, а между ними бекапить "дельты" - они меньше и быстрее бекапятся.
- непрерывно и постоянно отправляем WAL-ы (write ahead log) - postgres сам запускает нужную команду, когда накапливает wal.
В чем суть?
- файловый бекап восстанавливается быстрее - потому что это просто копирование файлов
- wal-ы позволяют восстановить инстанс на ЛЮБОЕ время. Например. Сейчас 11:00. Последний фулл-бекап был в час ночи. Евстахий получил доступ на прод и навернул таблицу в 10:45. Я могу восстановить состояние кластера на 10:44. Потеря данных (не считая даунтайма на рестор) - 16 минут.
Я настраивал это с помощью софтины wal-g, которую делает Яндекс, и, кстати говоря, использует у себя в Managed Service for PostgreSQL в облаке. Сделал себе еще дашбордик с метриками и логами по бекапам, чтобы смотреть - всё ли с ними ок.Когда я преподавал в Практикуме (был такой период, да. еще и видео снимал), студенты часто возмущались, что менторы не дают прямого ответа на их вопросы. Они подсказывают направление для размышления, но не дают готовое решение.
Я верю в то, что когда человек в процессе обучение натыкается на проблему и решает её сам - именно так он получает тот самый ✨ОПЫТ✨. Это не потерянное время.
Наткнулся на видео от JetBrains Academy (7 минуток), про то, что происходит, когда новички учатся кодить с помощью Codex/Claude. Там разбирают интересное исследование, где группе студентов давали задачу и следили за тем как именно они приходят к решению.
В большинстве случаев они давали рабочее решение, но студенты разделились на две группы с разным подходом:
1. понял, что нужно сделать - AI предлагает решение - оцениваю - принимаю/отбрасываю
2. не понимаю, что делать - AI предлагает решение - выглядит норм - принимаю
Первые сами говорят агенту, что им нужно сделать. У них уже было в голове примерное направление действий, агент помогал ускорить весь процесс.
Вторых, изначально недостаточно разобравшись в задаче, двигались туда, куда их двигал агент. Решение есть - понимания "почему так" нет.
То есть программа в конце работает у обеих групп, только часть людей не обязательно стала лучше понимать технологию.
Суть в чем, возвращаясь к моей изначальной идее: если ты закидываешь задачу в ЛЛМ и бездумно применяешь предложенное решение - опыта ты не получаешь. Грош - цена таким знаниям.
Лучший вариант, это просить агента оценить твое решение, объяснить концепцию, дать подсказку, показать ошибку.
Когда учишься - не перекладывай на AI сам процесс мышления.
Используй AI для ускорения своей работы.
Некоторое время назад столкнулся с тем, что какой-то сервис отправил мне огромное количество логов в Локи. В цитируемом посте я рассказывал процесс поиска виновника.
Сейчас проблема повторилась, и я разобрался как принудительно удалять старые логи.
Во-первых, в конфигурации должен быть активирован compactor. У меня такой конфиг:
compactor:
working_directory: {{ loki_data_dir }}/retention
compaction_interval: 10m
retention_enabled: true
retention_delete_delay: 30m
retention_delete_worker_count: 150
delete_request_store: filesystem
delete_request_cancel_period: 5m
Затем делаем такой запрос в Loki с указанием query для фильтрации и таймштампами:
curl -G -X POST 'http://localhost:3100/loki/api/v1/delete' \
--data-urlencode 'query={service="your-damned-service"}' \
--data-urlencode 'start=2026-06-05T00:00:00Z' \
--data-urlencode 'end=2026-06-25T00:00:00Z'
Смотрим статус запроса:
curl 'http://localhost:3100/loki/api/v1/delete'
[{"request_id":"d7f76f7f","start_time":1780617600,"end_time":1782345600,"query":"{service="your-damned-service"}","status":"processed","created_at":1782393974.431}]
Ждем некоторое время (compaction_interval: 10m, delete_request_cancel_period: 5m, retention_delete_delay: 30m) и наслаждаемся освобожденным местом.Kubernetes — мощно, но сложно. 8 июля в Москве собираются инженеры, чтобы разобраться вместе: поговорить о реальных кейсах, пообщаться за круглыми столами и познакомиться с теми, кто решал похожие задачи.
Будут доклад о домашнем кластере на несколько квартир и дата-центров, разбор ИИ-агента, который управляет инфрой как админ, и дискуссии по сетям, виртуализации и безопасности.
Это юбилейный митап Deckhouse User Community — и на этот раз особенно много внимания тем, кто активно участвует в жизни сообщества. Коллеги поделятся запуском программы поддержки контрибьюторов.
Только офлайн, количество мест ограниченно, регистрируйтесь!
Уже довольно давно я сталкиваюсь с плавающими проблемами связи между Яндекс Облаком и Hetzner: у рандомного https- соединения скорость может упасть до 100кб/с. Способы стабилизировать это нашлись, а вот полностью решить так и не получилось.
Захотелось замониторить эту скорость скачивания, чтобы найти какие-то закономерности (спойлер, на нашел).
За несколько гугл-запросов подходящий экспортер не нашелся - в основом все проверяют скорость до speedtest-а, а мне нужны кастомные URL - например свой Nexus.
Время сейчас такое, что если чего то нет, или функционал не устраивает, ты просто пишешь своё. Вот и я сделал download speed exporter:
👉 https://github.com/etoosamoe/download_speed_exporter
Собираем образ контейнера, запускаем, а в Prometheus или vmagent дергаем
/probe-ручку с URL-ом целевого файла. В ответ получаем метрики по скорости скачивания. Дашборд есть в комплекте.
На самом деле я сделал его уже давно, просто почему-то не рассказывал тут.
На скриншоте пример того, как скорость скачивания вообще упала до минимума на пол дня.Работа в небольшой продуктовой компании часто расширяет границы твоей позиции. Я уже давно не просто “девопс”, который перезагружает серверы и дебажит пайплайны. Вопрос плохо это или хорошо - оставлю каждому на размышление.
Например, не так давно занимался сначала объединением двух "сайтов" Jira и Confluence в один. Такую попытку уже делал мой лид за полгода до этого, но всё разбилось об ограничения: проблемы при мерже, скачивание/загрузка бекапа и тд.
А спустя время, оказалось, что Atlassian только-только выпустили новый инструмент как раз для переноса данных между “сайтами”. Он отработал просто превосходно, перенес проекты со всеми данными, аттачами и почти ничего не поломал.
Затем приступил к миграции из Notion в Confluence. Это получилось более хаотично. Многие страницы в Ноушене были приватными. А к приватным страницам даже у админа нет доступа. Нужно просить сотрудников, у кого есть доступ, экспортировать архив, скидывать его мне, а я уже импортирую его в Confluence.
Одновременно с этим руководство захотелось разделить все статьи по пространствам - например Sales, Finance, Product и так далее. Минус Confluence по сравнению с Notion или Google Docs в том, что ты не можешь дать доступ одному человеку к одному документу. Нужно добавить человека в раздел (Space), и тогда он получит доступ ко всем открытым статьям.
Несмотря на то, что Confluence - хороший инструмент, переезд из Notion был воспринят коллегами не очень мягко. Было много непоняток с доступами, проблем с поисками “потерянных” статей, исправлений ссылок в легаси-местах, проблем с форматированием.
Суммарно наэкономили около $24к в год. Жаль не в свой карман, конечно.
В последние недели погрузился в абсолютно новую для себя штуку - мобильную разработку. Точнее, DevOps-часть для нее.
Всё новое, изначально ничего непонятно, даже то, с какой стороны подступаться к этому. Хорошо, что мы живем в 2026 году, где существуют ChatGPT и Claude. Но ответственность не позволяет навайбкодить все конфиги и решения. Вместо этого я сижу и делаю кросс-проверки, разбираюсь что и зачем нужно, какие инструменты есть, и почему именно они.
Больше, конечно, ковырялся именно с iOS частью, потому что проверять сборку проще локально - андроид телефона у меня нет. Разобрался в целом с воркфлоу разработки: Xcode, Apple Developer аккаунт, bundle-id, загрузка в App Store Connect, публикация в TestFlight для внутренних тестировщиков.
Понял, что если мы хотим тестировать приложение на dev-окружении, то по сути надо делать два приложения dev и prod. Потому что внутрь приложения зашивается конфигурация, в которой API-эндпоинты указаны. Получается, что можно собрать dev-приложение, загрузить его в App Store Connect и отправить в TestFlight для проверок. В ревью оно никогда не пойдет. А вот prod-приложение уже пойдет по пути TestFlight → Review → Store.
Отсюда вытекает то, что нужно иметь схемы и конфигурации для сборки двух приложений в одном репозитории. И Firebase конфигурации тоже - этим прямо сейчас занимаюсь. Firebase - это платформа от гугла с кучей вспомогательных сервисов для мобилок, например: аналитика и push-уведомления.
Ага, не забыть еще скриптик для инкремента версий приложений - сторы требуют обновления версии при каждой новой загрузке приложений. А еще требуют иконку для приложения. Чтобы на iOS картинку не зашакалило (частично из-за Liquid Glass), её надо сделать в Icon Composer-е с помощью SVG-элементов.
Разобрался как работают OTA-апдейты (over the air). Это когда можешь пересобрать только внутреннюю часть, запаковать ее в js-бандл и залить в s3. Приложение при запуске проверит наличие апдейта, скачает и предложит перезапуститься. Самая большая выгода - для мелких багфиксов тебе не нужно публиковать новую версию в сторы и ждать ревью. Тут надо быть осторожным, потому что добавлять функционал через OTA-апдейты без ревью запрещается правилами сторов.
Описал для разработчиков релизный процесс - как будем работать с фича-ветками, релизными ветками, версиями. Постарался сделать так, чтобы это минимально отличалось от уже привычного всем в команде процесса.
В общем, погружение в новую область это всегда так - сначала чувствуешь себя идиотом, не знаешь основных терминов. Мозг к вечеру выжат настолько, что хочется просто сесть на диван и смотреть в стену. Главное помнить - глаза боятся, а руки делают. Потихоньку, мелкими итерациями, но в итоге картина становится яснее, начинаешь лучше во всем ориентироваться.
Немного бомбления про Docker Swarm.
Есть опция
max_replicas_per_node - позволяет ограничить количество реплик сервиса на одну ноду.
Кейс простой: две ноды, две реплики сервиса → хочу строго 1+1, чтобы не было перекоса по памяти.
Ставлю:
max_replicas_per_node: 1
Логично? Логично.
Дальше деплой новой версии с start-first (это как rolling update в кубе, чтобы без даунтайма):
- Поднимается новый контейнер
- Ждём healthcheck
- Гасим старый
- Повторяем
Но тут начинается веселье.
У нас уже есть по одной реплике на каждой ноде.
Swarm пытается поднять новую реплику → и не может:
- на node1 уже 1 → нельзя
- на node2 уже 1 → нельзя
- других нод нет
И всё. Деплой просто блокируется с no suitable node.
Почему?
Потому что в Swarm это правило жёсткое. Он не умеет временно нарушать placement constraints, даже ради rolling update.
Что с этим делать?
Да фиг его знает ¯\_(ツ)_/¯Deckhouse Conf 2026 — один день концентрированного технического хардкора.
9 апреля в Москве соберём 1000 инженеров, которые строят платформенные продукты и управляют инфраструктурами.
Зачем приезжать:
— Проверенные кейсы внедрения.
— Демо технологий Deckhouse и возможность протестировать их своими руками.
— Хардкорные технические доклады про виртуализацию, SDN, мониторинг, миграцию на микросервисы и многое другое.
— Прямой контакт с разработчиками и ведущими инженерами.
Один день, два трека, 1000 гостей, максимум полезного общения.
Регистрируйтесь
Хочу немного рассказать про стек, о котором я раньше никогда не слышал, а четыре года назад пришлось плотненько познакомиться сначала со стороны администрирования, а спустя время - и чуть-чуть потраблшутить часть разработки.
Добро пожаловать в мир Hadoop.
Всё это поделие максимально плотно связано с Java и Apache. Есть несколько коммерческих дистрибутивов/платформ, но в целом всё это с горем пополам и чертовой матерью поднимается и вручную (хотя может я просто не умею его готовить).
Hadoop - это фреймворк распределенной системы обработки данных, который состоит из нескольких продуктов. Ключевая идея - большие данные распределены и обрабатываются на кластере серверов.
Расскажу про те, которые используются у нас.
HDFS - Hadoop Distributed File System - это буквально сетевая файловая хранилка, с репликацией данных, большой пропускной способностью. Состоит из NameNode (мастер-ноды) со всеми метаданными, и DataNode (агенты) - фактическое хранение данных.
HDFS фундаментально работает по системе Write Once Read Many - мы записали файл, и уже не можем его изменить (кроме как append и truncate). Это сильно упрощает модель согласованности данных: читатель гарантированно получает то, что было записано, без конфликтов. HDFS любит большие последовательные чтения файлов. Читать мелкие файлы - не очень.
YARN (Yet Another Resource Negotiator) - это оркестратор задач. Грубо говоря как кубер, только для data-задач - он выделяет на CPU/RAM для них. Есть кластер из нескольких серверов, мы можем запускать на нем yarn-контейнеры(не путать с докером) и выполнять в них map-reduce задачки. Не только MR, конечно, есть еще Spark, Hive и тд.
ResourceManager - мастер, NodeManager - агент на каждой ноде. При создании задачи появляется ApplicationMaster - он общается с ресурс-менеджером и управляет выполнением конкретного задания.
HBase - распределённая NoSQL-база поверх HDFS: по сути, это огромная таблица-словарь, где данные лежат как `row_key → набор полей → значения` (а не как "строка строго фиксированных в схеме колонок", как в реляционках).
В отличие от PostgreSQL, где схема задаёт одинаковые колонки для всех строк, в HBase "схема" обычно фиксирует только column family — условные "папки" полей. А внутри каждой такой "папки" у конкретной строки может быть любой набор колонок, и он может отличаться от строки к строке - удобно для разреженных данных и атрибутов, которые меняются со временем без бесконечных миграций.
Это может быть сложно представить, поэтому вот очень грубый пример:
0001: profile:name, profile:city, profile:role, contacts:telegram
0002: profile:name, profile:role, contacts:vk, contacts:instagram
0003: profile:name (и всё)
0004: profile:name, profile:city, skills:java, skills:kafka, skills:spark
Смысл: families (profile, contacts, skills) заранее "разрешены", а какие именно поля внутри них есть у конкретного пользователя - может отличаться.
По кластеру таблица разъезжается через regions: это диапазоны строк по row_key, и каждый RegionServer обслуживает свой набор регионов.
- HMaster - распределяет данные по нодам, DDL-операции
- RegionServer - воркеры на нодах. Сами данные физически лежат в HDFS, а RegionServer отвечает за чтение, запись и кеширование. Клиент через ZooKeeper/мета‑информацию узнаёт, на каком RegionServer лежит нужный кусок, и дальше идёт напрямую туда.
Во всех этих сервисах ZooKeeper используется как централизованное хранилище информации о состоянии кластера (кто жив, где мастер, где мета-данные). Как etcd в Кубере.
MapReduce - подход к обработке больших данных в два шага. На этапе Map данные разбиваются на куски, YARN поднимает контейнеры-мапперы и каждый преобразует свой кусок в пары "ключ → значение". На этапе Reduce результаты группируются по ключу и агрегируются. Простой грубый пример: считаем сколько пользователей в каждом городе. Map проходит по записям и выдает "Belgrade → 1", "Moscow → 1", "Belgrade → 1". Reduce суммирует по ключу: "Belgrade → 2", "Moscow → 1".Redis - это такая key-value база данных, часто используется для кэша, часто - как очередь для Celery, либо для других целей. Ее особенность в том, что все данные она хранит в оперативной памяти, из-за чего очень быстро работает. Если данные нужны, то в случае перезагрузок/перезапусков или просто по времени Redis может сохранять слепок на диск.
В одном внутреннем сервисе Redis стал кушать ну слишком много памяти. Даже виртуалку повесил по OOM-у.
Полез разбираться. Оказалось, что в одной базе лежит неприлично больше количество ключей - около 2 млн. Посмотрел, что в ней хранится - токены сессий для API. Хм, а у них в коде MAX_AGE стоит 180 дней всего.
Спускаюсь ниже по коду, там где для записи указывается TTL:
expire=int(datetime.utcnow().timestamp()) + SESSION_MAX_AGE
Ну вроде хорошо, берем текущий таймштамп и плюсуем 180 дней. Так и записываем.
Пошел в документацию библиотечки:
expire(key, timeout)
Set a timeout on key
Таймаут… не таймштамп.. получается что о_о
Каждая запись создавалась с TTL 1780743477 секунд, что чуть больше 56 лет…
Фикс выкатил. А Claude тем временем помог сделать скриптик, который пробежит по всем записям в Redis с TTL > 1 года и удалит старые. Скрипт запустил в фоне, крутится неспеша, память освобождается, все довольны.Про миграции в базе данных, блокировки и lock queue.
Значит, ситуация: CD-пайплайн запускает миграцию в БД, которая делает совершенно рядовой
ALTER TABLE - например добавляет колонку или меняет поле. Миграция должна пройти за считанную секунду, но она зависает, и приходит алерт, что прод перестал отвечать на запросы.
Представили? А мне и представлять не нужно. Рассказываю до чего докопался и что узнал.
Каждый SELECT запрос в PostgreSQL создает блокировку - AccessShareLock на таблицы, которые он задействует. Это самый нестрогий лок, он не конфликтует с другими запросами и локами. Кроме одного.
AccessExclusiveLock - создается при различных изменениях таблиц: DROP, ALTER, TRUNCATE и тд. Этот единственный лок, который блокирует другие SELECT запросы.
(подробнее про локи тут).
В базу отправляется жирнющий SELECT-запрос, который выполняется, скажем, 60 секунд. Все это время на таблице висит AccessShareLock, но он никак не мешает другим запросам - всё работает.
И тут прилетает ALTER TABLE со своим AccessExclusiveLock. Он встает в очередь за предыдущей транзакцией - ждет пока она не завершится и не освободит таблицу от Share-лока. А заодно ExclusiveLock блокирует все последующие запросы в БД к этим таблицам, которые просто выстраиваются в очередь. А если табличка популярная, то и запросы с прода будут зависать.
Вот схемка, спасибо Claude за визуализацию, все остальное писал сам:
10:00:00 — Большой SELECT с кучей JOIN-ов на 60 секунд
🟢 AccessShareLock взят
10:00:10 — ALTER TABLE запускается (нужен AEL)
🔴 Встал в очередь, ждёт окончания SELECT
10:00:11 — Обычный SELECT от приложения
⏸️ Встал в очередь ЗА миграцией
10:00:13 — SELECT, INSERT, SELECT, UPDATE...
⏸️ Все в очереди!
10:01:00 — Первый SELECT закончился
🔴 ALTER TABLE начал выполняться (5 секунд)
10:01:05 — Миграция завершилась
✅ Очередь разблокировалась
В моем случае было еще немного классного легаси-кода: есть периодическая джоба, которая открывает транзакцию (сессию) к БД, забирает из часть данных (2-5 сек), начинает эти данные обрабатывать, работать со внешними системами, потом забирает еще часть данных, обрабатывает их, и так далее в цикле. И только в сааааамом конце - закрывает транзакцию в БД (делает `COMMIT`).
Проблема в том, что все это время на задействованных таблицах висит AccessShareLock потому что транзакция не закрыта, COMMIT-а не было. Обычной работе это не мешает. А вот если во время работы этой джобы мы запускаем миграцию, то получается жопа.
Что делаем?
- Закрываем транзакции после того как сделали запрос. Лучше сделать 100 отдельных селектов только когда они нужны, и не держать транзакцию в idle in transaction, удерживая AccessShareLock.
- Оптимизируем большие запросы.
- Миграции запускаем с параметром PGOPTIONS="-c lock_timeout=5s" - тогда если миграция и уткнулась в ShareLock, то через 5 секунд она упадет и прод не будет лежать
- Установить idle_in_transaction_session_timeout в настройках PostgreSQL - оно будет дропать подобные транзакции в Idle in transaction статусе. Но сначала надо починить старые запросы, которые создают такое.
- Переносим SELECT-запросы на read-реплику (пока только теория)
- Мониторинг и логирование. Пока еще думаю какое именно.
- (еще почитать)26 февраля — Deckhouse User Community meetup #4. Это митап для тех, кто хочет понимать Kubernetes глубже.
Зарегистрироваться
Эксперты Deckhouse и приглашённые спикеры расскажут, как запускать K8s поверх любых дистрибутивов, эксплуатировать платформу в одиночку, развёртывать домашнюю виртуализацию на бюджетном железе и грамотно подходить к безопасности.
И покажут: на митапе будет работать зона «Попробуй сам», где можно протестировать работу Deckhouse Kubernetes Platform Community Edition своими руками.
Классный пример поддержки от SaaS. Именно такой уровень вовлеченности ожидаешь от поддержки крупного (очень) сервиса.
Я сейчас занимаюсь переездами в небезызвестный Confluence (поделие Atlassian) из всяких Notion-ов, из соседних пространств, меняю структуру разделов и т.д. И наткнулся на неочевидную проблему - не видно страниц внутри некоторых разделов. А поиском - видно.
Написал тикет, поставил галочку "можно получить доступ в наш инстанс" и стал ждать.
Через полчаса увидел в списке пользователей сотрудника Atlassian, а еще через полчаса в почту пришел ответ. В нем самое интересное.
Во-первых, текст был не похож на LLM-ответ. Вероятно был отформатирован, но само написание выглядело человеческим. Такое - редкость сейчас.
Во-вторых, он содержал несколько скринов, подробное описание того, в чем проблема. А главное, чел написал, что в одном разделе всё пофиксил и записал ВИДЕО ✨ о том, как починить во всех остальных разделах.
Вот так вот.
Проблема только в том, что мир SaaS ждут проблемы. С приходом LLM каждая компания по сути может сделать свой SaaS.
Клонов Notion-а уже over9000. Зачем платить 10 баксов за юзера, если можно навайбкодить свои Jira и Confluence для внутреннего использования.
Но условная Figma под меньшим риском - её сложнее повторить, это уже некий стандарт индустрии фронтенда/дизайна.
Получается что под угрозой те SaaS-ы, которые раньше покупали из-за сложности разработки.
Главная проблема даже не в том, что клиенты реально могут сделать качественный продукт (далеко не все понимают, что идет в архитектуру крупных систем). Проблема в том, что клиенты чувствуют, что могут. И этого может быть достаточно для отмены подписки на SaaS.
Что же остается делать SaaS-ам?
- Глубже подсаживать пользователей на свой продукт. Это когда один раз начав активно пользоваться сервисом, уже сложно будет переехать на другой (DataDog?).
- Адаптироваться под клиента. Улучшать поддержку.
- Менять продукт и становиться "ближе к деньгам клиента"
В интересные времена живем...
В последнее время я не то что бы очень часто делаю что-то прям техническое-девопсерское руками, и когда выпадает возможность что-то потраблшутить, можно сказать даже радуюсь этому.
Вот вечером приходит алерт, что на виртуалке с VictoriaMetrics и Loki осталось <5% места. А я последний раз когда смотрел, там 160+ гигов было (из 300). Что-то явно пошло не так. Вряд ли это метрики, я давно не добавлял новые скрейп-таргеты, значит какие-то логи начали лететь.
Начинаю смотреть графики виртуалки:
- 22 декабря с 7 утра начал активно забиваться диск
Как понимаю, что это именно Loki жрет место. Аномально много места занимают два конкретных чанка данных. Смотрим в ncdu или du -sh на директории с данными VM и Loki:
--- /var/lib/loki/chunks/fake ---
/..
71.1 GiB [##########] /71a536873561d772
71.0 GiB [######### ] /4ed05483e3664242
9.9 GiB [# ] /47a1d97545945804
4.0 GiB [ ] /ab30104fb5a49027
3.4 GiB [ ] /1fb8841baeb3ef1
2.3 GiB [ ] /eaeec6ed138da0a2
1.4 GiB [ ] /f973cac99619b763
1.4 GiB [ ] /39aad22606baff2b
Причем видно, что файлы в них начали создаваться только после 22 декабря, значит ОНО:
find /var/lib/loki/chunks/fake/71a536873561d772/ -type f -printf '%TY-%Tm-%Td\\n' | sort | uniq -c
872 2025-12-22
1390 2025-12-23
1390 2025-12-24
1394 2025-12-25
....
Посмотрим количество логов с разбивкой по лейблу job (тайм-рейндж выставляю на 1 час вокруг начала инцидента). Иду в Grafana Explore, выбираю Loki как data source:
sum by (job) (count_over_time({job=~".+"}[5m]))
Там вижу, что всплеск и дальнейшее плато выпадает на job-у containerlogs. В этот лейбл я собираю логи с контейнеров со всех серверов. Значит надо посмотреть с какого хоста это летит и с какого compose-сервиса (или swarm-stack-а):
topk(5, sum by (host) (count_over_time({job="containerlogs"}[5m])))
Вижу на графике конкретный хост, добавляю его в фильтр. Так же меняю лейбл для суммирования, на stack_name (так как на этом сервере подняты сервисы Docker Swarm):
sum by (stack_name) (count_over_time({job="containerlogs", host="host.com"}[5m]))
Теперь на графике видно сбойный сервис. Беру в охапку автора и идем смотреть конкретные логи этого сервиса в момент инцидента. Вижу, что сервис в определенный момент начал сыпать ошибками. Обычно, если ошибок нет, то нет и логов от сервиса. А тут он начал писать по ~300к строк в минуту. Анализирую ошибки, понимаю, что они в целом одинаковые.
Выясняем, что в сервис пришел кривой JSON, сервис не смог его обработать корректно, выдавал ошибку, возвращал JSON в очередь. А затем брал его же из очереди, выдавал ошибку... и так далее по кругу.
Теперь самое сложное: что сделать, чтобы избежать подобного в будущем?
- Более тщательный мониторинг занятого места на диске, лучше с predict-ом “с таким темпом место кончится через Х дней/часов”. Но расход был медленный, алерт пришел бы сильно не скоро.
- Rate Limit на количество логов в минуту на стороне Loki, повесить алерт на превышение.
- Ронять контейнер на каждый сбой? Норм, если есть алертинг на рестарты контейнеров. А что, если в очереди куча сбойных json-ов?
- Складывать сбойные json-ы не обратно в основную очередь, а в другую. И мониторить ее наполняемость? А если json был нормальным, просто сбой был временным - тогда валидный json улетит в мусорку…
- Настроить отправку exceptions в Sentry и сделать алерты?
- 👉 Стоит ли вообще что-то делать, если сервис - легаси и ломается примерно никогда и не сильно влияет на прод?
А можно ли удалить лишние логи? Я в свое время не нашел явного несложного способа дропнуть логи в Loki, например, за определенные даты. Только если настраивать новый ретеншен, либо грохать всё. В моем случае я просто оставлю, пока есть свободное место - в течение ретеншен-периода оно уйдет.Пару постов назад я делился своим .zshrc файликом с алиасами и настройками ZSH. И в чате мне напомнили, что более сложные можно оборачивать в функции и даже скинули пример для git add/commit/push одной командой (спасибо, Саша!).
Я его немного допилил:
- добавил возможность писать без кавычек несколько слов, чтобы они улетали как commit message
- проверку, что ты коммитишь НЕ в master/main ветку
Получилось очень удобно, теперь я просто в терминале пишу
gacp Update Nginx configs, вместо череды из git add, git commit -m, git push (хотя эти команды уже где-то в подкорке сидят).
Вот код функций, там я утянул еще из oh-my-zsh шаблонов одну:
function git_current_branch() {
local ref
ref=$(git symbolic-ref --quiet HEAD 2> /dev/null)
local ret=$?
if [[ $ret != 0 ]]; then
[[ $ret == 128 ]] && return # no git repo.
ref=$(git rev-parse --short HEAD 2> /dev/null) || return
fi
echo ${ref#refs/heads/}
}
gacp() {
if [ -z "$1" ]; then
echo "Commit message required"
return 1
fi
local branch=$(git_current_branch)
if [[ "$branch" == "master" || "$branch" == "main" ]]; then
echo "You are on branch '$branch'"
read "REPLY?Commit to $branch? (Y/n) "
if [[ $REPLY =~ ^[Nn]$ ]]; then
echo "Commit cancelled"
return 1
fi
fi
git add -A
git commit -m "$*"
git push origin "$(git_current_branch)"
}
Как обычно код можно посмотреть у меня в репозитории, вот ссылка.Небольшой отчет о том, чем занимался на работе в последнее время.
У нас есть довольно большой проект на Python и вот мы решили мигрировать его со стандартного pip для управления зависимостями на современный и популярный uv.
Как было раньше: два файла -
requirements.txt с основным набором зависимостей и requirements-dev.txt - в нем добавлены некоторые дополнительные зависимости для локальной разработки и в тестах. Чтобы добавить их в pyproject.toml можно использовать uv add -r requirements.txt
dev-файлик перезжает в uv в отдельную "группу" зависимостей. Зависимости такой группы можно поставить вместе с основными через флаг --group name.
Сам uv генерит uv.lock файл, в котором зафиксирован слепок этих зависимостей, подзависимостей, транзитивных и так далее. Похоже на package-lock.json в Node.
Основной воркфлоу теперь немного меняется:
- uv sync создает виртуальное окружение (как раньше в .venv) и ставит в него все зависимости
- uv run <command> запускает команду в виртуальном окружении, например uv run python main.py
- учитывая наличие .venv после uv sync, можно сделать source .venv/bin/activate и работать как раньше
Dockerfile тоже нужно переделать. Пример беру в официальном example-репозитории.
Сам бинарник uv беру из образа через COPY, чтобы не конфликтовать по версии Python между pyproject.toml (там например зафиксирована 3.X.13) и официальным образом uv-python (они фиксируют только 3.X без патч-версии).
Из интересных оптимизаций билда - использование mount-ов для RUN директив. Они позволяют использовать кэш на диске сборочного инстанса в случае инвалидации слоя - например поменялся слой с зависимостями, но мы все равно используем кэш, чтобы все не перекачивать.
В gitlab-ci настраиваю использование кэша:
cache:
key:
files:
- uv.lock
paths:
- .cache/uv
Если используете self-hosted докер-раннеры, то в настройках надо добавить:
[[runners]]
[runners.docker]
disable_cache = false
volumes = ["/var/path/to/cache:/cache"]
А вот что не получилось пока что сделать: указать кастомный индекс (nexus-proxy) для Python-зависимостей только на сборках/тестах. Чтобы при локальной установке качать зависимости с PyPi, а на сборке - с Nexus. При изменении index-url uv меняет uv.lock файл, чего я делать не хочу. Но, наверное, это логично.Наткнулся на статью в которой автор говорит, что у него Oh My Zsh тормозит запуск терминала: типа 380мс (на голый OMZ) это очень медленно, а у него еще какой-то воркфлоу, который постоянно открывает новые окна терминала.
Я полистал, и вспомнил, что у меня-то терминал тоже долго открывается. Оказалось до 1.47 сек. Неприятненько. Решил разобраться что к чему.
Для начала небольшая экскурсия в часто-рекомендуемый стек работы с терминалом:
- эмулятор терминала: например iTerm2 или ghostty. Отвечает за отрисовку терминала, GUI по сути.
- zsh - командный интерпретатор. По дефолту в Макоси стоит именно zsh. Аналоги: bash, fish, sh
- oh-my-zsh - фреймворк для zsh для более удобной установки плагинов, тем и т.д.
- prompt - тема для строки приглашения, например: powerlevel10k или starship
У zsh есть свой профайлер, чтобы его включить добавляем в
~/.zshrc:
# в начало файла
zmodload zsh/zprof
.....
# в конец файла
zprof
Посмотрим, что там у меня больше всего тормозит:
nvm_auto 398.78ms 50.55%
__yc_bash_source 163.05ms 20.67%
nvm 195.29ms 24.75%
compinit 226.47ms 28.71%
nvm, тулза для яндекс облака, и какой-то compinit. В принципе, вещи которыми я пользуюсь довольно редко. Скрипты nvm и yc в .zshrc поставились автоматически при установке соответствующих инструментов. Там всякие автокомплиты, обновления, сканы файлов…
Для nvm и yc сделал “ленивую” загрузку, чтобы эти скрипты загружались в сессию только при запуске команды. (см. в примере ниже)
compinit - это оказался автокомплит в zsh. Для него выключаю аудит файлов, проверку кэша:
autoload -Uz compinit
compinit -C -u
Из той статьи я решил подчерпнуть минималистичность конфигурации и призыв ставить только то, что нужно. Поэтому удаляю все следы oh-my-zsh из .zshrc. Понадобится - поставлю.
Плагин zsh-autosuggestions ставлю через brew.
А любимую тему для prompt (powerlevel10k) ставлю без oh-my-zsh.
Вот такое мне уже нравится:
/usr/bin/time zsh -i -c exit
0.17 real
Теперь хочется добавить в промпт информацию об активной версии Python и venv.
В репозитории обновил свой актуальный .zshrc.
✨ А чем вы пользуетесь для работы с терминалом? Делитесь вашим стеком, темами и скриншотами в комментариях!