Sobes.tech
Senior

Hoe voeg je op een naadloze manier een nieuw veld toe aan een tabel met een miljard records zonder downtime?

sobes.tech AI

Antwoord van 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)
    // }
}

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