Skip to main content
Django — это веб-фреймворк на Python со встроенной ORM и системой миграций, который работает с PostgreSQL через драйвер psycopg 3. В этом руководстве вы подключите новый проект Django к ClickHouse Managed Postgres через PgBouncer, создадите таблицу todos с помощью миграции и реализуете обработку запросов на создание, чтение, обновление и удаление в небольшом JSON-представлении. Все соединения защищены TLS с полной проверкой сертификатов.

Предварительные требования

  • Python 3.12 или новее (этого требует Django 6). Руководство проверено на Python 3.13, Django 6.1.1 и psycopg 3.3.6.
  • Аккаунт ClickHouse Cloud
  • psql для создания базы данных. Также можно выполнить оператор CREATE DATABASE в консоли SQL.

Создание сервиса ClickHouse Managed Postgres

В консоли ClickHouse Cloud нажмите New service и выберите Postgres. Инстанс будет готов через несколько минут. Пошаговые инструкции приведены в кратком руководстве по началу работы.

Получение сведений о подключении

Нажмите Connect на левой боковой панели вашего сервиса. В открывшемся модальном окне отображаются имя пользователя, пароль, сервер и порт, а также переключатель Directly / via PgBouncer. В этом руководстве Django подключается через встроенный PgBouncer на порту 6432. В продакшне приложение Django обычно работает в виде нескольких воркер-процессов на нескольких машинах, и каждый из них открывает собственные соединения. PgBouncer работает в режиме пулинга транзакций (transaction pooling) и позволяет всем этим процессам совместно использовать небольшое число соединений с Postgres. Для работы с пулингом транзакций Django нужно несколько настроек — они приведены и описаны в разделе Настройки PgBouncer для Django. Выберите via PgBouncer, чтобы увидеть сведения о подключении через пул. Порт изменится на 6432, а строка подключения будет содержать sslmode=verify-full. В модальном окне нажмите Download CA‑сертификат. Сертификат также можно скачать в разделе Settings → CA‑сертификат. Он уникален для вашего инстанса, поэтому драйвер может с его помощью убедиться, что подключается именно к вашему серверу.

Настройка проекта

Создайте каталог проекта с виртуальным окружением, установите Django и psycopg 3, а затем создайте проект Django с приложением todos:
psycopg[binary] включает актуальную версию libpq, поэтому устанавливать PostgreSQL локально не нужно.
Добавляете в существующее приложение?Пропустите приведённые выше команды. В виртуальном окружении проекта выполните pip install "psycopg[binary]", затем перейдите к разделу Настройка соединения, где параметр DATABASES для SQLite заменяется новым. Чтобы перенести данные из SQLite:
  • Перед изменением DATABASES выполните python manage.py dumpdata --natural-foreign --natural-primary -e contenttypes -e auth.permission -o data.json.
  • После выполнения python manage.py migrate запустите python manage.py loaddata data.json.

Настройка соединения

Переместите загруженный CA‑сертификат в каталог проекта (рядом с manage.py) и переименуйте его в ca-certificate.pem. Создайте базу данных для приложения. Замените <PASSWORD> и хост значениями из модального окна Connect:
Задайте хост и пароль через переменные окружения, чтобы не хранить их в коде:
Откройте mysite/settings.py, добавьте import os после строки from pathlib import Path и замените параметр DATABASES следующим кодом:
mysite/settings.py
Django передаёт ключи из OPTIONS в psycopg, а тот — в libpq. Если для sslmode задано значение verify-full, libpq проверяет, что сертификат сервера подписан CA вашего сервиса и соответствует имени хоста. BASE_DIR делает путь к сертификату абсолютным, поэтому настройка работает независимо от того, из какого каталога запускается Django.
Если sslrootcert указывает на неверный CA, установить соединение не удастся: возникнет ошибка SSL error: certificate verify failed. Без sslrootcert возникает ошибка root certificate file ".../.postgresql/root.crt" does not exist, поскольку libpq в этом случае ищет CA в расположении по умолчанию. Если подключаться по IP-адресу, а не по имени хоста, возникает ошибка server certificate for "..." does not match host name.
Если ваше приложение считывает DATABASE_URL с помощью dj-database-url, эта библиотека передаёт sslmode и sslrootcert из строки запроса URL в OPTIONS. Чтобы получить те же настройки, вызовите dj_database_url.config(conn_max_age=600, conn_health_checks=True, disable_server_side_cursors=True). Относительный путь sslrootcert в URL отсчитывается от каталога, из которого запускается Django.

Создайте модель и выполните миграции

Добавьте приложение todos в INSTALLED_APPS в файле mysite/settings.py:
mysite/settings.py
Определите модель Todo в файле todos/models.py:
todos/models.py
Создайте миграцию для модели и примените все миграции:
Помимо todos_todo, команда migrate создаёт таблицы для встроенных приложений Django, таких как auth_user и django_session, а также записывает применённые миграции в django_migrations. Если у вас уже есть приложение, пропустите создание модели todos и выполните только python manage.py migrate — эта команда создаст все таблицы вашего приложения в новой базе данных. migrate не захватывает рекомендательную блокировку (advisory lock) уровня сеанса, поэтому работает через PgBouncer. При этом она не защищает от одновременных запусков, поэтому выполняйте миграции только из одного места, например на отдельном шаге развёртывания.

