Cơ sở dữ liệu và triển khai không có thời gian ngừng hoạt động

Cơ sở dữ liệu và triển khai không có thời gian ngừng hoạt động

Bài viết này giải thích chi tiết cách giải quyết các vấn đề tương thích cơ sở dữ liệu trong quá trình triển khai. Chúng tôi sẽ cho bạn biết điều gì có thể xảy ra với các ứng dụng sản xuất của bạn nếu bạn cố gắng triển khai mà không có sự chuẩn bị sơ bộ. Sau đó chúng ta sẽ đi qua các giai đoạn trong vòng đời ứng dụng được yêu cầu để không có thời gian ngừng hoạt động (khoảng làn đường: xa hơn - không có thời gian chết). Kết quả hoạt động của chúng tôi sẽ là áp dụng thay đổi cơ sở dữ liệu không tương thích ngược theo cách tương thích ngược.

Nếu bạn muốn hiểu các ví dụ mã từ bài viết, bạn có thể tìm thấy chúng tại GitHub.

Giới thiệu

Triển khai không có thời gian ngừng hoạt động

Thật là huyền bí triển khai thời gian chết bằng không? Bạn có thể nói đây là khi ứng dụng của bạn được triển khai theo cách mà bạn có thể đưa phiên bản mới của ứng dụng vào sản xuất thành công mà người dùng không nhận thấy phiên bản đó không có sẵn. Từ góc độ người dùng và công ty, đây là kịch bản triển khai tốt nhất có thể vì nó cho phép giới thiệu các tính năng mới và sửa lỗi mà không bị gián đoạn.

Làm thế nào để đạt được điều này? Có một số cách, đây là một trong số đó:

  • triển khai phiên bản số 1 của dịch vụ của bạn
  • thực hiện di chuyển cơ sở dữ liệu
  • Triển khai phiên bản số 2 của dịch vụ của bạn song song với phiên bản số 1
  • ngay khi bạn thấy phiên bản số 2 hoạt động bình thường, hãy xóa phiên bản số 1
  • đã sẵn sàng!

Dễ dàng phải không? Thật không may, nó không đơn giản như vậy và chúng ta sẽ xem xét điều đó một cách chi tiết sau. Bây giờ hãy kiểm tra một quy trình triển khai khá phổ biến khác - triển khai xanh lam.

Bạn đã bao giờ nghe nói về triển khai xanh xanh? Cloud Foundry khiến việc này trở nên cực kỳ dễ dàng. Chỉ cần nhìn vào bài viết này, nơi chúng tôi mô tả điều này chi tiết hơn. Để tóm tắt ngắn gọn, hãy để chúng tôi nhắc bạn cách triển khai màu xanh lam:

  • đảm bảo rằng hai bản sao mã sản xuất của bạn (“xanh lam” và “xanh lục”) hoạt động;
  • hướng tất cả lưu lượng truy cập đến môi trường xanh, tức là để các URL sản xuất trỏ đến đó;
  • triển khai và thử nghiệm mọi thay đổi của ứng dụng trong môi trường xanh;
  • chuyển url từ môi trường xanh sang xanh

Triển khai xanh lam là một phương pháp cho phép bạn dễ dàng giới thiệu các tính năng mới mà không lo gián đoạn quá trình sản xuất. Điều này là do thực tế là ngay cả khi có điều gì đó xảy ra, bạn có thể dễ dàng quay trở lại môi trường trước đó chỉ bằng cách “bật công tắc”.

Sau khi đọc tất cả những điều trên, bạn có thể đặt câu hỏi: Thời gian ngừng hoạt động bằng XNUMX có liên quan gì đến việc triển khai Blue green?

Chà, chúng có khá nhiều điểm chung, vì việc duy trì hai bản sao của cùng một môi trường đòi hỏi nỗ lực gấp đôi để duy trì chúng. Đây là lý do tại sao một số đội yêu cầu Martin Fowler, hãy làm theo một biến thể của phương pháp này:

Một tùy chọn khác là sử dụng cùng một cơ sở dữ liệu, tạo các chuyển đổi màu xanh lam-xanh lục cho các lớp web và miền. Theo cách tiếp cận này, cơ sở dữ liệu thường có thể gặp sự cố, đặc biệt khi bạn cần thay đổi lược đồ của nó để hỗ trợ phiên bản mới của phần mềm.

