Computer >> คอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Ruby

ผ่าแยกรางการอพยพ

ในโพสต์ของวันนี้ เราจะเจาะลึกการโยกย้าย Rails เราจะแบ่งการโยกย้ายออกเป็นส่วนๆ และในกระบวนการนี้ คุณจะได้เรียนรู้วิธีการเขียนการย้ายถิ่นอย่างมีประสิทธิภาพ เราจะเรียนรู้วิธีเขียนการย้ายข้อมูลสำหรับหลายฐานข้อมูล ตลอดจนวิธีจัดการกับการย้ายที่ล้มเหลวและเทคนิคการย้อนกลับ

เพื่อให้เข้าใจโพสต์ทั้งหมด คุณจะต้องมีความเข้าใจพื้นฐานเกี่ยวกับฐานข้อมูลและ Rails

การย้ายถิ่น 101

การย้ายข้อมูลใน Rails ช่วยให้เราสามารถพัฒนาฐานข้อมูลตลอดอายุของแอปพลิเคชัน การย้ายข้อมูลช่วยให้เราสามารถเขียนโค้ด Ruby ธรรมดาเพื่อเปลี่ยนสถานะของฐานข้อมูลโดยจัดเตรียม DSL ที่สง่างาม เราไม่จำเป็นต้องเขียน SQL เฉพาะฐานข้อมูล เนื่องจากการย้ายข้อมูลให้ abstractions เพื่อจัดการฐานข้อมูลและดูแลรายละเอียดที่สำคัญของการแปลง DSL เป็นการสืบค้น SQL เฉพาะฐานข้อมูลเบื้องหลัง การย้ายข้อมูลยังหลีกทางให้กับเราและให้วิธีการดำเนินการ SQL แบบดิบบนฐานข้อมูล หากมีความจำเป็นดังกล่าวเกิดขึ้น

สองหมื่นลีกสู่การย้ายฐานข้อมูล Rails

เราสามารถสร้างตาราง เพิ่มหรือลบคอลัมน์ และเพิ่มดัชนีในคอลัมน์โดยใช้การย้ายข้อมูล

ทุกแอป Rails มีไดเร็กทอรีพิเศษ—db/migrate —ที่จัดเก็บการโยกย้ายทั้งหมด

เริ่มจากการย้ายข้อมูลที่สร้างตาราง events ลงในฐานข้อมูลของเรา

$ rails g migration CreateEvents category:string

คำสั่งนี้สร้างไฟล์ประทับเวลา 20200405103635_create_events.rb ใน db/migrate ไดเรกทอรี เนื้อหาของไฟล์มีดังนี้

class CreateEvents < ActiveRecord::Migration[6.0]
  def change
    create_table :events do |t|
      t.string :category
 
      t.timestamps
    end
  end
end

มาแยกย่อยไฟล์การโยกย้ายนี้กันเถอะ

  • ทุกไฟล์การโยกย้ายที่ Rails สร้างมีการประทับเวลาที่มีอยู่ในชื่อไฟล์ การประทับเวลานี้มีความสำคัญและ Rails ใช้เพื่อยืนยันว่ามีการย้ายข้อมูลหรือไม่ ดังที่เราเห็นในภายหลัง
  • การย้ายข้อมูลมีคลาสที่สืบทอดมาจาก ActiveRecord::Migration[6.0] . ขณะที่ฉันใช้ Rails 6 ซูเปอร์คลาสการโยกย้ายมี [6.0] . ถ้าฉันใช้ Rails 5.2 อยู่ ซูเปอร์คลาสจะเป็น ActiveRecord::Migration[5.2] . ต่อมา เราจะพูดถึงสาเหตุที่เวอร์ชัน Rails เป็นส่วนหนึ่งของชื่อซูเปอร์คลาส
  • การโยกย้ายมีวิธีการ change ซึ่งมีรหัส DSL ที่จัดการฐานข้อมูล ในกรณีนี้ change เมธอดกำลังสร้าง events ตารางที่มีคอลัมน์ category ประเภท string .
  • การย้ายข้อมูลใช้รหัส t.timestamps เพื่อเพิ่มการประทับเวลา created_at และ updated_at ไปที่ events ตาราง.

