Хранить набор protobuf на диске

Я использую protobuf в качестве сериализатора для форматирования данных на диске. У меня может быть большой набор объектов protobuf, скажем, миллионы. как лучше разместить их на диске? объекты protobuf будут считываться последовательно один за другим или считываться с произвольным доступом по внешнему индексу.

Раньше я использовал формат length(int)+protobuf_object+length(int)...., но это не срабатывало, если один из protobuf оказывался грязным. и если многие из объектов protobuf малы, это может иметь некоторые накладные расходы.


person Shawn    schedule 09.01.2013    source источник
comment
Было бы полезно узнать, как нужно обращаться к данным: просто последовательное чтение, произвольный доступ, случайная запись, поиск по какому-то критерию?   -  person jpa    schedule 09.01.2013
comment
определить, что это не удалось, когда один из protobuf грязный; вы имеете в виду, что я не мог просто перезаписать эту часть файла, потому что, если длина изменится, в файле будет либо пробел (с мусором), либо он перезапишет следующий бит данных?   -  person Marc Gravell    schedule 09.01.2013
comment
да, я просто хочу читать его последовательно или искать по «индексу», указанному в файле. случайная запись не нужна.   -  person Shawn    schedule 09.01.2013


Ответы (2)


Если вам нужен только последовательный доступ, самый простой способ хранить несколько сообщений — написать перед ним размер объекта, как это рекомендуется в документации: http://developers.google.com/protocol-buffers/docs/techniques#streaming

Например, вы можете создать класс «MessagesFile» со следующими функциями-членами для открытия, чтения и записи ваших сообщений:

// File is opened using append mode and wrapped into
// a FileOutputStream and a CodedOutputStream
bool Open(const std::string& filename,
          int buffer_size = kDefaultBufferSize) {

    file_ = open(filename.c_str(),
                 O_WRONLY | O_APPEND | O_CREAT, // open mode
                 S_IREAD | S_IWRITE | S_IRGRP | S_IROTH | S_ISUID); //file permissions

    if (file_ != -1) {
        file_ostream_ = new FileOutputStream(file_, buffer_size);
        ostream_ = new CodedOutputStream(file_ostream_);
        return true;
    } else {
        return false;
    }
}

// Code for append a new message
bool Serialize(const google::protobuf::Message& message) {
    ostream_->WriteLittleEndian32(message.ByteSize());
    return message.SerializeToCodedStream(ostream_);
}

// Code for reading a message using a FileInputStream
// wrapped into a CodedInputStream 
bool Next(google::protobuf::Message *msg) {
    google::protobuf::uint32 size;
    bool has_next = istream_->ReadLittleEndian32(&size);
    if(!has_next) {
        return false;
    } else {
        CodedInputStream::Limit msgLimit = istream_->PushLimit(size);
        if ( msg->ParseFromCodedStream(istream_) ) {
            istream_->PopLimit(msgLimit);
            return true;
        }
        return false;
    }
}

Затем, чтобы написать свои сообщения, используйте:

MessagesFile file;
reader.Open("your_file.dat");

file.Serialize(your_message1);
file.Serialize(your_message2);
...
// close the file

Чтобы прочитать все ваши сообщения:

MessagesFile reader;
reader.Open("your_file.dat");

MyMsg msg;
while( reader.Next(&msg) ) {
    // user your message
}
...
// close the file
person aeciosan    schedule 10.01.2013

(Надеюсь, я правильно понял ваш вопрос, и мой ответ соответствует вашему варианту использования!)

Один из методов сохранения произвольного потока сообщений буфера протокола на диск состоит в том, чтобы определить сообщение-оболочку, в котором все поля определены как repeated (что подразумевает optional), а затем, когда вы читаете свои байты, вы получаете экземпляр класса-оболочки и вызовите на нем методы hasX(), чтобы найти то, что у вас есть на самом деле. Проблема с этим подходом в вашем случае заключается в том, что вы не получаете произвольного доступа и реальной потоковой передачи (все сообщения типа Foo будут вместе, за которыми следуют все Bars), и если ваши данные слишком велики, вы не сможете поместиться все в память.

На самом деле вы в основном запрашиваете методологию хранения любых данных таким образом, чтобы их можно было передавать в потоковом режиме или получать произвольный доступ. Это общая проблема, а не проблема, специфичная для протокольных буферов.

Ваши проблемы:

  • разграничение записей... (см. примечание)
  • ...таким образом, чтобы повреждение можно было обнаружить и либо допустить, либо отремонтировать...
  • ... при сохранении индекса для обеспечения произвольного доступа

Вы, вероятно, использовали бы индекс, чтобы разрешить некоторые проверки целостности, но даже для этого требуется механизм, гарантирующий, что индекс и данные соответствуют и остаются синхронизированными.

Следовательно, это может быть не идеальное решение, но один из способов добиться того, что вы хотите, особенно если целостность является проблемой, — это хранить эту информацию в базе данных, которая позволяет хранить двоичные данные и может быстро возвращать эти данные. Тогда вопрос произвольного доступа и целостности данных станет обязанностью поставщика базы данных. Любая традиционная база данных, способная хранить BLOB, сможет это сделать, хотя я бы также рассмотрел возможность хранения в NoSQL, например в MongoDB.

Примечание

Если вы тщательно определите свои протокольные буферы (т. е. вы знаете типы и длину сохраняемых полей), вам не нужно будет разграничивать ваши записи, поскольку их длина никогда не изменится. Однако это нарушило бы одну из особенностей буферов протоколов, а именно их перспективность. Если бы вы разработали .proto таким образом, чтобы размер сообщения был фиксированным, вы не смогли бы добавлять новые поля и по-прежнему вписываться в тот же формат файла, с уверенностью говоря, что каждое новое сообщение начинается после x байтов.

person Rich    schedule 09.01.2013