Learning Platform
Глоссарий Troubleshooting
Урок 02.01 · 22 мин
Средний
in-processembeddedolaparchitecture

In-process (embedded) OLAP-СУБД: что значит «база внутри процесса»

В первом уроке вводного модуля мы сказали, что DuckDB — это in-process OLAP-СУБД, и сравнили его с SQLite. Теперь разберём, что именно стоит за словами «in-process» и «embedded», на уровне устройства процесса операционной системы. Это не терминологическая придирка: модель встраивания определяет производительность, развёртывание и границы применимости DuckDB. Не понимая её, легко принять неверное архитектурное решение — и потом дорого его исправлять.

В этом уроке мы разберём, как клиент-серверная СУБД и embedded-СУБД устроены на уровне процессов ОС, что физически происходит при запросе, и почему «база внутри процесса» — это и сила DuckDB, и его ограничение одновременно.

Сразу зафиксируем терминологию, чтобы дальше не путаться. «In-process» и «embedded» — практически синонимы, два названия одного свойства. «Embedded» («встроенный») подчёркивает форму поставки: СУБД встроена в приложение как библиотека. «In-process» («внутри процесса») подчёркивает место исполнения: движок работает в том же процессе операционной системы, что и приложение. Противоположность обоих — «клиент-серверная» (или «standalone») СУБД, которая работает как отдельный процесс-сервер. Когда в курсе встретится любой из этих терминов применительно к DuckDB, речь об одном и том же: движок-библиотека внутри процесса хоста.


Анатомия клиент-серверной СУБД

Возьмём PostgreSQL. Когда вы «работаете с базой», в системе на самом деле есть как минимум два разных процесса операционной системы.

Первый — процесс СУБД-сервера (демон postgres). Он стартует при загрузке машины, открывает TCP-порт 5432, имеет собственное адресное пространство, собственный пул памяти (shared buffers), собственные рабочие потоки и процессы. Он — владелец данных: только он читает и пишет файлы базы на диске.

Второй — процесс вашего приложения (Python-скрипт, например). У него своё адресное пространство, изолированное от сервера операционной системой. Напрямую дотянуться до памяти сервера он не может — это запрещено на уровне ядра ОС.

Чтобы приложение получило данные, оно открывает соединение с сервером и обменивается с ним сообщениями по протоколу. Даже если сервер на той же машине, общение идёт через сетевой стек (loopback-интерфейс) или Unix-сокет.

Клиент-серверная СУБД: два процесса ОС
Процесс приложенияСвоё адресное пространство, изолированное ядром ОС. Не может напрямую читать память сервера
протокол / сокет
Граница процессовИзоляция адресных пространств, обеспеченная ядром ОС: данные пересекают её только сериализованными
протокол / сокет
Процесс СУБД-сервераДемон postgres: свой пул памяти, свои потоки, владелец файлов БД. Стартует отдельно и работает постоянно

Что физически происходит при запросе SELECT ... в этой модели:

  1. Приложение формирует текст запроса и сериализует его в байты протокола.
  2. Байты идут через сокет — это системный вызов и переключение контекста.
  3. Сервер десериализует запрос, парсит, планирует, исполняет.
  4. Результат — набор строк — сервер сериализует обратно в байты протокола.
  5. Байты идут через сокет к приложению.
  6. Приложение десериализует строки в свои структуры данных.

Шаги 1, 2, 4, 5, 6 — это чистые накладные расходы транспорта. На больших результатах сериализация и десериализация могут стоить дороже самого вычисления. Подчеркнём: эти расходы возникают не из-за плохой реализации какой-то конкретной СУБД, а из-за самой клиент-серверной модели. Раз сервер и приложение — разные процессы, данные обязаны пройти через границу между ними, а пройти её можно только сериализованными. Это структурная цена архитектуры, и заплатит её любая клиент-серверная СУБД, как бы хорошо она ни была написана.


Анатомия embedded-СУБД

Теперь DuckDB. Когда вы пишете import duckdb в Python и вызываете запрос, нового процесса операционной системы не появляется. Нет демона. Нет порта. Нет сокета.

DuckDB — это разделяемая библиотека (.so на Linux, .dylib на macOS, .dll на Windows), которую интерпретатор Python загружает в своё собственное адресное пространство. С точки зрения операционной системы DuckDB-движок — это просто часть кода вашего Python-процесса, ровно как numpy или код самого интерпретатора.

«Embedded» («встроенный») и означает именно это: СУБД встроена в процесс хоста как библиотека. «In-process» — синоним, подчёркивающий, что движок исполняется внутри того же процесса.

Embedded-СУБД: один процесс ОС
Один процесс ОСНапример, процесс Python-интерпретатора: одно адресное пространство на всё
Код приложенияВаш Python-код: переменные, DataFrame — всё в этом же адресном пространстве
Движок DuckDBЗагруженная разделяемая библиотека: исполняется в том же адресном пространстве, видит ту же память

