Magazine

Dataset Serialization

Davide Senatore

17/11/2006

Una valutazione delle prestazioni di serializzazione e compressione sulla trasmissione di Dataset in uno scenario n-tier

0%100%
per esprimere un voto รจ necessario registrarsi al sito

Web Services, ADO.net, architettura n-tier

BinaryDatasetSerialization.zip (97,00 Kb)

 

Da quando è stato lanciato con il .net Framework 1.0, il Dataset ha subito molte critiche, e nelle versioni 1.x del Framework, molte di queste corrispondevano a verità. E' anche vero, però, che il dataset è uno strumento estremamente comodo per traghettare blocchi di informazioni da un tier ad un altro di un'applicazione. Tra i vantaggi che possiamo rilevare, c'è la gestione dello stato delle modifiche/aggiunte/cancellazioni e la semplicità del modello, oltre che la possibilità di impiegarlo come Typed Dataset, offrendo così un accesso Type-Safe alle proprietà delle entità modellate dal dataset.

Una delle cose che però minavano alla base l'utilizzo del Dataset, era l'impossibilità di serializzarlo in binario, obbligando quindi un suo uso XML anche sui canali TCP (binari) utilizzati da remoting.
La serializzazione XML è estremamente verbosa, e mentre può andare bene per classi non troppo ricche di contenuti, poco si addice ad una struttura che, almeno sulla carta, dovrebbe essere adatta per trasportare dati contenuti in database.

Nella versione 2.0 del .net Framework è stata aggiunta la possibilità di effettuare una serializzazione binaria del dataset, rendendolo di fatto utilizzabile finalmente anche su canali binari, quali il canale TCP di Remoting.
Il problema, però, rimane sempre lo stesso. Nel caso di Web Services, il formato di serializzazione resta XML, con grossi problemi di utilizzo di banda, soprattutto per grossi volumi di dati scambiati.

Introduzione

Come fare, dunque, per risparmiare banda durante i trasferimenti di grosse moli di dati basate su XML?
Prima di tutto, dobbiamo descrivere il nostro scenario:

  • Database SQL Server 2000 con una tabella contenente un discreto numero di record, ciascuno con un numero di campi abbastanza elevato, in modo da poter osservare un certo carico nel trasferimento dei dati
  • Un provider per l'accesso ai dati
  • Un helper per la serializzazione binaria
  • Un Web service che espone 3 metodi:
    • GetEmployeesBinary (con compressione opzionale)
    • GetEmployeesXML (non compresso)
    • GetEmployeesString (con compressione opzionale)
  • Un'applicazione client che consumi il Web Service sulla stessa macchina, senza passare per la rete e senza quindi introdurre rallentamenti dovuti al traffico di rete stesso

Descriviamo le parti salienti del codice. Tralascio il provider per l'accesso ai dati, in quanto non fa altro che fare una query con clausola TOP per simulare il carico restituendo un dataset. Vediamo piuttosto il BinaryHelper:

Imports System.Data
Imports System.Data.SqlClient
Imports System.IO
Imports System.IO.Compression

