o2o-negosium-original/postgres-init/dbeaver/3_lps_dbeaver.sql

87 lines
6.1 KiB
SQL

ALTER DATABASE lps_db SET timezone TO 'UTC';
SET timezone TO 'UTC';
CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- gen_random_uuid()
-- 작업 큐(PostgreSQL 을 큐로: 원자적 CAS claim + lease 소유권 + dead-letter)
CREATE TABLE IF NOT EXISTS job (
job_id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
job_type SMALLINT NOT NULL, -- JobType
status SMALLINT NOT NULL DEFAULT 1, -- JobStatus (1=PENDING)
priority SMALLINT NOT NULL DEFAULT 100, -- 낮을수록 우선
payload JSONB NOT NULL DEFAULT '{}'::jsonb, -- 잡 입력
result JSONB NULL, -- 잡 출력(완료 시)
dedupe_key VARCHAR(200) NULL, -- 활성 중복 방지 키(부분 유니크)
attempts SMALLINT NOT NULL DEFAULT 0, -- 시도 횟수(claim 시 +1)
max_attempts SMALLINT NOT NULL DEFAULT 3,
run_after TIMESTAMPTZ NOT NULL DEFAULT now(), -- 이 시각 이후에만 claim(백오프)
lease_until TIMESTAMPTZ NULL, -- 소유권 임대 만료(reaper 회수 기준)
worker_id VARCHAR(80) NULL, -- 현재 점유 워커
run_started_at TIMESTAMPTZ NULL, -- RUNNING 진입 시각
last_error TEXT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now() -- onupdate 는 앱(ORM)에서 갱신
);
CREATE INDEX IF NOT EXISTS ix_job_claim ON job (status, run_after, priority, created_at);
CREATE INDEX IF NOT EXISTS ix_job_lease ON job (status, lease_until);
-- 활성 중복 방지: 같은 dedupe_key 는 PENDING/RUNNING(1,2) 중 하나만 존재 가능
CREATE UNIQUE INDEX IF NOT EXISTS uq_job_dedupe_active
ON job (dedupe_key) WHERE status IN (1, 2) AND dedupe_key IS NOT NULL;
-- 네거티브 캐시 — '검색해도 없더라'를 TTL 동안 기억(재검색 낭비 방지)
CREATE TABLE IF NOT EXISTS search_negative (
key VARCHAR(300) PRIMARY KEY, -- 보통 product_code(없으면 query)
until TIMESTAMPTZ NOT NULL, -- 이 시각까지 not_found 로 간주
reason VARCHAR(200) NULL, -- 종료 사유 메모(관측)
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 상품별 최저가 스냅샷(트리거 기반) — 시계열 그래프용
CREATE TABLE IF NOT EXISTS price_history (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
product_code VARCHAR(100) NOT NULL, -- 상품 식별(조회 키)
job_id uuid NULL, -- 검색 잡 연결(추적)
triggered_at TIMESTAMPTZ NOT NULL DEFAULT now(), -- X축(검색 실행 시각)
outcome VARCHAR(20) NOT NULL, -- found / not_found
matched_count INTEGER NULL, -- AI 매칭 건수
naver_lowest INTEGER NULL,
naver_name VARCHAR(300) NULL,
naver_url TEXT NULL,
coupang_lowest INTEGER NULL,
coupang_name VARCHAR(300) NULL,
coupang_url TEXT NULL,
final_lowest INTEGER NULL, -- 전체 최저가(Y축 핵심)
final_source VARCHAR(20) NULL, -- 최종 최저가 소스
by_mall JSONB NULL, -- 몰별 최저가 스냅샷(열린 스키마)
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS ix_price_history_product ON price_history (product_code, triggered_at);
-- IP(프록시 포트) 세션 종료 이력 — 요청 예산(LPS_IP_REQUEST_BUDGET) 상한 튜닝의 원천 데이터
CREATE TABLE IF NOT EXISTS ip_session (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
source VARCHAR(20) NOT NULL, -- coupang 등
proxy_port INTEGER NULL, -- 사용 포트(=IP 세션), 프록시 미사용이면 NULL
requests INTEGER NOT NULL, -- 이 IP 로 보낸 요청 수
ok_count INTEGER NOT NULL DEFAULT 0, -- 성공 검색 수
blocked_count INTEGER NOT NULL DEFAULT 0, -- 차단 감지 수
elapsed_sec INTEGER NULL, -- 세션 지속 시간(초)
end_reason VARCHAR(20) NOT NULL, -- budget/block/proxy_error/window/idle/shutdown/rotate
created_at TIMESTAMPTZ NOT NULL DEFAULT now() -- 세션 종료 시각
);
CREATE INDEX IF NOT EXISTS ix_ip_session_source ON ip_session (source, created_at);
-- 봇 감지 이력 — IP·요청순번·차단방식 축적(패턴 분석)
CREATE TABLE IF NOT EXISTS bot_detection (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
source VARCHAR(20) NOT NULL, -- coupang 등
query VARCHAR(300) NULL, -- 감지 당시 검색어
ip_request_no INTEGER NULL, -- 현재 IP로 몇 번째 요청이었나
proxy_port INTEGER NULL, -- 사용 중이던 프록시 포트(=IP 세션)
elapsed_sec INTEGER NULL, -- 브라우저 실행 후 경과(초)
marker VARCHAR(120) NULL, -- 감지 근거(차단 페이지 마커)
headless BOOLEAN NULL,
html_len INTEGER NULL, -- 응답 길이(차단 페이지는 작음)
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);