
מאמר זה מסביר בפירוט כיצד לפתור בעיות תאימות של מסדי נתונים בפריסה. אנו אגיד לך מה יכול לקרות ליישומי הייצור שלך אם תנסה לפרוס ללא הכנה מוקדמת. לאחר מכן נעבור את שלבי מחזור החיים של האפליקציה שנדרשים לאפס זמן השבתה (משוער. נתיב: עוד יותר - אפס זמן השבתה). התוצאה של הפעולות שלנו תהיה להחיל את השינוי במסד הנתונים שאינו תואם לאחור באופן תואם לאחור.
אם אתה רוצה להבין את דוגמאות הקוד מהמאמר, תוכל למצוא אותן בכתובת .
מבוא
אפס פריסת זמן השבתה
איזה מיסטי אפס פריסת זמן השבתה? אתה יכול לומר שזה כאשר האפליקציה שלך פרוסה בצורה כזו שתוכל להציג בהצלחה גרסה חדשה של האפליקציה לייצור, בזמן שהמשתמש לא שם לב לאי-זמינותה. מנקודת מבט של משתמש וחברה, זהו תרחיש הפריסה הטוב ביותר האפשרי מכיוון שהוא מאפשר להציג תכונות חדשות ולתקן באגים ללא הפרעה.
איך להשיג זאת? ישנן מספר דרכים, הנה אחת מהן:
- לפרוס גרסה מס' 1 של השירות שלך
- לבצע העברת מסד נתונים
- פרוס את גרסה מס' 2 של השירות שלך במקביל לגרסה מס' 1
- ברגע שאתה רואה שגרסה מס' 2 עובדת כמו שצריך, הסר את גרסה מס' 1
- נעשה!
קל, לא? למרבה הצער, זה לא כל כך פשוט, ונסתכל על זה בפירוט בהמשך. כעת נבדוק עוד תהליך פריסה נפוץ למדי - פריסה כחול ירוק.
האם שמעת אי פעם על ? Cloud Foundry עושה את זה קל במיוחד. רק תסתכל , שם אנו מתארים זאת ביתר פירוט. לסיכום קצר, הרשו לנו להזכיר לכם כיצד לבצע פריסה כחולה ירוקה:
- ודא ששני עותקים של קוד הייצור שלך ("כחול" ו"ירוק") עובדים;
- להפנות את כל התנועה לסביבה הכחולה, כלומר. כך שכתובות אתרים לייצור מצביעות לשם;
- לפרוס ולבדוק את כל השינויים באפליקציה בסביבה ירוקה;
- החלף כתובות אתרים מסביבה כחולה לירוקה
פריסה כחול ירוק היא גישה המאפשרת לך להציג בקלות תכונות חדשות מבלי לדאוג לשבירת הייצור. זה נובע מהעובדה שגם אם משהו קורה, אתה יכול בקלות לחזור אחורה לסביבה הקודמת על ידי פשוט "הקשת מתג".
לאחר קריאת כל האמור לעיל, אתה עשוי לשאול את השאלה: מה הקשר לאפס זמן השבתה לפריסה כחולה ירוקה?
ובכן, יש להם לא מעט במשותף, שכן שמירה על שני עותקים של אותה סביבה דורשת מאמץ כפול כדי לשמור עליהם. זו הסיבה שכמה קבוצות טוענות , בצע וריאציה של גישה זו:
אפשרות נוספת היא להשתמש באותו מסד נתונים, יצירת מתגים כחולים-ירוקים עבור שכבות האינטרנט והדומיין. בגישה זו, מסד הנתונים יכול להוות לעתים קרובות בעיה, במיוחד כאשר אתה צריך לשנות את הסכימה שלו כדי לתמוך בגרסה חדשה של התוכנה.
וכאן אנו מגיעים לבעיה העיקרית במאמר זה. מסד נתונים. בואו נסתכל שוב על הביטוי הזה.
לבצע העברת מסד נתונים.
כעת עליכם לשאול את עצמכם את השאלה - מה אם השינוי במסד הנתונים אינו תואם לאחור? האם הגרסה הראשונה שלי של האפליקציה לא תישבר? למעשה, זה בדיוק מה שיקרה...
לכן, למרות היתרונות העצומים של אפס זמן השבתה / פריסה כחולה ירוקה, חברות נוטות לבצע את התהליך הבטוח הבא יותר לפריסת היישומים שלהן:
- להכין חבילה עם גרסה חדשה של האפליקציה
- כבה אפליקציה פועלת
- להפעיל סקריפטים כדי להעביר את מסד הנתונים
- לפרוס ולהפעיל גרסה חדשה של האפליקציה
במאמר זה, נפרט כיצד תוכל לעבוד עם מסד הנתונים והקוד שלך כדי לנצל את היתרונות של פריסת זמן השבתה אפס.
בעיות במסד נתונים
אם יש לך אפליקציה חסרת מדינה שאינה מאחסנת נתונים במסד הנתונים, תוכל לקבל אפס פריסה בזמן השבתה מיד. למרבה הצער, רוב התוכנות צריכות לאחסן נתונים במקום כלשהו. זו הסיבה שאתה צריך לחשוב פעמיים לפני ביצוע שינויים כלשהם במעגל. לפני שניכנס לפרטים כיצד לשנות את הסכימה כך שתתאפשר פריסה ללא זמן השבתה, בואו נתמקד תחילה בסכימת ניהול הגרסאות.
ערכת גירסאות
במאמר זה נשתמש ככלי בקרת גרסאות (משוער. תרגום: אנחנו מדברים על העברת מסדי נתונים). באופן טבעי, נכתוב גם אפליקציית Spring Boot שיש לה תמיכה מובנית ב-Flyway ותבצע העברת סכימה תוך הגדרת הקשר האפליקציה. בעת שימוש ב-Flyway, אתה יכול לאחסן סקריפטים להעברה בתיקיית הפרויקטים שלך (כברירת מחדל ב classpath:db/migration). כאן אתה יכול לראות דוגמה לקבצי הגירה כאלה
└── db
└── migration
├── V1__init.sql
├── V2__Add_surname.sql
├── V3__Final_migration.sql
└── V4__Remove_lastname.sqlבדוגמה זו אנו רואים 4 סקריפטים למיגרציה, שאם לא בוצעו קודם לכן, יבוצעו בזה אחר זה עם הפעלת האפליקציה. בואו נסתכל על אחד הקבצים (V1__init.sql) לדוגמא.
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');הכל מובן מאליו לחלוטין: אתה יכול להשתמש ב-SQL כדי להגדיר כיצד יש לשנות את מסד הנתונים שלך. למידע נוסף על Spring Boot ו-Flyway, בדוק .
על ידי שימוש בכלי בקרת מקור עם Spring Boot, אתה מקבל 2 יתרונות גדולים:
- אתה מפריד בין שינויים בבסיס הנתונים לבין שינויים בקוד
- העברת מסדי נתונים מתרחשת יחד עם השקת האפליקציה שלך, כלומר. תהליך הפריסה שלך מפושט
פתרון בעיות בבסיס הנתונים
בחלק הבא של המאמר, נתמקד בהסתכלות על שתי גישות לשינויים במסד הנתונים.
- אי התאמה לאחור
- תאימות לאחור
הראשון ייחשב כאזהרה שאין לבצע פריסת זמן השבתה אפס ללא הכנה מוקדמת... השני מציע פתרון כיצד ניתן לבצע פריסה ללא השבתה ובמקביל לשמור על תאימות לאחור.
הפרויקט שלנו עליו נעבוד יהיה יישום פשוט Spring Boot Flyway שיש לו Person с first_name и last_name במסד הנתונים (משוער. תִרגוּם: Person הוא שולחן ו וirst_name и last_name - אלו השדות שבו). אנחנו רוצים לשנות את השם last_name в surname.
הנחות
לפני שניכנס לפרטים, יש כמה הנחות שעלינו לעשות לגבי היישומים שלנו. התוצאה העיקרית שאנו רוצים להשיג תהיה תהליך פשוט למדי.
הפתק. עסקי PRO-TIP. פישוט תהליכים יכול לחסוך לך הרבה כסף על תמיכה (ככל שיש לך יותר אנשים שעובדים בחברה שלך, כך תוכל לחסוך יותר כסף)!
אין צורך להחזיר את מסד הנתונים לאחור
זה מפשט את תהליך הפריסה (חלק מהחזרות מסד נתונים הן כמעט בלתי אפשריות, כגון ביטול מחיקה). אנו מעדיפים להחזיר רק אפליקציות. בדרך זו, גם אם יש לך מסדי נתונים שונים (לדוגמה, SQL ו-NoSQL), צינור הפריסה שלך ייראה זהה.
זה חייב להיות תמיד אפשרי להחזיר את האפליקציה לאחור גרסה אחת אחורה (לא יותר)
החזרה לאחור צריכה להיעשות רק כאשר יש צורך. אם יש באג בגרסה הנוכחית שלא ניתן לתקן בקלות, אנחנו אמורים להיות מסוגלים לחזור לגרסה האחרונה שעובדת. אנו מניחים שגרסת העבודה האחרונה הזו היא הקודמת. שמירה על תאימות קוד ומסד נתונים עבור יותר משגרה אחת תהיה קשה ויקרה במיוחד.
הפתק. לקריאות רבה יותר, במאמר זה נשנה את הגרסה העיקרית של האפליקציה.
שלב 1: מצב התחלתי
גרסת האפליקציה: 1.0.0
גרסת DB: v1
הערה
זה יהיה המצב הראשוני של הבקשה.
שינויים במסד הנתונים
DB מכיל 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');שינויים בקוד
האפליקציה מאחסנת נתוני אדם ב 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
+ "]";
}
}שינוי שם העמודה לא תואם לאחור
בואו נסתכל על דוגמה כיצד לשנות שם עמודה:
תשומת הלב. הדוגמה הבאה תשבור דברים בכוונה. אנו מראים זאת כדי להדגים את בעיית תאימות מסדי הנתונים.
גרסת האפליקציה: 2.0.0.BAD
גרסת DB: v2bad
הערה
השינויים הנוכחיים אינם מאפשרים לנו להפעיל שני מופעים (ישן וחדש) בו זמנית. לפיכך, אפס פריסת זמן השבתה תהיה קשה להשגה (אם לוקחים בחשבון הנחות, זה למעשה בלתי אפשרי).
בדיקת A/B
המצב הנוכחי הוא שיש לנו גרסת אפליקציה 1.0.0, פרוס בייצור, ומסד נתונים v1. אנחנו צריכים לפרוס מופע שני של האפליקציה, גרסה 2.0.0.BAD, ועדכן את מסד הנתונים ל v2bad.
שלבים:
- נפרס מופע חדש של אפליקציית הגרסה
2.0.0.BADאשר מעדכנת את מסד הנתונים לv2bad - במסד הנתונים
v2badעמודהlast_nameכבר לא קיים - הוא שונה לsurname - עדכון מסד הנתונים והיישום הצליח וחלק מהמופעים פועלים
1.0.0, אחרים - ב2.0.0.BAD. הכל מחובר למסד הנתוניםv2bad - כל המופעים של הגרסה
1.0.0יתחילו לזרוק שגיאות כי הם ינסו להכניס נתונים לעמודהlast_nameשלא קיים יותר - כל המופעים של הגרסה
2.0.0.BADיעבוד ללא בעיות
כפי שאתה יכול לראות, אם נבצע שינויים לא תואמים לאחור במסד הנתונים וביישום, בדיקת A/B היא בלתי אפשרית.
החזרת אפליקציה לאחור
בואו נניח שאחרי שניסינו לבצע פריסת A/B (משוער. לכל.: המחבר כנראה התכוון לבדיקת A/B כאן) החלטנו שעלינו להחזיר את האפליקציה לאחור לגרסה 1.0.0. נניח שאנחנו לא רוצים להחזיר את מסד הנתונים לאחור.
שלבים:
- אנו עוצרים את מופע אפליקציית הגרסה
2.0.0.BAD - מסד הנתונים עדיין
v2bad - מאז הגרסה
1.0.0לא מבין מה זהsurname, נראה שגיאות - הגיהנום השתחרר, אנחנו לא יכולים לחזור יותר
כפי שאתה יכול לראות, אם נבצע שינויים לא תואמים לאחור במסד הנתונים וביישום, לא נוכל לחזור לגרסה הקודמת.
יומני ביצוע סקריפט
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"}שינויים במסד הנתונים
סקריפט הגירה שמשנה שם last_name в surname
מקור תסריט Flyway:
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');סקריפט שמשנה שם last_name.
-- This change is backward incompatible - you can't do A/B testing
ALTER TABLE PERSON CHANGE last_name surname VARCHAR;שינויים בקוד
שינינו את שם השדה lastName על surname.
שינוי שם של עמודה באופן תואם לאחור
זהו המצב הנפוץ ביותר שאנו עלולים להיתקל בו. אנחנו צריכים לעשות שינויים לא תואמים לאחור. כבר הוכחנו שלפריסה בזמן השבתה אפס, אנחנו לא צריכים פשוט להחיל העברת מסד נתונים ללא שלבים נוספים. בחלק זה של המאמר, נבצע 3 פריסות של האפליקציה יחד עם העברות מסד נתונים כדי להשיג את התוצאה הרצויה תוך שמירה על תאימות לאחור.
הפתק. נזכיר שיש לנו מסד נתונים של גרסאות
v1. הוא מכיל עמודותfirst_nameиlast_name. אנחנו חייבים לשנותlast_nameעלsurname. יש לנו גם גרסת אפליקציה1.0.0,שעדיין לא נעשה בו שימושsurname.
שלב 2: הוסף שם משפחה
גרסת האפליקציה: 2.0.0
גרסת DB: v2
הערה
על ידי הוספת עמודה חדשה והעתקת תוכנה, אנו יוצרים שינויים במסד נתונים תואמים לאחור. יחד עם זאת, אם נחזיר את ה-JAR לאחור או ש-JAR ישן פועל, הוא לא ישבר במהלך הביצוע.
אנחנו משיקים גרסה חדשה
שלבים:
- לבצע העברת מסד נתונים כדי ליצור עמודה חדשה
surname. עכשיו גרסת ה-DB שלךv2 - העתק נתונים מ
last_nameвsurname. שים לבשאם יש לך הרבה מהנתונים האלה, עליך לשקול העברת אצווה! - כתוב את הקוד במקום שבו הם משמשים שניהם и חדשו - старый טור. עכשיו גרסת האפליקציה שלך
2.0.0 - קרא את הערך מהעמודה
surname, אם זה לאnull, או מ-last_nameאםsurnameלא מוגדר. אתה יכול למחוקgetLastName()מהקוד, מכיוון שהוא יפלוטnullבעת ביטול היישום שלך מ3.0.0до2.0.0.
אם אתה משתמש ב-Spring Boot Flyway, שני השלבים האלה יבוצעו במהלך אתחול הגרסה 2.0.0 יישומים. אם אתה מפעיל את כלי ניהול הגרסה של מסד הנתונים באופן ידני, תצטרך לעשות שני דברים שונים כדי לעשות זאת (תחילה עדכן את גרסת ה-db ידנית ולאחר מכן לפרוס את היישום החדש).
זה חשוב. זכור שהעמודה החדשה שנוצרה לא צריך להיות לא ריק. אם תבצע החזרה לאחור, האפליקציה הישנה לא יודעת על העמודה החדשה ולא תתקין אותה במהלך
Insert.אבל אם תוסיף את האילוץ הזה וה-db שלך יהיהv2, זה ידרוש הגדרת הערך של העמודה החדשה. מה שיוביל להפרות הגבלות.זה חשוב. כדאי להסיר את השיטה
getLastName(), כי בגרסה3.0.0אין מושג של עמודה בקודlast_name. המשמעות היא ש-null יוגדר שם. אתה יכול לעזוב את השיטה ולהוסיף צ'קים עבורnull, אבל פתרון הרבה יותר טוב יהיה לוודא את זה בהיגיוןgetSurname()בחרת את הערך הנכון שאינו אפס.
בדיקת A/B
המצב הנוכחי הוא שיש לנו גרסת אפליקציה 1.0.0, פרוס על הייצור, ומסד הנתונים ב v1. עלינו לפרוס מופע שני של אפליקציית הגרסה 2.0.0אשר יעדכן את מסד הנתונים ל v2.
שלבים:
- נפרס מופע חדש של אפליקציית הגרסה
2.0.0אשר מעדכנת את מסד הנתונים לv2 - בינתיים, חלק מהבקשות עובדו על ידי מופעי גרסה
1.0.0 - העדכון הצליח ויש לך מופעים מרובים של יישום הגרסה
1.0.0וגרסאות אחרות2.0.0.כולם מתקשרים עם מסד הנתונים בv2 - גרסה
1.0.0אינו משתמש בעמודת שם המשפחה במסד הנתונים, אלא בגרסה2.0.0שימושים. הם אינם מפריעים זה לזה, ולא אמורות להיות שגיאות. - גרסה
2.0.0מאחסן נתונים בעמודה הישנה והחדשה, ומבטיח תאימות לאחור
זה חשוב. אם יש לך שאילתות שסופרות פריטים על סמך ערכים מהעמודה הישנה/חדשה, עליך לזכור שכעת יש לך ערכים כפולים (סביר להניח שהם עדיין עוברים). לדוגמה, אם ברצונך לספור את מספר המשתמשים ששם משפחתם (איך שתקרא העמודה) התחיל באות
A, ואז עד להשלמת העברת הנתונים (old→newעמודה) ייתכן שיש לך נתונים לא עקביים אם תבצע שאילתה בעמודה חדשה.
החזרת אפליקציה לאחור
עכשיו יש לנו גרסת אפליקציה 2.0.0 ומסד נתונים ב v2.
שלבים:
- החזר את האפליקציה שלך לגרסה
1.0.0. - גרסה
1.0.0אינו משתמש בעמודה במסד הנתוניםsurname, כך שהחזרה לאחור אמורה להצליח
שינויים ב-DB
מסד הנתונים מכיל עמודה בשם 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');הוסף סקריפט surname.
תשומת הלב. זכור שאינך יכול להוסיף שום אילוצי NOT NULL לעמודה שאתה מוסיף. אם תחזיר את ה-JAR לאחור, לגרסה הישנה לא יהיה מושג לגבי העמודה שנוספה והיא תגדיר אותה באופן אוטומטי ל-NULL. אם יש מגבלה כזו, האפליקציה הישנה פשוט תישבר.
-- 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שינויים בקוד
אנו מאחסנים נתונים כ last_name, ו ב surname. במקביל אנו קוראים מתוך 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;
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
+ "]";
}
}שלב 3: הסרת last_name מהקוד
גרסת האפליקציה: 3.0.0
גרסת DB:v3
הערה
הערה per.: ככל הנראה, במאמר המקורי המחבר העתיק בטעות את הטקסט של בלוק זה משלב 2. בשלב זה, יש לבצע שינויים בקוד האפליקציה שמטרתם להסיר את הפונקציונליות המשתמשת בעמודה last_name.
על ידי הוספת עמודה חדשה והעתקת תוכנה, יצרנו שינויים במסד נתונים תואמים לאחור. כמו כן, אם נחזיר את ה-JAR לאחור או ש-JAR ישן פועל, הוא לא יישבר במהלך הביצוע.
החזרת אפליקציה לאחור
כרגע יש לנו גרסת אפליקציה 3.0.0 ומסד נתונים v3. גִרְסָה 3.0.0 לא שומר נתונים ל last_name. זה אומר שב surname המידע המעודכן ביותר נשמר.
שלבים:
- החזר את האפליקציה שלך לגרסה
2.0.0. - גרסה
2.0.0משתמש וlast_nameиsurname. - גרסה
2.0.0ייקחsurname, אם הוא לא אפס, אחרת -last_name
שינויים במסד הנתונים
אין שינויים מבניים במסד הנתונים. הסקריפט הבא מבוצע כדי לבצע את ההעברה הסופית של הנתונים הישנים:
-- 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;שינויים בקוד
הערה per.: גם התיאור של בלוק זה הועתק בטעות על ידי המחבר משלב 2. בהתאם להיגיון של המאמר, שינויים בקוד בשלב זה צריכים להיות מכוונים להסיר ממנו אלמנטים שעובדים עם העמודה last_name.
אנו מאחסנים נתונים כ last_name, ו ב surname. בנוסף, אנו קוראים מהטור 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 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
+ "]";
}
}שלב 4: הסרת last_name ממסד הנתונים
גרסת האפליקציה: 4.0.0
גרסת DB: v4
הערה
בשל העובדה שקוד הגרסה 3.0.0 לא השתמש בעמודה last_name, שום דבר רע לא יקרה במהלך הביצוע אם נחזור אליו 3.0.0 לאחר הסרת עמודה ממסד הנתונים.
יומני ביצוע סקריפט
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"}שינויים ב-DB
לגבי v3 אנחנו פשוט מסירים את העמודה last_name ולהוסיף הגבלות חסרות.
-- 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;שינויים בקוד
אין שינויים בקוד.
פלט
החלמנו בהצלחה שינוי שם עמודה שאינו תואם לאחור על ידי ביצוע מספר פריסות תואמות לאחור. להלן סיכום הפעולות שבוצעו:
- פריסת גרסת האפליקציה
1.0.0сv1סכימת מסד נתונים (שם עמודה =last_name) - פריסת גרסת האפליקציה
2.0.0,אשר מאחסן נתונים בlast_nameиsurname. האפליקציה קוראת מlast_name. מסד הנתונים נמצא בגרסהv2המכיל עמודות כמוlast_nameו -surname. surnameהוא עותק של last_name. (הערה: אסור לעמודה זו לכלול אילוץ לא null) - פריסת גרסת האפליקציה
3.0.0, אשר מאחסן רק נתונים בsurnameוקורא משם משפחה. באשר למסד הנתונים, ההגירה האחרונה מתרחשתlast_nameвsurname. גם מגבלה לא ריק הוסר מlast_name. מסד הנתונים נמצא כעת בגרסהv3 - פריסת גרסת האפליקציה
4.0.0- לא בוצעו שינויים בקוד. פריסת מסדי נתוניםv4, אשר מסירlast_name. כאן אתה יכול להוסיף אילוצים חסרים למסד הנתונים.
על ידי ביצוע גישה זו, אתה תמיד יכול להחזיר גרסה אחת לאחור מבלי לשבור את תאימות מסדי הנתונים/אפליקציות.
קוד
כל הקוד המשמש במאמר זה זמין בכתובת . להלן תיאור נוסף.
פרוייקטים
לאחר שיבוט המאגר, תראה את מבנה התיקיות הבא.
├── 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)סקריפטים
אתה יכול להפעיל את הסקריפטים המתוארים בסקריפטים למטה, שידגימו שינויים תואמים לאחור ובלתי תואמים במסד הנתונים.
לראות המקרה עם שינויים תואמים לאחור, הרץ:
./scripts/scenario_backward_compatible.shולראות מקרה עם שינויים לא תואמים לאחור, הרץ:
./scripts/scenario_backward_incompatible.shמסלול דגימה של מגף אביב
כל הדוגמאות לקוחות מ Spring Boot Sample Flyway.
אתה יכול להעיף מבט http://localhost:8080/flyway, יש רשימה של סקריפטים.
דוגמה זו כוללת גם את קונסולת H2 (בכתובת http://localhost:8080/h2-console) כדי שתוכל להציג את סטטוס מסד הנתונים (כתובת ברירת המחדל של jdbc היא jdbc:h2:mem:testdb).
בנוסף
קרא גם מאמרים אחרים בבלוג שלנו:
מקור: www.habr.com
