что-то на инженерном


Гео и язык канала: Россия, Русский
Категория: Технологии


все о дата инжиниринге тут
*исключительно мнение и опыт автора*
сотрудничество/реклама: @iamannabo

Связанные каналы

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


На днях стала свидетелем неприятной ситуации.

Началось все с того, что я провела K. мок-собеседование на дата инженера. K. прошел его настолько хорошо, что мне искренне захотелось ему помочь, так что я порекомендовала его знакомым ребятам, которые как раз были в поиске инженера.

K. успешно прошел эйчарский скрининг, далее техническую секцию, где ему определили уверенный мидловский грейд, далее была встреча с PO - и спустя некоторое время я узнала, что K. готовы сделать оффер. Я была очень рада: наконец-то удалось помочь классному инженеру, который уже долгое время пребывает без работы.

K. заполнил анкету СБ, приложил все необходимые выписки… а потом письмо от эйчар - данному кандидату не рекомендуют делать оффер ввиду отсутствия релевантного опыта и явного расхождения в резюме и трудовой книжке.

Оказалось, что тот опыт, который был в резюме и про который так уверенно рассказывал инженер, вообще не сопоставим с трудовой книжкой. Вместо трех лет инженерного опыта в одной компании там были частые смены работ и совершенно разные роли.

Для меня эта история про риск: понятное дело, что с текущим кул-офф на рынке и не самым красивым опытом в резюме возникает соблазн его приукрасить, чтобы просто повысить конверсию откликов и приглашений на собеседование. Я не осуждаю, но и не советую всем такой подход - у меня нет статистики, как часто это прокатывает.
Но думаю, что данный кейс вполне мог бы разрешиться удачно, если бы K. на одном из этапов упомянул про расхождение своего реального опыта с тем, что написано в резюме.

А вы что думаете? Как относитесь к подобной практике приукрашивания опыта?


StarRocks DB

В предыдущем посте я упомянула, что сейчас активно занимаюсь задачами миграции на новый стек. В качестве аналитического/бизнес слоя у нас выбран StarRocks. Сегодня расскажу, что это за база данных, какие у нее особенности, и какие сложности у нас возникли при миграции.

StarRocks - это распределенная аналитическая СУБД класса MPP OLAP с векторизованным движком и оптимизатором на основе стоимости (CBO).

Особенности:
🟣sub-second latency - сверхбыстрая реакция на сложные запросы с агрегациями, джойнами и аналитическими функциями.
🟣работа с Data Lake - прямая поддержка данных в формате Iceberg, Hudi, Delta Lake без импорта.
🟣real-time аналитика - быстрая загрузка данных, поддержка мгновенных upsert/delete, кэш и материализованные представления.

Архитектура StarRocks:
Состоит из двух компонентов: Frontend Nodes и Backend Nodes.

🟣Frontend (FE) управляют метаданными, принимают запросы, планируют и распределяют выполнение.
🟣Backend разделен еще на два типа в зависимости от режима работы:
◀️Режим хранилища (Shared-Nothing) - Backend Nodes (BE), которые хранят данные и выполняют SQL.
◀️Режим озер данных (Shared-Data) - Compute Nodes (CN), которые не хранят данные локально, а выполняют вычисления с кэшем.

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

Одна из проблем, с которой мы столкнулись:
Большая часть легаси кода у нас в формате хранимых процедур Greenplum. На текущий момент StarRocks не поддерживает нативных хранимых процедур в духе GP/PostgreSQL.
Поэтому, существующие процессы было решено переписать на PySpark. И, если раньше аналитики сами писали процедуры под свои задачи, то теперь придется выстраивать процесс иначе.

Поскольку, мы еще находимся в процессе миграции, то окончательное мнение об этой БД высказывать не стану. Но пока ощущения скорее положительные, да и в целом изучать новые инструменты всегда приятно.

➡️Для тех, кто хочет детальнее погрузиться в StarRocks - я рекомендую короткий бесплатный курс на Stepik. Курс без практики, но автор понятными словами знакомит с особенностями архитектуры StarRocks и кейсами его применения.

p.s картинка с архитектурой из блога alibaba cloud.

©️что-то на инженерном


Сто лет ничего не писала, потому что было очень много настоящей инженерной работы. Моя команда сейчас в активной миграции на новый стек - переводим процессы с Greenplum и Hadoop в Lakehouse на s3 + iceberg + starrocks. Как это обычно случается с миграцией - оценить и предусмотреть все невозможно, но в данном случае сроки изначально были очень амбициозными.

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

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

Удивительно, что вернуться к базовым настройкам мне помогли горящие сроки и давно забытые переработки. Все еще не одобряю такой подход на постоянной основе, но как будто бы такую временную встряску можно назвать рабочим лайфхаком. Будем надеяться, что это не кратковременный эффект от пригретой меня весенным солнышком, и теперь я буду чаще приходить сюда с полезными постами 🥰.

А как ваши дела?


Ищем проблемный запрос в ClickHouse

Если CPU на пределе, память почти закончилась, а запросы тормозят, то самое время искать виноватого. О том, как это сделать хорошо рассказывает автор статьи на хабре: CPU 80%. Как найти проблемный запрос в ClickHouse? Я оставила важные моменты и дополнила нюансами, которых мне не хватило.

Начинаем с того, что смотрим, что выполняется прямо сейчас:
SELECT
query_id,
user,
elapsed,
formatReadableSize(memory_usage) AS ram,
formatReadableSize(read_bytes) AS read_size,
read_rows,
query
FROM system.processes
ORDER BY elapsed DESC;

