Reddit 12억 개를 열려다 네 번 실패한 이야기
HN에서 발견한 Position-Mediated Feedback 모델을 Reddit으로 교차검증하려 했다. Arctic Shift API, PullPush, wget, HF DuckDB — 네 번의 시도와 네 번의 실패. 그리고 아직 안 풀린 수학.
왜 이 연구를 시작했나
이전 글에서 Hacker News 30만 개를 전수 분석하고, 하나의 결론을 얻었다.
불평등은 "품질의 차이"가 아니라 "위치의 고착"이 만든다.
이걸 Position-Mediated Feedback(PMF) 모델이라고 이름 붙였다. 순위 k의 투표율이 k^(-α)에 비례하고, HN의 α는 약 1.51이다. 이 모델이 Gini 0.836이라는 극단적 불평등과 Dubach의 ρ ≈ 0을 동시에 설명한다.
근데 이 모델이 HN에서만 작동하는 거면 의미가 없다. 한 플랫폼의 특수 현상이 아니라 순위 기반 시스템의 보편 법칙이라는 걸 보여야 한다. 그래서 다음 목표가 정해졌다: Reddit에서 같은 분석을 돌려서, α가 다른 값으로 나오되 같은 구조(Zipf 정상상태, ρ ≈ 0)가 유지되는지 확인하는 것.
Reddit을 고른 이유는 단순하다. HN과 구조가 비슷하되(upvote 기반 랭킹), 규모가 압도적으로 크고(일 게시물 수백만), 서브레딧마다 커뮤니티 성격이 달라서 α가 서브레딧 간에 변하는지도 볼 수 있다.
쉬울 줄 알았다.
시도 1: Arctic Shift API — 400 Bad Request
Reddit 데이터를 얻는 가장 유명한 경로는 Pushshift였다. 근데 2023년에 Reddit이 API 접근을 전면 차단하면서 Pushshift는 사실상 죽었고, 그 대안으로 나온 게 Arctic Shift다. 자체 API를 제공하고, 과거 Reddit 데이터를 호스팅하고 있다.
Colab에서 Arctic Shift API를 때렸다.
# 첫 번째 시도: Arctic Shift API
url = "https://arctic-shift.photon-reddit.com/api/posts"
params = {
"subreddit": "AskReddit",
"after": "1704067200", # 2024-01-01 UTC
"before": "1735689600", # 2024-12-31 UTC
"limit": 1000
}
response = requests.get(url, params=params)
400 Bad Request.
원인은 허무했다. after와 before 파라미터가 Unix timestamp를 기대하는 게 아니라 ISO 8601 형식(2024-01-01T00:00:00Z)을 원했다. 문서에 그렇게 안 쓰여 있었는데, 실제로는 그랬다.
수정해서 다시 보냈더니 이번엔 응답이 왔다. 근데 rate limit이 빡빡해서 AskReddit 하나의 2024년 데이터를 다 받으려면 수백 번 호출해야 했다. 10개 서브레딧이면 수천 번. Colab 세션 시간 안에 끝나지 않는다.
설사 형식을 맞춰도, 1.2B 행을 API 페이징으로 받는 건 비현실적이었다.
시도 2: PullPush API — IP 차단
PullPush는 Pushshift의 또 다른 미러다. API 구조가 거의 같아서 코드를 조금만 바꾸면 된다.
# 두 번째 시도: PullPush API
url = "https://api.pullpush.io/reddit/search/submission/"
params = {
"subreddit": "AskReddit",
"after": "1704067200",
"before": "1735689600",
"size": 500
}
response = requests.get(url, params=params)
Connection refused.
Colab의 IP 대역이 통째로 차단되어 있었다. 아마 누군가가 Colab에서 대량 크롤링을 해서 전체 IP 범위가 블랙리스트에 올라간 것 같다. 로컬에서 테스트하면 되겠지만, 이 연구의 포인트는 Colab에서 재현 가능한 파이프라인을 만드는 거였으니까.
시도 3: Arctic Shift wget — 404 Not Found
API가 안 되면 원본 데이터 파일을 직접 받으면 된다. Arctic Shift GitHub에 데이터 다운로드 링크가 있다길래 wget을 시도했다.
# 세 번째 시도: 직접 다운로드
wget https://files.arctic-shift.com/dumps/reddit/submissions/RS_2024-01.zst
404 Not Found.
URL 패턴이 맞지 않았다. GitHub README에 적힌 경로와 실제 호스팅 서버의 경로가 달랐다. 여러 패턴을 시도했지만 전부 404. 데이터 호스팅 구조가 바뀌었는데 문서가 업데이트되지 않은 것 같았다.
이 시점에서 방향을 완전히 전환했다.
시도 4: Hugging Face DuckDB — IO Error
Arctic Shift 데이터가 통째로 Hugging Face에 open-index/arctic이라는 데이터셋으로 올라와 있다는 걸 발견했다.
- 1.2B submissions + 251M comments
- Parquet 포맷, 연도/월별 분할
- 172GB 압축
Parquet의 장점은 predicate pushdown이 가능하다는 거다. 172GB 전체를 안 받고, SQL의 WHERE subreddit = 'AskReddit'를 보내면 해당 행만 네트워크에서 읽어온다. DuckDB가 이걸 지원한다.
이론적으로는 완벽한 접근이었다.
# 네 번째 시도: DuckDB + Hugging Face Parquet
import duckdb
con = duckdb.connect()
con.execute("INSTALL httpfs; LOAD httpfs;")
df = con.execute("""
SELECT subreddit, score, created_utc
FROM 'hf://datasets/open-index/arctic/data/submissions/2024/*.parquet'
WHERE subreddit = 'AskReddit'
""").fetchdf()
IO Error: No files found that match the pattern
"hf://datasets/open-index/arctic/data/submissions/2024/*.parquet"
경로가 틀렸다. HF 데이터셋 페이지에서는 data/submissions/{year}/**/*.parquet 구조라고 되어 있는데, 실제 Parquet 파일의 디렉토리 구조는 달랐다. data/submissions/2024/01/000.parquet 형태의 더 깊은 중첩 구조였고, glob 패턴이 맞지 않았다.
v2 스크립트를 만들었다. HF API로 실제 Parquet URL을 먼저 탐색하고, 그 URL로 직접 쿼리하는 방식. 근데 이번엔 datasets 라이브러리의 streaming 모드가 1.2B 행을 한 행씩 Python으로 읽으려 해서, 서브레딧 하나에 수십 분이 걸렸다. 10개면 수 시간.
v3까지 갔다. DuckDB가 실제 Parquet URL을 배치로 쿼리하는 방식. CELL 1에서 HF API로 모든 Parquet 파일의 URL을 수집하고, CELL 2에서 12개씩 배치로 WHERE subreddit IN (...) SQL을 실행.
v3의 CELL 1을 실행하고 결과를 기다리는 사이에 세션이 끝났다. 아직 경로 확인이 안 된 상태.
총 6개 스크립트를 작성했고, 성공한 건 0개다.
왜 이걸 공개하는가
연구를 하다 보면 실패는 기록되지 않는다. 논문에는 성공한 방법만 쓰고, 블로그에도 깔끔하게 작동하는 코드만 올린다. 실패한 6개의 스크립트와 4가지 접근법은 어디에도 안 남는다.
근데 나는 이게 더 유용한 정보라고 생각한다.
2024년 기준 Reddit 대량 데이터를 Colab에서 얻으려는 사람이 분명히 있을 거다. 그 사람에게 "Arctic Shift API는 ISO 형식을 써야 하고, PullPush는 Colab IP가 차단되어 있고, HF 데이터셋의 Parquet 경로는 문서와 다르다"는 정보가 삽질 하루를 아껴줄 수 있다. 그게 이 글의 첫 번째 목적이다.
두 번째 목적은 더 개인적이다. 실패를 기록하면 다음 세션에서 정확히 어디서 멈췄는지 알 수 있다. v3의 CELL 1이 돌아간 결과가 어떤지, HF API가 반환한 실제 Parquet URL 패턴이 뭔지 — 거기서부터 다시 시작하면 된다.
그래서 다음에 풀어야 할 수학
Reddit 데이터는 아직 못 얻었지만, 풀어야 할 수학적 질문은 명확하다.
PMF 모델에서 정상 상태가 Zipf(α)라는 건 증명했다. 근데 과도기(transient) — 불평등이 만들어지는 과정 자체는 분석하지 못했다. 구체적으로:
1. 수렴 속도 — 불평등은 얼마나 빨리 형성되는가?
모든 글이 score=1에서 시작할 때, Zipf 분포에 도달하는 데 몇 스텝이 걸리는가? 이건 α의 함수일 거다. α가 크면(주목도 감소가 가파르면) 초기 랜덤 순위가 더 빨리 고착될 테니까. 수렴 시간 T(α, N)의 스케일링 법칙을 유도하고 싶다.
2. β(α)의 닫힌 형태
PMF 모델의 α와, 고전적 부익부 모델(GPC)의 β 사이에 β(α) 매핑이 존재한다는 걸 수치적으로 확인했다. α ≤ 1.5 범위에서 시뮬레이션과 이론의 gap이 0.02 이하로 매우 좁다. 이 매핑을 닫힌 형태(closed-form)로 유도하면, "이 플랫폼의 eye-tracking α를 측정하면 불평등 수준을 예측할 수 있다"는 실용적 도구가 된다.
3. 교차 검증 — 다른 플랫폼에서 같은 α를 관측하는가?
이게 Reddit을 얻으려 했던 이유다. HN의 α=1.51은 eye-tracking 문헌(0.8~1.5)의 상한이었다. Reddit은 UI가 다르고(카드형 vs 리스트형), 투표 행동도 다르니까 α가 달라야 한다. 하지만 같은 PMF 구조 — Zipf 정상상태, ρ ≈ 0 — 는 유지되어야 한다. 이걸 확인하는 게 모델의 보편성을 증명하는 핵심이다.
Reddit이 안 되면 Stack Exchange가 차선이다. SEDE(Stack Exchange Data Explorer)에서 SQL로 바로 쿼리할 수 있어서 데이터 접근 문제가 없다. 근데 Stack Overflow는 랭킹 메커니즘이 HN/Reddit과 좀 다르기 때문에(시간 기반이 아니라 투표 기반 정렬) 직접 비교가 까다롭다.
어쨌든, 이 세 가지 중 하나라도 풀면 논문 하나는 나온다.
연구 타임라인
295,931개 분석. Gini = 0.836 측정. 성공.
Position-Mediated Feedback. Zipf 정상상태 증명. 성공.
ρ ≈ 0은 부익부 부재가 아닌 균형 도달의 증거. 성공.
4가지 접근, 6개 스크립트. 전부 실패. 진행 중.
불평등 형성 속도 T(α, N). 미착수.
eye-tracking → 불평등 예측 공식. 미착수.
이 연구의 코드네임은 RedQueen이다. 이상한 나라의 앨리스에서 붉은 여왕이 한 말에서 따왔다:
"제자리에 있으려면 계속 뛰어야 한다."
플랫폼 위의 콘텐츠가 정확히 그렇다. 순위를 유지하려면 계속 투표를 받아야 하고, 멈추면 밀려난다. 그리고 그 경쟁의 규칙은 위치가 결정한다.
다음 글에서는 Reddit 데이터를 실제로 얻어서 교차검증을 완료하거나, 또는 Stack Exchange로 방향을 틀거나. 어느 쪽이든 기록할 예정이다.
관련 글
HN 프론트페이지의 수학: 왜 1등이 모든 걸 먹는가
Hacker News 30만 개 글을 전수조사했다. 프론트페이지 1등은 30등보다 50배 많은 관심을 받는다. 왜? 답은 '점수'가 아니라 '위치'에 있었다.
사고 도구에 AI가 꼭 필요한가
모든 프로덕트에 AI를 붙이는 시대. 하지만 Dimension은 AI 없이도 작동하는 사고 도구를 지향한다. 왜 그런 선택을 했는지, 그리고 이건 노트 앱과 어떻게 다른지.