基础概念
基本上,我编写了一个在Node中为MongoDB生成测试数据的程序。
问题
为此,该程序会读取模式文件,并从中生成指定数量的测试数据。问题在于,这些数据最终可能会变得非常庞大(想象一下创建100万个用户(具有所需的所有属性)和2000万条聊天消息(带有userFrom
和userTo
),并且必须将所有数据保存在RAM中以进行修改/转换/映射,然后将其保存到文件中。
工作原理
该程序的工作原理如下:
- 读取模式文件
- 从模式中创建测试数据并将其存储在结构中(请查看下面的结构)
- 遍历该结构并将所有对象
referenceTo
链接到具有匹配referenceKey
的随机对象。 - 将对象结构转换为MongoDB插入语句的
string[]
- 将该
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万消息的示例为例,情况如下:
- 1个IGeneratedCollection(
collectionName="users"
)- 100万个IGeneratedDocument
- 每个用户包含10个IGeneratedField
- 100万个IGeneratedDocument
- 1个IGeneratedCollection(
collectionName="messages"
)- 2000万个IGeneratedDocument
- 其中包含3个IGeneratedField(
message, userFrom, userTo
)
- 其中包含3个IGeneratedField(
- 2000万个IGeneratedDocument
这将导致190M个IGeneratedField
实例(1x1Mx10 + 1x20Mx3 = 190M
)。
结论
很显然,这对于需要同时存储所有内容的RAM来说是一个巨大的挑战。
临时解决方案
目前的工作方式如下:
- 一次生成500个文档(在SQL中的行)
JSON.stringify
这些500个文档,并将它们放入一个具有以下结构的 SQLite 表中 (dbName STRING, collectionName STRING, value JSON)- 从JS中删除这500个文档,并让垃圾回收器完成其工作
- 重复上述步骤,直到所有数据都生成并存储在 SQLite 表中
- 每次取出一行(包含500个文档),应用
JSON.parse
并搜索其中的键 - 重复上述步骤,直到查询所有数据并检索所有键
- 每次取出一行,应用
JSON.parse
并搜索其中的键引用 - 如果必要(如果找到并解析了键引用),应用
JSON.stringify
并更新该行 - 重复上述步骤,直到查询所有数据并解析所有键
- 每次取出一行,应用
JSON.parse
并将文档转换为有效的 SQL/MongoDB 插入语句 - 将该插入语句(字符串)添加到一个具有以下结构的 SQLite 表中 (singleInsert STRING)
- 从 SQLite 表中删除旧的、现在已不再使用的行
- 将所有插入语句写入文件(如果从命令行运行),或者返回一个 dataHandle 以查询 SQLite 表中的数据(如果从其他 Node 应用程序运行)
这种解决方案可以处理内存问题,因为当 RAM 满时,SQLite 会自动切换到硬盘。
但是
正如您所看到的,其中涉及大量的 JSON.parse
和 JSON.stringify
,这会严重降低整个过程的速度。
我的想法:
也许我应该修改生成的字段(IGeneratedField),只使用变量的简短名称 (fieldName
-> fn
, fieldValue
-> fv
, fieldIsObject
-> fio
, fieldIsArray
-> fia
, ....)。
这将使在 SQLite 表中所需的存储更小,但也会使代码更难阅读。
使用面向文档的数据库(但我还没有真正找到这样的数据库),以更好地处理 JSON 数据。
问题
是否有更好的解决方案来处理这样的大对象?
我的临时解决方案是否可行?有什么问题?能否进行改进以提高性能?