system.processes содержит активные запросы, их длительность, память и чтения. Если запрос явно лишний, его можно остановить через:
-- ASYNC (по умолчанию) — команда вернётся сразу, запрос остановится чуть позже
KILL QUERY WHERE query_id = 'xxx' ASYNC;

-- SYNC — ждёт фактической остановки запроса
KILL QUERY WHERE query_id = 'xxx' SYNC;

Но KILL QUERY требует либо права KILL QUERY у пользователя, либо чтобы запрос принадлежал ему самому, иначе получите ошибку.

За анализом завершённых запросов идём в system.query_log. Чтобы получить свежие данные, предварительно можно выполнить:
SYSTEM FLUSH LOGS;

Важно помнить, что query_log локален для каждого узла. На кластере нужно смотреть на каждом узле отдельно или использовать clusterAllReplicas:
SELECT *
FROM clusterAllReplicas('your_cluster', system.query_log)
WHERE event_date >= today()
AND type = 'QueryFinish'
ORDER BY query_duration_ms DESC
LIMIT 20;

Ищем виновников среди пользователей и хостов:
SELECT
user,
client_hostname,
count() AS queries,
round(sum(query_duration_ms) / 1000, 1) AS total_sec,
formatReadableSize(sum(read_bytes)) AS total_read
FROM system.query_log
WHERE event_date >= today()
AND type = 'QueryFinish'
GROUP BY user, client_hostname
ORDER BY total_sec DESC
LIMIT 10;

Когда непонятно, кто именно создает нагрузку, смотрим на user, client_hostname и особенно log_comment. Это очень помогает, если под одной учеткой работает оркестратор или несколько сервисов. Но только в случае, если log_comment уже встроен в ваши сервисные клиенты.

Для поиска конкретно тяжелых запросов сортируем по нужной метрике в зависимости от симптома: query_duration_ms, memory_usage, read_bytes или CPU:
SELECT
query_id,
user,
query_duration_ms / 1000 AS duration_sec,
formatReadableSize(memory_usage) AS ram,
formatReadableSize(read_bytes) AS read_size,
read_rows,
ProfileEvents['OSCPUVirtualTimeMicroseconds'] / 1e6 AS cpu_sec,
query
FROM system.query_log
WHERE event_date >= today()
AND type = 'QueryFinish'
ORDER BY memory_usage DESC -- меняем на нужную метрику
LIMIT 20;

При этом стоит знать про значения поля type, это поможет при отладке не только медленных, но и падающих запросов:
⭐️QueryStart -> Запрос начался
⭐️QueryFinish -> Успешно завершился
⭐️ExceptionBeforeStart -> Ошибка до старта
⭐️ExceptionWhileProcessing -> Ошибка в процессе

Для поиска падающих запросов:
WHERE type IN ('ExceptionBeforeStart', 'ExceptionWhileProcessing')

Для профилактики появления проблемных запросов полезно использовать:
EXPLAIN indexes = 1
SELECT ... -- подозрительный запрос

Он показывает, насколько запрос реально использует ключ сортировки и сколько гранул будет прочитано. Если читается почти вся таблица, проблема, скорее всего, в фильтре или в структуре хранения. Если нужно понять pipeline выполнения целиком:
EXPLAIN PIPELINE
SELECT ...;

Еще один уровень защиты - это пользовательские ограничения на уровне профиля или отдельного пользователя. Они не дают одному запросу положить весь кластер:
max_execution_time = 60 -- максимум 60 секунд на запрос
max_memory_usage = 10000000000 -- максимум ~10 GB RAM на запрос
max_rows_to_read = 1000000000 -- максимум 1 млрд строк
В большинстве случаев этого уже хватает, чтобы за минуты найти тяжелый запрос и понять, что именно чинить.


Хочу поделиться классной штукой - симулятором карьерного пути дата инженера от Джо Рейса, автора книги Fundamentals of Data Engineering.

Это интерактивная игра, где можно пройти путь инженера данных на разных этапах: beginner, mid-level и senior. На каждом уровне приходится принимать решения, как в реальных проектах: какие технологии выбрать, как построить пайплайн и как справиться с типичными (и не очень) проблемами инфраструктуры.

Вот пример одного из заданий:
Initech is growing fast. You now have 200+ dbt models, and the analytics team is complaining that queries are slow, data is inconsistent between dashboards, and nobody knows which model to trust... The senior analyst wants a fully normalized star schema. The ML engineer wants wide denormalized tables. The new analyst just wants “one big table with everything.” Your data architect quit last month — something about his stapler and the basement. Bill says he’s gonna need you to go ahead and propose a modeling strategy. That would be great. What do you do?


Мне кажется, такой вопрос мог бы вполне встретиться на собеседовании.

Я прошла уровень mid-level успешно. Думаю, теперь можно уверенно претендовать на роль staff data engineer 😅.

В общем, очень рекомендую попробовать - отличный способ проверить свои знания и опыт, ну и заодно развлечься.

Делитесь в комментариях своими результатами 🔚


Разбираемся с unionByName() в Spark

Представьте, что у вас есть два датафрейма из разных источников: один выгрузили из PostgreSQL с колонками name, age, city, а второй из CSV с city, name, age. Обычный union() в «лучшем» случае сольёт их по позициям, и city из первого улетит в age второго, либо упадет в ошибку. А unionByName() решает это элегантно: матчит колонки по именам, а не по порядку.

Как работает unionByName()?

unionByName(df2, allowMissingColumns=True) объединяет датафреймы, сопоставляя столбцы по названиям, независимо от их последовательности.

