Все специализации

Дата-инженер: вопросы на собеседовании

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

Логи задач в Apache Airflow можно посмотреть несколькими способами:

Через веб-интерфейс Airflow:

Перейдите в раздел DAGs.

Выберите нужный DAG и кликните на конкретный запуск (Run).

В списке задач (Tasks) выберите нужную задачу.

Нажмите на иконку "Log" или ссылку "View Log" — откроется лог выполнения задачи.

На файловой системе:

По умолчанию логи хранятся в директории, указанной в конфигурации Airflow (airflow.cfg) в параметре base_log_folder.

Обычно это путь вроде /home/airflow/airflow/logs/.

Внутри папки логи структурированы по DAG, дате и задаче.

Через командную строку:

Можно использовать CLI команду:

airflow tasks logs <dag_id> <task_id> <execution_date>

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

Рассмотрим виды JOIN и результат для запроса select * from t1 <join> t2 on t1.t = t2.t на данных:

INNER JOIN

Возвращает строки, где условие соединения выполняется (t1.t = t2.t).

Результат:

null не равен null в SQL, поэтому строки с null не соединятся.

LEFT JOIN (LEFT OUTER JOIN)

Возвращает все строки из t1 и совпадающие из t2, если нет совпадения — NULL в столбцах t2.

Результат:

Строка с NULL из t1 не соединится с NULL из t2, но сама строка из t1 попадёт в результат.

RIGHT JOIN (RIGHT OUTER JOIN)

Все строки из t2 и совпадающие из t1, иначе NULL в t1.

Результат:

FULL JOIN (FULL OUTER JOIN)

Все строки из t1 и t2, совпадающие соединяются, остальные дополняются NULL.

Результат:

CROSS JOIN

Декартово произведение всех строк из t1 и t2, без условия.

Результат (всего 4 строки t1 * 4 строки t2 = 16):

Таким образом, основные виды JOIN и их поведение на данных приведены выше.

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

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

Пример: если в колонке "Страна" много повторяющихся значений "Россия", "США" и т.п., то сжатие будет очень эффективным, а в строчном формате эти значения разбросаны по разным строкам и смешаны с другими данными.

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

Например, если вы создаёте словарь для подсчёта количества вхождений слов в тексте, то память будет расти с увеличением количества уникальных слов. При этом сама структура словаря обычно содержит дополнительные накладные расходы на хранение хэш-значений и управление коллизиями, но в целом оценка O(N) по дополнительной памяти — наиболее практичная.

Функции RANK() и DENSE_RANK() используются для присвоения рангов строкам в отсортированном наборе данных, но они отличаются тем, как обрабатывают одинаковые значения (т.е. как учитывают пропуски в рангах).

RANK() присваивает одинаковый ранг одинаковым значениям, но при этом пропускает следующие ранги. Например, если два элемента занимают 1-е место, следующий получит ранг 3.

DENSE_RANK() также присваивает одинаковый ранг одинаковым значениям, но не пропускает ранги. В том же примере следующий после двух первых с рангом 1 получит ранг 2.

Пример:

В Pandas пропущенные данные обычно представлены значением NaN (Not a Number). Pandas предоставляет несколько способов обработки таких данных:

isna() или isnull() — для обнаружения пропущенных значений.

dropna() — удаление строк или столбцов с пропущенными значениями.

fillna() — заполнение пропущенных значений заданным значением или стратегией (например, средним, медианой).

Пример:

Таким образом, Pandas предоставляет гибкие инструменты для обнаружения и обработки пропущенных данных в таблицах.

Индексы в базах данных — это специальные структуры данных, которые ускоряют поиск и сортировку записей по определённым полям таблицы. Без индексов поиск требует полного перебора всех строк (полный скан), что медленно при больших объёмах данных.

Зачем нужны индексы:

Ускорение операций SELECT с условиями WHERE, JOIN, ORDER BY.

Повышение производительности при выборках.

Внутренняя структура индексов зависит от типа базы и индекса, но наиболее распространённые — это:

B-деревья (B-tree) — сбалансированные деревья, где каждый узел содержит ключи и ссылки на дочерние узлы. Позволяют быстро находить диапазоны значений и отдельные ключи.

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

Bitmap-индексы — эффективны для столбцов с небольшим числом уникальных значений.

Пример структуры B-tree:

Корень, внутренние узлы и листья.

В листьях хранятся ссылки на записи таблицы.

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

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

В Python строка (str) и кортеж (tuple) — это разные типы данных, и цикл по ним отличается по сути содержимого и по некоторым аспектам производительности.

Отличия:

Итерация по строке происходит по символам (каждый элемент — строка длиной 1).

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

Базовые типы:

str и tuple — встроенные неизменяемые типы (immutable).

Строка — последовательность символов Unicode.

Кортеж — последовательность произвольных объектов.

Влияние на производительность:

Итерация по строке обычно быстрее, так как символы хранятся компактно и однородны.

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

Пример:

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

Параллелизм в Apache Airflow проявляется на нескольких уровнях:

Параллельное выполнение задач внутри DAG — Airflow позволяет запускать несколько задач одновременно, если они не зависят друг от друга. Это достигается за счет использования пула воркеров и настройки параметров параллелизма.

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

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

Параллелизм в рамках одной задачи — если задача реализована с поддержкой многопоточности или multiprocessing (например, в PythonOperator), внутри самой задачи может быть реализован параллелизм.

Настройки, влияющие на параллелизм:

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

dag_concurrency — максимальное число одновременно выполняемых задач в одном DAG.

max_active_runs_per_dag — максимальное число одновременных запусков одного DAG.

pool — позволяет ограничивать параллелизм для групп задач.

Пример настройки параллелизма в airflow.cfg:

Таким образом, параллелизм в Airflow позволяет эффективно использовать ресурсы и ускорять выполнение сложных рабочих процессов.

Строчное хранение (row-oriented) сохраняет данные построчно, то есть все поля одной записи вместе, что эффективно для транзакционных систем с частыми операциями вставки и обновления.

Колоночное хранение (column-oriented) сохраняет данные по столбцам, что ускоряет аналитические запросы, которые читают и агрегируют отдельные поля большого объема данных.

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

В моём опыте работы Data Engineer я занимался обработкой и трансформацией больших объёмов данных для аналитики и отчётности. Основные задачи включали:

Разработку и поддержку ETL-процессов

Оптимизацию загрузки данных из различных источников

Автоматизацию пайплайнов данных

Стек технологий, с которым работал:

Язык Python для написания скриптов и обработки данных

Apache Airflow для оркестрации задач

Базы данных: PostgreSQL, ClickHouse

Облачные сервисы для хранения и обработки данных

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

Pandas предоставляет удобные структуры данных — Series и DataFrame — которые значительно упрощают работу с табличными данными по сравнению с обычными списками и словарями Python. Основные преимущества:

Оптимизация по скорости и памяти: Pandas реализован на C и использует NumPy, что позволяет эффективно обрабатывать большие объемы данных.

Удобство работы с данными: есть встроенные методы для фильтрации, группировки, агрегации, объединения и сортировки данных.

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

Индексация и метки: данные индексируются, что облегчает доступ и манипуляции.

Интеграция с другими библиотеками: легко использовать с matplotlib, scikit-learn и др.

Пример:

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

sql

WITH user_counts AS (

SELECT

membership_type,

COUNT(DISTINCT user_id) AS users_count

FROM memberships

GROUP BY membership_type

),

total_users AS (

SELECT COUNT(DISTINCT user_id) AS total FROM memberships

),

visits_count AS (

SELECT

m.membership_type,

COUNT(v.visit_id) AS total_visits

FROM visits v

JOIN memberships m ON v.user_id = m.user_id

GROUP BY m.membership_type

)

SELECT

uc.membership_type,

uc.users_count,

COALESCE(vc.total_visits, 0) AS total_visits,

ROUND((uc.users_count::numeric / t.total) * 100, 1) AS user_share

FROM user_counts uc

JOIN total_users t ON true

LEFT JOIN visits_count vc ON uc.membership_type = vc.membership_type