Và ở đây chúng ta đi đến vấn đề chính trong bài viết này. Cơ sở dữ liệu. Chúng ta hãy xem xét lại cụm từ này.

thực hiện di chuyển cơ sở dữ liệu.

Bây giờ bạn phải tự đặt câu hỏi - điều gì sẽ xảy ra nếu thay đổi cơ sở dữ liệu không tương thích ngược? Phiên bản ứng dụng đầu tiên của tôi có bị hỏng không? Trên thực tế, đây chính xác là những gì sẽ xảy ra...

Vì vậy, ngay cả khi có những lợi ích to lớn của việc không có thời gian ngừng hoạt động/triển khai xanh lam, các công ty vẫn có xu hướng tuân theo quy trình an toàn hơn sau để triển khai ứng dụng của mình:

  • chuẩn bị một gói có phiên bản mới của ứng dụng
  • tắt một ứng dụng đang chạy
  • chạy tập lệnh để di chuyển cơ sở dữ liệu
  • triển khai và khởi chạy phiên bản mới của ứng dụng

Trong bài viết này, chúng tôi sẽ trình bày chi tiết cách bạn có thể làm việc với cơ sở dữ liệu và mã của mình để tận dụng việc triển khai không có thời gian ngừng hoạt động.

Các vấn đề về cơ sở dữ liệu

Nếu bạn có một ứng dụng không có trạng thái không lưu trữ bất kỳ dữ liệu nào trong cơ sở dữ liệu, bạn có thể triển khai ngay lập tức mà không có thời gian ngừng hoạt động. Thật không may, hầu hết các phần mềm đều cần lưu trữ dữ liệu ở đâu đó. Đây là lý do tại sao bạn nên suy nghĩ kỹ trước khi thực hiện bất kỳ thay đổi nào đối với mạch điện. Trước khi đi vào chi tiết về cách thay đổi lược đồ để có thể triển khai không có thời gian ngừng hoạt động, trước tiên hãy tập trung vào lược đồ lập phiên bản.

Sơ đồ phiên bản

Trong bài viết này chúng tôi sẽ sử dụng Đường bay như một công cụ kiểm soát phiên bản (khoảng Dịch: chúng ta đang nói về việc di chuyển cơ sở dữ liệu). Đương nhiên, chúng tôi cũng sẽ viết một ứng dụng Spring Boot có hỗ trợ Flyway tích hợp và sẽ thực hiện di chuyển lược đồ trong khi thiết lập ngữ cảnh ứng dụng. Khi sử dụng Flyway, bạn có thể lưu trữ tập lệnh di chuyển trong thư mục dự án của mình (theo mặc định trong classpath:db/migration). Tại đây bạn có thể xem ví dụ về các tệp di chuyển như vậy

└── db
 └── migration
     ├── V1__init.sql
     ├── V2__Add_surname.sql
     ├── V3__Final_migration.sql
     └── V4__Remove_lastname.sql

Trong ví dụ này, chúng ta thấy 4 tập lệnh di chuyển, nếu không được thực thi trước đó, sẽ lần lượt được thực thi khi ứng dụng khởi động. Hãy xem xét một trong các tập tin (V1__init.sql) làm ví dụ.

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

Mọi thứ đều hoàn toàn dễ hiểu: bạn có thể sử dụng SQL để xác định cách sửa đổi cơ sở dữ liệu của mình. Để biết thêm thông tin về Spring Boot và Flyway, hãy xem Tài liệu khởi động mùa xuân.

Bằng cách sử dụng công cụ kiểm soát nguồn với Spring Boot, bạn nhận được 2 lợi ích lớn:

  • bạn tách biệt các thay đổi cơ sở dữ liệu với các thay đổi mã
  • Di chuyển cơ sở dữ liệu xảy ra cùng với việc triển khai ứng dụng của bạn, tức là. quá trình triển khai của bạn được đơn giản hóa

Khắc phục sự cố cơ sở dữ liệu

Trong phần tiếp theo của bài viết, chúng ta sẽ tập trung xem xét hai cách tiếp cận đối với những thay đổi cơ sở dữ liệu.

  • không tương thích ngược
  • khả năng tương thích ngược

Điều đầu tiên sẽ được coi là cảnh báo rằng bạn không nên thực hiện triển khai không có thời gian ngừng hoạt động mà không có sự chuẩn bị sơ bộ... Điều thứ hai đưa ra giải pháp về cách bạn có thể thực hiện triển khai mà không có thời gian ngừng hoạt động, đồng thời duy trì khả năng tương thích ngược.

Dự án mà chúng tôi đang thực hiện sẽ là một ứng dụng Spring Boot Flyway đơn giản có Person с first_name и last_name trong cơ sở dữ liệu (khoảng dịch: Person là một cái bàn và first_name и last_name - đây là những trường trong đó). Chúng tôi muốn đổi tên last_name в surname.

Giả định

Trước khi đi vào chi tiết, chúng ta cần đưa ra một số giả định về ứng dụng của mình. Kết quả chính mà chúng tôi muốn đạt được sẽ là một quá trình khá đơn giản.

Các lưu ý. Kinh doanh PRO-TIP. Việc đơn giản hóa các quy trình có thể giúp bạn tiết kiệm rất nhiều tiền cho việc hỗ trợ (càng có nhiều người làm việc cho công ty của bạn thì bạn càng tiết kiệm được nhiều tiền hơn)!

Không cần khôi phục cơ sở dữ liệu

Điều này giúp đơn giản hóa quá trình triển khai (một số khôi phục cơ sở dữ liệu gần như không thể thực hiện được, chẳng hạn như khôi phục xóa). Chúng tôi muốn chỉ khôi phục các ứng dụng. Bằng cách này, ngay cả khi bạn có các cơ sở dữ liệu khác nhau (ví dụ: SQL và NoSQL), quy trình triển khai của bạn sẽ giống nhau.

LUÔN LUÔN phải có thể khôi phục ứng dụng trở lại một phiên bản (không hơn)

Rollback chỉ nên được thực hiện khi cần thiết. Nếu có một lỗi trong phiên bản hiện tại không dễ khắc phục, chúng tôi có thể quay lại phiên bản hoạt động mới nhất. Chúng tôi giả định rằng phiên bản làm việc mới nhất này là phiên bản trước đó. Việc duy trì khả năng tương thích mã và cơ sở dữ liệu cho nhiều lần triển khai sẽ cực kỳ khó khăn và tốn kém.

Ghi chú. Để dễ đọc hơn, trong bài viết này, chúng tôi sẽ thay đổi phiên bản chính của ứng dụng.

Bước 1: Trạng thái ban đầu

Phiên bản ứng dụng: 1.0.0
Phiên bản cơ sở dữ liệu: v1

Bình luận

Đây sẽ là trạng thái ban đầu của ứng dụng.

Thay đổi cơ sở dữ liệu

cơ sở dữ liệu chứa last_name.

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

Thay đổi mã

Ứng dụng lưu trữ dữ liệu Người trong last_name:

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return this.lastName;
    }

    public void setLastName(String lastname) {
        this.lastName = lastname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName
                + "]";
    }
}

Đổi tên cột không tương thích ngược

Hãy xem một ví dụ về cách thay đổi tên cột:

Chú ý. Ví dụ sau đây sẽ cố ý phá vỡ mọi thứ. Chúng tôi trình bày điều này để chứng minh vấn đề tương thích cơ sở dữ liệu.

Phiên bản ứng dụng: 2.0.0.BAD

Phiên bản cơ sở dữ liệu: v2bad

Bình luận

Những thay đổi hiện tại KHÔNG cho phép chúng tôi chạy hai phiên bản (cũ và mới) cùng một lúc. Do đó, việc triển khai không có thời gian ngừng hoạt động sẽ khó đạt được (nếu tính đến các giả định thì thực tế là không thể).

Thử nghiệm A/B

Tình hình hiện tại là chúng ta đã có phiên bản ứng dụng 1.0.0, được triển khai trong sản xuất và cơ sở dữ liệu v1. Chúng ta cần triển khai phiên bản thứ hai của ứng dụng, phiên bản 2.0.0.BADvà cập nhật cơ sở dữ liệu lên v2bad.

Các bước:

  1. một phiên bản mới của ứng dụng phiên bản được triển khai 2.0.0.BADcập nhật cơ sở dữ liệu lên v2bad
  2. trong cơ sở dữ liệu v2bad cột last_name không còn tồn tại - nó đã được đổi thành surname
  3. Cập nhật cơ sở dữ liệu và ứng dụng đã thành công và một số phiên bản đang chạy 1.0.0, những người khác - trong 2.0.0.BAD. Mọi thứ đều được kết nối với cơ sở dữ liệu v2bad
  4. tất cả các phiên bản của phiên bản 1.0.0 sẽ bắt đầu đưa ra lỗi vì họ sẽ cố gắng chèn dữ liệu vào cột last_namengười không còn tồn tại
  5. tất cả các phiên bản của phiên bản 2.0.0.BAD sẽ hoạt động mà không gặp vấn đề gì

