0

我一直在尝试优化要导入 MongoDB 的超过 500K POCO 的 JSON 序列化,但遇到的只是头痛。我最初尝试了 Newtonsoft Json.Convert() 函数,但这花费了太长时间。然后,根据 SO、Newtonsoft 自己的网站和其他位置上的几篇帖子的建议,我尝试手动序列化对象。但是没有注意到太多,如果有任何性能提升的话。

这是我用来启动序列化过程的代码......在每行上方的注释中,是给定一个包含 1000 个对象的数据集,完成每个单独操作所花费的时间。

//
// Get reference to the MongoDB Collection
var collection = _database.GetCollection<BsonDocument>("sessions");
//
// 8ms - Get the number of records already in the MongoDB. We will skip this many when retrieving more records from the RDBMS
Int32 skipCount = collection.AsQueryable().Count();
//
// 74ms - Get the records as POCO's that will be imported into the MongoDB (using Telerik OpenAcces ORM)
List<Session> sessions = uow.DbContext.Sessions.Skip(skipCount).Take(1000).ToList();

//
// The duration times displayed in the foreach loop are the cumulation of the time spent on 
// ALL the items and not just a single one.
foreach (Session item in sessions)
{
    StringWriter sw       = new StringWriter();         
    JsonTextWriter writer = new JsonTextWriter(sw);     
    //
    // 585,934ms (yes - 9.75 MINUTES) - Serialization of 1000 POCOs into a JSON string. Total duration of ALL 1000 objects 
    item.ToJSON(ref writer);
    //
    // 16ms - Parse the StringWriter into a String. Total duration of ALL 1000 objects.
    String json = sw.ToString();
    //
    // 376ms - Deserialize the json into MongoDB BsonDocument instances. Total duration of ALL 1000 objects.
    BsonDocument doc = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(json); // 376ms

    //
    // 8ms - Insert the BsonDocument into the MongoDB dataStore. Total duration of ALL 1000 objects.
    collection.InsertOne(doc);

}

目前,将每个单独的对象序列化为 JSON 文档大约需要 0.5 - 0.75 秒……对于 1000 个文档,大约需要 10 分钟……对于 10,000 个文档,大约需要 100 分钟,等等。我发现持续时间相当一致,但最终这意味着为了加载 600K 记录,执行数据加载将需要大约 125 个连续小时的处理时间。这是针对最终可能每天添加 20K - 100K 新文档的消息传递系统,因此性能对我们来说是一个真正的问题。

我正在序列化的对象包含几层“导航”属性或“嵌套文档”(取决于您是通过 ORM 还是 MongoDB 镜头查看它们),但在其他方面并不是特别复杂或值得注意。

我构建的序列化代码将前面代码示例中创建的 JsonTextWriter 实例传递到 POCO 的 ToJSON 函数中,因此我们不会为每个模型创建新的编写器以在序列化自身时使用。

以下代码是一些对象的截断示例,旨在说明实现技术(如何传递编写器以及如何手动构造 JSON)。还有更多属性和更多相关/嵌套对象,但这是我必须进行的“最深”遍历的一个示例。

它从“Session”对象开始,递归地调用它的依赖属性来序列化自己。

public class Session
{

    #region properties

    public Guid SessionUID { get; set; }

    public String AssetNumber { get; set; }

    public Int64? UTCOffset { get; set; }

    public DateTime? StartUTCTimestamp { get; set; }

    public DateTime? StartTimestamp { get; set; }

    public DateTime? EndTimestamp { get; set; }

    public String Language { get; set; }

    // ... many more properties 

    #endregion properties 

    #region navigation properties

    public virtual IList<SessionItem> Items { get; set; }

    #endregion navigation properties

    #region methods
    public void ToJSON(ref JsonTextWriter writer)
    {
        Session session = this;     
        // {
        writer.WriteStartObject();

        writer.WritePropertyName("SessionUID");
        writer.WriteValue(session.SessionUID);

        writer.WritePropertyName("AssetNumber");
        writer.WriteValue(session.AssetNumber);

        writer.WritePropertyName("UTCOffset");
        writer.WriteValue(session.UTCOffset);

        writer.WritePropertyName("StartUTCTimestamp");
        writer.WriteValue(session.StartUTCTimestamp);

        writer.WritePropertyName("StartTimestamp");
        writer.WriteValue(session.StartTimestamp);

        writer.WritePropertyName("EndTimestamp");
        writer.WriteValue(session.EndTimestamp);

        writer.WritePropertyName("Language");
        writer.WriteValue(session.Language);

        // continues adding remaining instance properties

        #endregion write out the properties

        #region include the navigation properties

        // "Items": [ {}, {}, {} ]
        writer.WritePropertyName("Items");
        writer.WriteStartArray();
        foreach (SessionItem item in this.Items)
        {
            item.ToJSON(ref writer);
        }
        writer.WriteEndArray();

        #endregion include the navigation properties

        // }
        writer.WriteEndObject();
        //return sw.ToString();
    }

