o2o-negosium-original/lps/migrations/2026-08-05-proxy_port.sql
민헌 3b345929d4 feat(lps): 포트(IP) 장부를 DB 로 올려 멀티 프로세스에서 안전하게 — 프로필 슬롯도 분리
한 DECODO 계정을 여러 워커 프로세스가 나눠 쓰는 전제로 전환한다. 인메모리 장부는
프로세스마다 따로라 (1) 같은 IP 를 동시에 잡고 (2) 한쪽이 태운 IP 를 다른 쪽이 곧바로
집으며 (3) 재시작하면 쿨다운이 통째로 사라졌다.

proxy_port 테이블 = 단일 진실. 상태는 세 시각으로만 표현한다(leased/rest/cooldown_until).
- acquire: 한 UPDATE 안에서 FOR UPDATE SKIP LOCKED 로 후보를 잠그고 임대까지 끝낸다
  (잡 큐와 같은 방식 — SELECT 후 UPDATE 로 나누면 그 틈에 다른 프로세스가 같은 행을 집는다)
- 회전은 LRU(last_used_at). 프로세스가 몇 개든 '가장 오래 안 쓴 IP'를 집으므로 전체가
  자연히 한 바퀴씩 돈다 → 프로세스별 seed_offset 계산 제거
- 죽은 프로세스 회수: leased_until 만료로 자동 복귀(별도 reaper 불필요)
- 차단·휴식은 전역이라 재시작해도 유지된다

DB 왕복은 비동기라 검색 루프(동기)에서 곧바로 못 한다 → 회전·차단을 pending 에 적어두고
ensure_port(브라우저 재기동 직전, async)에서 한 번에 flush. _close_ctx 에서도 flush 해
종료 시 유실(=태운 IP 를 남이 그대로 집는 상황)을 막는다.

**프로필 슬롯**(services/search/profile_slot): Chrome 은 user_data_dir 당 1 인스턴스다.
예전엔 워커 인덱스로만 갈라서 프로세스 2개면 같은 경로를 잡아 두 번째가 통째로 죽었다
(실측: 잡 3건 중 2건 DEAD, TargetClosedError). 파일 락으로 슬롯을 선점한다 — PID 경로가
아니라 슬롯이라 재시작 시 재사용돼 웜 쿠키(cf_clearance·Akamai)를 버리지 않는다.

검증: 프로세스 2개 동시 acquire 20회 → 중복 배정 0건. 워커 2프로세스 e2e → 잡 3건 모두
DONE(네이버가 삼다수 최저가 획득 8,960 < 13,200). 테스트 14건 추가, 전체 217 passed.
2026-08-05 11:44:54 +09:00

19 lines
1.3 KiB
SQL

-- 프록시 포트(=IP 세션) 임대 장부 — 프로세스 간 공유 상태.
-- 한 DECODO 계정을 여러 워커 프로세스가 나눠 쓰므로 임대·휴식·쿨다운을 DB 에 둔다.
-- (인메모리면 서로의 차단을 몰라 같은 IP 를 동시에 잡거나 태운 IP 를 곧바로 재사용한다)
CREATE TABLE IF NOT EXISTS proxy_port (
host varchar(80) NOT NULL, -- 게이트웨이(gate/kr — 같은 번호라도 IP 가 다름)
port integer NOT NULL,
owner varchar(80), -- 현재 임대자(소스-PID-워커)
leased_until timestamptz, -- 임대 만료(=sticky 수명). 프로세스가 죽어도 자동 회수
rest_until timestamptz, -- 휴식 만료(선제 회전 — 탄 게 아님)
cooldown_until timestamptz, -- 쿨다운 만료(차단)
last_used_at timestamptz, -- 마지막 임대 시각(LRU 회전 기준)
last_reason varchar(40),
use_count integer NOT NULL DEFAULT 0,
burn_count integer NOT NULL DEFAULT 0, -- 누적 차단(불량 IP 슬롯 식별)
updated_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (host, port)
);
CREATE INDEX IF NOT EXISTS ix_proxy_port_pick ON proxy_port (host, last_used_at);