감사 상세를 무손실 압축하고 이전 기수 정리 처리량을 개선한다

This commit is contained in:
Hide_D committed 2026-11-09 20:43:05 +09:00
1 parent da79013226
commit 078b9aa205
23 files changed
+504 -95

No files matched your search

+1
View File
@@ -15,3 +15,4 @@ export * from './playAuditDiplomacy.js';
export * from './messageEnvelope.js';
export * from './webPushOutbox.js';
export * from './playAuditBestEffort.js';
export * from './playAuditDecisionCodec.js';
@@ -0,0 +1,44 @@
import { gzipSync, gunzipSync } from 'node:zlib';
import type { InputJsonValue } from './turnEngineDb.js';
const ENCODING = 'gzip-json-v1';
const MAX_STEPS = 128;
const MAX_BYTES = 16 * 1024 * 1024;
/** 조회/재실행 hash에 쓰는 원문은 바꾸지 않고 저장할 때만 무손실 압축한다. */
export const encodeAuditDecisionSteps = (steps: readonly InputJsonValue[]): InputJsonValue => {
if (steps.length > MAX_STEPS) throw new Error('Audit decision chunk exceeds its step limit');
const json = JSON.stringify(steps);
// 드문 초대형 기존 payload도 버리거나 자르지 않는다. 압축 reader 상한 밖이면 원래 형식으로 저장한다.
if (Buffer.byteLength(json) > MAX_BYTES) return steps.slice();
const encoded = { encoding: ENCODING, data: gzipSync(json, { level: 3 }).toString('base64') };
return Buffer.byteLength(JSON.stringify(encoded)) < Buffer.byteLength(json) ? encoded : steps.slice();
};
/** 이전 JSONB 배열과 압축 형식을 함께 읽는다. 손상/알 수 없는 버전은 빈 기록으로 숨기지 않는다. */
export const decodeAuditDecisionSteps = (value: unknown): unknown[] => {
if (Array.isArray(value)) {
if (value.length > MAX_STEPS) throw new Error('Audit decision chunk exceeds its step limit');
return value;
}
if (!value || typeof value !== 'object' || !('encoding' in value) || !('data' in value))
throw new Error('Invalid audit decision storage');
if (
value.encoding !== ENCODING ||
typeof value.data !== 'string' ||
value.data.length > Math.ceil(MAX_BYTES / 3) * 4 + 256 ||
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value.data)
)
throw new Error('Invalid audit decision encoding');
let decoded: unknown;
try {
decoded = JSON.parse(
gunzipSync(Buffer.from(value.data, 'base64'), { maxOutputLength: MAX_BYTES }).toString('utf8')
);
} catch {
// DB 원문이나 압축 payload가 오류 응답/로그에 포함되지 않게 한다.
throw new Error('Corrupt audit decision storage');
}
if (!Array.isArray(decoded) || decoded.length > MAX_STEPS) throw new Error('Invalid audit decision steps');
return decoded;
};
@@ -195,8 +195,9 @@ export const pruneDeliveredReadModelOutbox = async (
SELECT "id"
FROM "read_model_outbox"
WHERE "delivered_at" < ${input.deliveredBefore}
ORDER BY "id"
ORDER BY "delivered_at", "available_at", "id"
LIMIT ${limit}
FOR UPDATE SKIP LOCKED
)
RETURNING "id"
`);