【问题标题】:How can you store and modify large datasets in node.js?如何在 node.js 中存储和修改大型数据集?
【发布时间】:2019-10-24 10:45:39
【问题描述】:

基础知识

所以基本上我已经编写了一个程序,它在 Node 中为 MongoDB 生成测试数据。

我在这个问题上给予了赏金以获得更多答案。要检查当前的解决方案,请向下滚动到下面的块引用!

问题

为此,程序会读取一个模式文件并从中生成指定数量的测试数据。问题是这些数据最终会变得非常大(考虑创建 1M 用户(以及它需要的所有属性)和 20M 聊天消息(使用 userFromuserTo),并且它必须将所有这些都保存在 RAM 中修改/转换/映射它,然后将其保存到文件中。

工作原理

程序是这样工作的:

  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
}

实际数据

因此,在有 100 万用户和 2000 万条消息的示例中,它看起来像这样:

  • 1x IGeneratedCollection (collectionName = "users")
    • 1Mx IGeneratedDocument
      • 10x IGeneratedField(例如每个用户有 10 个字段)
  • 1x IGeneratedCollection (collectionName = "messages")
    • 20Mx IGeneratedDocument
      • 3x IGeneratedField (message, userFrom, userTo)

这将导致 1.9 亿个 IGeneratedField (1x1Mx10 + 1x20Mx3x = 190M) 实例。

结论

对于 RAM 来说显然需要处理很多事情,因为它需要同时存储所有这些。

临时解决方案

现在是这样工作的:

  1. 一次生成 500 个文档(sql 中的行)
  2. JSON.stringify 这 500 个文档,并将它们放入带有架构的 SQLite 表中(dbName STRING,collectionName STRING,value JSON)
  3. 从 JS 中删除这 500 个文档,让垃圾收集器完成它的工作
  4. 重复直到所有数据都生成并在 SQLite 表中
  5. 一次取一行(每行包含 500 个文档),应用 JSON.parse 并在其中搜索键
  6. 重复直到查询完所有数据并检索到所有键
  7. 一次取一行,应用JSON.parse 并在其中搜索关键引用
  8. 应用JSON.stringify 并在必要时更新行(如果找到并解决关键引用)
  9. 重复直到查询完所有数据并解析所有键
  10. 一次取一行,应用 JSON.parse 并将文档转换为有效的 sql/mongodb 插入
  11. 在带有架构 (singleInsert STRING) 的 SQLite 表中添加插入(字符串)
  12. 从 SQLite 表中删除旧的和现在未使用的行
  13. 将所有插入写入文件(如果从命令行运行)或返回 dataHandle 以查询 SQLite 表中的数据(如果从其他 节点应用)

此解决方案确实解决了 RAM 的问题,因为当 RAM 已满时 SQLite 会自动切换到硬盘

但是

如您所见,其中涉及很多JSON.parseJSON.stringify,这大大减慢了整个过程

我的想法:

也许我应该修改 IGeneratedField 以仅使用缩写名称作为变量 (fieldName -> fn, fieldValue -> fv, fieldIsObject -> fio, fieldIsArray -> @ 987654347@, ....)

这会使 SQLite 表中所需的存储空间更小,但也会使代码更难阅读

使用面向文档的数据库(但我还没有真正找到),更好地处理 JSON 数据

问题

有没有更好的解决方案来处理节点中这样的大对象?

我的临时解决方案可以吗?它有什么不好?可以改成更好的表现吗?

【问题讨论】:

  • 我会使用 SQLite 和内存表。 SQLite 是为处理 RAM 和磁盘交换管理而构建的。与解决方案 3 类似,但无需托管数据库或需要网络连接。 SQLite 数据库将存在于目标 PC 上
  • @Jason 感谢您的宝贵时间。你认为使用 SQLite 是个好主意吗?它是一个关系数据库,保存的对象具有嵌套元素等。这在 SQLite 中是否可行? (我对 SQLite 知之甚少)。

标签: javascript node.js performance bigdata test-data


【解决方案1】:

从概念上讲,在流中生成项目。

您不需要数据库中的所有 1M 用户。您可以一次添加 10k。

对于消息,从 db 中随机抽取 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);
}

})();

根据您调整流的方式,您会得到不同的分布。这是均匀分布。通过交错用户和消息,您可以获得更多的长尾分布。例如,您可能希望为留言板执行此操作。

在我将 blockSize 切换为 1000 后,大小变为 200MB。

【讨论】:

  • 用户 -> 消息示例只是一个非常小的示例。考虑一个模型,其中每个用户都有一个朋友列表、一个朋友请求列表、一个组列表等等。然后我想它会变得非常复杂。另外,不要忘记程序没有直接访问数据库的权限,所以无法保存并再次获取。之间需要有一个临时数据库。这又会增加很多读/写。
  • @MauriceNino “不要忘记该程序无法直接访问数据库” 抱歉,我没有看到这些要求。通常人们想要测试的是工作数据库的性能,通过他们使用的任何 API。很少有人在原始 MongoDB 上实际测试插入命令。无论如何,如果你真的有这种问题,我个人会做的是在外部生成一个数据库,然后 mongodump/mongorestore 将其存储到测试服务器。
  • @MauriceNino 如果将其视为随机图问题,这并不是很复杂。采样节点,然后添加边。使用npmjs.com/package/dummy-json 可以从模式中生成大量数据。您需要做的就是从采样中添加“外键”(或嵌入项)。
  • 在我看来有两个用例。 1)创建测试数据以验证前端是否正在使用“真实数据”进行测试。 2)正如你所描述的 - >渗透测试你的数据库。为了支持这两个用例,我必须创建我想在程序中插入的东西,然后将它写到我想要的任何位置(直接到数据库或文件)。
  • 感谢您的工具。如果我在开始编写这个程序之前有这个,我就可以省去努力了。不过现在我想完成它。
猜你喜欢
  • 2022-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-28
  • 2019-11-10
  • 2011-05-26
相关资源
最近更新 更多