Skip to content

Repository files navigation

congestion-radar

서울시 실시간 도시데이터 API를 10분(야간 30분) 주기로 수집해 SQLite에 쌓는 개인 프로젝트. 목표는 예쁜 서비스가 아니라 "몇 달간 안 죽고 도는 무인 시스템을 실제로 운영해보는 것" — 그 과정에서 만난 진짜 문제(Windows 절전이 예약 작업을 죽이는 것, 죽은 작업이 로그에 흔적조차 안 남는 것 등)와 그걸 어떻게 고쳤는지를 코드와 문서에 그대로 남겨두고 있다.

지금 상태: SmartSense·keepawake 조치 이후 절전 재발 없이 안정적으로 수집 중 (자세한 사고 기록은 docs/apis.md, 상태 타임라인 참고).

기여는 언제나 환영 — 새 수집 장소 추가, 다른 OS/환경에서의 무인 운영 경험, 코드 리뷰 전부 좋다. 이슈 남겨주면 됨.

Week 1 — 수집기

python -m venv .venv
.venv\Scripts\activate      # Windows
pip install -r requirements.txt

copy .env.example .env      # SEOUL_API_KEY 채워넣기
python collector.py         # 1회 실행 테스트

정상 동작하면 congestion.db, raw/YYYY-MM-DD/, logs/collector.log가 생성된다.

상시 실행 (Windows 작업 스케줄러)

10분마다 실행되도록 등록:

schtasks /create /tn "congestion-collector" /tr "C:\Workspace\congestion-radar\.venv\Scripts\python.exe C:\Workspace\congestion-radar\collector.py" /sc minute /mo 10

자정~06시는 collector.py가 스스로 판단해 30분 주기로 줄인다 (NIGHT_HOURS, _is_night_skip()). 스케줄은 그대로 10분마다 돌지만 해당 시간대엔 대부분 슬롯에서 조용히 종료한다. 완전히 끄지 않는 이유는 로드맵 Week 7-10 백분위 엔진과 예측 정확도 측정에 하루 24시간 전체의 과거 분포가 필요하기 때문이다 — 표본이 희박한 것과 아예 없는 것은 나중에 되돌릴 수 있느냐가 다르다. 실제 예보로 확인한 결과 금요일 밤에도 대부분 자정 이후 "여유"로 떨어지지만 이태원 관광특구는 새벽 2~3시까지 "약간 붐빔"이 남아 있었다 — 완전 중단이 아니라 주기만 늦춘 이유다. report.py도 이 창을 알고 있어서 30분 간격을 공백으로 오탐하지 않는다.

등록만으로는 부족하다. schtasks 기본 설정과 전원 설정이 수집기를 죽인다 — 아래를 반드시 함께 적용할 것:

# 절전 진입이 10분이면 수집 주기와 겹쳐 매번 실행 도중에 대기로 들어간다.
powercfg /change standby-timeout-ac 0     # 전원 연결 시 절전 안 함
powercfg /change standby-timeout-dc 30    # 배터리는 30분
powercfg /change monitor-timeout-ac 30
powercfg /change monitor-timeout-dc 30
$t = Get-ScheduledTask -TaskName "congestion-collector"
$t.Settings.StartWhenAvailable         = $true   # 놓친 실행을 복귀 후 따라잡기
$t.Settings.WakeToRun                  = $true   # 대기 상태에서 깨워서 실행
$t.Settings.IdleSettings.StopOnIdleEnd = $false  # 사용자가 활동을 시작해도 죽이지 않기
$t.Settings.ExecutionTimeLimit         = "PT5M"  # 기본 PT72H — 멈춘 실행이 이후 전부를 막는다
Set-ScheduledTask -TaskName "congestion-collector" -Settings $t.Settings

# WakeToRun은 전원 플랜의 '절전 해제 타이머'가 켜져 있어야 동작한다 (기본 꺼짐)
$scheme = (powercfg /getactivescheme) -replace '.*GUID: ([a-f0-9\-]+).*','$1'
powercfg /setacvalueindex $scheme SUB_SLEEP BD3B718A-0680-4D9D-8AB2-E1D2B4AC806D 1
powercfg /setactive $scheme

