【问题标题】:Insert huge files (2G) to mongodb将大文件(2G)插入mongodb
【发布时间】:2018-04-15 06:33:59
【问题描述】:

我有 2GB 文件(其中 9 个),其中包含大约 12M 的字符串记录,我想将每个记录作为文档插入到本地 mongodb(Windows)。

现在我正在逐行读取并插入每隔一行(第一行是不必要的标题),如下所示:

bool readingFlag = false;
foreach (var line in File.ReadLines(file))
{
    if (readingflag)
    {
        String document = "{'read':'" + line + "'}";
        var documnt = new BsonDocument(
             MongoDB
             .Bson
             .Serialization
             .BsonSerializer
             .Deserialize<BsonDocument>(document));

        await collection.InsertOneAsync(documnt);
        readingflag = false;
    }
    else
    {
        readingflag = true;
    }
}

此方法有效,但没有我预期的那么快。我现在在文件中间,我假设它会在大约 4 小时内结束,只需要一个文件。 (我的所有数据需要 40 小时)

我认为我的瓶颈是文件读取,但由于文件非常大,VS 不允许我将其加载到内存中(内存不足异常)。

还有其他我在这里想念的方式吗?

【问题讨论】:

  • 不是一次插入一个,为什么不批量插入呢?我相信连接有一个可用的InsertBatch 方法,您可以在其中读出一些行并将其插入。
  • 我刚刚使用File.ReadLines() 读取了一个包含 605.6 万行的 960mb UTF-8 文本文件,并为每一行反序列化一个字符串(您的 document 字符串与 "test" 作为line 每行),只用了 19 秒。所以我怀疑你的瓶颈是读取甚至反序列化,但你可以通过使用Stopwatch 计时并评论你的InsertOneAsync() 行来确定。一旦您评论该行,可能只需要一两分钟。
  • 你能分享至少10行的示例文件吗?
  • BenM - 我会检查答案并更新关于@Quantic - 你是对的,我现在检查了。 jOSe - 都是 100 个字符

标签: c# mongodb


【解决方案1】:

我认为我们可以利用这些东西:

  1. 获取一些行并通过插入许多添加到一堆
  2. 在单独的线程中插入数据,因为我们不需要等待完成
  3. 使用类型化类TextData 将序列化推送到其他线程

您可以一次玩限制 - 因为这取决于从文件中读取的数据量

public class TextData{
    public ObjectId _id {
        get;
        set;
    }
    public string read {
        get;
        set;
    }
}

public class Processor{
    public async void ProcessData() {
        var client = new MongoClient("mongodb://localhost:27017");
        var database = client.GetDatabase("test");

        var collection = database.GetCollection < TextData > ("Yogevnn");
        var readingflag = false;
        var listOfDocument = new List < TextData > ();
        var limiAtOnce = 100;
        var current = 0;

        foreach(var line in File.ReadLines( @ "E:\file.txt")) {
            if (readingflag) {
                var dataToInsert = new TextData {
                    read = line
                };
                listOfDocument.Add(dataToInsert);
                readingflag = false;
                Console.WriteLine($ "Current position: {current}");

                if (++current == limiAtOnce) {
                    current = 0;
                    Console.WriteLine($ "Inserting data");
                    var listToInsert = listOfDocument;

                    var t = new Task(() =  > {
                                Console.WriteLine($ "Inserting data START");
                                collection.InsertManyAsync(listToInsert);
                                Console.WriteLine($ "Inserting data FINISH");
                            });
                    t.Start();
                    listOfDocument = new List < TextData > ();
                }
            } else {
                readingflag = true;
            }
        }

        // insert remainder
        await collection.InsertManyAsync(listOfDocument);
    }
}

欢迎任何 cmets!

【讨论】:

  • 在调试之后和实际工作之前删除所有那些Console.WriteLine(),因为相对而言,每个调用都是进程密集型的。如我对 OP 的评论中所述,将 2 个Console.WriteLine() 添加到我的File.ReadLines() 测试中,读取我的文本文件的 10,000 行从 0.094 秒到 20 秒。
  • @Quantic 对此表示感谢 - 我认为 Yogewen 会做到这一点 :-) 并且可能会首先尝试进行一些调试 - 看看它是如何进行的。在我的例子中,这个 sn-p 在不到 2 秒的时间内从文件的 233332 行中读取了 116666 行。
  • 感谢您的宝贵时间!最后一行应该做什么?以及如何插入 TextData(我收到转换错误)
  • @yog 最后一行插入余数,所以我们有 50 行而不是 100 行,余数被插入。 TEXTDATA 是一个添加的类,它使集合成为类型化的而不是 bson 的。检查我是否已将此类添加到您的代码中。
  • 当循环执行时,我们的当前将假设为 15,那么这 15 个项目需要插入,因为我们在 counter=limtAtOnce 时插入 - 为了避免截断数据,我们需要确保在进程结束时也将插入剩余部分
【解决方案2】:

在我的实验中,我发现Parallel.ForEach(File.ReadLines("path")) 是最快的。 文件大小约为 42 GB。我还尝试批处理一组 100 行并保存批处理,但比 Parallel.ForEach 慢。

另一个例子:Read large txt file multithreaded?

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-26
    • 2013-02-21
    • 2023-03-08
    • 1970-01-01
    • 2014-04-28
    • 2017-06-11
    • 1970-01-01
    相关资源
    最近更新 更多