Public Class BinaryHelper

    
Public Shared Function GetBinaryRepresentation(ByVal ds As DataSet, ByVal compress As BooleanAs Byte()
        
'in questo modo, il dataset sarà serializzato in binario e non in XML
        
ds.RemotingFormat = SerializationFormat.Binary
        Dim 
myFormatter As System.Runtime.Serialization.IFormatter = New System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
        
Dim myStream As New System.IO.MemoryStream
        myFormatter.Serialize(myStream, ds)
        
Dim _buffer(myStream.Length - 1) As Byte
        
myStream.Position = 0
        myStream.Read(_buffer, 0, myStream.Length)
        myStream.
Close()
        
If Not compress Then
            Return 
_buffer
        
Else
            Return 
BinaryHelper.Compress(_buffer)
        
End If
    End Function

    Public Shared Function 
GetBinaryRepresentation(ByVal data As StringByVal compress As BooleanAs Byte()
        
If Not compress Then
            Return 
System.Text.Encoding.UTF8.GetBytes(data)
        
Else
            Return 
BinaryHelper.Compress(System.Text.Encoding.UTF8.GetBytes(data))
        
End If
    End Function

    Public Shared Function 
Compress(ByVal data As Byte()) As Byte()
        
Dim output As New MemoryStream()
        
Dim gzip As New GZipStream(output, CompressionMode.Compress, True)
        gzip.
Write(data, 0, data.Length)
        gzip.
Close()
        
Return output.ToArray()
    
End Function
End Class

Questa semplicissima classe ci permette, passando un dataset ed un flag di compressione, di ottenere una versione binaria (compressa) del dataset, oppure di ottenere una versione binaria (compressa) di una stringa. Comunque, il risultato è un'array di bytes. La compressione viene effettuata utilizzando il GZipStream. Vediamo ora il web service. Questo servizio espone 3 metodi per effettuare i test, come possiamo vedere nella classe sottostante:

Public Class WSEmployees
    
Inherits System.Web.Services.WebService

    <WebMethod()> _
    
Public Function GetEmployeesBinary(ByVal rows As IntegerByVal compress As BooleanAs Byte()
        
'Il client DEVE essere .net
        
Return BinaryHelper.GetBinaryRepresentation(DataProvider.GetEmployees(rows), compress)
    
End Function

    
<WebMethod()> _
    
Public Function GetEmployeesXML(ByVal rows As IntegerAs DataSet
        
'Il client può non essere .net, ma deve conoscere come deserializzare il Dataset
        
Return DataProvider.GetEmployees(rows)
    
End Function

    
<WebMethod()> _
    
Public Function GetEmployeesString(ByVal rows As IntegerByVal compress As BooleanAs Byte()
        
'Il client può essere di qualunque tipo
        
Return BinaryHelper.GetBinaryRepresentation(DataProvider.GetEmployees(rows).GetXml, compress)
    
End Function

End Class

E' da notare l'uso che viene fatto dei tre metodi: Il più semplice trasferisce i dati in XML, e rappresenta il modo in cui solitamente siamo abituati a trasferire i dataset tra tiers. Il GetEmployeesBinary serializza il dataset contenente n righe comprimendolo o meno, a seconda del flag. Sul client il dataset manterrà tutte le sue proprietà ma l'applicazione consumer dovrà essere .net. Il terzo metodo consente di trasferire dati di un dataset comprimendoli, ma sotto forma di stringa.

Client Application

A questo punto, parliamo dell'applicazione client, che sarà il nostro banco di prova per valutare le prestazioni di banda passante e velocità di trasferimento del dataset. Non riporto tutta l'applicazione, in quanto sarebbe troppo lungo commentarla e rinvio alla lettura del codice sorgente.

 Dim _file As StreamWriter = File.CreateText("C:\DSBinary.csv")
'Warm-up call
Dim _rawData As Byte() = _service.GetEmployeesBinary(100, _compress)
Console.WriteLine(
DateTime.Now.Subtract(_startTime).TotalMilliseconds)
Console.WriteLine("Data extent (binary):" & _rawData.Length)

'Start Cycle
For As Integer = _minRecords To _maxRecords Step _step
    Console.WriteLine("Records fetched:" & i)
    _startTime = 
DateTime.Now
    
_dataset = GetDataSetFromBinary(_service, i, _compress, _dataExtent)
    _binaryTime = 
DateTime.Now.Subtract(_startTime).TotalMilliseconds
    Console.WriteLine("Bytes transferred: {0} Bytes", _dataExtent)

    _startTime = 
DateTime.Now
    
_dataset = GetDataSet(_service, i, _datasetExtent)
    _xmlTime = 
DateTime.Now.Subtract(_startTime).TotalMilliseconds
    Console.WriteLine("Bytes transferred: {0} Bytes", _datasetExtent)

    _startTime = 
DateTime.Now
    
_dataset = GetDataSetFromCompressedString(_service, i, _compress, _stringDataExtent)
    _stringTime = 
DateTime.Now.Subtract(_startTime).TotalMilliseconds
    Console.WriteLine("Bytes transferred: {0} Bytes", _stringDataExtent)

    _file.WriteLine("{0};{1};{2};{3};{4};{5};{6}", i, _dataExtent, _binaryTime, _datasetExtent, _xmlTime, _stringDataExtent, _stringTime)
Next

Il client, dopo aver fatto una chiamata di warm-up, effettua le chiamate dei 3 metodi in sequenza, tenendo traccia di tempi di esecuzione e banda passante (peso del DataSet) in un file CSV. Nell'applicazione completa esiste anche una versione interattiva del client.

Il client, dopo aver fatto una chiamata di warm-up, effettua le chiamate dei 3 metodi in sequenza, tenendo traccia di tempi di esecuzione e banda passante (peso del DataSet) in un file CSV. Nell'applicazione completa esiste anche una versione interattiva del client.

Risultati e loro interpretazione

I risultati del test sono stati scremati e raccolti qui sotto, campionando le misure ogni 100 records.

Record number Binary Bandwidth (Bytes) Binary Times (ms) XML Bandwidth (Bytes) XML Times (ms)
0 15692 234,37 10914 78,12
100 30796 296,87 450703 171,87
200 41738 390,62 893193 328,12
300 53764 375,00 1331292 453,12
400 65053 453,12 1770542 562,49
500 73865 562,49 2193734 703,12
600 83901 562,49 2631069 843,74
700 96643 671,87 3063068 968,74
800 109443 703,12 3492836 1156,24
900 123036 796,86 3933999 1296,86
1000 137303 781,24 4371201 1484,36

Vorrei far notare l'ENORME differenza di banda richiesta per trasferire un dataset con 1000 righe e l'altrettanta differenza di tempi di risposta del sistema. In pratica si tratta di un dimezzamento dei tempi ed una banda passante di 30 volte inferiore. I grafici degli andamenti qui sotto riportati possono inoltre spiegare altre cose interessanti:

La tendenza è chiaramente visibile; la serializzazione XML esplode letteralmente, passando da 10k a 4 MB mentre la serializzazione binaria va da 15k (maggiore di XML per 0 records...) a 137k per un numero di righe che va da 0 a 1000. Per quanto riguarda i tempi, la serializzazione binaria è più lenta della serializzazione XML per pochi records, in quanto c'è un overhead dovuto alla compressione, ma da 300 records in poi, comincia ad essere conveniente la serializzazione binaria, in quanto trasferire grosse moli di dati in XML è comunque costoso e quindi i rallentamenti cominciano ad apparire per la laboriosità del trasferimento dei dati attraverso la rete.

Conclusioni

L'esempio proposto vuole essere un esame di ciò che effettivamente "passa sul cavo" quando trasferiamo dei dati da un server ad un'applicazione client in un ambiente distribuito. Ovviamente gli stessi ragionamenti possono essere applicati ad un domain model di classi serializzate, il quale si avvantaggerà della compressione zip tanto quanto il DataSet, se non di più! Come ultima osservazione, consideriamo sempre la mole di dati che dobbiamo far viaggiare sulla rete e se possibile, per volumi di dati notevoli, applichiamo una compressione. La nostra applicazione risulterà più veloce e responsiva, e la banda passante non ne soffrirà!!

Commenti
Nome

Sito web
Commento


indietro