Formati di dati binari, come assicurarti di poter leggere versioni di formati diversi?

4

Nel nostro progetto abbiamo questo formato di dati che usiamo per elaborare e registrare dati su. A partire da ora la nostra applicazione è cambiata in modo tale che molti dei parametri dei formati dati sono diventati obsoleti. Attualmente riceviamo questi "pacchetti" di dati su Internet (UDP o TCP) e da file binari salvati.

Vogliamo creare un nuovo formato più efficiente sotto il profilo dello spazio, rimuovendo ciò di cui non abbiamo bisogno. Ogni formato è diviso in un'intestazione e in un payload, in cui sono presenti nell'intestazione cose come le informazioni sul timestamp e alcune descrizioni del carico utile.

Per garantire che possiamo supportare più versioni di un formato, abbiamo deciso che era logico inserire una sorta di ID di versione del formato nella parte superiore del formato per ogni formato che realizziamo. Purtroppo il formato precedente (creato da persone che non sono più nel nostro team) non segue la convenzione e ad un certo punto è stata presa la decisione di inserire l'ID della versione del formato nel medio del formato, in mezzo a dove erano tutti i dati inutili spazzatura.

leggere questo vecchio formato è un problema perché al momento disponiamo attualmente di gigabyte di dati di quel formato che utilizziamo come dati di test per la nostra applicazione, elementi raccolti nel campo.

In che modo entrambi garantiamo che i formati che non seguono il formato format version ID, everything else possono ancora essere letti dalla nostra applicazione e dalle versioni future del formato che creiamo?

