TGViewer
About Python [ru] About Python [ru] @python_tesst · 6.45K subscribers
Post #2776 368
⁣ORM-баттл: массовая вставка и обновление в PostgreSQL

Массовая вставка 100K записей в production — частый сценарий ETL-пайплайнов и миграций. Многие выбирают ORM ради удобства, но забывают про overhead сериализации. Я сравнил три async ORM в условиях zero-overhead — минимум преобразований типов, никаких моделей, только сырые dict'ы.

Методология
Стек: PostgreSQL 15, Python 3.11, asyncio. Каждая ORM получает готовые dict'ы. Вставка через bulk_insert, обновление — bulk_update или аналог. Тесты на 100K записей, замеры времени и памяти.

Результаты: время (сек) и память (MB)
* SQLAlchemy async: вставка 2.3с, обновление 3.1с, память ~45 MB
* Tortoise-ORM: вставка 4.1с, обновление 6.7с, память ~82 MB
* GINO: вставка 5.8с, обновление 7.2с, память ~95 MB

SQLAlchemy async лидирует. При использовании insert().returning() с bulk-операциями и отключённым автокоммитом overhead минимален. Core-level доступ к данным позволяет обойти лишние сериализации.

Почему Tortoise отстаёт
Обязательная валидация полей модели при каждом bulk-вызове. Даже с bulk_create(batch_size=500) каждый объект проходит через __init__ модели. Нет прямого доступа к сырым dict'ам без конвертации. Совет: если нужна простота, используйте Tortoise, но для high-throughput лучше перейти на raw SQL.

GINO — самый медленный
Архитектура на SQLAlchemy 1.x. Отсутствие нормального bulk-update вынуждает писать raw SQL. Overhead от asyncpg-шных prepared statements. Типичная ошибка: считать GINO "легковесным" — он legacy, я не рекомендую.

Production-oriented пример: zero-overhead вставка
from sqlalchemy.ext.asyncio import create_async_engine
from sqlalchemy import text

async def bulk_insert_fast(data: list[dict]):
engine = create_async_engine("postgresql+asyncpg://...")
async with engine.begin() as conn:
await conn.execute(
text("""
INSERT INTO users (name, email, created_at)
SELECT unnest(:names::text[]),
unnest(:emails::text[]),
unnest(:created_ats::timestamptz[])
"""),
{
"names": [d["name"] for d in data],
"emails": [d["email"] for d in data],
"created_ats": [d["created_at"] for d in data]
}
)

unnest + массивы — реальный zero-overhead. Модели не загружаются, сериализация не происходит, всё на уровне raw SQL. Для обновления используйте UPDATE ... FROM с массивами.

Вывод: Для высоконагруженных пайплайнов на PostgreSQL SQLAlchemy async — лучший выбор из-за минимального overhead и гибкости core-уровня, тогда как Tortoire удобна в простых проектах, а GINO стоит избегать.
  • 👎 1
More from @python_tesst
  1. Sep 28, 2026Post #3191
  2. Sep 27, 2026OpenAI метит в подписку за 500 баксов Что там в описании тарифа? Пока что от ChatGPT Pro о…
  3. Sep 27, 2026Post #3189
  4. Sep 27, 2026Свежая обложка The Economist подъехала Журналисты: да мы вообще не сгущаем краски Те же жу…
  5. Sep 27, 2026Ночная годнота: Docker выкатила первые официальные скиллы для ИИ-агентов, которые ковыряют…
  6. Sep 26, 2026Claude Code больше не будет рубить всё на полуслове, если 5-часовой лимит прилетит прямо п…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →