Как максимально бесшовно добавить новое поле в таблицу с миллиардом записей без даунтайма?
Answer from AI
sobes.tech AI
Чтобы добавить новое поле в таблицу с миллиардом записей без даунтайма, нужно использовать стратегию, минимизирующую блокировки и нагрузку на базу данных. Обычно это делается в несколько этапов:
-
Добавление поля с дефолтным значением NULL — это обычно быстрая операция, так как не требует обновления всех строк.
-
Постепенное заполнение поля значениями — с помощью фонового процесса или батчевых обновлений, чтобы не создавать пиковую нагрузку.
-
Обновление приложения для поддержки нового поля — сначала читать старое состояние, затем использовать новое поле, если оно заполнено.
-
Если нужно, сменить поле на 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)
// }
}
Такой подход позволяет избежать длительных блокировок и даунтайма.