데이터베이스 마이그레이션에서 잠재적으로 위험하거나 파괴적인 작업을 감지하십시오.
패키지는 다음을 추가하여 설치할 수 있습니다. mix.exs 에서 의존성 목록에 :excellent_migrations :
def deps do
[
{ :excellent_migrations , "~> 0.1" , only: [ :dev , :test ] , runtime: false }
]
end 문서는 HEXDOC에서 제공됩니다.
이 도구는 마이그레이션 파일의 코드 (AST)를 분석합니다. 안전 보장을위한 구성 주석을 추가하는 경우를 제외하고 마이그레이션 파일에 추가 코드를 편집하거나 포함 할 필요가 없습니다.
우수한 마이그레이션과 통합하는 방법에는 여러 가지가 있습니다.
탁월한 마이그레이션은 Credo에 대한 사용자 지정, 즉시 사용 가능한 확인을 제공합니다.
.Credo.MigrationssAfety를 .credo 파일에 ExcellentMigrations.CredoCheck.MigrationsSafety 추가하십시오.
% {
configs: [
% {
# …
checks: [
# …
{ ExcellentMigrations.CredoCheck.MigrationsSafety , [ ] }
]
}
]
}예제 Credo 경고 :
Warnings - please take a look
┃
┃ [W] ↗ Raw SQL used
┃ apps/cookbook/priv/repo/migrations/20211024133700_create_recipes.exs:13 #(Cookbook.Repo.Migrations.CreateRecipes.up)
┃ [W] ↗ Index added not concurrently
┃ apps/cookbook/priv/repo/migrations/20211024133705_create_index_on_veggies.exs:37 #(Cookbook.Repo.Migrations.CreateIndexOnVeggies.up)
mix excellent_migrations.check_safety
이 믹스 작업은 마이그레이션을 분석하고 감지 된 각 위험에 대한 경고를 기록합니다.
mix excellent_migrations.migrate
이 작업을 실행하면 먼저 마이그레이션을 분석합니다. 위험이 감지되지 않으면 mix ecto.migrate 진행됩니다. 있으면 오류를 기록하고 중지합니다.
코드에서도 사용할 수도 있습니다. 이를 Code.string_to_quoted/2 File.read!/1 통해 마이그레이션 파일의 소스 코드 및 AST를 가져와야합니다. 그런 다음 ExcellentMigrations.DangersDetector.detect_dangers(ast) . 위험 유형과 감지 된 라인이 포함 된 키워드 목록을 반환합니다.
잠재적으로 위험한 운영 :
Postgres 특정 점검 :
모범 사례 :
특정 수표를 비활성화 할 수도 있습니다.
ecto가 여전히 응용 프로그램의 실행중인 인스턴스에서 열을 읽도록 구성된 경우 데이터를 스트러크에로드 할 때 쿼리가 실패합니다. 이는 다중 노드 배포에서 발생하거나 마이그레이션을 실행하기 전에 응용 프로그램을 시작한 경우에 발생할 수 있습니다.
나쁜
# Without a code change to the Ecto Schema
def change do
alter table ( "recipes" ) do
remove :no_longer_needed_column
end
end좋은 ✅
애플리케이션 코드가 먼저 업데이트되어 열에 대한 참조를 제거하여 더 이상로드하거나 쿼리하지 않으면 안전을 확보 할 수 있습니다. 그런 다음 열을 테이블에서 안전하게 제거 할 수 있습니다.
첫 번째 배포 :
# First deploy, in the Ecto schema
defmodule Cookbook.Recipe do
schema "recipes" do
- column :no_longer_needed_column, :text
end
end두 번째 배포 :
def change do
alter table ( "recipes" ) do
remove :no_longer_needed_column
end
end기본 값의 기본 값이있는 열을 기존 테이블에 추가하면 테이블을 다시 작성할 수 있습니다. 이 기간 동안, 읽기와 쓰기는 Postgres에서 차단되며, MySQL과 Mariadb에서 쓰기가 차단됩니다.
나쁜
참고 : 이것은 안전 해집니다.
def change do
alter table ( "recipes" ) do
add :favourite , :boolean , default: false
# This took 10 minutes for 100 million rows with no fkeys,
# Obtained an AccessExclusiveLock on the table, which blocks reads and
# writes.
end
end좋은 ✅
먼저 열을 추가 한 다음 기본값을 포함하도록 변경하십시오.
첫 번째 마이그레이션 :
def change do
alter table ( "recipes" ) do
add :favourite , :boolean
# This took 0.27 milliseconds for 100 million rows with no fkeys,
end
end두 번째 마이그레이션 :
def change do
alter table ( "recipes" ) do
modify :favourite , :boolean , default: false
# This took 0.28 milliseconds for 100 million rows with no fkeys,
end
end스키마 새 열을 읽으려면 스키마 변경 :
schema "recipes" do
+ field :favourite, :boolean, default: false
end 기본값이 휘발성 인 경우 (예 : clock_timestamp() , uuid_generate_v4() , random() ) 각 행은 ALTER TABLE 실행될 때 계산 된 값으로 업데이트되어야합니다.
나쁜
열에 휘발성 기본값 추가 :
def change do
alter table ( :recipes ) do
modify ( :identifier , :uuid , default: fragment ( "uuid_generate_v4()" ) )
end
end휘발성 기본값으로 열 추가 :
def change do
alter table ( :recipes ) do
add ( :identifier , :uuid , default: fragment ( "uuid_generate_v4()" ) )
end
end좋은 ✅
잠재적으로 긴 업데이트 작업을 피하기 위해, 특히 어쨌든 대부분의 비정 기준 값으로 열을 채우려면 다음보다 바람직 할 수 있습니다.
UPDATE 쿼리를 사용하여 올바른 값을 삽입하십시오또한 휘발성 기본값이있는 열로 새 테이블을 작성하는 것은 레코드가 포함되어 있지 않기 때문에 안전합니다.
Ecto는 각 마이그레이션에 대한 거래를 생성하고 동일한 트랜잭션에서 백필을 변경하여 테이블을 변경하여 백필 기간 동안 테이블을 잠그십시오. 또한 데이터를 업데이트하기 위해 단일 쿼리를 실행하면 큰 테이블에 대한 문제가 발생할 수 있습니다.
나쁜
defmodule Cookbook.BackfillRecipes do
use Ecto.Migration
import Ecto.Query
def change do
alter table ( "recipes" ) do
add :new_data , :text
end
flush ( )
Cookbook.Recipe
|> where ( new_data: nil )
|> Cookbook.Repo . update_all ( set: [ new_data: "some data" ] )
end
end좋은 ✅
안전한 백필을 수행하기위한 몇 가지 전략이 있습니다. 이 기사에서는 자세한 내용을 설명합니다.
열 유형을 변경하면 테이블이 다시 작성 될 수 있습니다. 이 기간 동안, 읽기와 쓰기는 Postgres에서 차단되며, MySQL과 Mariadb에서 쓰기가 차단됩니다.
나쁜
포스트 그레스에서 안전 :
MySQL/mariadb에서 안전 :
def change do
alter table ( "recipes" ) do
modify :my_column , :boolean , from: :text
end
end좋은 ✅
단계적 접근 방식 :
스스로에게 물어보십시오. " 정말 열의 이름을 바꿔야합니까?" 아마 그렇지는 않지만, 당신이해야한다면, 계속 읽고 시간과 노력이 필요하다는 것을 알아야합니다.
ecto가 응용 프로그램의 실행중인 인스턴스에서 열을 읽도록 구성되면 데이터를 스트러크에로드 할 때 쿼리가 실패합니다. 이는 다중 노드 배포에서 발생하거나 마이그레이션을 실행하기 전에 응용 프로그램을 시작한 경우에 발생할 수 있습니다.
바로 가기가 있습니다. 데이터베이스 열의 이름을 바꾸지 말고 대신 스키마의 필드 이름 이름을 바꾸고 데이터베이스 열을 가리 키도록 구성하십시오.
나쁜
# In your schema
schema "recipes" do
field :summary , :text
end
# In your migration
def change do
rename table ( "recipes" ) , :title , to: :summary
end마이그레이션 실행과 응용 프로그램 사이의 시간이 새 코드를 얻는 데 문제가 발생할 수 있습니다.
좋은 ✅
전략 1
스키마에서만 필드의 이름을 바꾸고 데이터베이스 열을 가리키고 데이터베이스 열을 동일하게 유지하도록 구성하십시오. 이전 필드 이름에 의존하는 모든 통화 코드가 새 필드 이름을 참조하도록 업데이트되었는지 확인하십시오.
defmodule Cookbook.Recipe do
use Ecto.Schema
schema "recipes" do
field :author , :string
field :preparation_minutes , :integer , source: :prep_min
end
end # # Update references in other parts of the codebase:
recipe = Repo.get(Recipe, "my_id")
- recipe.prep_min
+ recipe.preparation_minutes전략 2
단계적 접근 방식 :
"테이블의 이름을 바꿔야 하나요 ?"라고 스스로에게 물어보십시오. 아마 그렇지는 않지만, 당신이해야한다면, 계속 읽고 시간과 노력이 필요하다는 것을 알아야합니다.
ecto가 응용 프로그램의 실행중인 인스턴스에서 테이블을 읽도록 여전히 구성된 경우 데이터를 스트러크에로드 할 때 쿼리가 실패합니다. 이는 다중 노드 배포에서 발생하거나 마이그레이션을 실행하기 전에 응용 프로그램을 시작한 경우에 발생할 수 있습니다.
바로 가기가 있습니다 : 스키마의 이름 만 바꾸고 기본 데이터베이스 테이블 이름을 변경하지 마십시오.
나쁜
def change do
rename table ( "recipes" ) , to: table ( "dish_algorithms" )
end좋은 ✅
전략 1
스키마의 이름 만 바꾸고 모든 호출 코드를 바꾸고 테이블의 이름을 바꾸지 마십시오.
- defmodule Cookbook.Recipe do
+ defmodule Cookbook.DishAlgorithm do
use Ecto.Schema
schema "dish_algorithms" do
field :author, :string
field :preparation_minutes, :integer
end
end
# and in calling code:
- recipe = Cookbook.Repo.get(Cookbook.Recipe, "my_id")
+ dish_algorithm = Cookbook.Repo.get(Cookbook.DishAlgorithm, "my_id")전략 2
단계적 접근 방식 :
체크 제한 조건 블록을 추가하면 Postgres의 테이블에 읽고 씁니다. 모든 행이 확인되는 동안 MySQL/Mariadb에 블록이 씁니다.
나쁜
def change do
create constraint ( "ingredients" , :price_must_be_positive , check: "price > 0" )
# Creating the constraint with validate: true (the default when unspecified)
# will perform a full table scan and acquires a lock preventing updates
end좋은 ✅
두 가지 작업이 발생합니다.
이러한 명령이 동시에 발생하는 경우 전체 테이블을 확인하고 테이블을 완전히 스캔하므로 테이블의 잠금을 얻습니다. 이 전체 테이블 스캔을 피하기 위해 작업을 분리 할 수 있습니다.
한 번의 마이그레이션에서 :
def change do
create constraint ( "ingredients" , :price_must_be_positive , check: "price > 0" , validate: false )
# Setting validate: false will prevent a full table scan, and therefore
# commits immediately.
end다음 마이그레이션에서 :
def change do
execute "ALTER TABLE ingredients VALIDATE CONSTRAINT price_must_be_positive" , ""
# Acquires SHARE UPDATE EXCLUSIVE lock, which allows updates to continue
end이들은 동일한 배포에있을 수 있지만 2 개의 별도 마이그레이션이 있는지 확인합니다.
기존 열 블록에서 null을 설정하지 않으면 모든 행을 확인하는 동안 읽고 씁니다. 체크 제약 조건 시나리오 추가와 마찬가지로 두 가지 작업이 발생합니다.
전체 테이블 스캔을 피하기 위해이 두 작업을 분리 할 수 있습니다.
나쁜
def change do
alter table ( "recipes" ) do
modify :favourite , :boolean , null: false
end
end좋은 ✅
검사를 확인하지 않고 확인 제약 조건을 추가하고 백필 데이터를 작성하여 제약 조건을 만족시킨 다음 검증하십시오. 이것은 기능적으로 동일합니다.
첫 번째 마이그레이션에서 :
# Deployment 1
def change do
create constraint ( "recipes" , :favourite_not_null , check: "favourite IS NOT NULL" , validate: false )
end이렇게하면 모든 새 행에서 제약 조건이 적용되지만 해당 행이 업데이트 될 때까지 기존 행에 신경 쓰지 않습니다.
이 시점에서 제약 조건이 충족되도록 데이터 마이그레이션이 필요할 수 있습니다.
그런 다음 다음 배치의 마이그레이션에서 모든 행에 대한 제약 조건을 시행합니다.
# Deployment 2
def change do
execute "ALTER TABLE recipes VALIDATE CONSTRAINT favourite_not_null" , ""
endPostgres 12+를 사용하는 경우 제약 조건을 검증 한 후 Not Null을 열에 추가 할 수 있습니다. Postgres 12 문서에서 :
세트 NOT NULL이 표에만 적용될 수 있습니다. 표에있는 레코드 중 어느 것도 열에 대한 널 값이 포함되어 있지 않습니다. 일반적으로 이것은 전체 테이블을 스캔하여 변경 테이블 중에 확인됩니다. 그러나 유효한 점검 제약 조건이 발견되면 널이 존재할 수 없음을 입증하면 테이블 스캔이 건너 뜁니다.
# **Postgres 12+ only**
def change do
execute "ALTER TABLE recipes VALIDATE CONSTRAINT favourite_not_null" , ""
alter table ( "recipes" ) do
modify :favourite , :boolean , null: false
end
drop constraint ( "recipes" , :favourite_not_null )
end제약 조건이 실패하면 먼저 백필 데이터를 고려하여 원하는 데이터 무결성의 간격을 다루고 제약 조건을 검증하는 것을 다시 방문해야합니다.
우수한 마이그레이션은 원시 SQL 문의 안전을 보장 할 수 없습니다. 당신이하고있는 일이 안전한 지 확인한 다음 사용하십시오.
defmodule Cookbook.ExecuteRawSql do
# excellent_migrations:safety-assured-for-this-file raw_sql_executed
def change do
execute ( "..." )
end
end색인을 만들면 읽기와 쓰기가 모두 차단됩니다.
나쁜
def change do
create index ( "recipes" , [ :slug ] )
# This obtains a ShareLock on "recipes" which will block writes to the table
end좋은 ✅
Postgres를 사용하면 대신 읽기를 차단하지 않는 인덱스를 동시에 만듭니다. CONCURRENTLY 사용하려면 데이터베이스 트랜잭션을 비활성화해야하며, Ecto는 데이터베이스 트랜잭션을 통해 마이그레이션 잠금을 얻기 때문에 경쟁 노드가 동일한 마이그레이션 (예 : 스타트 업 전에 마이그레이션을 실행하는 다중 노드 Kubernetes 환경에서 동일한 마이그레이션을 실행하려고 시도 할 수도 있음을 의미합니다. 따라서 일부 노드는 여러 가지 이유로 시작에 실패합니다.
@ disable_ddl_transaction true
@ disable_migration_lock true
def change do
create index ( "recipes" , [ :slug ] , concurrently: true )
end마이그레이션은 여전히 실행하는 데 시간이 걸릴 수 있지만 행에 대한 읽기와 업데이트는 계속 작동합니다. 예를 들어, 10 만 행의 경우 마이그레이션을 추가하는 데 165 초가 걸렸지 만 실행 중에 선택 및 업데이트가 발생할 수 있습니다.
동일한 마이그레이션에 다른 변화가 없습니다 . 인덱스 만 동시에 만 만들고 다른 변경을 나중에 마이그레이션으로 분리하십시오.
동시 인덱스는 @disable_ddl_transaction 과 @disable_migration_lock 모두 true로 설정해야합니다. 자세히보기 :
나쁜
defmodule Cookbook.AddIndex do
def change do
create index ( :recipes , [ :cookbook_id , :cuisine ] , concurrently: true )
end
end좋은 ✅
defmodule Cookbook.AddIndex do
@ disable_ddl_transaction true
@ disable_migration_lock true
def change do
create index ( :recipes , [ :cookbook_id , :cuisine ] , concurrently: true )
end
end외국 키 블록을 추가하면 두 테이블에 기록됩니다.
나쁜
def change do
alter table ( "recipes" ) do
add :cookbook_id , references ( "cookbooks" )
end
end좋은 ✅
첫 번째 마이그레이션에서
def change do
alter table ( "recipes" ) do
add :cookbook_id , references ( "cookbooks" , validate: false )
end
end두 번째 마이그레이션에서
def change do
execute "ALTER TABLE recipes VALIDATE CONSTRAINT cookbook_id_fkey" , ""
end이러한 마이그레이션은 동일한 배포에있을 수 있지만 별도의 마이그레이션인지 확인하십시오.
json 열 추가Postgres에는 JSON 열 유형에 대한 평등 연산자가 없으므로 응용 프로그램에서 기존 선택된 고유 한 쿼리에 오류가 발생할 수 있습니다.
나쁜
def change do
alter table ( "recipes" ) do
add :extra_data , :json
end
end좋은 ✅
대신 JSONB를 사용하십시오. 어떤 사람들은 "JSON"과 같지만 " B Etter"라고 말합니다.
def change do
alter table ( "recipes" ) do
add :extra_data , :jsonb
end
end나쁜
3 개가 넘는 열이없는 비 유니 인덱스를 추가하면 성능이 거의 향상되지 않습니다.
defmodule Cookbook.AddIndexOnIngredients do
def change do
create index ( :recipes , [ :a , :b , :c , :d ] , concurrently: true )
end
end좋은 ✅
대신 결과를 가장 좁히는 열이있는 색인을 시작하십시오.
defmodule Cookbook.AddIndexOnIngredients do
def change do
create index ( :recipes , [ :b , :d ] , concurrently: true )
end
endPostgres의 경우 동시에 추가하십시오.
안전한 사용 구성 주석으로 마이그레이션에서 작업을 표시합니다. 분석 중에 무시됩니다.
사용 가능한 두 가지 구성 주석이 있습니다.
excellent_migrations:safety-assured-for-next-line <operation_type>excellent_migrations:safety-assured-for-this-file <operation_type>주어진 라인에 대한 검사 무시 :
defmodule Cookbook.AddTypeToRecipesWithDefault do
def change do
alter table ( :recipes ) do
# excellent_migrations:safety-assured-for-next-line column_added_with_default
add ( :type , :string , default: "dessert" )
end
end
end전체 파일 검사 무시 :
defmodule Cookbook.AddTypeToRecipesWithDefault do
# excellent_migrations:safety-assured-for-this-file column_added_with_default
def change do
alter table ( :recipes ) do
add ( :type , :string , default: "dessert" )
end
end
end가능한 작동 유형은 다음과 같습니다.
check_constraint_addedcolumn_added_with_defaultcolumn_reference_addedcolumn_removedcolumn_renamedcolumn_type_changedcolumn_volatile_defaultindex_concurrently_without_disable_ddl_transactionindex_concurrently_without_disable_migration_lockindex_not_concurrentlyjson_column_addedmany_columns_indexnot_null_addedoperation_deleteoperation_insertoperation_updateraw_sql_executedtable_droppedtable_renamed모든 마이그레이션 검사에 대한 특정 위험을 무시하십시오.
config :excellent_migrations , skip_checks: [ :raw_sql_executed , :not_null_added ] 이 패키지를 추가하기 전에 생성 된 마이그레이션 분석을 건너 뛰려면 구성에서 start_after 의 마지막 마이그레이션에서 타임 스탬프를 설정하십시오.
config :excellent_migrations , start_after: "20191026080101" 모든 사람들 이이 프로젝트를 개선하는 데 도움이되고 환영합니다. 다음은 도움을 줄 수있는 몇 가지 방법입니다.
저작권 (C) 2021 Artur Sulej
이 작업은 무료입니다. MIT 라이센스의 조건에 따라 재분배 및/또는 수정할 수 있습니다. 자세한 내용은 License.md 파일을 참조하십시오.