Skip to content
Byeongmin Bae edited this page Jul 19, 2026 · 2 revisions

ERD

erDiagram
		User ||--|| UserSubscription : ""
		SubscriptionPlan ||--o{ SubscriptionPlanBenefit : ""
		SubscriptionPlan ||--o{ UserSubscription : ""
		SubscriptionBenefit ||--o{ SubscriptionPlanBenefit : ""
		UserSubscription ||--o{ PaymentHistory : ""
    User ||--o{ Watch : ""
    User ||--|{ UserOAuth : ""
		UserOAuth ||--o| NaverOAuth : ""
    Watch ||--|{ WatchCondition : ""
    Watch ||--o{ WatchSnapshot : ""
    PaymentHistory ||--|| PlanPaymentHistory : ""
    PaymentHistory ||--|| TossPaymentHistory : ""
    TossPaymentHistory ||--o{ TossPaymentCancelHistory : ""
    
    Base {
        Long id PK
        %% ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒ์„ฑ๋œ ๋‚ ์งœ์™€ ์‹œ๊ฐ„. ๋ชจ๋“  ๋‚ ์งœ๋Š” DB์— UTC๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ €์žฅ
        LocalDateTime createdAt
        %% ์—”ํ‹ฐํ‹ฐ์˜ ํ•„๋“œ์ค‘ ํ•˜๋‚˜ ์ด์ƒ์ด ์—…๋ฐ์ดํŠธ๋œ ๋‚ ์งœ์™€ ์‹œ๊ฐ
        LocalDateTime updatedAt 
        %% ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋…ผ๋ฆฌ์‚ญ์ œ๋œ ๋‚ ์งœ์™€ ์‹œ๊ฐ„
        LocalDateTime deletedAt
    }
    LogBase {
        Long id PK
        %% ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์ƒ์„ฑ๋œ ๋‚ ์งœ์™€ ์‹œ๊ฐ„. ๋ชจ๋“  ๋‚ ์งœ๋Š” DB์— UTC๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์ €์žฅ
        LocalDateTime createdAt 
    }
    SubscriptionPlan {
		    %% ํ”Œ๋žœ ์ด๋ฆ„
		    String name
		    %% ํ”Œ๋žœ ๊ฐ€๊ฒฉ
		    BigDecimal price
		    %% ํ”Œ๋žœ ์ œ๊ณต ์ผ์ˆ˜
		    Int durationDays
		    %% ํ”Œ๋žœ ์ด์šฉ์ค‘์ด๋ฉด์„œ ์ƒ์œ„ ํ”Œ๋žœ์„ ์ถ”๊ฐ€๊ฒฐ์ œ ํ•˜๋Š”๊ฒฝ์šฐ ์—…๊ทธ๋ ˆ์ด๋“œ๊ฐ€ ๋จ
		    %% ํ”Œ๋žœ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œ๋Š” ๋ถˆ๊ฐ€๋Šฅํ•˜์ง€๋งŒ ํ™˜๋ถˆ์€ ๋‚จ์€ ์ผ์ˆ˜๋งŒํผ ๊ฐ€๋Šฅ
		    Int priority
		    %% ์ด๋ฒคํŠธ์„ฑ ํ”Œ๋žœ์ด๋ผ๋ฉด ๊ธฐ๋ก
		    LocalDateTime availableUntil
		}
		SubscriptionPlanBenefit {
				SubscriptionPlan subscriptionPlan FK
				SubscriptionBenefit subscriptionBenefit FK
				%% 20๊ฐœ
		    Int value
		}
    SubscriptionBenefit {
		    %% WATCH_AMOUNT
		    Enum benefitType
    }
    %% ์œ ์ €๊ฐ€ ํ˜„์žฌ ์ด์šฉ์ค‘์ธ ํ”Œ๋žœ. ํ˜„์žฌ ๊ธฐ๋ณธ์€ free ํ”Œ๋žœ
    UserSubscription{
		    User user FK
		    SubscriptionPlan plan FK
		    %% PaymentHistory ๋”ฐ๋ผ ๊ฒฐ์ •๋จ
		    %% ๋งŒ์•ฝ ๋™์ผ ํ”Œ๋žœ์œผ๋กœ ์žฌ๊ฒฐ์ œํ•œ ๊ฒฝ์šฐ -> ๋‚ ์งœ๋งŒ ๋Š˜๋ฆฌ๋ฉด ๋จ
		    %% ์—…๊ทธ๋ ˆ์ด๋“œ์ธ ๊ฒฝ์šฐ -> ๊ธฐ์กด ํ”Œ๋žœ ๋‚จ์€์ผ์ˆ˜ ๊ณ„์‚ฐํ•ด์„œ ์ฐจ์•ก์€ ํ• ์ธ. 
		    LocalDateTime expiredAt
		    %% ์ƒˆ ํ”Œ๋žœ ๊ฒฐ์ œ์ผ๋กœ startedAt ๊ฐฑ์‹ 
		    LocalDateTime startedAt
    }
    PaymentHistory {
        User user FK
		    %% ํ• ์ธ๋ฐ›์€ ๊ธˆ์•ก
		    BigDecimal discountPrice
        %% ๊ฒฐ์ œ API ์ œ๊ณต PG์‚ฌ
		    Enum pgProvider
		    %% ๊ฒฐ์ œ ์Šน์ธ ๋‚ ์งœ ์‹œ๊ฐ„
				LocalDateTime approvedAt
				%% ํ™˜๋ถˆ์‹œ ๊ธฐ๋ก
				LocalDateTime refundedAt
    }
    PlanPaymentHistory{
        PaymentHistory paymentHistory FK
    		%% ํ”Œ๋žœ ์ด๋ฆ„
		    String name
		    %% ํ”Œ๋žœ ๊ฐ€๊ฒฉ
		    BigDecimal price
		    %% ํ”Œ๋žœ ๊ฐœ์›”
		    Int month
		    %% ํ”Œ๋žœ ์šฐ์„ ์ˆœ์œ„
		    Enum tier
    }
    TossPaymentHistory {
	      PaymentHistory paymentHistory FK
        %% ์—ฌ๊ธฐ์— api ๋ฌธ์„œ์— ์žˆ๋Š” ํ•„๋“œ ๋”๋“ค์–ด๊ฐ€์•ผํ•จ
		    %% ๊ฒฐ์ œ์ทจ์†Œ์— ํ•„์š”
        String paymentKey
        String orderId
        %% ๊ฒฐ์ œ ๊ธˆ์•ก
        Int balanceAmount
        %% DONE, CANCELED ๋“ฑ
        Enum status
        String rawResponse   
    }
    %% ์ทจ์†Œ๋Š” ์—ฌ๋Ÿฌ๋ฒˆ ๋ฐœ์ƒ ๊ฐ€๋Šฅ(๋ถ€๋ถ„์ทจ์†Œ)
    TossPaymentCancelHistory {
	      TossPaymentHistory tossPaymentHistory FK
		    %% ๊ฒฐ์ œ๋ฅผ ์ทจ์†Œํ•œ ๊ธˆ์•ก์ž…๋‹ˆ๋‹ค.
		    Int cancelAmount
		    %% ๊ฒฐ์ œ๋ฅผ ์ทจ์†Œํ•œ ์ด์œ ์ž…๋‹ˆ๋‹ค. ์ตœ๋Œ€ ๊ธธ์ด๋Š” 200์ž์ž…๋‹ˆ๋‹ค.
		    String cancelReason
		    %% ๊ฒฐ์ œ ์ทจ์†Œ๊ฐ€ ์ผ์–ด๋‚œ ๋‚ ์งœ์™€ ์‹œ๊ฐ„ ์ •๋ณด์ž…๋‹ˆ๋‹ค. 
		    LocalDateTime canceledAt
		    %% ์ทจ์†Œ ๊ฑด์˜ ํ‚ค๊ฐ’์ž…๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ ๊ฑด์˜ ์ทจ์†Œ ๊ฑฐ๋ž˜๋ฅผ ๊ตฌ๋ถ„ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ์ตœ๋Œ€ ๊ธธ์ด๋Š” 64์ž์ž…๋‹ˆ๋‹ค.
		    String transactionKey
		}
    User {
		    %% USER, ADMIN
		    Enum role
        %% accessToken ๋งŒ๋ฃŒ์‹œ ๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ์œผ๋กœ ์žฌ๋ฐœ๊ธ‰ ๊ฐ€๋Šฅ
        String refreshToken
        LocalDateTime lastLoginAt
        %% ์•„๋ž˜๋Š” ๋กœ๊ทธ์ธ API ์—์„œ ๋„˜์–ด์˜จ ์š”์†Œ๋“ค
        String nickname
        String email
        String profileImageUrl
    }
    UserOAuth {
		    User user FK
		    %% NAVER, GOOGLE
		    Enum provider
		    String providerId
		    LocalDateTime connectedAt
		}
		%% ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ์—์„œ ๋ฐœ๊ธ‰๋˜๋Š” ๋„ค์ด๋ฒ„ ์„œ๋น„์Šค ์ „์šฉ ํ† ํฐ
		NaverOAuth {
		    UserOAuth userOAuth FK
		    String accessToken
		    String refreshToken
		    LocalDateTime expiredAt
		}
    Watch {
		    User user FK
		    %% ์œ ์ €๊ฐ€ ๋“ฑ๋กํ•œ ์ด๋ฆ„
        String name
        %% ์œ ์ €๊ฐ€ ๋“ฑ๋กํ•œ url
        String url
        %% ์ฒซ ์š”์ฒญํ›„ ๊ฐ€์ ธ์˜จ ํŒŒ๋น„์ฝ˜. ์ผ์ • ์‹œ๊ฐ„๋งˆ๋‹ค ๊ฐฑ์‹  ํ•„์š”ํ•จ
        String faviconUrl
        %% RUNNING, PAUSED(์‚ฌ์šฉ์ž๊ฐ€ ์ผ์‹œ์ •์ง€), PAYMENT_REQUIRED(๊ฒฐ์ œ ํ•„์š”), ILLEGAL_SUSPENDED(๋ถˆ๋ฒ•์ ์ธ ๋‚ด์šฉ)
        Enum status
        %% ํฌ๋กค๋ง ์ฃผ๊ธฐ
        Int intervalSecond
        Boolean isIncludedInStat
        %% ๋งˆ์ง€๋ง‰ ํฌ๋กค๋ง ์ˆ˜ํ–‰ ์‹œ๊ฐ„
        LocalDateTime lastFetchedAt
        %% ๋งˆ์ง€๋ง‰ ์•Œ๋ฆผ ์ˆ˜ํ–‰ ์‹œ๊ฐ„
        LocalDateTime lastNotifiedAt
    }
    %% ํƒ€์ž… ๋Š˜์–ด๋‚˜์„œ nullable ํ•„๋“œ๊ฐ€ ์ƒ๊ธด๋‹ค๋ฉด ์Šˆํผ/์„œ๋ธŒํƒ€์ž… ๊ณ ๋ ค
    WatchCondition {
		    Watch watch FK
		    %% HTML_FULL, KEYWORD
		    Enum type
		    String keyword
    }
    %% fetch fail ์‹œ ์ƒ์„ฑ๋˜์ง€ ์•Š์Œ
    WatchSnapshot {
		    Watch watch FK
		    %% ํ˜„์žฌ ํฌ๋กค๋งํ•ด์„œ ๊ฐ€์ ธ์˜จ html ์›๋ณธ
		    String htmlContentUrl
		    %% ์ด์ „ html ํ•˜๊ณ  ํ˜„์žฌ html ํ•˜๊ณ  diff ๊ฒฐ๊ณผ
		    String htmlContentDiffUrl
		    %% ํ˜„์žฌ ํฌ๋กค๋งํ•œ ์‚ฌ์ดํŠธ ์บก์ณ๋ณธ
		    String screenshotUrl
		    %% ์ด์ „ ์‚ฌ์ดํŠธ ์บก์ณ๋ณธ๊ณผ ๋น„๊ตํ•ด ๊ฒฐ๊ณผ์ ์œผ๋กœ ๋‹ค๋ฅธ ๋ถ€๋ถ„์ด ๊ฐ•์กฐ๋œ ์บก์ณ๋ณธ
		    String screenshotDiffUrl
		    %% htmlContentDiff ๊ธฐ๋ฐ˜์œผ๋กœ ์ƒ์„ฑ๋œ AI ์š”์•ฝ
		    String aiSummary
		    %% ์•Œ๋ฆผ ์ „์†ก ์‹œ๊ฐ„
		    LocalDateTime notifiedAt
    }
    HotUrlStat {
        %% ์กฐํšŒ๋œ ํšŸ์ˆ˜
		    Int hit
		    %% ๋ฉ”์ธ ์‚ฌ์ดํŠธ ํ†ต๊ณ„์ชฝ์— ๋ณด์ผ url(a.com)
		    String url
		    %% ์ผ ๋‹จ์œ„ ํ†ต๊ณ„
		    LocalDate statDate
    }
    Crawlinglog {
        %% ์›Œ์น˜๋ฆฌ์ŠคํŠธ์— ์‚ฌ์šฉ์ž๊ฐ€ ๋“ฑ๋กํ•œ ์ด๋ฆ„
		    String name
		    %% ์š”์ฒญ์ด ์ˆ˜ํ–‰๋œ url
		    String requestedUrl
		    %% ํฌ๋กค๋ง ์ฃผ๊ธฐ
        Int intervalSecond
        %% ํฌ๋กค๋ง ์ž‘์—… ์‹œ์ž‘ ์‹œ๊ฐ„
        LocalDateTime startedAt
        %% ํฌ๋กค๋ง ์ž‘์—…์ด ์„ฑ๊ณตํ•œ ์‹œ๊ฐ„. ์‹คํŒจ์‹œ null
        LocalDateTime succeededAt
        %% ํฌ๋กค๋ง ์ž‘์—…์ด ๋๋‚œ ์‹œ๊ฐ„
        LocalDateTime endedAt
        %% http ์‘๋‹ต ์ฝ”๋“œ
        Int httpResponseCode
        %% ์‹คํŒจ์‹œ ์ฑ„์›Œ์ง€๋Š” ์••์ถ•๋œ html ์›๋ณธ ์ปจํ…์ธ 
        String compressedHtml
    }   
Loading

REST API ๋ช…์„ธ

Auth

Method URL ์„ค๋ช…
GET /auth/naver/url ๋„ค์ด๋ฒ„ OAuth URL ์ƒ์„ฑ ๋ฐ ๋ฐ˜ํ™˜
GET /auth/naver/callback ๋„ค์ด๋ฒ„ OAuth ์ฝœ๋ฐฑ ์ฒ˜๋ฆฌ ๋ฐ Watchtower JWT ๋ฐœ๊ธ‰
DELETE /auth/naver/revoke ๋„ค์ด๋ฒ„ OAuth Token Revocation
POST /auth/renew Watchtower Token ์žฌ๋ฐœ๊ธ‰
DELETE /auth/logout Watchtower Refresh Token ์‚ญ์ œ

Users

Method URL ์„ค๋ช…
GET /users/{userId} ํ”„๋กœํ•„ ์กฐํšŒ
GET /users/{userId}/payments ๊ฒฐ์ œ ์ด๋ ฅ ์กฐํšŒ
GET /users/{userId}/watches ์™€์น˜ ๋ชฉ๋ก ์กฐํšŒ
PATCH /users/{userId} ํ”„๋กœํ•„ ์ •๋ณด ์ˆ˜์ •
DELETE /users/{userId} ํšŒ์› ํƒˆํ‡ด (๋…ผ๋ฆฌ์‚ญ์ œ + ๋„ค์ด๋ฒ„ revoke)
GET /users/{userId}/subscriptions ํ˜„์žฌ ๊ตฌ๋…์ค‘์ธ ํ”Œ๋žœ ์กฐํšŒ
DELETE /users/{userId}/subscriptions ๊ตฌ๋… ํ•ด์ง€ ์š”์ฒญ(ํ™˜๋ถˆ)

Payments

Method URL ์„ค๋ช…
GET /payments/{paymentId} ๊ฒฐ์ œ ๋‚ด์—ญ ๋‹จ๊ฑด ์ƒ์„ธ ์กฐํšŒ
POST /payments/toss/confirm ํ† ์Šค ๊ฒฐ์ œ ์Šน์ธ

Watches

Method URL ์„ค๋ช…
POST /watches ์™€์น˜ ๋“ฑ๋ก
GET /watches/trending ์‹ค์‹œ๊ฐ„ ์ธ๊ธฐ URL ์กฐํšŒ
GET /watches/{watchId} ์™€์น˜ ๋‹จ๊ฑด ์ƒ์„ธ ์กฐํšŒ
PATCH /watches/{watchId} ์™€์น˜ ์ˆ˜์ •(์ผ์‹œ์ •์ง€/์žฌ๊ฐœ ํฌํ•จ)
DELETE /watches/{watchId} ์™€์น˜ ์‚ญ์ œ
POST /watches/{watchId}/notify ํ…Œ์ŠคํŠธ ์•Œ๋ฆผ ๋ฐœ์†ก
GET /watches/{watchId}/conditions ์™€์น˜ ์กฐ๊ฑด ๋ชฉ๋ก ์กฐํšŒ
POST /watches/{watchId}/conditions ์™€์น˜ ์กฐ๊ฑด ์ถ”๊ฐ€
PATCH /watches/{watchId}/conditions/{conditionId} ์™€์น˜ ์กฐ๊ฑด ์ˆ˜์ •
DELETE /watches/{watchId}/conditions/{conditionId} ์™€์น˜ ์กฐ๊ฑด ์‚ญ์ œ
GET /watches/{watchId}/snapshots ์Šค๋ƒ…์ƒท ํƒ€์ž„๋ผ์ธ ์กฐํšŒ
GET /watches/{watchId}/snapshots/{snapshotId} ์Šค๋ƒ…์ƒท ๋‹จ๊ฑด ์ƒ์„ธ ์กฐํšŒ

Admin

Method URL ์„ค๋ช…
GET /admin/stats/watches ๋Œ€์‹œ๋ณด๋“œ ์™€์น˜ ํ†ต๊ณ„ ์กฐํšŒ
GET /admin/stats/payments ๋Œ€์‹œ๋ณด๋“œ ๊ฒฐ์ œ ํ†ต๊ณ„ ์กฐํšŒ
GET /admin/stats/users ๋Œ€์‹œ๋ณด๋“œ ์œ ์ €์ˆ˜/ํšŒ์›ํƒˆํ‡ด์ˆ˜ ๋“ฑ ํ†ต๊ณ„ ์กฐํšŒ
GET /admin/stats/auth ๋Œ€์‹œ๋ณด๋“œ ๋กœ๊ทธ์ธ/ํšŒ์›๊ฐ€์ž… ํ†ต๊ณ„ ์กฐํšŒ
PATCH /admin/watches/{watchId} ์™€์น˜ ์ƒํƒœ ๋ณ€๊ฒฝ(์ •์ฑ… ์œ„๋ฐ˜ ์ •์ง€ ํฌํ•จ)
POST /admin/payments/{paymentId}/cancel ๊ฒฐ์ œ ์ง๊ถŒ ์ทจ์†Œ
GET /admin/payments ๊ฒฐ์ œ ๋‚ด์—ญ ์กฐํšŒ/๊ฒ€์ƒ‰
GET /admin/users ์ „์ฒด ์œ ์ € ์กฐํšŒ/๊ฒ€์ƒ‰
PATCH /admin/users/{userId}/roles ์œ ์ € ๊ถŒํ•œ ์ˆ˜์ •
PATCH /admin/users/{userId}/status ban/unban ๋“ฑ ์œ ์ € ์ƒํƒœ ๋ณ€๊ฒฝ

๊ธฐ๋Šฅ์  ์š”๊ตฌ์‚ฌํ•ญ

  1. ํšŒ์›

    • ํ˜„์žฌ๋กœ์„  ๋„ค์ด๋ฒ„ ๋กœ๊ทธ์ธ๋งŒ ์ง€์›ํ•˜๊ณ  ์ž์ฒด ํšŒ์›๊ฐ€์ž…์€ ์ง€์›ํ•˜์ง€ ์•Š์Œ
    • ์œ ๋ฃŒ ์ •์ฑ…
      • ํ˜„์žฌ๋Š” Free(์™€์น˜๋ฆฌ์ŠคํŠธ ์ตœ๋Œ€ 10๊ฐœ) ํ”Œ๋žœ๊ณผ Basic(ํ•œ๋‹ฌ์— 1000์› ์™€์น˜๋ฆฌ์ŠคํŠธ ์ตœ๋Œ€ 20๊ฐœ) ํ”Œ๋žœ๋งŒ ์กด์žฌ
      • ๊ธฐ์กด ํ”Œ๋žœ ์ด์šฉ์ž๊ฐ€ ๋™์‹œ์— ์ƒˆ๋กœ์šด ํ”Œ๋žœ์„ ์ถ”๊ฐ€๋กœ ๊ฒฐ์ œํ•˜๋ ค๋Š” ๊ฒฝ์šฐ ๊ธฐ์กด ํ”Œ๋žœ ๊ฐ€๊ฒฉ์„ ์‚ฌ์šฉํ•œ ์ผ์ˆ˜๋งŒํผ ์ฐจ๊ฐํ•œ ํ›„ ํ• ์ธ์ด ์ ์šฉ๋œ ํ”Œ๋žœ ๊ตฌ๋งค ๊ฐ€๋Šฅ
      • ํ”Œ๋žœ ๊ธฐ๊ฐ„์ด ๋๋‚˜ free ํ”Œ๋žœ์œผ๋กœ ๋ณ€๊ฒฝ๋ ๊ฒฝ์šฐ 1์ฃผ์ผ์•ˆ์— ์žฌ๊ฒฐ์ œ๋ฅผ ํ•˜์ง€ ์•Š์„ ์‹œ free ํ”Œ๋žœ์˜ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚˜๋Š” ์ตœ๊ทผ์— ์ถ”๊ฐ€๋œ ์™€์น˜๋ฆฌ์ŠคํŠธ๋“ค์ด ์ •์ง€๋˜๊ณ  ๊ฒฐ์ œ ์ „๊นŒ์ง€ ์‚ฌ์šฉ์ž ์ž„์˜๋กœ ํ•ด์ œ ๋ถˆ๊ฐ€
      • ํ™˜๋ถˆ ์š”์ฒญ์‹œ ์‚ฌ์šฉ์ผ์ˆ˜๋ฅผ ์ฐจ๊ฐํ•˜์—ฌ ํ™˜๋ถˆ
  2. ์™€์น˜๋ฆฌ์ŠคํŠธ(๋ชจ๋‹ˆํ„ฐ๋ง URL) ๋“ฑ๋ก

    • 1๋…„๋™์•ˆ ์‚ฌ์ดํŠธ์— ๋กœ๊ทธ์ธ์„ ํ•˜์ง€ ์•Š์œผ๋ฉด ๋“ฑ๋ก๋œ ๋ฆฌ์ŠคํŠธ ์ „๋ถ€ ์ผ์‹œ์ค‘์ง€๋จ
    • ์ ‘๊ทผ์ด ๋ถˆ๊ฐ€๋Šฅํ•œ ์ฃผ์†Œ์— ๋Œ€ํ•ด์„  ๋“ฑ๋ก ๋ถˆ๊ฐ€
      • ๋งŒ์•ฝ ๋“ฑ๋ก ์ดํ›„ ์ ‘๊ทผ ๋ถˆ๊ฐ€์‹œ ์ผ์‹œ์ •์ง€๋กœ ๋ณ€๊ฒฝ, ์‚ฌ์šฉ์ž๊ฐ€ ์žฌ๊ฐœ ๋ฒ„ํŠผ์„ ๋ˆŒ๋Ÿฌ ์ ‘๊ทผ์ด ๋˜๋Š”๊ฒƒ์ด ํ™•์ธ๋œ ํ›„์— ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ
    • ๋กœ๊ทธ์ธ ํ•ด์•ผ๋งŒ ๋ณด์ด๋Š” ํŽ˜์ด์ง€๋Š” ์ถ”ํ›„ ์ฟ ํ‚ค ์ œ๊ณต์— ๋™์˜ํ•œ ๊ณ ๊ฐ๋“ค์— ํ•œํ•ด์„œ ์ด์šฉ๊ฐ€๋Šฅ
    • ์™€์น˜๋ฆฌ์ŠคํŠธ ์™ผ์ชฝ์—” ๋‹ค์Œ ํ•ญ๋ชฉ์ด ํ‘œ์‹œ๋œ๋‹ค(๋ฌดํ•œ ์Šคํฌ๋กค)
      • URL
      • ํŒŒ๋น„์ฝ˜
      • ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ์‚ฌ์ดํŠธ ์ด๋ฆ„
      • ์ผ์‹œ์ •์ง€ ์—ฌ๋ถ€(ํšŒ์ƒ‰๊ณผ ์ดˆ๋ก์ƒ‰ ์  ์•„์ด์ฝ˜์œผ๋กœ)
      • ์ตœ๊ทผ AI ์š”์•ฝ
    • ์™€์น˜๋ฆฌ์ŠคํŠธ ์˜ค๋ฅธ์ชฝ์—” ์ฒ˜์Œ ๋“ค์–ด๊ฐ„ ๊ฒฝ์šฐ ๋งจ ์ฒซ๋ฒˆ์งธ ์•„์ดํ…œ์ด ๊ธฐ๋ณธ ์„ ํƒ๋จ
    • ๋งŒ์•ฝ ํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋“ฑ๋กํ•œ ์™€์น˜๋ฆฌ์ŠคํŠธ๊ฐ€ ์—†๋‹ค๋ฉด ๋“ฑ๋ก ์š”๊ตฌ๋ฅผ ํ•ด์•ผํ•จ
    • ์™€์น˜๋ฆฌ์ŠคํŠธ์˜ ์˜ค๋ฅธ์ชฝ์—๋Š” ๋‹ค์Œ ํ•ญ๋ชฉ์ด ํ‘œ์‹œ๋œ๋‹ค.
      • ์œ„์— ํ‘œ์‹œ๋˜์—ˆ๋˜ ์ •๋ณด์™€ ํ•จ๊ป˜
      • ์ตœ๊ทผ ๊ฐฑ์‹ ๋œ ์‹œ๊ฐ
      • ์ผ์‹œ์ •์ง€/์žฌ๊ฐœ ๋ฒ„ํŠผ(์žฌ๊ฐœ์‹œ URL ์ ‘๊ทผ ๊ฐ€๋Šฅ์—ฌ๋ถ€ ์ฒดํฌํ›„ ์ผ์‹œ์ •์ง€ ํ’€์–ด์•ผํ•จ)
      • ํŽ˜์ด์ง€ ์ „์ฒด์˜ ํƒ€์ž„๋ผ์ธ๊ณผ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋งŒ์„ ์‹œ๊ฐํ™”ํ•˜๋Š” ํƒ€์ž„๋ผ์ธ์œผ๋กœ ์œ ์ €๊ฐ€ ์–ด๋–ค ๋ถ€๋ถ„์ด ๋ณ€ํ–ˆ๋Š”์ง€ ์•Œ์ˆ˜์žˆ๋„๋กํ•จ
      • ๊ฐ ํƒ€์ž„๋ผ์ธ์—์„œ ์Šค๋ƒ…์ƒท๋“ค์€ ์ตœ๋Œ€ ์ผ์ฃผ์ผ๊ฐ„ ๋ณด๊ด€ ๋ฐ ์กฐํšŒ๊ฐ€๋Šฅ(S3 ์š”๊ธˆ์„ ๊ณ ๋ คํ•ด์„œ ์ถ”ํ›„์— ๋Š˜๋ฆฌ๋Š”๊ฒƒ ๊ณ ๋ ค)
    • ์ตœ์†Œ ํ™•์ธ ์ฃผ๊ธฐ๋Š” 5๋ถ„๋‹จ์œ„๋กœ 5๋ถ„๋ถ€ํ„ฐ ์ตœ๋Œ€ 24์‹œ๊ฐ„๊นŒ์ง€ ์„ค์ • ๊ฐ€๋Šฅ.
      • ๋งŒ์•ฝ ๋™์ผ URL ์„ ์—ฌ๋Ÿฌ ์กฐ๊ฑด์œผ๋กœ ์•Œ๋žŒ์„ ๋ฐ›๊ณ  ์‹ถ๋‹ค๋ฉด ํ•˜๋‚˜์˜ ์™€์น˜๋ฆฌ์ŠคํŠธ์—์„œ ๋‹ค์ค‘ ์กฐ๊ฑด์„ ์„ค์ •ํ•˜๋ฉด๋จ.
      • ์—ฌ๋Ÿฌ ์กฐ๊ฑด์—๋Š” ํ˜„์žฌ๋กœ์„  html ์ „์ฒด ๊ฐ์ง€์™€ ํŠน์ • ํ‚ค์›Œ๋“œ ๋“ฑ์žฅ/์†Œ๋ฉธ ๊ฐ์ง€๊ฐ€ ์žˆ์Œ
      • ํ•˜๋‚˜์˜ ์™€์น˜๋ฆฌ์ŠคํŠธ์—๋Š” ๋‹ค์ค‘์กฐ๊ฑด์ด ์žˆ๋Š”๊ฒƒ๊ณผ๋Š” ๋ฌด๊ด€ํ•˜๊ฒŒ ํ•˜๋‚˜์˜ ํ™•์ธ์ฃผ๊ธฐ๋งŒ ์กด์žฌํ•จ(์˜ˆ: a.com/abc ์— ๋Œ€ํ•œ ์กฐ๊ฑด์ด 3๊ฐœ ์žˆ๋”๋ผ๋„ ๋ชจ๋‘ ํ™•์ธ ์ฃผ๊ธฐ๋Š” ๋™์ผํ•จ)
      • ์ค‘๋ณต์กฐ๊ฑด์€ ์ตœ๋Œ€ 3๊ฐœ๊นŒ์ง€ ๋“ฑ๋ก๊ฐ€๋Šฅ
  3. ํ˜„์žฌ Hot ํ•œ ์™€์น˜๋ฆฌ์ŠคํŠธ

    • ๋„ค์ด๋ฒ„ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ƒ‰์–ด์ฒ˜๋Ÿผ ๋ณ€ํ™”์ˆ˜๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ˜„์žฌ ํ•ซํ•œ URL ์„ ์ผ๋‹จ์œ„ ์ง‘๊ณ„ํ•˜์—ฌ 10๊ฐœ๊นŒ์ง€ ๋‚˜์—ด
    • URL ์„ ํด๋ฆญํ•ด์„œ ๋“ค์–ด๊ฐˆ์ˆ˜์žˆ์Œ
    • ๋…ธ์ถœ๋œ๋‹ค๋Š” ๊ฑฐ๋ถ€๊ฐ์ด ๋“ค์ง€ ์•Š๋„๋ก ๋„๋ฉ”์ธ๊นŒ์ง€๋งŒ(a.com) ํ‘œ๊ธฐ
    • ํ†ต๊ณ„ ์ง‘๊ณ„๋ฅผ ์›ํ•˜์ง€ ์•Š๋Š” ๊ณ ๊ฐ์€ ํ•ด๋‹น ์™€์น˜๋ฆฌ์ŠคํŠธ ์ƒ์„ฑ์‹œ์—๋งŒ ๋„๊ธฐ ๊ฐ€๋Šฅ
  4. ์™€์น˜๋ฆฌ์ŠคํŠธ ๋ณ€ํ™” ์•Œ๋ฆผ

    • ํ˜„์žฌ๋กœ์„  ๋””์Šค์ฝ”๋“œ์™€ ํ…”๋ ˆ๊ทธ๋žจ๋งŒ ์ง€์›(์นด์นด์˜คํ†ก, ๋ฉ”์ผ๊ฐ™์€ ๋น„์šฉ๋“œ๋Š”๊ฑด ์ดํ›„์— ๊ณ ๋ ค)
    • ์ตœ์ดˆ ํฌ๋กค๋ง์€ ์Šค๋ƒ…์ƒท๋งŒ ์ €์žฅํ•˜๊ณ  ์•Œ๋ฆผ์€ ์—†์Œ
    • ์™€์น˜๋ฆฌ์ŠคํŠธ์—๋Š” ์•Œ๋ฆผ ์ˆ˜์‹  ํ…Œ์ŠคํŠธ ๊ธฐ๋Šฅ์ด ์žˆ์–ด์•ผํ•จ Uptime Kuma ๊ฐ™์ด
    • ์•Œ๋ฆผ๋‚ด์šฉ์—” ๋‹ค์Œ์ด ํฌํ•จ๋˜์–ด์žˆ์–ด์•ผํ•จ
      • ์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ์‚ฌ์ดํŠธ ์ด๋ฆ„
      • URL
      • ๋ฐฉ๊ธˆ ๋ฐœ์ƒํ•œ ๋ณ€ํ™”์— ๋Œ€ํ•œ AI ์š”์•ฝ
      • ์ด์ „ ์Šค๋ƒ…์ƒท๊ณผ ๋‹ฌ๋ผ์ง„ ๋ถ€๋ถ„๋งŒ ๊ฐ•์กฐ

๋น„๊ธฐ๋Šฅ์  ์š”๊ตฌ์‚ฌํ•ญ

  1. ์ธ์ฆ
    • ์„œ๋ฒ„ ์ˆ˜ํ‰ํ™•์žฅ์‹œ redis ์‚ฌ์šฉ์„ ํ•˜์ง€ ์•Š๊ธฐ์œ„ํ•ด(๊ด€๋ฆฌํฌ์ธํŠธ/๋น„์šฉ ์ ˆ๊ฐ) JWT ์‚ฌ์šฉ
  2. ์™€์น˜๋ฆฌ์ŠคํŠธ ๋“ฑ๋ก
    • ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ์ฃผ์†Œ๋ฅผ ํŒ๋‹จํ•˜๋Š” ๊ธฐ์ค€
      • 400๋ฒˆ๋Œ€ ์—๋Ÿฌ
      • ๋นˆํŽ˜์ด์ง€
      • Cloudflare challenge(์ถ”ํ›„์— ์šฐํšŒ๋ฒ•์„ ์ฐพ์•„์•ผํ•จ)
      • ๋“ฑ๋“ฑ...(์ถ”ํ›„์— ์ถ”๊ฐ€)
    • js ๋ Œ๋”๋ง์„ ํ•ด์•ผ ๋‚˜ํƒ€๋‚˜๋Š” ์ปจํ…์ธ ๋„ ์žˆ์œผ๋‹ˆ Playwright ๊ฐ™์€๊ฑธ ์จ์•ผํ•จ
      • ์Šคํ”„๋ง๊ณผ๋Š” ๋ณ„๊ฐœ๋กœ SQS ํƒœ์›Œ์„œ ๋ฉ”์‹œ์ง€ ์ˆ˜ ๊ธฐ์ค€์œผ๋กœ ์˜คํ†  ์Šค์ผ€์ผ๋ง ๊ณ ๋ ค
    • ์ฟ ํ‚ค๋Š” document.cookie ๋ฅผ ์‚ฌ์šฉ์ž๊ฐ€ ์น˜๊ฒŒ ๋งŒ๋“ค๊ฑฐ๋‚˜, ๋ธŒ๋ผ์šฐ์ € ์ต์Šคํ…์…˜ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๋“ฑ ๋ฐฉ๋ฒ•์„ ๊ณ ๋ ค
  3. ํ˜„์žฌ Hot ํ•œ ์™€์น˜๋ฆฌ์ŠคํŠธ
    • ์นด์šดํŒ…์€ ๊ฒฝํ•ฉ์ด ํฌ์ง€ ์•Š์„๊ฒƒ์œผ๋กœ ์˜ˆ์ƒ๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋‚™๊ด€๋ฝ์œผ๋กœ 3ํšŒ ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„+jitter๋กœ ๋ฆฌํŠธ๋ผ์ดํ•˜๊ณ  ๊ทธ๋ž˜๋„ ์‹คํŒจ์‹œ ์Šฌ๋ž™์— ๋กœ๊ทธ ๋‚จ๊ธฐ๊ธฐ
    • ์‹ฌํ•˜๋ฉด ๋น„๊ด€๋ฝ ๋˜๋Š” ๊ฐ row ์—์„œ ์—…๋ฐ์ดํŠธ ์น˜๊ณ  ํ†ต๊ณ„ ํ…Œ์ด๋ธ” ๋”ฐ๋กœ ๋งŒ๋“œ๋Š” ๋“ฑ๋“ฑ๋“ฑ ๋‹ค๋ฅธ ์ „๋žต ๊ณ ๋ ค
  4. ๋ณ€ํ™” ๊ฐ์ง€ ํ”„๋กœ์„ธ์Šค
    • HTML ์Šค๋ƒ…์ƒท ์ƒ์„ฑ โ†’ ์ด์ „ ์Šค๋ƒ…์ƒท๊ณผ diff
    • ๋ณ€ํ™” ์—†์œผ๋ฉด ์ข…๋ฃŒ
    • ๋ณ€ํ™” ์žˆ์œผ๋ฉด ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰
      1. ๋ณ€ํ™” ์ „/ํ›„ ์Šคํฌ๋ฆฐ์ƒท ์ดฌ์˜
      2. OpenCV๋กœ ๋ณ€ํ™” ์˜์—ญ ๋นจ๊ฐ„ ๋„ค๋ชจ ๊ฐ•์กฐ
      3. ํ† ํฐ์„ ์•„๋ผ๊ธฐ์œ„ํ•ด ์ด์ „ html ๊ณผ ์‹ ๊ทœ html ๊ฐ„์˜ diff ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  llm ์— ์ „๋‹ฌ
      4. ์•Œ๋ฆผ ์ „์†ก
    • ์š”์ฒญํ›„ 1๋ถ„์ด๋‚ด ์‘๋‹ต ์—†์„์‹œ ์‹คํŒจ๋กœ ๊ฐ„์ฃผํ•˜๊ณ  ์ง€์ˆ˜ ๋ฐฑ์˜คํ”„๋กœ 3๋ฒˆ๊นŒ์ง€ ์žฌ์‹œ๋„(์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ์‚ฌ์ดํŠธ๋Š” ์‚ฌ์ „์— ๊ฑธ๋Ÿฌ์ง€๋ฏ€๋กœ ์—ฌ๊ธฐ์„  ์ผ์‹œ์ ์ธ ๋„คํŠธ์›Œํฌ/์„œ๋ฒ„ ์˜ค๋ฅ˜์ผ ๊ฐ€๋Šฅ์„ฑ)
    • ๋ชจ๋“  ์š”์ฒญ ์‹คํŒจํ•˜๋ฉด ์‚ฌ์ดํŠธ์— ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ๋‹ค๋Š” ์•Œ๋žŒ๋ณด๋‚ธ๋’ค ๊ฐ์ง€ ์ •์ง€ ์ฒ˜๋ฆฌํ•จ
  5. ์•Œ๋ฆผ
    • ์•Œ๋ฆผ ์ „์†ก ์‹คํŒจ ์‹œ ๋ฐฑ์˜คํ”„+jitter(ํญ์ฃผ ๋ฐฉ์ง€)๋กœ ์ตœ๋Œ€ 10ํšŒ๊นŒ์ง€ ์žฌ์‹œ๋„ํ›„ ๋‚ด ์Šฌ๋ž™์œผ๋กœ ์•Œ๋ฆผ(๊ธด๊ธ‰. ์™ธ๋ถ€์ชฝ ์žฅ์• ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Œ)
  6. ๊ทธ์™ธ
    • ๋ชจ๋“  ์—”ํ‹ฐํ‹ฐ๋Š” ๋…ผ๋ฆฌ์‚ญ์ œ๋กœ ์ฒ˜๋ฆฌ
    • ๋ฌด์ค‘๋‹จ ๋ฐฐํฌ(GitHub Action + aws๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ ๊ณ ๋ ค)
    • graceful shutdown ์œผ๋กœ ๋ชจ๋“  connection drainํ•˜๊ณ  ์ค‘๋‹จ
    • ์Šคํ‚ค๋งˆ ์—…๋ฐ์ดํŠธ๋ฅผ ํ•˜๋”๋ผ๋„ ์•Œ๋žŒ, ๊ฒฐ์ œ์„œ๋น„์Šค๋Š” ๋Š๊ธฐ๋ฉด ์•ˆ๋˜๋ฏ€๋กœ SQS์— ๋„ฃ์–ด์„œ ๊ด€๋ฆฌ
    • ๋ชจ๋‹ˆํ„ฐ๋ง์ด๋ž‘ ๋ฐฑ์—… ๊ณ ๋ ค