Требуется ли Avro schema evolution доступ к старым и новым схемам?

если я сериализую объект, используя версию схемы 1, а затем обновляю схему до версии 2 (скажем, добавляя поле) - должен ли я использовать версию схемы 1 при последующей десериализации объекта? В идеале я хотел бы просто использовать схему версии 2 и десериализованный объект имеет значение по умолчанию для поля, которое было добавлено в схему после первоначальной сериализации объекта.

возможно, какой-то код объяснит лучше...

таблице schema1:

{"type": "record",
 "name": "User",
 "fields": [
  {"name": "firstName", "type": "string"}
 ]}

таблицы schema2:

{"type": "record",
 "name": "User",
 "fields": [
  {"name": "firstName", "type": "string"},
  {"name": "lastName", "type": "string", "default": ""}
 ]}

использование общего подхода без генерации кода:

// serialize
ByteArrayOutputStream out = new ByteArrayOutputStream();
Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
GenericDatumWriter writer = new GenericDatumWriter(schema1);
GenericRecord datum = new GenericData.Record(schema1);
datum.put("firstName", "Jack");
writer.write(datum, encoder);
encoder.flush();
out.close();
byte[] bytes = out.toByteArray();

// deserialize
// I would like to not have any reference to schema1 below here
DatumReader<GenericRecord> reader = new GenericDatumReader<GenericRecord>(schema2);
Decoder decoder = DecoderFactory.get().binaryDecoder(bytes, null);
GenericRecord result = reader.read(null, decoder);

приводит к EOFException. С помощью jsonEncoder приводит к AvroTypeException.

Я знаю, что это сработает, если я передам schema1 и schema2 в GenericDatumReader конструктор, но мне бы не хотелось хранить репозиторий всех предыдущих схем, а также как-то отслеживать из которых схема использовалась для сериализации каждого конкретного объекта.

Я также попробовал подход code-gen, сначала сериализуясь в файл, используя класс пользователя, сгенерированный из schema1:

User user = new User();
user.setFirstName("Jack");
DatumWriter<User> writer = new SpecificDatumWriter<User>(User.class);
FileOutputStream out = new FileOutputStream("user.avro");
Encoder encoder = EncoderFactory.get().binaryEncoder(out, null);
writer.write(user, encoder);
encoder.flush();
out.close();

затем обновление схемы до версии 2, регенерация класса пользователя и попытка прочитать файл:

DatumReader<User> reader = new SpecificDatumReader<User>(User.class);
FileInputStream in = new FileInputStream("user.avro");
Decoder decoder = DecoderFactory.get().binaryDecoder(in, null);
User user = reader.read(null, decoder);

но это также приводит к EOFException.

просто для сравнения, что я пытаюсь сделать, кажется, работает с protobufs...

:

option java_outer_classname = "UserProto";
message User {
    optional string first_name = 1;
}

сериализовать:

UserProto.User.Builder user = UserProto.User.newBuilder();
user.setFirstName("Jack");
FileOutputStream out = new FileOutputStream("user.data");
user.build().writeTo(out);

добавьте необязательное last_name для форматирования, восстановления UserProto и десериализации:

FileInputStream in = new FileInputStream("user.data");
UserProto.User user = UserProto.User.parseFrom(in);

как ожидается, user.getLastName() - пустая строка.

может ли что-то подобное быть сделано с Avro?

3 ответов


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

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

Avro использует другой подход: нет номеров тегов, вместо этого двоичный макет полностью определяется программой, выполняющей кодировку - это схема писателя. (Поля записи просто хранятся одно за другим в двоичной кодировке без каких-либо тегов или разделителей, а порядок определяется схемой записи.) Это делает кодировку более компактной и избавляет вас от необходимости вручную поддерживайте теги в схеме. Но это означает, что для чтения вы должны знать точную схему, с которой были записаны данные, или вы не сможете понять ее.

Если знание схемы писателя необходимо для декодирования Avro, схема читателя-это слой тонкости поверх него. Если вы выполняете генерацию кода в программе, которая должна считывать данные Avro, вы можете сделать codegen из схемы считывателя, что избавляет вас от необходимости регенерировать его каждый раз схема писателя изменяется (при условии, что она изменяется таким образом, который может быть разрешен). Но это не спасает вас от необходимости знать схему писателя.

Плюсы И Минусы

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

подход буферов протокола, вероятно, лучше для RPC, где отдельные объекты передаются по сети (как параметры запроса или возвращаемое значение). Если вы используете Avro здесь, у вас могут быть разные клиенты и разные серверы с разными версиями схем, поэтому вам придется пометить каждый двоичный blob версией схемы Avro, которую он использует, и поддерживать реестр схем. При этом point вы могли бы также использовать встроенные тегирования буферов протокола.


чтобы сделать то, что вы пытаетесь сделать, вам нужно сделать поле last_name необязательным, разрешив значения null. Тип last_name должен быть ["null", "string"] вместо "string"


Я попытался обойти эту проблему. Я кладу его сюда:

Я также попытался использовать две схемы одна схема просто добавление другого столбца к другой схеме с помощью API-интерфейса Avro. У меня есть следующая схема:

Employee (having name, age, ssn)
ExtendedEmployee (extending Employee and having gender column)

Я предполагаю, что в файле, который имел Employee объекты ранее теперь также имеет ExtendedEmployee объект, и я попытался прочитать этот файл как:

    RecordHandler rh = new RecordHandler();
    if (rh.readObject(employeeSchema, dbLocation) instanceof Employee) {
        Employee e = (Employee) rh.readObject(employeeSchema, dbLocation);
        System.out.print(e.toString());
    } else if (rh.readObject(schema, dbLocation) instanceof ExtendedEmployee) {
        ExtendedEmployee e = (ExtendedEmployee) rh.readObject(schema, dbLocation);
        System.out.print(e.toString());
    }

это решает проблему. Тем не менее, я хотел бы знать если есть API, в котором мы можем дать ExtendedEmployee схема для чтения объектов Employee Как хорошо.