2016-04-29 2 views
0

Я пытаюсь оптимизировать JSON-сериализацию более 500 тыс. POCO для импорта в MongoDB и столкнулся только с головными болями. Сначала я попробовал функцию Newtonsoft Json.Convert(), но это было слишком долго. Затем, основываясь на советах нескольких должностей здесь, на SO, собственном сайте Newtonsoft и других местах, я попытался вручную сериализовать объекты. Но не заметили много, если какая-либо производительность.Оптимизация производительности сериализации JSON .NET POCOs

Это код, который я использую для запуска процесса сериализации ... Над каждой строкой в ​​комментариях указано количество времени, которое каждая отдельная операция выполняла для заполнения, учитывая набор данных из 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); 

} 

В настоящее время они занимают около 0,5 - 0,75 сек для каждого отдельного объекта для сериализации в документ JSON ... что составляет около 10 минут на 1000 документов ... 100 минут на 10000 документов и т.д. Я нахожу, что длительность достаточно последовательна, но в конечном итоге это означает, что для загрузки 600K записей потребуется около 125 прямых часов обработки для выполнения dataload. Это для системы обмена сообщениями, которая в конечном итоге может добавлять 20 тыс. 100 тыс. Новых документов в день, поэтому для нас РЕАЛЬНАЯ проблема.

Объект (ы), который я сериализую, содержит пару уровней свойств «навигации» или «вложенных документов» (в зависимости от того, просматриваете ли вы их через объектив ORM или MongoDB), но не особо особо сложны или примечательны.

Код сериализации, который я построил, передает экземпляр JsonTextWriter, созданный в предыдущем примере кода, в функции ToJSON для POCOs, поэтому мы не создаем новых авторов для каждой модели, которая будет использоваться при сериализации.

Следующий код представляет собой усеченный пример нескольких объектов в попытке проиллюстрировать технику реализации (как передается писатель и как вручную создается JSON). Есть еще много свойств и несколько других связанных/вложенных объектов, но это пример «самого глубокого» обхода, который я должен сделать.

Он начинается с объекта «Сессия» и рекурсивно вызывает его зависимые свойства, а также сериализуется.

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().

Любая помощь, диагностирующая источник задержки в сериализации, будет наиболее оценена. Спасибо!

UPDATE

Пока я не выбрался доступа к данным из ОРМ пока я был в состоянии подтвердить, что задержка на самом деле идет от ОРМ (спасибо комментаторов). Когда я добавил FetchStrategy, как было предложено, время ожидания все еще там, но время перешло от того, чтобы тратиться на сериализацию на расходование на запрос (т. Е. На загрузку свойств навигации).

Таким образом, проблема не является сериализацией, так как она оптимизирует извлечение данных.

+0

Это не похоже на проблему Json.NET - для сериализации объекта не требуется .75 с, если не размер 10 МБ. Вы [профилировались] (https://stackoverflow.com/questions/3927/what-are-some-good-net-profilers)? – dbc

+1

Кроме того, вы написали * используя Telerik OpenAcces ORM *; может ли это быть проблемой с субоптимальной ленивой загрузкой? См. [Как выявлять и решать проблемы N + 1] (http://docs.telerik.com/data-access/developers-guide/profiling-and-tuning/profiler-and-tuning-advisor/data-access- profiler-n-plus-one-problem) и [Начало работы с API FetchPlans] (http://docs.telerik.com/data-access/developers-guide/crud-operations/defining-fetch-plans/getting-started -root-get-started-with-fetchplans) – dbc

+1

Да, попробуйте без этого дерьма ORM и посмотрите, что произойдет. – Dusan

ответ

2

В попытке обеспечить закрытие на этот вопрос я хотел опубликовать свое решение.

После дальнейших исследований, комментаторы на первоначальном посту имели это правильно. Это не проблема сериализации, а проблема доступа к данным. ORM была «лениво загружалась» навигационными свойствами, поскольку они запрашивались во время процесса сериализации. Когда я реализовал FetchStrategy, чтобы «с жадностью» получить связанные объекты, источник латентности сдвинулся со счетчиков, которые у меня были на месте вокруг процесса сериализации, на счетчики, которые я размещал вокруг доступа к данным.

Мне удалось решить эту проблему, добавив индексы в поля внешнего ключа в базе данных. Задержка уменьшилась более чем на 90%, а время, затрачиваемое на выполнение более 100 минут, теперь завершается в 10.

Так спасибо людям, которые прокомментировали и помогли удалить мои шторы, напомнив мне, что еще происходит.

+0

Спасибо, что вернулись, чтобы опубликовать ваше решение. Вы можете принять свой собственный ответ, чтобы закрыть вопрос. –

0

Это benchmark comparison график различных сериализаторов JSON. Попробуйте ProtoBuf-net или NetJson, который наивысшим ранжированным кандидатом быстрее сериализуется для simle POCOs.

enter image description here

Смежные вопросы