העברה מ-PostgreSQL אל Spanner (ניב GoogleSQL)

בדף הזה מוסבר איך להעביר מסד נתונים של PostgreSQL בקוד פתוח אל Spanner.

ההעברה כוללת את המשימות הבאות:

  • מיפוי סכימת PostgreSQL לסכימת Spanner.
  • יצירת מופע, מסד נתונים וסכימה של Spanner.
  • שינוי מבנה האפליקציה כדי שתפעל עם מסד הנתונים שלכם ב-Spanner.
  • העברת הנתונים.
  • אימות המערכת החדשה והעברתה לסטטוס ייצור.

במאמר הזה מפורטות גם כמה סכימות לדוגמה באמצעות טבלאות ממסד הנתונים של MusicBrainz PostgreSQL.

מיפוי של סכימת PostgreSQL ל-Spanner

השלב הראשון בהעברת מסד נתונים מ-PostgreSQL ל-Spanner הוא לקבוע אילו שינויים בסכימה צריך לבצע. משתמשים ב-pg_dump כדי ליצור הצהרות של שפת הגדרת נתונים (DDL) שמגדירות את האובייקטים במסד הנתונים של PostgreSQL, ואז משנים את ההצהרות כמו שמתואר בקטעים הבאים. אחרי שמעדכנים את הצהרות ה-DDL, משתמשים בהן כדי ליצור את מסד הנתונים במופע Spanner.

סוגי נתונים

בטבלה הבאה מתואר המיפוי של סוגי נתונים ב-PostgreSQL לסוגי נתונים ב-Spanner. מעדכנים את סוגי הנתונים בהצהרות DDL מסוגי נתונים של PostgreSQL לסוגי נתונים של Spanner.

PostgreSQL Spanner
Bigint

int8

INT64
Bigserial

serial8

INT64
bit [ (n) ] ARRAY<BOOL>
bit varying [ (n) ]

varbit [ (n) ]

ARRAY<BOOL>
Boolean

bool

BOOL
box ARRAY<FLOAT64>
bytea BYTES
character [ (n) ]

char [ (n) ]

STRING
character varying [ (n) ]

varchar [ (n) ]

STRING
cidr STRING, באמצעות סימון CIDR רגיל.
circle ARRAY<FLOAT64>
date DATE
double precision

float8

FLOAT64
inet STRING
Integer

int

int4

INT64
interval[ fields ] [ (p) ] INT64 אם הערך מאוחסן באלפיות שנייה, או STRING אם הערך מאוחסן בפורמט של מרווח זמן שמוגדר באפליקציה.
json STRING
jsonb JSON
line ARRAY<FLOAT64>
lseg ARRAY<FLOAT64>
macaddr STRING, באמצעות סימון כתובת MAC סטנדרטי.
money INT64 או STRING בשביל מספרים עם דיוק שרירותי.
numeric [ (p, s) ]

decimal [ (p, s) ]

ב-PostgreSQL, סוגי הנתונים NUMERIC ו-DECIMAL תומכים ב-217 ספרות של דיוק וב-214-1 של קנה מידה, כפי שמוגדר בהצהרת העמודה.‫

סוג הנתונים NUMERIC ב-Spanner תומך ב-38 ספרות דיוק וב-9 ספרות אחרי הנקודה העשרונית.

אם אתם צריכים דיוק גבוה יותר, כדאי לעיין במאמר אחסון נתונים מספריים עם דיוק שרירותי כדי לקבל מידע על מנגנונים חלופיים.
path ARRAY<FLOAT64>
pg_lsn סוג הנתונים הזה ספציפי ל-PostgreSQL, ולכן אין לו מקבילה ב-Spanner.
point ARRAY<FLOAT64>
polygon ARRAY<FLOAT64>
Real

float4

FLOAT64
Smallint

int2

INT64
Smallserial

serial2

INT64
Serial

serial4

INT64
text STRING
time [ (p) ] [ without time zone ] STRING, באמצעות סימון HH:MM:SS.sss.
time [ (p) ] with time zone

