[Laravel] Maximum retries of 10000 reached without finding a unique value ってどういうこと? [Faker]
こんにちは。
開発チームのワイルド担当、まんだいです。
Laravel で開発する際、 seeder でテストデータを作ることになるわけですが、基本同梱されている Faker を使うことが多いと思います。
多言語化もできているし、いろんなテストデータが作成できるので便利なんですが、重複しない ID を持つ複数のデータを作る際に便利な unique というメソッドが用意されていて、これがまた便利なんですよね。
ただ、使い方によっては、タイトルのようなエラーが出ることもあって、どういうことなのか分からない、解消できずに別のやり方で... となると、せっかく Faker を導入したのにもったいない!ってことになるので、今回は、 Faker でよく見る「Maximum retries of 10000 reached without finding a unique value」というエラーの解消方法をご紹介したいと思います。
エラーの原因
エラー文言から察するに、どこかの無限ループにハマったなという気はしますが、具体的に心当たりがないことがほとんどで、 Faker の unique() を使っている辺りからエラーが出ていることだけがわかります。
自分では無限ループになるようなコードは書いていないですからね。
適度なタイミングで リセットしてやるとうまく動くのですが、もちろんリセットすると重複が発生します。
このあたりのさじ加減はしばらく使ってみないと分からない部分もありますが、原因が分かれば対処法も自然と分かってくるはず...!!
ということで、Faker の unique() メソッドのコードを追ってみます。
unique() の挙動を追う
github のこの部分が unique() メソッドのコードです。
UniqueGenerator というクラスに受け取った変数をそのままパスしているので、更に UniqueGenerator クラスのコードを確認します。
マジックメソッドでうまく挙動を制御していますが、要は UniqueGenerator::__call() でオブジェクトが作られた際に受け取った Faker\Generator オブジェクトのメソッドを呼び出しているだけです。
while の条件式で重複チェックが入っているため、返ってくる値は一意だ、ということです。
配列キーによる重複チェックはかなり原始的な印象ですが、原因としてはわかりやすいですね。
OverflowException がスローされる際のエラーメッセージにタイトルの文言があり、この do while 文のループ回数がリトライ回数に達したというのが原因でした。
エラー回避の方法
エラーを回避するための調整項目は一つしかなく、unique() メソッドの第2引数の値を変更し、ループのリトライ回数を引き上げるしかありません。
デフォルトで10000回ですが、欲しい値の数が15000個だと必ずオーバーフローします。
リトライ回数を引き上げるには、以下のようにします。
$factory->define(User::class, function(Faker\Generator $faker) { return [ 'name' => $faker->name(), 'age' => $faker->unique(false, 15000)->numberBetween(1, 80), // リトライ回数を 15000 回に変更 ]; });
あと、別の原因として、 Faker\Generator オブジェクトが返す値のバリエーションがそもそも10000個より少ないケースがあります。
上の例もそうですが、そもそも numberBetween() は 1~80 までしか返さないため、81個目のデータはユニークな値が生成できず、リトライ回数を越えてループが実行されます。
人名など、ランダムの組み合わせでバリエーションも豊富な印象ですが、英語のファーストネームは男女合わせて3000程度、ラストネームは473なのでバリエーションはそれなりにありますが、日本語だと名前は男女で50程度しかなく、名字は31、フルネームでも1500程度のバリエーションしかありません。
日本語で5000人分のフルネームが欲しくても、1500通りの組み合わせしかないのであれば、unique() を通しても無意味ということになりますので、何か別の方法を考えなければいけません。
例えば、 Faker\Generator クラスに対して新しいプロバイダーを独自で実装して追加する、という方法があるようですが、まだ詳しく調べきれていないので、いずれご紹介できればと思っています。
まとめ
今回は Faker を使っていてちょくちょく見かける「Maximum retries of 10000 reached without finding a unique value」というエラーについて、調べてみました。
事の始まりは、エラーメッセージからネットで調べていると、 StackOverFlow で reset(true) でいいじゃん!みたいな内容を見かけて、全力でおかしいだろうと思ったことがきっかけです。
なにか根本的な勘違いがあるんじゃないかと思って調べてみましたが、落とし穴は一つじゃなかったようです。
「Maximum retries of 10000 reached without finding a unique value」に出くわしたら、リトライ回数と得られるダミーデータの数を調査しよう、ということでした。
以上です。