Если в одном датафрейме колонка отсутствует, то подставится null. Это особенно полезно в ETL-пайплайнах, где схемы слегка расходятся (добавились/убрались поля), но семантика та же.

Пример кода

df1 = spark.createDataFrame([
("Anton", 23, "Moscow"),
("Valentina", 27, "Omsk")
], ["name", "age", "city"])

df2 = spark.createDataFrame([
("Frank", "Moscow", 19, 'M'),
("Boris", "Omsk", 55, 'M')
], ["name", "city", "age", "gender"]) # порядок другой!

result = df1.unionByName(df2, allowMissingColumns=True)
result.show()

+---------+---+------+------+
| name|age| city|gender|
+---------+---+------+------+
| Anton| 23|Moscow| null|
|Valentina| 27| Omsk| null|
| Frank| 19|Moscow| M|
| Boris| 55| Omsk| M|
+---------+---+------+------+


Результат: spark сам разобрался, и все красиво слиплось по именам колонок.

Особенности:

⭐️В случае «плавающих» схем в источниках необходимо добавить параметр allowMissingColumns=True. Тогда при появлении новых полей в одном из датафреймов автоматически добавятся отсутствующие колонки со значениями null для присоединения другого датафрейма. В версиях spark < 3.1.0 придется вручную выравнивать схемы через select или withColumn.
⭐️Добавлять distinct() в конце для union без дублей. Поскольку в spark union() и unionByName() работают как union all (сохраняют дубликаты).
⭐️До соединения датафреймов лучше унифицировать типы данных. Spark будет пытаться привести типы, но может упасть, если это невозможно (например, String vs Int).

Что в итоге?

Считаю, что это один из самых удобных методов в spark. Я практически во всех своих пайплайнах использую unionByName(), чтобы не заморачиваться с дрейфующими схемами данных. Пожалуй, только за исключением данных типа StructType с несколькими уровнями StructType внутри. Как правило, на корневом уровне все срабатывает корректно, но со вложенными структурами уже начинается путаница.

Подробнее в статьях: spark.apache.org, mungingdata.

©️что-то на инженерном


По ту сторону собеседований

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

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

Чаще всего перед собеседованием я успевала посмотреть резюме кандидата и отметить те инструменты и технологии, которые пересекаются с нашим стеком. Предварительно у меня был сформирован свой список вопросов - те, на которые я сама смогла бы с уверенностью ответить.

Процесс был следующим: во время проведения технического этапа основной пул вопросов задавал тимлид, остальные участники (PO, инженеры) заполняли паузы своими вопросами. Сразу же после собеседования обсуждали кандидата и принимали решение об его дальнейшей судьбе.

Я не могу сказать, что с моей стороны и со стороны моих коллег собеседования были проведены идеально. Например, один из кандидатов, на выборе которого я настаивала и в навыках которого была уверена, ушел от нас через месяц — выбрал другую компанию с лучшим оффером💸.

Думаю, что тогда я поторопилась с решением и не оценила уровень его мотивации и заинтересованности в работе с нами.

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

В книге «Think Like a CTO» (согласна, у книги дурацкое название) Алана Уильямсона мне очень понравилась глава 5: про то, как нужно выстраивать процесс интервьюирования кандидатов. Вот небольшая выжимка из того, я себе выписала:

🌸До старта проведения собеседований составлен перечень обязательных и желательных требований к кандидату, который затем будет использован как лист оценки навыков кандидата интервьюерами. Соответственно, на выходе после собеседования получается не сухая обратная связь в стиле шарит/не шарит, а конкретная оценка компетенциям кандидата.

🌸Собеседование выстроено в формате диалога, а не допроса или экзамена в стиле топ-100 вопросов для дата инженера. Вопросы и задачи сформированы из реальных проблем, с которыми команда сталкивалась в работе или которые предстоит решить новому сотруднику. Никаких «нестандартных» задач на логику и личных тем.

🌸Вместо кошмарного лайф-кодинга задач, которые и близко не используются в работе, лучше придумать кейс с неочевидными вариантами решения, над которым можно подискутировать вместе с кандидатом.

🌸Идеально, когда решение о выборе кандидата принимается двумя и более интервьюерами. Но при этом роли интервьюеров заранее определены, они не перескакивают с темы на тему и не перебивают друг друга.

🌸Заинтересовать кандидата работой в компании и выделить время на его вопросы. Лично я часто замечаю, что эту часть обычно оставляют на конец собеседования, когда у кандидата не остается сил и времени, чтобы задать вопросы и вообще понять, что за работа его ждёт. Узнать мнение кандидата по поводу компании/продукта, чтобы выявить уровень осведомленности и интереса.

🌸Отдельно отмечу, что важно понравившегося кандидата заинтересовать прямо на собеседовании, не ограничиваться только ответами на его вопросы. А если становится понятно, что кандидат не подходит, лучше завершить с ним общение как можно раньше.

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


➡️Какое собеседование было бы идеальным для вас? Что вы бы добавили/убрали из списка рекомендаций от автора книги?


В конце года уже сложно сформулировать что-то универсальное и подобрать подходящие слова, вообще сложно формулировать хоть что-то… в общем, как смогла))

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

Поэтому мое пожелание простое:

Пусть в наступающем году к вам притянется именно то самое, чего не хватало.

Новая работа, повышение, семья, путешествие, все, что угодно, что принесет вам счастье.

С благодарностью отпускаю этот сложный и богатый на события год.

Друзья, коллеги, подписчики, до встречи в новом году! 🥂


Обещала после отпуска новый материал, но на неделе после работы и всех предновогодних дел никак не доходили руки до ведения канала. Исправляюсь!

