tl; dr
pip install pyfarmhash
で PyFarmHash をインストールしたうえで、
import struct
import farmhash
def farm_fingerprint(value: str) -> int:
hash_unsigned = farmhash.fingerprint64(value)
hash_binary = struct.pack("Q", hash_unsigned)
return struct.unpack("q", hash_binary)[0]
はじめに:FARM_FINGERPRINTとは
BigQueryやCloud Spannerでは、引数の値からハッシュ値を作る FARM_FINGERPRINT
という関数が用意されている。
SHA256やSHA512といったおなじみのハッシュ関数も用意されているものの、 FARM_FINGERPRINT
は戻り値がINT64なので値をそのままカラムに突っ込むことができ、重宝している。
(他のハッシュ関数は戻り値がBYTE型なので変換の手間によってちょっと敬遠してしまう。)
さて、今回BigQuery上で行っていた計算処理を外出しし、Python上で自前で行うように改修を加えることになった。
具体的にはBigQuery上のデータを一度外部にエクスポートし、外部のサーバーのPython上で計算してから、計算結果をBigQueryにインポートする、というものだ。
特に計算処理の過程では FARM_FINGERPRINT
関数を使っていたため FARM_FINGERPRINT
も自前で実装しなければならなくなった。
実行環境
- Python 3.9.7
- PyFarmHash 0.2.2
PythonでFARM_FINGERPRINTを再現する
Pythonの場合、コアとなるロジックは PyFarmHash パッケージとして提供されているため、これを使う。
ライブラリのインストールは pip install pyfarmhash
で行えばよい。
ハマりポイントは戻り値の符号だった。
PyFarmHashが用意しているハッシュ値計算は farmhash.fingerprint64
という関数で行われるのだが、この
farmhash.fingerprint64
の戻り値の範囲は、0から(2^64)-1である。
つまり 符号なし64ビット整数 の範囲なのだ。
一方、BigQueryのINT64型は 符号あり64ビット整数 (-(2^63)〜(2^63)-1)だ。
したがって単純に farmhash.fingerprint64
で算出した値をBigQueryにインポートしようとすると範囲エラーによって取り込めない。
したがって符号なし64ビット整数を符号あり64ビット整数に変換する処理が必要になってくる。
Pythonではこのような変換のためにstructというパッケージを使うことができる。
structを使えば、符号あり/なしnビット整数とバイト配列の相互変換を行うことができる。
今回の場合、farmhash.fingerprint64
の出力である符号なし64ビット整数をバイト配列化し、そのバイト配列を符号あり64ビット整数に再変換すればよい。
import struct
import farmhash
# ハッシュ値を取得(戻り値は符号なし64bit整数の範囲)
hash_unsigned = farmhash.fingerprint64(value)
# hash_unsignedをバイナリー化する("Q"はhash_unsignedが符号なし64bit整数であることを指定している)
hash_binary = struct.pack("Q", hash_unsigned)
# hash_binaryを符号あり64bit整数化する("q"は符号あり64bit整数に変換することを指定している)
hash_signed = struct.unpack("q", hash_binary)[0]
struct.pack
がバイト配列への変換関数、 struct.unpack
がバイト配列からの変換関数だ。
それぞれの関数の第1引数は、入力/出力となる整数の符号あり/なしとビット数を指定するフォーマット文字だ。
公式のドキュメントの 書式指定文字
という項に一覧があるが、C言語の型事情に精通していることを前提とした書き方になっていて分かりづらいので、翻訳した表を載せておく(整数型のみ)。
フォーマット | 厳密な整数型 |
---|---|
b |
符号あり8bit |
B |
符号なし8bit |
h |
符号あり16bit |
H |
符号なし16bit |
l |
符号あり32bit |
L |
符号なし32bit |
q |
符号あり64bit |
Q |
符号なし64bit |
この一連の処理を関数化したものが、冒頭に挙げた farm_fingerprint
となる。
おわりに
BigQuery、Cloud Spanner互換のfingerprint64処理が書くことができた。
structの話(というかC言語の型の話)はだいぶ端折ったので不明点があればコメントにお願いします。