เมื่อย้ายข้อมูลนี้โดยใช้ rails db:migrate คำสั่งจะสร้าง events ตารางที่มี category คอลัมน์ประเภท string และคอลัมน์ประทับเวลา created_at และ updated_at .

ประเภทคอลัมน์ฐานข้อมูลจริงจะเป็น varchar หรือข้อความ ขึ้นอยู่กับฐานข้อมูล

ความสำคัญของการประทับเวลาการย้ายข้อมูลและตาราง schema_migration

ทุกครั้งที่มีการสร้างการย้ายข้อมูลโดยใช้ rails g migration คำสั่ง Rails สร้างไฟล์การโยกย้ายด้วยการประทับเวลาที่ไม่ซ้ำกัน การประทับเวลาอยู่ในรูปแบบ YYYYMMDDHHMMSS .เมื่อใดก็ตามที่มีการเรียกใช้การย้ายข้อมูล Rails จะแทรกการประทับเวลาการย้ายข้อมูลลงในตารางภายใน schema_migrations . Rails สร้างตารางนี้เมื่อเราเรียกใช้การย้ายข้อมูลครั้งแรก ตารางมีเฉพาะคอลัมน์ version ซึ่งเป็นคีย์หลักด้วย นี่คือโครงสร้างของ schema_migrations ตาราง

CREATE TABLE IF NOT EXISTS "schema_migrations" ("version" varchar NOT NULL PRIMARY KEY);

ตอนนี้เราได้เรียกใช้การย้ายข้อมูลเพื่อสร้าง events มาดูกันว่า Rails ได้เก็บบันทึกเวลาของการย้ายข้อมูลนี้ไว้ใน schema_migrations หรือไม่ ตาราง

sqlite> select * from schema_migrations;
20200405103635

ถ้าเราเรียกใช้การโยกย้ายอีกครั้ง Rails จะตรวจสอบก่อนว่ามีรายการอยู่ใน schema_migrations ตารางที่มีการประทับเวลาของไฟล์การโยกย้าย และดำเนินการก็ต่อเมื่อไม่มีรายการดังกล่าว เพื่อให้แน่ใจว่าเราสามารถเพิ่มการเปลี่ยนแปลงในฐานข้อมูลได้เรื่อยๆ และการย้ายจะทำงานเพียงครั้งเดียวในฐานข้อมูล

สคีมาฐานข้อมูล

เมื่อเราเรียกใช้การย้ายข้อมูลมากขึ้นเรื่อยๆ สคีมาฐานข้อมูลก็พัฒนาขึ้นเรื่อยๆ Rails เก็บสคีมาฐานข้อมูลล่าสุดในไฟล์ db/schema.rb . ไฟล์นี้เป็นตัวแทน Ruby ของการย้ายข้อมูลทั้งหมดบนฐานข้อมูลของคุณตลอดอายุของแอปพลิเคชัน เนื่องจากไฟล์นี้ เราจึงไม่จำเป็นต้องเก็บไฟล์การโยกย้ายแบบเก่าไว้ใน codebase Rails จัดเตรียมงานเพื่อ dump สคีมาล่าสุดจากฐานข้อมูลไปยัง schema.rb และ load สคีมาลงในฐานข้อมูลจาก schema.rb . ดังนั้นการโยกย้ายที่เก่ากว่าจึงสามารถลบออกจากฐานรหัสได้อย่างปลอดภัย การโหลดสคีมาลงในฐานข้อมูลนั้นเร็วกว่าเมื่อเทียบกับการเรียกใช้การย้ายแต่ละครั้งทุกครั้งที่เราตั้งค่าแอปพลิเคชัน

Rails ยังจัดเตรียมวิธีการเก็บสคีมาฐานข้อมูลในรูปแบบ SQL เรามีบทความเปรียบเทียบทั้งสองรูปแบบอยู่แล้ว คุณสามารถอ่านเพิ่มเติมได้ที่นี่

เวอร์ชัน Rails ในการย้ายข้อมูล

การย้ายข้อมูลทั้งหมดที่เราสร้างมีเวอร์ชัน Rails เป็นส่วนหนึ่งของซูเปอร์คลาส ดังนั้นการย้ายข้อมูลที่สร้างโดยแอป Rails 6 จึงมี ActiveRecord::Migration[6.0] ระดับซูเปอร์คลาส ในขณะที่การโยกย้ายที่สร้างโดยแอป Rails 5.2 มีซูเปอร์คลาส ActiveRecord::Migration[5.2] . หากคุณมีแอปเก่าที่มี Rails 4.2 หรือต่ำกว่า คุณจะสังเกตเห็นว่าไม่มีเวอร์ชันในซูเปอร์คลาส ซูเปอร์คลาสเป็นเพียง ActiveRecord::Migration .

เพิ่มเวอร์ชัน Rails ลงในซูเปอร์คลาสการโยกย้ายใน Rails 5 โดยพื้นฐานแล้วทำให้มั่นใจได้ว่า migrationAPI สามารถพัฒนาได้ตลอดเวลาโดยไม่ทำลายการโยกย้ายที่สร้างโดย Rails เวอร์ชันเก่า

มาเจาะลึกเรื่องนี้กันโดยดูที่การย้ายข้อมูลเดียวกันสำหรับการสร้าง events ตารางในแอป Rails 4.2

class CreateEvents < ActiveRecord::Migration
  def change
    create_table :events do |t|
      t.string :category
 
      t.timestamps null: false
    end
  end
end

ถ้าเราดูที่สคีมาของ events ตารางที่สร้างโดยการย้ายข้อมูล Rails 6 เราจะเห็นว่า NOT NULL มีข้อจำกัดสำหรับคอลัมน์ประทับเวลา

sqlite> .schema events
CREATE TABLE IF NOT EXISTS "events" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "category" varchar, "created_at" datetime(6) NOT NULL, "updated_at" datetime(6) NOT NULL);

นี่เป็นเพราะตั้งแต่ Rails 5 เป็นต้นไป API การโยกย้ายจะเพิ่ม NOT NULL โดยอัตโนมัติ ข้อจำกัดของคอลัมน์ประทับเวลาโดยไม่จำเป็นต้องเพิ่มอย่างชัดเจนในไฟล์การโยกย้าย เวอร์ชัน Rails ในชื่อซูเปอร์คลาสช่วยให้มั่นใจว่าการย้ายข้อมูลจะใช้ API การย้ายข้อมูลของเวอร์ชัน Rails ที่สร้างการย้ายข้อมูล วิธีนี้ช่วยให้ Rails รักษาความเข้ากันได้แบบย้อนหลังกับการย้ายข้อมูลแบบเก่า ในขณะเดียวกันก็พัฒนา API การย้ายข้อมูล

การเปลี่ยนรูปแบบฐานข้อมูล

change วิธีเป็นวิธีหลักในการโยกย้าย เมื่อทำการโยกย้าย จะเรียก change เมธอดและรันโค้ดที่อยู่ภายใน

พร้อมกับ create_table , Rails ยังมีวิธีที่มีประสิทธิภาพอีกวิธีหนึ่ง—change_table .ตามชื่อของมัน มันถูกใช้เพื่อเปลี่ยนสคีมาของตารางที่มีอยู่

def change
  change_table :events do |t|
    t.remove :category
    t.string :event_type
    t.boolean :active, default: false
  end
end

การย้ายข้อมูลนี้จะลบ category คอลัมน์จาก events ตาราง เพิ่มคอลัมน์สตริงใหม่ events_type และคอลัมน์บูลีนใหม่ active ด้วยค่าเริ่มต้น false .

Rails ยังมีวิธีการช่วยเหลืออื่นๆ อีกมากที่สามารถนำมาใช้ในการย้ายข้อมูลได้ เช่น:

  • change_column
  • add_index
  • remove_index
  • rename_table

และอื่น ๆ อีกมากมาย. ดูวิธีการทั้งหมดที่สามารถใช้กับการเปลี่ยนแปลงได้ที่นี่

การประทับเวลา

เราเห็นว่า t.timestamps ถูกเพิ่มในการโยกย้ายโดย Rails และเพิ่มคอลัมน์created_at และ updated_at ไปที่ events โต๊ะ. คอลัมน์พิเศษเหล่านี้ถูกใช้โดย Railsto เพื่อติดตามว่าเร็กคอร์ดถูกสร้างขึ้นและอัปเดตเมื่อใด Rails จะเพิ่มค่าให้กับคอลัมน์เหล่านี้เมื่อมีการสร้างเรกคอร์ดและต้องแน่ใจว่าได้อัปเดตเมื่อมีการอัพเดตเรคคอร์ด คอลัมน์เหล่านี้ช่วยเราในการติดตามอายุของบันทึกฐานข้อมูล

updated_at คอลัมน์ไม่ได้รับการอัปเดตเมื่อเราดำเนินการ updated_all วิธีการจาก Rails

การจัดการกับความล้มเหลว

การย้ายถิ่นไม่ใช่ระบบกันกระสุน พวกเขาสามารถล้มเหลว เหตุผลอาจเป็นไวยากรณ์ที่ไม่ถูกต้องหรือแบบสอบถามฐานข้อมูลที่ไม่ถูกต้อง ไม่ว่าด้วยเหตุผลใด เราต้องจัดการกับความล้มเหลวและกู้คืนจากมัน เพื่อไม่ให้ฐานข้อมูลอยู่ในสถานะที่ไม่สอดคล้องกัน Rails แก้ปัญหานี้ด้วยการเรียกใช้แต่ละการย้ายข้อมูลภายในธุรกรรม หากการย้ายล้มเหลว ธุรกรรมจะถูกย้อนกลับ เพื่อให้แน่ใจว่าฐานข้อมูลไม่เข้าสู่สถานะที่ไม่สอดคล้องกัน

ทำได้เฉพาะกับฐานข้อมูลที่สนับสนุนธุรกรรมสำหรับการอัพเดตสคีมาฐานข้อมูล พวกเขาเรียกว่าธุรกรรม Data Definition Language (DDL) MySQL และ PostgreSQL รองรับธุรกรรม DDL

บางครั้ง เราไม่ต้องการดำเนินการย้ายข้อมูลบางอย่างภายในธุรกรรม ตัวอย่างง่ายๆ คือ เมื่อเพิ่มดัชนีที่เกิดขึ้นพร้อมกันใน PostgreSQL ไม่สามารถดำเนินการย้ายดังกล่าวภายในธุรกรรม DDL เช่น PostgreSQLtries เพื่อเพิ่มดัชนีโดยไม่ต้องรับการล็อกบนโต๊ะ เพื่อให้เราสามารถเพิ่มดัชนีในฐานข้อมูลที่ใช้งานจริงโดยไม่ต้องลบฐานข้อมูล Rails มอบวิธีการยกเลิกการทำธุรกรรมภายในการย้ายข้อมูลในรูปแบบของ disable_ddl_transactions! .

def change
  disable_ddl_transactions!
 
  add_index :events, :user_id, algorithm: :concurrently

การดำเนินการนี้จะไม่เรียกใช้การย้ายข้อมูลภายในธุรกรรม หากการย้ายถิ่นล้มเหลว เราต้องกู้คืนด้วยตนเอง ในกรณีนี้ เราสามารถ REINDEX หรือลบดัชนีแล้วลองเพิ่มอีกครั้ง

การย้ายถิ่นแบบย้อนกลับได้

Rails ช่วยให้เราสามารถย้อนกลับการเปลี่ยนแปลงฐานข้อมูลด้วยคำสั่งต่อไปนี้

rails db:rollback

คำสั่งนี้จะคืนค่าการย้ายข้อมูลล่าสุดที่รันบนฐานข้อมูล หากการย้ายข้อมูลเพิ่มคอลัมน์event_type จากนั้นการย้อนกลับจะลบคอลัมน์นั้น หากการย้ายข้อมูลเพิ่มดัชนี การย้อนกลับจะลบดัชนีนั้นออก

นอกจากนี้ยังมีคำสั่งสำหรับการย้อนกลับการโยกย้ายก่อนหน้าและเรียกใช้ มันคือ rails db:redo .

Rails ฉลาดพอที่จะรู้วิธีย้อนกลับการโยกย้ายส่วนใหญ่ แต่เรายังสามารถให้คำแนะนำแก่ Railson เกี่ยวกับวิธีคืนค่าการย้ายข้อมูลด้วยการให้ up และ down แทนการใช้ change method.The up เมธอดจะถูกใช้เมื่อมีการเรียกใช้การโยกย้ายในขณะที่ down เมธอดจะถูกใช้เมื่อมีการย้อนกลับการย้ายข้อมูล

def up
  change_table :events do |t|
    t.change :price, :string
  end
end
 
def down
  change_table :events do |t|
    t.change :price, :integer
  end
end

ในตัวอย่างนี้ เรากำลังเปลี่ยน price คอลัมน์ events จาก integer เป็น string . เราระบุวิธีการย้อนกลับใน down วิธีการ

การย้ายข้อมูลแบบเดียวกันนี้สามารถเขียนได้โดยใช้ change วิธีการ

def change
  reversible do |direction|
    change_table :events do |t|
      direction.up { t.change :price, :string }
      direction.down { t.change :price, :integer }
    end
  end
end

Rails ยังให้วิธีการคืนค่าการโยกย้ายก่อนหน้าทั้งหมดโดยใช้ revert วิธีการ

def change
  revert CreateEvents
 
  create_table :events do
   ...
  end
end

revert เมธอดยังยอมรับการบล็อกเพื่อคืนค่าการย้ายข้อมูลบางส่วนด้วย

def change
  revert do
    reversible do |direction|
      change_table :events do |t|
        direction.up { t.remove :event_type }
        direction.down { t.string :event_type }
      end
    end
  end
end

การดำเนินการแบบดิบ

บางครั้ง เราต้องการรัน SQL ที่ซับซ้อนภายในการย้ายข้อมูล ในกรณีเช่นนี้ เราสามารถลืม DSL การย้ายข้อมูลทั่วไปและรัน SQL ดิบแทนได้ดังนี้

def change
  execute <<-SQL
    ....
  SQL
end

หลายฐานข้อมูลและการย้ายข้อมูล

Rails 6 เพิ่มการรองรับสำหรับการใช้หลายฐานข้อมูลภายในแอปพลิเคชัน Rails เดียว หากเราต้องการใช้หลายฐานข้อมูล เรากำหนดค่าฐานข้อมูลเหล่านั้นใน database.yml ไฟล์.

development:
  primary:
    <<: *default
    database: db/development.sqlite3
  analytics:
    adapter: sqlite3
    database: db/analytics_dev.sqlite3

การกำหนดค่านี้บอก Rails ว่าเราต้องการใช้สองฐานข้อมูล—primary และ analytics ดังที่เราเห็นก่อนหน้านี้ การย้ายข้อมูลจะถูกเก็บไว้ใน db/migrate ไดเรกทอรีโดยค่าเริ่มต้น แต่ในกรณีนี้ เราไม่สามารถเพิ่มการโยกย้ายของฐานข้อมูลทั้งสองภายในไดเร็กทอรีเดียว เราไม่ต้องการเรียกใช้การโยกย้ายของ analytics ฐานข้อมูลใน primary ฐานข้อมูลและในทางกลับกัน หากเราใช้หลายฐานข้อมูล จำเป็นต้องจัดเตรียมเส้นทางสำหรับจัดเก็บการย้ายข้อมูลสำหรับฐานข้อมูลที่สอง ซึ่งสามารถทำได้โดยระบุ migrations_paths ใน database.yml .

development:
  primary:
    <<: *default
    database: db/development.sqlite3
  analytics:
    adapter: sqlite3
    database: db/analytics_dev.sqlite3
    migrations_paths: db/analytics_migrate

จากนั้น เราสามารถสร้างการย้ายข้อมูลสำหรับ analytics ฐานข้อมูลดังนี้

rails generate migration AddExperiments rule:string active:boolean --db=analytics

สิ่งนี้จะสร้างการย้ายข้อมูลภายใน db/analytics_migrate และเราสามารถเรียกใช้ได้ดังนี้

rails db:migrate --db=analytics

หากเรารันเฉพาะ rails db:migrate จะทำการย้ายฐานข้อมูลทั้งหมด

analytics ฐานข้อมูลจะมี schema_migrations . เป็นของตัวเอง ตารางเพื่อติดตามว่าการย้ายข้อมูลใดที่เรียกใช้และรายการใดที่ไม่ได้ดำเนินการ

เรียกใช้การย้ายข้อมูลระหว่างการปรับใช้

เนื่องจากการย้ายข้อมูลสามารถเปลี่ยนสถานะของฐานข้อมูล และรหัสของเราอาจขึ้นอยู่กับการเปลี่ยนแปลงเหล่านั้น จึงเป็นเรื่องสำคัญอย่างยิ่งที่จะต้องมีการเรียกใช้การย้ายก่อนที่จะใช้รหัสใหม่

ในการปรับใช้ตาม Heroku การย้ายข้อมูลสามารถทำได้ใน version เฟสของ Procfile .

# Profile
web: bin/puma -C config/puma.rb
release: bundle exec rake db:migrate

เพื่อให้แน่ใจว่ามีการเรียกใช้การย้ายข้อมูลก่อนที่จะรีสตาร์ทไดโนของแอป

ในการปรับใช้ตาม Capistrano การย้ายข้อมูลควรทำงานก่อนที่เซิร์ฟเวอร์จะรีสตาร์ท

ในการปรับใช้ตามนักเทียบท่า เราสามารถเรียกใช้คอนเทนเนอร์ข้างเคียงเพื่อเรียกใช้การย้ายข้อมูลก่อนที่จะรีสตาร์ทแอป สิ่งนี้สำคัญมาก มิฉะนั้น คอนเทนเนอร์ใหม่อาจเข้าสู่สถานะไม่สอดคล้องกันหากพวกเขาเริ่มใช้รหัสใหม่ก่อนที่จะใช้การเปลี่ยนแปลงฐานข้อมูลสำหรับรหัสใหม่นั้น

บทสรุป

ในโพสต์นี้ เราได้เห็นแง่มุมต่างๆ ของการเขียนการย้ายฐานข้อมูลใน Rails เรายังเห็นสิ่งที่ถือเป็นการโยกย้าย ตลอดจนวิธีจัดการกับความล้มเหลวและย้อนกลับการย้ายข้อมูลหากจำเป็น Rails 6 ช่วยให้เราใช้หลายฐานข้อมูลและต้องเพิ่มการโยกย้ายสำหรับแต่ละฐานข้อมูลแยกกัน สุดท้ายนี้ เราได้เห็นวิธีการเรียกใช้การย้ายข้อมูลระหว่างการปรับใช้งานโดยสังเขป เพื่อให้การเปลี่ยนแปลงฐานข้อมูลถูกนำไปใช้อย่างเหมาะสมก่อนที่โค้ดใหม่จะเริ่มใช้งาน

ป.ล. หากคุณต้องการอ่านโพสต์ Ruby Magic ทันทีที่ออกจากสื่อ สมัครรับจดหมายข่าว Ruby Magic ของเราและไม่พลาดแม้แต่โพสต์เดียว!