Borra datos con una Cloud Function que admite llamadas

En esta página se describe cómo usar una Cloud Function que admite llamadas para borrar datos. Una vez que implementes la función, podrás llamarla directamente desde tu sitio web o app para dispositivos móviles a fin de borrar documentos y colecciones de manera recurrente. Por ejemplo, puedes usar la solución para otorgar a usuarios específicos la capacidad de borrar colecciones completas.

Si quieres conocer otras formas de borrar colecciones, consulta cómo borrar datos.

Solución: Borra datos con una Cloud Function que admite llamadas

Implementar la eliminación de colecciones enteras desde una app para dispositivos móviles con recursos limitados puede ser difícil por estos motivos:

  • No existe una operación que borre una colección de forma atómica.
  • Si borras un documento, no se borrarán los documentos de sus subcolecciones.
  • Si tus documentos tienen subcolecciones dinámicas, puede ser difícil saber qué datos borrar de una ruta de acceso determinada.
  • Se requieren varias operaciones de escritura en lotes o cientos de eliminaciones individuales para borrar una colección de más de 500 documentos.
  • En muchas apps, no es recomendable otorgar permiso a los usuarios finales para borrar colecciones completas.

Afortunadamente, puedes escribir una Cloud Function que admite llamadas para ejecutar eliminaciones seguras y eficaces de colecciones completas o árboles de colecciones. La siguiente Cloud Function se implementa como función que admite llamadas, es decir, puedes llamarla directamente desde tu sitio web o app para dispositivos móviles como lo harías con una función local.

Para implementar la función y probar una demostración, consulta el código de muestra.

Cloud Function

La Cloud Function de más abajo permite borrar una colección y todas sus subcolecciones.

En lugar de implementar tu lógica de eliminación recurrente para la Cloud Function, puedes aprovechar el comando firestore:delete en la interfaz de línea de comandos (CLI) de Firebase. Puedes importar cualquier función de Firebase CLI a tu aplicación de Node.js con el paquete de firebase-tools.

Firebase CLI usa la API de REST de Cloud Firestore para buscar todos los documentos en la ruta de acceso especificada y borrarlos de forma individual. Esta implementación no requiere conocer la jerarquía de datos específica de tu app. Incluso buscará y borrará documentos "huérfanos" que ya no tienen un elemento superior.

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 
    };
  });

Invocación al cliente

Para llamar a la función, obtén una referencia a la función desde el SDK de Firebase y pasa los parámetros obligatorios:

Web
/**
 * 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
Nota: Este producto no se encuentra disponible en los destinos de watchOS ni de App Clips.
    // Snippet not yet written
    
Objective-C
Nota: Este producto no se encuentra disponible en los destinos de watchOS ni de App Clips.
    // Snippet not yet written
    

Kotlin

/**
 * 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
                    // ...
                }
            });
}

Cuando usas el SDK cliente para las Cloud Functions que admiten llamadas, el parámetro path y el estado de autenticación del usuario se pasan sin problemas a la función remota. Cuando se completa la función, el cliente recibirá una devolución de llamada con el resultado o una excepción. Si quieres aprender a llamar a una Cloud Function desde Android, Apple o una plataforma distinta, consulta la documentación.

Limitaciones

La solución anterior muestra cómo borrar colecciones desde una función que admite llamadas. Sin embargo, debes tener en cuenta estas limitaciones:

  • Coherencia: El código anterior borra un documento por vez. Si realizas una consulta cuando haya una operación de eliminación en curso, es posible que tus resultados reflejen un estado parcialmente completo en el que se borren solo algunos documentos objetivo. Tampoco hay garantía de que se efectúen o no las operaciones de eliminación de manera uniforme, por lo tanto, debes estar preparado para manejar casos de eliminación parcial.
  • Tiempos de espera: La función anterior está configurada para ejecutarse durante un período máximo de 540 segundos antes de que se agote el tiempo de espera. En el mejor de los casos, el código de eliminación puede borrar 4,000 documentos por segundo. Si necesitas borrar más de 2,000,000 de documentos, recomendamos que ejecutes la operación en tu propio servidor para que no se agote el tiempo de espera. Si quieres ver un ejemplo de cómo borrar una colección desde tu propio servidor, consulta cómo borrar colecciones.
  • Si borras una gran cantidad de documentos, es posible que el visor de datos de la consola de Google Cloud se cargue lentamente o muestre un error de tiempo de espera.