На самом деле скопилось много наработок, идей и тем для постов, но каждый раз перед тем, как сесть за их написание, я задаюсь вопросом: «а интересно ли это кому-то вообще?».

Сейчас, как правило, когда у меня возникает какой-то срочный вопрос или интерес, то я иду в perplexity и добываю ответ там. Уже гораздо реже я ищу информацию в де-шных блогах или на медиум. Скорее использую эти ресурсы с целью получения новых знаний, а не поиска ответов на вопросы.

Мои размышления привели меня к научной статье, опубликованной в National Library of Medicine, про влияние генеративных AI на онлайн дев-комьюнити.

Исследование по Stack Overflow и Reddit показало, что запуск ChatGPT в конце 2022 года (почему-то авторы не упомянули другие модели, типа Claude, Gemini) заметно повлиял на поведение разработчиков в онлайне:

➖После релиза ChatGPT Stack Overflow потерял около 1 млн визитов в день (~12% трафика), а число новых вопросов существенно просело.

➖Сильнее всего упали вопросы по темам, где LLM особенно эффективны: Python, SQL, Pandas, CSS, React, Django, т.е. хорошо формализованные и широко представленные в открытых данных задачи.

➖Темы, требующие специфического контекста и закрытых корпоративных знаний (например, Spring/Spring Boot, AWS, Azure, Docker), пострадали меньше, т.к. их сложнее полностью заменить ии-шными ответами.


Интереснее всего, что после появления ChatGPT средний «стаж» аккаунтов, задающих вопросы на Stack Overflow, вырос, а сложность формулировок увеличилась. Это трактуют так:

Новички и джуны уходят с вопросами сразу к ИИ.

На платформе остаются более опытные разработчики с нетривиальными, контекстными задачами.


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

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

Получается, что ИИ выедает источник, на котором сам же и вырос 🤷‍♀️.

➖➖➖➖➖➖➖➖➖➖

Вы как? Еще читаете телеграм-каналы и статьи на хабре/медиум? Или все потребности в знаниях закрываете ИИ?


Всем привет!

Я нахожусь в отпуске, поэтому постов пока нет, но специально для вас есть акула, даже две 🦈 🦈

Скоро вернусь отдохнувшая и с новым материалом 🤍


Последние недели получились какие-то максимально загруженные в плане работы и личных дел. Уже чувствуется, как год подходит к концу, поэтому держимся из последних сил 💙

Ресурсы читать и изучать что-то рабоче-полезное тоже заканчиваются, поэтому пятничная вылазка на финал МТС True Tech Champ для меня как глоток свежего воздуха: никаких спарков, айсбергов и кликхаусов))

Узнала много нового про роботизацию и AI, посмотрела битву роботов. Искренне болела за семейную пару в финале, которые назвали команду в честь своей чихуахуашки и забрали третье место благодаря двум бутылкам с водой на своем роботе. На втором видео финальная битва за первое место с призом в 4 млн рублей, которая длилась меньше минуты 😅. Восторг!

Еще из интересного вчера были дебаты на тему: отпустить или удержать сотрудника, который хочет уйти.

С одной стороны, если сотрудник хороший, вас устраивает его работа, то расставаться экономически невыгодно.

Денис Горчаков, CISO Lamoda Tech, привел следующие цифры:
🔵средний срок жизни сотрудника на технической позиции составляет 12-18 месяцев;
🔵найм на техническую позицию в среднем занимает 36-60 дней;
🔵через 6 месяцев окупается сотрудник, лид / руководитель - через год.

Ну и прибавьте сюда расходы:
🔵время сотрудников на проведение собесов;
🔵время наставника / бадди, который будет погружать в работу нового сотрудника;
🔵время, когда новый сотрудник не делает ничего полезного и вкатывается в задачи.

В итоге, получается, что в данном случае выгоднее сделать все возможное, чтобы оставить сотрудника.

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

CTO кластера «развитие инфраструктуры» в MWS Андрей Ефремов рассказал о сотрудниках, которых лучше отпустить:
🔵токсичные ребята, с которыми трудно работать в команде;
🔵скрытые саботажники, которые мешают работе команды;
🔵low performers - сотрудники с низкой производительностью или те, кто не успевают за скоростью развития команды;
🔵те, кому пора двигаться дальше: кто проделал большой путь в компании и «вырос» из нее.

Кстати, я пока ни разу не пользовалась опцией, чтобы меня удержали повышением зп за счет оффера из другой компании. Обычно были не только финансовые причины, из-за которых я принимала решение об уходе.

А как у вас? Поделитесь историей, как вас удерживали в компании💬


Разбираемся с TTL в Clickhouse

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

С помощью TTL (Time-to-Live) в Clickhouse можно настроить автоматический перенос данных старше 30 дней в холодное хранилище, а в быстром доступе оставить агрегированные данные для статистики. Как это сделать расскажу ниже.

Что такое TTL?
TTL задает правило, по которому данные (строки или значения столбцов) автоматически обновляются или удаляются после истечения указанного времени, обычно связанного со столбцом типа Date, DateTime или DateTime64.

Важно: TTL-операции (удаление, обновление) выполняются не мгновенно. Они происходят во время фоновых операций слияния (merges) частей данных. Если части долго не сливаются, устаревшие строки могут сохраняться в таблице дольше указанного срока.


Виды TTL

🟣TTL для столбца задает правило для конкретного поля таблицы: через заданный интервал значение этого столбца заменяется на значение по умолчанию.

Если все значения столбца в части таблицы устарели, Clickhouse удалит этот столбец из части, экономя место. Такой подход полезен для частичной очистки данных, например, удаления чувствительных атрибутов (IP, имя пользователя) через сутки, но сохранения остальной информации.

Например:
ip_address String TTL event_time + INTERVAL 1 DAY;

➡️ Через сутки IP-адрес будет заменен на значение '' (пустую строку).

Или
price Float64 TTL event_time + INTERVAL 1 MONTH GROUP BY product_id SET price = avg(price);

➡️ Через месяц агрегируются данные по product_id.

🟣TTL для таблицы задает условие для удаления всех строк целиком. Например, хранение логов за 30 дней, по истечении которых устаревшие строки полностью удаляются. Это более простое и часто используемое правило управления историей данных.

Например:
TTL event_date + INTERVAL 30 DAY
TO VOLUME 'cold_storage'
DELETE;

➡️ Через 30 дней данные переместятся на том cold_storage, а после дальнейшего срока удалятся.

TTL для таблицы можно комбинировать с TTL для отдельных столбцов. Это удобно при хранении чувствительных данных, которые нужно анонимизировать раньше, чем удалять весь ряд.


Рекомендации по настройке TTL

⭐️Официальной докой рекомендуется всегда включать настройку ttl_only_drop_parts

Параметр ttl_only_drop_parts = 1 означает, что Clickhouse будет удалять только целые части данных, в которых все строки устарели по TTL, вместо попыток частичного удаления строк внутри части.
Это существенно облегчает процесс удаления и снижает нагрузку.

При таком подходе, особенно если партиции совпадают с единицами удаления по TTL (например, по дням), данные из отдельных дней не сливаются между собой, что позволяет эффективно и быстро удалять устаревшие части.


⭐️С включенной ttl_only_drop_parts можно уменьшить значение merge_with_ttl_timeout - интервала между слияниями с удалением устаревших частей, поскольку операция удаления становится проще и занимает меньше ресурсов.

Это снижает влияние TTL на производительность сервера и уменьшает время реакции на удаление устаревших данных.

По умолчанию составляет 14400 секунд (4 часа), но можно безопасно уменьшать, например, до 300-600 секунд.
При слишком низком значении могут возникать частые внеплановые слияния, что увеличит нагрузку на систему.


⭐️Принудительно запустить применение TTL можно командой:
ALTER TABLE my_table MATERIALIZE TTL;

⭐️Для моментального обновления данных без ожидания фоновых слияний можно использовать команду OPTIMIZE ... FINAL, но она тяжелая и не рекомендуется к частому применению.

Подробнее про TTL и больше примеров ➡️ в моей статье на Medium.

©️что-то на инженерном


🫡Думай как дата инженер

Тут на днях вышла статья про то, что важно не только уметь в хадупы и спарки, но еще и уметь думать как дата инженер.

Автору статьи в свое время менторы дали советы, которые стали для него определяющими в формировании его дата инженерского мышления.

Совет 1. Не гонись за деньгами, гонись за знаниями. Деньги последуют за тобой.

Каждый раз, когда автор выбирает между высокооплачиваемой позицией и менее оплачиваемой работой, но предполагающей развития скиллов, то он выбирает второй вариант. и не прогадывает. Впоследствии деньги волшебным образом сами приходят к нему.


Совет 2. Моделируй мир через призму данных и инжиниринга.

Все, что вас окружает, может быть представлено как модель данных. Буквально полки с продуктами в супермаркете можно представить как таблицу продуктов, а само событие покупки формирует уже другие таблицы - таблицу покупок, таблицу покупателей. И все они связаны между собой. В общем, хорошее упражнение, которое в реальности может встретиться в качестве задачи по проектированию DWH.


Совет 3. Мысли системно.

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


Совет 4. Поверь в себя

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


От себя я бы добавила следующий совет для тех, кто только начинает:

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

Следите за трендами в индустрии, изучайте новые инструменты, пробуйте их и добавляйте в свое резюме.

Аналогично с собеседованиями: не ждите какой-то конкретной отметки, после которой вы начнете ходить на них.

Откликайтесь на вакансии -> фиксируйте пробелы, дорабатывайте резюме -> пробуйте снова, и все обязательно получится.


А какой совет вы бы дали начинающим инженерам?😎


Гарантии доставки сообщений в Kafka✈️

В распределенных системах часто приходится выбирать: в каких сценариях можно позволить себе потерю или задержку сообщения, а где важно гарантировать его обработку без повторов.

В Kafka есть несколько уровней гарантий доставки: At Most Once, At Least Once и Exactly Once. От выбора уровня зависит поведение системы при сбоях, баланс между производительностью и надежностью, а также требования к идемпотентности обработки.

Предлагаю разобрать подробно, что означает каждая гарантия и где её целесообразно применять.

At Most Once (Не более одного раза)
Это гарантия, при которой сообщение отправляется только один раз, и если оно потерялось, повторной доставки не будет. Такой подход исключает дублирование, но допускает потерю данных.

Как это работает в Kafka?
⭕️Продюсер отправляет сообщение, не дожидаясь подтверждения от брокера (acks=0).
⭕️Брокер не гарантирует сохранение, т.е. сообщение может быть потеряно при сбое.
⭕️Консьюмер может использовать автофиксацию смещений (enable.auto.commit=true), что приведет к автоматическому коммиту даже до успешной обработки данных.

Но! Если консьюмер упадет после чтения сообщения, но до завершения обработки, это сообщение уже не будет доступно для повторной обработки.


