Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Load encoded values from disk #2542

Merged
merged 13 commits into from Mar 22, 2022
Merged

Load encoded values from disk #2542

merged 13 commits into from Mar 22, 2022

Conversation

easbar
Copy link
Member

@easbar easbar commented Mar 21, 2022

I tried using Jackson to serialize/deserialize an EnumEncodedValue.
For example a RoadClass EV is serialized to:

{"className":"com.graphhopper.routing.ev.EnumEncodedValue","name":"road_class","bits":5,"minValue":0,"maxValue":31,"negateReverseDirection":false,"storeTwoDirections":false,"enumType":"com.graphhopper.routing.ev.RoadClass"}

For EnumEncodedValue we do not store the enum values explicitly, but rather the name of the enum class. For StringEncodedValue we simply store all the values.

Comment on lines 58 to 70
// serialize
List<String> serializedEVs = new ArrayList<>();
for (EncodedValue e : encodedValues) {
String s = mapper.writeValueAsString(e);
serializedEVs.add(s);
}

// deserialize
List<EncodedValue> deserializedEVs = new ArrayList<>();
for (String s : serializedEVs) {
JsonNode jsonNode = mapper.readTree(s);
deserializedEVs.add(mapper.treeToValue(jsonNode, EncodedValue.class));
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty nice and seems to work for all our EncodedValue subclasses automatically, due to the @JsonTypeInfo annotation that I added to the EncodedValue interface.

@easbar easbar marked this pull request as ready for review March 21, 2022 17:01
Comment on lines +49 to +54
int storedVersion = jsonNode.get("version").asInt();
((ObjectNode) jsonNode).remove("version");
EncodedValue encodedValue = MAPPER.treeToValue(jsonNode, EncodedValue.class);
if (storedVersion != encodedValue.getVersion())
throw new IllegalStateException("Version does not match. Cannot properly read encoded value: " + encodedValue.getName() + ". " +
"You need to use the same version of GraphHopper you used to import the data");
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bit self-made and probably can also be done using Jackson Annotations, but since we aren't really planning to de/serialize encoded values with something other than these two methods I think this is fine. I'm not so experienced with Jackson and open for improvements of course.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also no expert. I tried to get it working and embedded your work into a common Deserializer (and using @JsonProperty(access = JsonProperty.Access.READ_ONLY) for getVersion to avoid a custom Serializer).

But the problem was that we don't want to manually handle the className and I wasn't able to find a way to use both:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
and
@JsonDeserialize(using = EncodedValueDeserializer.class).

The only solution was with some wrapper class but this here looks simpler IMO.

@@ -25,14 +28,30 @@
* This class allows to store distinct values via an enum. I.e. it stores just the indices
*/
public final class EnumEncodedValue<E extends Enum> extends IntEncodedValueImpl {
private final E[] arr;
public final Class<E> enumType;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be reverted back to private ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, thanks I forgot.

@@ -30,7 +27,7 @@ public StringEncodedValue(String name, int expectedValueCount, boolean storeTwoD

this.maxValues = roundUp(expectedValueCount);
this.values = new ArrayList<>(maxValues);
this.indexMap = new ObjectIntHashMap<>(maxValues);
this.indexMap = new HashMap<>(maxValues);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we avoid this with a dedicated getter instead of using field access? I'm not sure if we want to deal with auto(un)boxing in a potentially hot code path.

Copy link
Member Author

@easbar easbar Mar 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this really not work because we use field access? I thought we need a custom serializer for hppc, but I might be wrong. I thought about the auto unboxing for a moment but then thought it's probably not as bad bc the maps will be very small. But you are more than welcome to make this work with the hppc map again. I just did not feel like spending a lot more time on this, also because currently the default GraphHopper server does not use StringEncodedValue currently.

Copy link
Member Author

@easbar easbar Mar 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried changing indexMap to the concrete type ObjectIntHashMap and got another error which is due to the fact that HashOrderMixingStrategy is an abstract type and Jackson does not know which type to use upon de-serialization.

Copy link
Member Author

@easbar easbar Mar 21, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works when we add jackson-datatype-hppc: https://github.com/FasterXML/jackson-datatypes-collections
I tried this here including a little speed test: fa80be9
I could not measure a notable difference, though so I would go with java.util for now. You are invited to prove the opposite, of course.

@easbar
Copy link
Member Author

easbar commented Mar 21, 2022

I went for the all-args constructor approach where we can keep the fields final but have to add some all-args constructors for Jackson.

@otbutz maybe this is related to #2243?

@otbutz
Copy link
Contributor

otbutz commented Mar 22, 2022

Yes this would also solve #2243 😉

Does Jackson require those constructors to be public? Maybe we could reduce the visibility if they're only intended to be used by Jackson.

@easbar
Copy link
Member Author

easbar commented Mar 22, 2022

Does Jackson require those constructors to be public? Maybe we could reduce the visibility if they're only intended to be used by Jackson.

Yes, package private also works and is better thanks. We could also make them protected, but not sure if realistically users want to create their own subclasses of IntEncodedValueImpl etc.

Copy link
Member

@karussell karussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines +49 to +54
int storedVersion = jsonNode.get("version").asInt();
((ObjectNode) jsonNode).remove("version");
EncodedValue encodedValue = MAPPER.treeToValue(jsonNode, EncodedValue.class);
if (storedVersion != encodedValue.getVersion())
throw new IllegalStateException("Version does not match. Cannot properly read encoded value: " + encodedValue.getName() + ". " +
"You need to use the same version of GraphHopper you used to import the data");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm also no expert. I tried to get it working and embedded your work into a common Deserializer (and using @JsonProperty(access = JsonProperty.Access.READ_ONLY) for getVersion to avoid a custom Serializer).

But the problem was that we don't want to manually handle the className and I wasn't able to find a way to use both:
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
and
@JsonDeserialize(using = EncodedValueDeserializer.class).

The only solution was with some wrapper class but this here looks simpler IMO.


static {
MAPPER.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for consistency shall we add
MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
here?

@easbar easbar merged commit acd82a8 into master Mar 22, 2022
@easbar easbar deleted the serialize_encoded_values branch March 22, 2022 18:12
@karussell karussell added this to the 6.0 milestone Mar 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants