פיתוח אפליקציות באמצעות GoogleSQL ל-Bigtable

אתם יכולים להשתמש ב-GoogleSQL ל-Bigtable כדי להריץ שאילתות מהאפליקציות שלכם ולהעביר פונקציות חישוב ל-Bigtable. כך מצטמצם הצורך בעיבוד בצד הלקוח. ‫GoogleSQL היא שפת שאילתות מובנית שתואמת לתקן ANSI, והיא מיושמת ב-Bigtable ובשירותים אחרים. Google Cloud מידע נוסף על שימושים אחרים ב-GoogleSQL ל-Bigtable זמין במאמר סקירה כללית על GoogleSQL ל-Bigtable.

לפני שמתחילים

חשוב לוודא שיש לכם את הפריטים הבאים:

מקרה לדוגמה

בדוגמאות במדריך הזה נעשה שימוש בטבלת weather-data לדוגמה שאפשר לגשת אליה ממופע תקופת הניסיון בחינם במסוףGoogle Cloud . נניח שהטבלה שלכם מאחסנת מידע מתחנות מזג אוויר שונות שמזוהות באמצעות station_id. כל תחנה מתעדת את הטמפרטורה בסולם פרנהייט בעמודה בשם temp_f. במקום לחשב את הטמפרטורה באפליקציה, אפשר להשתמש בשאילתת SQL כדי להמיר את הטמפרטורות האלה לסולם צלזיוס ישירות ב-Bigtable לפני שליחת התוצאות בחזרה לאפליקציה. מידע נוסף על שליחת שאילתות לטבלת נתוני מזג האוויר זמין במאמר בנושא שליחת שאילתות לנתונים לדוגמה.

שיטות מומלצות

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

  • שימוש חוזר בלקוח: יוצרים BigtableDataClient אחד לאפליקציה. הלקוח מטפל בניהול של ערוצי gRPC ומיועד לשימוש במקביל במספר רב של תהליכים. מומלץ ליצור את הלקוח הזה פעם אחת אחרי שהאפליקציה מתחילה, ואז להשתמש בו מחדש לכל שאילתה.
  • שימוש בשאילתות עם פרמטרים: משתמשים ב-PreparedStatement כדי להפריד בין הלוגיקה של השאילתה לבין ערכי הנתונים. כך Bigtable יכולה לשמור במטמון את תוכנית ההפעלה ולמנוע הזרקת SQL.
  • שימוש חוזר בהצהרות מוכנות: קוראים ל-PreparedStatement רק פעם אחת לכל מחרוזת SQL. אחסון ההצהרה ושימוש חוזר בה בכמה בקשות.

יצירת לקוח Bigtable

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

BigtableDataSettings settings = BigtableDataSettings.newBuilder()
    .setProject(PROJECT_ID)
    .setInstance(INSTANCE_ID)
    .build();

// The client is thread-safe and should be reused.
try (BigtableDataClient dataClient = BigtableDataClient.create(settings)) {
    System.out.println("Connected to: " + INSTANCE_ID);
}

הכנת השאילתה

במקום להזין את הנתונים ישירות למחרוזת SQL, מומלץ להשתמש בשאילתות עם פרמטרים. ב-GoogleSQL ל-Bigtable, שאילתות שמכילות פרמטרים מקבילות להצהרה מוכנה. כך אפשר להפריד בין הלוגיקה של השאילתה לבין ערכי הנתונים.

בדוגמה הבאה אפשר לראות איך להגדיר שאילתת GoogleSQL באמצעות placeholders של פרמטרים, כמו @sid. לאחר מכן יוצרים PreparedStatement על ידי ציון מחרוזת ה-SQL והסוגים של כל פרמטר, כדי שהשאילתה תוכל לפעול מהר יותר בפעם הבאה:

Map<String, SqlType<?>> paramTypes = new HashMap<>();
paramTypes.put("sid", SqlType.string());

String sql = "SELECT " +
             "ROUND((CAST(CAST(weather['temp_f'] AS STRING) AS INT64) - 32) * 5 / 9, 2) AS temp_celsius " +
             "FROM " + TABLE_NAME + " WHERE station_id = @sid LIMIT 1";

// Create and reuse the PreparedStatement.
PreparedStatement preparedStatement = dataClient.prepareStatement(sql, paramTypes);

הרצה של שאילתה

מזינים ערכי פרמטרים ומריצים את השאילתה. בדוגמה הבאה אפשר לראות איך משתמשים ב-PreparedStatement כדי לקשור ערכים לפרמטרים וליצור BoundStatement. הסקריפט מריץ את השאילתה ומבצע לולאה על התוצאות כדי להדפיס את ResultSet, שהיא הטמפרטורה במעלות צלזיוס:

BoundStatement boundStatement = preparedStatement.bind()
    .setStringParam("sid", stationId)
    .build();

try (ResultSet resultSet = dataClient.executeQuery(boundStatement)) {
    while (resultSet.next()) {
        System.out.println("Temp: " + resultSet.getDouble("temp_celsius"));
    }
}

בדיקת מבנה הנתונים

כשמפתחים או בודקים SQL, מומלץ לבדוק את המטא-נתונים שמשויכים לטבלה כדי להבין את מבנה הנתונים. בגלל הסכימה הגמישה של Bigtable, אם משתמשים בשאילתות SELECT *, תוצאות השאילתה יכולות להשתנות בהתאם לנתונים. לכן, אנחנו ממליצים לא להשתמש בשאילתות SELECT * באפליקציות בסביבת הייצור.

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

ResultSetMetadata metadata = resultSet.getMetadata();
System.out.print("Columns: ");
metadata.getColumnsList().forEach(col -> System.out.print(col.getName() + " "));
System.out.println();

עבודה עם קבוצות עמודות

ב-Bigtable, הנתונים מאוחסנים בקבוצות של עמודות. ‫GoogleSQL מחזירה את המשפחות האלה כרשימות של שמות וערכים שנקראות מיפויים של מסנני עמודות וערכים. כשקוראים קבוצת עמודות, Bigtable מחזיר את הסוג SqlType.Map. כשקוראים קבוצת עמודות מטבלה עם היסטוריה, כמו SELECT column FROM my_table(with_history=>true), ‏ Bigtable מחזיר סוג SqlType.historicalMap(), שהוא מיפוי של מגדירי עמודות ומערך של חותמות זמן וערכים של תאים. דוגמאות נוספות לסוגי SqlType מופיעות במאמר SqlType.

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

Map<String, SqlType<?>> paramTypes = new HashMap<>();
paramTypes.put("keyPrefix", SqlType.bytes());
String sql = String.format("SELECT weather FROM %s WHERE STARTS_WITH(_key, @keyPrefix)", TABLE_NAME);
PreparedStatement preparedStatement = dataClient.prepareStatement(sql, paramTypes);
BoundStatement boundStatement = preparedStatement.bind()
   .setBytesParam("keyPrefix", ByteString.copyFromUtf8(key))
   .build();


try (ResultSet resultSet = dataClient.executeQuery(boundStatement)) {
   while (resultSet.next()) {
     SqlType.Map<ByteString, ByteString> mapType = SqlType.mapOf(SqlType.bytes(), SqlType.bytes());
     Map<ByteString, ByteString> weatherValues = resultSet.getMap("weather", mapType);
     for (Map.Entry<ByteString, ByteString> entry : weatherValues.entrySet()) {
       System.out.printf("%s = %s\n",
         entry.getKey().toStringUtf8(),
         entry.getValue().toStringUtf8());
   }
 }
}

המאמרים הבאים