ORDER BY uc.membership_type;

Impala — это распределённая SQL-движок для анализа больших данных, работающий поверх Hadoop и HDFS. В моей практике с Impala я использовал её для выполнения интерактивных запросов к большим объёмам данных, хранящимся в HDFS и Hive-таблицах.

Основные моменты работы с Impala:

Создавал и оптимизировал SQL-запросы для аналитики.

Использовал Impala для быстрого получения результатов, благодаря её in-memory обработке.

Настраивал соединения с внешними BI-инструментами через JDBC/ODBC.

Следил за производительностью запросов, используя EXPLAIN и профилирование.

Пример простого запроса в Impala:

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

В моём опыте работы с Greenplum я использовал эту MPP-базу данных для хранения и обработки больших объёмов данных, что позволяло эффективно масштабировать аналитические нагрузки.

DBT применял для трансформации данных внутри хранилища, обеспечивая модульность, повторное использование и версионирование SQL-кода.

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

Переход от модели снежинки к Data Vault обусловлен следующими причинами:

Data Vault более адаптивна к изменениям в источниках данных и бизнес-требованиях.

Обеспечивает историчность и трассируемость данных.

Упрощает интеграцию и масштабирование хранилища.

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

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

Сбор метрик и профилирование — измеряем время выполнения запросов, нагрузку на базу, использование индексов.

Анализ плана выполнения — изучаем, как СУБД выполняет запрос (EXPLAIN, EXPLAIN ANALYZE).

Определение узких мест — выявляем операции с высокой стоимостью (сканирование таблиц, сортировки, джойны).

Оптимизация — добавление индексов, переписывание запросов, денормализация данных, кэширование.

Тестирование и мониторинг — проверяем, как изменения влияют на производительность.

Оптимизация — итеративный процесс, который начинается с понимания проблемы и заканчивается проверкой результата.

Чтобы решить задачу за линейное время O(n) с помощью словаря (хэша), нужно:

Подсчитать количество каждого символа в исходной строке и сохранить в словаре, где ключ — символ, значение — количество.

Итерироваться по строке order, для каждого символа брать из словаря его количество и добавлять этот символ столько раз в результат.

Обработать символы, которых нет в order — пройтись по словарю и добавить оставшиеся символы в любом порядке (например, в порядке появления).

Пример на Python:

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

Файл в HDFS, например Parquet, читается по блокам, а не целиком. HDFS разбивает файлы на блоки (обычно по 128 МБ), и при чтении данные загружаются блоками, что позволяет эффективно обрабатывать большие объемы данных и параллелить операции. Parquet — это колоночный формат, и при чтении можно выбирать только нужные колонки, что дополнительно оптимизирует доступ к данным.

Встроенная функция sorted в Python работает за время O(n log n), где n — количество элементов в списке. Это оптимальное время для общего случая сортировки сравнениями.

Если задача позволяет использовать более специфичные алгоритмы (например, сортировка подсчетом, если элементы — целые числа в ограниченном диапазоне), можно добиться времени O(n).

Пример более быстрого решения для сортировки целых чисел в диапазоне:

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

В PostgreSQL существуют следующие основные типы индексов:

B-tree — самый распространённый тип, используется для быстрого поиска по равенству и диапазонам.

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

GiST (Generalized Search Tree) — обобщённый индекс, поддерживает различные структуры данных, например, для геометрических данных.

SP-GiST (Space-partitioned GiST) — индекс для разбиения пространства, полезен для специфичных структур данных.

GIN (Generalized Inverted Index) — индекс для быстрого поиска по элементам массивов, JSON и полнотекстовому поиску.

BRIN (Block Range Index) — компактный индекс для больших таблиц с упорядоченными данными, индексирует диапазоны блоков.

Каждый тип индекса подходит для разных сценариев использования и типов данных.

Чтобы в выборку попали все пользователи, включая тех, у кого нет заказов, нужно использовать LEFT JOIN.

Объяснение:

LEFT JOIN возвращает все строки из левой таблицы (users) и соответствующие строки из правой таблицы (orders). Если соответствующих заказов нет, поля из orders будут заполнены NULL.

