trash to /dev/null

화가 치밀어 오늘때가 있다. 예전엔 안그랬는데, 사소한것에 신경이 날카로워지고 꾹꾹 참다 애꿎은 곳에 화풀이 한다.
크게 소리를 질러보다가 욕을 크게 함질러 볼까 하다가 조용히 내뱉고 삼킨다.
왜 그럴까 했는데 그런 생각은 안하기로 했다. 그냥 그럴 수 밖에 없는 나라는 물질의 특성이라고 인정하고 더 이상 깊게 파보려 하지 않는다.
마트에 가서 장을 보다 요거트에 1+1 행사 상품을 보고 집어 들었다. 그런데 팩이 아닌 낱개로 되어 있었다.
8개나 되는 요거트를 어떻게 들고 갈까 고민을 잠시했다. 정문에 있는 바구니를 가져오면 될것을 그냥 양손과 몸을 바구니 삼아 쌓아 올려 계산대까지 갔다. 계산대에 내려 놓다 1개가 떨어지고 바닥에 요거트가 터져 흘렀다.
순간 계산대 아주머니는 이거 사야한다고 말하고, 나는 연신 죄송하다고 얼른 터진 요겉를 집어 세웠다.
주위에 사람들이 많지 않았지만 쪽팔림과 함께 왜 이런 멍청한 짓을 했는지 속으로 나를 탓하고 있었다.
집에서 가져간 장바구니가 있었지면 아직 사지도 않은 제품을 여기 넣는건 왠지 물건 훔치는 모슴으로 비춰지는것 같아  사용하지 못했다.
나가서 장바구니를 가져올걸, 1+1 아닌 다른 요거 상품을 살걸하면서, 후회한다.
그렇게 집에 오는 길에 아씨 아씨 하면서 잔뜩 성난 표정으로 한숨을 내쉬며 온다.
이 모습은 내가 봐도 화가 풀리지 않는 어린아이 행동 처럼 보인다.
뭐가 분한건지 그 누구도 아닌 내 잘못인것을 마치 누명이 씌워진 것 처럼 억울해 한다.
그래 안다. 이런 욕하고 투덜거림이 좋지 않다는것을.
하지만 또 한번 생각해보면 이걸 악스런 구린내 감정들을 어디다 퍼붓지 않으면 폭발할것 같았다.
그때의 그 감정이 남들이 보기에 흉측해 보여도 그 감정을 어찌할 방법을 몰라 발을 동동 굴리는 안쓰런 내가 있었다.
차라리 힘을 빼버릴까 그러면서 욕할 힘도 사라질테니
그냥 베개라도 실컷 때려볼까 그럼 좀 나아질 수도
그 당시 그 통제할 수 없는 그 감정은 바로 해결까진 아니더라고 내와 내가 사랑하는 이들이 실망하지 않게 다치지 않게 안전 장치가 있었으면 하는 생각이 들었다.
그 쓰레가 같은 욕도 감정도 모두 담아 둘 수 없기에 난 cat trash >! /dev/null 가 되길 바란다.
하지만 이 물질 세계의 존재인 내 말들은 /dev/stdout /dev/stderr 로 흘러갈 수 밖에 없다.

vector memory growth issue in argo-workflow

k8s 환경의 특정 노드에서 동작중인 vector pod 1개가 메모리가 계속증가하고 있었다.
이유는 argo workflow 노드1개에만 스케쥴링되어서다.

[스케쥴링 원인]
argo-workflow(controller) 가 직접 스케줄링 하는 게 아니라 k8s 기본 스케줄러 (kube-scheduler) 가 워크플로우 task 파드를 노드에 배치한다.
workflow spec 에 nodeSelector/affinity 가 없어 다음 스케줄러의 기본 점수 두 개로 결정된다.
1. NodeResourcesFit(과거 k8s v1.22 이전 NodeResourcesLeastAllocated, NodeResourcesMostAllocated, RequestedToCapacityRatio 등이 통합됨)
- 각 노드의 요청(request) 점유율이 가장 낮은 노드에 가장 높은 점수.
- 당시 CPU request worker01 34% / worker02 35% / worker03 25% <- 최저 / worker04 30% / worker05 30%
- 그래서 새로 들어오는 워크플로우 task 가 매번 worker03 으로 떨어짐.

2. ImageLocality
- 노드에 이미 캐시된 이미지를 쓰는 파드 에 추가 점수.
- 첫 워크플로우가 worker03 으로 떨어지면 그 노드에 이미지가 캐시 -> 다음 워크플로우도 worker03 이 더 높은 점수 -> 눈덩이 효과.

결과적으로 워크플로우 task 생성되면 -> kube-scheduler 점수 계산 -> worker03 선택 -> 이미지 더 강하게 캐시됨 -> 다음 task 도 같은 결과 -> 누적