Như bạn có thể thấy, nếu chúng tôi thực hiện các thay đổi không tương thích ngược đối với cơ sở dữ liệu và ứng dụng thì thử nghiệm A/B là không thể.

Khôi phục ứng dụng

Giả sử rằng sau khi thử triển khai A/B (khoảng per.: tác giả có lẽ muốn nói đến thử nghiệm A/B ở đây) chúng tôi đã quyết định rằng chúng tôi cần khôi phục ứng dụng về phiên bản 1.0.0. Giả sử chúng ta không muốn khôi phục cơ sở dữ liệu.

Các bước:

  1. chúng tôi dừng phiên bản ứng dụng phiên bản 2.0.0.BAD
  2. cơ sở dữ liệu vẫn còn v2bad
  3. kể từ phiên bản 1.0.0 không hiểu nó là gì surname, chúng ta sẽ thấy lỗi
  4. Địa ngục đã tan vỡ, chúng ta không thể quay lại được nữa

Như bạn có thể thấy, nếu chúng tôi thực hiện các thay đổi không tương thích ngược đối với cơ sở dữ liệu và ứng dụng, chúng tôi không thể quay lại phiên bản trước.

Nhật ký thực thi tập lệnh

Backward incompatible scenario:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0.BAD
05) Wait for the app (2.0.0.BAD) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0 <-- this should fail
07) Generate a person by calling POST localhost:9992/person to version 2.0.0.BAD <-- this should pass

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"b73f639f-e176-4463-bf26-1135aace2f57","lastName":"b73f639f-e176-4463-bf26-1135aace2f57"}

Starting app in version 2.0.0.BAD
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

curl: (22) The requested URL returned error: 500 Internal Server Error

Generate a person in version 2.0.0.BAD
Sending a post to 127.0.0.1:9995/person. This is the response:

{"firstName":"e156be2e-06b6-4730-9c43-6e14cfcda125","surname":"e156be2e-06b6-4730-9c43-6e14cfcda125"}

Thay đổi cơ sở dữ liệu

Tập lệnh di chuyển đổi tên last_name в surname

Tập lệnh Flyway nguồn:

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

Kịch bản đổi tên last_name.

-- This change is backward incompatible - you can't do A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;

Thay đổi mã

Chúng tôi đã thay đổi tên trường lastName trên surname.

Đổi tên cột theo cách tương thích ngược

Đây là tình huống phổ biến nhất mà chúng ta có thể gặp phải. Chúng ta cần thực hiện những thay đổi không tương thích ngược. Chúng tôi đã chứng minh rằng để triển khai không có thời gian ngừng hoạt động, chúng tôi không nên chỉ áp dụng di chuyển cơ sở dữ liệu mà không có các bước bổ sung. Trong phần này của bài viết, chúng tôi sẽ thực hiện 3 lần triển khai ứng dụng cùng với việc di chuyển cơ sở dữ liệu để đạt được kết quả mong muốn trong khi vẫn duy trì khả năng tương thích ngược.

Các lưu ý. Hãy nhớ lại rằng chúng tôi có cơ sở dữ liệu phiên bản v1. Nó chứa các cột first_name и last_name. Chúng ta phải thay đổi last_name trên surname. Chúng tôi cũng có phiên bản ứng dụng 1.0.0, cái đó vẫn chưa được sử dụng surname.

Bước 2: Thêm họ

Phiên bản ứng dụng: 2.0.0
Phiên bản cơ sở dữ liệu: v2

Bình luận

Bằng cách thêm một cột mới và sao chép nội dung của nó, chúng tôi tạo ra những thay đổi tương thích ngược với cơ sở dữ liệu. Đồng thời, nếu chúng ta khôi phục JAR hoặc có một JAR cũ đang chạy, nó sẽ không bị hỏng trong quá trình thực thi.

Chúng tôi đang tung ra một phiên bản mới