Пример запроса:

Это позволит получить всех пользователей с их последними заказами, а для пользователей без заказов — NULL в полях заказа.

Рассмотрим виды JOIN и результат запроса select * from t1 <join> t2 on t1.t = t2.t для таблиц:

CROSS JOIN — декартово произведение всех строк из t1 и t2, без условия соединения.

Результат будет 4 (строки t1) * 4 (строки t2) = 16 строк, например:

INNER JOIN — возвращает строки, где условие t1.t = t2.t истинно.

Результат:

(так как null = null в SQL не считается равенством, но в некоторых СУБД с использованием IS NULL может быть иначе; обычно null не равен null)

LEFT JOIN — все строки из t1, и соответствующие из t2, если нет совпадения — NULL в столбцах t2:

RIGHT JOIN — все строки из t2, и соответствующие из t1, если нет совпадения — NULL в столбцах t1:

FULL JOIN — объединяет LEFT и RIGHT JOIN, все строки из обеих таблиц, NULL там, где нет совпадений:

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

Для получения топ 10 водителей по количеству заказов в каждом городе можно использовать оконную функцию ROW_NUMBER() или RANK() в SQL. Пример запроса:

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

Query optimizer в Oracle — это компонент СУБД, который отвечает за выбор наиболее эффективного плана выполнения SQL-запроса. Он анализирует различные варианты доступа к данным и порядок операций, чтобы минимизировать затраты ресурсов и время выполнения.

Оптимизатор относится к классу cost-based optimizer (CBO), то есть он оценивает стоимость каждого возможного плана на основе статистики о данных (например, количество строк, распределение значений, наличие индексов).

Основные факторы, на которые смотрит optimizer:

Статистика таблиц и индексов (количество строк, плотность данных)

Наличие и типы индексов

Фильтры и условия в запросе

Связи между таблицами (join conditions)

Доступные методы доступа (полный скан, индексный скан и т.д.)

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

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

Сообщение трассировки (span) обычно состоит из следующих основных блоков:

Trace ID — уникальный идентификатор всего трассировочного запроса, связывает все спаны в одной цепочке.

Span ID — уникальный идентификатор конкретного спана (операции).

Parent Span ID — идентификатор родительского спана, если есть, для построения иерархии.

Operation Name — название операции или метода, который выполняется.

Start Timestamp — время начала операции.

Duration — продолжительность выполнения операции.

Tags/Attributes — дополнительные метаданные, например, статус, ошибки, параметры запроса.

Logs/Events — временные метки с описанием событий внутри спана.

Эти блоки позволяют отслеживать и анализировать распределённые запросы в системах микросервисов.

На последних местах работы я занимался проектами, связанными с обработкой больших данных и построением ETL-процессов. В одном из проектов мы собирали и обрабатывали данные из различных источников (лог-файлы, базы данных, API) для аналитики и отчетности.

Стек включал:

Apache Spark для распределённой обработки данных

Apache Kafka для потоковой передачи сообщений

Python и SQL для написания скриптов и запросов

Airflow для оркестрации задач

Hadoop HDFS для хранения больших объемов данных

Также имел опыт работы с облачными сервисами AWS, в частности с S3 и EMR для масштабируемой обработки данных.

Для оптимизации SQL-запросов, которые изначально выполнялись 12-15 секунд, я применял несколько подходов:

Индексация: добавлял индексы на колонки, участвующие в условиях WHERE, JOIN и ORDER BY, чтобы ускорить поиск и сортировку.

Переписывание запросов: заменял подзапросы на JOIN, упрощал сложные конструкции, чтобы СУБД могла лучше оптимизировать план выполнения.

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

Ограничение выборки: добавлял LIMIT или фильтры, чтобы уменьшить объем обрабатываемых данных.

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

В результате этих действий время выполнения удалось сократить до 1-2 секунд, что значительно повысило производительность системы.

Для получения списка бронирований на определённый день, где стоимость превышает $30, без использования подзапросов, можно использовать JOIN и фильтрацию в WHERE. Предположим, у нас есть таблицы bookings, members, facilities, и стоимость рассчитывается исходя из длительности и тарифа, который зависит от того, гость это (ID=0) или член.

Пример SQL-запроса:

Здесь:

:phone — параметр с датой бронирования.

slots — количество половинчасовых интервалов.

Стоимость рассчитывается с учётом типа пользователя.

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

Вот пример функции на Go, которая возвращает максимальное количество подряд идущих единиц в строке:

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

Рассмотрим два подхода для поиска максимального количества подряд идущих единиц в строке:

Цикл с переменными:

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

При встрече нуля сбрасываем счетчик.

Отслеживаем максимальное значение.

split('0') + list comprehension + max():

Разбиваем строку по нулям, получая список подстрок из единиц.

Вычисляем длины этих подстрок.

Находим максимум.

По потреблению памяти:

Цикл с переменными использует константное количество памяти, так как хранит только счетчики.

split() создает список подстрок, что требует дополнительной памяти, пропорциональной количеству и длине подстрок.

Таким образом, первый метод более экономен по памяти и обычно быстрее, особенно на больших строках. Второй метод проще в реализации, но менее эффективен по памяти.

В HDFS (Hadoop Distributed File System) данные нарезаются на блоки фиксированного размера (обычно 128 МБ или 256 МБ) для эффективного распределённого хранения и обработки. Это позволяет:

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

Параллельно обрабатывать блоки на разных узлах, что ускоряет вычисления.

Обеспечить отказоустойчивость за счёт репликации блоков на нескольких узлах.

Таким образом, нарезание на блоки — ключевой механизм масштабируемости и надёжности HDFS.

DBT (Data Build Tool) выбран часто из-за своей простоты и эффективности в организации процессов трансформации данных в аналитических проектах.

Плюсы DBT:

Простота использования: SQL-ориентированный подход позволяет аналитикам и инженерам быстро создавать и поддерживать модели.

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

Автоматизация: поддержка автоматического построения DAG зависимостей между моделями.

Тестирование данных: встроенные возможности для написания тестов качества данных.

Интеграция с современными хранилищами: хорошо работает с облачными платформами (Snowflake, BigQuery, Redshift).

Минусы DBT:

Ограниченность SQL: сложные трансформации вне SQL требуют обходных путей или внешних инструментов.

Отсутствие полноценного оркестратора: DBT не управляет загрузкой данных, только трансформацией.

Зависимость от инфраструктуры: требует наличия подходящего хранилища данных.

В целом, DBT отлично подходит для организации прозрачных, повторяемых и тестируемых трансформаций данных, особенно если команда ориентирована на SQL и современную архитектуру данных.

MapReduce — это модель программирования для обработки больших данных, состоящая из двух основных стадий: Map и Reduce.

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

Стадия Reduce собирает все промежуточные значения с одинаковым ключом и агрегирует их, выполняя сводную операцию (например, суммирование, подсчет, объединение).

Пример: подсчет количества слов в большом тексте.

Map: для каждого слова emit (слово, 1)

Shuffle: группировка по слову

Reduce: суммирование всех единиц для каждого слова

Таким образом, Reduce агрегирует данные, полученные от Map, и формирует итоговый результат.

Threading — это многопоточность, где несколько потоков выполняются в рамках одного процесса и могут работать параллельно, но в Python из-за GIL (Global Interpreter Lock) одновременно выполняется только один поток Python-кода. Подходит для задач с большим количеством операций ввода-вывода (I/O), например, сетевые запросы.

Multiprocessing — это запуск нескольких процессов, каждый со своей памятью и интерпретатором Python, что позволяет обойти GIL и выполнять код параллельно на нескольких ядрах CPU. Хорошо подходит для CPU-интенсивных задач.

Asyncio — это асинхронное программирование с использованием событийного цикла и корутин. Позволяет эффективно обрабатывать большое количество I/O операций без создания множества потоков или процессов. Идеально для сетевых приложений, где нужно обрабатывать тысячи соединений.

Когда что использовать:

Для CPU-зависимых задач — multiprocessing.

Для I/O-зависимых задач с небольшим количеством параллелизма — threading.

