programing

Firestore에서 모든 하위 컬렉션과 중첩된 하위 컬렉션이 있는 문서 삭제

css3 2023. 6. 14. 22:07

Firestore에서 모든 하위 컬렉션과 중첩된 하위 컬렉션이 있는 문서 삭제

문서의 모든 컬렉션과 중첩된 하위 컬렉션을 사용하여 문서를 삭제하려면 어떻게 해야 합니까?(기능환경 개선)

RDB에서 다음을 수행할 수 있습니다.ref.child('../someNode).setValue(null)그러면 원하는 동작이 완료됩니다.

저는 당신이 원하는 삭제 동작을 달성할 수 있는 두 가지 방법을 생각할 수 있습니다. 둘 다 엄청난 단점이 있습니다.

  1. 모든 문서를 스파이더 처리하여 일괄 삭제하는 '슈퍼' 기능을 만듭니다.이 기능은 복잡하고 변경에 취약하며 실행 시간이 오래 걸릴 수 있습니다.

  2. 각 문서 유형에 대해 'onDelete' 트리거를 추가하고 직접 하위 컬렉션을 삭제합니다.루트 문서에서 삭제를 호출하면 삭제 호출이 '트리' 아래로 전파됩니다.이는 느리고 확장성이 형편없으며 엄청난 기능 실행 부하로 인해 비용이 많이 듭니다.

'GROUP'과 그 모든 것이 어린이라는 것을 삭제해야 한다고 상상해 보십시오.#1에서는 매우 혼란스럽고 #2에서는 가격이 비쌀 것입니다(문서당 1개의 함수 호출).

groups > GROUP > projects > PROJECT > files > FILE > assets > ASSET
                                                   > urls > URL
                                    > members > MEMBER
               > questions > QUESTION > answers > ANSWER > replies > REPLY
                                      > comments > COMMENT
               > resources > RESOURCE > submissions > SUBMISSION
                                      > requests > REQUEST

문서와 중첩된 모든 하위 컬렉션을 삭제할 수 있는 우수한 방법이 있습니까?

당신이 콘솔에서 할 수 있다는 것을 고려하면 가능할 것입니다.

화재 기지 문서에 따르면:
https://firebase.google.com/docs/firestore/solutions/://firebase.google.com/docs/firestore/solutions/delete-collections
중첩된 하위 컬렉션이 있는 컬렉션을 삭제하는 것은 서버 측의 node-JS를 사용하여 쉽고 깔끔하게 수행할 수 있습니다.

const client = require('firebase-tools');
await client.firestore
      .delete(collectionPath, {
        project: process.env.GCLOUD_PROJECT,
        recursive: true,
        yes: true
      }); 

불행히도, 당신의 분석은 정확하고 이 사용 사례는 많은 의식을 필요로 합니다.공식 문서에 따르면, 한 의 샷에서 딥 삭제를 지원하지 않습니다.firestore클라이언트 라이브러리나 rest-api 또는cli 공구, 공구

cli는 오픈 소스이며 구현은 https://github.com/firebase/firebase-tools/blob/master/src/firestore/delete.js 에 있습니다.그들은 기본적으로 당신이 질문에서 설명한 옵션 1을 구현했습니다. 그래서 당신은 거기서 영감을 얻을 수 있습니다.

옵션 1과 2는 모두 이상적인 상황과는 거리가 멀고 솔루션을 100% 안정적으로 만들기 위해서는 장기간 실행 절차에서 오류가 발생하면 시스템이 정의되지 않은 상태로 유지되므로 삭제 작업을 통해 대기열을 지속적으로 유지해야 합니다.

재귀적 클라우드 함수 호출이 매우 쉽게 잘못될 수 있기 때문에 원시 옵션 2로 가는 것은 권장하지 않습니다. 예를 들어 최대 한계에 도달하는 것과 같습니다.

https://github.com/firebase/firebase-tools/blob/master/src/firestore/delete.js 의 전체 소스 아래에서 링크가 변경된 경우:

"use strict";

var clc = require("cli-color");
var ProgressBar = require("progress");

var api = require("../api");
var firestore = require("../gcp/firestore");
var FirebaseError = require("../error");
var logger = require("../logger");
var utils = require("../utils");

/**
 * Construct a new Firestore delete operation.
 *
 * @constructor
 * @param {string} project the Firestore project ID.
 * @param {string} path path to a document or collection.
 * @param {boolean} options.recursive true if the delete should be recursive.
 * @param {boolean} options.shallow true if the delete should be shallow (non-recursive).
 * @param {boolean} options.allCollections true if the delete should universally remove all collections and docs.
 */
function FirestoreDelete(project, path, options) {
  this.project = project;
  this.path = path;
  this.recursive = Boolean(options.recursive);
  this.shallow = Boolean(options.shallow);
  this.allCollections = Boolean(options.allCollections);

  // Remove any leading or trailing slashes from the path
  if (this.path) {
    this.path = this.path.replace(/(^\/+|\/+$)/g, "");
  }

  this.isDocumentPath = this._isDocumentPath(this.path);
  this.isCollectionPath = this._isCollectionPath(this.path);

  this.allDescendants = this.recursive;
  this.parent = "projects/" + project + "/databases/(default)/documents";

  // When --all-collections is passed any other flags or arguments are ignored
  if (!options.allCollections) {
    this._validateOptions();
  }
}

/**
 * Validate all options, throwing an exception for any fatal errors.
 */
FirestoreDelete.prototype._validateOptions = function() {
  if (this.recursive && this.shallow) {
    throw new FirebaseError("Cannot pass recursive and shallow options together.");
  }

  if (this.isCollectionPath && !this.recursive && !this.shallow) {
    throw new FirebaseError("Must pass recursive or shallow option when deleting a collection.");
  }

  var pieces = this.path.split("/");

  if (pieces.length === 0) {
    throw new FirebaseError("Path length must be greater than zero.");
  }

  var hasEmptySegment = pieces.some(function(piece) {
    return piece.length === 0;
  });

  if (hasEmptySegment) {
    throw new FirebaseError("Path must not have any empty segments.");
  }
};

/**
 * Determine if a path points to a document.
 *
 * @param {string} path a path to a Firestore document or collection.
 * @return {boolean} true if the path points to a document, false
 * if it points to a collection.
 */
FirestoreDelete.prototype._isDocumentPath = function(path) {
  if (!path) {
    return false;
  }

  var pieces = path.split("/");
  return pieces.length % 2 === 0;
};

/**
 * Determine if a path points to a collection.
 *
 * @param {string} path a path to a Firestore document or collection.
 * @return {boolean} true if the path points to a collection, false
 * if it points to a document.
 */
FirestoreDelete.prototype._isCollectionPath = function(path) {
  if (!path) {
    return false;
  }

  return !this._isDocumentPath(path);
};

/**
 * Construct a StructuredQuery to find descendant documents of a collection.
 *
 * See:
 * https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery
 *
 * @param {boolean} allDescendants true if subcollections should be included.
 * @param {number} batchSize maximum number of documents to target (limit).
 * @param {string=} startAfter document name to start after (optional).
 * @return {object} a StructuredQuery.
 */
FirestoreDelete.prototype._collectionDescendantsQuery = function(
  allDescendants,
  batchSize,
  startAfter
) {
  var nullChar = String.fromCharCode(0);

  var startAt = this.parent + "/" + this.path + "/" + nullChar;
  var endAt = this.parent + "/" + this.path + nullChar + "/" + nullChar;

  var where = {
    compositeFilter: {
      op: "AND",
      filters: [
        {
          fieldFilter: {
            field: {
              fieldPath: "__name__",
            },
            op: "GREATER_THAN_OR_EQUAL",
            value: {
              referenceValue: startAt,
            },
          },
        },
        {
          fieldFilter: {
            field: {
              fieldPath: "__name__",
            },
            op: "LESS_THAN",
            value: {
              referenceValue: endAt,
            },
          },
        },
      ],
    },
  };

  var query = {
    structuredQuery: {
      where: where,
      limit: batchSize,
      from: [
        {
          allDescendants: allDescendants,
        },
      ],
      select: {
        fields: [{ fieldPath: "__name__" }],
      },
      orderBy: [{ field: { fieldPath: "__name__" } }],
    },
  };

  if (startAfter) {
    query.structuredQuery.startAt = {
      values: [{ referenceValue: startAfter }],
      before: false,
    };
  }

  return query;
};

/**
 * Construct a StructuredQuery to find descendant documents of a document.
 * The document itself will not be included
 * among the results.
 *
 * See:
 * https://firebase.google.com/docs/firestore/reference/rest/v1beta1/StructuredQuery
 *
 * @param {boolean} allDescendants true if subcollections should be included.
 * @param {number} batchSize maximum number of documents to target (limit).
 * @param {string=} startAfter document name to start after (optional).
 * @return {object} a StructuredQuery.
 */
FirestoreDelete.prototype._docDescendantsQuery = function(allDescendants, batchSize, startAfter) {
  var query = {
    structuredQuery: {
      limit: batchSize,
      from: [
        {
          allDescendants: allDescendants,
        },
      ],
      select: {
        fields: [{ fieldPath: "__name__" }],
      },
      orderBy: [{ field: { fieldPath: "__name__" } }],
    },
  };

  if (startAfter) {
    query.structuredQuery.startAt = {
      values: [{ referenceValue: startAfter }],
      before: false,
    };
  }

  return query;
};

/**
 * Query for a batch of 'descendants' of a given path.
 *
 * For document format see:
 * https://firebase.google.com/docs/firestore/reference/rest/v1beta1/Document
 *
 * @param {boolean} allDescendants true if subcollections should be included,
 * @param {number} batchSize the maximum size of the batch.
 * @param {string=} startAfter the name of the document to start after (optional).
 * @return {Promise<object[]>} a promise for an array of documents.
 */
FirestoreDelete.prototype._getDescendantBatch = function(allDescendants, batchSize, startAfter) {
  var url;
  var body;
  if (this.isDocumentPath) {
    url = this.parent + "/" + this.path + ":runQuery";
    body = this._docDescendantsQuery(allDescendants, batchSize, startAfter);
  } else {
    url = this.parent + ":runQuery";
    body = this._collectionDescendantsQuery(allDescendants, batchSize, startAfter);
  }

  return api
    .request("POST", "/v1beta1/" + url, {
      auth: true,
      data: body,
      origin: api.firestoreOrigin,
    })
    .then(function(res) {
      // Return the 'document' property for each element in the response,
      // where it exists.
      return res.body
        .filter(function(x) {
          return x.document;
        })
        .map(function(x) {
          return x.document;
        });
    });
};

/**
 * Progress bar shared by the class.
 */
FirestoreDelete.progressBar = new ProgressBar("Deleted :current docs (:rate docs/s)", {
  total: Number.MAX_SAFE_INTEGER,
});

/**
 * Repeatedly query for descendants of a path and delete them in batches
 * until no documents remain.
 *
 * @return {Promise} a promise for the entire operation.
 */
FirestoreDelete.prototype._recursiveBatchDelete = function() {
  var self = this;

  // Tunable deletion parameters
  var readBatchSize = 7500;
  var deleteBatchSize = 250;
  var maxPendingDeletes = 15;
  var maxQueueSize = deleteBatchSize * maxPendingDeletes * 2;

  // All temporary variables for the deletion queue.
  var queue = [];
  var numPendingDeletes = 0;
  var pagesRemaining = true;
  var pageIncoming = false;
  var lastDocName;

  var failures = [];
  var retried = {};

  var queueLoop = function() {
    if (queue.length == 0 && numPendingDeletes == 0 && !pagesRemaining) {
      return true;
    }

    if (failures.length > 0) {
      logger.debug("Found " + failures.length + " failed deletes, failing.");
      return true;
    }

    if (queue.length <= maxQueueSize && pagesRemaining && !pageIncoming) {
      pageIncoming = true;

      self
        ._getDescendantBatch(self.allDescendants, readBatchSize, lastDocName)
        .then(function(docs) {
          pageIncoming = false;

          if (docs.length == 0) {
            pagesRemaining = false;
            return;
          }

          queue = queue.concat(docs);
          lastDocName = docs[docs.length - 1].name;
        })
        .catch(function(e) {
          logger.debug("Failed to fetch page after " + lastDocName, e);
          pageIncoming = false;
        });
    }

    if (numPendingDeletes > maxPendingDeletes) {
      return false;
    }

    if (queue.length == 0) {
      return false;
    }

    var toDelete = [];
    var numToDelete = Math.min(deleteBatchSize, queue.length);

    for (var i = 0; i < numToDelete; i++) {
      toDelete.push(queue.shift());
    }

    numPendingDeletes++;
    firestore
      .deleteDocuments(self.project, toDelete)
      .then(function(numDeleted) {
        FirestoreDelete.progressBar.tick(numDeleted);
        numPendingDeletes--;
      })
      .catch(function(e) {
        // For server errors, retry if the document has not yet been retried.
        if (e.status >= 500 && e.status < 600) {
          logger.debug("Server error deleting doc batch", e);

          // Retry each doc up to one time
          toDelete.forEach(function(doc) {
            if (retried[doc.name]) {
              logger.debug("Failed to delete doc " + doc.name + " multiple times.");
              failures.push(doc.name);
            } else {
              retried[doc.name] = true;
              queue.push(doc);
            }
          });
        } else {
          logger.debug("Fatal error deleting docs ", e);
          failures = failures.concat(toDelete);
        }

        numPendingDeletes--;
      });

    return false;
  };

  return new Promise(function(resolve, reject) {
    var intervalId = setInterval(function() {
      if (queueLoop()) {
        clearInterval(intervalId);

        if (failures.length == 0) {
          resolve();
        } else {
          reject("Failed to delete documents " + failures);
        }
      }
    }, 0);
  });
};

/**
 * Delete everything under a given path. If the path represents
 * a document the document is deleted and then all descendants
 * are deleted.
 *
 * @return {Promise} a promise for the entire operation.
 */
FirestoreDelete.prototype._deletePath = function() {
  var self = this;
  var initialDelete;
  if (this.isDocumentPath) {
    var doc = { name: this.parent + "/" + this.path };
    initialDelete = firestore.deleteDocument(doc).catch(function(err) {
      logger.debug("deletePath:initialDelete:error", err);
      if (self.allDescendants) {
        // On a recursive delete, we are insensitive to
        // failures of the initial delete
        return Promise.resolve();
      }

      // For a shallow delete, this error is fatal.
      return utils.reject("Unable to delete " + clc.cyan(this.path));
    });
  } else {
    initialDelete = Promise.resolve();
  }

  return initialDelete.then(function() {
    return self._recursiveBatchDelete();
  });
};

/**
 * Delete an entire database by finding and deleting each collection.
 *
 * @return {Promise} a promise for all of the operations combined.
 */
FirestoreDelete.prototype.deleteDatabase = function() {
  var self = this;
  return firestore
    .listCollectionIds(this.project)
    .catch(function(err) {
      logger.debug("deleteDatabase:listCollectionIds:error", err);
      return utils.reject("Unable to list collection IDs");
    })
    .then(function(collectionIds) {
      var promises = [];

      logger.info("Deleting the following collections: " + clc.cyan(collectionIds.join(", ")));

      for (var i = 0; i < collectionIds.length; i++) {
        var collectionId = collectionIds[i];
        var deleteOp = new FirestoreDelete(self.project, collectionId, {
          recursive: true,
        });

        promises.push(deleteOp.execute());
      }

      return Promise.all(promises);
    });
};

/**
 * Check if a path has any children. Useful for determining
 * if deleting a path will affect more than one document.
 *
 * @return {Promise<boolean>} a promise that retruns true if the path has
 * children and false otherwise.
 */
FirestoreDelete.prototype.checkHasChildren = function() {
  return this._getDescendantBatch(true, 1).then(function(docs) {
    return docs.length > 0;
  });
};

/**
 * Run the delete operation.
 */
FirestoreDelete.prototype.execute = function() {
  var verifyRecurseSafe;
  if (this.isDocumentPath && !this.recursive && !this.shallow) {
    verifyRecurseSafe = this.checkHasChildren().then(function(multiple) {
      if (multiple) {
        return utils.reject("Document has children, must specify -r or --shallow.", { exit: 1 });
      }
    });
  } else {
    verifyRecurseSafe = Promise.resolve();
  }

  var self = this;
  return verifyRecurseSafe.then(function() {
    return self._deletePath();
  });
};

module.exports = FirestoreDelete;

클라우드 기능을 사용하기 싫거나 사용할 수 없는 사람들을 위해, 저는 다음과 같은 것을 찾았습니다.recursiveDeletesdk:admin sdk 파일의 :

https://googleapis.dev/nodejs/firestore/latest/Firestore.html#recursiveDelete

// Recursively delete a reference and log the references of failures.
const bulkWriter = firestore.bulkWriter();
bulkWriter
  .onWriteError((error) => {
    if (error.failedAttempts < MAX_RETRY_ATTEMPTS) {
      return true;
    } else {
      console.log('Failed write at document: ', error.documentRef.path);
      return false;
    }
  });
await firestore.recursiveDelete(docRef, bulkWriter);

이제 NodeJS를 사용하여 문서와 모든 하위 컬렉션을 삭제하는 간단한 방법이 있습니다.

이 기능은 nodejs-firestore 버전 v4.11.0에서 사용할 수 있습니다.

에서문:recursiveDelete()지정된 수준 이하의 모든 문서 및 하위 컬렉션을 재귀적으로 삭제합니다.

import * as admin from 'firebase-admin'

const ref = admin.firestore().doc('my_document')
admin.firestore().recursiveDelete(ref)

나는 당신에게 얼마나 도움이 되는지 모르지만 그것을 테스트하고 내가 소방서 문서에서 사용하는 실행 시간을 비교합니다.

  /** Delete a collection in batches to avoid out-of-memory errors.
     * Batch size may be tuned based on document size (atmost 1MB) and application requirements.
    */



 void deleteCollection(CollectionReference collection, int batchSize) {
      try {
        // retrieve a small batch of documents to avoid out-of-memory errors
        ApiFuture<QuerySnapshot> future = collection.limit(batchSize).get();
        int deleted = 0;
        // future.get() blocks on document retrieval
        List<QueryDocumentSnapshot> documents = future.get().getDocuments();
        for (QueryDocumentSnapshot document : documents) {
          document.getReference().delete();
          ++deleted;
        }
        if (deleted >= batchSize) {
          // retrieve and delete another batch
          deleteCollection(collection, batchSize);
        }
      } catch (Exception e) {
        System.err.println("Error deleting collection : " + e.getMessage());
      }
    }

위에서 언급한 바와 같이, 당신은 이를 위한 코드를 잘 작성해야 합니다.삭제할 각 문서에 대해 하나 이상의 모음이 있는지 확인해야 합니다.그러면 삭제 대기열도 표시해야 합니다.이를 위해 아래 코드를 작성했습니다.대규모 데이터 세트로 확장할 수 있는지 테스트되지 않았습니다. 소규모 통합 테스트 후 정리하는 데 사용하기 때문에 문제가 없습니다.확장성이 뛰어난 제품이 필요한 경우 이를 시작점으로 삼아 더 많은 배치 작업을 수행할 수 있습니다.

class FirebaseDeleter {


constructor(database, collections) {
    this._database = database;
    this._pendingCollections = [];
  }

  run() {
    return new Promise((resolve, reject) => {
      this._callback = resolve;
      this._database.getCollections().then(collections => {
        this._pendingCollections = collections;
        this._processNext();
      });
    });
  }

  _processNext() {
    const collections = this._pendingCollections;
    this._pendingCollections = [];
    const promises = collections.map(collection => {
      return this.deleteCollection(collection, 10000);
    });

    Promise.all(promises).then(() => {
      if (this._pendingCollections.length == 0) {
        this._callback();
      } else {
        process.nextTick(() => {
          this._processNext();
        });
      }
    });
  }

  deleteCollection(collectionRef, batchSize) {
    var query = collectionRef;

    return new Promise((resolve, reject) => {
      this.deleteQueryBatch(query, batchSize, resolve, reject);
    });
  }

  deleteQueryBatch(query, batchSize, resolve, reject) {
    query
      .get()
      .then(snapshot => {
        // When there are no documents left, we are done
        if (snapshot.size == 0) {
          return 0;
        }

        // Delete documents in a batch
        var batch = this._database.batch();
        const collectionPromises = [];
        snapshot.docs.forEach(doc => {
          collectionPromises.push(
            doc.ref.getCollections().then(collections => {
              collections.forEach(collection => {
                this._pendingCollections.push(collection);
              });
            })
          );
          batch.delete(doc.ref);
        });

        // Wait until we know if all the documents have collections before deleting them.
        return Promise.all(collectionPromises).then(() => {
          return batch.commit().then(() => {
            return snapshot.size;
          });
        });
      })
      .then(numDeleted => {
        if (numDeleted === 0) {
          resolve();
          return;
        }

        // Recurse on the next process tick, to avoid
        // exploding the stack.
        process.nextTick(() => {
          this.deleteQueryBatch(query, batchSize, resolve, reject);
        });
      })
      .catch(reject);
  }
}

Node.js Admin SDK를 사용한 솔루션


export const deleteDocument = async (doc: FirebaseFirestore.DocumentReference) => {
    const collections = await doc.listCollections()
    await Promise.all(collections.map(collection => deleteCollection(collection)))
    await doc.delete()
}

export const deleteCollection = async (collection: FirebaseFirestore.CollectionReference) => {
    const query = collection.limit(100)
    while (true) {
        const snap = await query.get()
        if (snap.empty) {
            return
        }
        await Promise.all(snap.docs.map(doc => deleteDocument(doc.ref)))
    }
}

Delete Firestore 이벤트에 트리거할 때 중첩된 모든 하위 항목을 재귀적으로 삭제하는 처리기를 작성할 수 있습니다.

핸들러의 예:

const deleteDocumentWithDescendants = async (documentSnap: FirebaseFirestore.QueryDocumentSnapshot) => {
  return documentSnap.ref.listCollections().then((subCollections) => {
    subCollections.forEach((subCollection) => {
      return subCollection.get().then((snap) => {
        snap.forEach((doc) => {
          doc.ref.delete();
          deleteDocumentWithDescendants(doc);
        });
      });
    });
  });
};

// On any document delete
export const onDocumentDelete = async (documentSnap: FirebaseFirestore.QueryDocumentSnapshot) => {
  await deleteDocumentWithDescendants(documentSnap);
};

파이어스토어 이벤트로 연결:

exports.onDeleteDocument = functions.firestore.document('{collectionId}/{docId}')
    .onDelete(onDocumentDelete);
// You can add all the collection hierarchy to object
private collectionsHierarchy = { 

    groups: [

        [
        'groups',
        'projects', 
        'files', 
        'assets',
        'urls',
        'members'
      ]

    ]

 };

async deleteDocument(rootDocument: string) {
     // if (!rootDocument.startsWith(`groups/${this.groupId()}`)) {
     //  rootDocument = `groups/${this.groupId()}/${rootDocument}`;
     // }

    const batchSize: number = 100;
    let root = await this.db
      .doc(rootDocument)
      .get()
      .toPromise();

    if (!root.exists) {
      return;
    }

    const segments = rootDocument.split('/');
    const documentCollection = segments[segments.length - 2]; 
    const allHierarchies = this.collectionsHierarchy[documentCollection];

    for (let i = 0; i < allHierarchies.length; i = i + 1) {
      const hierarchy = allHierarchies[i];
      const collectionIndex = hierarchy.indexOf(documentCollection) + 1;
      const nextCollections: [] = hierarchy.slice(collectionIndex);

      const stack = [`${root.ref.path}/${nextCollections.shift()}`];

      while (stack.length) {
        const path = stack.pop();
        const collectionRef = this.db.firestore.collection(path);
        const query = collectionRef.orderBy('__name__').limit(batchSize);
        let deletedIems = await this.deleteQueryBatch(query, batchSize);
        const nextCollection = nextCollections.shift();
        deletedIems = deletedIems.map(di => `${di}/${nextCollection}`);
        stack.push(...deletedIems);
      }
    }

    await root.ref.delete();
  }

  private async deleteQueryBatch(
    query: firebase.firestore.Query,
    batchSize: number
  ) {
    let deletedItems: string[] = [];
    let snapshot = await query.get();

    if (snapshot.size === 0) {
      return deletedItems;
    }

    const batch = this.db.firestore.batch();
    snapshot.docs.forEach(doc => {
      deletedItems.push(doc.ref.path);
      batch.delete(doc.ref);
    });


    await batch.commit();

    if (snapshot.size === 0) {
      return deletedItems;
    }

    const result = await this.deleteQueryBatch(query, batchSize);
    return [...deletedItems, ...result];
  }

배치와 함께 Node.js Admin SDK를 사용하는 다른 솔루션.

const traverseDocumentRecursively = async (
  docRef: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData>,
  accumulatedRefs: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData>[],
) => {  
  const collections = await docRef.listCollections();
  
  if (collections.length > 0) {
    for (const collection of collections) {
      const snapshot = await collection.get();
      for (const doc of snapshot.docs) {
        accumulatedRefs.push(doc.ref); 
        await traverseDocumentRecursively(doc.ref, accumulatedRefs);
      } 
    } 
  } 
};
import { chunk } from 'lodash';

const doc = admin.firestore().collection('users').doc('001');
const accumulatedRefs: FirebaseFirestore.DocumentReference<FirebaseFirestore.DocumentData>[] = [];

await traverseDocumentRecursively(doc, accumulatedRefs);
await Promise.all(
  // Each transaction or batch of writes can write to a maximum of 500 documents
  chunk(accumulatedRefs, 500).map((chunkedRefs) => {
    const batch = admin.firestore().batch();
    for (const ref of chunkedRefs) {
      batch.delete(ref);
    } 
    return batch.commit();
  }),
);

이것이 여기 있는 사람들에게 도움이 될지는 모르겠지만, 저는 파이어베이스 도구를 사용할 때 "문서 삭제 중 치명적인 오류"라는 오류에 자주 직면합니다.firestore.delete 메서드(firebase-tools 버전 9.22.0).

저는 현재 올레그 본다렌코의 답변에 인용된 코드를 다시 쓰는 것을 피하기 위해 반환된 오류 메시지를 사용하여 이러한 삭제 실패를 처리하고 있습니다.admin을 사용합니다.firestore를 사용하여 실패한 문서를 효과적으로 삭제할 수 있습니다.

오류 메시지에 의존하기 때문에 해결책이 부족하지만 적어도 몇 줄만 수정하기 위해 전체 FirestoreDelete 코드를 복사하도록 강요하지는 않습니다.

firebase_tools.firestore
    .delete(path, {
      project: JSON.parse(process.env.FIREBASE_CONFIG!).projectId,
      recursive: true,
      yes: true,
      token: getToken(),
    })
    .catch((err: Error) => {
      if (err.name == "FirebaseError") {
        // If recursive delete fails to delete some of the documents, 
        // parse the failures from the error message and delete it manually
        const failedDeletingDocs = err.message.match(
          /.*Fatal error deleting docs ([^\.]+)/
        );
        if (failedDeletingDocs) {
          const docs = failedDeletingDocs[1].split(", ");
          const docRefs = docs.map((doc) =>
            firestore.doc(doc.slice(doc.search(/\(default\)\/documents/) + 19))
          );

          firestore
            .runTransaction(async (t) => {
              docRefs.forEach((doc) => t.delete(doc));
              return docs;
            })
            .then((docs) =>
              console.log(
                "Succesfully deleted docs after failing: " + docs.join(", ")
              )
            )
            .catch((err) => console.error(err));
        }
      }
    });

사용자 데이터를 삭제하려는 경우 2022년에 고려해야 할 해결책은 사용자 데이터 파이어베이스 확장 삭제입니다.

활성화되면 Firebase Auth에서 사용자를 삭제하여 사용자 문서의 재귀 삭제를 트리거할 수 있습니다.

import admin from "firebase-admin";
admin.auth().deleteUser(userId); 

전화할 수 있습니다.firebase.firestore().doc("whatever").set()그러면 그 문서의 모든 것이 삭제됩니다.

유일한 방법.set모든 것이 지워지지는 않습니다. 만약 당신이 그것을 설정한다면.merge에 깃발을 내미는.true.

데이터 추가에 대한 Firestore 설명서 참조

var cityRef = db.collection('cities').doc('BJ');

var setWithMerge = cityRef.set({
    capital: true
}, { merge: true });

언급URL : https://stackoverflow.com/questions/49286764/delete-a-document-with-all-subcollections-and-nested-subcollections-in-firestore