Các bước:

  1. thực hiện di chuyển cơ sở dữ liệu để tạo một cột mới surname. Bây giờ phiên bản DB của bạn v2
  2. sao chép dữ liệu từ last_name в surname. Chú ýrằng nếu bạn có nhiều dữ liệu này, bạn nên xem xét việc di chuyển hàng loạt!
  3. viết mã nơi chúng được sử dụng CẢ HAI и mớicái cũ cột. Bây giờ phiên bản ứng dụng của bạn 2.0.0
  4. đọc giá trị từ cột surname, nếu không phải vậy null, hoặc từ last_name, nếu surname không được chỉ định. Bạn có thể xóa getLastName() từ mã, vì nó sẽ xuất ra null khi khôi phục ứng dụng của bạn từ 3.0.0 để 2.0.0.

Nếu bạn đang sử dụng Spring Boot Flyway, hai bước này sẽ được thực hiện trong quá trình khởi động phiên bản 2.0.0 các ứng dụng. Nếu bạn chạy công cụ lập phiên bản cơ sở dữ liệu theo cách thủ công, bạn sẽ phải thực hiện hai việc khác nhau để thực hiện việc này (trước tiên hãy cập nhật phiên bản db theo cách thủ công và sau đó triển khai ứng dụng mới).

Điều quan trọng. Hãy nhớ rằng cột mới được tạo KHÔNG NÊN được CÓ GIÁ TRỊ. Nếu bạn thực hiện khôi phục, ứng dụng cũ sẽ không biết về cột mới và sẽ không cài đặt nó trong quá trình thực hiện. Insert. Nhưng nếu bạn thêm ràng buộc này và db của bạn sẽ v2, điều này sẽ yêu cầu đặt giá trị của cột mới. Điều này sẽ dẫn đến vi phạm các hạn chế.

Điều quan trọng. Bạn nên loại bỏ phương pháp getLastName(), bởi vì trong phiên bản 3.0.0 Không có khái niệm về một cột trong mã last_name. Điều này có nghĩa là null sẽ được đặt ở đó. Bạn có thể để lại phương thức và thêm kiểm tra cho null, nhưng giải pháp tốt hơn nhiều là đảm bảo rằng theo logic getSurname() bạn đã chọn đúng giá trị khác XNUMX.

Thử nghiệm A/B

Tình hình hiện tại là chúng ta đã có phiên bản ứng dụng 1.0.0, được triển khai trên sản xuất và cơ sở dữ liệu trong v1. Chúng tôi cần triển khai phiên bản thứ hai của ứng dụng phiên bản 2.0.0sẽ cập nhật cơ sở dữ liệu lên v2.

Các bước:

  1. một phiên bản mới của ứng dụng phiên bản được triển khai 2.0.0cập nhật cơ sở dữ liệu lên v2
  2. trong thời gian chờ đợi, một số yêu cầu đã được xử lý bởi các phiên bản phiên bản 1.0.0
  3. bản cập nhật đã thành công và bạn có nhiều phiên bản đang chạy của ứng dụng phiên bản 1.0.0 và các phiên bản khác 2.0.0. Mọi người giao tiếp với cơ sở dữ liệu trong v2
  4. phiên bản 1.0.0 không sử dụng cột họ trong cơ sở dữ liệu, nhưng phiên bản 2.0.0 công dụng. Chúng không can thiệp lẫn nhau và sẽ không có sai sót.
  5. phiên bản 2.0.0 lưu trữ dữ liệu ở cả cột cũ và cột mới, đảm bảo khả năng tương thích ngược

Điều quan trọng. Nếu bạn có bất kỳ truy vấn nào đếm các mục dựa trên các giá trị từ cột cũ/mới, bạn nên nhớ rằng hiện tại bạn có các giá trị trùng lặp (rất có thể chúng vẫn đang di chuyển). Ví dụ: nếu bạn muốn đếm số lượng người dùng có họ (bất kể cột được gọi là gì) bắt đầu bằng chữ cái A, sau đó cho đến khi quá trình di chuyển dữ liệu hoàn tất (oldnew cột), bạn có thể có dữ liệu không nhất quán nếu bạn truy vấn một cột mới.

Khôi phục ứng dụng

Bây giờ chúng tôi có phiên bản ứng dụng 2.0.0 và cơ sở dữ liệu trong v2.

Các bước:

  1. khôi phục ứng dụng của bạn về phiên bản 1.0.0.
  2. phiên bản 1.0.0 không sử dụng một cột trong cơ sở dữ liệu surname, vì vậy việc khôi phục sẽ thành công

thay đổi cơ sở dữ liệu

Cơ sở dữ liệu chứa một cột có tên last_name.