[vector 로그 대상]
vector 의 kubernetes_logs source 는 pod들을 watch 한다.
pod 가 살아있으면 vector 는 그 파드를 수집 대상으로 계속 인식하고 다음을 메모리에 들고 있는다.
- 파드 메타데이터 캐시: namespace / pod_name / container_name / labels / annotations / node_name 등 100여 개 누적되면 무시 못할 양.
- file watcher state: 각 컨테이너 로그 파일의 inode, 마지막 read offset(checkpoint), open file descriptor.
- glob rescanning: config 에 glob_minimum_cooldown_ms: 8000 이라 8초마다 /var/log/pods/** 를 다시 스캔. 완료된 파드 디렉토리가 매번 결과에 잡혀 비교 작업이 일어남.

[조치1]
ns:argo-workflow > cm:workflow-controller-configmap > 아래 내용을 설정한다.
data:
  config: |
    workflowDefaults: # 각 워크플로가 별도의 설정이 없다면 사용될 디폴트 설정
       spec:
         podGC: # 워크플로우 task 파드 정리 전략1 - 상태 기반
           strategy: OnWorkflowSuccess # 워크플로우 전체가 성공으로 끝났을 때 task 파드들을 일괄 삭제. 실패하면 파드를 남겨 두어 사후 디버깅.
           deleteDelayDuration: 1m # strategy 가 발동한 뒤 1분 대기 후 실제 삭제. 로그 수집기(vector 등) 가 마지막 라인을 읽어 Kafka 까지 보낼 시간 확보.
         ttlStrategy: # 워크플로우 task 파드 정리 전략2 - 시간 기반
           secondsAfterSuccess: 3600 # 성공한 Workflow CR 을 1시간 뒤 삭제.
           secondsAfterFailure: 86400 # 실패한 Workflow CR 은 24시간 보존 후 삭제.

podGC + ttlStrategy 로 스케줄링 노드 편중을 막진 않지만 누적을 끊어 영향 자체를 무력화시킨다.
podGC , ttlStrategy 는 둘중 하나만 만족되면 정리가 되네 일정기간 유지하고 싶다면 podGC는 빼고 ttlstrategy 만 사용해야 한다.

argo workflows v2.4+ 부터 controller 가 ConfigMap 변경을 watch 해서 자동 reload 해서 별도 재시작이 필요 없다.
workflowDefaults(podGC/ttlStrategy)는 신규 워크플로우 부터만 적용된다.

[조치2]
이미 진행 중이거나 완료된 워크플로우에는 적용 안되니 작업이 끝난 workflow pod들을 종료한다.
kubectl -n argo-workflow delete pod --field-selector=status.phase=Succeeded
kubectl -n argo-workflow delete pod --field-selector=status.phase=Failed

[조치3]
vector 는 rust 로 작성되어 있고 jemalloc 을 기본 할당자로 사용한다.
jemalloc 은 한번 so 로 부터 받은 메모리를 내부 free pool 로 두고 잘 OS 로 돌려주지 않는다.
즉 vector 가 내부적으로 이제 안 쓰는 메모리가 됐어도 프로세스 RSS / working set 은 그대로 보인다.
새로 들어오는 워크로드는 이 free pool 을 재활용하므로 메모리는 더 늘지 않지만, 줄어들지도 않는다.
이런 이유로 메모리가 증가된 vector pod1개는 새로띄운다.(vector daemonset 이라 pod 삭제하면 해당노드새 새 vector pod 생성된다.)
kubectl -n vector delete pod vector-abc123

사실 개별 workflow 는 ttl 설정이 되어 있어 자동 정리(삭제)가 되는데, cronworkflow 부분이 빠져 있어 문제가 됐다.

Usage of Claude Code and Codex

claude-code를 사용할때는 하루에 입력토큰이 많아야 2M 로 limit 에 걸릴일이 없었는데,
codex 를 사용하고 부턴 질문 몇개에 금방 5h limit 에 걸려 보니 input token 이 2M가 금방찬다.

https://github.com/ryoppippi/ccusage 는 claude-code/codex 의 사용량 현황을 알려준다.
참고로 사용자 .jsonl 의 토큰 및 캐시 사용 기록 파악한다.
~/.claude/projects/<project>/*.jsonl
~/.codex/sessions/<year>/<month>/<day>/*.jsonl

# claude-code usage
bunx ccusage
bunx ccusage weekly
bunx ccusage monthly

# codex usage
#bunx @ccusage/codex@latest -> deprecatd
bunx ccusage@20/codex@latest
bunx ccusage@20/codex@latest weekly
bunx ccusage@20/codex@latest monthly

다음과 같이 둘을 비교해보니 한달치 input token 이 claude-code 가 훨씬 적다.

Claude Code: Input 적고 > Cache Read로 대부분 처리 > 저렴
Codex: Input이 크고 > Cache Read 비율이 낮음 > 비쌈

codex reasoning(내부 추론) 과정은 캐시 키를 매번 바꿔서 캐시를 무효화시키는 경향이 있다고 한다.
fast 모드도 비용에 영향을 준다고 함.

다음 설정을 해서 줄여볼 수 있다고 하는데 여전히 input token 이 팍팍 증가한다.
# ~/.codex/config.toml
model_reasoning_effort = "low"
model_reasoning_summary = "concise"
model_verbosity = "low"
[features]
fast_mode = false # /fast 자체가 숨김 처리된다.