    #endregion methods 
}

public class SessionItem
{
    #region properties

    public Int64 ID { get; set; }

    public Int64 SessionID { get; set; }

    public Int32 Quantity { get; set; }

    public Decimal UnitPrice { get; set; }

    #endregion properties

    #region navigation properties

    public virtual Session Session { get; set; }

    public virtual IList<SessionItemAttribute> Attributes { get; set; }

    #endregion navigation properties

    #region public methods
    public void ToJSON(ref JsonTextWriter writer)
    {
        // {
        writer.WriteStartObject();

        #region write out the properties

        writer.WritePropertyName("ID");
        writer.WriteValue(this.ID);

        writer.WritePropertyName("SessionID");
        writer.WriteValue(this.SessionID);

        writer.WritePropertyName("Quantity");
        writer.WriteValue(this.Quantity);

        writer.WritePropertyName("UnitPrice");
        writer.WriteValue(this.UnitPrice);

        #endregion write out the properties

        #region include the navigation properties
        //
        // "Attributes": [ {}, {}, {} ]
        writer.WritePropertyName("Attributes");
        writer.WriteStartArray();
        foreach (SessionItemAttribute item in this.Attributes)
        {
            item.ToJSON(ref writer);
        }
        writer.WriteEndArray();

        #endregion include the navigation properties

        // }
        writer.WriteEndObject();
        //return sw.ToString();
    }
    #endregion public methods
}

public class SessionItemAttribute : BModelBase, ISingleID
{
    public Int64 ID { get; set; }

    public String Name { get; set; }

    public String Datatype { get; set; }

    public String Value { get; set; }

    #region navigation properties

    public Int64 ItemID { get; set; }
    public virtual SessionItem Item { get; set; }

    public Int64 ItemAttributeID { get; set; }
    public virtual ItemAttribute ItemAttribute { get; set; }

    #endregion navigation properties

    #region public methods
    public void ToJSON(ref JsonTextWriter writer)
    {
        // {
        writer.WriteStartObject();

        #region write out the properties

        writer.WritePropertyName("ID");
        writer.WriteValue(this.ID);

        writer.WritePropertyName("Name");
        writer.WriteValue(this.Name);

        writer.WritePropertyName("Datatype");
        writer.WriteValue(this.Datatype);

        writer.WritePropertyName("StringValue");
        writer.WriteValue(this.StringValue);

        writer.WritePropertyName("NumberValue");
        writer.WriteValue(this.NumberValue);

        writer.WritePropertyName("DateValue");
        writer.WriteValue(this.DateValue);

        writer.WritePropertyName("BooleanValue");
        writer.WriteValue(this.BooleanValue);

        writer.WritePropertyName("ItemID");
        writer.WriteValue(this.ItemID);

        writer.WritePropertyName("ItemAttributeID");
        writer.WriteValue(this.ItemAttributeID);

        #endregion write out the properties

        // }
        writer.WriteEndObject();
        //return sw.ToString();
    }
    #endregion public methods
}

我怀疑我忽略了某些东西,或者问题在于我实现序列化的方式。一位 SO 发帖人声称通过手动序列化数据将他的加载时间从 28 秒减少到 31毫秒,所以我期待更显着的结果。事实上,这与我使用 Newtonsoft Json.Convert() 方法观察到的性能几乎完全相同。

任何诊断序列化延迟源的帮助将不胜感激。谢谢!

更新

虽然我还没有从 ORM 中提取数据访问,但我已经能够确认延迟实际上来自 ORM(感谢评论者)。当我按照建议添加 FetchStrategy 时,延迟仍然存在,但时间从花在序列化上转移到花在查询上(即加载导航属性)。

所以问题不在于序列化,而在于优化数据检索。

4

2 回答 2

2

为了解决这个问题,我想发布我的解决方案。

经过进一步研究,原始帖子的评论者认为它是正确的。这不是序列化问题,而是数据访问问题。ORM 在序列化过程中被请求时“延迟加载”导航属性。当我实现 FetchStrategy 以“贪婪地”获取相关对象时,延迟的来源从我在序列化过程中放置​​的计数器转移到我在数据访问周围放置的计数器。

我能够通过在数据库中的外键字段上添加索引来解决这个问题。延迟下降了 90% 以上,原本需要 100 多分钟的运行时间现在在 10 分钟内完成。

因此,感谢那些评论并通过提醒我还发生了什么来帮助移除我的眼罩的人们。

于 2016-05-12T12:53:26.933 回答
0

这是不同 JSON 序列化程序的基准比较图。尝试使用 ProtoBuf-netNetJson,它们是排名最高的候选者,用于简单 POCO 的更快序列化。

在此处输入图像描述

于 2016-04-29T18:30:15.797 回答