בדף הזה מוסבר איך להשתמש בפונקציית Cloud Functions שניתן להפעיל כדי למחוק נתונים. אחרי הפריסה של הפונקציה הזו, תוכלו להפעיל אותה ישירות מהאפליקציה לנייד או מהאתר כדי למחוק באופן רקורסיבי מסמכים ואוספים. לדוגמה, אפשר להשתמש בפתרון הזה כדי לתת למשתמשים נבחרים את היכולת למחוק אוספים שלמים.
דרכים אחרות למחיקת אוספים מפורטות במאמר מחיקת נתונים.
פתרון: מחיקה של נתונים באמצעות Cloud Function שניתן לקריאה
יכול להיות שיהיה קשה להטמיע מחיקה של אוספים שלמים מאפליקציה לנייד עם משאבים מוגבלים מהסיבות הבאות:
- אין פעולה שמוחקת אוסף באופן אטומי.
- מחיקת מסמך לא מוחקת את המסמכים באוספים המשניים שלו.
- אם למסמכים יש אוספים משניים דינמיים, יכול להיות שיהיה קשה לדעת אילו נתונים למחוק במסלול נתון.
- כדי למחוק אוסף של יותר מ-500 מסמכים, צריך לבצע כמה פעולות כתיבה באצווה או מאות פעולות מחיקה בודדות.
- באפליקציות רבות לא מתאים לתת למשתמשי הקצה הרשאה למחוק אוספים שלמים.
למרבה המזל, אפשר לכתוב פונקציית Cloud Function שאפשר להפעיל כדי לבצע מחיקה בטוחה ויעילה של אוספים שלמים או של עצי אוספים. פונקציית Cloud Functions שבהמשך מיישמת פונקציה שניתן להפעיל, כלומר אפשר להפעיל אותה ישירות מהאפליקציה לנייד או מהאתר, כמו שאפשר להפעיל פונקציה מקומית.
כדי לפרוס את הפונקציה ולנסות הדגמה, אפשר לעיין בקוד לדוגמה.
Cloud Function
פונקציית Cloud Functions שבהמשך מוחקת אוסף ואת כל הצאצאים שלו.
במקום להטמיע לוגיקה משלכם למחיקה רפטורית ב-Cloud Function, תוכלו להשתמש בפקודה firestore:delete
בממשק שורת הפקודה (CLI) של Firebase. אפשר לייבא כל פונקציה של Firebase CLI לאפליקציית Node.js באמצעות החבילה firebase-tools
.
ה-CLI של Firebase משתמש ב-API ל-REST של Cloud Firestore כדי למצוא את כל המסמכים בנתיב שצוין ולמחוק אותם בנפרד. כדי להטמיע את הפתרון הזה לא צריך ידע לגבי היררכיית הנתונים הספציפית של האפליקציה, והוא גם ימצא וימחק מסמכים 'יתומים' שכבר אין להם הורה.
Node.js
/** * Initiate a recursive delete of documents at a given path. * * The calling user must be authenticated and have the custom "admin" attribute * set to true on the auth token. * * This delete is NOT an atomic operation and it's possible * that it may fail after only deleting some documents. * * @param {string} data.path the document or collection path to delete. */ exports.recursiveDelete = functions .runWith({ timeoutSeconds: 540, memory: '2GB' }) .https.onCall(async (data, context) => { // Only allow admin users to execute this function. if (!(context.auth && context.auth.token && context.auth.token.admin)) { throw new functions.https.HttpsError( 'permission-denied', 'Must be an administrative user to initiate delete.' ); } const path = data.path; console.log( `User ${context.auth.uid} has requested to delete path ${path}` ); // Run a recursive delete on the given document or collection path. // The 'token' must be set in the functions config, and can be generated // at the command line by running 'firebase login:ci'. await firebase_tools.firestore .delete(path, { project: process.env.GCLOUD_PROJECT, recursive: true, force: true, token: functions.config().fb.token }); return { path: path }; });
קריאה מלקוח
כדי לקרוא לפונקציה, מקבלים הפניה לפונקציה מ-Firebase SDK ומעבירים את הפרמטרים הנדרשים:
אינטרנט
/** * Call the 'recursiveDelete' callable function with a path to initiate * a server-side delete. */ function deleteAtPath(path) { var deleteFn = firebase.functions().httpsCallable('recursiveDelete'); deleteFn({ path: path }) .then(function(result) { logMessage('Delete success: ' + JSON.stringify(result)); }) .catch(function(err) { logMessage('Delete failed, see console,'); console.warn(err); }); }
Swift
// Snippet not yet written
Objective-C
// Snippet not yet written
Kotlin+KTX
/** * Call the 'recursiveDelete' callable function with a path to initiate * a server-side delete. */ fun deleteAtPath(path: String) { val deleteFn = Firebase.functions.getHttpsCallable("recursiveDelete") deleteFn.call(hashMapOf("path" to path)) .addOnSuccessListener { // Delete Success // ... } .addOnFailureListener { // Delete Failed // ... } }
Java
/** * Call the 'recursiveDelete' callable function with a path to initiate * a server-side delete. */ public void deleteAtPath(String path) { Map<String, Object> data = new HashMap<>(); data.put("path", path); HttpsCallableReference deleteFn = FirebaseFunctions.getInstance().getHttpsCallable("recursiveDelete"); deleteFn.call(data) .addOnSuccessListener(new OnSuccessListener<HttpsCallableResult>() { @Override public void onSuccess(HttpsCallableResult httpsCallableResult) { // Delete Success // ... } }) .addOnFailureListener(new OnFailureListener() { @Override public void onFailure(@NonNull Exception e) { // Delete failed // ... } }); }
כשמשתמשים ב-SDK של הלקוח לפונקציות ניתנות לקריאה בענן, מצב האימות של המשתמשים והפרמטר path
מועברים בצורה חלקה לפונקציה מרחוק.
כשהפונקציה תסתיים, הלקוח יקבל קריאה חוזרת עם התוצאה או עם חריגה. במסמכי התיעוד מוסבר איך קוראים לפונקציה ב-Cloud מ-Android, מ-Apple או מפלטפורמה אחרת.
מגבלות
הפתרון שמוצג למעלה מראה איך מוחקים אוספים מפונקציה שניתן להפעיל, אבל חשוב לדעת על המגבלות הבאות:
- עקביות – הקוד שלמעלה מוחק מסמכים אחד אחרי השני. אם תשלחו שאילתה בזמן שמתבצעת פעולת מחיקה, יכול להיות שהתוצאות ישקפו מצב חלקי שבו רק חלק מהמסמכים המטורגטים נמחקו. בנוסף, אי אפשר להבטיח שפעולות המחיקה יצליחו או יכשלו באופן אחיד, לכן חשוב להיות מוכנים למקרים של מחיקה חלקית.
- Timeouts – הפונקציה שלמעלה מוגדרת לפעול למשך 540 שניות לכל היותר לפני שהיא תסתיים. במקרה הטוב, קוד המחיקה יכול למחוק 4,000 מסמכים בשנייה. אם אתם צריכים למחוק יותר מ-2,000,000 מסמכים, מומלץ להריץ את הפעולה בשרת שלכם כדי שהיא לא תסתיים בגלל תפוגת הזמן. דוגמה למחיקת קולקציה מהשרת שלכם מופיעה במאמר מחיקת קולקציות.
- מחיקת מספר גדול של מסמכים עלולה לגרום לטעינה איטית של חלון הצפייה בנתונים במסוף Google Cloud או להצגת שגיאת זמן קצוב לתפוגה.