timetz

STRING, באמצעות סימון HH:MM:SS.sss+ZZZZ. לחלופין, אפשר לפצל את זה לשתי עמודות, אחת מהסוג TIMESTAMP ועוד אחת שמכילה את אזור הזמן.
timestamp [ (p) ] [ without time zone ] אין מקבילה. אתם יכולים לשמור את הקובץ כ-STRING או כ-TIMESTAMP לפי שיקול דעתכם.
timestamp [ (p) ] with time zone

timestamptz

TIMESTAMP
tsquery אין מקבילה. במקום זאת, צריך להגדיר מנגנון אחסון באפליקציה.
tsvector אין מקבילה. במקום זאת, צריך להגדיר מנגנון אחסון באפליקציה.
txid_snapshot אין מקבילה. במקום זאת, צריך להגדיר מנגנון אחסון באפליקציה.
uuid STRING או BYTES
xml STRING

מפתחות ראשיים

בטבלאות במסד הנתונים של Spanner שמוסיפים להן נתונים לעיתים קרובות, מומלץ להימנע משימוש במפתחות ראשיים שגדלים או קטנים באופן מונוטוני, כי הגישה הזו גורמת לנקודות חמות במהלך פעולות כתיבה. במקום זאת, צריך לשנות את ההצהרות של DDL CREATE TABLE כך שישתמשו בשיטות נתמכות של מפתח ראשי. אם אתם משתמשים בתכונה של PostgreSQL כמו UUID סוג נתונים או פונקציה, SERIAL סוגי נתונים, IDENTITY עמודה או רצף, אתם יכולים להשתמש בשיטות להעברת מפתחות שנוצרו אוטומטית שאנחנו ממליצים עליהן.

חשוב לזכור: אחרי שמגדירים את המפתח הראשי, אי אפשר להוסיף או להסיר עמודה של מפתח ראשי, או לשנות ערך של מפתח ראשי, בלי למחוק את הטבלה וליצור אותה מחדש. מידע נוסף על הגדרת המפתח הראשי זמין במאמר סכימה ומודל נתונים – מפתחות ראשיים.

במהלך ההעברה, יכול להיות שתצטרכו לשמור חלק ממפתחות המספרים השלמים הקיימים שגדלים באופן מונוטוני. אם אתם צריכים לשמור מפתחות כאלה בטבלה שמתעדכנת לעיתים קרובות עם הרבה פעולות על המפתחות האלה, אתם יכולים למנוע יצירת נקודות חמות על ידי הוספת מספר פסאודו-אקראי כמחרוזת לפני המפתח הקיים. הטכניקה הזו גורמת ל-Spanner להפיץ מחדש את השורות. מידע נוסף על השימוש בגישה הזו זמין במאמר What DBAs need to know about Spanner, part 1: Keys and indexes.

מפתחות זרים ושלמות רפרנציאלית

מידע נוסף על תמיכה במפתחות זרים ב-Spanner

אינדקסים

אינדקסים של b-tree ב-PostgreSQL דומים לאינדקסים משניים ב-Spanner. במסד נתונים של Spanner, משתמשים באינדקסים משניים כדי ליצור אינדקס לעמודות שמתבצע בהן חיפוש לעיתים קרובות, וכדי להחליף אילוצים של UNIQUE שצוינו בטבלאות. לדוגמה, אם ב-DDL של PostgreSQL מופיעה ההצהרה הבאה:

   CREATE TABLE customer (
      id CHAR (5) PRIMARY KEY,
      first_name VARCHAR (50),
      last_name VARCHAR (50),
      email VARCHAR (50) UNIQUE
     );

ההצהרה הזו תשמש אתכם ב-Spanner DDL:

   CREATE TABLE customer (
      id STRING(5),
      first_name STRING(50),
      last_name STRING(50),
      email STRING(50)
      ) PRIMARY KEY (id);

    CREATE UNIQUE INDEX customer_emails ON customer(email);