Когда использовать?
Подходит для систем, где допустима небольшая потеря данных ради высокой пропускной способности, например:
⭕️сбор событий кликов на веб-сайтах;
⭕️телеметрия с IoT-устройств, где пропуски некритичны.

At Least Once (Как минимум один раз)
Гарантирует, что сообщение будет доставлено минимум один раз. Это исключает потерю сообщений, но допускает их дублирование.

Как это работает в Kafka?
⭕️Продюсер отправляет сообщение и ждет подтверждения от брокера (acks=1 или acks=all).
⭕️Консьюмер получает сообщение, обрабатывает его и только после успешной обработки коммитит смещение (manual commit).
⭕️Если консьюмер или процесс обработки упадет после выполнения бизнес-логики, но до комитта, консьюмер сможет запросить то же сообщение повторно.

Чтобы избежать искажений при повторной обработке, операции должны быть идемпотентными, т.е. повторное выполнение не должно изменять результат.


Когда использовать?
Подходит для систем, где потеря данных недопустима, а дублирование можно обработать:
⭕️обработка заказов или событий e-commerce;
⭕️системы мониторинга и логирования;
⭕️ETL-пайплайны, где данные могут быть дедуплицированы на следующем шаге.

Exactly Once (Ровно один раз)
Строгая гарантия, при которой сообщение обрабатывается ровно один раз, даже при сбоях или повторных доставках.

Как это работает в Kafka?
⭕️Продюсер включает идемпотентность (enable.idempotence=true), чтобы брокер игнорировал дубликаты сообщений.
⭕️Используются транзакции, объединяющие запись сообщений и фиксацию смещений в одну атомарную операцию.
⭕️После успешной транзакции коммит смещений и запись данных происходят одновременно, что исключает двойную обработку.

Консьюмер должен использовать isolation.level=read_committed, чтобы видеть только зафиксированные транзакции.

Когда использовать?
Идеален для критичных сценариев, где недопустимы ни потери, ни дублирование:
⭕️финансовые расчеты и платежные системы;
⭕️обработка заказов с жесткими гарантиями;
⭕️интеграции, где консистентность данных обязательна.

Зафиксируемся:
⭕️At Most Once - высокая скорость, но есть риск потери данных.
⭕️At Least Once - надежно, но возможны дубликаты.
⭕️Exactly Once - идеальная консистентность, но требует дополнительных усилий и ресурсов.


Источники:
📎Доставка сообщений и гарантии Kafka
📎Гарантированная доставка и хранение данных в Apache Kafka: внутренняя механика

©️что-то на инженерном


Предлагаю скрасить пятничный вечер и определить какой петух ваш тимлид 🐔

А если серьезно, на хабре вышла забавная статья с типологией тимлидов через образ петухов. Автор выделяет следующие типы:

🔵Руководитель, создающий видимость деятельности - горластый петух.
🔵Тихий, но эффективный лидер - тихий петух.
🔵Тиран, забирающий ресурсы для себя - жадный петух.
🔵Наставник и ресурс для команды - щедрый петух.
🔵Паникер, создающий хаос - петух-паникер.
🔵Защитник и кризисный менеджер - петух-защитник.
🔵Нарцисс, делающий всю работу сам - петух-наседка.
🔵Организатор, создающий условия для работы - петух-организатор.

Вообще классической моделью типологии управленческих стилей принято считать модель Ицхака Адизеса, который выделяет четыре типа руководителей:
🤮Производитель (Producer, P) - отвечает за получение результатов и выполнение задач. Это человек, ориентированный на продуктивность и достижение целей. Я думаю, что это классический тихий петух.
🤮Администратор (Administrator, A) - создает и поддерживает бизнес-процессы, структуры и порядок, обеспечивая эффективное взаимодействие и контроль. Похоже на описание петуха-организатора.
🤮Предприниматель (Entrepreneur, E) - генерирует новые идеи, управляет изменениями и развитием, ориентирован на инновации и стратегическое мышление. В зависимости от обстоятельств светлая или темная сторона петуха-паникера.
🤮Интегратор (Integrator, I) - обеспечивает объединение коллектива, гармонию и сотрудничество внутри команды, поддерживает долгосрочную жизнеспособность организации. Думаю, это больше подходит щедрому петуху.


Ну что, к какому типу относится ваш тимлид?)
Ответ просто петух не принимается.


Сегодня предлагаю разобрать популярную задачу с SQL-собесов.

Звучит она обычно так:
Есть таблицы t1 и t2, состоящие из одного столбца и имеющие m и n строк соответственно.
Какое минимальное и максимальное количество строк будет в конечной таблице T, полученной в результате джойна t1 и t2?
🟣t1 inner join t2
🟣t1 left join t2
🟣t1 right join t2
🟣t1 full outer join t2
🟣t1 cross join t2

⭐️Учитывая, что значения могут повторяться или быть равны NULL.

🏁🏁🏁🏁🏁🏁🏁🏁🏁
Для наглядности работы джойнов представим, что обе таблицы содержат по 5 строк с уникальными значениями от 1 до 5, типа:
t1 = [1,2,3,4,5]
t2 = [1,2,3,4,5]

🟣Все типы джойнов, кроме CROSS JOIN, вернут по 5 строк, т.к. для каждого значения в t1 есть ровно одно совпадающее значение в t2.
🟣CROSS JOIN создает все возможные комбинации пар строк: 5×5 = 25 строк.
🟣То есть, если значения одинаковые и уникальные, результат всех основных джойнов (INNER, LEFT, RIGHT, FULL OUTER) - это количество уникальных строк, а CROSS JOIN - произведение их количества.
🏁🏁🏁🏁🏁🏁🏁🏁🏁

