Sobes.tech
Back to questions
Senior
10

Как максимально бесшовно добавить новое поле в таблицу с миллиардом записей без даунтайма?

Companies where asked
Lenta Tech

Answer from AI

sobes.tech AI

Чтобы добавить новое поле в таблицу с миллиардом записей без даунтайма, нужно использовать стратегию, минимизирующую блокировки и нагрузку на базу данных. Обычно это делается в несколько этапов:

  1. Добавление поля с дефолтным значением NULL — это обычно быстрая операция, так как не требует обновления всех строк.

  2. Постепенное заполнение поля значениями — с помощью фонового процесса или батчевых обновлений, чтобы не создавать пиковую нагрузку.

  3. Обновление приложения для поддержки нового поля — сначала читать старое состояние, затем использовать новое поле, если оно заполнено.

  4. Если нужно, сменить поле на NOT NULL с дефолтным значением — делать это после того, как все записи обновлены.

В PostgreSQL, например, добавление поля с дефолтным значением, отличным от NULL, может блокировать таблицу, поэтому лучше сначала добавить поле с NULL, затем обновлять данные.

Пример на Go, который запускает добавление поля и обновление батчами:

package main

import (
    "database/sql"
    _ "github.com/lib/pq"
    "log"
)

func main() {
    db, err := sql.Open("postgres", "user=postgres dbname=mydb sslmode=disable")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Добавляем поле с NULL
    _, err = db.Exec("ALTER TABLE big_table ADD COLUMN new_field TEXT NULL")
    if err != nil {
        log.Fatal(err)
    }

    // Обновляем записи батчами
    batchSize := 10000
    offset := 0
    for {
        res, err := db.Exec(`
            UPDATE big_table
            SET new_field = 'default_value'
            WHERE id IN (
                SELECT id FROM big_table ORDER BY id LIMIT $1 OFFSET $2
            )`, batchSize, offset)
        if err != nil {
            log.Fatal(err)
        }
        rowsAffected, _ := res.RowsAffected()
        if rowsAffected == 0 {
            break
        }
        offset += batchSize
    }

    // После обновления можно добавить ограничение NOT NULL, если нужно
    // _, err = db.Exec("ALTER TABLE big_table ALTER COLUMN new_field SET NOT NULL")
    // if err != nil {
    //     log.Fatal(err)
    // }
}

Такой подход позволяет избежать длительных блокировок и даунтайма.