Kouhei Sutou
kou****@clear*****
2013年 9月 3日 (火) 12:19:46 JST
須藤です。 調査結果を教えてもらってありがとうございます! > ≪実験4≫ > 1. cronによる毎分チェックを停止 > 2. チェック用シェルからcountフィールドのUPDATE処理を除去 > 3. 実験3と同様の方法で正常なproductsテーブルを準備 > 4. チェック用シェルをcronで毎分、「二重に」走らせてみる > > <目的> > これでエラーが再現するなら、問題はSELECT文同士の競合?で起きている可能性が高いと考えられる(ただし「UPDATE文との組み合わせでも同じことが起きる」可能性は排除できない)。 > > <結果> > 380回実行したうち、二重に実行したチェック用シェルの > ・両方が成功:2回 > ・片方がエラー:375回 > ・両方がエラー:3回 > となりました。 おぉ、countのUPDATEは関係ないということはわかりましたね。 ということは、問題となっているproductsへの更新は関係ないとい う事がいえると思います。 手元でCentOS 6の仮想マシンを作って実験(後述)してみたのです が再現しませんでした。。。お手数ですが、以下を試してもらえな いでしょうか? (1) 「チェック用シェル」からすべてのUPDATEを除去 UPDATEが関係あるかどうかを切りわけたいです。これでも再現する ならUPDATEは関係ないことがわかります。 ≪実験4≫の 「2. チェック用シェルからcountフィールドのUPDATE処理を除去」 という手順を 「2. チェック用シェルからすべてのUPDATE処理を除去」 に変更してみてください。 (2) 「チェック用シェル」からproductsテーブル以外への操作をすべて除去 productsテーブル以外が関係あるのかどうかを切りわけたいです。 これでも再現するならproductsテーブル以外は関係ありません。 ≪実験4≫の 「2. チェック用シェルからcountフィールドのUPDATE処理を除去」 という手順を 「2. チェック用シェルからcountフィールドのUPDATE処理を除去 し、さらに、他のテーブルへの操作もすべて除去」 に変更してみてください。 (3) productsテーブルから関係なさそうなカラムを除去 [groonga-dev,01718] http://sourceforge.jp/projects/groonga/lists/archive/dev/2013-August/001719.html のやりかたで関係なさそうなカラムを減らしていけると思います。 ただ、問題が再現するかどうかはすでに手順が確立しているので [groonga-dev,01718]の方法ではなく、チェック用シェルを二重に 実行する方法を使ってもらえますか? 後述するといったこちらで試した手順です。これが最小じゃないか というので試してみましたが、そんなに甘くなかったです。 init.sql: ---- DROP DATABASE mroonga; CREATE DATABASE mroonga; USE mroonga; SET NAMES utf8; CREATE TABLE IF NOT EXISTS `products` ( `id` int(11) NOT NULL AUTO_INCREMENT, `code` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `code_vendor_id` (`code`), FULLTEXT KEY `ft_all` (`code`) COMMENT 'normalizer "NormalizerAuto"' ) ENGINE=mroonga DEFAULT CHARSET=utf8 COMMENT='engine "innodb"'; SOURCE data.sql; ---- data.sql: ---- INSERT INTO products VALUES(NULL, "0"); INSERT INTO products VALUES(NULL, "1"); INSERT INTO products VALUES(NULL, "2"); ... INSERT INTO products VALUES(NULL, "999997"); INSERT INTO products VALUES(NULL, "999998"); INSERT INTO products VALUES(NULL, "999999"); ---- data.sqlは以下のコマンドで生成しました。 % ruby -e '1000000.times {|i| puts "INSERT INTO products VALUES(NULL, #{i.to_s.dump});"}' > data.sql 「チェック用シェル」相当は端末を3つ開いて、zshで以下のコマン ドを実行しました。 % while mysql -u root -e "USE mroonga; SELECT * FROM products WHERE code IN ($(for x in {1..300}; do; [ $x != 1 ] && echo -n ', '; ruby -e 'print rand(1000000)'; done))"; do :; done が、残念ながら再現しませんでした。 SQLがエラーになるとmysqlコマンドが0以外を返すのでwhileのルー プが終了するはずですが、終了せずにずっと動き続けました。 ということで、私が試した手順では原因となる要素を除去してしまっ ている気がします。。。 -- 須藤 功平 <kou****@clear*****> 株式会社クリアコード <http://www.clear-code.com/> (03-6231-7270) groongaサポート: http://groonga.org/ja/support/ パッチ採用はじめました: http://www.clear-code.com/recruitment/ コミットへのコメントサービスはじめました: http://www.clear-code.com/services/commit-comment.html In <BAY17****@phx*****> "[groonga,02386] Re: FW: mroonga適用テーブルへの「WHERE IN 多数」クエリでエラー" on Mon, 2 Sep 2013 15:03:49 +0900, Kimura A <a.kim****@live*****> wrote: > 木村です。前回のメール送信時に、件名に「FW:」が付いてしまっていました。 > 今から消すとかえってスレッドが混乱しそうなのでこのままでいきます。すみませんm(_ _)m > > > >yokuさん > > ご回答どうもありがとうございます。 > > 内容を見て、ああ本職のエンジニアはそういう手段を使って検証するものなんだ、と単純に感心してしまいました。 > エラー直後に処理を止めて、そこからさかのぼって一連の処理のログを確保し、その上で後続の処理に戻す、という趣旨ですよね。たしかにそれができれば・・・という感じはします。 > > ただ残念ながら須藤さんのご指摘通り、おそらくそのgbdというものは僕の手に余るだろうと思います。 > 僕はPHPを独学してCakePHPに進んだ素人プログラマーで、今のところ「MySQLは見よう見まねでなんとかいじれる/サーバー扱いについては初心者レベル」という段階です。 > 将来的には扱えるようになってみたいものですが。 > > > >須藤さん > > 詳細なレクチャーをどうもありがとうございます。 > > 実は教えていただいた再現手段を試みる前に、僕自身がメールに書いた > 「インポート直後のDBは正常(少なくとも『SELECT code IN (300件)』クエリが必ず通る状態)である」 > という前提をもう少し確実にしておきたいと考え、外部アクセスから隔離したDBで再度実験してみました。 > > > ≪実験1≫ > 1. 空のtestデータベースを用意 > 2. 実働DBからダンプした全データをインポート > 3. 毎分1回の「WHERE products.code IN ({ランダム300件})」だけが走っている状態にして約20時間放置 > > <結果> > 1200回程度のクエリがすべて成功。 > > この点はやはり大丈夫そうです。 > なので、さっそく簡略化したproductsテーブルを準備して再現条件の検証に移ろうかと思ったのですが、「外部アクセスなしでエラーを再現できた例がない」点がどうしても気になって着手しそびれているうちに、 > >> productsの中で更新しているのはcountだけなんですね。 > > というご指摘がなんとなく引っかかってきたので、同じtestデータベースを使ってそのまま以下の実験をやってみました。 > > > ≪実験2≫ > 1. 以下のクエリを手動で実行 > UPDATE products SET count=count+1 WHERE id=1; > 2. チェック用シェル(cronによる毎分チェックで走らせているもの)を手動で実行して結果判定 > 3. 手順1のUPDATE文のWHERE節を適宜変更(UPDATE 〜 WHERE id={2,3,4,…})しつつ手順1、2を反復 > > <目的> > エラーが起きれば、トラブルの直接の原因はcountフィールドのUPDATE処理ということになる(・・・はずだったのですが、うっかり毎分実行中のチェック用シェルを止めるのを忘れていたため、この目的は果たせませんでした)。 > > <結果> > ・3回目まではUPDATE、チェック用シェルともに成功。 > ・4回目にUPDATEは成功したものの、チェック用シェルが失敗。 > (※UPDATE、チェック用シェルとも手動で実行したものについての結果) > > エラーが出たので、「これは!」と思ったものの、毎分チェックを止め忘れていたことにはすぐ気づきました。 > しかし怪我の功名というか、先日来の実験では一向に起こらなかった「外部アクセスなしでのエラーの再現」がここで実現しました。 > > > cronが走ったままだったことを考慮すると、可能性は3つ残されているように思えました。 > > <可能性> > 1. 手動のUPDATE文によるcount+1処理が原因 > 2. 毎分チェックによるSELECT文と手動のUPDATE文の競合?が原因 > 3. 毎分チェックによるSELECT文と手動のSELECT文の競合?が原因 > > とりあえず最も検証が容易な可能性1を確認することにして、以下の実験を行いました。 > > > ≪実験3≫ > 1. cronによる毎分チェックを停止 > 2. 全テーブルを消去 > 3. 実働DBのproductsテーブルをインポート > 4. 「SELECT 〜 IN (300件)」クエリの正常動作を確認:20回ほど連続成功すればよしとする > 5. チェック用シェルから送られるクエリの冒頭に以下を加筆 > UPDATE products SET count=count+1 WHERE id={ランダムな整数値1〜200000}; > 6. 毎分チェック再開:これ以降、手動では一切クエリを送らずに放置 > > <目的> > エラーが再現した場合、トラブルの直接の原因はcount+1のUPDATE文だと特定できる。 > > <結果> > 65回の実行で1度も再現せず。 > > > この結果を踏まえると、残る可能性は2か3で、いずれにせよ「相次いで送られた複数の処理」が再現の条件となっている可能性が高い、といえそうです。 > だとすると、教えていただいたINSERT⇔SELECTクエリを反復的に送っていく方法だけでは、今回の問題は再現しない可能性が高そうに思えてきました。 > > 続いて、可能性2と3のどちらが問題になっているのかを絞り込むために以下の実験を行いました。 > > > ≪実験4≫ > 1. cronによる毎分チェックを停止 > 2. チェック用シェルからcountフィールドのUPDATE処理を除去 > 3. 実験3と同様の方法で正常なproductsテーブルを準備 > 4. チェック用シェルをcronで毎分、「二重に」走らせてみる > > <目的> > これでエラーが再現するなら、問題はSELECT文同士の競合?で起きている可能性が高いと考えられる(ただし「UPDATE文との組み合わせでも同じことが起きる」可能性は排除できない)。 > > <結果> > 380回実行したうち、二重に実行したチェック用シェルの > ・両方が成功:2回 > ・片方がエラー:375回 > ・両方がエラー:3回 > となりました。 > > > やはり複数の「SELECT 〜 IN (多数)」文を同時にさばこうとするとエラーが出るようです。 > ・「SELECT 〜 IN (多数)」文とUPDATE文との組み合わせではどうか > ・「SELECT 〜 IN (多数)」文と単純なSELECT文ではどうか > ・そもそも単純なSELECT文を二重三重に実行した場合、本当に同様のエラーは出ないのか > など、まだまだ詰める余地はありますが、とりあえず今日わかったのはここまででした。 > > > 以上のような成り行きで、教えていただいた方法を試しそびれてしまったのが現在の状況です。 > 明日以降は、自分としては一応、以下のような手段を考えています。 > > ・上記「『SELECT 〜 IN (多数)』文とUPDATE文との組み合わせ」などの挙動を試す。 > ・教えていただいた検証手順を以下のように一部アレンジして実行してみる。 > 1. 簡易化した空のproductsテーブルを準備し、 > 2. INSERT⇔SELECTクエリを反復的に送るCakePHPシェルを作成し、 > 3. cronからチェック用シェルと交互に実行(チェック用シェルは二重実行)し結果をロギングして、エラーが再現するポイントを探す。 > > 何かお気づきの点があれば、引き続きアドバイスいただければ幸いです。 > どうぞよろしくお願いしますm(_ _)m >