В нашей статье о совместимости PostgREST подробно описано, что функционально обеспечивает сервер (фильтры, внедрение, RPC, RLS) и что он оставляет на ваше усмотрение. Этот пришел откуда-то еще. Он документирует, используя код Aurabase и официальную документацию PostgREST в качестве источников, где и почему PostgREST на самом деле останавливается в масштабе. Мы не воспроизводим здесь банк нагрузки, который не запускали сами. Наша методология тестирования объясняет, почему изолированный показатель без опубликованного протокола не кажется нам надежным.
Самое необходимое
- PostgREST сам по себе легкий: от 50 до 250 милликорней ЦП, от 64 до 128 МБ ОЗУ на реплику в выделенных экземплярах Aurabase. Необработанная пропускная способность HTTP почти никогда не является ограничивающим фактором в производстве.
- Реальный потолок — это бюджет подключения Postgres:
PGRST_DB_POOL× реплик. Проверено в коде Aurabase: 20 подключений на проект на выделенном уровне (10×2), 4 на общем уровне (2×2). Это осознанный выбор, позволяющий разместить больше клиентов на одномmax_connections. Prefer: count=exactвызывает дорогостоящее сканирование MVCC на больших таблицах. PostgREST документирует две более дешевые альтернативы:count=plannedиcount=estimated, стоимость которых приблизительна.- Потолок
db-max-rows(1000 строк по умолчанию в Aurabase) усекает ответ БЕЗ сообщения о нем вContent-Range(измерения в реальных условиях, подробно описанные ниже). - После миграции DDL кэш схемы PostgREST перезагружается асинхронно. Шлюз Aurabase повторяет попытку до 8 раз (в худшем случае совокупно около 3,5 секунд), прежде чем сдаться, и это поведение задокументировано непосредственно в коде.
Что измеряет тест PostgREST и что он не измеряет
Тест пропускной способности HTTP на PostgREST в основном измеряет Postgres, реже PostgREST. Сервер — это тонкий слой трансляции перед базой. В подавляющем большинстве реальных нагрузок время ответа определяется выполняемым SQL-запросом, а не процессом, который его сгенерировал.
Проект PostgREST поддерживает специальный репозиторий для этой темы PostgREST/postgrest-benchmark на GitHub, который отслеживает изменения пропускной способности от выпуска к выпуску, а не публикует отдельные маркетинговые данные. Мы не исполняли и не переиздавали ее здесь. Его результаты зависят от аппаратного обеспечения, размера схемы и протестированного сценария, а именно от тех переменных, которые наш собственный протокол тестирования требует документирования, прежде чем приводить цифры.
Ниже PostgREST находится pgbench, который измеряет действительно важный уровень: время транзакции SQL при одновременной нагрузке. Это официальный инструмент тестирования PostgreSQL (postgresql.org/docs/current/pgbench.html, по состоянию на 24 августа 2026 г.). Вместо того, чтобы воспроизводить здесь этот протокол, в этой статье документируются четыре конкретных архитектурных ограничения PostgREST в производстве, каждое из которых проверено в исходном коде Aurabase или в официальной документации проекта.
Фактический размер экземпляра PostgREST в Aurabase
Каждый проект ядра Aurabase Postgres получает две выделенные реплики PostgREST, расположенные рядом с его кластером. Манифест Kubernetes, который их развертывает, устанавливает скромные ресурсы.
На самом деле эти реплики потребляют не ЦП, а соединения с основным сервером Postgres. Каждый экземпляр PostgREST подключается напрямую к основному (-rw), минуя пул PgBouncer, развернутый для клиента. Этот выбор уже подробно описан в нашей статье о совместимости PostgREST: механизм перезагрузки схемы LISTEN/NOTIFY требует постоянного соединения, несовместимого с пулером в режиме транзакций. Что добавляет эта статья: сколько это на самом деле стоит с точки зрения подключений и где она достигает максимума.
Размер этого пула на реплику (PGRST_DB_POOL) намеренно различается в зависимости от уровня проекта, что проверено в k8s_tenant.rs, функции, которая создает манифест PostgREST для каждого проекта:
| Несущий | PGRST_DB_POOL / реплика | Реплики | Связи / пробудившийся проект |
|---|---|---|---|
| Выделенный (премиум, А1) | 10 (по умолчанию PostgREST) | 2 | 20 |
| Общий (флот, свободный/профессиональный/командный) | 2 (по умолчанию Aurabase, понижено) | 2 | 4 |
На выделенном уровне ограничение ослаблено: проект имеет собственный кластер CNPG, следовательно, свой собственный max_connections, без лишних соседей. На общем уровне несколько проектов одной и той же организации разделяют один кластер: именно этот контекст делает решающим бюджет связей, который рассматривается в следующем разделе.
Бюджет подключений определяет, сколько арендаторов работает одновременно.
В общем кластере Postgres не пропускная способность HTTP ограничивает количество одновременно активных проектов. Это количество соединений, которые их экземпляры PostgREST удерживают открытыми на первичном сервере, по сравнению с доступным max_connections.
Aurabase получает этот бюджет непосредственно из фактических ограничений кластера, проверенных в fleet.rs: (max_connections − réserve superuser/CNPG − connexions réservées au pooler) ÷ connexions par projet éveillé, нижний уровень равен 1. Фиксированный резерв составляет 10 подключений (суперпользователь, менеджер экземпляров CNPG, экспортер метрик, область администратора поставщика). Для доставленных дефектов (пул из 2 на реплику, 2 реплики или 4 подключения на пробуждённый проект) расчет дает три разных бюджета в зависимости от уровня размера кластера.
Источник: получено из fleet.rs::derive_wake_budget и wake_budget_for_org_plan, кода Aurabase, перечитано 24 августа 2026 г.
Этот бюджет не является квотой собственных проектов: организация team может содержать 50 проектов, большинство из которых неактивны. Это ограничение параллелизма: количества проектов, которые могут одновременно поддерживать открытые соединения на основном сервере. Пробуждение из-за превышения бюджета не дает сбоя, оно откладывается до тех пор, пока родственный проект снова не перейдет в режим сна, что проверено в том же файле. Тема определения размера max_connections подробно рассматривается в нашей статье о настройке max_connections, а также о выборе выделенного/взаимного компромисса в целом в выделенной и общей базе.
Почему предпочтительнее: count=exact замедляет запрос к большой таблице
Запрос точной суммы вынуждает Postgres подсчитывать видимые строки отфильтрованного результата при каждом запросе, а затраты растут вместе с таблицей, а не бесплатная операция.
PostgreSQL не поддерживает никаких индексированных счетчиков строк по умолчанию. В MVCC видимость строки зависит от транзакции, которая ее читает. Поэтому точный COUNT(*) должен посещать строки-кандидаты, а не читать заранее вычисленное значение. Это хорошо задокументированное структурное ограничение в экосистеме Postgres, включая поставщиков аналитики, таких как ClickHouse, которые сравнивают свои собственные приблизительные счетчики с транзакционным поведением Postgres.
PostgREST изначально документирует эти три стратегии подсчета (postgrest.org, по состоянию на 24 августа 2026 г.). Стратегия exact гарантирует итоговую сумму по цене сканирования. planned возвращает почти бесплатную оценку из планировщика запросов. estimated автоматически переключается между ними в зависимости от порогового значения. Выбор не косметический: разбивка на страницы, требующая count=exact в таблице из нескольких миллионов строк, оплачивает это сканирование на каждой странице, даже если пользователь никогда не обращается к последней.
Усечение строк db-max невидимо без count=exact
Ограничение строки может обрезать ответ PostgREST без какого-либо указания в теле или заголовках, если только явно не запрашивается точная сумма. Мы измерили это в реальных условиях на выделенном экземпляре Aurabase, а не предполагали.
В тестовой таблице из 10 строк с PGRST_DB_MAX_ROWS=5PostgREST v12.2.3 отображает один и тот же заголовок Content-Range для двух совершенно разных ситуаций:
| Запрос | Орисованные линии | Диапазон содержимого | мета (Аурабаза) |
|---|---|---|---|
| ?limit=50 (без учета) | 5/10 реальный | 0-4/* | {} |
| ?limit=50&count=точный | 5/10 реальный | 0-4/10 | {всего: 10} |
Без count=exactответ из 5 строк неотличим от таблицы, которая на самом деле содержала бы только 5: Content-Range: 0-4/* описывает отображаемые строки, без применения ограничения. Фактическое ограничение в этом случае нигде не отображается, оно измеряется непосредственно на пути PostgREST Aurabase SDK.
Если в вашем развертывании PostgREST установлено значение db-max-rows (по умолчанию в Aurabase установлено значение 1000), клиент, который сравнивает data.length с запрошенным пределом для обнаружения полной страницы, может ошибиться. Ошибка появляется, как только потолок сервера становится ниже этого предела. Единственный надежный сигнал — сравнить количество полученных строк с total, возвращаемым count=exact, что напрямую приводит к компромиссу стоимости, описанному в предыдущем разделе.
Перезагрузка кэша схемы после миграции
PostgREST сохраняет схему Postgres в памяти при запуске. После DDL (создание таблицы, добавление столбца) этот кеш должен быть перезагружен до того, как новый маршрут ответит, и эта перезагрузка является асинхронной.
Запись, поступающая в это окно, может получить временную ошибку 404 (кэш еще не обновлен), даже если таблица действительно существует на стороне Postgres. Шлюз Aurabase поглощает это с помощью ограниченного цикла повторов, проверенного в postgrest_proxy.rs: до 8 попыток, увеличивающаяся задержка (250 мс плюс 100 мс на попытку), совокупное время 3,5 секунды в худшем случае. Этот механизм влияет только на запись, но не на чтение.
Шлюз не выдает никакого сигнала перезагрузки: он только ждет. Единственный настоящий триггер — это pg_notify('pgrst', 'reload schema'), выдаваемый службой базы данных по пути DDL. Если путь миграции забудет отправить этот сигнал, 8 попыток исчерпают кэш, который никогда не изменится, и этот риск задокументирован в комментарии к коду, а не скрыт.
Для локального развертывания PostgREST урок обобщается. Каждый путь DDL в вашем приложении должен инициировать перезагрузку процесса через NOTIFY или сигнал SIGUSR1. В противном случае миграция приводит к резкому увеличению задержки p99, замаскированному под периодические ошибки, сразу после развертывания.
Что срезает архитектура, а не чистая пропускная способность
Четыре ограничения, описанные здесь, имеют одну общую черту: ни одно из них не обнаруживается при изолированном тесте пропускной способности HTTP, однако все четыре определяют, масштабируется ли развертывание PostgREST до рабочей среды.
- Бюджет подключения: ограничивает количество одновременно активных клиентов в общем кластере независимо от пропускной способности каждого клиента.
- Стоимость точного COUNT: растет вместе с таблицей, а не с нагрузкой; обходится с помощью
planned/estimated. - Тихое усечение: правильно настроенное ограничение строки все равно может нарушить плохо инструментированную подкачку.
- Перезагрузка схемы: окно задержки после каждой миграции, ограниченное, если сигнал перезагрузки хорошо подключен, и неограниченное в противном случае.
Независимо от того, выбираете ли вы между самостоятельным размещением PostgREST, слоем GraphQL в стиле Hasura или пользовательским API, эти четыре оси являются лучшей точкой сравнения, чем изолированное число запросов/ов. См. наше сравнение PostgREST, Hasura и пользовательского API. Выбор пулера, который стоит перед вашей базой данных, имеет не меньшее значение: наше сравнение PgBouncer vs Supavisor vs PgCat подробно описывает, почему PostgREST не может проходить через пулер в режиме транзакций.
Часто задаваемые вопросы
Что следует помнить
PostgREST почти никогда не ломается под нагрузкой HTTP: его архитектура для этого слишком проста. Что ломается в производстве, так это то, что его окружает: сколько соединений поддерживают открытыми его реплики, сколько точная общая стоимость. Сюда также входит, остается ли усечение видимым и как долго длится окно после миграции.
Эти четыре ограничения не являются специфичными для Aurabase: они применимы к любому развертыванию PostgREST, как самостоятельному, так и управляемому. Этот код показывает, как мультитенантное развертывание делает их явными, а не оставляет их неожиданными в рабочей среде.