כדי למצוא את האינדקסים של כל אחת מהטבלאות שלכם ב-PostgreSQL, מריצים את פקודת המטא \di ב-psql.

אחרי שקובעים את האינדקסים שצריך, מוסיפים משפטי CREATE INDEX כדי ליצור אותם. פועלים לפי ההנחיות שבקטע יצירת אינדקסים.

ב-Spanner, אינדקסים מיושמים כטבלאות, ולכן יצירת אינדקס לעמודות עם ערכים שעולים באופן מונוטוני (כמו עמודות שמכילות נתוני TIMESTAMP) עלולה לגרום לנקודה חמה. מידע נוסף על שיטות למניעת נקודות חמות זמין במאמר What DBAs need to know about Spanner, part 1: Keys and indexes (מה שבעלי תפקידים של DBA צריכים לדעת על Spanner, חלק 1: מפתחות ואינדקסים).

בדיקת מגבלות

מידע נוסף על תמיכה במגבלות של CHECK ב-Spanner

אובייקטים אחרים במסד הנתונים

עליך ליצור את הפונקציונליות של האובייקטים הבאים בלוגיקה של האפליקציה:

  • תצוגות
  • טריגרים
  • תהליכים מאוחסנים
  • פונקציות בהגדרת המשתמש (UDF)
  • עמודות שמשתמשות בסוגי הנתונים serial כגנרטורים של רצפים

כשמעבירים את הפונקציונליות הזו ללוגיקה של האפליקציה, חשוב לזכור את הטיפים הבאים:

יצירת מכונת Spanner

אחרי שמעדכנים את הצהרות ה-DDL כך שיתאימו לדרישות של סכימת Spanner, משתמשים בהן כדי ליצור את מסד הנתונים ב-Spanner.

  1. יוצרים מופע Spanner. כדי לקבוע את ההגדרה האזורית הנכונה ואת קיבולת החישוב שתתמוך ביעדי הביצועים שלכם, פועלים לפי ההנחיות שבקטע Instances.

  2. יוצרים את מסד הנתונים באמצעות המסוף Google Cloud או הכלי gcloud של שורת הפקודה:

המסוף

  1. כניסה לדף Instances
  2. לוחצים על השם של המופע שבו רוצים ליצור את מסד הנתונים לדוגמה כדי לפתוח את הדף פרטי המופע.
  3. לוחצים על יצירת מסד נתונים.
  4. מקלידים שם למסד הנתונים ולוחצים על המשך.
  5. בקטע Define your database schema (הגדרת סכימת מסד הנתונים), מעבירים את המתג של האפשרות Edit as text (עריכה כטקסט).
  6. מעתיקים את הצהרות ה-DDL ומדביקים אותן בשדה הצהרות DDL.
  7. לוחצים על יצירה.

gcloud

  1. מתקינים את ה-CLI של gcloud.
  2. משתמשים בפקודה gcloud spanner databases create כדי ליצור את מסד הנתונים:
    gcloud spanner databases create DATABASE_NAME --instance=INSTANCE_NAME
    --ddl='DDL1' --ddl='DDL2'
  • DATABASE_NAME הוא השם של מסד הנתונים.
  • INSTANCE_NAME הוא מופע Spanner שיצרתם.
  • DDLn הם הצהרות ה-DDL ששיניתם.

אחרי שיוצרים את מסד הנתונים, פועלים לפי ההוראות שבמאמר הקצאת תפקידי IAM כדי ליצור חשבונות משתמשים ולהעניק הרשאות למופע ולמסד הנתונים של Spanner.

שינוי מבנה האפליקציות ושכבות הגישה לנתונים

