【大阪 / 横浜】インフラ / サーバーサイドエンジニア募集中!

【大阪 / 横浜】インフラ / サーバーサイドエンジニア募集中!

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【導入実績 500社以上】AWS 構築・運用保守・監視サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【CentOS 後継】AlmaLinux OS サーバー構築・移行サービス

【WordPress 専用】クラウドサーバー『ウェブスピード』

【WordPress 専用】クラウドサーバー『ウェブスピード』

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【格安】Webサイト セキュリティ自動診断「クイックスキャナー」

【低コスト】Wasabi オブジェクトストレージ 構築・運用サービス

【低コスト】Wasabi オブジェクトストレージ 構築・運用サービス

【予約システム開発】EDISONE カスタマイズ開発サービス

【予約システム開発】EDISONE カスタマイズ開発サービス

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【100URLの登録が0円】Webサイト監視サービス『Appmill』

【中国現地企業に対応】中国クラウド / サーバー構築・運用保守

【中国現地企業に対応】中国クラウド / サーバー構築・運用保守

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

【YouTube】ビヨンド公式チャンネル「びよまるチャンネル」

[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」に出くわしたら、リトライ回数と得られるダミーデータの数を調査しよう、ということでした。

 
以上です。

この記事がお役に立てば【 いいね 】のご協力をお願いいたします!
1
読み込み中...
1 票, 平均: 1.00 / 11
4,810
X facebook はてなブックマーク pocket
【2024.6.30 CentOS サポート終了】CentOS サーバー移行ソリューション

【2024.6.30 CentOS サポート終了】CentOS サーバー移行ソリューション

【大阪 / 横浜】インフラエンジニア・サーバーサイドエンジニア 積極採用中!

【大阪 / 横浜】インフラエンジニア・サーバーサイドエンジニア 積極採用中!

この記事をかいた人

About the author

萬代陽一

ソーシャルゲームのウェブ API などの開発がメイン業務ですが、ありがたいことにマーケティングなどいろんな仕事をさせてもらえています。
なおビヨンド内での私の肖像権は CC0 扱いになっています。