exceptionhazelcasthazelcast-imapjcache

HazelcastSerializationException for an IMap InternalKey - how to debug or log


We are using a Class that implements Cache<K, V>. The cached stored in a Hazelcast IMap<InternalKey<K, C>, V>. There's a "remove" logic that removes by an InternalKey of <String, String>.

K, C and V are all Strings.

public class HazelcastMapJCacheAdapter<K, C, V> implements Cache<K, V> {
    private IMap<InternalKey<K, C>, V> cachedData;

    public void removeEntriesByKeyAndContext(BiPredicate<K, C> removeCondition) {
        Predicate<InternalKey<K, C>, V> predicate = (Predicate<InternalKey<K, C>, V> & Serializable)
            mapEntry -> (removeCondition.test(mapEntry.getKey().getOriginalKey(), mapEntry.getKey().getContext()));
        cachedData.removeAll(predicate);
    }
}

I keep getting a HazelcastSerializationException which I can't understand. What can go wrong with String serialisation? How can I properly log this scenario in order to get more data (the argument I have at hand is a Predicate which is a function interface...)? I fail to reproduce this scenario on my development environment, so debugging it is a problem.

Thanks

2020-03-23 02:07:34 WARN | [10.212.179.245]:5701 [MyApp] [3.12.5] Error while logging processing event
com.hazelcast.nio.serialization.HazelcastSerializationException: Failed to serialize 'com.hazelcast.spi.impl.operationservice.impl.operations.PartitionIteratingOperation'
    at com.hazelcast.internal.serialization.impl.SerializationUtil.handleSerializeException(SerializationUtil.java:82) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toBytes(AbstractSerializationService.java:157) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toBytes(AbstractSerializationService.java:133) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toBytes(AbstractSerializationService.java:124) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.OutboundOperationHandler.send(OutboundOperationHandler.java:56) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.Invocation.doInvokeRemote(Invocation.java:656) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.Invocation.doInvoke(Invocation.java:631) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.Invocation.invoke0(Invocation.java:592) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.Invocation.invoke(Invocation.java:256) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.InvocationBuilderImpl.invoke(InvocationBuilderImpl.java:61) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.InvokeOnPartitions.invokeOnAllPartitions(InvokeOnPartitions.java:121) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.InvokeOnPartitions.invokeAsync(InvokeOnPartitions.java:99) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.InvokeOnPartitions.invoke(InvokeOnPartitions.java:88) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.spi.impl.operationservice.impl.OperationServiceImpl.invokeOnAllPartitions(OperationServiceImpl.java:385) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.map.impl.proxy.MapProxySupport.removeAllInternal(MapProxySupport.java:618) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.map.impl.proxy.NearCachedMapProxyImpl.removeAllInternal(NearCachedMapProxyImpl.java:330) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.hazelcast.map.impl.proxy.MapProxyImpl.removeAll(MapProxyImpl.java:285) ~[hazelcast-3.12.5.jar:3.12.5]
    at com.myapp.cache.impl.HazelcastMapJCacheAdapter.removeEntriesByKeyAndContext(HazelcastMapJCacheAdapter.java:578) ~[myapp-distributed-cache-impl.jar:?]

Solution

  • Here is a summary of requirements for removeAll (and any other kind of server-side processing like executeOnKey etc):

    My guess is that in your case the BiPredicate<K, C> removeCondition is not serializable; however it has to be serialized as part of the lambda you pass to removeAll. For example consider this code:

    class Scratch {
        public static void main(String[] args) {
            remove((x, y) -> true);
        }
    
        private static void remove(BiPredicate<String, String> predicate) {
            HazelcastInstance hz = Hazelcast.newHazelcastInstance();
            // put 100 items in the map
            IMap<String, String> map = hz.getMap("map");
            for (int i = 0; i < 100; i++) {
                map.put("" + i, "" + i);
            }
    
            map.removeAll((Predicate<String, String> & Serializable)
                    entry -> predicate.test(entry.getKey(), entry.getValue()));
    
            // now size is 0
            System.out.println("Map size after removeAll " + map.size());
        }
    }
    

    The above code will be executed without any issues. The moment you add another HazelcastInstance in the cluster (just add Hazelcast.newHazelcastInstance() in remove method body), the Predicate instance must be serialized and sent over the network between the 2 cluster members. This fails because the predicate argument is not Serializable but it is part of the lambda Predicate passed as argument to removeAll. The solution is to make sure everything referenced in your lambda is serializable. So in the above example the fix is to update main method like this:

    public static void main(String[] args) {
        remove((BiPredicate<String, String> & Serializable) (x, y) -> true);
    }
    

    On a general note, exercise caution when using lambdas because it is easy to accidentally capture outer class fields and in that case you will need to address serializability of containing class and your lambda's serialized form size will balloon unexpectedly. Quoting https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html#serialization "You can serialize a lambda expression if its target type and its captured arguments are serializable. However, like inner classes, the serialization of lambda expressions is strongly discouraged."