Как вы можете хранить и изменять большие наборы данных в node.js? - PullRequest
6 голосов
/ 10 июня 2019

Основы

Итак, в основном я написал программу, которая генерирует тестовые данные для MongoDB в Node.

Я дал щедрость на этот вопрос, чтобы получить больше ответов. Чтобы проверить текущее решение, пожалуйста, прокрутите вниз до цитаты ниже!

Проблема

Для этого программа считывает файл схемы и генерирует из него указанное количество тестовых данных. Проблема в том, что эти данные могут в конечном итоге стать достаточно большими (подумайте о создании 1M пользователей (со всеми необходимыми им свойствами) и 20M сообщений чата (с userFrom и userTo), и они должны хранить все это в оперативной памяти, чтобы измените / преобразуйте / отобразите его и после этого сохраните в файл.

Как это работает

Программа работает так:

  1. Чтение файла схемы
  2. Создать тестовые данные из схемы и сохранить их в структуре (структура приведена ниже)
  3. Запустите эту структуру и свяжите все объекты referenceTo со случайным объектом с соответствием referenceKey.
  4. Преобразование структуры объекта в string[] операторов вставки MongoDB
  5. Сохраните это string[] в файле.

Это структура сгенерированных тестовых данных:

export interface IGeneratedCollection {
    dbName: string,                 // Name of the database
    collectionName: string,         // Name of the collection
    documents: IGeneratedDocument[] // One collection has many documents
}

export interface IGeneratedDocument {
    documentFields: IGeneratedField [] // One document has many fields (which are recursive, because of nested documents)
}

export interface IGeneratedField {
    fieldName: string, // Name of the property
    fieldValue: any,   // Value of the property (Can also be IGeneratedField, IGeneratedField[], ...)
    fieldNeedsQuotations?: boolean, // If the Value needs to be saved with " ... "
    fieldIsObject?: boolean,        // If the Value is a object (stored as IGeneratedField[]) (To handle it different when transforming to MongoDB inserts)
    fieldIsJsonObject?: boolean,    // If the Value is a plain JSON object
    fieldIsArray?: boolean,         // If the Value is array of objects (stored as array of IGeneratedField[])
    referenceKey?: number,          // Field flagged to be a key
    referenceTo?: number            // Value gets set to a random object with matching referenceKey
}

Фактические данные

Таким образом, в примере с 1M Users и 20M сообщениями это будет выглядеть так:

  • 1x IGeneratedCollection (collectionName = "users")
    • 1Mx IGeneratedDocument
      • 10x IGeneratedField (например, у каждого пользователя есть 10 полей)
  • 1x IGeneratedCollection (collectionName = "messages")
    • 20Mx IGeneratedDocument
      • 3x IGeneratedField (message, userFrom, userTo)

что приведет к 190M экземплярам IGeneratedField (1x1Mx10 + 1x20Mx3x = 190M).

Заключение

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

Временное решение

Теперь это работает так:

  1. Генерируйте 500 документов (строк в sql) одновременно
  2. JSON.stringify этих 500 документов и поместите их в таблицу SQLite со схемой (dbName STRING, collectionName STRING, значение JSON)
  3. Удалите эти 500 документов из JS и позвольте сборщику мусора сделать свое дело
  4. Повторять до тех пор, пока все данные не будут сгенерированы и в таблице SQLite
  5. Возьмите одну из строк (каждая из которых содержит 500 документов) за раз, примените JSON.parse и найдите в них ключи
  6. Повторять до тех пор, пока не будут запрошены все данные и не будут получены все ключи.
  7. Возьмите одну из строк за раз, примените JSON.parse и найдите в них ключевые ссылки
  8. Применить JSON.stringify и обновить строку, если необходимо (если ключевые ссылки найдены и разрешены)
  9. Повторять до тех пор, пока все данные не будут запрошены и все ключи не будут разрешены
  10. Возьмите одну из строк за раз, примените JSON.parse и преобразуйте документы в допустимые вставки sql / mongodb
  11. Добавление вставки (строки) в таблицу SQLite со схемой (singleInsert STRING)
  12. Удалить старую и теперь неиспользуемую строку из таблицы SQLite
  13. Записать все вставки в файл (если они запускаются из командной строки) или вернуть dataHandle для запроса данных в таблице SQLite (если они запускаются из других узел приложения)

Это решение решает проблему с оперативной памятью, поскольку SQLite автоматически переключается на жесткий диск при заполнении оперативной памяти

НО

Как видите, задействовано много JSON.parse и JSON.stringify, что резко замедляет весь процесс

Что я думал:

Может быть, я должен изменить IGeneratedField, чтобы использовать только сокращенные имена в качестве переменных (fieldName -> fn, fieldValue -> fv, fieldIsObject -> fio, fieldIsArray -> fia, ....)

Это уменьшит необходимое хранилище в таблице SQLite, НО также затруднит чтение кода

Использовать документно-ориентированную базу данных (но я не нашел ее), чтобы лучше обрабатывать данные JSON

Вопрос

Есть ли лучшее решение для обработки больших объектов, таких какэто в узле?

Мое временное решение в порядке?Что в этом плохого?Можно ли изменить его для улучшения производительности?

1 Ответ

2 голосов
/ 10 июня 2019

Концептуально, генерировать элементы в потоке.

Вам не нужны все пользователи 1M в дБ.Вы можете добавить 10 тыс. За раз.

Для сообщений, случайных выборок 2n пользователей из базы данных, они отправляют сообщения друг другу.Повторите, пока не выполнено.

Пример:

// Assume Users and Messages are both db.collections
// Assume functions generateUser() and generateMessage(u1, u2) exist.
const desiredUsers = 10000;
const desiredMessages = 5000000;
const blockSize = 1000;


(async () => {

for (const i of _.range(desiredUsers / blockSize) ) {
    const users = _.range(blockSize).map(generateUser);
    await Users.insertMany(users);
}


for (const i of _.range(desiredMessages / blockSize) ) {
    const users = await Users.aggregate([ { $sample: { size: 2 * blockSize } } ]).toArray();
    const messages = _.chunk(users, 2).map( (usr) => generateMessage(usr[0], usr[1]));
    await Messages.insertMany(messages);
}

})();

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

Memory usage

Пошло до 200 МБ после того, как я переключил blockSize на 1000.

Timing

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...