호출 가능한 Cloud 함수를 사용한 데이터 삭제

이 페이지에서는 호출 가능한 Cloud 함수를 사용하여 데이터를 삭제하는 방법을 설명합니다. 이 함수를 배포하면 모바일 앱 또는 웹사이트에서 함수를 직접 호출하여 문서 및 컬렉션을 재귀적으로 삭제할 수 있습니다. 예를 들어 이 솔루션을 사용하여 일부 사용자에게 전체 컬렉션을 삭제할 수 있는 권한을 부여할 수 있습니다.

컬렉션을 삭제하는 다른 방법은 데이터 삭제를 참조하세요.

솔루션: 호출 가능한 Cloud 함수를 사용한 데이터 삭제

다음과 같은 이유로 리소스가 제한된 모바일 앱에서 전체 컬렉션 삭제를 구현하는 것은 어려울 수 있습니다.

  • 컬렉션을 원자적으로 삭제하는 작업은 없습니다.
  • 문서를 삭제해도 하위 컬렉션의 문서는 삭제되지 않습니다.
  • 문서에 동적 하위 컬렉션이 있는 경우 지정된 경로에서 삭제할 데이터를 파악하기가 어려울 수 있습니다.
  • 500개를 초과하는 문서가 있는 컬렉션을 삭제하려면 일괄 쓰기 작업을 여러 번 수행하거나 하나씩 삭제하는 작업을 수백 번 수행해야 합니다.
  • 많은 앱에서 최종 사용자에게 전체 컬렉션을 삭제할 권한을 부여하는 것은 적절하지 않습니다.

다행히 호출 가능한 Cloud 함수를 작성하여 전체 컬렉션 또는 컬렉션 트리를 안전하고 효율적으로 삭제할 수 있습니다. 아래의 Cloud 함수는 호출 가능한 함수를 구현합니다. 즉, 로컬 함수와 마찬가지로 모바일 앱이나 웹사이트에서 직접 호출할 수 있습니다.

함수를 배포하고 데모를 사용해 보려면 샘플 코드를 참조하세요.

Cloud 함수

아래 Cloud 함수는 컬렉션과 해당 컬렉션의 모든 하위 항목을 삭제합니다.

Cloud 함수에 자체 재귀 삭제 로직을 구현하는 대신 Firebase 명령줄 인터페이스(CLI)에서 firestore:delete 명령어를 활용할 수 있습니다. firebase-tools 패키지를 사용하여 Firebase CLI의 모든 함수를 Node.js 애플리케이션으로 가져올 수 있습니다.

Firebase CLI는 Cloud Firestore REST API를 사용하여 지정된 경로에 있는 모든 문서를 찾아 개별적으로 삭제합니다. 이를 구현하기 위해 앱의 특정 데이터 계층 구조를 알아야 할 필요는 없으며, 더 이상 상위 항목이 존재하지 않는 '분리된' 문서도 찾아서 삭제합니다.

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
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
    // Snippet not yet written
    
Objective-C
참고: 이 제품은 watchOS 및 앱 클립 대상에서 사용할 수 없습니다.
    // 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
                    // ...
                }
            });
}

호출 가능한 클라우드 함수에 클라이언트 SDK를 사용하면 사용자의 인증 상태와 path 매개변수가 원격 함수로 원활하게 전달됩니다. 함수가 완료되면 클라이언트에 결과 또는 예외와 함께 콜백이 수신됩니다. Android, Apple 또는 다른 플랫폼에서 Cloud 함수를 호출하는 방법을 알아보려면 관련 문서를 읽어보시기 바랍니다.

제한사항

위에 나온 솔루션은 호출 가능한 함수로 컬렉션을 삭제하는 방법을 다루며 다음 제한사항이 있습니다.

  • 일관성 - 위의 코드는 문서를 한 번에 하나씩 삭제합니다. 진행 중인 삭제 작업이 있을 때 쿼리하면 삭제할 문서 중 일부만 삭제된 상태가 결과에 반영될 수 있습니다. 삭제 작업이 균일하게 성공 또는 실패한다는 보장이 없으므로 부분적으로만 삭제되는 경우에 대비해야 합니다.
  • 시간 제한 - 위의 함수는 시간 초과가 발생하기 전에 최대 540초 동안 실행되도록 구성됩니다. 최적의 상황에서는 삭제 코드가 초당 문서 4,000개를 삭제할 수 있습니다. 2백만 개를 초과하는 문서를 삭제해야 하는 경우 시간 초과가 발생하지 않도록 자체 서버에서 작업을 실행하는 방안을 고려해야 합니다. 자체 서버에서 컬렉션을 삭제하는 방법의 예시는 컬렉션 삭제를 참조하세요.
  • 많은 수의 문서를 삭제하면 Google Cloud 콘솔의 데이터 뷰어가 느리게 로드되거나 시간 초과 오류가 발생할 수 있습니다.