tgindex
DataДжунгли🌳

DataДжунгли🌳

Статистика

Data Engineering на пальцах: 🥸Про Airflow-DAG. 🍀ETL, с сарказмом. 🅾️оптимизируем SQL. 🐍Python трюки.

Последний пост
7 авг.
Последнее чтение
22:54
Постов за неделю
0
Всего постов
25
Тип
открытый
Язык
русский
Категория
Технологии
В каталоге с
13 авг.
Подписчики
283
−1 за 4 дн.
Сутки
−1
−0,35%
Неделя
 
Месяц
 
Просмотров на пост
236
25 постов
Вовлечённость
83,4%
к подписчикам
Постов в день
0,0
всего 25
Упоминаний
1
каналов
Охват размещения
оценка
1/24сутки в ленте
126
1/48двое суток
144
1/72трое суток
155

Оценка по просмотрам недавних постов: пост набирает почти всё за первые сутки.

Посты

  • 🔍 Как читать план запроса: EXPLAIN, оценки и почему оптимизатор врёт #SQLWednesday В посте про последнюю транзакцию я обещал показать план выполнения. Обещание закрываю, только это будет не разбор одной задачи, а навык, который окупается каждый день. Смысл EXPLAIN в одной фразе: ты перестаёшь гадать, почему запрос медленный, и просто спрашиваешь у движка, что он собирается делать. Ответ бывает обидным. Первое, что надо запомнить - EXPLAIN и EXPLAIN ANALYZE это разные вещи. Первый показывает намерение: план и предположения оптимизатора, запрос не выполняется. Второй реально выполняет и показывает, что получилось на самом деле. Почти вся диагностика живёт в разнице между этими двумя картинками. Второе - план читается снизу вверх. Внизу источники данных, наверху результат. И третье: смотреть надо всего на три вещи. Где время. Не на весь запрос, а по операторам. Обычно 90% висит на одном, и это не то место, куда ты смотрел. Вот мой прогон на 5 млн транзакций, дедуп через ROW_NUMBER: WINDOW 5,000,000 rows 3.51s TABLE_SCAN 5,000,000 rows 0.06s Чтение таблицы — шесть сотых секунды. Всё остальное время съела сортировка внутри оконной функции. Оптимизировать тут «чтение с диска» бессмысленно, лечится только раскладкой данных по ключу партиционирования. Сколько строк реально прошло через каждый оператор. Если между двумя соседними шагами строк стало в сто раз больше - у тебя размножение на join. Если фильтр стоит наверху и отсекает 99% строк, значит вся эта гора тащилась через весь план впустую, и его надо опустить вниз. Оценка против факта. Вот это самое интересное. Оптимизатор не знает данные, он знает статистику: сколько строк в таблице, сколько уникальных значений в колонке, как они распределены по гистограмме. Из этого он гадает, сколько строк вернёт каждый шаг, и по этой догадке выбирает алгоритм. Проверил на своей таблице. Условие status = 'refund', оптимизатор говорит: SEQ_SCAN Filters: status='refund' ~2,500,000 rows Два с половиной миллиона. По факту — 5029 строк. Ошибка в пятьсот раз, просто потому что статистики по этой колонке нет и движок честно поделил таблицу пополам. Само по себе это не страшно, страшны последствия. На оценке в пять тысяч строк движок возьмёт nested loop и индекс. На оценке в два с половиной миллиона — построит хеш-таблицу и пойдёт сканом. Ошибся на входе - выбрал не тот алгоритм для всего дерева выше. Так запрос, который вчера отрабатывал за две секунды, сегодня висит сорок минут, хотя ты в нём ничего не менял. - ! Менялись данные. Отсюда типовые причины, почему оценки врут: статистика устарела после массовой заливки; в WHERE несколько условий, которые движок считает независимыми, а они жёстко связаны (город и почтовый индекс); на колонку навешана функция — DATE(created_at) = ... прячет от оптимизатора и статистику, и индекс; данные перекошены, и «средний» клиент существует только на бумаге. Что с этим делать? : обновить статистику (ANALYZE в Postgres, UPDATE STATISTICS в SQL Server), убрать функции с колонок в предикатах, разбить монстра на шаги с материализацией, а не надеяться, что оптимизатор разрулит семиэтажный CTE. И архитектурный угол. В лейкхаусе всё то же самое, только статистика живёт в метаданных: min/max по row groups в Parquet, счётчики в манифестах Iceberg. Поэтому Spark умеет adaptive query execution - начинает выполнять, видит реальные объёмы и на лету перестраивает план, потому что доверять оценкам на распределёнке уже никто не хочет. Открываете план, когда что-то тормозит, или сразу лезете вешать индексы наугад? И какая была самая дикая разница между оценкой и фактом? 👇 #SQL #SQLWednesday #performance #ETL #DataJungle

  • 🧱 Parquet под капотом: почему «размер файла имеет значение» Мы шли сверху вниз: разделили compute и storage → положили сверху Iceberg → разобрали каталоги. Остался последний этаж — сами файлы. Под каждым Iceberg лежит обычный Parquet, и именно здесь производительность или выигрывается, или проваливается. 🦖 Как было: строчный формат CSV, Avro, любая OLTP-таблица хранят данные построчно. Чтобы посчитать SUM по одной колонке, движок физически читает строки целиком — все 200 полей. Для аналитики это катастрофа I/O: платишь за 200 колонок, используешь 3. 🟢 Как стало: колонки вместо строк • Читаешь только нужные колонки (projection pushdown) — остальные с диска не поднимаются вообще. • В колонке лежат однородные данные → сжатие в разы. Пять повторяющихся статусов в строке не сожмёшь, а в колонке они схлопываются в словарь. 🔬 Как устроен файл файл → row groups (горизонтальные срезы, обычно ~128 МБ) → column chunks → pages. В футере — схема и статистика по каждому chunk: min, max, число NULL. И вот главное. Запрос WHERE created_at = ‘2026-07-20’: движок читает футер, видит по min/max, что в этом row group нужных дат нет, и пропускает его целиком — не читая ни байта данных. Это predicate pushdown. 🛠 Что это значит для DE 🔴 Мелкие файлы убивают. Миллион файлов по 10 КБ = миллион HTTP-запросов к S3, и платишь ты не за объём, а за latency каждого (привет посту про compute vs storage). Компакть в 128–512 МБ. 🔴 Несортированные данные = бесполезная статистика. Если ключ фильтрации размазан по всем row groups, min/max в каждом — «от января до декабря», и пропустить нельзя ничего. Сортируй перед записью по колонке, по которой реально фильтруешь: диапазоны станут узкими, и pruning начнёт резать. 🟡 Следи за размером row group и шириной схемы: слишком широкие и глубоко вложенные схемы бьют по памяти на запись, слишком мелкие row groups раздувают метаданные. 🟡 Партиционируй с умом (hidden partitioning в Iceberg), но не плоди тысячи мелких партиций — вернёшься ровно к проблеме №1. 🔮 А Parquet не устарел? Формату больше десяти лет, и под ИИ-нагрузки он тесноват — точечный доступ к одной записи, фичестор на тысячи колонок, эмбеддинги. На этом выросли Lance (быстрый random access и вектора), Vortex (инкубируется в Linux Foundation) и Nimble от Meta. Но в 2026-ом: Parquet под управлением Iceberg остаётся источником правды для BI и всей классической аналитики, а новые форматы живут рядом, под ML. Переезжать всем пока не за чем. 🔗 Замыкаем цепочку сеть (compute/storage) + колоночный формат (Parquet) + указатель каталога (Iceberg + REST) = весь Cloud-Native стек, который мы собирали четыре поста подряд. Дальше — вверх, к движкам и стримингу. Как боретесь с мелкими файлами — автокомпакция каталога (S3 Tables/Iceberg), OPTIMIZE, свой джоб? И сортируете ли данные перед записью? 👇 #data_engineering #parquet #lakehouse #storage #dj_architecture #DataJungle

  • 🏝️ Gaps & Islands: как из потока событий собрать сессии одним оконным трюком #SQLWednesday Задача, которая отделяет «пишу SQL» от «думаю окнами». Есть поток событий пользователя — надо склеить их в сессии (островки активности) и найти паузы между ними. На собесе спрашивают, а в проде это сессионизация, стрики и SLA-даунтаймы. ❓ Задача Таблица событий. Сессия = подряд идущие события одного юзера, где пауза между соседними ≤ 30 минут. Нужно вернуть: id сессии, начало, конец, число событий, длительность. 📋 Таблица и данные CREATE TABLE events ( user_id BIGINT, event_time TIMESTAMP ); user_id | event_time | комментарий 1 | 2026-07-20 10:00 | сессия 1 1 | 2026-07-20 10:10 | сессия 1 (гэп 10 мин) 1 | 2026-07-20 10:25 | сессия 1 (гэп 15 мин) 1 | 2026-07-20 12:00 | пауза 95 мин -> сессия 2 1 | 2026-07-20 12:20 | сессия 2 2 | 2026-07-20 09:00 | Bob, сессия 1 2 | 2026-07-20 09:45 | пауза 45 мин -> сессия 2 🔴 Как НЕ надо Self-join «каждое событие с каждым», чтобы найти соседнее → O(n²). На миллионах событий встаёт колом. 🟢 Трюк Gaps & Islands — 3 шага LAG(event_time) — берём время предыдущего события того же юзера. Флаг новой сессии: если предыдущего нет ИЛИ разрыв > 30 мин → 1, иначе 0. Кумулятивная SUM(флаг) OVER (... ORDER BY time) — это и есть номер сессии (island id). Дальше обычный GROUP BY. WITH ordered AS ( SELECT user_id, event_time, LAG(event_time) OVER (PARTITION BY user_id ORDER BY event_time) AS prev_time FROM events ), flagged AS ( SELECT user_id, event_time, CASE WHEN prev_time IS NULL OR event_time - prev_time > INTERVAL '30 minutes' THEN 1 ELSE 0 END AS is_new FROM ordered ), sessions AS ( SELECT user_id, event_time, SUM(is_new) OVER (PARTITION BY user_id ORDER BY event_time) AS session_id FROM flagged ) SELECT user_id, session_id, MIN(event_time) AS session_start, MAX(event_time) AS session_end, COUNT(*) AS events, date_diff('minute', MIN(event_time), MAX(event_time)) AS duration_min FROM sessions GROUP BY user_id, session_id ORDER BY user_id, session_id; ⚙️ Через призму эффективности Один проход + оконные функции вместо self-join. По сути один sort по (user_id, event_time). В lakehouse: если данные уже отсортированы/партиционированы по этому ключу — движок пропускает сортировку. Приём один-в-один масштабируется в Spark/BigQuery. В стриминге это вообще встроено: session window в Spark/Flink делает ровно это на лету. 🧩 Тот же приём — другие задачи Найти паузы (gaps): те же prev_time, но фильтр «разрыв > 30 мин» → окна простоя. Стрики «N дней подряд заходил»: island по разнице дат. Аптайм/даунтайм сервиса по логам. 🔴 Ловушки RANGE vs ROWS во фрейме: по умолчанию окно работает как RANGE. Для нумерации сессий это ок, но при равных event_time знай разницу. Тай-брейк: два события в одну секунду → добавь второй ключ в ORDER BY (event_id), иначе порядок недетерминирован. Часовые пояса: «30 минут» на timestamptz считай аккуратно. Диалекты: Postgres/DuckDB — event_time - prev_time > interval '30 minutes'; в других — date_diff / timestampdiff по минутам. Проверил на DuckDB: 7 событий двух юзеров → 4 сессии. У Alice(имя выдуманное 🙂 ) первая — 3 события / 25 мин, вторая — 2 / 20 мин. Паузы 95 и 45 мин ловятся тем же LAG. Как режете сессии на проде — этим трюком, session_window в Spark/Flink, или отдаёте стриминг-движку? И какой порог берёте — 30 мин, 15? 👇 #SQL #SQLWednesday #window_functions #sessionization #DataJungle

  • 🧠➡️🗃️ Text-to-SQL и семантический слой: почему LLM не убил аналитика(хотя и сильно повлияло на рынок) Мой тезис из поста про будущее: «1 Data-спец + LLM закроет весь цикл». Пора его уточнить. Есть место, где LLM красиво падает лицом в стол - когда его пускают напрямую к сырой схеме БД. Разбираемся, почему Text-to-SQL работает на демке и разваливается на проде. 🎬 Обещание вендоров Пишешь «покажи выручку по регионам за Q2» → LLM генерит SQL → готово. На трёх таблицах магия работает, все в восторге. 🔴 Почему ломается на реальном DWH (это уже прозвали accuracy cliff) LLM угадывает смысл данных по косвенным признакам — именам таблиц и колонок. Он не знает, что такое «активный клиент», какой из пяти столбцов revenue настоящий и где soft-delete. И вы можете хоть усраться с инструкцией - промптом это все не работает поверьте мне я пытался такую систему создать почти год. 200 таблиц не влезут в контекст. Чтобы демо взлетело, схему грузят в промпт целиком - на большой базе так нельзя. Самое опасное: ошибка Text-to-SQL выглядит не как ошибка, а как правдоподобный НЕправильный ответ. Запрос отработал, цифра красивая - а join не тот. Глазами это не ловится. Порядок величин: в бенчмарке dbt (2026) на «сложных» вопросах голый Text-to-SQL давал ~50–65% верных ответов. Почти половина - тихий брак. Это проверено лично мной это с воздуха цифры. 🟢 Что реально помогает - семантический слой dbt Semantic Layer, Cube, LookML. Метрики, сущности и связи описаны один раз и версионируются в git. Дальше LLM обращается не к 200 сырым таблицам, а к метрике revenue - а слой сам разворачивает корректный SQL(из RAG памяти) с нужными джойнами и фильтрами. Ключевая разница - слой детерминирован. Вопрос вне его области → ты получаешь ошибку, а не выдуманную цифру. В том же бенчмарке на вопросах «в области» слоя точность - 66-78%. Это уже сильно лучше но построение этой архитектуры это большой сложный пайплайн + надо его поддерживать решать проблемы улучшать ответы, делать тюн модели, авто расширять RAG память. И вот эти все приседания стоят месяцев работы, кучи сгоревших токенов, времени специалиста а эффект как бы по прежнему не 100%. При этом человек пописавший SELECTы придет с какой то доказательной базой к стейкхолдерам и докажет свою точку зрения на данных, LLM вы либо верите либо нет 😁 потому что ее доказательства они могут быть какими угодно - дабы угодить пользователю - то есть вам. Таков принцип этих моделей давать вам средневзвешанное наиболее подходящее. 🧰 Плюс паттерны grounding (пока слоя нет) RAG по описаниям таблиц и бизнес-глоссарию; few-shot из проверенных, «золотых» запросов; LLM работает не по всей схеме, а по ограниченному набору вью; «LLM предлагает - тесты/человек подтверждают», а не «LLM сразу в прод». 🛠 Мой взгляд - что это значит для DE и других DATA специалистов. LLM сдвигает работу не в «писать меньше SQL», а в «строить семантику и контракты, на которые модель может опереться». Инженер данных = тот, кто делает данные понятными для машины: чистые метрики, документированные сущности, governance. Это и есть та самая «база» из прошлых постов - архитектура важнее синтаксиса. И это лищь продолжение поста(мысли) про каталоги: семантический слой + governance — ровно то, без чего нельзя пускать к данным ни аналитика-LLM, ни автономного агента. Есть еще один слой в этом посте который я давно уже заметил. Ваш ИИ помошник - настолько же умен, насколько умны вы 😎. Я расскрою тему вам в следующем посте по теме ИИшных дел. Пробовали Text-to-SQL на проде? Взлетело или тихо врало? И строите ли семантический слой — dbt, Cube, LookML, своё? 👇 #data_engineering #AI #LLM #text2sql #semantic_layer #DataJungle

  • Решил поделиться некоторыми мыслями касательно KPI, которыми так любят обкладывать нашу работу. В моей компании лишь недавно руководству стало интересно «А че тут ваще все делают» и дабы объяснить в каких то цифровых выражениях работу творческих личностей типо меня, тут же откуда то берутся метрики которые ваш покорный слуга всегда считал началом конца 😁 Да наверное среди менеджеров были допущены некоторые ошибки, которые привели к потерям и в маркетинге возможно тоже, но кто мне скажет об этом явно:) Я лишь могу поселектить чуть 🤏 и сделать некоторые выводы. Как правило «нам нужны метрики для оценки вашей работы» сопровождается еще и увольнениями старой гвардии в компании. Типо они давно работали и возможно их пыл угас и им не так хочется зарабатывать деньги для компании а хочется просто жить, растить детей и путешествовать. Они же настроили все процессы и создали команды. Но для компании как правило такой сотрудник уже не особо нужен. Я это заметил уже не в одной компании и процесс всегда одинаковый когда начинается перестройка. Я понимаю что бизнесу надо все померить и посчитать. И надо это лишь в одном случае - убедится что ты не мало работаешь и перформишь 😁 На мой вопрос а что если я согласно вашей же метрики сильно лучше делаю, вам надо 80% а я делаю 110%. Отразится это на мне положительно ? Может выходной дополнительный, может вымпел, медаль, грамота? Про деньги говорить вообще бессмысленно 😁 Ответ компании - нет. А вот если я буду давать не 110% стабильных процентов а только 80% требуемых:) то это быстро заметят. Щас самые умные из вас скажут а зачем делать больше если никто не просит :) А я жил и не знал что я столько делаю метрик то не было. А теперь есть. И вот как быть ? Я не чувствую выгорания мне в целом нормально перформить больше тем более сейчас с ИИ это можно делать. Но теперь я знаю что делаю больше чем надо 😁 Снизить перфоманс? Или оставаться собой и будь что будет? Как бы вы поступили ? Все персонажи вымышлены. #мысливслух

  • 🗂️ Война каталогов Iceberg: Glue vs Polaris vs Unity vs Nessie - кто держит указатель #dj_architecture В посте про Iceberg я закончил вопросом «на каком каталоге сидите?». Ответов от вас не последовало ибо вы походу и не знает что это 🙂 🦖 Быстрое напоминание Iceberg - это метаданные поверх обычных Parquet. Любая запись = новый metadata.json + атомарная подмена указателя на текущий. Так вот тот, кто хранит этот указатель и атомарно его переключает, и есть каталог. Без него нет ни ACID, ни single source of truth: два движка просто затрут друг друга. 🔑 Почему всё крутится вокруг REST-каталога Ключевая вещь последних лет - Iceberg REST Catalog spec. Это один HTTP-контракт: движок пишет REST-клиент один раз, каталог — REST-сервер один раз, и дальше все со всеми. Раньше под каждую пару движок ↔ каталог пилили отдельную интеграцию. Теперь нет. Вот это и есть настоящая смерть vendor lock-in - но уже на уровне метаданных, а не файлов. ⚔️ Игроки (честно, с их дырами) 🟢 Apache Polaris - дорос до top-level проекта Apache, де-факто community-стандарт REST-каталога. Open source + managed-вариант от Snowflake. 🟡 AWS Glue - родной для экосистемы AWS, но REST-полнота хромает (нет UpdateTable, пробелы в v3). 🟢 AWS S3 Tables - Iceberg как first-class ресурс прямо в S3: v3, автокомпакция, высокий tx-rate. По сути managed-каталог из коробки для тех, кто на AWS. 🟡 Unity Catalog (OSS) — Databricks открыли исходники, но он Delta-first: поддержка Iceberg догоняет, gap до managed-версии реальный. Логичен, если живёшь в Databricks. 🔵 Nessie - git-like ветки и теги для метаданных: эксперимент на ветке, мерж как в git. Data CI/CD. Минус - нужен внешний слой авторизации. 🔵 Lakekeeper - лёгкий бинарь на Rust, сильная авторизация (OpenFGA/OPA), k8s-native. Молодой, но интересный, если своё облако и жёсткий RBAC. ⚫ Hive Metastore - легаси на Thrift. Не выбор для нового, а то, с чего мигрируют в день, когда переросли. Честно я сам половину слов не знаю накопал с помощью ИИ в интернетах для обощревания просто. 🛠 Что это значит для DE Каталог - это не служебная деталь, а точка governance: кто видит таблицу, кто пишет, как разрулить конкурентную запись. Выбор по-простому: весь в AWS без Databricks → S3 Tables / Glue; хочешь вендор-нейтральный стандарт → Polaris; нужен git-workflow на данных → Nessie; своё k8s + жёсткий доступ → Lakekeeper; в Databricks → Unity. Правило простое: данные держи в открытом формате (Iceberg на S3), а каталог выбирай так, чтобы в любой момент переключить движок и не переезжать. Lock-in теперь прячется именно в каталоге, а не в формате. 🔮 Мостик к будущему что мне действительно нравится. Формат-война кончилась, и весь фронт сместился на каталог. Именно он теперь решает не только кто читает таблицу, но и governance для волны ИИ-агентов, которые сами ходят в данные. Каталог = кто и что имеет право сделать с данными: хоть человек, хоть агент. На каком каталоге сидите в проде — и почему ушли (или наоборот не уходите) с Glue/Hive? 👇 #data_engineering #iceberg #lakehouse #catalog #DataJungle

  • Прям статья какя то а не номер сессии :)

  • 🗄️ Последняя транзакция каждого клиента: одна задача с собеса - три решения #SQLWednesday - и не важно что сегодня не среда. Продолжаю разбор задач с собеседований, под новым углом. Сегодня классика, на которой сыпется половина кандидатов. ❓ Задача Есть таблица транзакций. Нужно вернуть последнюю по времени транзакцию каждого клиента(успешную или нет не важно) - целиком, со всеми полями, а не только max(время). top-1 на группу (greatest-N-per-group). табличка транзакций CREATE TABLE Transactions ( id BIGINT PRIMARY KEY, -- id транзакции customer_id BIGINT, -- клиент amount DECIMAL(12,2), -- сумма status VARCHAR, -- статус created_at TIMESTAMP -- время транзакции ); Пример данных id | customer | amount | status | created_at 101 | 1 | 50.00 | done | 2026-07-01 10:00 102 | 1 | 75.00 | done | 2026-07-10 14:30 201 | 2 | 20.00 | done | 2026-07-05 09:00 202 | 2 | 30.00 | failed | 2026-07-15 09:00 203 | 2 | 30.00 | done | 2026-07-15 09:00 301 | 3 | 100.00 | done | 2026-07-12 12:00 Итак какие у нас варианты решений - да их больше чем 1: 1) Коррелированный подзапрос. Просто в лоб и напрашивается само. SELECT t.* FROM Transactions t WHERE t.created_at = ( SELECT MAX(t2.created_at) FROM Transactions t2 WHERE t2.customer_id = t.customer_id ); Да работает, но: • На каждую строку лезет в подзапрос за MAX (оптимизатор иногда перепишет, иногда нет); • БАГ: у customer_id=2 две транзакции с равным created_at → вернутся ОБЕ. Должно быть 3 строки а будет 4. Придется думать над доп фильтром; 2) Оконная функция ROW_NUMBER() - конечно же. WITH ranked AS ( SELECT t.*, ROW_NUMBER() OVER ( PARTITION BY customer_id ORDER BY created_at DESC, id DESC ) AS rn FROM Transactions t ) SELECT * FROM ranked WHERE rn = 1; Почему это стандарт для подобных задач?: • один проход по таблице; • тай-брейк вторым ключом (id DESC) - детерминированно одна строка, бага с дублями нет; ⚡️ ROW_NUMBER, а не RANK — RANK при равных значениях вернёт дубли. Но есть ли еще что то ? Еще более быстро и красиво? Знакомьтес с мистером LATERALL и CROSS APPLY 3) LATERAL (Postgres) / CROSS APPLY (Sql Server) — Postgres SELECT t.* FROM Customers c CROSS JOIN LATERAL ( SELECT * FROM Transactions t WHERE t.customer_id = c.customer_id ORDER BY t.created_at DESC, t.id DESC LIMIT 1 ) t; — Sql Server SELECT t.* FROM Customers c CROSS APPLY ( SELECT TOP 1 * FROM Transactions t WHERE t.customer_id = c.customer_id ORDER BY t.created_at DESC, t.id DESC ) t; 🟢Фишка этого решения - при индексе (customer_id, created_at DESC) движок делает index seek на каждую группу - это почти O(числа клиентов), а не сортировка всей таблицы. CREATE INDEX ix_txn ON Transactions (customer_id, created_at DESC, id DESC); маловероятно что в продакшн таблица транзакций не будет иметь индексов. ⚖️ Что выбрать - через призму архитектуры • Классическая OLTP-СУБД с индексом (Postgres / SQL Server) → LATERAL / APPLY обычно быстрее: seek по индексу вместо полной сортировки. • Колоночный движок / lakehouse (BigQuery, Snowflake, Spark, Iceberg + Trino) → индексов нет, зато дёшево сканировать и сортировать колонки → выигрывает ROW_NUMBER, и он отлично параллелится. • Коррелированный подзапрос → ответ уровня а как ещё можно - не более :). 🔴 Ловушки, на которых валят • RANK вместо ROW_NUMBER = дубли при равных ключах. • Нет тай-брейка (id) в ORDER BY → результат недетерминирован, на равном времени каждый раз может быть разная строка. • NULL в created_at: при ORDER BY ... DESC в Postgres NULL уезжает наверх (NULLS FIRST) и притворяется «последним». Пиши NULLS LAST. • Нужны и клиенты без транзакций? Подзапрос и оконка их не вернут — бери LEFT JOIN LATERAL. #SQL #window_functions #ETL #DataJungle

  • Вопрос: доверили бы автономному агенту реальный кошелёк - и где ваша граница: только demo, микро-депозит или всерьёз? 👇 #data_engineering #AI #agents #AgentKit #crypto #DataJungle

  • Рубрика #НАВАЙБКОЖЕНО 🤖 Я собрал ИИ-агента, который сам торгует криптой ончейн. Разбираю архитектуру В посте «Я вернулся» обещал показать ИИ-агента для торговли: разбираю для вас архитектуру рабочей системы. Это Telegram-бот, который сам находит сигнал, сам исполняет своп в блокчейне и сам отчитывается. Человек в цикле — только чтобы нажать «Старт». Спойлер - денег не принесло 🙂 но и особо не потеряло 🙂 🦖 Как выглядит «бот-трейдер» обычно API-ключи к бирже + скрипт по расписанию, который дёргает REST и ставит ордера. Деньги лежат на бирже (кастодиально), ключи — вечная головная боль по безопасности. И «агентность» тут фейковая: это cron, а не агент. Я хотел иначе: агент с собственным некастодиальным кошельком, который действует ончейн сам. А LLM - это слой оркестрации и объяснения решений, а не оракул, который «чувствует» цену. LLM выполняла контракт но не принимала решение как таковое, данные были реальные с биржи. 🧩 Архитектура по слоям Bot Layer — aiogram 3.x (async). Команды /start, /wallet, /portfolio, /strategy, /mode, /pause. Витрина — Telegram Mini App на React 18 + Vite + Tailwind. Agent Layer — здесь ИИ. Coinbase AgentKit + LangChain + Claude. Cигналы считаются детерминированно, а не нейронка угадывает рынок. Signal analyzer: RSI(14), volume ratio (объём против 7-дневного среднего), тренд по кроссу EMA20/EMA50. Данные — CoinGecko/DexScreener. Правило buy-сигнала: RSI < 35 И volume_ratio > 1.3 И trend = bullish → сделка с confidence-скором. Trade executor: агенту прилетает промпт «Swap {amount} {token_in} for {token_out}», и AgentKit исполняет своп на Uniswap/Aerodrome. Роль LLM/агента — превратить решение стратегии в безопасное ончейн-действие. Не предсказание, а исполнение. On-chain — Coinbase CDP, сеть base-mainnet. На каждого юзера создаётся некастодиальный CDP-кошелёк: приватный ключ живёт в TEE у Coinbase, у меня его нет. Это убивает самый страшный риск бота — «слили ключи, увели депозит». Своп реально уходит в блокчейн и возвращает tx_hash — всё аудируемо. Data Layer — PostgreSQL + SQLAlchemy (async) + Alembic. Таблицы: users, wallets (cdp_wallet_id + Base-адрес), trades (сигнал + P&L + tx_hash), portfolio_snapshots, notifications_log. Scheduler — APScheduler. Тут вся «жизнь» агента: market monitor — каждые 5 мин анализ сигналов; risk manager — каждую минуту проверяет стопы; snapshot портфеля — раз в час; daily report — в 9:00. 🛡️ Риск-менеджмент (без него это казино) Demo-режим по умолчанию: виртуальная $1000 USDC. Боевой режим включаешь сам. Три профиля — conservative / balanced / aggressive: размер позиции 5–20%, стоп −5…−12%, тейк +8…+25%, вайтлист токенов. Глобальные предохранители: дневной убыток больше 15% → агент встаёт; макс. слиппедж 2%; не торгует при балансе меньше $10. Demo и боевые сделки жёстко разделены в БД и в UI (бейдж WHAT IF) — чтобы не путать «как бы» и реальные деньги. 🛠 Что это значит для DE «ИИ-агент» на проде - это на 80% обычная дата-инженерия: сбор данных, фиче-инжиниринг (RSI/EMA/объёмы), стейт в Postgres, шедулер, идемпотентность, аудит. LLM - тонкий слой сверху, а не вся система. Самое сложное не модель сама все делает, а надёжность: упал провайдер данных на середине цикла; как не исполнить своп дважды; как держать портфель консистентным. Те же вопросы, что и в любом ETL - только цена бага живые деньги. AgentKit и x402 из прошлого поста тут перестают быть теорией: кошелёк агента + автономное ончейн-действие = «машина торгует сама». И это буквально тезис «1 Data-спец + LLM»: один человек закрыл данные, стратегию, бэкенд, исполнение и фронт. ⚖️ Осторожно Это не печатная машинка денег. Бэктест ≠ будущее, рынок меняет режим — любой ТА однажды ломается. Поэтому demo-first, лимиты и circuit breaker — не украшение, а условие запуска. Автономный агент с реальным кошельком = новый класс рисков: баг исполняется реальным свопом. Ничего из этого не инвест-совет. РЕПОЗИТОРИЙ ОТКРЫТ Только для моих верных подписчиков - там более чем все подробно отдавайте вашему ИИ и может у вас выйдет то что не вышло у меня 🙂 делитесь в коментах что думаете.

  • без подписи

  • ⚡️ Apache Iceberg: ACID на S3 и конец vendor lock-in 🧊 В прошлом посте разобрал, почему compute уехал отдельно от storage. Остался висящий хвост: данные в S3, а как там делать UPDATE, откатить кривой DAG, поменять схему без перезаливки 50 ТБ? В DWH за это отвечала база. В озере на S3 — никто. До Iceberg. 🦖 Жизнь до этого: Hive Hive table format = «папка в S3 это таблица, подпапки — партиции». Метаданные — только список путей. Что не так:🔴 • Никакого ACID. Пайплайн упал на середине — читатели видят битые данные. • Schema evolution через боль. Поменять тип колонки = переписать таблицу. • Партиции зашиты в путь. Сменить схему партиционирования — перезаливай всё. • Planner делает LIST по S3. На миллионе партиций умирает. • UPDATE/DELETE нет. GDPR-удаление = читай партицию, фильтруй, перезаписывай. ☁️ Что сделал Iceberg Не движок и не сторадж. Спецификация метаданных поверх обычных Parquet. Слои: - Каталог (Glue/Polaris/Unity/Nessie) — указатель на текущий metadata.json. - metadata.json — схема, партиции, список снапшотов. - Manifest list → manifest → data files (Parquet) со статистикой по колонкам. Любая запись = новый metadata.json + атомарная замена указателя в каталоге. Старые файлы остаются. На этом построено всё: 🟢 ACID. Запись видна целиком или не видна. Конкуренция разруливается через optimistic concurrency на уровне каталога. 🟢 Time travel. SELECT ... FOR VERSION AS OF 12345. Откат на снапшот без бэкапов. 🟢 Schema evolution. Колонки идентифицируются по ID, переименование/смена типа — правка metadata.json, данные не трогаются. 🟢 Hidden partitioning. Партиции в метаданных. Меняешь day → hour через ALTER, старые данные не переписываешь. 🟢 Pruning без LIST. Planner читает manifest и сразу знает нужные файлы. 🟢 Branching/tagging как в git. 🛠 Что это значит для DE Vendor lock-in мёртв. Один датасет в S3 одновременно читают Snowflake, Databricks, Trino, DuckDB, BigQuery — и видят одно и то же. Война форматов закончилась. Snowflake купил Tabular, Databricks open-source-нул Unity Catalog, AWS выкатил S3 Tables (это Iceberg под капотом). Индустрия сошлась на одном формате впервые за 15 лет. ⚖️ Где не подходит • OLTP не закроет: каждая запись = коммит в каталог, это десятки миллисекунд. Частые мелкие апдейты убьют через write amplification. Iceberg для аналитики, истории, ML-фич, маркетов — то есть 80% работы DE. Используете в проде? На каком каталоге — Glue, Unity, Polaris, своё? Делитесь в комментах 👇 #data_engineering #iceberg #lakehouse #s3 #dj_architecture #DataJungle

  • Вот книга про которую пишу, всем инженерам рекомендую к прочтению 1️⃣0️⃣0️⃣🔤

  • Инженерия данных - это всё ещё про написание кода или уже про настройку - а вот чего настраивать ? 😎 Недавно зарылся в финальную, 11-ю главу «Основ инженерии данных» Риса и Хоусли. Авторы пытаются заглянуть за горизонт и понять, куда мы все катимся. Что там пишут? 🤯 - издание от 2022 года если что 🤖 • Инструменты упрощаются. • Эпоха, когда нужно было быть «хакером инфраструктуры», чтобы просто заставить данные течь из пункта А в пункт Б, уходит. • Наступает эра Cloud Data OS, где всё работает из коробки, а фокус смещается с как это поднять на как это приносит пользу бизнесу(FinOps). • Стриминг вытеснет батчи. + Ещё пророчат окончательное слияние инженерии данных с разработкой приложений и ML. То есть инженер данных будущего - это не просто парень - ETL-пайплайн, а полноценный архитектор систем, где данные и логика неразрывны. В целом то мы никогда не были "Просто парнем с ETL" 👀 В чем авторы оказались чертовски правы? 🔤 Прошло почти 4 года с момента выхода оригинала, и вот что из предсказаний стало нашей реальностью: • Инструменты для людей (User-Friendly Infrastructure): Посмотрите на расцвет стека вокруг dbt, Airbyte или managed-решений в облаках. Настройка базового пайплайна теперь занимает часы, а не недели. Порог входа в железо действительно упал. • Инженер данных <-> Backend-разработчик: Грань стирается. Мы всё чаще пишем на Python/Go, используем CI/CD, тесты и программные паттерны. Больше нет просто SQL-парней, есть разработчики платформ данных. • Real-time - это база: Если в 2022-м потоковая обработка была космосом для избранных, то сегодня бизнес требует отчеты здесь и сейчас. Kafka, ClickHouse и стриминг стали стандартом де-факто для живых систем. • Фокус на FinOps: Как и предсказывали авторы, умение не просто поднять кластер, а сделать это эффективно и не раздеть компанию на счетах от AWS/GCP - это теперь чуть ли не ключевой скилл сеньора. Мой взгляд на джунгли данных сегодня: 1. Бизнес-эффект- это фильтр №1. Построить эффективную платформу, где ясно, как данные превращаются в решения - это база. Если нет понимания бизнес-эффекта от датасета, мы такое в работу... ну, в идеале не берем (хотя, признаюсь, грешим иногда, берем 😄). Но стратегически - DE без понимания бизнеса больше не живет. 2. Стриминг перестал быть болью. Технологии типа Pub/Sub - это теперь супер-удобно. Легко поднимается, масштабируется, а мониторинг не превращается в ночной кошмар. 3. Грани между DE и Backend никогда и не было. Для меня всегда было нормой: сам разработал сервис, сам поднял, сам встроил в архитектуру и сам поддерживаешь. Будь то кастомный код или Open Source вроде Airflow и Superset. Ты - инженер в широком смысле, а не просто перекладыватель json-ов. 4. ML - это просто часть пайплайна. Имплементация моделей в логику, настройка входов/выходов и дообучение — это задачи, которые DE-специалисту(мне) приходится решать постоянно. 5. FinOps - бесконечная история. Облака и их стоимость - это отдельное искусство, которое я сам продолжаю осваивать. Это критически важный навык: сделать не просто работающее решение, а экономически оправданное. Какое будущее? 🔮 Мой вывод: скоро формула будет выглядеть так: 1 Data-спец + LLM. Один человек с помощью ИИ сможет закрыть весь цикл: ✅ Глубокий анализ. ✅ Создание ML-модели. ✅ Написание бэкенда. ✅ Деплой и настройка CI/CD. В итоге мы получаем не просто инженера данных, а Full-stack Data Product Engineer - человека, который в одиночку выдает готовый продукт, полностью закрывающий потребность бизнеса. Согласны с таким прогнозом или DE останется узкой нишей? Пишите в комментариях! 👇 #data_engineering #future #books #DataJungle

  • ⚡️Separation of Compute and Storage: Почему все уезжают в облако и как это работает на уровне железа? Прямо сейчас на моей работе мы с командой ДЕ переносим наш SQL Server в BigQuery. Не буквально конечно :). Это не просто смена вендора. Это переход на фундаментально другую архитектуру работы с данными с онпрем сервера(ов) на распределенное облако. Что такого в облаках стоящего? Помимо того что навязывает маркетинг. 🦖 Эпоха HDFS и Data Locality Раньше был Hadoop и классические СУБД (тот же SQL Server на железе). Архитектура строилась на принципе Shared-Nothing и Data Locality: данные лежат на тех же серверах, которые их обрабатывают. Проблема очевидная: Вычисления (CPU/RAM) и диски масштабируются неравномерно. У вас копятся петабайты исторических логов? Будьте добры докупить серверы, переплачивая за мощные процессоры, которые будут просто простаивать. ☁️Эпоха Cloud-Native Современные базы (BigQuery, Snowflake, ClickHouse Cloud) и Data Lakes работают иначе. Данные навсегда отрываются от серверов и уезжают в Объектное хранилище (S3 / GCS). А серверы вычислений (Compute nodes) живут отдельно и поднимаются по клику. Но тут возникает главная архитектурная проблема: Физика объектных хранилищ. S3 — это не файловая система. Это гигантский распределенный Key-Value сторадж, с которым мы общаемся по HTTP-протоколу. И у него есть две важнейшие характеристики, которые дата-инженер обязан понимать: 🔴 Ужасная задержка (Latency): Время до получения первого байта (TTFB) из S3 измеряется десятками миллисекунд. Для базы данных это вечность. Локальный SSD отдал бы данные в тысячи раз быстрее. Если ваша база будет читать из S3 мелкие файлы в один поток — она умрет. Но! 🟢 Бесконечная пропускная способность (Throughput): В отличие от жесткого диска, который упирается в интерфейс SATA/PCIe, S3 позволяет скачивать тысячи файлов одновременно. Вы можете поднять 1000 серверов, и каждый скачает свой кусок на скорости 10 Гбит/с. 🛠 Как современные движки обходят физику? Если S3 такой медленный на старт, почему BigQuery или Snowflake отдают аналитику за секунды? Кэширование на локальных NVMe-дисках. Вычислительные узлы (Compute) в облаке все равно имеют свои сверхбыстрые диски. Но они больше не используются как Primary Storage. Теперь это просто эфемерный кэш. Как выглядит жизненный цикл запроса: Вы пишете SELECT. - Оптимизатор делит запрос на тысячи мелких тасок. - Движок смотрит: есть ли нужные колонки в оперативной памяти? Если нет — лезет на локальный NVMe SSD(горячие данные, которые читали недавно). Если и там нет — идет в S3 (холодные данные), но делает это агрессивно и параллельно, выкачивая гигантские блоки данных большими кусками, чтобы перекрыть высокую сетевую задержку огромным Throughput'ом. Что это значит для дата-инженера? Размер файла имеет значение. Миллион мелких файлов по 10 КБ в S3 убьют любой движок из-за Latency на каждый HTTP-запрос. Локальные джойны в памяти уходят в прошлое. Теперь узкое горлышко вашей архитектуры — это пропускная способность сети внутри дата-центра. Мы уходим от оптимизации индексов на жестких дисках к оптимизации сетевого I/O и колоночных форматов. Добро пожаловать в Cloud-Native! Волшебной таблетки нет. Нужно держать 10,000 транзакций в секунду с миллисекундным откликом (OLTP)? Берем классическую архитектуру с тесной связкой CPU и SSD. Нужно сканировать терабайты данных, отдавать их разным командам и не разориться (OLAP/Data Lake)? Уходим в Cloud-Native форматы с разделением стораджа и вычислений. #data_engineering #cloud_native #bigquery #olap #oltp #dj_architecture

  • Я вернулся, или Почему в Data Jungle была тишина 🌿 Всем привет! С октября от меня не было ни звука. Пришло время объясниться. 🙈 Честно? Я ушел в глубокий «режим выживания» и «стройки». Пока индустрия гудела, а ИИ начал стремительно наступать на пятки и (давайте будем честны) пытаться откусить кусок нашего хлеба, я решил не паниковать, а адаптироваться. Все эти месяцы я с головой ушел в свои проекты. Коих накопилось немало: 1️⃣ Автоматизированная крипто-торговля (фьючерсы). Мы с другом-трейдером объединили опыт, чтобы автоматизировать стратегию высокочастотной торговли (анализ ежеминутный). Немало копий было сломано и денег потеряно, но бэк-тесты за несколько лет показывают более 200% PnL в год (на 1-е плечо!). Сейчас идет тест на реальном небольшом депозите — и пока всё идет по плану(в +). 📈 Это не «прогрев» аудитории и проект не будет публичным, просто хочу показать: если эксперты вкладывают душу, цифры говорят сами за себя. 2️⃣Преподавание. Участвую в благотворительном проекте: обучаю детей SQL. Прививаю им «любовь» к базам данных, чтобы не только мы одни страдали, как говорится! 😈📊 3️⃣ ИИ-агент для торговли. Новый проект по автоматизации через ИИ-агентов. Оказалось, что у Coinbase есть протокол, где ИИ-агент может совершать сделки прямо в блокчейне! Ты просто пишешь ему: «Купи ETH за USDC» — и всё происходит автоматически. Сам не верил, пока не попробовал. Скоро дам вам протестировать! 🤖🔗 а вот это маленький прогрев :) Что я понял за это время? 💡 Писать вам про банальные Python-трюки стало бессмысленно. Есть нейронки, которые делают это за вас и зачастую куда изящнее. 🐍➡️🤖 Я уверен на 100%: сейчас критически важно качать архитектурное мышление и понимание низкоуровневых процессов. Умение писать код — это теперь база, а вот умение проектировать системы, которые работают эффективно и масштабируемо — это скилл будущего тем более в дата отрасли. Читал на Хабре что спрос на Дата профессии сочетающие в себе + DevOPs роль выросли на 300%. Если вы специалист в дата области + умеете в серверной части многое - поверьте вы очень нужны рынку. Поэтому меняю вектор канала: я продолжу делиться тем, что реально важно, и возобновлю разбор задач с собеседований, но уже под другим углом — через призму архитектуры и эффективности. В следующем посте расскажу подробнее про ИИ-агента для торговли. Не переключайтесь! 🚀 #DataJungle #DataScience #AI #MachineLearning #PersonalProjects #BackToWork #SoftwareArchitecture

  • Поиск отличий между таблицами («дифф») #SQLWednesday 👯‍♂️ Две таблицы заходят в бар… и нужно понять, чем они отличаются. Классика собесов - сравнить два снапшота данных. Есть таблицы: • Customers_2024 • Customers_2025 Нужно: 1. Кто появился новым? 2. Кто удалился? 3. У кого изменились данные? -- Новые клиенты (есть в 2025, нет в 2024) SELECT c2025.id, c2025.name FROM Customers_2025 c2025 LEFT JOIN Customers_2024 c2024 ON c2024.id = c2025.id WHERE c2024.id IS NULL; -- Удалённые клиенты (были, но пропали) SELECT c2024.id, c2024.name FROM Customers_2024 c2024 LEFT JOIN Customers_2025 c2025 ON c2025.id = c2024.id WHERE c2025.id IS NULL; -- Изменения в данных (id совпал, поля разные) SELECT c2024.id, c2024.name AS old_name, c2025.name AS new_name FROM Customers_2024 c2024 JOIN Customers_2025 c2025 ON c2025.id = c2024.id WHERE c2024.name <> c2025.name; -- вот тут будет ошибка если NULL поэтому будьте аккуратны Что тут важно: • JOIN-ы вместо минусов - так быстрее и прозрачнее. • Для сравнения всех полей можно собрать CHECKSUM(*) или HASHBYTES (SQL Server) / md5(row::text) (Postgres). • Не забудьте про NULL: <> не работает, используйте IS DISTINCT FROM (Postgres) или EXCEPT для простого диффа. Конечно все это уже давно решается с помощью SCD но это задача с собеседований на DE не редкость. 🔥 Такой «diff» помогает искать не только ошибки загрузки, но и баги в ETL, поэтому это валидно и для анализа и для DE. #SQL #DataDiff #ETL #DataJungle

  • #PyTrickMonday asyncio — Часть 2 Контроль задач: таймауты, отмена, обработка «медленников» 🎪 Когда задач много, «запустить и забыть» уже не работает. Нужен контроль: кто отвалился, кто завис, кого пора отменить. 1) gather vs wait vs as_completed asyncio.gather(*tasks) — простейший «всё сразу». Удобно, когда: • все задачи равнозначны; • результат нужен только когда всё готово. • Плюс: может собирать результаты в порядке аргументов. • Минус: по умолчанию ошибка одной задачи «роняет» всё (см. return_exceptions=True). asyncio.wait(tasks, return_when=...) — когда нужен статус-контроль: • FIRST_COMPLETED — продолжай, как только что-то завершилось; • FIRST_EXCEPTION — реагируй на первую ошибку; • ALL_COMPLETED — эквивалент дождаться всех, но с наборами done/pending. • asyncio.as_completed(tasks) — стримишь результаты по мере готовности: • обрабатываешь «быстрые» ответы, не дожидаясь «черепах». 2) Таймауты: жёсткий дедлайн для всего или для каждой задачи Глобальный таймаут (для группы) import asyncio async def api_call(t): # имитируем сетевой вызов await asyncio.sleep(t) return f"ok in {t}s" async def main(): tasks = [api_call(1), api_call(5), api_call(10)] try: # «Всё должно уложиться за 6 секунд» res = await asyncio.wait_for(asyncio.gather(*tasks), timeout=6) print(res) except asyncio.TimeoutError: print("⏰ Глобальный таймаут: не все успели!") asyncio.run(main()) - Индивидуальные таймауты (каждой задаче свой лимит) import asyncio async def fetch(t): await asyncio.sleep(t) return t async def safe_fetch(t, limit=2): try: return await asyncio.wait_for(fetch(t), timeout=limit) except asyncio.TimeoutError: return f"timeout>{limit}s" async def main(): tasks = [safe_fetch(1), safe_fetch(3), safe_fetch(0.5)] print(await asyncio.gather(*tasks)) asyncio.run(main()) 3) Отмена задач: .cancel() и обработка CancelledError Задачи нужно уметь «выключать культурно», освобождая ресурсы. import asyncio async def worker(): try: while True: await asyncio.sleep(0.5) # работа except asyncio.CancelledError: print("🔌 Получил cancel, закрываю соединения...") # тут — закрытие сессий/курсов/файлов raise # пробрасываем дальше (правильно!) async def main(): task = asyncio.create_task(worker()) await asyncio.sleep(2) # пусть поработает task.cancel() try: await task except asyncio.CancelledError: print("✅ Отмена завершена корректно") asyncio.run(main()) 4) Обрабатывать результаты «как приходят» — as_completed Отлично подходит для «медленного зоопарка» API. import asyncio from random import randint async def call(i): t = randint(1, 5) await asyncio.sleep(t) return f"#{i} in {t}s" async def main(): tasks = [asyncio.create_task(call(i)) for i in range(5)] for fut in asyncio.as_completed(tasks): try: res = await fut print("✅", res) except Exception as e: print("❌ ошибка:", e) asyncio.run(main()) 5) Частые грабли и лайфхаки • gather(..., return_exceptions=True) — не роняй весь батч из-за одной ошибки; обработай её в результате. • Создавай задачи через asyncio.create_task(), если нужно запустить и продолжать работу (иначе просто await подряд). • Не блокируй event loop: никакого time.sleep() и долгих CPU-циклов внутри async — только await I/O или выноси CPU в concurrent.futures/multiprocessing. • Таймауты — не панацея: всегда закрывай ресурсы и корректно обрабатывай отмену. • Смешивать threading и asyncio можно, но осторожно: для блокирующих библиотек используй loop.run_in_executor(...). ⚡ Контролируй асинхронность - и она начнёт экономить тебе время, а не нервы 😏 делитесь этим с друзьями и коллегами ! #Python #Asyncio #DataJungle

  • #SQLWednesday Привет, SQL-джедаи ⚔️ Сегодня разберём задачу SQL интервью, без которой ни один аналитик не уходит со свидания с данными: сравнение с прошлым периодом. Хотим понять: выросли мы или упали, и насколько сильно 📉📈. Есть таблица Sales (дата, сумма продаж). Нужно: 1. Посчитать разницу с предыдущим днём. 2. Узнать % прироста/падения. SELECT sale_date, amount, LAG(amount) OVER (ORDER BY sale_date) AS prev_amount, amount - LAG(amount) OVER (ORDER BY sale_date) AS diff, ROUND( 100.0 * (amount - LAG(amount) OVER (ORDER BY sale_date)) / NULLIF(LAG(amount) OVER (ORDER BY sale_date), 0), 2 ) AS pct_change FROM Sales; Что тут за магия: • LAG() — смотрим «назад» на 1 строку. • diff — простое вычитание. • pct_change — разница в процентах (NULLIF спасает от деления на 0). 🔥 А если нужен рост/падение к прошлому месяцу - то же самое, только PARTITION BY EXTRACT(MONTH FROM sale_date) или DATE_TRUNC('month', sale_date). ⚡ Это хорошая задачка на собесах, потому что проверяет: умеешь ли ты в оконнынные функции или продолжаешь self-joinить 😏 #SQL #WindowFunctions #DataJungle

  • #PyTrickMonday Часть 1. Базовое знакомство с асинхроном. asyncio - когда Python становится акробатом 🤸‍♂️ или как запускать кучу задач одновременно, не дожидаясь поочерёдно Глупый пример но тем не менее 🙂 "тебе надо сходить за хлебом, оплатить коммуналку и приготовить кофе." Синхронно: делаешь всё подряд, ждёшь пока закипит вода ☕, потом топаешь в магазин. Асинхронно: ставишь чайник, пока он греется — бежишь за хлебом, по пути платишь счета. так работает asyncio import asyncio async def task(name, delay): await asyncio.sleep(delay) print(f"{name} done after {delay}s") async def main(): await asyncio.gather( task("💡 Coffee", 3), task("🍞 Bread", 2), task("💸 Bills", 1), ) asyncio.run(main()) Результат: всё завершается параллельно, а не по очереди. • async — объявляешь асинхронную функцию; • await — говоришь “подожди, но пока можно делать другое”; • gather — запускает несколько задач одновременно. 📌 Когда использовать asyncio: • ✅ Когда много I/O-операций: запросы к API, базы данных, парсинг сайтов. • ✅ Когда важна скорость реакции: чат-боты, стриминг, веб-сервисы. • ✅ Когда нужно уместить сотни/тысячи соединений в один процесс. 🚫 Когда НЕ использовать: • ❌ Для тяжёлых вычислений (ML, обработка больших массивов) — тут поможет multiprocessing или Spark. • ❌ Если у тебя простая линейная программа (асинхронщина только усложнит код). • ❌ Когда надо жать CPU на 100% (асинхронный код от этого не ускорится). ⚡ В следующей части покажу, как управлять этими задачами: ставить таймауты, отменять и ловить «медленников». #Python #Asyncio #DataJungle