Добавление конечной точки JSON

Замените содержимое todos/views.py двумя представлениями, которые создают, выводят, обновляют и удаляют задачи с помощью Django ORM:
todos/views.py
Замените содержимое mysite/urls.py, чтобы маршрутизировать /todos и /todos/<id> в представления:
mysite/urls.py

Запуск и проверка

Запустите сервер для разработки:
Во втором терминале создайте две задачи, отметьте первую как выполненную и удалите вторую:
Запрос PATCH возвращает обновлённую строку:
Откройте в браузере http://localhost:3111/todos, чтобы просмотреть список оставшихся задач. Ответ будет выглядеть так:
Чтобы просмотреть данные в консоли, откройте консоль SQL на левой боковой панели вашего сервиса, разверните guide_django, затем public и выберите таблицу todos_todo. В таблице осталась одна строка:

Настройки PgBouncer для Django

PgBouncer работает в режиме пулинга транзакций (пулинг транзакций): каждая транзакция или каждый оператор вне транзакции может выполняться на другом соединении с Postgres. Ниже описано, как это влияет на приведённые выше настройки и другие возможности Django:
  • DISABLE_SERVER_SIDE_CURSORS: QuerySet.iterator читает строки фрагментами через серверный курсор. Вне транзакции Django объявляет курсор как WITH HOLD и получает каждый фрагмент отдельно, поэтому при работе через PgBouncer очередной запрос фрагмента может попасть на соединение с Postgres, в котором этого курсора нет, и завершиться ошибкой cursor "_django_curs_..." does not exist. Это происходит, только когда пулом одновременно пользуются другие клиенты, поэтому при разработке проблема часто не проявляется. Если настройка включена, .iterator() использует обычный курсор, и psycopg загружает весь результат в память. Чтобы потоково обработать очень большую таблицу, выполняйте такой код через прямое соединение на порту 5432 с отключённой настройкой.
  • CONN_MAX_AGE: по умолчанию (0) Django открывает новое соединение для каждого запроса. Установка TLS‑соединения требует нескольких сетевых обменов — как через PgBouncer, так и напрямую, — поэтому повторное использование соединений значительно ускоряет обработку запросов. В тесте с ноутбука с четырьмя воркерами Gunicorn значение CONN_MAX_AGE, равное 600, сократило медианное время запроса примерно с 670 мс до 95 мс. При работе через PgBouncer открытое соединение Django не удерживает соединение с Postgres между транзакциями, поэтому постоянные соединения не расходуют лимит max_connections. CONN_HEALTH_CHECKS заставляет Django проверять повторно используемое соединение в начале каждого запроса и переподключаться, если оно было закрыто.
  • Пул соединений: встроенный пул Django ("pool": True в OPTIONS, для которого требуется pip install "psycopg[binary,pool]") даёт сопоставимое ускорение и также работает через PgBouncer. Он требует значения CONN_MAX_AGE, равного 0, и держит открытыми не менее четырёх соединений на процесс. С PgBouncer большинству приложений достаточно CONN_MAX_AGE; пул имеет смысл рассмотреть для многопоточных или ASGI‑серверов.
  • Подготовленные операторы: настройка не требуется. По умолчанию Django устанавливает параметр psycopg prepare_threshold в None и привязывает параметры запроса на стороне клиента, поэтому серверные подготовленные операторы не создаются. Если включить "server_side_binding": True и задать "prepare_threshold" в OPTIONS, подготовленные операторы также будут работать через PgBouncer, поскольку psycopg[binary] поставляется с libpq 18 и освобождает операторы с помощью сообщения протокола Close. Если psycopg собран с libpq версии 16 или более ранней, освобождение оператора внутри транзакции завершается ошибкой prepared statement "_pg3_0" does not exist. Чтобы проверить версию, выполните python -c "import psycopg; print(psycopg.pq.version())". Требуется 170000 или выше.
  • Состояние сеанса: оператор SET, например SET search_path, применяется только к соединению с Postgres, в котором он был выполнен, поэтому последующие запросы могут его не увидеть, а запросы других клиентов — наоборот, увидеть. PgBouncer отклоняет настройку Django "options": "-c search_path=..." с ошибкой unsupported startup parameter in options: search_path. Вместо этого задавайте значения по умолчанию на уровне базы данных, например с помощью ALTER DATABASE guide_django SET search_path = myschema, public;. Не используйте параметр assume_role, который выполняет SET ROLE. Встроенная обработка TIME_ZONE в Django и параметр isolation_level работают через PgBouncer.
Чтобы подключаться напрямую, установите PORT в 5432 и удалите DISABLE_SERVER_SIDE_CURSORS. В этом случае каждый воркер Django удерживает собственное соединение с Postgres, поэтому общее число воркеров должно быть меньше max_connections.

Дальнейшие шаги