0

いくつかのドキュメントを mongodb から .csv にエクスポートしようとしています。一部の大きなリストの場合、ファイルは 40M 程度になり、メモリ制限に関するエラーが発生します。

Fatal error: Allowed memory size of 134217728 bytes exhausted 
(tried to allocate 44992513 bytes) in
/usr/share/php/Zend/Controller/Response/Abstract.php on line 586

なぜこのエラーが発生するのだろうか。そのような量のメモリを消費するのは何ですか? memory_limit現在設定されているものを変更せずに、このようなエラーを回避するにはどうすればよいですか128M

私は次のようなものを使用します:

public static function exportList($listId, $state = self::SUBSCRIBED)
{
        $list = new Model_List();       
        $fieldsInfo = $list->getDescriptionsOfFields($listId);
        $headers = array(); 
        $params['list_id'] = $listId;
        $mongodbCursor = self::getCursor($params, $fieldsInfo, $headers);
        $mongodbCursor->timeout(0);
        $fp = fopen('php://output', 'w');       
        foreach ($mongodbCursor as $subscriber) {
            foreach ($fieldsInfo as $fieldInfo) {           
                $field = ($fieldInfo['constant']) ? $fieldInfo['field_tag'] : $fieldInfo['field_id'];
                if (!isset($subscriber->$field)) {
                    $row[$field] = '';
                } elseif (Model_CustomField::isMultivaluedType($fieldInfo['type'])) {
                    $row[$field] = array();     
                    foreach ($subscriber->$field as $value) {
                        $row[$field][] = $value;                        
                    }
                    $row[$field] = implode(self::MULTIVALUED_DELEMITOR, $row[$field]);
                } else {
                    $row[$field] = $subscriber->$field;
                }                               
            }               
            fputcsv($fp, $row);                                  
        }                   
}

次に、コントローラーで次のように呼び出します。

public function exportAction()
{

    set_time_limit(300);


    $this->_helper->layout->disableLayout();
    $this->_helper->viewRenderer->setNoRender();
    $fileName = $list->list_name . '.csv';

    $this->getResponse()->setHeader('Content-Type', 'text/csv; charset=utf-8')
                        ->setHeader('Content-Disposition', 'attachment; filename="'. $fileName . '"');                                                              

    Model_Subscriber1::exportList($listId);
    echo 'Peak memory usage: ', memory_get_peak_usage()/1024, ' Memory usage: ', memory_get_usage()/1024;

}

したがって、データをエクスポートするファイルの最後にいます。1M ドキュメントのようなものでエクスポートしたリストが正常にエクスポートされ、次のように表示されるのはかなり奇妙です。

> Peak memory usage: 50034.921875 Kb Memory usage: 45902.546875 Kb

しかし、1.3M のドキュメントをエクスポートしようとすると、数分後にエクスポート ファイルしか取得できません。

Fatal error: Allowed memory size of 134217728 bytes exhausted 
(tried to allocate 44992513 bytes) in
/usr/share/php/Zend/Controller/Response/Abstract.php on line 586.

エクスポートするドキュメントのサイズはほぼ同じです。

memory_limit を 256M に増やし、1.3M のリストをエクスポートしようとしたところ、次のように表示されました。

ピーク メモリ使用量: 60330.4609375Kb メモリ使用量: 56894.421875 Kb。

私には非常に混乱しているようです。このデータはそれほど不正確ではありませんか? それ以外の場合、memory_limit を 128M に設定するとメモリ不足エラーが発生するのはなぜですか?

4

2 に答える 2

1

ドキュメントのサイズはほぼ同じかもしれませんが、それらを処理するためにPHPによって割り当てられるサイズは、ドキュメントのサイズやドキュメントの数に直接比例しません。これは、PHPではタイプが異なれば必要なメモリ割り当ても異なるためです。あなたが行くにつれていくらかのメモリを解放することができるかもしれません、しかし私はあなたがあなたのコードでそうすることができる場所を見ません。

最善の答えは、おそらくメモリ制限を増やすことです。

実行できることの1つは、処理を外部スクリプトにオフロードし、それをPHPから呼び出すことです。多くの言語は、PHPよりもメモリ効率の高い方法でこの種の処理を行います。

また、memory_get_peak_usage()が常に正確であるとは限らないことにも気づきました。mem_limitを256に増やして、より大きなデータセット(130万)で実行する実験を試みます。128の制限を下回っていることも報告されていることに気付くでしょう。

于 2012-04-10T21:01:32.750 に答える
0

memory_get_usage () で示されるように、システムに十分なメモリがあるはずの CSV ファイルをエクスポートする同様のケースでこの問題を再現できましたが、最終的に同じ致命的なエラー Fatal error: Allowed memory size が発生しました。

CSV の内容を物理的な一時ファイルに出力することで、この問題を回避しました。ファイルをループで書き込んだので、各反復は制限されたデータのチャンクのみを書き込んで、メモリの制限を超えないようにしました。圧縮後の圧縮率は、最初に壁にぶつかったサイズの 10 倍以上の生ファイルを処理できるほどのものでした。無事、成功でした。

ヒント: アーカイブを作成するときは、$zip->close() を呼び出す前にアーカイブ コンポーネントのリンクを解除しないでください。そうしないと、空のアーカイブになってしまいます!

コードサンプル:

<?php
$zip = new ZipArchive;
if ($zip->open($full_zip_path, ZipArchive::CREATE) === TRUE) {
    $zip->addFile($full_csv_path, $csv_name);
    $zip->close();

    $Response->setHeader("Content-type", "application/zip; charset=utf-8");
    $Response->setHeader("Content-disposition", "attachment; filename=" . $zip_name);

    $Response->setBody(file_get_contents($full_zip_path));
}
else {
    var_dump(error_get_last());
    echo utf8_decode("Couldn't create zip archive '$full_zip_path'."), "\r\n";
}

unset($zip);
?>

注意: Windows ベースの OS を使用している場合は、アイテムを zip アーカイブに追加するときに、アイテムの名前の先頭にスラッシュを追加しないでください。

元の問題に関する議論:

引用された行の Zend ファイルは、

public function outputBody()
{
    $body = implode('', $this->_body);
    echo $body;
}

Zend_Controller_Response_AbstractクラスのoutputBody () メソッドから。

ただし、 echo、またはprint、またはreadfileを介して行うと、ディスパッチ前に応答リターン機能をオフにしても、出力は常にキャプチャされ、応答本文にスタックされます。

各$response->sendResponse() の後に$response->clearBody()がメモリを解放することを念頭に置いて、 echo loop内でclearBody()クラスメソッドを使用しようとしましたが、失敗しました。Zend が応答の送信を処理する方法は、未加工の CSV ファイルのフル サイズのメモリ割り当てを常に取得するようなものです。

出力バッファを「キャプチャ」しないように Zend に指示する方法はまだ決定されていません。

于 2016-02-17T11:52:00.013 に答える