Усложняемся, добавим в наборы данных NULL и дубликаты:

t1  = [1, 2, 2, NULL, 5]
t2  = [2, 2, 3, NULL, NULL]

🟣INNER JOIN
В t1 и t2 совпадают только значения “2”, следовательно, количество строк: 2×2=4 (каждая двойка из t1 с каждой двойкой из t2).
NULL с NULL не совпадает, строк с NULL в результате нет.
🟣LEFT JOIN
Все 5 строк из t1 гарантированы.
Значения с “2” вернут + две дополнительные строки, количество = 7 строк.
🟣RIGHT JOIN
Аналогично LEFT JOIN, но со всеми строками t2, количество = 7 строк.
🟣FULL OUTER JOIN
Включает все: дубликаты, NULL с обеих таблиц. Количество = 10 строк.
🟣CROSS JOIN
Каждая строка из первой таблицы умножается на каждую из второй = 25 строк.
🏁🏁🏁🏁🏁🏁🏁🏁🏁

💡Становится понятно, что минимум будет достигаться в случае отсутствия пересечения вообще. В таком случае:

🟣INNER JOIN вернет 0 строк, т.к. нет совпадений.
🟣LEFT JOIN вернет все строки из t1 с NULL в местах столбцов t2 ➡️ минимум m строк.
🟣RIGHT JOIN вернет все строки из t2 с NULL в местах столбцов t1 ➡️ минимум n строк.
🟣FULL OUTER JOIN вернет сумму количества строк из обеих таблиц (m + n), т.к. ни одна строка не совпала.
🟣CROSS JOIN остается без изменений ➡️ m × n строк.


💡А максимум будет достигаться, когда каждая строка t1 совпадает с каждой строкой t2:
При полном пересечении, когда каждая строка t1 совпадает с каждой строкой t2, все типы джойнов вернут максимальное количество строк m × n, потому что каждый элемент из одной таблицы сочетается с каждым элементом из другой, образуя полный набор пар совпадающих строк.


💡Если одна из таблиц полностью пустая (не содержит строк):
🟣INNER JOIN вернет 0 строк, т.к. нет данных для совпадений.
🟣LEFT JOIN, если пустая таблица справа, вернет все строки из левой таблицы с NULL в столбцах правой таблицы (число строк равно количеству строк в левой таблице).
🟣RIGHT JOIN, если пустая таблица слева, вернет все строки из правой таблицы с NULL в столбцах левой таблицы (число строк равно количеству строк в правой таблице).
🟣FULL OUTER JOIN вернет все строки из непустой таблицы с NULL в столбцах пустой таблицы (число строк равно количеству строк непустой таблицы).
🟣CROSS JOIN вернет 0 строк, т.к. произведение по пустому множеству всегда пусто.


А вам попадалась эта задача на собеседованиях?
😈

©️что-то на инженерном


SELECT FOR UPDATE - как правильно использовать блокировку

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

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

🤩При работе с транзакциями в PostgreSQL есть проблема «потерянного обновления» - это когда две транзакции понимают, что данные не изменились, читают одни и те же строки и обе пытаются их изменить одновременно. В итоге, одна перезаписывает изменения другой, и данные теряются.

Простой способ избежать этого - заблокировать нужные строки во время чтения, чтобы другие транзакции не могли их поменять или удалить. Для этого чаще всего используют SELECT FOR UPDATE:

sql
BEGIN;

SELECT * FROM orders WHERE order_id = 123 FOR UPDATE;

/* здесь логика обработки заказа */

UPDATE orders SET status = 'processed' WHERE order_id = 123;

COMMIT;

Таким образом гарантируется, что пока ваша транзакция не закончилась, никто другой не сможет обновить или удалить эти строки.

🤩 Но чаще всего операция SELECT FOR UPDATE избыточна и очень сильно влияет на производительность.

В PostgreSQL существует несколько режимов блокировок строк, которые влияют на параллелизм транзакций:

⭐️FOR KEY SHARE - самый мягкий режим, нужен для проверок ссылочной целостности (например, внешних ключей). Блокирует строку от удаления или изменения ключа, но позволяет обновлять обычные поля и разрешает другим транзакции читать данные под своей блокировкой.

⭐️FOR SHARE - блокирует строку для предотвращения изменений, позволяя читать с разделяемой блокировкой. Позволяет нескольким транзакциям одновременно читать данные, но никто не может их менять.

FOR NO KEY UPDATE - блокирует строку так, чтобы никто не мог её удалить или изменить ключи, и дает эксклюзивное право обновлять неключевые поля. Почти всегда подходит для обычных обновлений.

FOR UPDATE - самый жёсткий режим блокировки, блокирует строку полностью: эксклюзивное право на изменение или удаление строк. Использовать стоит только если вы меняете ключи или собираетесь удалять строки. При этом оставляет право читать ту же строку другим параллельным транзакциям, но только с помощью обычного оператора SELECT (без явных блокировок).

Что происходит, если использовать SELECT FOR UPDATE, когда достаточно FOR NO KEY UPDATE?

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


🔥Ключевые конфликты:

🤩FOR UPDATE конфликтует абсолютно со всеми другими режимами, гарантируя эксклюзивный доступ для полного изменения или удаления строки.

🤩FOR NO KEY UPDATE конфликтует с другими FOR NO KEY UPDATE и с FOR SHARE, потому что FOR SHARE запрещает любые изменения строки, а FOR NO KEY UPDATE разрешает изменения неключевых полей. При этом, не конфликтует с FOR KEY SHARE - это ключевая особенность для повышения параллелизма.