Что физически происходит при запросе в этой модели:

  1. Приложение вызывает функцию DuckDB и передаёт ей текст запроса — это обычный вызов функции в пределах процесса, как len(x).
  2. Движок парсит, планирует, исполняет запрос прямо здесь же.
  3. Результат отдаётся как структура данных в той же памяти — указатель, а не сериализованные байты.

Шагов сериализации, сокетов и переключений контекста нет вообще. Запрос — это вызов функции, результат — указатель на память. Разница с клиент-серверной моделью здесь не количественная («чуть быстрее»), а качественная: целого этапа транспорта данных просто не существует.


Прямое следствие: zero-copy обмен данными

Самое важное практическое следствие in-process модели — обмен данными с хост-средой почти бесплатен.

Допустим, у вас в Python есть pandas DataFrame sales на 50 миллионов строк, и вы хотите выполнить по нему SQL. В клиент-серверной СУБД пришлось бы загрузить эти 50 миллионов строк в сервер: сериализовать, передать по сокету, сервер их десериализует и материализует у себя. Объём данных физически прошёл бы границу процессов — дважды (туда данные, обратно результат).

DuckDB находится в том же адресном пространстве, что и DataFrame sales. Движок может получить указатель на буферы, где pandas уже хранит данные, и читать их прямо оттуда. Никакого копирования, никакой сериализации. Это и называется zero-copy.

import duckdb
import pandas as pd

sales = pd.DataFrame({
    "region": ["EU", "US", "EU", "ASIA", "US"],
    "amount": [100, 250, 70, 300, 180],
})

# DuckDB видит DataFrame sales по имени — он в той же памяти процесса
result = duckdb.sql("SELECT region, SUM(amount) AS total FROM sales GROUP BY region ORDER BY total DESC")
print(result)

Вывод:

┌─────────┬───────┐
│ region  │ total │
│ varchar │ int64 │
├─────────┼───────┤
│ US      │   430 │
│ ASIA    │   300 │
│ EU      │   170 │
└─────────┴───────┘

Обратите внимание: мы нигде не «загружали» sales в DuckDB и не регистрировали его. DuckDB просто увидел переменную sales в области видимости Python — потому что она в той же памяти. Механику этого (replacement scans) курс разберёт отдельно; здесь важно одно: это работает только потому, что движок и DataFrame физически в одном процессе.

NOTE

Zero-copy — не магия DuckDB, а следствие in-process архитектуры. Любая embedded-СУБД в одном процессе с приложением имеет к его памяти прямой доступ. Клиент-серверная СУБД не имеет принципиально: граница процессов, которую держит ядро ОС, пересекается только сериализованными данными.

Стоит оценить, насколько крупный это выигрыш. Представьте аналитический пайплайн, который несколько раз перекладывает данные между шагами: что-то посчитали, передали дальше, ещё посчитали. В клиент-серверной модели каждая такая передача — это сериализация на одной стороне и десериализация на другой, и на больших объёмах суммарное время на эту «упаковку-распаковку» может превысить время самих вычислений. В in-process модели этих шагов просто нет: данные остаются на месте, операторы движка читают их по указателю. Получается, что in-process архитектура убирает не отдельную медленную операцию, а целый класс накладных расходов — транспорт данных между процессами. Для аналитики, где данных много и их часто перекладывают, это решающее преимущество.

Полезно также понять, почему граница процессов в принципе непреодолима для zero-copy. Изоляция адресных пространств — это не «настройка», а фундаментальное свойство, которое ядро операционной системы обеспечивает ради безопасности и стабильности: один процесс не должен иметь возможности читать или портить память другого. Поэтому два процесса физически не могут просто «поделиться» структурой данных по указателю — указатель одного процесса в адресном пространстве другого бессмыслен. Любой обмен данными между процессами обязан пройти через явный механизм — сокет, файл, разделяемую память — и в общем случае это означает сериализацию. Embedded-СУБД обходит проблему радикально: она вообще не пересекает границу процессов, потому что находится по ту же сторону, что и приложение.


Embedded — это и ограничение

Apache DataFusion: альтернативный embeddable-движок на Rust

In-process модель даёт скорость и простоту, но у неё есть оборотная сторона, и понимать её обязательно.

Раз DuckDB живёт внутри процесса, у базы нет независимого от процесса существования. Из этого следуют ограничения.

Нет сетевого доступа. К DuckDB нельзя «подключиться» с другой машины или из другого процесса по сети — подключаться не к чему, демона нет. Если данные нужны нескольким сервисам одновременно по сети — embedded-модель не подходит (для облачного сценария существует MotherDuck, отдельный managed-сервис).

Один процесс — один писатель. Persistent-файл DuckDB в каждый момент может быть открыт на запись только одним процессом. Внутри этого процесса много потоков и много читателей — но не много пишущих процессов. Это делает DuckDB непригодным для роли разделяемой транзакционной БД многих сервисов.