Abbiamo preso in considerazione quanto segue:

  • Passa al formato successivo, ignorando i vecchi dati. Non responsabile, proibitivamente costoso.

  • Avere all'utente un po 'come specificare quale formato è (formati che possono essere trovati dall'intestazione immediatamente rispetto ai vecchi tipi di formato). Fastidioso, e duro con le persone che non sono sviluppatori di questo progetto, ma contribuiscono anche (di cui ce ne sono molti).

  • Le nuove versioni di formato seguono la versione precedente fino alla parte dell'ID versione. attenua molti dei vantaggi del passaggio alla nuova versione, richiede un'attenta pianificazione di dove posizionare i byte di intestazione per garantire che l'ID della versione sia ancora nella stessa posizione (più difficile per gli sviluppatori).

  • Conversione di vecchi formati nelle versioni dell'intestazione dell'ID della versione, richiede nuovi strumenti e il mantenimento del convertitore di versione, richiede anche l'aggiornamento di tutti i file di altri, questi file registrati sono con persone che non sono sviluppatori e non stanno utilizzando anche il controllo della versione, quindi sarà difficile accertarsi che i dati già registrati possano essere utilizzati correttamente per tutti.

Ecco un esempio di come appare l'intestazione corrente:

* = contrassegnato per la rimozione

size: 8 bytes
payload metadata: 8 bytes
payload metadata: 8 bytes
* non-standard timeformat: 8 bytes 
* non-standard timeformat: 8 bytes
* legacy undocumented data: 8 bytes
version number: 8 bytes
* source metadata: 8 bytes // may not want this all the time
sequence number: 8 bytes
short range time: 8 bytes
payload metadata: 8 bytes
* size data?: 8 bytes
* spare data: 8 bytes
payload: N bytes
    
posta opa 11.01.2018 - 15:59
fonte

4 risposte

11

Mi sembra che la soluzione più semplice sia quella di rendere l'intestazione della versione univoca e assicurarsi che il vecchio formato non assomigli mai all'intestazione di un formato, basta cercarlo. Se non è lì, presumi che sia il vecchio stile e cerca di trovarlo da metà. Potrebbero esserci anche cose all'inizio del vecchio formato in cui puoi indovinare.

La chiave qui è che è necessario trovare una sorta di schema per il preambolo della versione che il vecchio formato non può produrre. Ad esempio, supponiamo che la vecchia versione non inizi mai con un byte 0. Puoi iniziare il tuo preambolo con 0x00 0x00 0x00 0x00 . Quindi, quando inizi a leggere i dati, leggi i primi 4 byte e se c'è un valore diverso da zero, stai guardando una vecchia versione (o una cattiva richiesta). Un esempio di ciò che viene fatto è in UTF-8 e la sua retrocompatibilità con ascii.

    
risposta data 11.01.2018 - 16:07
fonte
9

Il tuo vecchio formato inizia con un elemento "size" da 8 byte (probabilmente lungo 64 bit). Se questa è la dimensione del file in byte (cosa immagino), probabilmente non ha mai superato 100 GByte, ovvero 2 ^ 37. Quindi, se hai compensato i tuoi nuovi numeri di versione, ad es. 2 ^ 40 e memorizzarli come i primi 8 byte, sarà facile discriminare:

  • Leggi la prima parola a 64 bit come "versione".
  • Versione sotto 2 ^ 40: è un vecchio file, ramo al vecchio lettore che rilegge o reinterpreta i byte iniziali come dimensione.
  • Versione sopra 2 ^ 40: ramo al nuovo lettore appropriato per questa versione.
risposta data 11.01.2018 - 16:53
fonte
6

La convenzione format version ID, everything else è buona perché offre la massima flessibilità per il formato. Nel tuo caso l'ID della versione non è giusto nella parte anteriore ma in un offset fisso. Questo riduce leggermente la tua flessibilità ma non fatalmente: finché il tuo nuovo formato mantiene la versione con lo stesso offset, puoi continuare.

Il codice di analisi dell'intestazione dovrà controllare il numero di versione prima e quindi inviare l'implementazione di parsing nuova o precedente:

Header parse_header(char const* buffer) {
  int version = *(int const*)(buffer + VERSION_OFFSET);
  if (version < NEW_VERSION)
    return parse_header_legacy(buffer);
  else
    return parse_header_new(buffer);
}

Quindi puoi rimescolare i campi dell'intestazione per usare lo spazio come meglio credi. Ciò probabilmente impedisce un formato di dati auto-descrittivo poiché è ancora necessario un layout a dimensione fissa. Ma questo non è necessariamente un grosso problema. Se necessario, puoi implementare un meccanismo di estensione dell'intestazione che memorizza i campi di intestazione descrittivi nella sezione del payload dopo l'intestazione.

I costi sociali di un upgrade dell'header compatibile sono minimi, date le alternative. Il tuo nuovo codice sarà in grado di ricevere pacchetti nuovi e vecchi, anche se il vecchio codice non sarà in grado di leggere nuovi pacchetti. Se si incapsula il codice di analisi nelle routine che rappresentano l'intestazione con un formato di dati comune, questa modifica del formato non è un onere per i contributori: utilizzano la struttura dei dati analizzata, non i byte del pacchetto non elaborati. L'unica ragione per cui questo potrebbe non essere accettabile se hai davvero bisogno di un parsing overhead, cioè reinterpretando i byte grezzi come una struct.

    
risposta data 11.01.2018 - 16:40
fonte
-2

Dovresti dare un'occhiata a Buffer del protocollo . È probabile che ti piaccia e risolva i tuoi problemi.

L'idea nella versione più recente del formato dati è che i campi "obbligatori" non sono più supportati, dal momento che comunque un giorno cambierai un formato in modo tale che un campo che prima era richiesto non fosse più richiesto.

Finché non cambi i tipi o gli identificatori dei campi esistenti, il formato è completamente compatibile con le versioni precedenti e successive. Tuttavia, è un vero formato binario e la codifica e la decodifica sono abbastanza veloci.

Anche se il nome dice "protocollo", puoi usarli bene anche per memorizzare le informazioni sul disco. Quindi non c'è nulla di specifico del protocollo in questo.

Ci sono collegamenti per molte lingue. Se utilizzi un linguaggio con qualsiasi tipo di base di utenti ragionevole, probabilmente troverai il supporto dei buffer di protocollo per la tua lingua.

Naturalmente, se il tuo formato precedente non era basato su buffer di protocollo, potresti aver bisogno di supporto per la lettura di dati legacy oltre ai dati dei buffer di protocollo durante il periodo di transizione.

    
risposta data 11.01.2018 - 20:44
fonte

Leggi altre domande sui tag