בנוסף לקוד שנדרש כדי להחליף את אובייקטי מסד הנתונים הקודמים, צריך להוסיף לוגיקה של אפליקציה כדי לטפל בפונקציונליות הבאה:

  • גיבוב של מפתחות ראשיים לפעולות כתיבה, לטבלאות עם שיעורי כתיבה גבוהים למפתחות רציפים.
  • אימות נתונים שלא נכללים כבר במגבלות של CHECK.
  • בדיקות של שלמות רפרנציאלית שלא מכוסות כבר על ידי מפתחות זרים, שילוב טבלאות או לוגיקה של אפליקציות, כולל פונקציונליות שמטופלת על ידי טריגרים בסכימה של PostgreSQL.

מומלץ להשתמש בתהליך הבא כשמבצעים רפקטורינג:

  1. מוצאים את כל קוד האפליקציה שיש לו גישה למסד הנתונים, ומבצעים לו רה-פקטורינג למודול או לספרייה יחידים. כך תוכלו לדעת בדיוק איזה קוד ניגש למסד הנתונים, ולכן בדיוק איזה קוד צריך לשנות.
  2. לכתוב קוד שמבצע פעולות קריאה וכתיבה במופע Spanner, ולספק פונקציונליות מקבילה לקוד המקורי שקורא וכותב ב-PostgreSQL. במהלך פעולות כתיבה, צריך לעדכן את כל השורה, ולא רק את העמודות שהשתנו, כדי לוודא שהנתונים ב-Spanner זהים לנתונים ב-PostgreSQL.
  3. לכתוב קוד שמחליף את הפונקציונליות של אובייקטים ופונקציות במסד הנתונים שלא זמינים ב-Spanner.

העברת נתונים

אחרי שיוצרים את מסד הנתונים של Spanner ומבצעים רפקטורינג של קוד האפליקציה, אפשר להעביר את הנתונים אל Spanner.

  1. משתמשים בפקודה COPY של PostgreSQL כדי להעביר נתונים לקובצי ‎ .csv.
  2. מעלים את קובצי ה-CSV ל-Cloud Storage.

    1. יצירת קטגוריה של Cloud Storage
    2. במסוף Cloud Storage, לוחצים על שם הקטגוריה כדי לפתוח את דפדפן הקטגוריות.
    3. לוחצים על העלאת קבצים.
    4. עוברים אל הספרייה שמכילה את קובצי ה-CSV ובוחרים אותם.
    5. לוחצים על פתיחה.
  3. יוצרים אפליקציה לייבוא נתונים ל-Spanner. האפליקציה הזו יכולה להשתמש ב-Dataflow או בספריות הלקוח ישירות. כדי להשיג את הביצועים הטובים ביותר, חשוב לפעול לפי ההנחיות שמפורטות במאמר בנושא שיטות מומלצות לטעינת נתונים בכמות גדולה.

בדיקות

בודקים את כל הפונקציות של האפליקציה מול מופע Spanner כדי לוודא שהן פועלות כמצופה. מריצים עומסי עבודה ברמת הייצור כדי לוודא שהביצועים עונים על הצרכים שלכם. מעדכנים את קיבולת המחשוב לפי הצורך כדי להשיג את יעדי הביצועים שהוצבו.

מעבר למערכת החדשה

אחרי שתסיימו את הבדיקה הראשונית של האפליקציה, תפעילו את המערכת החדשה באמצעות אחד מהתהליכים הבאים. העברה אופליין היא הדרך הפשוטה ביותר להעברה. עם זאת, הגישה הזו גורמת לאפליקציה להיות לא זמינה למשך תקופה מסוימת, ולא מספקת אפשרות לחזור לגרסה קודמת אם מתגלות בעיות בנתונים בשלב מאוחר יותר. כדי לבצע העברה אופליין:

  1. מחיקת כל הנתונים במסד הנתונים של Spanner.
  2. משביתים את האפליקציה שמכוונת למסד הנתונים של PostgreSQL.
  3. מייצאים את כל הנתונים ממסד הנתונים של PostgreSQL ומייבאים אותם למסד הנתונים של Spanner, כמו שמתואר בסקירה כללית על העברה.
  4. מפעילים את האפליקציה שמכוונת למסד הנתונים של Spanner.

    תהליך העברת נתונים אופליין.

