Formatos de dados binários, como garantir que você possa ler diferentes versões de formato?

4

Em nosso projeto, temos esse formato de dados que usamos para processar e registrar dados. Ultimamente, nossa aplicação foi alterada, de modo que muitos dos parâmetros dos formatos de dados se tornaram obsoletos. Atualmente, recebemos esses dados "pacotes" pela Internet (UDP ou TCP) e a partir de arquivos binários salvos.

Queremos criar um novo formato mais eficiente em termos de espaço, removendo coisas de que não precisamos. Cada formato é dividido em um cabeçalho e na carga, onde informações como carimbo de data e hora e alguma descrição da carga estão no cabeçalho.

Para garantir que possamos oferecer suporte a várias versões de um formato, decidimos que fazia sentido colocar algum tipo de ID de versão de formato na parte superior do formato para cada formato que criamos. Infelizmente o formato anterior (criado por pessoas que não estão mais no nosso time) não segue a convenção, e em algum momento a decisão foi feita para colocar o ID da versão do formato no meio do formato, entre onde estavam todos os dados inúteis agora inúteis.

ler esse formato antigo é um problema, porque atualmente temos gigabytes dos formatos que usamos como dados de teste para nosso aplicativo, materiais que foram coletados em campo.

Como podemos garantir que os formatos que não seguem o formato format version ID, everything else ainda possam ser lidos por nosso aplicativo e futuras versões de formato que criamos?

Consideramos o seguinte:

  • Passando para o próximo formato, ignorando dados antigos. Não é responsável, proibitivamente caro.

  • Ter o usuário como especificar qual formato é qual (formatos que podem ser encontrados do cabeçalho imediatamente em relação aos tipos de formato antigos). Irritante, e duro com as pessoas que não são devs neste projeto, mas também contribuem (dos quais existem muitos).

  • Ter novas versões de formato segue a versão antiga até a parte da ID da versão. mitiga muitos dos benefícios de migrar para a nova versão, requer um planejamento cuidadoso de onde colocar bytes de cabeçalho para garantir que o ID de versão ainda esteja no mesmo local (mais difícil para os desenvolvedores).

  • A conversão de formatos antigos para as versões do primeiro cabeçalho do ID da versão requer ferramentas novas e a manutenção do conversor de versões exige que todos os arquivos sejam atualizados. Esses arquivos gravados são de pessoas que não são desenvolvedores e não estão usando controle de versão também, por isso, será difícil garantir que os dados já gravados possam ser usados corretamente para todos.

Aqui está um exemplo de como o cabeçalho atual se parece:

* = marcado para remoção

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
    
por opa 11.01.2018 / 15:59
fonte

4 respostas

11

Parece-me que a solução mais simples é tornar o cabeçalho da sua versão não ambíguo e garantir que o formato antigo nunca pareça ter um cabeçalho de formato, basta procurá-lo. Se não estiver lá, você assume que é o estilo antigo e tenta encontrá-lo do meio. Também pode haver coisas no começo do formato antigo que podem lhe dar uma pista.

A chave aqui é que você precisa encontrar algum tipo de esquema para o preâmbulo de sua versão que o formato antigo não pode produzir. Por exemplo, suponhamos que a versão antiga nunca comece com um byte de 0. Você poderia começar seu preâmbulo com 0x00 0x00 0x00 0x00 . Então, quando você começa a ler os dados, você lê os primeiros 4 bytes e, se houver algum valor diferente de zero, você está olhando para uma versão antiga (ou uma solicitação incorreta). Um exemplo disso é em UTF-8 e sua compatibilidade com versões anteriores com ascii.

    
por 11.01.2018 / 16:07
fonte
9

Seu formato antigo começa com um elemento "tamanho" de 8 bytes (provavelmente um comprimento de 64 bits). Se esse é o tamanho do arquivo em bytes (o que eu acho), provavelmente nunca ultrapassou 100 GByte, significando 2 ^ 37. Então, se você compensar seus novos números de versão, por exemplo, 2 ^ 40 e armazená-los como os primeiros 8 bytes, será fácil discriminar:

  • Leia a primeira palavra de 64 bits como "versão".
  • Versão abaixo de 2 ^ 40: é um arquivo antigo, ramificado para o antigo leitor que irá reler ou reinterpretar os bytes iniciais como tamanho.
  • Versão acima de 2 ^ 40: ramifique para o novo leitor apropriado para esta versão.
por 11.01.2018 / 16:53
fonte
6

A convenção format version ID, everything else é boa porque oferece flexibilidade máxima para o formato. No seu caso, o ID da versão não está bem na frente, mas em um deslocamento fixo. Isso reduz um pouco sua flexibilidade, mas não fatalmente: desde que seu novo formato mantenha a versão no mesmo deslocamento, você pode continuar.

Seu código de análise de cabeçalho precisará verificar o número da versão primeiro e, em seguida, enviar para a implementação de análise nova ou antiga:

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);
}

Você pode então reorganizar os campos de cabeçalho para usar o espaço como quiser. Isso provavelmente evita um formato de dados autoexplicativo, pois você ainda precisa de um layout de tamanho fixo. Mas isso não é necessariamente um grande problema. Se necessário, você pode implementar um mecanismo de extensão de cabeçalho que armazena campos de cabeçalho autodescritivos na seção de carga útil após o cabeçalho.

Os custos sociais de tal atualização de cabeçalho compatível são mínimos, dadas as alternativas. Seu novo código poderá receber pacotes novos e antigos, embora o código antigo não consiga ler novos pacotes. Se você encapsular o código de análise em rotinas que representam o cabeçalho com um formato de dados comum, essa alteração no formato não será um fardo para os colaboradores: eles usam a estrutura de dados analisada, não os bytes de pacote brutos. A única razão pela qual isso pode não ser aceitável se você realmente precisar de análise de sobrecarga zero, ou seja, reinterpretando os bytes brutos como uma estrutura.

    
por 11.01.2018 / 16:40
fonte
-2

Você deve realmente dar uma olhada em Protocol Buffers . É provável que você goste e resolva seus problemas.

A ideia na versão mais recente do formato de dados é que os campos "obrigatórios" não são mais suportados, já que você poderá mudar algum dia um formato de tal maneira que um campo que era necessário não seja mais necessário.

Contanto que você não vá alterando tipos ou identificadores de campos existentes, o formato é totalmente compatível com versões anteriores e futuras. No entanto, é um formato binário genuíno e a codificação e decodificação são bastante rápidas.

Embora o nome diga "protocolo", você pode usá-los bem mesmo para armazenar informações no disco. Então não há nada de protocolo específico nisso.

Existem ligações para muitos idiomas. Se você usar uma linguagem que tenha algum tipo de base de usuários razoável, provavelmente encontrará suporte de buffers de protocolo para seu idioma.

É claro que, se o formato legado não tiver sido baseado em buffers de protocolo, talvez seja necessário ter suporte para ler dados legados além dos dados de buffers de protocolo durante o período de transição.

    
por 11.01.2018 / 20:44
fonte