🤩FOR SHARE конфликтует с режимами обновления (FOR UPDATE и FOR NO KEY UPDATE), поскольку его цель - гарантировать, что строка не изменится вообще.

🤩FOR KEY SHARE конфликтует только с FOR UPDATE, потому что FOR UPDATE может включать удаление или изменение ключевых полей, что запрещено режимом FOR KEY SHARE.

Также приложила к посту алгоритм выбора уровня блокировки от автора статьи.

©️что-то на инженерном


Почему иногда моя мотивация к работе снижается?

Спойлер: дело не только в зарплате.

Я заметила, что сложнее всего мне даются задачи, когда я чувствую, что делаю работу ради работы на протяжении длительного времени.

Когда мне приходит задача типа «оптимизировать хранение данных, потому что витрина занимает много места в базе». Я беру и делаю ее: мне понятна формулировка задача, ее цель и какой должен быть конечный результат. Такая работа дается легко и приносит удовлетворение, когда процесс завершен и можно двигаться дальше.

Но бывают проекты, которые длятся долго и меняются по ходу дела: требования витрин постоянно корректируются, источники данных мигрируют, а заказчики уже забывают, что именно хотели изначально. При этом приоритеты таких задач постоянно скачут: от «срочно» до «можно подождать», что вызывает ощущение беспорядка и неопределённости.

➖➖➖➖➖➖➖➖➖
А еще, я опросила своих коллег и узнала, что их демотивирует в работе.

Из того, что назвали, было:
🤮задачи с непонятной постановкой и методологией;
🤮отсутствие помощи и поддержки от коллег в трудных кейсах;
🤮изматывающий сбор требований, когда стороны не заинтересованы в обсуждениях или намеренно затягивают сроки обратной связи по своей зоне ответственности;
🤮обсуждение одних и тех же проблем по кругу без проблесков их решения;
🤮внезапное появление новых срочных задачах, когда есть текущие процессы, требующие внимания.

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

На самом деле, в таких ситуациях нет единого подходящего решения. Но что можно сделать лично?

Можно попробовать внедрить несколько практик, которые помогают вернуть мотивацию и сохранить концентрацию, например:
🟢четко фиксировать задачи и их цели, чтобы понимать «почему» и «зачем» делается та или иная работа;
🟢просить уточнения и ставить вопросы при непонятной постановке, чтобы не терять время и силы на догадки;
🟢делить большие, неопределенные проекты на более мелкие этапы с конкретными результатами;
🟢искать поддержку и делиться сложностями с коллегами, чтобы совместно находить решения;
🟢устанавливать личные границы в работе с срочными задачами, чтобы не выгорать.

Что снижает мотивацию в вашем случае? И как вы справляетесь с этим?🫶




Способы дедупликации в Spark

Я тут наткнулась на статью с провокационным названием Stop Using dropDuplicates()! и не смогла пройти мимо нее.

Честно говоря, в большинстве случаев я использую либо dropDuplicates(), если мне необходимо удалить дубли по всем или выбранным столбцам в датафрейме, либо groupBy + count()/agg().


1️⃣ В статье утверждается, что стандартная функция dropDuplicates() в PySpark вызывает глобальный шаффл, что ведет к резкому падению производительности на больших объёмах данных. Это может стать серьезной проблемой при дедупликации миллиардных датасетов, т.к. перегруженные партиции создают перекосы данных, приводя к out-of-memory ошибкам и сбоям воркеров.

Чтобы гарантировать удаление всех дубликатов
по всему датафрейму
, dropDuplicates() / distinct() делает полный шаффл данных. Spark должен сгруппировать все потенциально дублирующиеся строки вместе на одной партиции для их сравнения и удаления. Это перемешивание происходит по ключам, определенным столбцами в dropDuplicates(subset=[...]) или по всем столбцам для пустого вызова.


2️⃣ Автор рекомендуют вместо dropDuplicates() применять оконные функции, а также предварительно анализировать распределение ключей и разумно репартиционировать данные.

В чем разница оконных функций и dropDuplicates()?

Ключевая идея автора в том, что при использовании оконной функции (row_number() и последующей фильтрации по rn=1) вы явно контролируете, какой дубликат (например, с наибольшей/наименьшей датой или ID) будет сохранен. Кроме того, перемешивание происходит только по столбцам, указанным в partitionBy. Если данные уже были разделены (например, с помощью repartition()) по тем же ключам, шаффл может быть пропущен.

3️⃣ В случае перекоса данных - автор предлагает применить salting ключей, т.е. добавления небольшого случайного префикса или суффикса к ключу перед агрегацией/дедупликацией, а затем на втором этапе десолтировать (удалить соль) и агрегировать уже по исходному ключу.

Что в итоге?

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

🤩 на небольшом объеме данных можно не парится и применять dropDuplicates().

🤩 если нужно очистить все строки по всем полям на большом объеме, то оконная функция - сомнительное решение, она также приведет к глобальному шаффлу. Действительно полезно в случае, когда нам нужен контроль над строками.

🤩 оконная функция по ключевым полям и dropDuplicates()с указанными полями будут работать практически идентично.

🤩 если соль распределена неверно, то салтинг данных может привести к формированию некорректных ключей, и как следствие, к некорректной дедупликации. Требует аккуратности.

🤩 groupBy() + agg(first()): для случаев, когда нужно взять первое значение из группы по определенному столбцу будет эффективнее оконной функции.

А как вы удаляете дубликаты?

©️что-то на инженерном

953 0 23 8 32
Показано 20 последних публикаций.