Python і Postgres: припиніть тягнути таблицю в пам’ять
SQL може бути правильним, а API все одно падає: безмежний .all(), запити через f-string, N+1 з ORM, нове з’єднання на запит, pandas як склад на веббоксі. Як я тримаю Python тонким навколо Postgres у 2026.
Стаття про SQL — це запит. Тут — Python, який відмовляється його скасувати. Я знову бачу ту саму аварію: чистий маршрут FastAPI, чиста модель SQLAlchemy і 200 МБ робочого набору, бо scalars().all() означало «усі». База зробила свою роботу. Застосунок попросив CSV складу.
Я вже написала п’ять SQL-форм замість циклу. Тут — конверт: параметри, пул, таймаут і дисципліна не збирати таблицю лише тому, що API датафрейма зручний.
1. Скрипт, який працював на 800 рядках
Локальний дамп, pandas, groupby, to_excel. Потім хтось наводить це на прод «лише для цього звіту». Це не звіт. Це витяг з основної бази в процес, який убиє OOM. Якщо відповідь — число, список із п’ятдесяти рядків або файл, який можна стримити, не збирайте таблицю. Агрегацію пишу в SQL — або materialized view, якщо це щодня — і лишаю Python конвертом: auth, параметри, формат.
2. f-string — це не API запитів
Збирати SELECT * FROM orders WHERE id = {order_id} через f-string — так ви даруєте базу. Беріть $1, %(id)s або прив’язані параметри. Вони є в кожного драйвера. У SQLAlchemy text() теж. LLM, який конкатенує SQL, — той самий баг із кращими манерами. Та сама родина: IN-списки з’єднанням рядків. Ставте ANY($1::int[]) або нормальний bind. Я не «екраную» собі вихід із цього.
3. N+1 з ORM, який виглядає як чистий Python
for invoice in invoices: invoice.customer.name — плюс один запит на рядок. selectinload, джойн або окремий запит для списку. «Пізніше додам лоадер» — так сторінка списку вмирає після виставки. ORM ставлю на записи, що збігаються з одиницею роботи: створити замовлення і рядки в одній сесії. SQL — для списків і експортів. Гібрид — не провал. ORM, який ховає чотириста запитів, — провал.
4. Одне з’єднання на запит — це не пул
Відкривати psycopg у кожному хендлері або створювати engine всередині функції — так ви впираєтесь у max_connections під час деплою. Один engine або один async-пул на процес. Таймаут на acquire. Checkout, який не живе через await до іншого сервісу. asyncpg або psycopg 3 на гарячому шляху, який контролюю. SQLAlchemy — якщо команда вже думає сесіями і потрібні міграції плюс identity map. Я не стартую три ORM, бо в уроці був Django.
5. Pandas — друга база: ставтесь відповідно
pandas groupby на кадрі в два гігабайти — складський запит на API-боксі. Якщо pandas потрібен, витяг уже має бути вузьким: колонки, які використовуєте, день, який потрібен, COPY або серверний курсор. chunksize існує. fetchmany існує. yield існує. pandas люблю в аналітичних ноутбуках. Не люблю як реалізацію /stats. Той маршрут — SQL плюс типізований dict.
6. Що я насправді віддаю в прод
Роутер FastAPI. Параметри перевірені — дати, id робочого простору, ліміт. Функція, яка виконує один параметризований запит, часто віконні форми з іншої статті. Пул. Statement timeout, щоб поганий план валив запит, а не інстанс. Без SELECT *. Без тихого .all() на таблиці без межі. Один запит — одна транзакція, доки немає названої причини: outbox, передача платежу. Ізоляція лишається READ COMMITTED, доки я не назву аномалію, якої боюсь.
Висновок: клей можна, другий склад — ні
Python — найкращий клей, який у мене є для HTTP і джоб. Він дорожчає, коли зображує Postgres. Напишіть запит, прив’яжіть параметри, стримте або агрегуйте і тримайте процес малим. Розумний цикл може почекати. У бази вже був кращий — це інша стаття.
Готові обговорити проєкт?
Я senior веброзробниця, спеціалізуюсь на React і Next.js - відкрита до фриланс-проєктів по всьому світу.
Місто
Київ, Україна
Upwork
Переглянути профільTelegram
Напишіть меніViber
Напишіть мені