Что такое коннектор: реализация SPI
В прошлом уроке мы выяснили, что Trino не хранит данные и адресует их через имя catalog.schema.table. Но как именно движок умеет читать из PostgreSQL и из Parquet-файла на S3, не зная заранее ничего ни о PostgreSQL, ни о Parquet? Ответ — коннекторы. Коннектор это компонент, который переводит язык конкретного источника на язык, понятный ядру Trino.
Этот урок отвечает на вопрос «что такое коннектор» через ключевое понятие — Service Provider Interface (SPI). Понять SPI важно, потому что именно эта архитектура объясняет, почему Trino расширяется новыми источниками без переписывания ядра.
Проблема: ядро не должно знать про источники
Представьте, что ядро Trino — парсер SQL, планировщик, распределённое исполнение — содержит код для работы с PostgreSQL: JDBC-вызовы, маппинг типов, специфику диалекта. Затем добавили поддержку MySQL — ещё код в ядре. Потом Iceberg, Kafka, MongoDB, Cassandra. Ядро превращается в свалку, где логика движка перемешана с логикой каждого источника. Добавить новый источник нельзя, не тронув ядро. Сторонний разработчик не может добавить свой источник вообще.
Решение — провести жёсткую границу. Ядро движка знает только абстрактный «источник данных», описанный набором интерфейсов. Конкретику каждого источника реализует отдельный модуль — коннектор. Эта граница и называется SPI — Service Provider Interface.
API (Application Programming Interface) и SPI смотрят в разные стороны. API — это то, что движок предоставляет наружу клиентам: вы посылаете SQL, получаете результат. SPI — то, что движок требует от плагина: «реализуй вот эти интерфейсы, и я смогу с тобой работать». SPI это контракт «provider» (поставщика возможностей) с движком.
Что такое SPI технически
Технически Trino SPI — это набор Java-интерфейсов и базовых классов в отдельном Maven-модуле trino-spi. Этот модуль намеренно изолирован: он почти не зависит от внутренностей движка. Так сделано, чтобы внутреннее устройство ядра можно было менять, не ломая существующие коннекторы.
Коннектор — это плагин, реализующий интерфейсы SPI. Главные точки входа:
Plugin— точка входа плагина. МетодgetConnectorFactories()возвращает список фабрик коннекторов, которые плагин предоставляет.ConnectorFactory— фабрика. МетодgetName()возвращает имя (то самое, что вы пишете вconnector.name), аcreate()строит экземпляр коннектора по переданным свойствам catalog.Connector— собственно коннектор. Отдаёт движку набор сервисов: как читать метаданные, как разбивать данные на части, как читать и писать.
Цепочка простая: при старте Trino загружает плагины, у каждого спрашивает фабрики, запоминает их по имени. Когда вы добавляете catalog с connector.name=postgresql, движок находит фабрику с этим именем и просит её создать коннектор, передавая свойства из properties-файла.
Один плагин может содержать несколько фабрик коннекторов. Например, плагин для систем object storage может поставлять и Hive-, и Iceberg-, и Delta Lake-коннекторы. Поэтому “плагин” и “коннектор” — не синонимы: плагин это единица поставки и загрузки, коннектор это единица подключения источника.
Где коннекторы лежат и как загружаются
Коннекторы поставляются как директории в каталоге plugin/ установки Trino. Каждая поддиректория — один плагин со своими JAR-файлами:
trino-server-481/
plugin/
postgresql/ <- JAR-ы коннектора PostgreSQL
iceberg/ <- JAR-ы коннектора Iceberg
kafka/ <- JAR-ы коннектора Kafka
tpch/ <- JAR-ы коннектора TPC-H
...
Каждый плагин загружается своим изолированным classloader. Это решает реальную проблему: коннектору PostgreSQL может быть нужна одна версия библиотеки, коннектору Iceberg — другая, несовместимая. Без изоляции classloader-ов эти версии конфликтовали бы. С изоляцией каждый плагин живёт в своём «пузыре зависимостей», и конфликта нет.
Загрузка происходит один раз — при старте сервера Trino. Плагины обнаруживаются в plugin/, classloader-ы создаются, фабрики регистрируются. Сами catalog при этом ещё не создаются — это отдельный шаг.
Важно различать два уровня:
| Уровень | Что это | Когда происходит |
|---|---|---|
| Плагин | Код коннектора в plugin/имя/ | Загружается при старте сервера |
| Catalog | Конфигурация подключения в etc/catalog/имя.properties | Создаётся из properties-файла |
Плагин это потенциальная возможность («Trino умеет PostgreSQL»). Catalog это конкретное использование («есть подключение prod_postgres к такому-то серверу»). Один плагин может обслуживать много catalog: десять PostgreSQL-серверов это один плагин postgresql и десять properties-файлов.
Что коннектор скрывает от движка
Главная ценность SPI-архитектуры в том, что движок работает со всеми источниками одинаково, а вся разница спрятана внутри коннектора. Что именно коннектор берёт на себя:
- Маппинг типов. Родные типы источника (
int4в PostgreSQL, INT96-таймстемп в Parquet) коннектор переводит в типы Trino и обратно. - Доступ к метаданным. Какие схемы и таблицы есть, какие у них столбцы — коннектор отвечает на это, обращаясь к источнику (system-каталоги PostgreSQL, Hive Metastore для Iceberg).
- Чтение данных. Коннектор знает, как физически достать строки: выполнить SQL через JDBC, распарсить ORC-файл, вычитать сообщения из топика.
- Запись данных — если источник её поддерживает и коннектор реализует соответствующую часть SPI.
- Поддержка pushdown. Коннектор сообщает движку, какие операции (фильтрация, проекция, агрегация) он умеет выполнить сам, на стороне источника, вместо того чтобы движок тянул всё к себе.
Последний пункт особенно важен. SPI не «всё или ничего». Коннектор может реализовать SPI частично: например, уметь читать, но не писать; поддерживать predicate pushdown, но не aggregation pushdown. Поэтому возможности у коннекторов разные, и это нормально — каждый реализует ровно то, что имеет смысл для его источника.
Когда вы оцениваете незнакомый коннектор, первым делом смотрите его страницу в документации Trino: там перечислено, что он умеет — чтение, запись, какие виды pushdown, какие SQL-операции (CREATE TABLE, INSERT, MERGE) поддержаны. Два коннектора к разным источникам почти никогда не равны по возможностям.
Коннектор работает на каждой ноде
Важная деталь, вытекающая из распределённой природы Trino: коннектор — это не что-то, что живёт только на coordinator. Экземпляр коннектора создаётся на каждой ноде кластера — и на coordinator, и на каждом worker.
Причина — в разделении ролей, которое мы вводили в архитектуре. Coordinator планирует запрос: ему коннектор нужен, чтобы получить метаданные — структуру таблиц, статистику. Worker-ы исполняют запрос: им коннектор нужен, чтобы физически читать данные — открывать соединения, парсить файлы. Раз обе роли работают с источником, коннектор должен быть доступен на всех нодах.
Отсюда практическое следствие, к которому мы вернёмся в уроке про конфигурацию: catalog properties file должен лежать на каждой ноде. Если конфигурацию положить только на coordinator, он сможет спланировать запрос (метаданные получены), но worker-ы при попытке прочитать данные не найдут конфигурацию источника — и запрос упадёт.
Ещё одно свойство — коннектор сам по себе не хранит состояние данных между запросами. Он не кэширует строки таблиц у себя (хотя может кэшировать метаданные на короткое время ради скорости планирования). Каждый запрос идёт в источник заново. Это согласуется с природой Trino: движок не хранилище, и коннектор — лишь адаптер доступа, а не буфер данных.
Не путайте экземпляр коннектора с подключениями к источнику. Экземпляр коннектора создаётся на каждой ноде при создании catalog. А вот соединения с источником (например, JDBC-соединения в пуле) коннектор открывает по мере надобности, когда задачам этой ноды реально нужно читать данные. Экземпляр коннектора есть всегда; соединения — приходят и уходят.
Почему это даёт расширяемость
Сложим картину. Ядро Trino знает только SPI — абстрактный контракт источника данных. Любой источник, для которого кто-то написал коннектор (реализацию SPI), становится доступен через тот же ANSI SQL и то же распределённое исполнение. Добавление нового источника не требует изменения ядра — нужен лишь новый плагин в plugin/.
Именно поэтому экосистема коннекторов Trino насчитывает десятки источников: реляционные СУБД, object storage с табличными форматами, NoSQL, поисковые движки, streaming. И поэтому компания или сообщество могут написать собственный коннектор к внутренней системе, не имея доступа к исходникам ядра и не дожидаясь релиза Trino. SPI это и есть механизм, превращающий Trino из «движка для конкретных источников» в универсальную точку доступа к данным.
Попробуй сам
На работающем кластере Trino исследуйте, какие плагины загружены, и как они соотносятся с catalog:
- Зайдите на сервер Trino и посмотрите содержимое директории
plugin/(в Docker-образе это путь вроде/usr/lib/trino/plugin/). Сколько там поддиректорий? Каждая — отдельный плагин. - Посмотрите содержимое
etc/catalog/. Сколько там.properties-файлов? Откройте один и найдите строкуconnector.name— это имя фабрики коннектора. - Сопоставьте: для каждого
connector.nameиз properties-файлов найдите соответствующую поддиректорию вplugin/. - Найдите плагин, для которого нет ни одного catalog. Это иллюстрирует разницу: плагин загружен (возможность есть), но catalog не создан (источник не подключён).
Сформулируйте письменно разницу между «плагин загружен» и «catalog создан» — это разделение объясняет всю модель расширяемости Trino.