From 7c2deb9ec1fff8015c9a31eae666ade1f2d8ae04 Mon Sep 17 00:00:00 2001 From: seo Date: Mon, 15 Jun 2026 19:06:12 +0900 Subject: [PATCH] Update data calibration and recover stale TCP queue --- README.md | 4 ++- common.php | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++++ monitor.php | 2 +- 3 files changed, 79 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cd5e60a..c6a9041 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ systemctl status car-tcp-worker.service 큐 테이블은 코드에서 자동 생성합니다. Worker가 중지되면 API 요청은 큐에 쌓이지만 4.95초 안에 처리되지 않아 실패로 반환될 수 있습니다. 안전을 위해 worker가 뒤늦게 오래된 queued 요청을 발견하면 실제 TCP 송신 없이 `queue_expired_before_send`로 만료 처리합니다. +Worker 교체나 예외 종료로 `processing`, `ui_timeout_pending` 상태가 남으면 다음 claim 전에 stale queue를 복구합니다. 전송 전이면 `expired_before_send`, 이미 TCP payload를 보낸 상태면 `settled_sent_only`로 정산해 사용량 누락을 막습니다. + ## 주요 함수/모듈 - `common.php`: secret 로드와 공통 DB/API 함수 @@ -99,6 +101,6 @@ systemctl status car-tcp-worker.service - 차량 TCP 실패 reason과 마지막 수신 지연을 확인합니다. - 명령별 rate limit과 감사 로그를 유지합니다. -- 통신사 기준 데이터 사용량 보정값을 주기적으로 확인합니다. +- 통신사 기준 데이터 사용량 보정값을 주기적으로 확인합니다. 2026-06-15 18:47 T world 실측 66.44MB(68030KB) 기준 2026-06 보정값은 통신사 누적 69,662,720 bytes, 로컬 보정 전 누적 35,100,790 bytes, 배율 1.984648입니다. - 4.95초 timeout 직후에는 사용량 기록이 즉시 생기지 않을 수 있으며, 최대 60초 뒤 늦은 정산 행이 추가될 수 있습니다. - `systemctl status car-tcp-worker.service`와 `journalctl -u car-tcp-worker.service`로 worker 상태를 확인합니다. diff --git a/common.php b/common.php index ee2c985..4fe2415 100644 --- a/common.php +++ b/common.php @@ -284,6 +284,7 @@ function tcp_queue_get(string $requestId): ?array function tcp_queue_claim_next(PDO $pdo): ?array { db_ensure_tcp_queue_schema($pdo); + tcp_queue_recover_stale($pdo); $pdo->beginTransaction(); try { @@ -313,6 +314,80 @@ function tcp_queue_claim_next(PDO $pdo): ?array } } +function tcp_queue_has_usage(PDO $pdo, string $requestId): bool +{ + $stmt = $pdo->prepare("SELECT 1 FROM car_data_usage WHERE request_id = :request_id LIMIT 1"); + $stmt->execute([':request_id' => $requestId]); + return (bool)$stmt->fetchColumn(); +} + +function tcp_queue_recover_stale(PDO $pdo): void +{ + $now = microtime(true); + $stmt = $pdo->prepare(" + SELECT * + FROM car_tcp_request_queue + WHERE status IN ('processing', 'ui_timeout_pending') + AND usage_deadline_ts > 0 + AND usage_deadline_ts < :now + ORDER BY id ASC + LIMIT 20 + "); + $stmt->execute([':now' => $now]); + $rows = $stmt->fetchAll(); + + foreach ($rows as $row) { + $requestId = (string)($row['request_id'] ?? ''); + if ($requestId === '') { + continue; + } + + $sentBytes = max(0, (int)($row['sent_bytes'] ?? 0)); + $receivedBytes = max(0, (int)($row['received_bytes'] ?? 0)); + $connectMs = max(0, (int)($row['connect_ms'] ?? 0)); + $readMs = max(0, (int)($row['read_ms'] ?? 0)); + $hasUsage = tcp_queue_has_usage($pdo, $requestId); + + if ($hasUsage) { + tcp_queue_update($pdo, $requestId, [ + 'status' => 'worker_lost_usage_recorded', + 'tcp_ok' => 0, + 'tcp_error' => 'worker_lost_after_usage_recorded', + ]); + continue; + } + + if ($sentBytes <= 0) { + tcp_queue_record_usage_and_status( + $pdo, + $row, + 'expired_before_send', + 0, + 0, + false, + 'worker_lost_before_send', + '', + $connectMs, + $readMs + ); + continue; + } + + tcp_queue_record_usage_and_status( + $pdo, + $row, + 'settled_sent_only', + $sentBytes, + $receivedBytes, + false, + 'worker_lost_late_settlement_sent_only', + '', + $connectMs, + $readMs + ); + } +} + function tcp_queue_record_usage_and_status( PDO $pdo, array $job, diff --git a/monitor.php b/monitor.php index 8bac6fa..2d06a7f 100644 --- a/monitor.php +++ b/monitor.php @@ -27,7 +27,7 @@ const RECEIVE_GAP_LIMIT_SEC = 60; // SKT T world "잔여기본통화 정보" 기준 현재월 누적값. // carrier_total_bytes / meter_adjusted_bytes 로 보정 배율을 역산해 로컬 송수신 계측값에 적용한다. const DATA_CURRENT_BILLING_USAGE_BYTES = [ - '2026-06' => ['total_bytes' => 69159936, 'meter_adjusted_bytes' => 34801709, 'included_bytes' => 104857600, 'coupon_registered_at' => null], + '2026-06' => ['total_bytes' => 69662720, 'meter_adjusted_bytes' => 35100790, 'included_bytes' => 104857600, 'coupon_registered_at' => null], ]; function car_db(): PDO