Какие проблемы решает настройка PostgreSQL?
Представьте: мобильное приложение с 50 000 DAU, каждое открытие списка товаров генерирует 101 запрос к БД вместо одного — time out. Время отклика API — 5 секунд, пользователи уходят. Мы сталкивались с таким на проекте для крупного ретейлера. Решение — грамотная настройка PostgreSQL с правильными индексами и пагинацией. В итоге отклик снизился до 80 мс, а нагрузка на базу упала на 70%. Мобильное приложение не подключается к PostgreSQL напрямую: это антипаттерн из-за учётных данных в коде и уязвимости к SQL-инъекциям. Мы проектируем и настраиваем полноценный бэкенд: от схемы базы до деплоя с пулингом и real-time уведомлениями. За 5 лет оптимизировали PostgreSQL для 30+ мобильных проектов — обеспечиваем отклик API под 100 мс даже при 10 000 одновременно подключенных клиентов.
Как устранить N+1 запросы в API?
GET /api/products — 100 продуктов, каждый требует категорию. Без eager loading — 101 запрос вместо одного JOIN. Мобильный клиент ждёт 2 секунды. Решение — явная загрузка связанных данных. На Node.js с Prisma это выглядит так:
const products = await prisma.product.findMany({ where: { categoryId, isActive: true }, include: { category: { select: { id: true, name: true, slug: true } }, images: { take: 1, orderBy: { sortOrder: 'asc' } }, _count: { select: { reviews: true } } }, orderBy: { createdAt: 'desc' }, take: 20, skip: offset }) Почему медленные запросы без индексов?
SELECT по неиндексированному полю на 1 млн строк — sequential scan. В production это приводит к timeout. Добавляем индексы с учётом фильтров и сортировки. CONCURRENTLY — без блокировки таблицы, обязательно для production:
-- Индексы CREATE INDEX CONCURRENTLY idx_products_category_active ON products(category_id, is_active) WHERE is_active = true; CREATE INDEX CONCURRENTLY idx_orders_user_created ON orders(user_id, created_at DESC); CREATE INDEX idx_products_search ON products USING gin(to_tsvector('russian', title || ' ' || description)); -- Пагинация SELECT * FROM products WHERE (created_at, id) < ($last_created_at, $last_id) AND category_id = $category_id AND is_active = true ORDER BY created_at DESC, id DESC LIMIT 20; Почему пагинация на курсоре быстрее?
Offset pagination (LIMIT 20 OFFSET 200) всё равно читает 220 строк. Cursor-based пагинация использует условие (created_at, id), работающее за O(log N). На мобильном клиенте — бесконечный скролл через Paging 3 или UICollectionView DiffableDataSource, получающий cursor из ответа API.
| Тип пагинации | Производительность на 10⁶ строк | Стабильность при вставках |
|---|---|---|
| Offset | Деградирует, O(N) | Смещение может дублировать записи |
| Cursor | O(log N) | Стабильна, курсор не зависит от вставок |
Как настроить real-time обновления?
PostgreSQL LISTEN/NOTIFY + WebSocket/SSE на бэкенде → push на мобильный клиент. Используем для чатов, уведомлений, живых дашбордов. Мобильный клиент получает событие через WebSocket (Starscream, OkHttp) и обновляет локальный кэш (Room/SQLite).
Детали реализации:
// Бэкенд: подписка на NOTIFY const client = await pool.connect() await client.query('LISTEN product_updates') client.on('notification', (msg) => { const payload = JSON.parse(msg.payload) broadcastToSubscribers(payload.categoryId, payload) }) Триггер на PostgreSQL:
CREATE FUNCTION notify_product_change() RETURNS trigger AS $$ BEGIN PERFORM pg_notify('product_updates', json_build_object('id', NEW.id, 'categoryId', NEW.category_id)::text); RETURN NEW; END; $$ LANGUAGE plpgsql; Более подробно о команде LISTEN/NOTIFY читайте в официальной документации.
Connection pooling: зачем нужен PgBouncer
Мобильные приложения создают много коротких соединений. Без пула каждый запрос = новое подключение. PgBouncer в транзакционном режиме держит фиксированный пул и выдаёт соединение только на время транзакции. Типичная схема: Mobile clients → API servers (N экземпляров) → PgBouncer (25 connections) → PostgreSQL.
| Режим PgBouncer | Задержка | Использование ресурсов |
|---|---|---|
| Session | Высокая | Удерживает соединение на всю сессию |
| Transaction | Низкая | Выдаёт соединение только на транзакцию |
| Statement | Минимальная | Только на один запрос |
Пошаговая настройка PostgreSQL для мобильного приложения
- Проектирование схемы БД с учётом типичных запросов клиента.
- Создание индексов и материализованных представлений для частых фильтров.
- Реализация cursor-based пагинации и eager loading.
- Настройка PgBouncer и конфигурация пула для ORM.
- Деплой LISTEN/NOTIFY для real-time фич.
- Документация по эндпоинтам и схемам.
Пример конфигурации PgBouncer:
[databases] * = host=localhost port=5432 auth_user=admin [pgbouncer] listen_addr = 0.0.0.0:6432 pool_mode = transaction default_pool_size = 25 max_client_conn = 200 Что входит в работу
- Анализ текущей схемы и нагрузки.
- Проектирование оптимальной схемы с индексами.
- Реализация API с eager loading и cursor-пагинацией.
- Настройка PgBouncer и конфигурация пула.
- Внедрение real-time уведомлений через LISTEN/NOTIFY.
- Документация всех эндпоинтов и схем.
- Передача доступов и обучение команды.
- Поддержка в течение месяца после деплоя.
Процесс работы
Аналитика → Проектирование → Реализация → Тест → Деплой. Мы предоставляем промежуточные результаты и документацию. Сроки: от 1 до 2 недель в зависимости от сложности. Стоимость рассчитывается индивидуально. Закажите консультацию — мы оценим ваш проект.
Типичные ошибки при настройке PostgreSQL для мобильного приложения
- Неиспользование проекций: возврат
SELECT *вместо только нужных полей — увеличивает размер ответа в 10 раз. - Отсутствие мониторинга медленных запросов: без
pg_stat_statementsвы не увидите тормозные JOIN. - Игнорирование кэширования: частые запросы к одним данным без Redis или локального кэша создают избыточную нагрузку на БД.
Наш опыт более 5 лет и 30+ проектов гарантирует, что вы не допустите этих ошибок. Экономия на инфраструктуре может составить до 40%, а окупаемость инвестиций — 1–2 месяца. Обращайтесь за деталями — мы подготовим индивидуальное предложение. Свяжитесь с нами для консультации.







