我一直在尝试优化要导入 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 时,延迟仍然存在,但时间从花在序列化上转移到花在查询上(即加载导航属性)。
所以问题不在于序列化,而在于优化数据检索。