-
Notifications
You must be signed in to change notification settings - Fork 3.5k
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
Fix MessageRouter hash inconsistent on C++/Java client #1029
Conversation
@@ -69,6 +69,9 @@ class ProducerConfiguration { | |||
ProducerConfiguration& setMessageRouter(const MessageRoutingPolicyPtr& router); | |||
const MessageRoutingPolicyPtr& getMessageRouterPtr() const; | |||
|
|||
ProducerConfiguration& setUseMurmurHash(const bool useMurmurHash); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to have an enum type to select the default hashing scheme (default, since it can always be overridden in a custom message router.
Something like:
ProducerConfiguration& setDefaultHashingScheme(HashingScheme hashingScheme);
This way it might be interesting to provide the same function across C++ and Java, even the existing functions (eg: JavaStringHash
)
pulsar-client-cpp/lib/Murmur3_32.cc
Outdated
@@ -0,0 +1,108 @@ | |||
/** |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jai1 There are various implementations of MurmurHash3. boost::hash
is much different from the Google Guava's one which is based on the original one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the actual result different between Guava and boost versions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merlimat Yes, and the result by my implementation is same as Google Guava's.
Google Guava: https://ideone.com/Jq4stk
boost::hash
: https://ideone.com/W4jEBT
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Thanks for checking
} | ||
|
||
@Override | ||
public int choosePartition(Message msg) { | ||
// If the message has a key, it supersedes the single partition routing policy | ||
if (msg.hasKey()) { | ||
return ((msg.getKey().hashCode() & Integer.MAX_VALUE) % numPartitions); | ||
if (useMurmurHash) { | ||
HashFunction hf = Hashing.murmur3_32(0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this logic here is not allocation-friendly. It generates two objects HashFunction
and HashCode
per message here.
I would suggest using a MurmurHash class that is a static method take takes a bytes array or a string to complete a hash value, rather than using guava HashFunction
or Hasher
. E.g.
public class MurmurHash3 {
public static int hash32(ByteBuf data, int offset, int length, int seed) { ... }
}
Another suggestion - it would be good to write a jmh microbenchmark, testing the difference between using MurmurHash3 and simple hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sijie I found the implementation of MurmurHash3 into Pulsar repository!
https://github.com/apache/incubator-pulsar/blob/554f0505fc424896d820d639252b28e0ec523526/pulsar-checksum/src/main/java/com/scurrilous/circe/CommonHashes.java
I will fix after #1033 merged. |
@Licht-T This should be unblocked. I had merged all the other PRs |
87de4d5
to
10ff43b
Compare
Thanks @merlimat, I'll fix up in this weekend. |
Just fixed up C++ client. Java client will be re-implemented in the same way. |
retest this please |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Licht-T The C++ LGTM. Just a comment on moving the .h
files into lib/
directory since they're not going to be part of user facing API.
|
||
#include <cstdint> | ||
#include <string> | ||
#include <boost/functional/hash.hpp> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this class should go in the lib/
directory since it's should not be part of the user API. As a user, I just need to know about the "Boost" hash enumerator and not its implementation
#include <string> | ||
|
||
namespace pulsar { | ||
class Hash { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same for Hash
, if the user interaction is with the enum, no need to expose this interface. In any case the user won't be able to directly supply a different hash function (other than writing a custom router).
11ec26d
to
31b26ab
Compare
@@ -46,7 +46,7 @@ | |||
private boolean blockIfQueueFull = false; | |||
private int maxPendingMessages = 1000; | |||
private MessageRoutingMode messageRouteMode = MessageRoutingMode.SinglePartition; | |||
private boolean useMurmurHash = true; | |||
private HashingScheme hashingScheme = HashingScheme.Murmur3_32Hash; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We cannot change the default from Java to Murmur hash, since it would unexpectedly break ordering for people upgrading the library.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merlimat Is this also can be said to C++ client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, unfortunately we cannot change the default in either library implementation. C++ would have to keep using the current BoostHash
MessageRouterBase(ProducerConfiguration.HashingScheme hashingScheme) { | ||
switch (hashingScheme) { | ||
case JavaStringHash: | ||
this.hash = new JavaStringHash(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can keep static instances of the Hashing objects since they can be shared around
package org.apache.pulsar.client.impl; | ||
|
||
public interface Hash { | ||
long makeHash(String s); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since we're converting later to an int
, we should just have an it here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merlimat MurmurHash returns an unsigned int value, so we have to return as a positive value for calculating the partition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, but the number is anyway masked with & Integer.MAX_INT
so the bit sign will be stripped.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merlimat Does this conversion keep the quality of hash?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is not good workaround because this may waste the quality of hash.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think that it really changes anything because the hash is then moduled to the number of partitions, so all the high order bits are anyway discarded.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merlimat IMO, this conversion may break the uniform distribution of hash. I concern that message routing is biased to the specific partition.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@merlimat After second thought, I found you are right! Excuse me!
else { | ||
return ((msg.getKey().hashCode() & Integer.MAX_VALUE) % topicMetadata.numPartitions()); | ||
} | ||
return (int) (hash.makeHash(msg.getKey()) % topicMetadata.numPartitions()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This depends on the makeHash()
to always return a positive number, which is true in the current implementation. We should mention that in the makeHash()
javadoc.
@Licht-T The change looks good to me. Just the last thing is to move the new headers added in |
3b16f8b
to
4cb3d88
Compare
4cb3d88
to
d4a0e9c
Compare
retest this please |
@merlimat C++ client is now fixed. I'll add some tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 Nice change!
…integer This is the same behavior as Hash classes on Java client
@merlimat Now tests added! |
@Licht-T Some of the changes like "constexpr" seems to be c++11 specific and it's breaking our build. |
@saandrews Oops! Excuse me! I'll fix soon! |
Motivation
This closes #1017.
Modifications
Result
The hash inconsistent on each client are resolved.