Для масштабируемых I/O-зависимых задач с большим количеством соединений — asyncio.

Пример использования asyncio:

Merge Join — это алгоритм соединения двух отсортированных наборов данных по ключу. Он эффективен, когда обе таблицы отсортированы по полю соединения.

Как работает Merge Join:

Имеются две отсортированные последовательности (например, таблицы A и B), отсортированные по ключу соединения.

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

Если ключи равны, происходит объединение строк с этим ключом.

Если ключ из первой последовательности меньше, указатель в первой последовательности сдвигается вперед.

Если ключ из второй последовательности меньше, сдвигается указатель во второй.

Процесс повторяется до конца одной из последовательностей.

Преимущества:

Высокая производительность при наличии отсортированных данных.

Линейная сложность по сумме размеров входных данных.

Недостатки:

Требуется предварительная сортировка, если данные не отсортированы.

Пример:

Таблица A: ключи [1, 3, 5, 7]

Таблица B: ключи [3, 5, 6, 7]

Алгоритм пройдет по ключам, объединяя строки с ключами 3, 5 и 7, пропуская несовпадающие.

Таким образом, Merge Join — это эффективный способ соединения больших отсортированных наборов данных без необходимости хеширования.

Чтобы сделать протяжку вверх (заполнить NULL предыдущим ненулевым значением в обратном направлении), можно использовать функции заполнения в pandas:

В SQL для этого часто используют оконные функции с LAST_VALUE или IGNORE NULLS, но поддержка зависит от СУБД. В PostgreSQL можно сделать так:

Где filled_up — это протяжка вверх (заполнение NULL предыдущим ненулевым значением в обратном направлении).

DAG (Directed Acyclic Graph) в Airflow — это направленный ацикличный граф, который описывает порядок выполнения задач (tasks) в рамках рабочего процесса (workflow).

Из чего состоит DAG:

Таски (Tasks) — отдельные единицы работы, например, запуск скрипта, запрос к базе, загрузка данных.

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

Параметры DAG — расписание (schedule_interval), стартовое время (start_date), настройки повторов и т.д.

Как Airflow исполняет DAG:

Планировщик (Scheduler) читает DAG-файлы и создает экземпляры задач для выполнения согласно расписанию.

Задачи ставятся в очередь (через брокер, например, Celery или локальный исполнитель).

Исполнители (Workers) берут задачи из очереди и запускают их.

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

Пример простого DAG на Python:

Таким образом, DAG описывает структуру и порядок выполнения задач, а Airflow управляет их запуском и мониторингом.

В современных проектах данные в хранилище обычно попадают через ETL/ELT-процессы (Extract, Transform, Load). На текущем месте работы процесс может выглядеть так:

Извлечение (Extract): данные берутся из различных источников — баз данных, API, файловых систем, логов.

Преобразование (Transform): данные очищаются, нормализуются, агрегируются, приводятся к нужному формату. Это может происходить в промежуточных слоях или прямо в хранилище, если оно поддерживает вычисления.

Загрузка (Load): преобразованные данные загружаются в целевое хранилище — это может быть Data Warehouse, Data Lake или специализированная база.

Для автоматизации часто используются инструменты вроде Apache Airflow, Talend, Informatica, или собственные скрипты на Python/SQL.

Пример: данные из CRM выгружаются в CSV, затем с помощью Python-скрипта обрабатываются и загружаются в PostgreSQL Data Warehouse, где доступны аналитикам.

Также возможна потоковая загрузка (streaming) через Kafka или другие брокеры сообщений, если данные должны поступать в реальном времени.

В Hive партиционирование используется для оптимизации запросов и управления большими объёмами данных. Партиция — это логическое разделение таблицы по значению одного или нескольких столбцов (например, по дате).

Физически партиционирование представлено в файловой системе как вложенные каталоги. Например, если таблица партиционирована по дате, то в HDFS будет структура:

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

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

Основные подходы:

append — просто добавляем новые записи, не трогая старые. Подходит, если данные не обновляются.

merge (upsert) — обновляем существующие записи и добавляем новые, используя уникальный ключ. Требует поддержки со стороны базы данных (например, MERGE или ON CONFLICT в Postgres).