ExecutionTimeLimit 기본값이 72시간인데 MultipleInstancesIgnoreNew라서, 한 번 멈춘 실행이 최대 72시간 동안 이후 모든 실행을 거부할 수 있다.

⚠ 이 노트북은 상시 수집 호스트로 부적합하다

최초 측정 가동률: 8.7% (30.3시간 동안 기대 183회 중 16회만 성공, 2026-07-31 기준). 아래 두 조치(keepawake + SmartSense 비활성화) 적용 후: 2026-08-01 11:43 이후 2시간 20분 연속 15/15 정상 (공백 0). 개선됐지만 반나절 안팎 관측이라 아직 확정은 아니다.

원인은 collector.py가 아니라 Modern Standby(S0 저전력 유휴) 다. powercfg /a 결과 이 기기는 S1/S2/S3를 지원하지 않아 화면이 꺼지면 곧바로 S0 대기로 들어가고, 그 순간 실행 중이던 태스크가 강제 종료된다 (작업 스케줄러 결과코드 0x800710E0 = Win32 4320 "operator or administrator has refused the request").

증상은 두 가지로 나타난다:

  • 유령 실행raw/에 1~2개 파일만 남기고 죽는다.
  • 통째로 누락 — 대기 구간이 길면 raw/ 파일조차 없다.

예전에는 유령 실행이 collection_log에 행조차 남기지 못해서 성공률이 언제나 100%로 보였다. 지금은 collect()가 시작 시점에 status='running' 행을 먼저 커밋하므로, 완주하지 못한 실행은 그 상태로 남아 report.py가 집어낸다. report.py는 공백을 찾으면 System 이벤트 로그(506/507)까지 자동 대조해서 원인 후보를 같이 보여준다.

위 응급 처치로 완화는 되지만 근본 해결은 아니다. roadmap.md 2-1의 상시 PC / 라즈베리파이 / VPS / GitHub Actions cron 중 하나로 옮겨야 한다.

또한 현재 태스크는 Logon Mode: Interactive only 라서 로그아웃 상태에선 아예 돌지 않는다. (/ru /rp 없이 등록했기 때문. 변경하려면 계정 암호 또는 S4U 권한 설정이 필요하다.)

keepawake.py — 화면은 꺼도 되지만 시스템은 재우지 않기

화면을 계속 켜두면 번인이 온다. 그렇다고 standby-timeout-ac 0만 걸어두면 안 되는 게, 이 노트북은 화면이 꺼지는 순간 그 자체가 절전 트리거 역할을 하는 것으로 관측됐다(2026-08-01 새벽, VIDEOIDLE 10분 설정 직후 반복 재현). SetThreadExecutionState(ES_SYSTEM_REQUIRED)로 "화면은 꺼도 되지만 시스템은 깨어있어라"를 OS에 직접 요청하면 이 문제를 우회한다 — ES_DISPLAY_REQUIRED는 넣지 않아서 화면 꺼짐(번인 방지)은 그대로 허용된다.