Kịch bản nguồn đường bay:

CREATE TABLE PERSON (
id BIGINT GENERATED BY DEFAULT AS IDENTITY,
first_name varchar(255) not null,
last_name varchar(255) not null
);

insert into PERSON (first_name, last_name) values ('Dave', 'Syer');

Thêm tập lệnh surname.

Chú ý. Hãy nhớ rằng bạn KHÔNG THỂ THÊM bất kỳ ràng buộc NOT NULL nào vào cột bạn đang thêm. Nếu bạn khôi phục JAR, phiên bản cũ sẽ không biết gì về cột được thêm vào và sẽ tự động đặt nó thành NULL. Nếu có giới hạn như vậy, ứng dụng cũ sẽ bị hỏng.

-- NOTE: This field can't have the NOT NULL constraint cause if you rollback, the old version won't know about this field
-- and will always set it to NULL
ALTER TABLE PERSON ADD surname varchar(255);

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
UPDATE PERSON SET PERSON.surname = PERSON.last_name

Thay đổi mã

Chúng tôi lưu trữ dữ liệu dưới dạng last_namevà trong surname. Đồng thời chúng tôi đọc từ last_name, vì cột này có liên quan nhất. Trong quá trình triển khai, một số yêu cầu có thể đã được xử lý bởi một phiên bản ứng dụng chưa được cập nhật.

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String lastName;
    private String surname;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    /**
     * Reading from the new column if it's set. If not the from the old one.
     *
     * When migrating from version 1.0.0 -> 2.0.0 this can lead to a possibility that some data in
     * the surname column is not up to date (during the migration process lastName could have been updated).
     * In this case one can run yet another migration script after all applications have been deployed in the
     * new version to ensure that the surname field is updated.
     *
     * However it makes sense since when looking at the migration from 2.0.0 -> 3.0.0. In 3.0.0 we no longer
     * have a notion of lastName at all - so we don't update that column. If we rollback from 3.0.0 -> 2.0.0 if we
     * would be reading from lastName, then we would have very old data (since not a single datum was inserted
     * to lastName in version 3.0.0).
     */
    public String getSurname() {
        return this.surname != null ? this.surname : this.lastName;
    }

    /**
     * Storing both FIRST_NAME and SURNAME entries
     */
    public void setSurname(String surname) {
        this.lastName = surname;
        this.surname = surname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", lastName=" + this.lastName + ", surname=" + this.surname
                + "]";
    }
}

Bước 3: Xóa họ_name khỏi mã

Phiên bản ứng dụng: 3.0.0

Phiên bản cơ sở dữ liệu:v3

Bình luận

Ghi chú per.: Rõ ràng, trong bài viết gốc tác giả đã sao chép nhầm văn bản của khối này từ bước 2. Ở bước này, cần thực hiện các thay đổi trong mã ứng dụng nhằm loại bỏ chức năng sử dụng cột last_name.

Bằng cách thêm một cột mới và sao chép nội dung của nó, chúng tôi đã tạo ra những thay đổi tương thích ngược với cơ sở dữ liệu. Ngoài ra, nếu chúng ta khôi phục JAR hoặc có một JAR cũ đang chạy, nó sẽ không bị hỏng trong quá trình thực thi.

Khôi phục ứng dụng

Hiện tại chúng tôi có phiên bản ứng dụng 3.0.0 và cơ sở dữ liệu v3. Phiên bản 3.0.0 không lưu dữ liệu vào last_name. Điều này có nghĩa là trong surname thông tin cập nhật nhất được lưu trữ.

Các bước:

  1. khôi phục ứng dụng của bạn về phiên bản 2.0.0.
  2. phiên bản 2.0.0 sử dụng và last_name и surname.
  3. phiên bản 2.0.0 sẽ mất surname, nếu nó không bằng XNUMX, nếu không -last_name

Thay đổi cơ sở dữ liệu

Không có thay đổi cấu trúc trong cơ sở dữ liệu. Tập lệnh sau được thực thi để thực hiện quá trình di chuyển cuối cùng của dữ liệu cũ:

-- WE'RE ASSUMING THAT IT'S A FAST MIGRATION - OTHERWISE WE WOULD HAVE TO MIGRATE IN BATCHES
-- ALSO WE'RE NOT CHECKING IF WE'RE NOT OVERRIDING EXISTING ENTRIES. WE WOULD HAVE TO COMPARE
-- ENTRY VERSIONS TO ENSURE THAT IF THERE IS ALREADY AN ENTRY WITH A HIGHER VERSION NUMBER
-- WE WILL NOT OVERRIDE IT.
UPDATE PERSON SET PERSON.surname = PERSON.last_name;

-- DROPPING THE NOT NULL CONSTRAINT; OTHERWISE YOU WILL TRY TO INSERT NULL VALUE OF THE LAST_NAME
-- WITH A NOT_NULL CONSTRAINT.
ALTER TABLE PERSON MODIFY COLUMN last_name varchar(255) NULL DEFAULT NULL;

Thay đổi mã

Ghi chú per.: Mô tả của khối này cũng bị tác giả sao chép nhầm từ bước 2. Theo logic của bài viết, những thay đổi trong mã ở bước này nên nhằm mục đích loại bỏ khỏi nó các phần tử hoạt động với cột last_name.

Chúng tôi lưu trữ dữ liệu dưới dạng last_namevà trong surname. Ngoài ra, chúng tôi đọc từ cột last_name, vì nó phù hợp nhất. Trong quá trình triển khai, một số yêu cầu có thể được xử lý bởi một phiên bản chưa được nâng cấp.

/*
 * Copyright 2012-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package sample.flyway;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Person {
    @Id
    @GeneratedValue
    private Long id;
    private String firstName;
    private String surname;

    public String getFirstName() {
        return this.firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getSurname() {
        return this.surname;
    }

    public void setSurname(String lastname) {
        this.surname = lastname;
    }

    @Override
    public String toString() {
        return "Person [firstName=" + this.firstName + ", surname=" + this.surname
                + "]";
    }
}

Bước 4: Xóa Last_name khỏi cơ sở dữ liệu

Phiên bản ứng dụng: 4.0.0

Phiên bản cơ sở dữ liệu: v4

Bình luận

Do thực tế là mã phiên bản 3.0.0 không sử dụng cột last_name, sẽ không có gì xấu xảy ra trong quá trình thực thi nếu chúng ta quay lại 3.0.0 sau khi xóa một cột khỏi cơ sở dữ liệu.

Nhật ký thực thi tập lệnh

We will do it in the following way:

01) Run 1.0.0
02) Wait for the app (1.0.0) to boot
03) Generate a person by calling POST localhost:9991/person to version 1.0.0
04) Run 2.0.0
05) Wait for the app (2.0.0) to boot
06) Generate a person by calling POST localhost:9991/person to version 1.0.0
07) Generate a person by calling POST localhost:9992/person to version 2.0.0
08) Kill app (1.0.0)
09) Run 3.0.0
10) Wait for the app (3.0.0) to boot
11) Generate a person by calling POST localhost:9992/person to version 2.0.0
12) Generate a person by calling POST localhost:9993/person to version 3.0.0
13) Kill app (3.0.0)
14) Run 4.0.0
15) Wait for the app (4.0.0) to boot
16) Generate a person by calling POST localhost:9993/person to version 3.0.0
17) Generate a person by calling POST localhost:9994/person to version 4.0.0

Starting app in version 1.0.0
Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2","lastName":"52b6e125-4a5c-429b-a47a-ef18bbc639d2"}

Starting app in version 2.0.0

Generate a person in version 1.0.0
Sending a post to 127.0.0.1:9991/person. This is the response:

{"firstName":"e41ee756-4fa7-4737-b832-e28827a00deb","lastName":"e41ee756-4fa7-4737-b832-e28827a00deb"}

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:

{"firstName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","lastName":"0c1240f5-649a-4bc5-8aa9-cff855f3927f","surname":"0c1240f5-649a-4bc5-8aa9-cff855f3927f"}

Killing app 1.0.0

Starting app in version 3.0.0

Generate a person in version 2.0.0
Sending a post to 127.0.0.1:9992/person. This is the response:
{"firstName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","lastName":"74d84a9e-5f44-43b8-907c-148c6d26a71b","surname":"74d84a9e-5f44-43b8-907c-148c6d26a71b"}

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:
{"firstName":"c6564dbe-9ab5-40ae-9077-8ae6668d5862","surname":"c6564dbe-9ab5-40ae-9077-8ae6668d5862"}

Killing app 2.0.0

Starting app in version 4.0.0

Generate a person in version 3.0.0
Sending a post to 127.0.0.1:9993/person. This is the response:

{"firstName":"cbe942fc-832e-45e9-a838-0fae25c10a51","surname":"cbe942fc-832e-45e9-a838-0fae25c10a51"}

Generate a person in version 4.0.0
Sending a post to 127.0.0.1:9994/person. This is the response:

{"firstName":"ff6857ce-9c41-413a-863e-358e2719bf88","surname":"ff6857ce-9c41-413a-863e-358e2719bf88"}

thay đổi cơ sở dữ liệu

Tương đối v3 chúng tôi chỉ xóa cột last_name và thêm các hạn chế còn thiếu.

-- REMOVE THE COLUMN
ALTER TABLE PERSON DROP last_name;

-- ADD CONSTRAINTS
UPDATE PERSON SET surname='' WHERE surname IS NULL;
ALTER TABLE PERSON ALTER COLUMN surname VARCHAR NOT NULL;

Thay đổi mã

Không có thay đổi nào đối với mã.

Đầu ra

Chúng tôi đã áp dụng thành công thay đổi tên cột không tương thích ngược bằng cách thực hiện một số triển khai tương thích ngược. Dưới đây là tóm tắt các hành động được thực hiện:

  1. triển khai phiên bản ứng dụng 1.0.0 с v1 lược đồ cơ sở dữ liệu (tên cột = last_name)
  2. triển khai phiên bản ứng dụng 2.0.0, nơi lưu trữ dữ liệu trong last_name и surname. Ứng dụng đọc từ last_name. Cơ sở dữ liệu đang ở phiên bản v2chứa các cột như last_namesurname. surname là một bản sao của last_name. (LƯU Ý: Cột này không được có ràng buộc không rỗng)
  3. triển khai phiên bản ứng dụng 3.0.0, chỉ lưu trữ dữ liệu trong surname và đọc từ họ. Đối với cơ sở dữ liệu, lần di chuyển cuối cùng đang diễn ra last_name в surname. Cũng là một hạn chế CÓ GIÁ TRỊ bị loại khỏi last_name. Cơ sở dữ liệu hiện đã có phiên bản v3
  4. triển khai phiên bản ứng dụng 4.0.0 - không có thay đổi nào được thực hiện đối với mã. Triển khai cơ sở dữ liệu v4, loại bỏ last_name. Tại đây bạn có thể thêm bất kỳ ràng buộc còn thiếu nào vào cơ sở dữ liệu.

Bằng cách làm theo phương pháp này, bạn luôn có thể khôi phục một phiên bản mà không làm ảnh hưởng đến tính tương thích của cơ sở dữ liệu/ứng dụng.

Tất cả mã được sử dụng trong bài viết này có sẵn tại Github. Dưới đây là mô tả bổ sung.

dự án

Sau khi nhân bản kho lưu trữ, bạn sẽ thấy cấu trúc thư mục sau.

├── boot-flyway-v1              - 1.0.0 version of the app with v1 of the schema
├── boot-flyway-v2              - 2.0.0 version of the app with v2 of the schema (backward-compatible - app can be rolled back)
├── boot-flyway-v2-bad          - 2.0.0.BAD version of the app with v2bad of the schema (backward-incompatible - app cannot be rolled back)
├── boot-flyway-v3              - 3.0.0 version of the app with v3 of the schema (app can be rolled back)
└── boot-flyway-v4              - 4.0.0 version of the app with v4 of the schema (app can be rolled back)

Tập lệnh

Bạn có thể chạy các tập lệnh được mô tả trong các tập lệnh bên dưới, tập lệnh này sẽ thể hiện các thay đổi tương thích ngược và không tương thích đối với cơ sở dữ liệu.

Để xem trường hợp có những thay đổi tương thích ngược, chạy:

./scripts/scenario_backward_compatible.sh

Và để xem trường hợp có những thay đổi không tương thích ngược, chạy:

./scripts/scenario_backward_incompatible.sh

Đường bay mẫu khởi động mùa xuân

Tất cả các ví dụ được lấy từ Spring Boot Sample Flyway.

Bạn có thể xem qua http://localhost:8080/flyway, có một danh sách các tập lệnh.

Ví dụ này cũng bao gồm bảng điều khiển H2 (tại http://localhost:8080/h2-console) để bạn có thể xem trạng thái cơ sở dữ liệu (URL jdbc mặc định là jdbc:h2:mem:testdb).

thêm

Cũng đọc các bài viết khác trên blog của chúng tôi:

Nguồn: www.habr.com

Thêm một lời nhận xét