[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-25-partitsionirovanie-postgresql-10m-strok":3},{"article":4,"author":55},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":24,"related_articles":35},"d0000000-0000-0000-0000-000000000225","a0000000-0000-0000-0000-000000000015","Deep EVM #25: Партиционирование таблиц PostgreSQL — когда таблица превышает 10M строк","deep-evm-25-partitsionirovanie-postgresql-10m-strok","Как и когда использовать партиционирование таблиц в PostgreSQL: стратегии range, list и hash partitioning для больших таблиц.","## Когда партиционирование становится необходимым\n\nPostgreSQL прекрасно работает с таблицами до нескольких миллионов строк. Но когда таблица транзакций блокчейна растёт до 10, 50, 100 миллионов записей, начинаются проблемы: VACUUM занимает часы, индексы раздуваются, запросы с фильтром по дате замедляются.\n\nПартиционирование — это разделение одной логической таблицы на несколько физических секций. PostgreSQL поддерживает нативное декларативное партиционирование с версии 10, и с каждой версией оно становится лучше.\n\n## Три стратегии партиционирования\n\n### Range Partitioning (по диапазону)\n\nСамая распространённая стратегия для временных данных:\n\n```sql\nCREATE TABLE transactions (\n    id UUID DEFAULT gen_random_uuid(),\n    block_number BIGINT NOT NULL,\n    tx_hash TEXT NOT NULL,\n    from_address TEXT NOT NULL,\n    to_address TEXT,\n    value NUMERIC NOT NULL,\n    gas_used BIGINT,\n    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n) PARTITION BY RANGE (created_at);\n\n-- Месячные партиции\nCREATE TABLE transactions_2024_01\n    PARTITION OF transactions\n    FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');\n\nCREATE TABLE transactions_2024_02\n    PARTITION OF transactions\n    FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');\n```\n\nPostgreSQL автоматически направляет INSERT в правильную партицию и при SELECT сканирует только релевантные партиции (partition pruning).\n\n### List Partitioning (по списку)\n\nДля данных с дискретными категориями:\n\n```sql\nCREATE TABLE events (\n    id UUID DEFAULT gen_random_uuid(),\n    chain TEXT NOT NULL,\n    event_type TEXT NOT NULL,\n    data JSONB,\n    created_at TIMESTAMPTZ DEFAULT NOW()\n) PARTITION BY LIST (chain);\n\nCREATE TABLE events_ethereum\n    PARTITION OF events FOR VALUES IN ('ethereum');\nCREATE TABLE events_polygon\n    PARTITION OF events FOR VALUES IN ('polygon');\nCREATE TABLE events_arbitrum\n    PARTITION OF events FOR VALUES IN ('arbitrum');\n```\n\n### Hash Partitioning (по хешу)\n\nДля равномерного распределения без естественного ключа:\n\n```sql\nCREATE TABLE address_balances (\n    address TEXT NOT NULL,\n    token TEXT NOT NULL,\n    balance NUMERIC NOT NULL,\n    updated_at TIMESTAMPTZ DEFAULT NOW()\n) PARTITION BY HASH (address);\n\nCREATE TABLE address_balances_0\n    PARTITION OF address_balances\n    FOR VALUES WITH (MODULUS 4, REMAINDER 0);\nCREATE TABLE address_balances_1\n    PARTITION OF address_balances\n    FOR VALUES WITH (MODULUS 4, REMAINDER 1);\nCREATE TABLE address_balances_2\n    PARTITION OF address_balances\n    FOR VALUES WITH (MODULUS 4, REMAINDER 2);\nCREATE TABLE address_balances_3\n    PARTITION OF address_balances\n    FOR VALUES WITH (MODULUS 4, REMAINDER 3);\n```\n\n## Автоматическое создание партиций\n\nВручную создавать партиции — ненадёжно. Используйте pg_partman:\n\n```sql\nCREATE EXTENSION pg_partman;\n\nSELECT partman.create_parent(\n    p_parent_table := 'public.transactions',\n    p_control := 'created_at',\n    p_type := 'native',\n    p_interval := '1 month',\n    p_premake := 3  -- Создавать на 3 месяца вперёд\n);\n```\n\npg_partman автоматически создаёт новые партиции и может удалять старые по retention policy.\n\n## Индексы на партиционированных таблицах\n\nИндексы создаются на родительской таблице и автоматически наследуются:\n\n```sql\nCREATE INDEX idx_transactions_hash\nON transactions (tx_hash);\n\nCREATE INDEX idx_transactions_from\nON transactions (from_address, created_at DESC);\n```\n\nPostgreSQL создаёт отдельный индекс для каждой партиции. Это означает, что каждый индекс меньше и эффективнее.\n\n### Уникальные ограничения\n\nУникальные индексы должны включать ключ партиционирования:\n\n```sql\n-- Это НЕ сработает:\nCREATE UNIQUE INDEX ON transactions (tx_hash);  -- ERROR\n\n-- Это сработает:\nCREATE UNIQUE INDEX ON transactions (tx_hash, created_at);\n```\n\n## Partition Pruning\n\nPartition pruning — это оптимизация, при которой PostgreSQL исключает ненужные партиции из плана запроса:\n\n```sql\n-- Сканирует ТОЛЬКО партицию за январь 2024\nSELECT * FROM transactions\nWHERE created_at >= '2024-01-01' AND created_at \u003C '2024-02-01';\n```\n\nПроверьте с EXPLAIN:\n\n```sql\nEXPLAIN SELECT * FROM transactions\nWHERE created_at >= '2024-01-01' AND created_at \u003C '2024-02-01';\n\n-- Append\n--   -> Seq Scan on transactions_2024_01  ← Только одна партиция!\n```\n\nВажно: partition pruning работает только с constants и параметризованными запросами. Выражения типа `WHERE created_at > NOW() - INTERVAL '30 days'` пруниться НЕ будут в старых версиях PostgreSQL.\n\n## Миграция существующей таблицы\n\nМиграция большой таблицы на партиционированную — нетривиальная задача:\n\n1. Создайте партиционированную таблицу с новым именем\n2. Создайте все необходимые партиции\n3. Копируйте данные batch'ами\n4. Переключите через RENAME в транзакции\n\n```sql\n-- 1. Новая партиционированная таблица\nCREATE TABLE transactions_partitioned (\n    LIKE transactions INCLUDING ALL\n) PARTITION BY RANGE (created_at);\n\n-- 2. Создать партиции...\n\n-- 3. Копирование батчами\nINSERT INTO transactions_partitioned\nSELECT * FROM transactions\nWHERE created_at >= '2024-01-01' AND created_at \u003C '2024-02-01';\n-- ... повторить для каждого месяца\n\n-- 4. Атомарное переключение\nBEGIN;\nALTER TABLE transactions RENAME TO transactions_old;\nALTER TABLE transactions_partitioned RENAME TO transactions;\nCOMMIT;\n```\n\n## Мониторинг партиций\n\nПолезные запросы для мониторинга:\n\n```sql\n-- Размер каждой партиции\nSELECT\n    schemaname || '.' || tablename AS partition,\n    pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS size,\n    n_live_tup AS rows\nFROM pg_stat_user_tables\nWHERE tablename LIKE 'transactions_%'\nORDER BY tablename;\n```\n\n## Когда НЕ нужно партиционирование\n\n- Таблица меньше 1M строк\n- Запросы не фильтруют по ключу партиционирования\n- Нужны сложные UNIQUE constraints\n- Нужны cross-partition foreign keys\n\n## Заключение\n\nПартиционирование PostgreSQL — мощный инструмент для управления большими таблицами. Range partitioning по дате — стандартный выбор для блокчейн-данных. Используйте pg_partman для автоматизации, следите за partition pruning через EXPLAIN, и помните: партиционирование решает проблемы масштабирования, но добавляет сложность. Применяйте его, когда таблица реально создаёт проблемы, а не заранее.","\u003Ch2 id=\"\">Когда партиционирование становится необходимым\u003C\u002Fh2>\n\u003Cp>PostgreSQL прекрасно работает с таблицами до нескольких миллионов строк. Но когда таблица транзакций блокчейна растёт до 10, 50, 100 миллионов записей, начинаются проблемы: VACUUM занимает часы, индексы раздуваются, запросы с фильтром по дате замедляются.\u003C\u002Fp>\n\u003Cp>Партиционирование — это разделение одной логической таблицы на несколько физических секций. PostgreSQL поддерживает нативное декларативное партиционирование с версии 10, и с каждой версией оно становится лучше.\u003C\u002Fp>\n\u003Ch2 id=\"\">Три стратегии партиционирования\u003C\u002Fh2>\n\u003Ch3>Range Partitioning (по диапазону)\u003C\u002Fh3>\n\u003Cp>Самая распространённая стратегия для временных данных:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE TABLE transactions (\n    id UUID DEFAULT gen_random_uuid(),\n    block_number BIGINT NOT NULL,\n    tx_hash TEXT NOT NULL,\n    from_address TEXT NOT NULL,\n    to_address TEXT,\n    value NUMERIC NOT NULL,\n    gas_used BIGINT,\n    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()\n) PARTITION BY RANGE (created_at);\n\n-- Месячные партиции\nCREATE TABLE transactions_2024_01\n    PARTITION OF transactions\n    FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');\n\nCREATE TABLE transactions_2024_02\n    PARTITION OF transactions\n    FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>PostgreSQL автоматически направляет INSERT в правильную партицию и при SELECT сканирует только релевантные партиции (partition pruning).\u003C\u002Fp>\n\u003Ch3>List Partitioning (по списку)\u003C\u002Fh3>\n\u003Cp>Для данных с дискретными категориями:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE TABLE events (\n    id UUID DEFAULT gen_random_uuid(),\n    chain TEXT NOT NULL,\n    event_type TEXT NOT NULL,\n    data JSONB,\n    created_at TIMESTAMPTZ DEFAULT NOW()\n) PARTITION BY LIST (chain);\n\nCREATE TABLE events_ethereum\n    PARTITION OF events FOR VALUES IN ('ethereum');\nCREATE TABLE events_polygon\n    PARTITION OF events FOR VALUES IN ('polygon');\nCREATE TABLE events_arbitrum\n    PARTITION OF events FOR VALUES IN ('arbitrum');\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch3>Hash Partitioning (по хешу)\u003C\u002Fh3>\n\u003Cp>Для равномерного распределения без естественного ключа:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE TABLE address_balances (\n    address TEXT NOT NULL,\n    token TEXT NOT NULL,\n    balance NUMERIC NOT NULL,\n    updated_at TIMESTAMPTZ DEFAULT NOW()\n) PARTITION BY HASH (address);\n\nCREATE TABLE address_balances_0\n    PARTITION OF address_balances\n    FOR VALUES WITH (MODULUS 4, REMAINDER 0);\nCREATE TABLE address_balances_1\n    PARTITION OF address_balances\n    FOR VALUES WITH (MODULUS 4, REMAINDER 1);\nCREATE TABLE address_balances_2\n    PARTITION OF address_balances\n    FOR VALUES WITH (MODULUS 4, REMAINDER 2);\nCREATE TABLE address_balances_3\n    PARTITION OF address_balances\n    FOR VALUES WITH (MODULUS 4, REMAINDER 3);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Автоматическое создание партиций\u003C\u002Fh2>\n\u003Cp>Вручную создавать партиции — ненадёжно. Используйте pg_partman:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE EXTENSION pg_partman;\n\nSELECT partman.create_parent(\n    p_parent_table := 'public.transactions',\n    p_control := 'created_at',\n    p_type := 'native',\n    p_interval := '1 month',\n    p_premake := 3  -- Создавать на 3 месяца вперёд\n);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>pg_partman автоматически создаёт новые партиции и может удалять старые по retention policy.\u003C\u002Fp>\n\u003Ch2 id=\"\">Индексы на партиционированных таблицах\u003C\u002Fh2>\n\u003Cp>Индексы создаются на родительской таблице и автоматически наследуются:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">CREATE INDEX idx_transactions_hash\nON transactions (tx_hash);\n\nCREATE INDEX idx_transactions_from\nON transactions (from_address, created_at DESC);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>PostgreSQL создаёт отдельный индекс для каждой партиции. Это означает, что каждый индекс меньше и эффективнее.\u003C\u002Fp>\n\u003Ch3>Уникальные ограничения\u003C\u002Fh3>\n\u003Cp>Уникальные индексы должны включать ключ партиционирования:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- Это НЕ сработает:\nCREATE UNIQUE INDEX ON transactions (tx_hash);  -- ERROR\n\n-- Это сработает:\nCREATE UNIQUE INDEX ON transactions (tx_hash, created_at);\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"partition-pruning\">Partition Pruning\u003C\u002Fh2>\n\u003Cp>Partition pruning — это оптимизация, при которой PostgreSQL исключает ненужные партиции из плана запроса:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- Сканирует ТОЛЬКО партицию за январь 2024\nSELECT * FROM transactions\nWHERE created_at &gt;= '2024-01-01' AND created_at &lt; '2024-02-01';\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Проверьте с EXPLAIN:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">EXPLAIN SELECT * FROM transactions\nWHERE created_at &gt;= '2024-01-01' AND created_at &lt; '2024-02-01';\n\n-- Append\n--   -&gt; Seq Scan on transactions_2024_01  ← Только одна партиция!\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Важно: partition pruning работает только с constants и параметризованными запросами. Выражения типа \u003Ccode>WHERE created_at &gt; NOW() - INTERVAL '30 days'\u003C\u002Fcode> пруниться НЕ будут в старых версиях PostgreSQL.\u003C\u002Fp>\n\u003Ch2 id=\"\">Миграция существующей таблицы\u003C\u002Fh2>\n\u003Cp>Миграция большой таблицы на партиционированную — нетривиальная задача:\u003C\u002Fp>\n\u003Col>\n\u003Cli>Создайте партиционированную таблицу с новым именем\u003C\u002Fli>\n\u003Cli>Создайте все необходимые партиции\u003C\u002Fli>\n\u003Cli>Копируйте данные batch’ами\u003C\u002Fli>\n\u003Cli>Переключите через RENAME в транзакции\u003C\u002Fli>\n\u003C\u002Fol>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- 1. Новая партиционированная таблица\nCREATE TABLE transactions_partitioned (\n    LIKE transactions INCLUDING ALL\n) PARTITION BY RANGE (created_at);\n\n-- 2. Создать партиции...\n\n-- 3. Копирование батчами\nINSERT INTO transactions_partitioned\nSELECT * FROM transactions\nWHERE created_at &gt;= '2024-01-01' AND created_at &lt; '2024-02-01';\n-- ... повторить для каждого месяца\n\n-- 4. Атомарное переключение\nBEGIN;\nALTER TABLE transactions RENAME TO transactions_old;\nALTER TABLE transactions_partitioned RENAME TO transactions;\nCOMMIT;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Мониторинг партиций\u003C\u002Fh2>\n\u003Cp>Полезные запросы для мониторинга:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- Размер каждой партиции\nSELECT\n    schemaname || '.' || tablename AS partition,\n    pg_size_pretty(pg_total_relation_size(schemaname || '.' || tablename)) AS size,\n    n_live_tup AS rows\nFROM pg_stat_user_tables\nWHERE tablename LIKE 'transactions_%'\nORDER BY tablename;\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"\">Когда НЕ нужно партиционирование\u003C\u002Fh2>\n\u003Cul>\n\u003Cli>Таблица меньше 1M строк\u003C\u002Fli>\n\u003Cli>Запросы не фильтруют по ключу партиционирования\u003C\u002Fli>\n\u003Cli>Нужны сложные UNIQUE constraints\u003C\u002Fli>\n\u003Cli>Нужны cross-partition foreign keys\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Ch2 id=\"\">Заключение\u003C\u002Fh2>\n\u003Cp>Партиционирование PostgreSQL — мощный инструмент для управления большими таблицами. Range partitioning по дате — стандартный выбор для блокчейн-данных. Используйте pg_partman для автоматизации, следите за partition pruning через EXPLAIN, и помните: партиционирование решает проблемы масштабирования, но добавляет сложность. Применяйте его, когда таблица реально создаёт проблемы, а не заранее.\u003C\u002Fp>\n","ru","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:23.960192Z","Партиционирование таблиц PostgreSQL — когда таблица превышает 10M строк","Range, list и hash партиционирование PostgreSQL: pg_partman, partition pruning и миграция больших таблиц.","партиционирование postgresql",null,"index, follow",[22,27,31],{"id":23,"name":24,"slug":25,"created_at":26},"c0000000-0000-0000-0000-000000000012","DevOps","devops","2026-03-28T10:44:21.513630Z",{"id":28,"name":29,"slug":30,"created_at":26},"c0000000-0000-0000-0000-000000000022","Performance","performance",{"id":32,"name":33,"slug":34,"created_at":26},"c0000000-0000-0000-0000-000000000005","PostgreSQL","postgresql",[36,43,49],{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":41,"published_at":42},"d0200000-0000-0000-0000-000000000013","Почему Бали становится хабом импакт-технологий Юго-Восточной Азии в 2026 году","pochemu-bali-stanovitsya-khabom-impakt-tekhnologiy-2026","Бали занимает 16-е место среди стартап-экосистем Юго-Восточной Азии. Растущая концентрация Web3-разработчиков, ИИ-стартапов в области устойчивого развития и компаний в сфере эко-тревел-технологий формирует нишу столицы импакт-технологий региона.","Инженерия","2026-03-28T10:44:37.953039Z",{"id":44,"title":45,"slug":46,"excerpt":47,"locale":12,"category_name":41,"published_at":48},"d0200000-0000-0000-0000-000000000012","Защита данных в ASEAN: чек-лист разработчика для мультистранового комплаенса","zashchita-dannykh-asean-chek-list-razrabotchika-komplaens","Семь стран ASEAN имеют собственные законы о защите данных с разными моделями согласия, требованиями к локализации и штрафами. Практический чек-лист для разработчиков мультистрановых приложений.","2026-03-28T10:44:37.944001Z",{"id":50,"title":51,"slug":52,"excerpt":53,"locale":12,"category_name":41,"published_at":54},"d0200000-0000-0000-0000-000000000011","Цифровая трансформация Индонезии на $29 миллиардов: возможности для софтверных компаний","tsifrovaya-transformatsiya-indonezii-29-milliardov-vozmozhnosti-dlya-kompaniy","Рынок IT-услуг Индонезии вырастет с $24,37 млрд в 2025 году до $29,03 млрд в 2026 году. Облачная инфраструктура, искусственный интеллект, электронная коммерция и дата-центры обеспечивают самый быстрый рост в Юго-Восточной Азии.","2026-03-28T10:44:37.917095Z",{"id":13,"name":56,"slug":57,"bio":58,"photo_url":19,"linkedin":19,"role":59,"created_at":60,"updated_at":60},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]