Update data calibration and recover stale TCP queue
This commit is contained in:
@@ -80,6 +80,8 @@ systemctl status car-tcp-worker.service
|
|||||||
|
|
||||||
큐 테이블은 코드에서 자동 생성합니다. Worker가 중지되면 API 요청은 큐에 쌓이지만 4.95초 안에 처리되지 않아 실패로 반환될 수 있습니다. 안전을 위해 worker가 뒤늦게 오래된 queued 요청을 발견하면 실제 TCP 송신 없이 `queue_expired_before_send`로 만료 처리합니다.
|
큐 테이블은 코드에서 자동 생성합니다. 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 함수
|
- `common.php`: secret 로드와 공통 DB/API 함수
|
||||||
@@ -99,6 +101,6 @@ systemctl status car-tcp-worker.service
|
|||||||
|
|
||||||
- 차량 TCP 실패 reason과 마지막 수신 지연을 확인합니다.
|
- 차량 TCP 실패 reason과 마지막 수신 지연을 확인합니다.
|
||||||
- 명령별 rate limit과 감사 로그를 유지합니다.
|
- 명령별 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초 뒤 늦은 정산 행이 추가될 수 있습니다.
|
- 4.95초 timeout 직후에는 사용량 기록이 즉시 생기지 않을 수 있으며, 최대 60초 뒤 늦은 정산 행이 추가될 수 있습니다.
|
||||||
- `systemctl status car-tcp-worker.service`와 `journalctl -u car-tcp-worker.service`로 worker 상태를 확인합니다.
|
- `systemctl status car-tcp-worker.service`와 `journalctl -u car-tcp-worker.service`로 worker 상태를 확인합니다.
|
||||||
|
|||||||
+75
@@ -284,6 +284,7 @@ function tcp_queue_get(string $requestId): ?array
|
|||||||
function tcp_queue_claim_next(PDO $pdo): ?array
|
function tcp_queue_claim_next(PDO $pdo): ?array
|
||||||
{
|
{
|
||||||
db_ensure_tcp_queue_schema($pdo);
|
db_ensure_tcp_queue_schema($pdo);
|
||||||
|
tcp_queue_recover_stale($pdo);
|
||||||
|
|
||||||
$pdo->beginTransaction();
|
$pdo->beginTransaction();
|
||||||
try {
|
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(
|
function tcp_queue_record_usage_and_status(
|
||||||
PDO $pdo,
|
PDO $pdo,
|
||||||
array $job,
|
array $job,
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ const RECEIVE_GAP_LIMIT_SEC = 60;
|
|||||||
// SKT T world "잔여기본통화 정보" 기준 현재월 누적값.
|
// SKT T world "잔여기본통화 정보" 기준 현재월 누적값.
|
||||||
// carrier_total_bytes / meter_adjusted_bytes 로 보정 배율을 역산해 로컬 송수신 계측값에 적용한다.
|
// carrier_total_bytes / meter_adjusted_bytes 로 보정 배율을 역산해 로컬 송수신 계측값에 적용한다.
|
||||||
const DATA_CURRENT_BILLING_USAGE_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
|
function car_db(): PDO
|
||||||
|
|||||||
Reference in New Issue
Block a user