Жизненный цикл базы привязан к процессу. In-memory база существует, пока жив процесс; завершился процесс — база исчезла. Persistent-файл переживает процесс, но активная сессия — нет.

АспектКлиент-сервер (PostgreSQL)Embedded (DuckDB)
Процессов ОССервер + клиент(ы)Один
Передача запросаСериализация + сокетВызов функции
Обмен данными с приложениемСериализация через границу процессовZero-copy, общая память
Сетевой доступДа, много клиентовНет
Конкурентная записьМного процессов-писателейОдин процесс-писатель
АдминистрированиеДемон, порты, пользователиНет, библиотека

Это не «недостатки» DuckDB — это границы его класса. DuckDB и клиент-серверная СУБД решают разные задачи. DuckDB — встраиваемый аналитический движок: быстрый, простой, для аналитики внутри одного процесса. Когда нужен сетевой многопользовательский доступ к общим данным, берут клиент-серверную СУБД. Выбор инструмента — это выбор под задачу, и in-process модель прямо очерчивает, для каких задач DuckDB создан.


Почему ограничения in-process — это разумный размен

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

Клиент-серверная архитектура даёт сетевой многопользовательский доступ — но платит за это сложностью. Сервер нужно развернуть, настроить, держать запущенным, мониторить, обновлять, защищать. Между приложением и данными встаёт сетевой слой со своими задержками и со стоимостью сериализации. Эта цена оправдана, когда вам действительно нужно, чтобы много клиентов с разных машин одновременно работали с общими данными — например, для операционной БД сервиса.

Но огромный класс задач этого не требует. Аналитик исследует датасет в своём ноутбуке. ETL-скрипт обрабатывает данные в одном процессе. Аналитический модуль приложения считает отчёты по локальным данным. Во всех этих случаях многопользовательский сетевой доступ не нужен вообще — а значит, и платить за него сложностью незачем. DuckDB сделал ровно этот размен: отказался от того, что этому классу задач не нужно (сервер, сеть, многопроцессная запись), и в обмен получил то, что этому классу задач критично важно (нулевой транспорт данных, тривиальное развёртывание, отсутствие администрирования).

То есть ограничения in-process модели — это не «DuckDB чего-то не умеет», а «DuckDB не несёт стоимость того, что его целевым задачам не нужно». Для аналитики в одном процессе это не компромисс, а точное попадание в задачу. А когда задача меняется — нужен именно сетевой доступ — это сигнал, что нужен другой класс инструмента, и в этом нет ничего плохого: инструмент выбирают под задачу.


Попробуй сам

Убедитесь, что DuckDB действительно не создаёт отдельного процесса. Запустите интерактивную Python-сессию и в ней выполните:

import os
print("PID процесса:", os.getpid())

import duckdb
con = duckdb.connect()
con.sql("SELECT 1").fetchall()
print("PID после работы с DuckDB:", os.getpid())

PID до и после — один и тот же, и нового процесса в системе не появилось. Параллельно откройте системный монитор (ps, top или Task Manager) и поищите процесс с именем duckdb — вы его не найдёте, потому что движок исполняется внутри процесса python. Сравните это с PostgreSQL: там ps aux | grep postgres покажет демон-процесс, существующий отдельно от любого приложения. Эта разница — наблюдаемое доказательство того, что значит «in-process». Запишите для себя вывод одним предложением: у DuckDB нет процесса, отдельного от приложения, — и именно из этого факта вытекают и zero-copy обмен данными, и простота развёртывания, и ограничения вроде модели «один писатель».


Проверка знанийKnowledge check
Почему zero-copy обмен данными между приложением и DuckDB возможен в принципе, а между приложением и PostgreSQL-сервером — нет, даже если сервер запущен на той же машине?
ОтветAnswer
DuckDB — embedded-СУБД: движок загружается как разделяемая библиотека в адресное пространство процесса хоста (например, Python-интерпретатора). Приложение и DuckDB-движок физически находятся в одной памяти, поэтому движок может получить указатель на буферы, где приложение уже хранит данные (например, pandas DataFrame), и читать их напрямую — без копирования и сериализации. PostgreSQL — клиент-серверная СУБД: сервер это отдельный процесс операционной системы со своим, изолированным ядром ОС адресным пространством. Даже на одной машине процесс приложения не может напрямую читать память процесса сервера — это запрещено ядром. Поэтому данные пересекают границу процессов только в сериализованном виде, через сокет, с переключениями контекста. Zero-copy — не особая оптимизация DuckDB, а прямое следствие того, что движок и приложение делят одно адресное пространство; клиент-серверная архитектура исключает это принципиально.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 4. Что физически происходит при выполнении `SELECT ...` через embedded-СУБД DuckDB в Python-процессе?

Закончили урок?

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

Войдите чтобы оценить урок

Прогресс модуля
0 из 6