insert_overwrite — перезаписываем часть таблицы, например, по партиции.

Пример конфигурации модели с merge:

Здесь при инкрементальной загрузке берутся только записи с updated_at больше максимального в целевой таблице, а затем dbt обновит или вставит их по unique_key.

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

Основное различие между WHERE и HAVING в SQL:

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

HAVING фильтрует группы после выполнения агрегатных функций (например, SUM, COUNT). Он применяется к результатам группировки.

Пример:

Здесь сначала выбираются сотрудники с зарплатой выше 50000 (WHERE), затем они группируются по отделам, и после этого выбираются только те отделы, где количество сотрудников больше 10 (HAVING).

Greenplum — это распределённая аналитическая СУБД на базе PostgreSQL, ориентированная на обработку больших объёмов данных.

Что касается ACID, то Greenplum поддерживает транзакции и обеспечивает атомарность, согласованность и изолированность на уровне отдельных сегментов (узлов), так как основан на PostgreSQL. Однако из-за распределённой архитектуры и параллельной обработки данных полная гарантия изоляции и долговечности транзакций на уровне всего кластера может быть ограничена.

Таким образом, Greenplum поддерживает ACID в рамках отдельных сегментов, но в распределённой среде некоторые аспекты, например, изоляция между сегментами, могут быть менее строгими по сравнению с традиционными СУБД.

Когда вы удалили локальную ветку hotfix/missing-footer с помощью git branch -d, но не сделали merge, ветка исчезла из локального репозитория, но коммиты остались в истории.

Чтобы восстановить ветку, можно найти последний коммит этой ветки и создать ветку заново:

Выполните команду, чтобы найти недавние коммиты, включая удалённые ветки:

Найдите SHA коммита, на котором была ветка hotfix/missing-footer.

Создайте ветку заново от этого коммита:

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

Если ветка была удалена и локально, и на удалённом, то reflog — ваш лучший способ найти коммит и восстановить ветку.

После восстановления ветки не забудьте выполнить merge в develop, чтобы сохранить изменения.

Когда CTE (Common Table Expression) требует много памяти, данные обычно начинают использовать временное хранение на диске (swap или spill to disk), если оперативной памяти недостаточно. Это может привести к замедлению выполнения запроса, так как операции с диском значительно медленнее, чем с памятью.

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

Индексы в базах данных нужны для ускорения поиска и выборки данных. Они работают как указатели на строки в таблице, позволяя СУБД быстро находить нужные записи без полного сканирования таблицы.

Плюсы:

Значительно ускоряют операции SELECT с условиями поиска.

Помогают при сортировках и объединениях (JOIN).

Могут обеспечить уникальность значений (уникальные индексы).

Минусы:

Занимают дополнительное место на диске.

Замедляют операции вставки (INSERT), обновления (UPDATE) и удаления (DELETE), так как индексы нужно обновлять.

Избыточное количество индексов может ухудшить производительность.

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

В Oracle для просмотра плана выполнения запроса используется команда EXPLAIN PLAN. Она записывает план в специальную таблицу, но не выполняет сам запрос. Это позволяет увидеть, как оптимизатор планирует выполнить запрос.

Пример:

EXPLAIN ANALYZE — это команда из PostgreSQL, в Oracle аналогичной команды нет. В PostgreSQL EXPLAIN ANALYZE выполняет запрос и показывает реальное время выполнения каждого шага, а не только план.

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

Итого:

EXPLAIN PLAN в Oracle показывает предполагаемый план без выполнения.

EXPLAIN ANALYZE в PostgreSQL выполняет запрос и показывает фактические затраты.

В Oracle для анализа фактического выполнения используют другие инструменты, например, DBMS_XPLAN.DISPLAY_CURSOR.

А если вопрос прозвучит не так, как вы готовились?

Так бывает чаще всего. ИзиСобес слышит вопрос интервьюера и подсказывает ответ прямо во время разговора — его не видно ни в Zoom, ни при демонстрации экрана.

Посмотреть ИзиСобес