Перейти к содержанию

Регионы

Provisa даёт вам два примитива для размещения данных: регион и закрепление за регионом, то есть создание региона и назначение ему таблицы. Ни один из них сам по себе не является политикой. То, для чего вы их используете, делится на три группы:

  • Производительность. Задержка до читателей, свежесть их копии и пригодность движка: где находится его центр обработки данных, какое хранилище он читает, сколько у него процессора и памяти.
  • Стоимость. Где выполняется работа и на чём: собственное оборудование или облако, большой движок или маленький, и во что обходится перемещение данных между местами (egress).
  • Суверенитет. Кто контролирует данные и куда они могут попадать: требования закона о размещении (где персональные данные можно обрабатывать и где хранить) и собственные линии контроля организации, например подразделение или дочерняя компания, которая держит свои данные и инфраструктуру под собственным контролем.

Механика для всех трёх одна и та же. Различаются только правила, которые вы пишете поверх.

Регион — это движок и его хранилища в одном месте

Регион — это место, где Provisa выполняет работу и хранит то, что после неё остаётся. У каждого региона свой движок и свои хранилища. (REQ-1922) [tool-verified: provisa/core/regions.py OrgRegion, STORE_ROLES]

Хранилище Что в нём
engine Движок федерации, выполняющий инструкции в этом регионе
replicas Реплики таблиц, закреплённых здесь, и таблиц без региона, которые копирует этот регион
views Копии материализованных представлений этого региона
cache Закешированные ответы
state Состояние сборок и обновлений
record Запись каждого обслуженного здесь запроса

Все регионы регулирует одна модель. Источники, таблицы, связи, роли, правила строк и колонок, представления и настройки свежести объявляются один раз, в одном хранилище модели, и каждый регион обслуживает эту же модель, поэтому определения не могут разойтись между регионами. Изменение доходит до каждого региона с одной и той же меткой, после чего каждый регион к нему сходится. (REQ-1914, REQ-1922)

Платформа объявляет физические регионы, в которых работают её узлы, с адресом, по которому отвечает каждый регион. Модель каждой организации выбирает, какие из этих регионов она использует, и называет своё хранилище для каждой роли в каждом регионе. Узел запускается в регионе командой provisa run --region <id> и обслуживает этот регион для каждой организации, которая его выбрала. Когда регионы объявлены, узел, запущенный без --region, отказывается запускаться и перечисляет регионы, которые мог бы обслуживать. (REQ-1922) [tool-verified: provisa/cli.py --region; provisa/core/regions.py validate_regions]

platform:
  regions:
    - { id: eu, address: "https://eu.provisa.example" }
    - { id: us, address: "https://us.provisa.example" }

stores:
  - { id: eu_engine, url: "${secret:eu-trino-url}", kind: trino }
  - { id: us_engine, url: "${secret:us-trino-url}", kind: trino }
  - { id: eu_data,   url: "${secret:eu-pg-url}" }
  - { id: us_data,   url: "${secret:us-pg-url}" }
  - { id: eu_cache,  url: "${secret:eu-redis-url}" }
  - { id: us_cache,  url: "${secret:us-redis-url}" }

regions:
  - { id: eu, engine: eu_engine, replicas: eu_data, views: eu_data, cache: eu_cache, state: eu_data, record: eu_data }
  - { id: us, engine: us_engine, replicas: us_data, views: us_data, cache: us_cache, state: us_data, record: us_data }
[inferred: the URLs and secret names are placeholders; the shape follows PlatformRegion, StoreConfig and OrgRegion in provisa/core/regions.py]

При загрузке конфигурация отклоняется с указанием причины, если регионы не согласуются. Регион должен назвать объявленное хранилище для каждой роли. Его replicas и views должны быть одним хранилищем, а хранилище движка должно называть kind движка. Если у платформы больше одного региона, встроенный файл движка отклоняется, потому что другие регионы не могут до него добраться. [tool-verified: require_one_materialize_store, require_engine_kind, require_reachable_engine]

Несколько регионов могут делить один экземпляр базы данных, один Redis или один бакет объектного хранилища. Всё, что хранит регион, несёт регион в своём имени, поэтому их хранилища не конфликтуют. (REQ-1922)

Развёртывание, которое не объявляет регионов, имеет один неявный регион и нигде не показывает регион. Нет поля региона и флага --region, а конфигурация, называющая регион, отклоняется. (REQ-1922)

Закрепление за регионом: таблица называет свой домашний регион

Таблица может назвать один из регионов своей организации своим домашним регионом. Только он строит и обновляет её реплику и только он может держать её горячей. Все остальные регионы читают таблицу из реплики домашнего региона, на месте, по адресу, который даёт модель. Правила строк и колонок читателя входят в эту инструкцию, поэтому фильтрация происходит в домашнем регионе и границу пересекает только то, что читателю разрешено видеть. (REQ-1921) [tool-verified: provisa/core/regions.py module docstring]

tables:
  - source_id: eu-shop
    schema_name: public
    table_name: customers
    region: eu
