経緯
Firestore を使ってみようと思い調査したところ、
ホットスポットなるものが生じない設計を心がけねばならないということがわかった。
辞書順で近い一連のドキュメントに対して、高頻度で読み取りや書き込みを行わないでください。この問題はホットスポットといいます。次のいずれかを行うと、アプリケーションにホットスポットが生じる可能性があります。
https://firebase.google.com/docs/firestore/best-practices?hl=ja
高頻度ってどれくらいだろうと思いつつ、日付をドキュメントで検索しようと思っていたのでこれは問題になりそうだと思い、ひとまず日付をハッシュ化して取り扱おうと考えた。
課題
日付をハッシュ化して問題となるのは桁数。
Firestoreは保存容量も課金対象になるので下手にハッシュ化して文字数が増えることは避けたい。
調べてみると大体8桁から128桁の関数が多いようだが、できればもっと少ないほうがいい。
解決
Hashids というライブラリを見つけた。
Android アプリで使おうと思っていたので Kotlin も対応されているのは嬉しい。
また、ハッシュ値をデコードすることもできるようだ。
今回は特に、本来そのまま扱いたい日付データを便宜上ランダム値に変換して扱わなくてはいけないというだけなので、デコードできればそれにこしたことはない。
でもハッシュ値ってなんだっけ?
衝突しないの?
Hashids の仕様を勘違いしていたのだが、
Hashids で作成したハッシュ値はその桁数が一定ではない。
引数に与えられる数値が増えていけばハッシュ値の桁数も増えていくようだ。
また公式を見ても、
処理的には数値を16進文字列に変換する処理と似たことをしていると書いてある。
なので衝突については考えなくて良いっぽい。
ただ念の為、戻り値が衝突しないのか試してみた。
要件的には日付データの 101 から 1231 、西暦データの 2020 から 2100 までのデータが衝突しないことが確認できればよかったのだが、めんどくさいので適当に 1 から 10,000 でデータが衝突しないか試した。
使用したのは以下のコード。
当たり前といえば当たり前だが、衝突することはなかった。
from hashids import Hashids
def create_hash(str:int) -> str:
hashids = Hashids()
id = hashids.encode(str)
return id
def setup_target_str_list() -> list:
target_str_list = []
for i in range(10000):
target_str_list.append(create_hash(i))
return target_str_list
def has_duplicate_item(list:list) -> bool:
return len(list) != len(set(list))
def create_duplicated_item_list(param:list) -> list:
return list(set([x for x in param if param.count(x) > 1]))
target_str_list = setup_target_str_list()
print("target_str_list:{}".format(target_str_list))
if has_duplicate_item(target_str_list):
duplicated_item_list = create_duplicated_item_list(target_str_list)
print(duplicated_item_list)
else:
print('No duplicated item')
なお、 1 をハッシュ化すると “gY” 、10,000 をハッシュ化すると “w0rR” となり、桁数もそれほど多くない。
ホットスポット対策としては良き良き。