ひろこま Hack Log

プログラミングや機械学習などの知識を記録・共有します

重複が起こらないようにランダムのIDを採番 [Python, Ruby]

f:id:twx:20200119172836p:plain
重複が起こらないようにランダムのIDを採番 [Python, Ruby]

IDをランダムにすると嬉しいこと

何かのシステムを作っていて、ユーザIDや商品IDなどをランダムにしたいことが結構ある。理由は大きく2つある。

  • IDが小さいとなんかダサい

たとえば、あなたの作った新規サービスに何人かのユーザが登録してくれたとして、発行されるユーザIDが30とかだったらどうだろうか? 「このサービス、俺が30人目かよ…」と思われてしまう。ダサい。

  • IDが連番だとハックされやすい

たとえば、いくつかの商品を扱っているECサイトがあったとして、商品番号が101, 102, 103, .... と連続していたらどうだろうか。URLの構造が容易に想像できてしまい、http://example.com/product/101からhttp://example.com/product/999 まで自動的にサイトを巡回するBotとかが作られやすくなってしまう。

そこでランダムなID

以上のような課題を解決するため、IDを文字列長が比較的長いランダムな文字列にしてしまうというアイデアがある。

ただし、完全にランダムだとID生成時に複数個が重複してしまう可能性があるので、その可能性をほぼゼロにしたい。

そこで、現在時刻とランダムな数を組み合わせるという方法が有力だ。

文字列の前半の数文字は現在時刻、後半の数文字はランダムにする。こうすることで、全く同じ時刻にIDを採番しない限り重複が生じることはないし、仮に全く同じ時刻に採番したとしても、乱数が偶然一致しない限り重複が生じることはない。

乱数の桁数を大きくすれば大きくするほど安全になる。

現在時刻は, たとえば2020年4月2日9時49分ならば 202004020949というように、月,日,時,分をゼロ埋めして2ケタにする。

そして、ランダム数字も、231ならば 000000000231 というように規定のケタにするためにゼロ埋めする。

そして、それらを結合(単にくっつける)する。上の例でいうと、

202004020949 + 000000000231 = 202004020949000000000231 となる。

最後に、この文字列をもっと短くしたいたので62進数でエンコードする。

以下では RubyとPythonで上述の採番を実現する方法を紹介する。

Rubyの場合

require 'base62-rb'

Base62.encode( ( ("%.8f" % Time.now.to_f()).to_s.gsub('.', '0') + [*0..9].tap { |x| break 8.times.map { x.sample }.join } ).to_i )

#出力結果
"cJBahuMmZpNrpKq"

Pythonの場合

import base62
import time
import random

base62.encode(int((str('%.8f'%time.time())+str('%.8f'%random.random())).replace('.','0')))

#出力結果
"KXFgQgFNP2YKWB7b"

いずれも、現在時刻をコンマ100000000分の1秒の精度で表現し、8ケタの乱数と結合した上で62進数にしている。


以上、PythonとRubyで重複が起こらないようにランダムのIDを採番する方法でした!

良い記事だと思っていただいた方は、以下の「★+」ボタンのクリック、SNSでのシェア、「読者になる」ボタンのクリック、Twitterのフォローをお願いします!

Koma Hirokazu 's Hacklog ―― Copyright © 2018 Koma Hirokazu