Некоторые следствия:

  • Ни один другой регион не читает источник таблицы; они читают только реплику домашнего региона. Источник, который вы реплицируете, чтобы защитить его от нагрузки, остаётся защищённым.
  • Если реплика не построена или домашний регион недоступен, запрос отклоняется с указанием домашнего региона. Источник вместо этого не читается напрямую.
  • Закреплённую таблицу, которая не реплицируется, с replicate Never или с настройкой, ещё не потребовавшей копии, каждый регион читает напрямую из её источника, как любую нереплицируемую таблицу.
  • Счётчик горячести таблицы — это трафик только её домашнего региона. (REQ-239)
  • Материализованное представление тоже может назвать регион. Тогда оно строится только там, а все остальные регионы читают эту копию, точно так же, как для закреплённой таблицы.

Региону, который читает реплику другого региона, нужен движок, способный её прочитать: duckdb, motherduck, pg, trino или trino-byo. Хранилище replicas домашнего региона должно быть сервером PostgreSQL. Конфигурация называет и то, и другое, когда они не подходят. [tool-verified: require_readable_elsewhere; REGION_READERS in provisa/core/engine_kinds.py]

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

Таблицы без региона копируются там, где их читают

Таблицу, не называющую регион, можно копировать в каждый регион. Когда её настройка replicate требует копии, регион, к которому подключился читатель, строит её в своём хранилище. Материализованное представление, не называющее регион, строится в каждом регионе, каждая копия в хранилище представлений этого региона. (REQ-1921)

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

Три причины использовать регионы

Производительность

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

  • Задержка. Читатели подключаются к ближайшему региону, и его движок, кеш и копии для них локальны.
  • Свежесть. Таблица, закреплённая там, где она меняется, обновляется там, рядом со своим источником.
  • Пригодность движка. В каждом регионе работает свой движок, поэтому движок можно подобрать к месту: к центру обработки данных, где он находится, к хранилищу, которое он читает, к его процессору и памяти.

Пример: два одинаковых региона. Объявите east и west в двух центрах обработки данных, каждый с одним и тем же видом движка и одними и теми же видами хранилищ, и не закрепляйте ни одной таблицы. Каждый регион обслуживает ближайших к нему читателей: строит свои копии того, что они читают, и кеширует их ответы. Оба могут стоять за одним балансировщиком нагрузки и делить нагрузку. Расписание обновления одно — расписание модели, — и оба региона начинают обновлять свои копии по нему, но их движки могут закончить не за одно и то же время. Поэтому в каждый момент не гарантировано, что оба находятся в одном состоянии, и читатель, переведённый из одного региона в другой, может увидеть данные другой свежести. Provisa не переводит клиента в другой регион, когда один останавливается; куда направить клиента, решает балансировщик нагрузки. [inferred: no failover or cross-region redirect exists in provisa/; clients connect to a region by its address, and each region builds and refreshes only its own copies, on the model's one schedule (REQ-1922)]

Стоимость

Регионы ради стоимости тоже не объявляют межрегиональных правил. Они размещают работу там, где её дешевле выполнять:

  • Собственное оборудование и облако. Держите регион на собственной площадке, на оборудовании, которым вы уже владеете, для таблиц и читателей, которые там, а другой регион запускайте в облаке для остального.
  • Размер движка. Запустите большой движок в одном регионе для эпизодической тяжёлой работы, а маленький в другом — для повседневного чтения. Читатели выбирают регион по его адресу.
  • Egress. Читатели в других регионах получают только отфильтрованные строки из реплики закреплённой таблицы, а большая таблица, закреплённая за одним регионом, не копируется в каждый регион.

Суверенитет

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

  • current_setting('provisa.region') — это регион узла, отвечающего на запрос, когда платформа объявляет регионы. Фильтр строк, написанный на нём, удерживает строки региона в этом регионе. (REQ-1922) [tool-verified: provisa/core/request_context.py session_vars_for]
  • Атрибут region участника даёт ему производные роли region и region__<value>, поэтому запись маски или предоставления может назвать analyst+region__eu. Собственная фоновая работа узла несёт его регион так же. (REQ-2057) См. Роли: предоставленные, производные и мета.

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

Кто может закрепить таблицу

Установка, изменение или удаление региона таблицы, источника или материализованного представления перемещает данные через границу. Для этого нужно право data_residency в дополнение к владению объектом через его домен. Предоставление называет значения, которые оно покрывает: регионы организации и «без региона». Перемещение объекта между двумя регионами требует предоставления, покрывающего оба. Сохранение за пределами предоставления отклоняется с указанием непокрытого значения. (REQ-1921)

Право существует только тогда, когда платформа объявляет регионы. Кто может изменять всё остальное в объекте, по-прежнему решает владение доменом. Регион не даёт собственных прав; организация, которая хочет, чтобы таблицами региона управляли люди этого региона, даёт им собственные домены. (REQ-1921)

Таблицу или представление можно также перевести в черновик: вне обслуживания и не предлагается ни одному читателю. Таблица, зарегистрированная через администрирование, начинает как черновик. Её ответственный просматривает её только на узлах региона, который она называет (если не называет никакого — там, где находится ответственный), и копия, которую загружает этот предпросмотр, хранится в этом регионе до выпуска таблицы. Копия, которую держит другой регион, оставшаяся с тех пор, когда регион не был задан или был другим, выводится из обращения и удаляется, как у опубликованной таблицы. Перенос между регионами может пройти через черновик, так что в промежутке таблица нигде не обслуживается: сторона первого региона переводит её в черновик, а владелец второго региона забирает объект, задав свой регион. (REQ-1921)

См. также демо с двумя регионами.