אפשר לבצע מיגרציה פעילה, אבל היא מצריכה שינויים משמעותיים באפליקציה כדי לתמוך במיגרציה.

דוגמאות להעברת סכימה

בדוגמאות האלה מוצגות ההצהרות CREATE TABLE של כמה טבלאות בסכימה של מסד הנתונים MusicBrainz PostgreSQL. כל דוגמה כוללת גם את סכימת PostgreSQL וגם את סכימת Spanner.

artist_credit table

GoogleSQL

CREATE TABLE artist_credit (
 hashed_id STRING(4),
 id INT64,
 name STRING(MAX) NOT NULL,
 artist_count INT64 NOT NULL,
 ref_count INT64,
 created TIMESTAMP OPTIONS (
    allow_commit_timestamp = true
 ),
) PRIMARY KEY(hashed_id, id);

PostgreSQL

CREATE TABLE artist_credit (
 id SERIAL,
 name VARCHAR NOT NULL,
 artist_count SMALLINT NOT NULL,
 ref_count INTEGER DEFAULT 0,
 created TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

טבלת הקלטות

GoogleSQL

CREATE TABLE recording (
  hashed_id STRING(36),
  id INT64,
  gid STRING(36) NOT NULL,
  name STRING(MAX) NOT NULL,
  artist_credit_hid STRING(36) NOT NULL,
  artist_credit_id INT64 NOT NULL,
  length INT64,
  comment STRING(255) NOT NULL,
  edits_pending INT64 NOT NULL,
  last_updated TIMESTAMP OPTIONS (
     allow_commit_timestamp = true
  ),
  video BOOL NOT NULL,
) PRIMARY KEY(hashed_id, id);

PostgreSQL

CREATE TABLE recording (
  id SERIAL,
  gid UUID NOT NULL,
  name VARCHAR NOT NULL,
  artist_credit INTEGER NOT NULL, -- references artist_credit.id
  length INTEGER CHECK (length IS NULL OR length > 0),
  comment VARCHAR(255) NOT NULL DEFAULT '',
  edits_pending INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >= 0),
  last_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  video BOOLEAN NOT NULL DEFAULT FALSE
);

recording-alias table

GoogleSQL

CREATE TABLE recording_alias (
  hashed_id STRING(36)  NOT NULL,
  id INT64  NOT NULL,
  alias_id INT64,
  name STRING(MAX)  NOT NULL,
  locale STRING(MAX),
  edits_pending INT64  NOT NULL,
  last_updated TIMESTAMP NOT NULL OPTIONS (
     allow_commit_timestamp = true
  ),
  type INT64,
  sort_name STRING(MAX)  NOT NULL,
  begin_date_year INT64,
  begin_date_month INT64,
  begin_date_day INT64,
  end_date_year INT64,
  end_date_month INT64,
  end_date_day INT64,
  primary_for_locale BOOL NOT NULL,
  ended BOOL NOT NULL,
) PRIMARY KEY(hashed_id, id, alias_id),
 INTERLEAVE IN PARENT recording ON DELETE NO ACTION;

PostgreSQL

CREATE TABLE recording_alias (
  id SERIAL, --PK
  recording INTEGER NOT NULL, -- references recording.id
  name VARCHAR NOT NULL,
  locale TEXT,
  edits_pending INTEGER NOT NULL DEFAULT 0 CHECK (edits_pending >=0),
  last_updated TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  type INTEGER, -- references recording_alias_type.id
  sort_name VARCHAR NOT NULL,
  begin_date_year SMALLINT,
  begin_date_month SMALLINT,
  begin_date_day SMALLINT,
  end_date_year SMALLINT,
  end_date_month SMALLINT,
  end_date_day SMALLINT,
  primary_for_locale BOOLEAN NOT NULL DEFAULT false,
  ended BOOLEAN NOT NULL DEFAULT FALSE
  -- CHECK constraint skipped for brevity
);