JSON 파일 생성을 스트리밍하려면 어떻게 해야 합니까?
데이터베이스 쿼리의 대규모 덤프에서 JSON 파일을 작성하려고 합니다.반환되는 행의 수를 100000 행으로 설정하면 동작합니다만, 모든 행을 반환하는 경우는, 502 에러가 됩니다(완료에 시간이 너무 걸려 페이지 요구가 취소되었습니다).php를 사용하여 JSON 파일을 비트 단위로 작성할 수 있는 방법이 있는지, 아니면 json 파일을 부품으로 작성할 수 있는 라이브러리가 있는지 궁금하십니까?
기본적으로 여기서 .php 파일을 실행하여 모든 주문을 woocommerce에서 json 형식으로 가져오려고 합니다.주문 Import Suite를 구입한 플러그인은 주문 Import 시 동작하지 않기 때문에 큐에 남아 있습니다.
그래서 모든 주문을 직접 내보내기로 했는데 502 오류 페이지가 계속 뜨면 .json 파일도 생성되지 않기 때문에 어떻게든 스트리밍을 해야겠다고 생각하고 있습니다.어떤 도움이라도 주시면 감사하겠습니다...
ini_set('memory_limit', '-1');
ini_set('max_execution_time', '-1');
set_time_limit(0);
error_reporting(E_ALL);
ob_implicit_flush(TRUE);
ob_end_flush();
global $wpdb, $root_dir;
if (!defined('ABSPATH'))
$root_dir = dirname(__FILE__) . '/';
else
$root_dir = ABSPATH;
$download = isset($_GET['download']);
// Allows us to use WP functions in a .php file without 404 headers!
require_once($root_dir . 'wp-config.php');
$wp->init();
$wp->parse_request();
$wp->query_posts();
$wp->register_globals();
if (empty($download))
$wp->send_headers();
// exclude
$exclude_post_statuses = array('trash', 'wc-refunded', 'wc_cancelled');
$start_date = !empty($_GET['start_date']) ? DateTime::createFromFormat('Y-m-d', $_GET['start_date']) : '';
$end_date = !empty($_GET['end_date']) ? DateTime::createFromFormat('Y-m-d', $_GET['end_date']) : '';
$order_db = array(
'columns' => array(
'p' => array('ID', 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_title', 'post_excerpt', 'post_status', 'comment_status', 'ping_status', 'post_password', 'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', 'post_content_filtered', 'post_parent', 'guid', 'menu_order', 'post_type', 'post_mime_type', 'comment_count'),
'pm' => array('meta_id', 'post_id', 'meta_key', 'meta_value'),
'oi' => array('order_item_id', 'order_item_name', 'order_item_type', 'order_id'),
'oim' => array('meta_id', 'order_item_id', 'meta_key', 'meta_value')
)
);
$select_data = '';
$total_columns = count($order_db['columns']);
$i = 1;
foreach($order_db['columns'] as $column_key => $columns)
{
$select_data .= implode(', ', array_map(
function ($v, $k) { return $k . '.' . $v . ' AS ' . $k . '_' . $v; },
$columns,
array_fill(0, count($columns), $column_key)
));
if ($i < $total_columns)
$select_data .= ', ';
$i++;
}
// HUGE DATABASE DUMP HERE, needs to be converted to JSON, after getting all columns of all tables...
$orders_query = $wpdb->get_results('
SELECT ' . $select_data . '
FROM ' . $wpdb->posts . ' AS p
INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
ORDER BY p.ID ASC', ARRAY_A);
$json = array();
if (!empty($orders_query))
{
foreach($orders_query as $order_query)
{
if (!isset($json[$order_query['p_post_type']], $json[$order_query['p_post_type']][$order_query['p_post_name']]))
$json[$order_query['p_post_type']][$order_query['p_post_name']] = array(
'posts' => array(),
'postmeta' => array(),
'woocommerce_order_items' => array(),
'woocommerce_order_itemmeta' => array()
);
if (!empty($order_query['p_ID']))
$json[$order_query['p_post_type']][$order_query['p_post_name']]['posts'][$order_query['p_ID']] = array_filter($order_query, function($k) {
$is_p = strpos($k, 'p_');
return $is_p !== FALSE && empty($is_p);
}, ARRAY_FILTER_USE_KEY);
if (!empty($order_query['pm_meta_id']))
$json[$order_query['p_post_type']][$order_query['p_post_name']]['postmeta'][$order_query['pm_meta_id']] = array_filter($order_query, function($k) {
$is_pm = strpos($k, 'pm_');
return $is_pm !== FALSE && empty($is_pm);
}, ARRAY_FILTER_USE_KEY);
if (!empty($order_query['oi_order_item_id']))
$json[$order_query['p_post_type']][$order_query['p_post_name']]['woocommerce_order_items'][$order_query['oi_order_item_id']] = array_filter($order_query, function($k) {
$is_io = strpos($k, 'oi_');
return $is_io !== FALSE && empty($is_io);
}, ARRAY_FILTER_USE_KEY);
if (!empty($order_query['oim_meta_id']))
$json[$order_query['p_post_type']][$order_query['p_post_name']]['woocommerce_order_itemmeta'][$order_query['oim_meta_id']] = array_filter($order_query, function($k) {
$is_oim = strpos($k, 'oim_');
return $is_oim !== FALSE && empty($is_oim);
}, ARRAY_FILTER_USE_KEY);
}
}
// Downloading or viewing?
if (!empty($download))
{
// Outputs json in a textarea for you to copy and paste into a .json file for import...
if (!empty($json))
{
$filename = uniqid('orders_') . '.json';
$fp = fopen($filename, 'w');
fwrite($fp, json_encode($json));
fclose($fp);
$size = filesize($root_dir . '/' . $filename);
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header("Content-Disposition: attachment; filename=\"" . $filename . "\"");
header('Content-Transfer-Encoding: binary');
header('Connection: Keep-Alive');
header('Expires: 0');
header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header('Pragma: public');
header('Content-Length: ' . $size);
readfile($root_dir . '/' . $filename);
}
}
else
{
// Outputs json in a textarea for you to copy and paste into a .json file for import...
if (!empty($json))
echo '<textarea cols="200" rows="50">', json_encode($json), '</textarea>';
}
생성된 JSON 파일은 500MB를 훨씬 초과할 수 있으며 최대 1Gb의 데이터도 있을 수 있습니다.따라서 PHP는 메모리가 부족하기 때문에 백그라운드에서 또는 php 메모리 제한에 도달하지 않고 조금씩 처리되어야 합니다.메모리 제한은 1024 MB로 설정되어 있습니다.이것은 상당히 높지만, 충분히 높지는 않고, TBh로 설정되어 있기 때문에, 지금 하고 있는 작업에서는, 현재의 상태로 작업을 실행할 수 있는 메모리가 부족하다고 생각합니다.json 처리 및/또는 다운로드 방법을 변경해야 합니다.그리고 JSON 파일을 여러 개 만들고 싶지 않으므로 JSON 파일 하나만 만들어 주세요.
몇 가지 문제가 있을 것 같아요.우선 프로파일링을 해보라고 제안합니다.
// HUGE DATABASE DUMP HERE, needs to be converted to JSON, after getting all columns of all tables...
echo 'Start Time: '. date("Y-m-d H:i:s");
echo ' Memory Usage: ' . (memory_get_usage()/1048576) . ' MB \n';
$orders_query = $wpdb->get_results('
SELECT ' . $select_data . '
FROM ' . $wpdb->posts . ' AS p
INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
ORDER BY p.ID ASC', ARRAY_A);
echo 'End Time: '. date("Y-m-d H:i:s");
echo ' Memory Usage: ' . (memory_get_usage()/1048576) . ' MB \n';
die('Finished');
$json = array();
위의 내용은 현재 사용 중인 메모리의 양을 파악하는 데 도움이 됩니다.Finished(완료)라고 출력되기 전에 실패하면 json 문제가 아님을 알 수 있습니다.스크립트가 정상적으로 동작하면 먼저 json이 아닌 csv 파일을 작성할 수 있습니다.선택 쿼리를 실행 중이기 때문에 (이 시점에서는) 필요한 중첩된 json 파일일 필요는 없습니다.CSV 파일을 작성하는 것만으로 플랫한 구조를 실현할 수 있습니다.
$csvFile = uniqid('orders') . '.csv';
$fp = fopen($csvFile, 'w');
if (!empty($orders_query))
{
$firstRow = true;
foreach($orders_query as $order_query)
{
if(true === $firstRow) {
$keys = array_keys($order_query);
fputcsv($fp, $order_query);
$firstRow = false;
}
fputcsv($fp, $order_query);
}
}
fclose($fp);
위의 방법으로 정상적으로 동작할 경우 최소한 CSV 파일을 사용할 수 있습니다.
이 시점에서는 데이터 구조가 얼마나 복잡한지 알 수 없습니다.예를 들어 'p_post_type' 및 'p_post_name'에 대해 몇 개의 고유한 값이 존재하는지 확인합니다.csv 파일을 해석하고 ['p_post_type']['p_post_name']['p_post_type']['p_post_name']['p_post_name'][poocommer_ce'] 및 각 항목에 대해 여러 json 파일을 생성해야 할 수 있습니다.
파일 수가 적은 경우 자동으로 병합하는 스크립트를 작성하거나 수동으로 병합할 수 있습니다.중첩된 항목이 너무 많으면 생성될 수 있는 json 파일의 수가 많고 병합하기 어려울 수 있으며 실행 가능한 옵션이 아닐 수 있습니다.
json 파일 수가 많다면 이렇게 큰 단일 json 파일의 목적이 무엇인지 알고 싶습니다.내보내기가 문제인 경우 Import도 문제가 될 수 있습니다.특히 메모리 내의 대용량 json 파일을 수집하면 문제가 될 수 있습니다.향후 어떤 단계에서 json 파일을 Import하는 것이 목적이라면 그 시점에서 필요한 것을 걸러내기 위해 사용하는 csv 파일만 있으면 된다고 생각합니다.
이게 도움이 됐으면 좋겠어요.
추가 업데이트
$wpdb->get_results는 mysqli_query/mysql_query(설정 내용에 따라 다름)를 사용하여 결과를 가져오는 것으로 보입니다.워드프레스 쿼리 문서를 참조하십시오.이 방법으로 데이터를 가져오는 것은 메모리 효율이 높은 방법이 아닙니다.이 시점에서는 ($wpdb->get_results) 자체에서 실패할 수 있습니다.$wpdb를 사용하지 않고 쿼리를 실행하는 것이 좋습니다.대용량 데이터 검색이 필요할 때마다 버퍼링되지 않은 쿼리의 개념이 있어 메모리에 미치는 영향이 매우 적습니다.자세한 내용은 mysql unbuffering을 참조하십시오.
이 점을 지나쳐도 메모리를 많이 소비하는 $json 변수에 모든 데이터를 저장하는 방법 때문에 메모리 문제가 발생할 수 있습니다.$json은 어레이입니다.PHP 어레이가 어떻게 동작하는지 알면 흥미로울 것 같습니다.PHP 어레이는 동적이고 새로운 요소가 추가될 때마다 메모리를 추가하지 않습니다.그것은 매우 느리기 때문입니다.대신 어레이 사이즈를 2의 거듭제곱으로 늘립니다.즉, 한계치가 소진될 때마다 어레이 제한을 현재의 제한치의 2배로 늘리고 그 과정에서 메모리를 2배로 늘리려고 합니다.PHP 7에서는 php core에 큰 변경을 가했기 때문에, 이것은 그다지 문제가 되지 않았습니다.따라서 $json에 저장해야 하는 2GB 데이터가 있는 경우 스크립트는 제한에 도달한 시점에 따라 3~4GB의 메모리를 쉽게 할당할 수 있습니다.자세한 내용은 php 어레이와 PHP 메모리의 실제 작동 방식을 참조하십시오.
$orders_query의 오버헤드와 $json의 오버헤드를 조합한 어레이의 오버헤드를 생각하면 PHP 어레이의 동작 방식 때문에 상당히 큰 의미가 있습니다.
다른 데이터베이스 B를 작성할 수도 있습니다.따라서 데이터베이스 A에서 데이터를 읽는 동안 데이터베이스 B에 데이터를 동시에 쓰기 시작합니다.결국 MySQL의 힘으로 모든 데이터가 포함된 데이터베이스 B를 갖게 됩니다.동일한 데이터를 MongoDB에 푸시할 수도 있습니다.이는 번개처럼 빠르고 원하는 json nesting에 도움이 될 수 있습니다.MongoDB는 대규모 데이터셋에서 매우 효율적으로 작동합니다.
JSON 스트리밍 솔루션
먼저 스트리밍은 순차적/선형 프로세스라고 말하고 싶습니다.따라서 이 시점 이전에 추가된 항목이나 이 시점 이후에 추가된 항목에 대한 메모리가 없습니다.이것은 작은 덩어리로 작동하기 때문에 메모리 효율이 매우 높습니다.따라서 실제로 글을 쓰거나 읽을 때, 스트리밍은 텍스트만 이해하고 json이 무엇인지에 대한 단서는 없으며 올바른 json을 쓰거나 읽는 데 방해가 되지 않기 때문에 특정 순서를 유지하는 것은 스크립트에 책임이 있습니다.
github https://github.com/skolodyazhnyy/json-stream에서 원하는 것을 달성하는 데 도움이 되는 라이브러리를 찾았습니다.코드를 시험해 본 결과, 코드의 몇 가지 수정이 가능한 것을 알 수 있습니다.
당신을 위해 의사 코드를 작성하겠습니다.
//order is important in this query as streaming would require to maintain a proper order.
$query1 = select distinct p_post_type from ...YOUR QUERY... order by p_post_type;
$result1 = based on $query1;
$filename = 'data.json';
$fh = fopen($filename, "w");
$writer = new Writer($fh);
$writer->enter(Writer::TYPE_OBJECT);
foreach($result1 as $fields1) {
$posttype = $fields1['p_post_type'];
$writer->enter($posttype, Writer::TYPE_ARRAY);
$query2 = select distinct p_post_name from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype order by p_post_type,p_post_name;
$result2 = based on $query2;
foreach($result2 as $fields2) {
$postname = $fields1['p_post_name'];
$writer->enter($postname, Writer::TYPE_ARRAY);
$query3 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where p_ID is not null order by p_ID;
$result3 = based on $query3;
foreach($result2 as $field3) {
$writer->enter('posts', Writer::TYPE_ARRAY);
// write an array item
$writer->write(null, $field3);
}
$writer->leave();
$query4 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where pm_meta_id is not null order by pm_meta_id;
$result4 = based on $query4;
foreach($result4 as $field4) {
$writer->enter('postmeta', Writer::TYPE_ARRAY);
// write an array item
$writer->write(null, $field4);
}
$writer->leave();
$query5 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where oi_order_item_id is not null order by oi_order_item_id;
$result5 = based on $query5;
foreach($result5 as $field5) {
$writer->enter('woocommerce_order_items', Writer::TYPE_ARRAY);
// write an array item
$writer->write(null, $field5);
}
$writer->leave();
$query6 = select ..YOUR COLUMNS.. from ...YOUR QUERY... YOUR WHERE ... and p_post_type= $posttype and p_post_name=$postname where oim_meta_id is not null order by oim_meta_id;
$result6 = based on $query6;
foreach($result6 as $field6) {
$writer->enter('woocommerce_order_itemmeta', Writer::TYPE_ARRAY);
// write an array item
$writer->write(null, $field5);
}
$writer->leave();
}
$writer->leave();
fclose($fh);
올바른 결과를 얻을 때까지 쿼리를 10가지로 제한해야 할 수 있습니다.위의 코드가 그대로 작동하지 않을 수 있기 때문입니다.같은 라이브러리에 리더 클래스가 있기 때문에, 같은 방법으로 코드를 읽을 수 있을 것입니다.독자와 작가를 모두 테스트해 봤는데 잘 작동하는 것 같아요.
파일 생성
코드의 문제는 전체 데이터 세트를 메모리에 넣으려고 하는 것입니다.데이터베이스가 충분히 커지면 바로 실패합니다.이 문제를 해결하려면 데이터를 일괄적으로 가져와야 합니다.
쿼리를 여러 번 생성할 예정이기 때문에 당신의 쿼리를 함수로 추출했습니다.간결하게 하기 위해 필요한 파라미터의 전달(또는 글로벌화)을 생략했기 때문에 스스로 조작할 필요가 있습니다.
function generate_query($select, $limit = null, $offset = null) {
$query = 'SELECT ' . $select . '
FROM ' . $wpdb->posts . ' AS p
INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
ORDER BY p.ID ASC';
if ($limit && $offset) {
$query .= ' LIMIT ' . $limit . ' OFFSET ' . $offset;
}
return $query;
}
이제 DB에서 결과를 일괄적으로 가져오고 메모리에 로드되는 반복당 레코드 수인 배치 카운트를 정의합니다.나중에 이 값을 사용하여 충분히 빠르고 PHP 크래시가 발생하지 않는 값을 찾을 수 있습니다.데이터베이스 쿼리 수를 가능한 한 줄이고 싶다는 점에 유의하십시오.
define('BATCH_COUNT', 500);
루프를 작성하기 전에 반복 횟수(데이터베이스 호출)를 알아야 하므로 총 주문 수가 필요합니다.이 값과 배치 카운트를 사용하면 이 값을 쉽게 계산할 수 있습니다.
$orders_count = $wpdb->get_col(generate_query('COUNT(*)'));
$iteration_count = ceil($orders_count / BATCH_COUNT);
그 결과, 결과 파일내에 큰 JSON 스트링을 가지고 싶다고 생각하고 있습니다. JSON이 JSON을 합니다.[ ★★★★★★★★★★★★★★★★★」]JSON 문자열의 양쪽에서 해당 문자열을 파일에 저장합니다.
최종 코드:
define('FILE', 'dump.json');
file_put_contents(FILE, '[');
for ($i = 0; $i < $iteration_count; $i++) {
$offset = $i * BATCH_COUNT;
$result = $wpdb->get_results(
generate_query($select_data, BATCH_COUNT, $offset),
ARRAY_A
);
// do additional work here, add missing arrays etc.
// ...
// I assume here the $result is a valid array ready for
// creating JSON from it
// we append the result file with partial JSON
file_put_contents(FILE, trim(json_encode($result), '[]'), FILE_APPEND);
}
file_put_contents(FILE, ']', FILE_APPEND);
축하합니다. 첫 번째 대규모 JSON 덤프를 작성했습니다.) 이 스크립트를 명령줄에서 실행하여 필요한 만큼 오래 사용할 수 있도록 해야 합니다.이제 메모리 제한을 변경할 필요가 없습니다.이는 앞으로 메모리 제한을 변경할 필요가 없기 때문입니다.
파일 전송
PHP로 대용량 파일을 스트리밍하는 것은 쉽고 이미 SO에서 여러 번 응답되었습니다.단, PHP에서는 명령행이나 파일 서버 중 어느 쪽이든 장시간 실행되는 프로세스로 인해 시간이 걸리는 작업을 하는 것은 권장하지 않습니다.
Apache를 사용하시는 것 같습니다.Send File을 사용하는 것을 고려하고 Apache가 대신 힘든 작업을 하도록 해야 합니다.이 방법은 대용량 파일을 처리할 때 훨씬 더 효율적입니다.이 방법은 매우 간단합니다.헤더 내의 파일에 경로를 전달하기만 하면 됩니다.
header('X-Sendfile: ' . $path_to_the_file);
Nginx를 사용하는 경우 XSend File도 지원됩니다.
이 메서드는 메모리를 많이 사용하지 않으며 PHP 프로세스를 차단하지 않습니다.이 파일은 webroot에서도 액세스할 수 있습니다.인증된 사용자에게 4K 동영상을 제공하기 위해 XSend File을 항상 사용합니다.
먼저 스스로에게 질문해야 합니다.데이터베이스 덤프를 직접 작성해야 합니까?
그렇지 않은 경우, 작업을 수행할 수 있는 서비스를 사용할 수 있습니다.Mysqldump-php는 이 작업을 수행할 수 있습니다.
그 후, 다음과 같이 할 수 있습니다.
include_once(dirname(__FILE__) . '/mysqldump-php-2.0.0/src/Ifsnop/Mysqldump/Mysqldump.php');
$dump = new Ifsnop\Mysqldump\Mysqldump('mysql:host=localhost;dbname=testdb', 'username', 'password');
$dump->start('storage/work/dump.sql');
하면 " " " 가 생성됩니다..sql★★를 요. 지만 、 은은은json파일이야 문제될 건 없을 거야이 툴로 나머지 작업을 수행할 수 있습니다.http://www.csvjson.com/sql2json
요.sql2jsonon github : https://github.com/martindrapeau/csvjson-app
거라고 생각합니다.Generators http://php.net/manual/en/language.generators.overview.phphttpphp.net/manual/en/language..overview.phphttps://scotch.io/tutorials/understanding-php-generatorshttpsscotch.io/tutorials/
그렇게 크게 만드는 대신$json한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번, 한 번을 $order_query각 반복마다 작업을 수행하여 메모리에 저장할 필요가 없습니다.
문제는 쿼리에서 큰 결과 세트를 얻는다는 것입니다.이러한 결과는 3개의 조인(join)이 있기 때문에 무겁습니다.
한계를 정의하고 오프셋을 사용하여 데이터를 청크로 가져온 다음 부품으로 json을 출력할 수 있습니다.가장 큰 문제는 어떻게든 json 데이터를 메모리에 넣고 그 데이터에 액세스하여 부품으로 출력하는 것입니다.
후자의 캐시 또는 nosql 데이터베이스를 사용할 수 있습니다.솔루션에서는 캐시, 특히 memcache를 사용합니다.
class Cache {
private $cache;
public function __construct($cache)
{
$this->cache = $cache;
}
public function addPostName($postName)
{
$this->addKeyToJsonObject('postNames', $postName);
}
public function addKeyToJsonObject($rootName, $key)
{
$childNames = $this->cache->get($rootName);
if($childNames === false) {
$this->cache->set($rootName, [$key]);
}
else {
$childNamesList = $childNames;
// not found
if(array_search($key, $childNamesList) === false) {
$childNamesList[] = $key;
$this->cache->set($rootName, $childNamesList);
}
}
}
public function getPostNames()
{
return $this->cache->get('postNames');
}
public function set($key, $value) {
$this->cache->add($key, $value);
}
public function addPostIdsByNameAndType($postName, $type, $pid)
{
$this->addKeyToJsonObject($postName . '-' . $type, $pid);
}
public function getPostIdsByNameAndType($postName, $type)
{
return $this->cache->get($postName . '-' . $type);
}
public function addPostValueByNameTypeAndId($postName, $type, $pid, $value)
{
$this->cache->set($postName . '-' . $type . '-' . $pid, $value);
}
public function getPostValueByNameTypeAndId($postName, $type, $pid)
{
return $this->cache->get($postName . '-' . $type . '-' . $pid);
}
}
그 후:
$memcache = new Memcache();
$memcache->connect('127.0.0.1', 11211) or die ("Could not connect");
$memcache->flush();
$cache = new Cache($memcache);
header('Content-disposition: attachment; filename=file.json');
header('Content-type: application/json');
echo '{"shop_order":{';
function getResultSet($wpdb, $offset = 1) {
return $wpdb->get_results('
SELECT ' . $select_data . '
FROM ' . $wpdb->posts . ' AS p
INNER JOIN ' . $wpdb->postmeta . ' AS pm ON (pm.post_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_items AS oi ON (oi.order_id = p.ID)
LEFT JOIN ' . $wpdb->prefix . 'woocommerce_order_itemmeta AS oim ON (oim.order_item_id = oi.order_item_id)
WHERE p.post_type = "shop_order"' . (!empty($exclude_post_statuses) ? ' AND p.post_status NOT IN ("' . implode('","', $exclude_post_statuses) . '")' : '') . (!empty($start_date) ? ' AND post_date >= "' . $start_date->format('Y-m-d H:i:s') . '"' : '') . (!empty($end_date) ? ' AND post_date <= "' . $end_date->format('Y-m-d H:i:s') . '"' : '') . '
ORDER BY p.ID ASC LIMIT 1000 OFFSET ' . $offset, ARRAY_A);
}
$offset = 1;
$orders_query = getResultSet($wpdb, 1);
while(!empty($orders_query)) {
cacheRowData($cache, $orders_query);
$offset = $offset + 1000;
$orders_query = getResultSet($wpdb, $offset);
}
outputRowData($cache);
function cacheRowData($cache, $orders_query)
{
foreach($orders_query as $order_query) {
if(empty($order_query)) { continue; }
$cache->addPostName($order_query['p_post_name']);
// posts
if (!empty($order_query['p_ID'])) {
$cache->addPostIdsByNameAndType($order_query['p_post_name'],'posts', $order_query['p_ID']);
$value = array_filter($order_query, function($k) {
$is_p = strpos($k, 'p_');
return $is_p !== FALSE && empty($is_p);
}, ARRAY_FILTER_USE_KEY);
$cache->addPostValueByNameTypeAndId($order_query['p_post_name'],'posts', $order_query['p_ID'], $value);
}
if (!empty($order_query['pm_meta_id'])) {
$cache->addPostIdsByNameAndType($order_query['p_post_name'],'postmeta', $order_query['pm_meta_id']);
$value = array_filter($order_query, function($k) {
$is_pm = strpos($k, 'pm_');
return $is_pm !== FALSE && empty($is_pm);
}, ARRAY_FILTER_USE_KEY);
$cache->addPostValueByNameTypeAndId($order_query['p_post_name'],'postmeta', $order_query['pm_meta_id'], $value);
}
// here do the same for "woocommerce_order_items" and "woocommerce_order_itemmeta"
}
}
function outputRowData($cache)
{
$cachedPostNames = $cache->getPostNames();
$firstRow = true;
foreach($cachedPostNames as $postName) {
if(empty($postName)) { continue; }
if($firstRow === false) {
echo ',';
}
$firstRow = false;
echo '"' . $postName . '":{';
$postIds = $cache->getPostIdsByNameAndType($postName, 'posts');
if(!$postIds) {
$postIds = [];
}
// generate posts
$postValues = [];
foreach ($postIds as $postId) {
$postValues[$postId] = $cache->getPostValueByNameTypeAndId($postName, 'posts', $postId);
}
$postMetaIds = $cache->getPostIdsByNameAndType($postName, 'postmeta');
if(!$postMetaIds) {
$postMetaIds = [];
}
$postMetaValues = [];
foreach ($postMetaIds as $postMetaId) {
$postMetaValues[$postMetaId] = $cache->getPostValueByNameTypeAndId($postName, 'postmeta', $postMetaId);
}
// here do the same for "woocommerce_order_items" and "woocommerce_order_itemmeta"
echo '"posts":' . json_encode($postValues) . ',';
echo '"postmeta":' . json_encode($postMetaValues);
echo '}';
ob_flush();
flush(); // flush the output to start the download
}
}
echo '}}';
이 일을 제대로 하기 위해서는 많은 것들이 필요합니다.제가 생각하고 있는 요점을 모두 말씀드리겠습니다.
Web Server에 의한 종료
Apache 또는 Nginx/PHP-FPM을 사용하는 경우 기본적으로는 둘 다 히트한 URL에 대한 타임아웃이 있습니다.그래서 비록 네가 이 모든 것들을
ini_set('memory_limit', '-1');
ini_set('max_execution_time', '-1');
set_time_limit(0);
스크립트를 오래 실행하지만 Apache, Nginx, PHP-FPM은 모두 타임아웃이 있어 스크립트가 작동하지 않습니다.그러니까 이걸 고쳐야 제대로 되는군요.어떤 서버를 사용했는지에 대해서는 언급하지 않았습니다.단, NGINX+PHP-FPM은 디폴트 설정으로 확실히 502가 됩니다.
메모리 사용량
사용하신 적이 있지만
ini_set('memory_limit', '-1');
메모리가 많이 필요할 경우 PHP가 페이징을 시작하고 코드가 느려질 수 있습니다.
PHP CLI 또는 PHP Web?
여기서 실행 빈도가 어느 정도인지는 확실하지 않지만, 낮으면 데이터 덤핑 스크립트는 HTTP가 아닌 PHP-CLI를 통해 실행되도록 고려할 수 있습니다.이는 터미널을 통해 직접 PHP 스크립트를 실행하여 JSON을 파일에 덤프하고 나중에 URL을 사용하여 파일을 직접 다운로드하는 것을 의미합니다.
X-Sendfile 또는 X-Accel-Redirect 사용
Apache를 사용하는 경우 헤더를 전송할 수 있습니다.
header('X-Sendfile: /data/generated.json');
Nginx의 경우,
header('X-Accel-Redirect: /data/generated.json');
이 작업은 스크립트를 CLI가 아닌 웹으로 실행하기로 결정한 경우에만 수행합니다.JSON 생성이 완료되면 스크립트가 파일 및 서버를 읽지 않도록 합니다.웹 서버가 처리해 주길 바랄 뿐입니다.
WPDB 쿼리 대신 버퍼링되지 않은 쿼리
https://core.trac.wordpress.org/browser/tags/4.9/src/wp-includes/wp-db.php#L2480
기본적으로는 WPDB 쿼리는 모든 데이터를 메모리로 가져옵니다.그러나 버퍼링되지 않은 쿼리를 사용하여 DB를 직접 쿼리할 수 있습니다. 이렇게 하면 메모리가 플래딩되지 않습니다.
Example #1 Unbuffered query example: mysqli
<?php
$mysqli = new mysqli("localhost", "my_user", "my_password", "world");
$uresult = $mysqli->query("SELECT Name FROM City", MYSQLI_USE_RESULT);
if ($uresult) {
while ($row = $uresult->fetch_assoc()) {
echo $row['Name'] . PHP_EOL;
}
}
$uresult->close();
?>
Example #2 Unbuffered query example: pdo_mysql
<?php
$pdo = new PDO("mysql:host=localhost;dbname=world", 'my_user', 'my_pass');
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
$uresult = $pdo->query("SELECT Name FROM City");
if ($uresult) {
while ($row = $uresult->fetch(PDO::FETCH_ASSOC)) {
echo $row['Name'] . PHP_EOL;
}
}
?>
Example #3 Unbuffered query example: mysql
<?php
$conn = mysql_connect("localhost", "my_user", "my_pass");
$db = mysql_select_db("world");
$uresult = mysql_unbuffered_query("SELECT Name FROM City");
if ($uresult) {
while ($row = mysql_fetch_assoc($uresult)) {
echo $row['Name'] . PHP_EOL;
}
}
?>
https://secure.php.net/manual/en/mysqlinfo.concepts.buffering.php
PS: 아직 머릿속에 몇 가지 부족한 점이 있을 수 있습니다.곧 업데이트 할 예정입니다.
언급URL : https://stackoverflow.com/questions/48535819/how-to-stream-creation-of-a-json-file
'programing' 카테고리의 다른 글
| 워드프레스:사용자용 새 사용자 이름 필드 만들기 (0) | 2023.03.14 |
|---|---|
| JSON을 사용하여 JSON 데이터를 C#으로 역직렬화합니다.그물 (0) | 2023.03.14 |
| Javascript:휴대용 바코드 스캐너를 가장 잘 읽는 방법은 무엇입니까? (0) | 2023.03.14 |
| Java 웹 애플리케이션용 Spring Boot의 단점은 무엇입니까? (0) | 2023.03.14 |
| WooCommerce Order API는 항상 빈 주문을 만듭니다. (0) | 2023.03.14 |