Обновление документа Firestore несколько раз с помощью функции триггера события перезаписывает или прекращает обновление после нескольких триггеров

Я пытаюсь обновить документ, когда размер группы изображений изменяется, просто чтобы показать прогресс и отслеживать, не удалось ли преобразовать какие-либо изображения.

у нас есть несколько изображений в каталоге, где dir — это идентификатор документа. используя идентификатор документа, мы должны обновить этот документ. мы должны установить поле статуса, статус содержит объекты для преобразованной или не удалось преобразовать информацию. {изображение: путь, обработка: строка}

это будет выглядеть как { ... положение дел : [ имя_файла1:{...}, имя_файла2:{...} ] }

Но когда я загружаю от 5 до 10 изображений, в поле статуса устанавливается только информация о первых 4-5 изображениях. он приостанавливается (или выглядит как пауза или перезаписывается небольшая информация) перед записью последней информации об изображении в статус.

Хотя я пытался просто заменить старые данные новыми в статусе, это означает, что здесь будет только один объект и он будет изменен соответствующим образом. и это работает большую часть времени. я видел редкий случай, когда это тоже не сработало, но в большинстве случаев это работает.

Также пытался создать массив объектов, но при этом также пропускалось несколько записей.

async function updateDoc({filePath, metadata}) {
    const [docId, fileName] = filePath.split('/');
    
    const db = admin.firestore();
    const collectionRef = db.collection(COLLECTION_PACKS).doc(docId);
    const stickersRef = collectionRef.collection(COLLECTION_STICKERS);
  
    let attempts = 0;
    const maxAttempts = 5;

    while (attempts < maxAttempts) {
        try {
            await db.runTransaction(async (transaction) => {

                const updatedStatus = {};
                updatedStatus[fileName] = metadata;

                // Update the document with the new status and progress
                transaction.update(collectionRef, {
                    status: updatedStatus,
                });
            });
            console.info("Updated document for", filePath);
            break; // Exit the loop if successful
        } catch (error) {
            attempts++;
            console.error(`Error updating document (attempt ${attempts}):`, error);
            if (attempts >= maxAttempts) {
                throw error; // Re-throw the error after max attempts
            }
            await new Promise(resolve => setTimeout(resolve, 1000 * attempts)); // Exponential backoff
        }
    }
}

Я также попробовал этот простой фрагмент, который добавляет новый объект в массив статусов, но это также не позволило сохранить интеграцию данных.


      const db = admin.firestore();
      const docRef = db.collection(COLLECTION_PACKS).doc(docId);

      await db.runTransaction(async (transaction) => {
        const docSnapshot = await transaction.get(docRef);
        if (docSnapshot.exists) {

          docRef.update({
            status: FieldValue.arrayUnion(metadata),
          });

        }
      });

Будь то вложенные объекты или массив объектов, любой из них приемлем.

Обновлено: Я наблюдал, как данные перезаписываются немногие объекты вставляются раньше и позже быстро заменяются новыми объектами. на несколько секунд это выглядит так

status : [
   object1, object2
]

тогда это становится

status : [
  object3, object4, object5
]

объект, который быстро вставляется при ранних триггерах, заменяется позже, и кажется, что новое триггерное событие не знает об этих вставленных данных. это расовая проблема.

Дополнительная деталь кода: вот как вызывается метод функции триггера события updateDoc()

exports.resize = onObjectFinalized({ 
    bucket:'sticker-app-2ad48', 
    region:'us-central1', 
    timeoutSeconds: 450, 
    memory:'1GiB',
    cpu:2 
  
  },
    transform
)

async function transform(event){
    ...
    let  metadatainfo = await bucket.file(filePath).getMetadata()[0];
    
    // Early return; 
    if (metadatainfo?.metadata?.process === "done"){
        console.info("previously done processing  | ",fileName)
        return; 
    }

    try {
        if (valid){
            console.info("image is valid ✅ |",filePath)
            await updateMeta({filePath, valid:true, process:"done"});
            return;
        }

        ...  // invoke image processing with sharpjs
        
        if (buffer?.length > 0){
            // save
            log("✔️ transformed ", fileName)
            await bucket.file(`${filePath}`).save(buffer);
            await updateMeta({filePath, valid:true, process:"done"});
            
        }else{
            // leave image data as it is. set metadata
            log("❌ failed to transform  ", fileName)
            await updateMeta({filePath, valid:false, process:"done"});
        }

        buffer = null;
    } catch (e) {
        console.error('Error_> ', e)
        await updateMeta({filePath, valid:false, process:"done", error: e.message});
    } 
}

async function updateMeta({filePath, process, valid, error}){
    // console.info("updateMeta ",filePath)
    let metadata = { process, valid }
    if (error) { metadata.error = error; }
    await bucket.file(filePath).setMetadata({
        metadata: metadata
    })

    await updateDoc({ filePath, metadata})
}

Обновлено: добавление кода на стороне клиента, который может быть причиной проблемы. Код клиентской стороны:

