質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.37%
Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

メモリリーク

メモリリークは、プログラムファイルがメモリの解放に失敗した時に起こります。

Sinatra

Sinatraは、Rubyで作られた オープンソースのWebアプリケーションフレームワークです。

Q&A

解決済

3回答

5947閲覧

NoMemoryError failed to allocate memory (*◔ڼ◔)

sirosiro

総合スコア26

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

MySQL

MySQL(マイエスキューエル)は、TCX DataKonsultAB社などが開発するRDBMS(リレーショナルデータベースの管理システム)です。世界で最も人気の高いシステムで、オープンソースで開発されています。MySQLデータベースサーバは、高速性と信頼性があり、Linux、UNIX、Windowsなどの複数のプラットフォームで動作することができます。

メモリリーク

メモリリークは、プログラムファイルがメモリの解放に失敗した時に起こります。

Sinatra

Sinatraは、Rubyで作られた オープンソースのWebアプリケーションフレームワークです。

0グッド

1クリップ

投稿2016/04/24 20:32

編集2016/04/26 19:46

###前提・実現したいこと
Ruby on Sinatraで極秘のシステムを作っています。
MySQLからselect文で読む時に以下↓のエラーメッセージが発生しました。
発生した箇所まで突き止めましたが必ず発生するわけではないようなので、設定メモリが小さ過ぎてメモリリークが実際に起きているのかもしれないと疑っております。
メモリリークの防ぎ方、またはSinatraでの使用可能メモリの増やし方、NoMemoryErrorと出ているがバグでじつは全く別の原因?かなど、ご教授願います。

###発生している問題・エラーメッセージ

NoMemoryError failed to allocate memory /magi/source/class/magiAdmin.rb:258:in `each' /magi/source/class/magiAdmin.rb:258:in `fuckinMethod' /magi/source/class/magiAdmin.rb:206:in `fuckin' /magi/source/online/web/pgeIndex.rb:84:in `httpGetfuck'

###該当のソースコード

ruby

1 2def fuckinMethod(strJobNameEN) 3 4 # create select sql 5 sqlSelect = 6 %Q{ select SQLLine \n} + 7 %Q{ from admin_log_sql \n} + 8 %Q{ where JobNameEN = '#{strJobNameEN}' \n} + 9 %Q{ order by LineNo asc \n}; 10 11 # run select sql 12 strSQL = ""; 13 rstDataset = self.runSQLSelect(sqlSelect); 14 # ↓ここでNoMemoryError発生(する時としない時がある) 15 rstDataset.each() do |recDataset| 16 # ↑ 17 strSQL = %Q{#{strSQL}#{recDataset["SQLLine"]}\n}; 18 end 19 20end 21 22def runSQLSelect(strSQL) 23 24 self.statusSQL(); 25 26 # connect to mysql server 27 objClient = nil; 28 objClient = Mysql2::Client.new( 29 :host => "localhost", 30 :username => @recUserInfo.strDBUser, 31 :password => @recUserInfo.strDBPass, 32 :database => "magi"); 33 34 # start transaction 35 begin 36 rstDataset = objClient.query(strSQL); 37 rescue => e 38 objClient.query("rollback"); 39 objError = SQLException.new(e.message, e.backtrace, strSQL); 40 ensure 41 objClient.close(); 42 objClient = nil; 43 end 44 45 if objError != nil then 46 raise objError; 47 end 48 49 self.debugSQL(strSQL, rstDataset); 50 self.statusProcess(); 51 52 return rstDataset; 53 54end 55 56# ###*********************************************************************### 57# # debug sql 58# ###*********************************************************************### 59 60def debugSQL(strSQL, rstDataset) 61 62 if @flgDebug then 63 64 @hshResult.delete(:sql); 65 @hshResult[:sql] = strSQL; 66 @hshResult.delete(:dataset); 67 @hshResult[:dataset] = self.debugSQL_display(rstDataset); 68 69 end 70 71end 72 73# ###=====================================================================### 74# # debug sql - display dataset 75# ###=====================================================================### 76 77def debugSQL_display(rstDataset) 78 79 lstDataset = Array.new(); 80 if rstDataset != nil then 81# ここから↓ 82 rstDataset.each() do |recDataset| 83 recDataset.each() do |strKey, datValue| 84 hshRow = Hash.new(); 85 hshRow[:key] = strKey; 86 hshRow[:value] = datValue.to_s(); 87 lstDataset.push(hshRow); 88 end 89 break; 90 end 91# ↑ここまでの処理を行わない時はエラーが起きない 92 end 93 94 return lstDataset; 95 96end

###メモリの空き

console

1 total used free shared buffers cached 2Mem: 16269900 3770256 12499644 0 294024 2722060 3-/+ buffers/cache: 754172 15515728 4Swap: 16609276 53336 16555940 5

###気になっていること
他のページではMySQLからselect文2回発行までは問題なく動いております。
今回のページは4回発行の4回目で失敗しております。
(必ず失敗するわけでもない)
4回目のSQL発行を無くし3回のみにするとNoMemoryErrorは発生しなくなります。

Sinatraのウェブページ表示は@付きの変数に処理結果を入れて*.erbファイルから参照してウェブページに表示するというものです。自分の場合は変数を増やしたくないので@hshResultというハッシュに色々と追加して、ウェブページで表示をしております。
どこかのサイトでrubyのHashに値を突っ込んでいくとメモリをどんどん消費して、何故かガベレージコレクタにも検知されず解放されない?というのを前に見た覚えがあります。自分は@hshResult = nil;することでメモリ解放をしているつもりなのですが、これはやり方としてあってますでしょうか?

###試したこと
debugSQL_display()でrstDataset.each()でselect結果を一度読み込んでいます。
この読み込みを辞めるとエラーが発生しなくなりました。
(読み込み結果をhashでarrayに入れてますがこの処理があってもなくてもエラーになります)
つまり結果データセットのeach()を2回目に発行した場合にMySQLのgem内でメモリエラーが起きたり起きなかったりしているみたいです。

MySQLからの結果データセットをeach()で読み込んだ後、読み込み開始位置?が最後のレコードになっている状態で、再度each()を発行するとダメということでしょうか?そもそも根本的に結果データセットのeach()について自分が勘違いしているかも知れませんので、(動くようにはなりましたが)自己解決にはしないことにします。
どなたか詳しい方、ご教授お願い致します。

###補足情報(言語/FW/ツール等のバージョンなど)
ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux-gnu]
sinatra 1.4.5
MySQL 5.5.32-0ubuntu7 (Ubuntu)

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

ベストアンサー

queryでstreamオプションを有効にするとうまくいくかも知れません。
https://github.com/brianmario/mysql2#user-content-streaming
あとは、全体の物理メモリやスワップメモリの容量はどうでしょうか?膨大なメモリの予約を行う場合があり、スワップメモリがある程度ないとメモリ不足として弾かれる場合があるというのを聞いたことがあります。


MySQLからの結果データセットをeach()で読み込んだ後、読み込み開始位置?が最後のレコードになっている状態で、再度each()を発行するとダメということでしょうか?そもそも根本的に結果データセットのeach()について自分が勘違いしているかも知れませんので、(動くようにはなりましたが)自己解決にはしないことにします。

どなたか詳しい方、ご教授お願い致します。

each()の実装の仕方によります。each()が実装されているオブジェクトは大きく二種類あって、ArrayやHashのように何からのデータの集まり(コレクション)なものと、IOなどのデータの流れ(ストリーム)なものです。コレクションでのeach()は単純で、毎回上から順番に辿ることができます。しかし、ストリームでは、基本一度流れたものは(リセットしない限り)二度目は流れません。このような場合はeach()が使えるのは一度だけになります。その代わり、ストリームはコレクションのように全てをメモリに展開する必要が無いため、メモリ消費量を抑えることができます。

mysql2のeach()はlazy(遅延)処理であり、必要になって初めてsqlの結果からオブジェクトを生成するようで、上で言うストリームに近い処理です。ただ二回走らせることができるようにと、キャッシュを生成するようになっています。問題はそのキャッシュです。each()が完全に終わるまではキャッシュは中途半端な状態であり、そんなときにeach()が呼ばれたため、キャッシュ生成に不整合が発生しておかしくなったと思われます。一度each()を呼び出した場合でうまくいったのは、そのキャッシュが完全にできあがったため、不整合が発生しなくなくなったからだと思われます。

なお、キャッシュを持たずに一回だけを省メモリで高速にするのが上で述べたstreamのオプションであり、この場合は完全にストリームとなって一度だけになります。また、配列と同じようにしたい場合にeachに:as => :arrayオプションが用意されているようです。こちらを使うことでも、上のキャッシュ生成による不整合を防げるかもしれません。

投稿2016/04/24 22:30

編集2016/04/25 22:03
raccy

総合スコア21737

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sirosiro

2016/04/24 23:14

freeの結果を添付しました。また、「気になっていること」にHashの初期化について追加しました。
sirosiro

2016/04/24 23:25

上記の「queryでstream」を参考に実装してみたのですが、私がウンコなのかselectの結果が取得できなくなりました…。rubyでの実装例がわかりません。
raccy

2016/04/25 22:00

追加の質問へ回答しました。
sirosiro

2016/04/26 19:53

1回のみ読む時は問題なく動いていた為、あとはその理由が知りたかったのですが、 上記の内容で納得致しました。ありがとうございます。 これで解決としようと思います。
guest

0

回答ではないですが、SQLインジェクションが起きそうなコードになっていますので、見直すことをお勧めします。

IPAが公開している安全なウェブサイトの作り方や、「体系的に学ぶ安全なWebアプリケーションの作り方」(ISBN 4797361190)などを読まれると良いでしょう。

投稿2016/04/27 02:56

suzukis

総合スコア1449

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

可能性を幾つか列挙します。

  • runSQLSelec() 中で objError の初期化がされていない? エラーが発生した時に例外が投げられていない?
  • fuckinMethod() なかで得ている rstDataset の数がとても大きくなっている?

可能なら、 limit 指定して個数を制限するとよいかもしれません。

stacktrace はでていないのでしょうか?
でていれば、それを提示して欲しいです。

投稿2016/04/24 22:04

katoy

総合スコア22324

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

sirosiro

2016/04/24 23:04

stacktraceを追加しました(極秘システムなので全部はお見せできません) objErrorが初期化されてないのは確かにバグです。ご指摘ありがとうございます。 ただ、メモリエラーとは関係ないようです。 また、rstDatasetのcountはゼロです(select結果がゼロ件) limitを指定して個数の制限をするテクニックは必要ですね、ありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.37%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問