Контекст
У меня есть база данных с набором документов, использующих эту схему (сокращенная схема, потому что некоторые данные не имеют отношения к моей проблеме):
{
title: string;
order: number;
...
...
...
modificationsHistory: HistoryEntry[];
items: ListRow[];
finalItems: ListRow[];
...
...
...
}
Эти документы могут легко достигать 100 или 200 КБ, в зависимости от количества элементов и finalItems, которые они содержат. Также очень важно, чтобы они обновлялись как можно быстрее с минимальным использованием полосы пропускания.
Это внутри контекста веб-приложения, используя Angular 9 и @angular/fire
6.0.0.
Проблемы
Когда конечный пользователь редактирует один элемент в массиве item
объекта, например, при редактировании только свойства, отражение того, что внутри базы данных требует от меня отправки всего объекта, потому что метод update
firestore не поддерживает индексы массива внутри пути к полю, единственные операции, которые могут быть выполнены с массивами, - это добавление или удаление элемента , как описано в документации .
Однако обновление элемента items
массив путем отправки всего документа создает плохую производительность для тех, у кого нет хорошего соединения, что характерно для многих моих пользователей.
Вторая проблема заключается в том, что наличие всего в реальном времени внутри одного документа затрудняет совместную работу в моем случае, поскольку некоторые из этих элементов могут редактировать несколько пользователей, В то же время, что создает две проблемы:
- Некоторые операции записи могут завершиться ошибкой из-за слишком большого количества конфликтов в документе, если два обновления выполняются за одну секунду.
- Обновления не являются atomi c, поскольку мы отправляем весь документ сразу, так как он не использует транзакции, чтобы еще больше избежать использования полосы пропускания.
Решения, которые я уже пробовал
Подколлекции
Описание
Это было очень простое решение: создать подколлекцию для массивов items
, finalItems
и modificationsHistory
, что упростит их редактирование, поскольку теперь у них есть собственный идентификатор так что их легко найти, чтобы обновить их.
Почему это не сработало
Наличие списка с 10 finalItems
, 30 items
и 50 записями внутри modificationsHistory
означает, что Мне нужно открыть в общей сложности 4 слушателя, чтобы один элемент был полностью прослушан. Учитывая тот факт, что пользователь может открывать многие из этих элементов одновременно, прослушивание нескольких десятков документов создает одинаково плохую ситуацию с производительностью, возможно, даже хуже в случае полного пользователя.
Это также означает, что если Я хочу обновить большой элемент со 100 элементами, и я хочу обновить половину из них, это будет стоить мне одной операции записи на элемент, не говоря уже о количестве операций чтения, необходимых для проверки разрешений, и т. Д. c, вероятно, 3 на запись, поэтому 150 операций чтения + 50 записей только для обновления 50 элементов в массиве.
Облачная функция для обновления документа
const {
applyPatch
} = require('fast-json-patch');
function applyOffsets(data, entries) {
entries.forEach(customEntry => {
const explodedPath = customEntry.path.split('/');
explodedPath.shift();
let pointer = data;
for (let fragment of explodedPath.slice(0, -1)) {
pointer = pointer[fragment];
}
pointer[explodedPath[explodedPath.length - 1]] += customEntry.offset;
});
return data;
}
exports.updateList = functions.runWith(runtimeOpts).https.onCall((data, context) => {
const listRef = firestore.collection('lists').doc(data.uid);
return firestore.runTransaction(transaction => {
return transaction.get(listRef).then(listDoc => {
const list = listDoc.data();
try {
const [standard, custom] = JSON.parse(data.diff).reduce((acc, entry) => {
if (entry.custom) {
acc[1].push(entry);
} else {
acc[0].push(entry);
}
return acc;
}, [
[],
[]
]);
applyPatch(list, standard);
applyOffsets(list, custom);
transaction.set(listRef, list);
} catch (e) {
console.log(data.diff);
}
});
});
});
Описание
Используя библиотеку различий, я проводил различие между предыдущим документом и новым обновленным и отправлял это различие в GCF, который был выполнение обновления с использованием API транзакции.
Преимущества этого подхода заключаются в том, что, поскольку транзакция происходит внутри GCF, она очень быстрая и не требует слишком большой пропускной способности, плюс для обновления требуется только отправка diff, а не больше всего документа.
Почему это не сработало
На самом деле, облачная функция была очень медленной, и некоторые обновления занимали более 2 секунд, они также могли выйти из строя из-за разногласия, без ведома соединителя firestore, поэтому в этом случае нет возможности гарантировать целостность данных.
Я буду отредактирован соответствующим образом, чтобы добавить больше решений, если я найду другие вещи, которые можно попробовать
Вопрос
Мне кажется, что я что-то упускаю, например, если бы в firestore было что-то, чего я вообще не знал, что могло бы решить мой вариант использования, но я не могу понять, что это такое, возможно, мои ранее протестированные решения были плохо реализовал или пропустил что-то важное. Что я пропустил? Возможно ли вообще добиться того, чем я хочу заниматься? Я открыт для ремоделирования данных, изменения запросов, всего, что угодно, поскольку это в основном для целей обучения.