const promises = selectedFiles.map(..) => fetch(`${baseUrl}/upload`, ...) )

// Uploads images which triggers the onObjectFinalized 
// to prcoess and add the info to the doc
Promise.all(promises)
.then((responses) => {
  const jsonPromises = responses.map((response) => response.json());
  return Promise.all(jsonPromises);
})
.then(uploadedImages=>{
  // structure the stickers objects with download url
  ...  
})
.then(stickerObjects=>{
  ... 
  // Populate subcollection "stickers"
  return fetch(`${baseUrl}/addStickerPack/${docId}`, { 
    method: 'PUT', 
    headers: {
      'Content-Type': 'application/json'
    },
    body: body 
  })

})
.then(result=>{
  console.info("Populated subcollection 'stickers' of the Doc ", {docId})
  ......
})
.catch(...)
.finally(...)

последний блок «тогда», который печатает «заполненную подколлекцию...», помог мне найти основную причину. Когда эта строка печатает поле статуса документа, для которого установлено значение неопределенное, что стирает поле статуса, и после этого журнала все, что добавляется в статус, остается интегрированным без каких-либо проблем. удаление метода выборки предотвращает любые изменения в подколлекции документов, устраняет проблему потери данных. Не знаю, почему это происходит.

вот внутренний код, который создает и обновляет подколлекцию.

app.put('/addStickerPack/:stickerPackId', async (req, res) => {
  try {
    const stickerPackDocId = req.params.stickerPackId;
    const {
      ...
      name,
      publisher,
      stickers
    } = req.body;
    
    const stickerPackData = {
      name,
      publisher,
      ...
      
    };


    const stickerPackRef = admin.firestore().collection(COLLECTION_PACKS).doc(stickerPackDocId);
    await stickerPackRef.set(stickerPackData);
    

      // Create sticker documents within the pack
    const stickerPromises = stickers.map(async (sticker) => {
      const stickerRef = stickerPackRef.collection(COLLECTION_STICKERS).doc();
      const stickerData = {
        name: sticker.name, // Replace with actual image storage logic
        image_file: sticker.image_file, // Replace with actual image storage logic
        emojis: sticker.emojis,
        filePath: sticker.filePath,
        valid:false,
      };
      console.info(sticker.image_file)
      return stickerRef.set(stickerData);
    });
      
    // Wait for all sticker writes to complete
    await Promise.all(stickerPromises);
    console.info('Sticker pack added successfully:', stickerPackData);
    const stickerPackId = stickerPackRef.id;
    res.status(201).send({message:'Sticker pack added successfully', id:stickerPackId});
  } catch (error) {
    console.error('Error adding sticker pack:', error);
    res.status(500).send('Internal Server Error');
  }
});

🤔 А знаете ли вы, что...
JavaScript может выполняться как на стороне клиента (в браузере), так и на стороне сервера (с использованием Node.js).


58
1

Ответ:

Решено

Использование db.runTransaction() не требуется, поскольку вы не получаете более старых значений для использования в обновлении, поэтому это просто добавляет больше ненужной обработки.

Если ваша структура данных на самом деле { ... status : { filename1:{...}, filename2:{...} } } (обратите внимание, что я заменил квадратные скобки фигурными скобками, я полагаю, вы имели в виду объект, а не массив), то вы можете просто обновить вложенный путь без использования транзакции. Повторные попытки также не требуются:

// this is a docRef, not a colRef as in your example
const docRef = db.collection(COLLECTION_PACKS).doc(docId);
const update = {
  [`status.${fileName}`]: metadata,
};
await docRef.update(update);

===== Обновлено:

Просто чтобы объяснить дальше, когда вы говорите:

Но когда я загружаю от 5 до 10 изображений, в поле статуса устанавливается только информация о первых 4-5 изображениях. он делает паузу перед записью информации о последнем изображении в статус.

Я не думаю, что это остановится. Я считаю, что у вас состояние гонки. Обратите внимание, что вы перезаписываете весь объект status в каждом файле. Это означает, что если файлы запускаются в этом порядке 1-2-3-4, но завершают обработку 1-2-4-3, вы получите информацию, которая была у вас в третьем файле, а не в последнем. Это не соответствует коду, который вы показываете, но, судя по тому, что вы говорите, это возможно, если вы намеревались получить весь объект status перед обновлением.

===== Обновлено еще раз:

Что касается последнего добавленного вами кода, который используется на стороне клиента: этот код перезаписывает весь документ, в результате чего свойство status становится неопределенным. Вот почему вы добавляете 1,2,очистить от клиента,3,4,5 и в итоге получаете только 3,4,5.

Измените строку, которая устанавливает документ, очищая остальную часть документа. Вместо:

await stickerPackRef.set(stickerPackData);

скорее используйте

await stickerPackRef.set(stickerPackData, {merge:true});
OR
await stickerPackRef.update(stickerPackData);

Интересные вопросы для изучения