# 지금 세션에 바로 적용
Start-Process -FilePath "C:\Workspace\congestion-radar\.venv\Scripts\pythonw.exe" `
  -ArgumentList "C:\Workspace\congestion-radar\keepawake.py" -WindowStyle Hidden

# 재부팅/재로그인 후에도 자동 시작되게 (schtasks 생성 권한이 막혀 있으면 이 방법)
$startup = [Environment]::GetFolderPath('Startup')
$ws = New-Object -ComObject WScript.Shell
$lnk = $ws.CreateShortcut("$startup\congestion-keepawake.lnk")
$lnk.TargetPath = "C:\Workspace\congestion-radar\.venv\Scripts\pythonw.exe"
$lnk.Arguments = "C:\Workspace\congestion-radar\keepawake.py"
$lnk.Save()

⚠ Lenovo Intelligent Sensing(SmartSense) — 진짜 원인이었던 것

keepawake를 적용한 뒤에도 절전이 재발했다(2026-08-01 11:10, 21분 공백). 원인은 idle 타이머가 아니라 사람 감지 기반 절전 — Lenovo Vantage의 "Intelligent Sensing" 서비스가 카메라/센서로 자리 비움을 판단해 독립적으로 절전을 건다. SetThreadExecutionState는 idle 타이머만 막기 때문에 이 경로는 못 막는다. 이 서비스를 끄고 나서(11:43) 지금까지는 재발이 없다.

# 관리자 권한 필요 (schtasks와 같은 이유로 일반 세션에서 막힘 — UAC 승인 필요)
Stop-Service -Name SmartSense -Force
Set-Service -Name SmartSense -StartupType Disabled

Lenovo가 아닌 기기라면 제조사별 "사람 감지/자리 비움 잠금" 기능이 있는지 비슷하게 의심해볼 것 — Idle Timeout이 아닌 다른 사유(Adaptive Connected Standby, AC/DC Display Burst Suppressed 등)로 절전이 걸리면 이 계열이다.

완료 기준

노트북을 닫아도 데이터가 쌓인다. (roadmap.md Week 1) — 현재 미충족 (개선 중, 관측 계속)

Week 2 — 운영

python report.py            # 오늘 수집 상태 (이상 있으면 종료코드 1)
python report.py 2026-08-01 # 특정 날짜
python backup.py            # 스냅샷 + 무결성 검사 + 원격 복사
python notify.py            # 리포트를 디스코드로 전송

셋 다 이상이 있으면 종료코드 1을 반환한다. 스케줄러의 '마지막 결과'만 봐도 상태를 알 수 있도록 맞춘 것이다.

알림 (디스코드)

디스코드 서버 설정 → 연동 → 웹후크 → URL을 복사해 .envDISCORD_WEBHOOK_URL에 넣는다. 봇 생성이나 채널 ID 조회는 필요 없다.

정상일 때도 매일 보낸다 — 침묵이 "잘 돌고 있다"인지 "알림까지 같이 죽었다"인지 구분되지 않으면 죽음 감지가 아니다.

하루 1회 실행 등록:

schtasks /create /tn "congestion-report" /sc daily /st 09:00 ^
  /tr "C:\Workspace\congestion-radar\.venv\Scripts\python.exe C:\Workspace\congestion-radar\notify.py"
schtasks /create /tn "congestion-backup" /sc daily /st 04:00 ^
  /tr "C:\Workspace\congestion-radar\.venv\Scripts\python.exe C:\Workspace\congestion-radar\backup.py"

등록 직후 반드시 아래를 적용할 것. schtasks /create의 기본값 DisallowStartIfOnBatteries=true 때문에, 배터리로 돌 때 작업이 시작조차 되지 않고 Queued 상태에 머문다. 이때 스케줄러의 Last Result직전 값(흔히 0)이 그대로 남아 성공한 것처럼 보인다 — 죽음 감지기가 조용히 죽는 가장 나쁜 형태다.

foreach ($n in @("congestion-report","congestion-backup")) {
  $t = Get-ScheduledTask -TaskName $n
  $t.Settings.DisallowStartIfOnBatteries = $false   # 이게 빠지면 배터리에서 안 돈다
  $t.Settings.StopIfGoingOnBatteries     = $false
  $t.Settings.StartWhenAvailable         = $true
  $t.Settings.WakeToRun                  = $true
  $t.Settings.IdleSettings.StopOnIdleEnd = $false
  $t.Settings.ExecutionTimeLimit         = "PT10M"
  Set-ScheduledTask -TaskName $n -Settings $t.Settings
}

검증 방법: schtasks /runStateQueued에 머물면 실행되지 않은 것이다. Ready로 돌아오고 logs/에 줄이 늘어야 실제로 돈 것이다.

⚠ 배터리로 쓰면 여전히 빈다

위 전원 설정(standby-timeout-ac 0)은 전원 연결 시에만 적용된다. 배터리에서는 30분 뒤 대기로 들어가고, 절전 해제 타이머도 AC에서만 켜 두었다. 가동률을 실제로 확인하려면 노트북을 전원에 연결해 둘 것.

About

서울시 실시간 도시데이터 수집기 — 예측 서비스보다 '무인 시스템을 몇 달간 안 죽고 운영하기'가 목표인 개인 프로젝트

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages