этогик // DevOps, Infrastructure, Productivity
رفتن به کانال در Telegram
🚀Блог DevOps-инженера. Вместе растем в хард- и софт скиллах, разбираемся как жить и работать продуктивно, ищем баланс во всем 🌿 📽 yt: https://www.youtube.com/@etogeek 🌍 site: https://etogeek.dev 🌿 chat: @etogeekchat Подробнее в закрепе 🦾
نمایش بیشتر4 359
مشترکین
اطلاعاتی وجود ندارد24 ساعت
+97 روز
+930 روز
در حال بارگیری داده...
کانالهای مشابه
ابر برچسبها
اشارات ورودی و خروجی
---
---
---
---
---
---
جذب مشترکین
اوت '26
اوت '26
+71
در 0 کانالها
ژوئیه '26
+57
در 0 کانالها
Get PRO
ژوئن '26
+70
در 0 کانالها
Get PRO
مه '26
+79
در 0 کانالها
Get PRO
آوریل '26
+119
در 0 کانالها
Get PRO
مارس '26
+161
در 2 کانالها
Get PRO
فوریه '26
+124
در 0 کانالها
Get PRO
ژانویه '26
+130
در 0 کانالها
Get PRO
دسامبر '25
+121
در 0 کانالها
Get PRO
نوامبر '25
+143
در 0 کانالها
Get PRO
اکتبر '25
+154
در 0 کانالها
Get PRO
سپتامبر '25
+143
در 0 کانالها
Get PRO
اوت '25
+132
در 0 کانالها
Get PRO
ژوئیه '25
+142
در 0 کانالها
Get PRO
ژوئن '25
+124
در 0 کانالها
Get PRO
مه '25
+171
در 0 کانالها
Get PRO
آوریل '25
+138
در 0 کانالها
Get PRO
مارس '25
+156
در 0 کانالها
Get PRO
فوریه '25
+168
در 0 کانالها
Get PRO
ژانویه '25
+168
در 0 کانالها
Get PRO
دسامبر '24
+125
در 0 کانالها
Get PRO
نوامبر '24
+132
در 0 کانالها
Get PRO
اکتبر '24
+232
در 0 کانالها
Get PRO
سپتامبر '24
+271
در 0 کانالها
Get PRO
اوت '24
+163
در 0 کانالها
Get PRO
ژوئیه '24
+159
در 0 کانالها
Get PRO
ژوئن '24
+143
در 0 کانالها
Get PRO
مه '24
+239
در 0 کانالها
Get PRO
آوریل '24
+190
در 0 کانالها
Get PRO
مارس '24
+282
در 0 کانالها
Get PRO
فوریه '24
+766
در 1 کانالها
Get PRO
ژانویه '24
+30
در 1 کانالها
Get PRO
دسامبر '23
+223
در 0 کانالها
| تاریخ | رشد مشترکین | اشارات | کانالها | |
| 31 اوت | +3 | |||
| 30 اوت | 0 | |||
| 29 اوت | 0 | |||
| 28 اوت | +5 | |||
| 27 اوت | +1 | |||
| 26 اوت | +9 | |||
| 25 اوت | +4 | |||
| 24 اوت | +3 | |||
| 23 اوت | +1 | |||
| 22 اوت | 0 | |||
| 21 اوت | +1 | |||
| 20 اوت | +2 | |||
| 19 اوت | +3 | |||
| 18 اوت | +3 | |||
| 17 اوت | +2 | |||
| 16 اوت | +3 | |||
| 15 اوت | +1 | |||
| 14 اوت | +1 | |||
| 13 اوت | 0 | |||
| 12 اوت | 0 | |||
| 11 اوت | +1 | |||
| 10 اوت | +3 | |||
| 09 اوت | +4 | |||
| 08 اوت | 0 | |||
| 07 اوت | +2 | |||
| 06 اوت | +3 | |||
| 05 اوت | +6 | |||
| 04 اوت | +3 | |||
| 03 اوت | +2 | |||
| 02 اوت | +3 | |||
| 01 اوت | +2 |
پستهای کانال
Чтобы немного подвести итог серии постов про бекапы (раз и два), расскажу как я ими управляю в своей инфрастркутуре.
Основная цель такого поста не показать единственный способ "как правильно" (такого нет), а поделиться моим опытом, из которого вы можете подцепить какую-нибудь идею и реализовать у себя.
В общем, конечно же, я делаю это через 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 в том виде, в котором они есть и попадут на машину.| 2 | Мониторинг бекапов
В прошлый раз я говорил про 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. | 1 654 |
| 3 | Про бекапы. Хочу немного поделиться своим опытом в нескольких постах. Это больше информация для новичков, на которой я хочу и в дальнейшем фокусироваться, если честно. Синьоры-помидоры, вы итак это всё знаете (надеюсь).
Что первое приходит в голову, когда мы говорим про бекапы баз данных?
Ну, делать дамп раз в сутки через 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 в облаке. Сделал себе еще дашбордик с метриками и логами по бекапам, чтобы смотреть - всё ли с ними ок. | 2 118 |
| 4 | Когда я преподавал в Практикуме (был такой период, да. еще и видео снимал), студенты часто возмущались, что менторы не дают прямого ответа на их вопросы. Они подсказывают направление для размышления, но не дают готовое решение.
Я верю в то, что когда человек в процессе обучение натыкается на проблему и решает её сам - именно так он получает тот самый ✨ОПЫТ✨. Это не потерянное время.
Наткнулся на видео от JetBrains Academy (7 минуток), про то, что происходит, когда новички учатся кодить с помощью Codex/Claude. Там разбирают интересное исследование, где группе студентов давали задачу и следили за тем как именно они приходят к решению.
В большинстве случаев они давали рабочее решение, но студенты разделились на две группы с разным подходом:
1. понял, что нужно сделать - AI предлагает решение - оцениваю - принимаю/отбрасываю
2. не понимаю, что делать - AI предлагает решение - выглядит норм - принимаю
Первые сами говорят агенту, что им нужно сделать. У них уже было в голове примерное направление действий, агент помогал ускорить весь процесс.
Вторых, изначально недостаточно разобравшись в задаче, двигались туда, куда их двигал агент. Решение есть - понимания "почему так" нет.
То есть программа в конце работает у обеих групп, только часть людей не обязательно стала лучше понимать технологию.
Суть в чем, возвращаясь к моей изначальной идее: если ты закидываешь задачу в ЛЛМ и бездумно применяешь предложенное решение - опыта ты не получаешь. Грош - цена таким знаниям.
Лучший вариант, это просить агента оценить твое решение, объяснить концепцию, дать подсказку, показать ошибку.
Когда учишься - не перекладывай на AI сам процесс мышления.
Используй AI для ускорения своей работы. | 2 837 |
| 5 | Некоторое время назад столкнулся с тем, что какой-то сервис отправил мне огромное количество логов в Локи. В цитируемом посте я рассказывал процесс поиска виновника.
Сейчас проблема повторилась, и я разобрался как принудительно удалять старые логи.
Во-первых, в конфигурации должен быть активирован 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) и наслаждаемся освобожденным местом. | 1 733 |
| 6 | Kubernetes — мощно, но сложно. 8 июля в Москве собираются инженеры, чтобы разобраться вместе: поговорить о реальных кейсах, пообщаться за круглыми столами и познакомиться с теми, кто решал похожие задачи.
Будут доклад о домашнем кластере на несколько квартир и дата-центров, разбор ИИ-агента, который управляет инфрой как админ, и дискуссии по сетям, виртуализации и безопасности.
Это юбилейный митап Deckhouse User Community — и на этот раз особенно много внимания тем, кто активно участвует в жизни сообщества. Коллеги поделятся запуском программы поддержки контрибьюторов.
Только офлайн, количество мест ограниченно, регистрируйтесь! | 1 818 |
| 7 | Уже довольно давно я сталкиваюсь с плавающими проблемами связи между Яндекс Облаком и Hetzner: у рандомного https- соединения скорость может упасть до 100кб/с. Способы стабилизировать это нашлись, а вот полностью решить так и не получилось.
Захотелось замониторить эту скорость скачивания, чтобы найти какие-то закономерности (спойлер, на нашел).
За несколько гугл-запросов подходящий экспортер не нашелся - в основом все проверяют скорость до speedtest-а, а мне нужны кастомные URL - например свой Nexus.
Время сейчас такое, что если чего то нет, или функционал не устраивает, ты просто пишешь своё. Вот и я сделал download speed exporter:
👉 https://github.com/etoosamoe/download_speed_exporter
Собираем образ контейнера, запускаем, а в Prometheus или vmagent дергаем /probe-ручку с URL-ом целевого файла. В ответ получаем метрики по скорости скачивания. Дашборд есть в комплекте.
На самом деле я сделал его уже давно, просто почему-то не рассказывал тут.
На скриншоте пример того, как скорость скачивания вообще упала до минимума на пол дня. | 2 632 |
| 8 | Работа в небольшой продуктовой компании часто расширяет границы твоей позиции. Я уже давно не просто “девопс”, который перезагружает серверы и дебажит пайплайны. Вопрос плохо это или хорошо - оставлю каждому на размышление.
Например, не так давно занимался сначала объединением двух "сайтов" Jira и Confluence в один. Такую попытку уже делал мой лид за полгода до этого, но всё разбилось об ограничения: проблемы при мерже, скачивание/загрузка бекапа и тд.
А спустя время, оказалось, что Atlassian только-только выпустили новый инструмент как раз для переноса данных между “сайтами”. Он отработал просто превосходно, перенес проекты со всеми данными, аттачами и почти ничего не поломал.
Затем приступил к миграции из Notion в Confluence. Это получилось более хаотично. Многие страницы в Ноушене были приватными. А к приватным страницам даже у админа нет доступа. Нужно просить сотрудников, у кого есть доступ, экспортировать архив, скидывать его мне, а я уже импортирую его в Confluence.
Одновременно с этим руководство захотелось разделить все статьи по пространствам - например Sales, Finance, Product и так далее. Минус Confluence по сравнению с Notion или Google Docs в том, что ты не можешь дать доступ одному человеку к одному документу. Нужно добавить человека в раздел (Space), и тогда он получит доступ ко всем открытым статьям.
Несмотря на то, что Confluence - хороший инструмент, переезд из Notion был воспринят коллегами не очень мягко. Было много непоняток с доступами, проблем с поисками “потерянных” статей, исправлений ссылок в легаси-местах, проблем с форматированием.
Суммарно наэкономили около $24к в год. Жаль не в свой карман, конечно. | 3 038 |
| 9 | В последние недели погрузился в абсолютно новую для себя штуку - мобильную разработку. Точнее, 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-апдейты без ревью запрещается правилами сторов.
Описал для разработчиков релизный процесс - как будем работать с фича-ветками, релизными ветками, версиями. Постарался сделать так, чтобы это минимально отличалось от уже привычного всем в команде процесса.
В общем, погружение в новую область это всегда так - сначала чувствуешь себя идиотом, не знаешь основных терминов. Мозг к вечеру выжат настолько, что хочется просто сесть на диван и смотреть в стену. Главное помнить - глаза боятся, а руки делают. Потихоньку, мелкими итерациями, но в итоге картина становится яснее, начинаешь лучше во всем ориентироваться. | 2 875 |
| 10 | Немного бомбления про 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.
Что с этим делать?
Да фиг его знает ¯\_(ツ)_/¯ | 1 953 |
| 11 | Deckhouse Conf 2026 — один день концентрированного технического хардкора.
9 апреля в Москве соберём 1000 инженеров, которые строят платформенные продукты и управляют инфраструктурами.
Зачем приезжать:
— Проверенные кейсы внедрения.
— Демо технологий Deckhouse и возможность протестировать их своими руками.
— Хардкорные технические доклады про виртуализацию, SDN, мониторинг, миграцию на микросервисы и многое другое.
— Прямой контакт с разработчиками и ведущими инженерами.
Один день, два трека, 1000 гостей, максимум полезного общения.
Регистрируйтесь | 1 297 |
| 12 | Хочу немного рассказать про стек, о котором я раньше никогда не слышал, а четыре года назад пришлось плотненько познакомиться сначала со стороны администрирования, а спустя время - и чуть-чуть потраблшутить часть разработки.
Добро пожаловать в мир 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". | 2 398 |
| 13 | 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 года и удалит старые. Скрипт запустил в фоне, крутится неспеша, память освобождается, все довольны. | 2 629 |
| 14 | Про миграции в базе данных, блокировки и 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-реплику (пока только теория)
- Мониторинг и логирование. Пока еще думаю какое именно.
- (еще почитать) | 2 774 |
| 15 | 26 февраля — Deckhouse User Community meetup #4. Это митап для тех, кто хочет понимать Kubernetes глубже.
Зарегистрироваться
Эксперты Deckhouse и приглашённые спикеры расскажут, как запускать K8s поверх любых дистрибутивов, эксплуатировать платформу в одиночку, развёртывать домашнюю виртуализацию на бюджетном железе и грамотно подходить к безопасности.
И покажут: на митапе будет работать зона «Попробуй сам», где можно протестировать работу Deckhouse Kubernetes Platform Community Edition своими руками. | 2 414 |
| 16 | Классный пример поддержки от SaaS. Именно такой уровень вовлеченности ожидаешь от поддержки крупного (очень) сервиса.
Я сейчас занимаюсь переездами в небезызвестный Confluence (поделие Atlassian) из всяких Notion-ов, из соседних пространств, меняю структуру разделов и т.д. И наткнулся на неочевидную проблему - не видно страниц внутри некоторых разделов. А поиском - видно.
Написал тикет, поставил галочку "можно получить доступ в наш инстанс" и стал ждать.
Через полчаса увидел в списке пользователей сотрудника Atlassian, а еще через полчаса в почту пришел ответ. В нем самое интересное.
Во-первых, текст был не похож на LLM-ответ. Вероятно был отформатирован, но само написание выглядело человеческим. Такое - редкость сейчас.
Во-вторых, он содержал несколько скринов, подробное описание того, в чем проблема. А главное, чел написал, что в одном разделе всё пофиксил и записал ВИДЕО ✨ о том, как починить во всех остальных разделах.
Вот так вот.
Проблема только в том, что мир SaaS ждут проблемы. С приходом LLM каждая компания по сути может сделать свой SaaS.
Клонов Notion-а уже over9000. Зачем платить 10 баксов за юзера, если можно навайбкодить свои Jira и Confluence для внутреннего использования.
Но условная Figma под меньшим риском - её сложнее повторить, это уже некий стандарт индустрии фронтенда/дизайна.
Получается что под угрозой те SaaS-ы, которые раньше покупали из-за сложности разработки.
Главная проблема даже не в том, что клиенты реально могут сделать качественный продукт (далеко не все понимают, что идет в архитектуру крупных систем). Проблема в том, что клиенты чувствуют, что могут. И этого может быть достаточно для отмены подписки на SaaS.
Что же остается делать SaaS-ам?
- Глубже подсаживать пользователей на свой продукт. Это когда один раз начав активно пользоваться сервисом, уже сложно будет переехать на другой (DataDog?).
- Адаптироваться под клиента. Улучшать поддержку.
- Менять продукт и становиться "ближе к деньгам клиента"
В интересные времена живем... | 2 885 |
| 17 | В последнее время я не то что бы очень часто делаю что-то прям техническое-девопсерское руками, и когда выпадает возможность что-то потраблшутить, можно сказать даже радуюсь этому.
Вот вечером приходит алерт, что на виртуалке с 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, например, за определенные даты. Только если настраивать новый ретеншен, либо грохать всё. В моем случае я просто оставлю, пока есть свободное место - в течение ретеншен-периода оно уйдет. | 2 412 |
| 18 | Пару постов назад я делился своим .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)"
}
Как обычно код можно посмотреть у меня в репозитории, вот ссылка. | 2 623 |
| 19 | Небольшой отчет о том, чем занимался на работе в последнее время.
У нас есть довольно большой проект на 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 файл, чего я делать не хочу. Но, наверное, это логично. | 2 441 |
| 20 | Наткнулся на статью в которой автор говорит, что у него 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.
✨ А чем вы пользуетесь для работы с терминалом? Делитесь вашим стеком, темами и скриншотами в комментариях! | 2 807 |
