5개 프로젝트를 마치고 — AI와 함께 개발하는 방식에 대한 회고

2026. 8. 16. 22:09ㆍPROJECT/Toy Project

5개 프로젝트를 마치고 — AI와 함께 개발하는 방식에 대한 회고

1. 시작하며

Playback Gate부터 Cloud Ops Lab까지 다섯 개의 프로젝트를 진행했다.

Playback Gate

Live Event Stream

ContentOps Agent

LLM Router

Cloud Ops Lab

각 프로젝트에서 다룬 기술은 모두 달랐다.

Playback Gate
→ API / Database / Cache / 동시성

Live Event Stream
→ Kafka / Consumer / Lag / Idempotency / Failure

ContentOps Agent
→ RAG / Evaluation / Tool Calling

LLM Router
→ Model Routing / Quality / Cost / Latency

Cloud Ops Lab
→ Kubernetes / Resource / Failure / Observability

처음에는 이 프로젝트들을 각각 다른 기술을 학습하기 위한 프로젝트로 생각했다.

하지만 다섯 프로젝트를 끝내고 돌아보니 실제로 가장 많이 바뀐 것은 기술 목록이 아니었다.

AI와 함께 개발하는 방식 자체가 계속 바뀌었다.

처음에는:

요구사항 작성
↓
AI에게 구현 요청
↓
Test
↓
완성

에 가까웠다.

프로젝트가 하나씩 늘어나면서 다음 질문이 계속 추가됐다.

코드가 동작하는 것과
문제가 해결된 것은 같은가?

성능이 좋아졌다는 것을
무엇으로 확인할 것인가?

실패한 상태를
왜 Repository에 남겨야 하는가?

AI 기능의 품질을
일반 Test로 판단할 수 있는가?

어떤 기술을 사용하지 않은 것도
프로젝트 결과가 될 수 있는가?

프로젝트 목표가 중간에 바뀌었다면
그 변화도 Harness가 관리해야 하는가?

Kubernetes가 복구된 것과
사용자가 느끼는 서비스 복구는 같은가?

결국 다섯 프로젝트를 관통하는 질문은 하나에 가까워졌다.

AI에게 구현 속도는 맡기되, 무엇을 만들고 무엇을 성공이라고 판단할지에 대한 통제권을 개발자가 어떻게 유지할 것인가?

이번 글에서는 각 프로젝트에서 사용한 기술을 다시 소개하기보다,
다섯 프로젝트를 거치면서 이 개발 방식이 어떻게 변했는지를 정리해보려고 한다.


2. 다섯 프로젝트를 한 줄씩 줄이면

각 프로젝트에서 가장 크게 남은 문제를 한 줄로 줄이면 다음과 같다.

Playback Gate
→ 빠른 것보다 정확한 것이 더 중요할 수도 있다.

Live Event Stream
→ 늘린다고 항상 더 빨라지는 것은 아니다.

ContentOps Agent
→ 코드가 돈다고 AI가 잘하는 것은 아니다.

LLM Router
→ Repository가 일관돼도 프로젝트 목표가 달라지면 Harness가 흔들린다.

Cloud Ops Lab
→ Infrastructure가 복구된 것과 서비스가 복구된 것은 다를 수 있다.

기술은 계속 달라졌지만,
프로젝트에서 반복해서 깨진 가정은 비슷했다.

더 큰 Pool
→ 더 높은 처리량?

더 많은 Partition
→ 더 높은 처리량?

더 복잡한 Retrieval
→ 더 높은 검색 품질?

더 큰 Model
→ 더 좋은 답변?

더 많은 Replica
→ 더 낮은 Latency?

실제 결과는 계속:

그럴 수도 있지만
현재 문제에서는 아닐 수 있다.

였다.

이 경험이 이후 프로젝트의 개발 방식에 계속 영향을 줬다.


3. Project 1 — Playback Gate

첫 번째 프로젝트는 Playback Gate였다.

영상 재생 요청을 가정하고:

회원 상태

콘텐츠 공개 상태

이용권

연령 제한

동시 재생 제한

Playback Session

Playback Token

같은 비즈니스 규칙을 구현했다.

하지만 실제 학습의 중심은 기능보다:

성능

Database

Cache

동시성

이었다.


4. Playback Gate에서 처음 배운 것은 Baseline이었다

처음에는 자연스럽게 여러 성능 기술을 생각했다.

Connection Pool 증가

Index

Redis Cache

Redis Lock

Distributed Lock

하지만 처음부터 적용하지 않았다.

먼저 기본 구조를 만들고 같은 k6 Scenario를 반복했다.

Baseline
   ↓
측정
   ↓
병목 확인
   ↓
변경
   ↓
재측정

이 프로젝트에서 처음으로:

변경했다는 사실보다 Before / After를 같은 조건에서 비교하는 것이 중요하다.

는 기준이 생겼다.


5. Connection Pool을 늘린다고 처리량이 크게 오르지는 않았다

처음에는 DB Connection Pool이 작으면 요청이 기다리게 되니까:

Pool 증가
↓
대기 감소
↓
RPS 증가

를 예상했다.

실제로 대기 시간은 줄어들 수 있었다.

하지만 처리량은 예상만큼 크게 늘지 않았다.

즉:

Connection Pool이 병목일 수 있다

와:

Connection Pool을 늘리면
전체 처리량이 크게 증가한다

는 다른 문제였다.

이때부터:

설정 값을 바꿨다
=
성능 개선

이라고 쓰지 않게 됐다.


6. Redis를 배우려고 했지만 최종 Cache는 Caffeine이었다

Cache 후보는 다음과 같았다.

DB Only

Caffeine Local Cache

Redis

처음 프로젝트를 시작할 때는 Redis를 사용할 가능성을 크게 생각했다.

하지만 현재 Application은 단일 인스턴스였다.

공유 Cache가 필요한 구조가 아니었다.

최종 선택은:

Caffeine Local Cache

였다.

결과는:

지표 Cache 전 Caffeine 후
p50 21.63 ms 15.82 ms
p95 33.88 ms 26.82 ms
p99 44.07 ms 36.10 ms
RPS 326.0 321.5

Latency는 좋아졌다.

하지만 RPS는 거의 그대로였다.

그리고 더 중요했던 것은:

PlaybackSession COUNT

를 Cache하지 않은 것이다.

동시 재생 수는 즉시 정확해야 했다.

이 값을 Cache하면 성능은 좋아 보일 수 있지만
프로젝트의 핵심 비즈니스 요구사항을 깨뜨릴 수 있었다.

Playback Gate에서 가장 크게 남은 문장 중 하나는:

Cache를 사용하는 것보다 어떤 데이터를 Cache하면 안 되는지 판단하는 과정이 더 중요했다.

였다.


7. 동시성 문제는 Redis Lock이 아니라 DB Lock으로 끝났다

동시 재생 제한의 Baseline은:

ACTIVE Session COUNT
        ↓
최대 동시 재생 수 확인
        ↓
Session INSERT

였다.

BASIC 이용권의 최대 동시 재생 수가 1인 상황에서
40개 요청을 동시에 보내자:

ACTIVE Session = 40

이 만들어졌다.

Race Condition이 실제로 재현됐다.

후보는:

DB Pessimistic Lock

JVM Lock

Redis Lock / Atomic

이었다.

최종적으로 선택한 것은:

Subscription
      ↓
SELECT ... FOR UPDATE
      ↓
ACTIVE Session COUNT
      ↓
Session INSERT

였다.

그 과정에서도 예상하지 못한 문제가 있었다.

MySQL의 REPEATABLE READ Snapshot 때문에
Lock 대기 중 다른 Transaction이 생성한 Session을 COUNT에서 보지 못하는 상황이 발생했다.

최종적으로:

PESSIMISTIC_WRITE
+
READ_COMMITTED

를 적용했다.

결과는:

Lock 전
ACTIVE = 40

Lock 후
ACTIVE = 1

이었다.


8. 첫 프로젝트부터 Final이 Baseline보다 느려졌다

동시성 정확성을 얻은 비용도 있었다.

지표 Lock 전 Lock 후
Iteration RPS 321.5 296.0
p50 15.82 ms 21.41 ms
p95 26.82 ms 32.61 ms
p99 36.10 ms 41.31 ms

전체 Baseline과 Final을 비교해도 처리량은 낮아졌다.

Baseline RPS
321.5

Final RPS
296.0

처음에는:

성능 프로젝트
↓
최종 RPS 증가

를 자연스럽게 기대했다.

하지만 실제 결과는:

RPS 감소

대신

동시 재생 정책 정확성 확보

였다.

이 프로젝트에서 처음 크게 바뀐 생각은:

성능 최적화는 무조건 RPS를 높이는 작업이 아니다.

였다.

정확성

Latency

처리량

Infrastructure 복잡도

를 같이 봐야 했다.


9. Playback Gate가 다음 프로젝트에 남긴 것

첫 프로젝트가 끝난 뒤 기술보다 개발 방식에 대한 규칙이 남았다.

REQUIREMENTS와 DESIGN 역할을 분리한다.

Experiment 이름에 해결 기술을 미리 넣지 않는다.

문제를 해결하기 전에 먼저 재현한다.

동일 조건 Before / After를 유지한다.

문제가 재현된 상태도 Git에 남긴다.

Graph나 복잡한 자동화는 필요할 때만 사용한다.

이때부터 프로젝트 자체뿐 아니라:

어떻게 개발할 것인가?

도 다음 프로젝트의 학습 대상이 됐다.


10. Project 2 — Live Event Stream

두 번째 프로젝트는 Kafka 기반 Live Event Stream이었다.

기본 구조는 단순했다.

Producer
   ↓
Kafka
   ↓
Consumer
   ↓
Database

하지만 확인하려던 문제는 많았다.

Producer Throughput

Consumer Throughput

Consumer Lag

Partition

Consumer Scaling

Duplicate Event

Poison Event

DLT

Recovery

Replay

Schema Evolution

Playback Gate에서 만든:

Baseline
→ 문제 재현
→ 측정

방식을 비동기 시스템으로 가져갔다.


11. Partition만 늘리면 빨라질 것이라고 생각했다

Baseline은:

Partition 1

Consumer 1

이었다.

500 events/sec 부하에서:

Max Lag = 4,252

평균 E2E 처리시간 ≈ 8.58초

가 나타났다.

자연스럽게:

Partition 증가
→ 병렬 처리 증가
→ Lag 감소

를 예상했다.

그래서:

P1 C1
→
P3 C1

로 바꿨다.

결과는 오히려:

Max Lag = 8,667

로 악화됐다.

첫 번째 프로젝트에서:

Pool 증가
≠
처리량 증가

를 봤다면,

두 번째 프로젝트에서는:

Partition 증가
≠
처리량 증가

를 봤다.

Partition을 실제로 병렬 처리할 Consumer가 필요했다.


12. Consumer도 Partition 수 이상 늘려도 의미가 없었다

최종적으로:

P3 C3

에서 500 events/sec를 따라갈 수 있었다.

Baseline과 비교하면:

P1 C1
Max Lag 4,252

↓

P3 C3
Final Lag 0

였다.

하지만 Consumer를 더 늘리면 계속 빨라지는 것도 아니었다.

P3 C6

에서는:

Consumer 3개
→ Partition 할당

나머지 3개
→ Idle

이었다.

Partition 3
↓
동시에 일할 수 있는 Consumer 최대 3

라는 구조적 한계가 있었다.

이 프로젝트에서도:

더 많이 배치한다
=
더 높은 성능

은 아니었다.


13. Hot Partition에서는 P3 C3도 P1 C1처럼 됐다

Kafka Key는 memberId를 사용했다.

일반적인 분포에서는 Event가 여러 Partition으로 분산됐다.

하지만 모든 Event가:

memberId = 1

이면 같은 Partition으로 몰렸다.

결과적으로:

P3 C3

구조도 사실상:

P1 C1

처럼 동작했다.

여기서는 다음 Trade-off가 생겼다.

균등한 Load Distribution

vs

동일 사용자의 Event Ordering

최종적으로 Ordering을 유지하기 위해 memberId Key를 유지했다.

즉 성능만 보고 Key 전략을 바꾸지는 않았다.

Playback Gate에서:

RPS
vs
정확성

을 봤다면,

Live Event Stream에서는:

분산 효율
vs
Ordering

을 봤다.


14. Duplicate Event도 Redis가 아니라 UNIQUE Constraint로 끝났다

At-least-once Delivery 환경에서 Duplicate를 직접 재현했다.

DB INSERT 성공
↓
Offset Commit 이전 실패
↓
같은 Event 재전달
↓
DB INSERT

초기에는 일부러 eventId UNIQUE를 넣지 않았다.

실제로 같은 eventId Row가 여러 개 생겼다.

후보는:

DB Unique Constraint

Processed Event Table

Redis Deduplication

이었다.

최종 선택은:

processed_event.event_id UNIQUE

였다.

500 events/sec 범위에서는 충분했고,
Redis Deduplication까지 추가할 근거는 없었다.

첫 번째 프로젝트와 다시 비슷한 결과였다.

Redis를 쓸 수 있다
≠
Redis가 현재 문제의 정답이다

였다.


15. Poison Event에서 예상한 문제와 실제 문제도 달랐다

처음 예상은:

Poison Event
↓
계속 실패
↓
Partition Blocking

이었다.

하지만 실제 Listener Failure는:

Poison Event
↓
Retry
↓
Skip
↓
뒤 Event 계속 처리
↓
실패 Event 유실

에 가까웠다.

즉 핵심 문제는:

Partition Blocking

보다:

실패 Event를 잃어버리는 것

이었다.

최종적으로:

Consumer Failure
↓
Backoff Retry
↓
계속 실패
↓
DLT

구조를 선택했다.

별도의 Retry Topic은 사용하지 않았다.


16. Live Event Stream에서 Experiment와 Git이 Harness에 들어왔다

Playback Gate에서는 문제와 해결의 과정을 문서와 Metric으로 관리했다.

Live Event Stream에서는 여기에:

Experiment

Git History

가 더 강하게 들어왔다.

Commit 흐름도:

Baseline 구현

Baseline 측정

Consumer Lag 재현

Partition / Consumer 비교

Idempotency

Failure Handling

Recovery / Replay

Schema Evolution

처럼 변화를 따라가도록 만들었다.

완벽하지는 않았다.

Duplicate와 Failure 일부는:

문제 재현

+

해결

이 충분히 분리되지 않았다.

그래서 다음 프로젝트로:

문제가 재현된 상태 자체를 해결 코드와 별도 Checkpoint로 남긴다.

는 규칙이 넘어갔다.


17. Skill이라고 만들었던 것이 사실 Rule에 가까웠다

Live Event Stream에서는 SKILL.md에 이런 내용을 넣었다.

현재 Phase를 벗어나지 않는다.

문제를 미리 해결하지 않는다.

Kafka 설정을 임의로 변경하지 않는다.

Human Gate 없이 중요한 구조를 바꾸지 않는다.

측정 없이 개선됐다고 하지 않는다.

프로젝트가 끝난 뒤 돌아보니 이것은 특정 Workflow가 아니었다.

프로젝트 전체에서 항상 지켜야 하는 제약이었다.

즉:

Skill

보다:

Project Rule

에 가까웠다.

그래서 다음 프로젝트부터:

Project Rule
↓
반복 Workflow 발견
↓
필요하면 Skill 추출

순서로 바꾸게 됐다.


18. Project 3 — ContentOps Agent

세 번째 프로젝트는 처음으로 AI 품질을 직접 다뤘다.

기본 목표는:

내부 운영 문서
+
콘텐츠 데이터
      ↓
올바른 정보 검색
      ↓
근거 있는 Answer
      ↓
필요한 Tool 선택

이었다.

여기서 이전 프로젝트의 Harness에 가장 큰 변화가 생겼다.


19. Test가 모두 성공해도 AI는 틀릴 수 있었다

이전 프로젝트에서는:

Unit Test

Integration Test

Load Test

가 중요한 검증 도구였다.

하지만 RAG에서는:

API가 200을 반환한다.

고 해서:

정답 문서를 찾았다.

고 볼 수 없었다.

그래서 처음으로 명확하게:

Test
≠
Evaluation

을 분리했다.

Test
→ 코드가 정상 동작하는가

Evaluation
→ AI가 올바른 결과를 만드는가

였다.

이때 Evaluation Dataset이 Harness의 핵심 구성 요소가 됐다.


20. RAG를 하나의 Black Box로 보지 않았다

AI 답변이 틀렸을 때:

Prompt 수정

부터 하지 않았다.

실패를 나눴다.

Retrieval Failure

Ranking Failure

Generation Failure

Grounding / Hallucination

No Answer

Tool Selection Failure

즉:

RAG 실패

라는 한 문장 대신:

어느 Layer에서 실패했는가?

를 먼저 봤다.

이 방식은 이후 LLM Router와 Cloud Ops Lab에서도 계속 이어졌다.


21. 예상했던 Exact Keyword 문제가 실제 첫 병목은 아니었다

처음에는 Vector Search의 약점으로:

OPS-101

M-03

CONTENT_BLOCKED

같은 Exact Keyword를 예상했다.

그래서 자연스럽게:

Keyword Search

Hybrid Search

RRF

같은 기술을 생각했다.

하지만 실제 Evaluation에서는 Exact Keyword 문서 Hit가:

1.0

이었다.

최종 Retrieval Metric은:

Hit Rate@K = 0.9

Recall@K = 0.9

MRR = 0.75

였고 Baseline과 Final이 동일했다.

문서 Ranking Failure도:

0건

이었다.

따라서:

Hybrid Search

RRF

Reranker

를 추가할 근거가 충분하지 않았다.


22. Chunking도 바꿨지만 Metric은 같았다

Chunking은 다음을 비교했다.

Fixed 500 / 50

Section

Fixed 200 / 20

처음에는 Chunk Size가 Retrieval 결과에 큰 영향을 줄 것이라고 예상했다.

하지만 Sample 문서의 Section 자체가 짧았다.

실제 Chunk가 서로 비슷하게 만들어졌다.

결과는:

Metric 동일

이었다.

그래서 Baseline Chunking을 유지했다.

여기서도:

Chunking 기법을 여러 개 구현했다

보다:

현재 Dataset에서는 차이를 만들지 못했다

가 실제 결과였다.


23. 더 긴 Prompt도 더 좋은 Prompt가 아니었다

Prompt A와 Prompt B를 비교했다.

강화된 Prompt B는:

LLM Latency
2012ms → 3275ms

Completion Token
353 → 1063

으로 증가했다.

RAG End-to-End도:

Baseline
3292ms

Final
3636ms

이었다.

그런데 해결하려던 Failure는 그대로 남았다.

즉:

더 자세한 Prompt
=
더 좋은 Prompt

가 아니었다.

Prompt도 코드처럼:

Before

vs

After

의 대상이라는 것을 확인했다.


24. Tool Calling에서도 Accuracy 하나만 보면 안 됐다

Agent 단계에서는:

search_policy_documents

search_contents

get_content_detail

Tool을 사용했다.

Tool Selection 결과는:

집합 Accuracy      0.625

Sequence Accuracy  0.375

MATCH        3
WRONG_ORDER  2
MISSING_TOOL 3
WRONG_TOOL   0

이었다.

여기서 중요한 것은:

Wrong Tool = 0

이라고 해서 Agent가 잘 동작한 것이 아니라는 점이었다.

실제 실패는:

필요한 두 번째 Tool을 호출하지 않음

이었다.

즉:

Tool을 틀리게 선택하는 문제

와:

필요한 Tool 조합을 끝까지 수행하지 않는 문제

는 달랐다.


25. LangGraph는 학습 대상이었지만 최종적으로 넣지 않았다

처음에는 Agent Workflow가 복잡해지면 LangGraph가 필요할 수 있다고 생각했다.

실제로 Multi Tool 실패도 확인했다.

하지만:

Wrong Tool = 0

Multi Tool Failure 존재

라는 한 번의 결과만으로 Graph 도입 비용을 정당화하기에는 근거가 부족하다고 판단했다.

따라서 Human Gate에서 보류했다.

여기서 표현도 중요했다.

LangGraph가 효과가 없다.

가 아니다.

현재 Dataset과 현재 Failure만으로 LangGraph 도입을 정당화할 근거가 충분하지 않았다.

가 정확했다.

이 문장은 이후 프로젝트에서 계속 사용하게 됐다.


26. ContentOps Agent가 Harness에 Evaluation을 추가했다

세 번째 프로젝트까지 Harness의 변화는 다음과 같았다.

Playback Gate

Docs
+
Test
+
Performance Metric
Live Event Stream

Docs
+
Rule
+
Test
+
Experiment
+
Git
ContentOps Agent

Docs
+
Rule
+
Test
+
Evaluation
+
Experiment
+
Git

여기서 중요한 변화는:

코드가 정상인지 확인하는 Harness와 결과 품질을 확인하는 Harness가 분리되기 시작했다.

는 점이었다.


27. Project 4 — LLM Router

네 번째 프로젝트는 LLM Router였다.

이 프로젝트에서는:

Request
↓
Router
↓
Model
↓
Response

구조에서:

Routing Decision

Answer Quality

Latency

Token

Cost

Provider Failure

를 확인하려고 했다.

하지만 이 프로젝트의 가장 큰 회고는 기술이 아니었다.

프로젝트 정의 자체의 문제였다.


28. 처음 블로그와 실제 Repository가 다른 프로젝트를 가리켰다

처음 #1과 #2에서 생각했던 LLM Router는 사실 Multi-LLM Gateway에 가까웠다.

Multi Provider

Streaming

Timeout

Retry

Failover

Circuit Breaker

Rate Limit

Provider Health

같은 문제를 다루려고 했다.

하지만 실제 Repository는:

Request
   ↓
Model 선택
   ↓
Quality / Cost / Latency 비교
   ↓
Routing Decision Evaluation

에 더 가까워졌다.

둘 다 LLM Router라는 이름 안에 들어갈 수는 있었다.

하지만 학습 목표는 꽤 달랐다.


29. 실제 Model 비교 결과도 처음 예상과 달랐다

Direct Model Evaluation은:

7 Case
×
2 Model

로 진행했다.

결과는:

model-small
6 / 7 PASS

model-large
6 / 7 PASS

였다.

Router Evaluation에서도 명확한 정책 위반으로 분류한:

ROUTING_FAILURE

는 없었다.

하지만 비용은 큰 차이가 났다.

model-large Cost
≈
model-small의 13.4배

즉 현재 Dataset에서는:

더 큰 Model
=
더 좋은 결과

라고 말할 근거가 없었다.

그래서:

Reasoning Request
→ 무조건 Large Model

같은 Rule을 바로 넣지 않았다.


30. 429를 Routing Failure로 만들지 않았다

Reasoning Case 일부에서는:

HTTP 429

가 발생했다.

결과만 보면:

Router Evaluation 실패

처럼 보일 수 있었다.

하지만 실제 흐름은:

Request
↓
Routing
↓
Model 선택
↓
Provider 호출
↓
429

였다.

그래서:

Routing Failure

가 아니라:

Provider Failure

로 분리했다.

ContentOps Agent에서:

Retrieval Failure
≠
Generation Failure

를 분리했던 방식이 그대로 확장됐다.


31. Baseline Router가 끝까지 남은 것도 Harness의 결과였다

LLM Router인데 최종 Routing Policy는:

BASELINE_DEFAULT

에 가까운 구조로 남았다.

처음 보면:

Router 프로젝트인데
복잡한 Routing이 없다?

라고 생각할 수 있다.

하지만 현재 Dataset에서는:

small 6 / 7

large 6 / 7

명확한 ROUTING_FAILURE 0

이었다.

따라서:

Semantic Routing

LLM Routing

Cascade

Fallback

을 추가해야 할 강한 근거가 나오지 않았다.

여기서 중요한 표현은:

Semantic Routing이 효과가 없었다.

가 아니다.

현재 Dataset과 Failure Case에서는 Semantic Routing 도입을 정당화할 근거가 충분하지 않았다.

였다.


32. LLM Router의 가장 큰 실패는 기술 미구현이 아니었다

처음 회고할 때는:

Multi Provider를 못 했다.

Streaming을 못 했다.

Retry를 못 했다.

Failover를 못 했다.

Circuit Breaker를 못 했다.

가 가장 큰 부족함처럼 보였다.

하지만 실제로 더 큰 문제는:

Blog #1 / #2
→ Gateway / Resilience

Repository
→ Routing Quality

였다.

즉 프로젝트 안에 성공 기준이 두 개 존재했다.

Repository 안에서는 꽤 일관됐다.

하지만 Repository 밖의 블로그와 Source of Truth가 달랐다.

Harness의 목적은:

AI와 개발자가 같은 프로젝트 상태와 같은 성공 기준을 공유하는 것

인데,
프로젝트 정의 자체의 변경을 Harness가 잡지 못했다.


33. 이 프로젝트에서 Scope 변경도 ADR 대상이 됐다

LLM Router 회고 이후 새로운 규칙이 생겼다.

Blog Goal
=
REQUIREMENTS Goal
=
ROADMAP 방향

을 시작 전에 맞춘다.

그리고 Scope가 바뀐다면:

기존 Scope
↓
변경 이유
↓
새 Scope
↓
포기하는 목표
↓
새 성공 기준
↓
Human Gate
↓
ADR

로 남긴다.

즉 Harness가 관리해야 할 대상이:

Code

Architecture

Evaluation

뿐 아니라:

Project Scope

까지 넓어졌다.


34. LLM Router에서 Failure Simulator의 필요성도 크게 남았다

실제 Provider Failure는 여러 번 관찰했다.

429

503

404

같은 상황이 있었다.

하지만 실제 외부 Provider 상태에 의존했다.

오늘 발생한 429를 내일 같은 조건으로 재현할 수 있다는 보장이 없었다.

Mockito Test Double은:

Error Mapping 경로

를 확인하는 데는 도움이 됐다.

하지만:

FIXED_DELAY

ALWAYS_500

ALWAYS_429

FAIL_N_TIMES_THEN_SUCCESS

같은 통제 가능한 Experiment는 아니었다.

그래서:

Retry 전후 p99

Failover 전후 Success Rate

Circuit Breaker Open 시 Latency

를 같은 조건에서 비교할 수 없었다.

이 교훈은 다음 Cloud Ops Lab의 Failure Scenario 설계에 직접 반영됐다.


35. Project 5 — Cloud Ops Lab

다섯 번째 프로젝트는 Cloud Ops Lab이었다.

처음 목표는:

Application
↓
Container
↓
Kubernetes
↓
Prometheus / Grafana
↓
HPA
↓
AWS

까지 운영 환경을 확장하는 것이었다.

하지만 실제 프로젝트 역시 처음 계획한 기술을 모두 사용하지 않았다.

HPA
→ 미도입

AWS
→ 미진행

KEDA
→ 미사용

Service Mesh
→ 미사용

Argo CD
→ 미사용

Chaos Mesh
→ 미사용

대신 실제 학습의 중심은:

Resource

Probe

Pod Failure

Recovery

Rolling Update

Client 경로

Observability

가 됐다.


36. 이번에는 Failure를 기다리지 않고 직접 만들었다

LLM Router에서 가장 아쉬웠던 것은 통제 가능한 Failure가 없었던 것이다.

Cloud Ops Lab에서는 처음부터 반대로 접근했다.

실제 장애가 생기면 관찰

이 아니라:

Failure Scenario를 직접 만든다.

로 시작했다.

실제로:

Resource Constraint

Readiness Failure

Liveness Failure

Pod Kill

Broken Image Deployment

같은 상태를 반복해서 만들었다.

이번 프로젝트에서 Evaluation Dataset 역할을 한 것은:

Experiment Scenario

였다.


37. Resource Constraint도 예상과 다른 결과가 나왔다

Baseline Resource는:

CPU Request 250m

CPU Limit 500m

Memory 512Mi

였다.

Resource Constraint에서:

CPU 50m

Memory 128Mi

까지 줄였다.

처음 예상은:

Resource 부족
↓
처리 지연
↓
Latency 증가

에 가까웠다.

실제 결과는:

새 Pod Ready 실패

128Mi
↓
OOMKilled

였다.

즉 Resource는 현재 Scenario에서:

Latency 조절 변수

보다:

Application 실행 가능 여부

에 더 직접적인 영향을 줬다.


38. Liveness와 Readiness는 직접 Failure를 만들어 보니 명확했다

Readiness Failure에서는:

Process 유지

Restart 0

이었다.

Liveness Failure에서는:

Restart 1

이 발생했다.

즉:

살아 있는가?

와:

Traffic을 받을 준비가 됐는가?

는 실제로 다른 상태였다.

문서에서 읽으면 단순한 개념이지만
실제 Failure에서 Kubernetes의 동작이 달라지는 것을 확인했다.


39. Pod가 Ready가 된 것과 Client가 복구된 것도 달랐다

Pod Failure에서는:

Replica 1
↓
Pod Kill
↓
Ready Pod = 0
↓
새 Pod 생성
↓
새 Pod Ready ≈ 13초

였다.

그 시점에 Pod 직접 호출은:

200

을 반환했다.

하지만 기존:

port-forward / k6

경로는 계속 실패했다.

즉:

Kubernetes Ready
≠
기존 Client 경로 정상화

였다.

다만 여기에도 Experiment 한계가 있었다.

실제 Client가:

k6
↓
kubectl port-forward
↓
Service

를 사용했기 때문에:

Service Routing Failure

와:

port-forward Failure

가 섞였다.

이번 프로젝트에서 가장 크게 남은 한계이기도 하다.


40. Replica를 늘려도 현재 Load에서는 Latency가 좋아지지 않았다

고정 Replica에서:

Replica 1

Replica 2

Replica 3

을 같은 조건으로 비교했다.

800 RPS에서도:

HTTP Error = 0

이었고,
CPU는 500m Limit에 도달하지 않았다.

Replica 2와 3에서도:

명확한 Latency 이득 없음

이었다.

대신:

Memory 사용 증가

가 더 분명했다.

첫 번째 프로젝트에서:

Pool 증가
≠
처리량 증가

두 번째에서:

Partition 증가
≠
처리량 증가

네 번째에서:

Large Model
≠
더 좋은 Quality

를 봤다면,

다섯 번째에서는:

Replica 증가
≠
낮은 Latency

를 다시 봤다.


41. 그래서 HPA를 사용하지 않았다

HPA는 #1부터 중요한 학습 대상으로 잡았다.

하지만 Harness 원칙은:

고정 Replica 문제 재현
↓
HPA 필요성 확인
↓
HPA 적용

이었다.

현재 Load에서는:

Capacity 한계

를 재현하지 못했다.

이 상태에서 임의의 Threshold를 정해서 HPA를 넣으면:

HPA를 배우려고
HPA가 필요한 문제를 만든 것

에 가까워질 수 있었다.

그래서 Human Gate에서 HPA를 적용하지 않는 쪽을 선택했다.

ADR로 남겼다.

여기서도 표현은:

HPA가 효과가 없었다.

가 아니다.

현재 Scenario에서는 HPA 도입을 정당화할 Capacity 문제를 재현하지 못했기 때문에 실험하지 않았다.

가 정확하다.


42. AWS도 같은 방식으로 내려놓았다

Cloud Ops Lab이라는 이름 때문에 AWS까지 가는 것이 자연스러워 보였다.

실제로 학습 목표에도 AWS가 있었다.

하지만:

Cloud에 배포했다
=
Cloud 운영을 검증했다

라고 쓰고 싶지 않았다.

결국 AWS Validation도 진행하지 않았다.

이 선택 역시 ADR로 남겼다.

그 결과:

AWS 환경에서
Load Balancer 경로와 Ready 상태를 검증했다.

라고는 말할 수 없다.

프로젝트 요구사항 중:

HPA

Scale Out

Scale In

AWS 실행

AWS 관측

은 미충족으로 남았다.

요구사항을 조용히 삭제하지 않았다.


43. 다섯 프로젝트에서 반복해서 틀린 가설

다섯 프로젝트를 한꺼번에 놓고 보면
반복해서 등장한 패턴이 꽤 선명하다.

Playback Gate

예상
Pool 증가 → 처리량 증가

실제
대기는 줄지만 RPS 차이는 작음
예상
Redis Cache

실제
단일 인스턴스라 Caffeine
예상
최종 성능 증가

실제
RPS 감소 + 정확성 확보

Live Event Stream

예상
Partition 증가 → 처리량 증가

실제
P3 C1은 더 악화
예상
Consumer 계속 증가 → 계속 개선

실제
Partition 수 초과 Consumer는 Idle
예상
Poison Event → Blocking

실제
Listener Failure의 핵심은 Event 유실

ContentOps Agent

예상
Exact Keyword가 Vector Search의 큰 실패

실제
Exact Keyword Hit 1.0
예상
Chunk Size 변경 → Retrieval 변화

실제
Metric 동일
예상
Prompt 강화 → 품질 향상

실제
Token / Latency 증가 + 목표 Failure 유지
예상
LangGraph가 Agent 단계의 자연스러운 다음 단계

실제
도입 근거 부족

LLM Router

예상
Large Model → Reasoning에서 더 좋은 결과

실제
small 6/7
large 6/7
예상
복잡한 Routing 필요

실제
명확한 Routing Failure 근거 부족

Cloud Ops Lab

예상
Traffic 증가 → CPU Saturation

실제
현재 Scenario에서 Limit 미도달
예상
Replica 증가 → Latency 감소

실제
명확한 Latency 이득 없음
예상
HPA까지 실험

실제
도입 조건이 만들어지지 않아 미실시

44. 가장 많이 사용한 기술보다 가장 많이 사용하지 않은 기술이 더 중요했다

다섯 프로젝트를 보면 계획에는 있었지만 최종적으로 사용하지 않은 기술이 많다.

Redis Cache

Redis Lock

Redis Deduplication

Retry Topic

Schema Registry

Hybrid Search

RRF

Reranker

LangGraph

Semantic Routing

Cascade

Fallback

HPA

AWS

KEDA

Service Mesh

처음에는:

이 기술을 안 썼으면
학습을 덜 한 것 아닌가?

라고 생각하기 쉽다.

하지만 프로젝트를 반복하면서 생각이 바뀌었다.

현재 문제에서 필요성을 확인하지 못했는데도 기술을 넣는 것은:

학습

이라기보다:

기술 Demo

에 가까워질 수 있다.

다만 반대 방향의 과장도 하면 안 된다.

사용하지 않았다

와:

효과가 없었다

는 다르다.

비교하지 않은 기술의 효과를 판단할 수는 없다.

그래서 다섯 프로젝트에서 반복해서 사용하게 된 표현은:

현재 Dataset / Metric / Failure Case에서는 해당 기술의 도입을 정당화할 근거가 충분하지 않았다.

였다.


45. Baseline은 갈수록 더 중요해졌다

첫 프로젝트에서는 Baseline을 주로 성능 비교 기준으로 생각했다.

Baseline RPS

Baseline p95

Baseline p99

두 번째부터는 문제 재현의 기준이 됐다.

P1 C1

↓

Lag 재현

세 번째에서는 품질 평가의 기준이 됐다.

Baseline Retrieval

↓

동일 Evaluation Dataset

네 번째에서는 Routing Policy의 기준이 됐다.

Baseline Router

↓

Quality / Cost / Latency

다섯 번째에서는 운영 상태의 기준이 됐다.

Fixed Replica

Normal Health

Normal Resource

↓

Failure Scenario

결국 Baseline은:

최종적으로 버릴 임시 코드가 아니라, 이후 모든 주장을 비교할 기준점

이 됐다.


46. Test의 의미도 프로젝트마다 계속 분리됐다

처음에는:

Test PASS

가 상당히 중요한 완료 기준이었다.

하지만 프로젝트가 복잡해지면서 Test가 답할 수 없는 질문이 많아졌다.

Playback Gate

Test PASS
≠
Race Condition 없음

Live Event Stream

Consumer Test PASS
≠
실제 Lag 없음

ContentOps Agent

API Test PASS
≠
정답 문서 Retrieval

LLM Router

Router Test PASS
≠
적절한 Model 선택

Cloud Ops Lab

Deployment 정상
≠
Failure에서 서비스 정상

그래서 Harness 안의 검증도 계속 늘어났다.

Test

Performance Metric

Experiment

Evaluation

Failure Scenario

Recovery Metric

47. Failure는 실패가 아니라 중요한 Artifact가 됐다

처음에는 개발 과정에서 실패한 상태는 빨리 고쳐야 할 것으로 보기 쉽다.

하지만 프로젝트를 반복하면서 오히려:

문제가 재현된 상태

가 중요한 결과가 됐다.

예:

ACTIVE Session = 40

Consumer Lag = 4,252

Duplicate Row 생성

retrieval-010 실패

simple-003 형식 실패

Provider 429

OOMKilled

Pod Failure 후 Client 경로 실패

이런 상태가 있어야:

왜 바꿨는가?

를 설명할 수 있었다.

따라서 Git과 Experiment 문서는:

최종 성공 결과 저장소

가 아니라:

Baseline
↓
Failure
↓
Decision
↓
Change
↓
Result

를 보존하는 역할로 바뀌었다.


48. Failure를 분류하는 방식도 계속 확장됐다

ContentOps Agent부터 실패를 하나의 Black Box로 보지 않기 시작했다.

Retrieval Failure

Ranking Failure

Generation Failure

Grounding Failure

Tool Selection Failure

LLM Router에서는:

Routing Failure

Model Quality Failure

Provider Failure

Cost Inefficiency

Latency Failure

로 확장됐다.

Cloud Ops Lab에서는:

Application 상태

Container 상태

Kubernetes Ready

Client 경로

Resource Failure

Recovery

를 분리하게 됐다.

결국 공통된 방식은:

결과가 나쁘다
↓
바로 해결책 적용

이 아니라:

결과가 나쁘다
↓
어느 Layer에서 실패했는가?

였다.


49. Metric 하나로 시스템을 설명하지 않게 됐다

첫 프로젝트에서 이미:

RPS가 높다
=
좋은 시스템

이 아니라는 것을 봤다.

그 뒤 프로젝트마다 서로 다른 Trade-off가 생겼다.

Playback Gate

RPS
vs
정확성
Live Event Stream

Throughput
vs
Ordering
ContentOps Agent

Quality
vs
Latency / Token
LLM Router

Quality
vs
Cost
vs
Latency
Cloud Ops Lab

Latency
vs
Resource
vs
Recovery

그래서 점점:

종합 Score 하나

보다:

각 Metric을 분리해서 보고
Trade-off를 설명하는 것

을 중요하게 보게 됐다.


50. Human Gate의 역할도 달라졌다

처음에는 Human Gate를:

AI가 작업
↓
개발자 확인

정도로 생각할 수 있었다.

하지만 모든 코드에 승인이 필요하면 AI의 속도 이점이 사라진다.

그래서 Human Gate는 점점:

시스템의 의미가 달라지는 결정

에만 두게 됐다.

예를 들어:

동시성 제어 방식

Kafka Key

Idempotency 전략

Failure Handling

Retrieval 전략

Prompt 정책

Tool 추가

Routing Policy

Model / Provider

HPA 정책

Resource 기본값

AWS Architecture

Project Scope

같은 것이다.

단순 DTO나 Test 구현은 AI에게 맡길 수 있다.

하지만:

무엇을 성공이라고 볼 것인가?

어떤 Trade-off를 받아들일 것인가?

는 개발자가 결정한다.


51. ADR도 기술 기록에서 판단 기록으로 바뀌었다

ADR도 처음에는:

Redis 사용

DB Lock 사용

HPA 사용

같은 Architecture 결과를 기록하기 쉽다.

하지만 프로젝트가 진행될수록 더 중요한 내용은:

어떤 문제가 있었는가?

어떤 Metric에서 확인됐는가?

어떤 후보가 있었는가?

왜 이 후보를 선택했는가?

무엇을 포기했는가?

왜 아무것도 도입하지 않았는가?

가 됐다.

Cloud Ops Lab에서는 실제로:

HPA를 사용하지 않는다.

AWS Validation을 진행하지 않는다.

같은 Decision도 ADR이 됐다.

아무것도 추가하지 않는 것도 Architecture Decision일 수 있었다.


52. Repository가 대화보다 더 중요한 기억이 됐다

프로젝트가 길어질수록 대화 Context만으로는 유지하기 어려운 정보가 많아졌다.

현재 Phase

현재 Architecture

실험 조건

왜 이 기술을 안 썼는지

어떤 Requirement가 아직 미충족인지

현재 Baseline이 무엇인지

그래서 Repository 구조가 점점 고정됐다.

docs/
├── REQUIREMENTS.md
├── DESIGN.md
├── ROADMAP.md
├── TASKS.md
├── adr/
└── experiments/

.cursor/
└── rules/

README.md

프로젝트에 따라:

evaluation/

experiments/scenarios/

scripts/

monitoring/

같은 영역이 추가됐다.

핵심은 파일 개수가 아니었다.

AI와 개발자가 같은 현재 상태를 보고 있는가?

였다.


53. 문서 역할도 프로젝트를 거치며 정리됐다

현재 기준은 거의 이렇게 정리됐다.

REQUIREMENTS
→ 무엇을 만족해야 하는가

DESIGN
→ 현재 실제 구조는 무엇인가

ROADMAP
→ 어떤 문제를 어떤 순서로 확인할 것인가

TASKS
→ 지금 무엇을 할 것인가

RULE
→ 항상 지켜야 하는 제약

EXPERIMENT
→ 어떤 조건에서 어떤 결과가 나왔는가

EVALUATION
→ 품질을 무엇으로 판단하는가

ADR
→ 왜 이 선택을 했는가

README
→ 사람과 AI가 Repository에 어떻게 진입하는가

GIT
→ Baseline부터 Decision까지 어떻게 변했는가

특히 중요한 것은:

DESIGN
≠
미래 계획

이었다.

ROADMAP에 HPA가 있어도 현재 사용하지 않으면 DESIGN에는 넣지 않는다.

ROADMAP에 Semantic Routing이 있어도 실제 사용하지 않으면 현재 Architecture처럼 쓰지 않는다.


54. ROADMAP도 기술 설치 목록에서 문제 확인 순서로 바뀌었다

초기에는 ROADMAP을 만들면 자연스럽게:

Redis 적용

Kafka Scaling

Hybrid Search

LangGraph

Semantic Routing

HPA

AWS

같은 기술 목록이 되기 쉽다.

하지만 이 방식은:

Phase에 도착했다
=
기술을 넣어야 한다

로 읽힌다.

점점 ROADMAP을:

동시성 문제 재현

Lag 원인 확인

Exact Keyword 실패 확인

Routing Failure 확인

Capacity 한계 확인

처럼 문제 확인 순서로 작성하게 됐다.

이 변화는 꽤 중요했다.

기술을 사용하지 않고 Phase를 닫는 것이 가능해졌기 때문이다.


55. 다섯 번째 프로젝트에서 새로 보인 문제 — 학습 대상과 도입 조건

Cloud Ops Lab에서는 처음부터:

HPA를 학습한다.

AWS까지 확장한다.

고 적었다.

동시에 Harness에는:

문제가 확인되기 전에 기술을 넣지 않는다.

는 원칙이 있었다.

실제 결과에서는 후자를 선택했다.

Capacity 문제 미재현
↓
HPA 미도입

이 선택은 Harness와 일관됐다.

하지만 문서에는 긴장이 남았다.

학습 대상

과:

현재 프로젝트에 실제 도입해야 하는 기술

을 같은 문장으로 적었기 때문이다.

다음부터는 이렇게 나누는 편이 더 명확할 것 같다.

학습 대상

HPA Scale Out / Scale In


도입 조건

고정 Replica의 Capacity 한계가
Metric으로 재현된 경우


조건 미충족

HPA를 사용하지 않고
미실시 결과를 남길 수 있음

이것이 Cloud Ops Lab에서 다음 프로젝트로 넘어가는 가장 큰 문서 규칙이다.


56. Harness의 진화 과정을 다시 정리하면

다섯 프로젝트를 통해 Harness는 다음처럼 변했다.

Playback Gate

Docs
+
Test
+
Performance Metric

Live Event Stream

Docs
+
Rule
+
Test
+
Experiment
+
Git

ContentOps Agent

Docs
+
Rule
+
Test
+
Evaluation Dataset
+
Experiment
+
Git

LLM Router

Docs
+
Rule
+
Test
+
Evaluation
+
Quality / Cost / Latency
+
Failure Layer
+
Experiment
+
ADR
+
Git

그리고 회고에서:

Blog Goal

Scope

README 시점

ADR 시점

도 Harness의 문제라는 것을 확인했다.

Cloud Ops Lab

Blog Goal

REQUIREMENTS

ROADMAP

Rule

Test

Experiment Scenario

Metric

Failure Injection

Recovery

Human Gate

ADR

Git

README

까지 넓어졌다.


57. 현재의 개발 Loop

다섯 프로젝트를 지나면서 지금 가장 자연스럽게 남은 Loop는 다음과 같다.

BLOG GOAL
      ↓
REQUIREMENTS
      ↓
ROADMAP
      ↓
TASK
      ↓
PLAN
      ↓
IMPLEMENT
      ↓
TEST
      ↓
MEASURE / EVALUATE
      ↓
FAILURE CASE
      ↓
ANALYZE
      ↓
후보 비교
      ↓
HUMAN GATE
      ↓
DECIDE
      ↓
ADR
      ↓
IMPLEMENT
      ↓
RE-MEASURE / RE-EVALUATE
      ↓
DESIGN UPDATE
      ↓
COMMIT
      ↓
NEXT TASK

모든 프로젝트에서 모든 단계를 기계적으로 사용하는 것은 아니다.

하지만 중요한 순서는 유지한다.

문제가 먼저

측정이 그 다음

기술 선택은 그 이후

다.


58. AI에게 맡기는 영역도 더 명확해졌다

다섯 프로젝트를 거치면서 AI Coding Assistant에게 맡기는 범위도 넓어졌다.

코드 구현

Test 작성

Manifest 작성

Script 작성

Metric Query 작성

Evaluation Runner 작성

실험 결과 초안 정리

README 초안

ADR 초안

문서 동기화

같은 일은 적극적으로 맡길 수 있다.

반대로 개발자가 계속 통제하는 영역은 더 명확해졌다.

무엇을 만들 것인가

현재 문제가 무엇인가

무엇을 성공이라고 볼 것인가

어떤 Metric을 믿을 것인가

어떤 Trade-off를 받아들일 것인가

어떤 기술을 도입할 것인가

Scope를 바꿀 것인가

어떤 Requirement를 내려놓을 것인가

즉 AI를 덜 사용하는 방향으로 간 것이 아니다.

오히려 더 많이 사용하면서:

AI가 판단하지 말아야 하는 영역을 더 명확하게 만든 것

에 가깝다.


59. 모든 Human Gate를 없애는 것이 목표는 아니었다

프로젝트가 진행되면서:

Human Gate가 많으면 느리다.

는 것도 분명했다.

그래서 모든 변경에서 승인을 받지는 않는다.

목표는:

Human Gate 0

가 아니다.

반복 가능하고 기준이 명확한 작업
→ AI / Script에 위임

시스템의 의미가 바뀌는 결정
→ Human Gate

로 나누는 것이다.

즉:

불필요한 Human Gate는 줄이고, 남아 있는 Human Gate에는 판단에 필요한 Metric과 후보를 충분히 제공한다.

가 현재 방향이다.


60. Script가 Agent보다 더 유용한 경우가 많았다

프로젝트를 시작할 때마다 AI와 Agent라는 주제 때문에 복잡한 자동화를 생각하기 쉽다.

하지만 실제로 반복해서 유용했던 것은:

Load Script

Experiment Runner

Evaluation Runner

Metric Collector

Failure Injection Script

같은 단순한 자동화였다.

Live Event Stream에서도:

measure.sh

lag experiment

같은 Script가 유용했다.

Cloud Ops Lab에서는:

run-baseline.sh

run-increasing-load.sh

run-replica-comparison.sh

run-resource-constraint.sh

run-readiness-failure.sh

run-liveness-failure.sh

run-pod-failure.sh

collect-metrics.sh

같은 Script가 생겼다.

따라서 현재 자동화 우선순위는 대략:

단순 코드
↓
Script
↓
Test / Evaluation Runner
↓
Human-controlled Loop
↓
필요하면 Agent

가 됐다.


61. Skill은 아직 가장 덜 해결된 부분이다

Rule과 Skill의 구분은 정리됐다.

Rule
→ 항상 지켜야 하는 제약

Skill
→ 반복되는 특정 Workflow

하지만 실제로 반복 Workflow를 Skill로 잘 추출했는지는 별개의 문제다.

ContentOps Agent에서는:

Evaluation
↓
Failure 분류
↓
Metric 정리
↓
Experiment Markdown

이 여러 번 반복됐다.

LLM Router에서도 비슷한 일이 있었다.

Cloud Ops Lab에서는 Script는 많이 생겼지만
Skill 추출 여부를 명시적으로 검토하는 Task가 없었다.

그래서 앞으로는 반복이 세 번째 정도가 되면:

이 Workflow를 Skill로 분리하면
실제로 Context와 작업량이 줄어드는가?

를 확인하는 단계를 넣는 것이 좋을 것 같다.

만들지 않는 결론도 괜찮다.

중요한 것은:

검토 후 안 만듦

과:

검토를 잊음

을 구분하는 것이다.


62. README도 뒤늦게 중요성을 알게 됐다

초기 프로젝트에서는 README를 마지막 소개 문서처럼 다뤘다.

하지만 프로젝트가 복잡해질수록:

어떻게 실행하지?

어떻게 Test하지?

어떻게 Evaluation하지?

현재 Limit은 뭐지?

를 찾는 진입점이 필요했다.

LLM Router 회고에서는 README를 후반에 많이 채운 것이 아쉬움으로 남았다.

Cloud Ops Lab에서는 Baseline 완료 조건에 README를 포함했다.

Baseline 완료
=
README를 보고
기본 환경과 실험을 재현할 수 있음

이 방향은 꽤 잘 작동했다.

README도 이제:

프로젝트 홍보 페이지

보다:

Harness의 Entry Point

에 가깝게 보고 있다.


63. 가장 큰 변화는 성공 기준을 기술에서 설명 가능성으로 옮긴 것이다

첫 프로젝트를 시작할 때는 성공 결과를 기술적으로 생각하기 쉬웠다.

Redis 적용

Kafka 확장

Hybrid Search 적용

LangGraph 적용

Semantic Routing 적용

HPA 적용

AWS 배포

다섯 프로젝트를 지난 지금은 성공 기준이 조금 다르다.

Playback Gate

왜 DB Lock을 선택했고
정확성과 성능이 어떻게 달라졌는가?

Live Event Stream

왜 P3 C3를 선택했고
Partition과 Consumer 수가 Lag에 어떤 영향을 줬는가?

ContentOps Agent

어떤 Query에서 어떤 Layer가 실패했고
왜 Hybrid / Reranker / LangGraph를 넣지 않았는가?

LLM Router

왜 Baseline Routing을 유지했고
Quality / Cost / Latency 결과가 무엇이었는가?

Cloud Ops Lab

어떤 Failure에서 Kubernetes와 Client 상태가 달랐고
왜 HPA와 AWS를 진행하지 않았는가?

즉:

무엇을 사용했는지보다 왜 그렇게 됐는지를 실제 결과로 설명할 수 있는가

가 더 중요한 기준이 됐다.


64. 완료하지 못한 학습 목표를 숨기지 않는 것도 프로젝트 결과다

다섯 프로젝트 모두 처음 계획한 범위를 100% 수행한 것은 아니다.

특히 LLM Router와 Cloud Ops Lab은 명확하다.

LLM Router

Multi Provider
미완료

Streaming
미완료

Retry / Failover
미완료

Circuit Breaker
미완료

Cloud Ops Lab

HPA
미실시

Scale Out / Scale In
미실시

AWS
미실시

이 부분을:

어차피 필요 없었다.

고 바꾸면 회고가 아니라 결과에 맞춰 목표를 다시 쓰는 것이 된다.

따라서:

현재 프로젝트에서 설명 가능한 것

vs

이번 프로젝트로는 설명할 수 없는 것

을 나누는 방식이 더 맞다고 생각하게 됐다.


65. 다섯 프로젝트에서 가장 크게 바뀐 개발자 역할

처음에는 AI Coding Assistant를 쓰면 개발자의 역할이:

코드를 직접 많이 작성하는 사람

에서:

AI가 작성한 코드를 검토하는 사람

으로 바뀐다고 생각하기 쉽다.

다섯 프로젝트를 진행하면서 조금 다르게 느꼈다.

더 중요해지는 역할은:

문제를 구조화하는 사람

측정 방법을 정하는 사람

실패 위치를 분리하는 사람

후보를 비교하는 사람

Trade-off를 결정하는 사람

성공 기준을 지키는 사람

Scope 변경을 통제하는 사람

에 가까웠다.

코드 작성 비중이 줄어들 수는 있다.

하지만 판단해야 할 것은 오히려 더 많아질 수 있다.


66. AI와 개발하면서 가장 경계하게 된 문장

다섯 프로젝트를 하면서 점점 쓰지 않게 된 표현들이 있다.

성능을 개선했다.

안정성을 높였다.

검색 품질을 개선했다.

Routing을 최적화했다.

장애 대응을 강화했다.

이런 문장은 너무 쉽게 쓸 수 있다.

이제는 뒤에 다음 질문이 따라온다.

어떤 Metric에서?

같은 조건으로 비교했나?

무엇이 실패했었나?

어떤 Layer의 문제였나?

무엇이 좋아졌나?

무엇은 나빠졌나?

비교하지 않은 기술은 무엇인가?

남은 한계는 무엇인가?

이 질문에 답할 수 없으면 표현을 낮추는 편이 맞다.


67. 지금 남아 있는 핵심 원칙

다섯 프로젝트를 지나면서 가장 오래 살아남은 원칙은 결국 몇 개 안 된다.

기술을 사용했다
≠
문제를 해결했다
코드가 동작한다
≠
품질이 좋다
변경했다
≠
개선됐다
더 많은 Resource
≠
더 좋은 결과
Phase에 도달했다
≠
기술을 반드시 사용해야 한다
사용하지 않았다
≠
효과가 없었다
Infrastructure가 복구됐다
≠
서비스가 복구됐다
처음 계획했다
≠
끝까지 그 계획이 정답이다

그리고 하나가 더 추가됐다.

학습 대상으로 적었다
≠
조건 없이 반드시 구현해야 한다

68. 다음 프로젝트에서 바꾸고 싶은 것

다섯 프로젝트를 끝내고 다음 프로젝트에서 조금 더 명확하게 가져가고 싶은 것은 다음과 같다.

1. Blog Goal과 Repository Goal을 처음부터 맞춘다

Blog Goal
=
REQUIREMENTS
=
ROADMAP

다르면 개발을 시작하지 않는다.

2. 학습 대상과 도입 조건을 분리한다

배우고 싶은 기술

vs

현재 프로젝트에서
실제로 적용할 조건

을 따로 쓴다.

3. Scope 변경은 ADR로 남긴다

기존 Scope
↓
변경 이유
↓
새 Scope
↓
포기한 목표
↓
새 성공 기준

을 기록한다.

4. README는 Baseline부터 완성한다

Baseline Commit
=
README로 실행 / 검증 가능

을 완료 조건으로 둔다.

5. Human Gate 직후 ADR을 작성한다

결정이 끝난 뒤 마지막에 복원하지 않는다.

6. 통제 가능한 Failure를 먼저 만든다

외부 장애를 기다리지 않는다.

7. 반복 Workflow는 세 번째 정도에 Skill 후보로 검토한다

무조건 Skill로 만들지는 않는다.

판단 자체를 남긴다.

8. Experiment Client 경로도 Scenario의 일부로 본다

Cloud Ops Lab에서:

port-forward

가 결과 해석에 영향을 줬다.

앞으로는:

Client Path

도 명시적인 Experiment 조건으로 넣는 편이 좋다.


69. 그래서 다섯 프로젝트는 무엇을 만든 프로젝트였나?

기술만 놓고 보면:

Playback API

Kafka Event Pipeline

RAG Agent

LLM Router

Kubernetes Lab

을 만들었다.

하지만 전체를 하나로 놓고 보면 조금 다른 결과가 보인다.

첫 프로젝트에서는:

측정 가능한 Baseline

을 만들기 시작했다.

두 번째에서는:

Failure와 Experiment History

를 남기기 시작했다.

세 번째에서는:

Evaluation Dataset

으로 품질을 검증하기 시작했다.

네 번째에서는:

프로젝트 Goal과 Scope 자체

도 Harness의 대상이라는 것을 배웠다.

다섯 번째에서는:

운영 환경의 Failure와 Recovery

도 반복 가능한 Scenario로 만들었다.

즉 실제로 만든 것은:

AI Coding Assistant를 사용하면서도 개발자가 문제 정의와 판단, 검증 기준을 계속 통제할 수 있는 개발 방식

에 더 가깝다.


70. 정리

Playback Gate를 시작할 때는:

AI와 개발하면서
성능 프로젝트 하나를 잘 만들어보자.

정도였다.

다섯 프로젝트가 끝난 지금은 개발 흐름이 꽤 달라졌다.

BLOG GOAL
      ↓
REQUIREMENTS
      ↓
ROADMAP
      ↓
TASK
      ↓
IMPLEMENT
      ↓
TEST
      ↓
MEASURE / EVALUATE
      ↓
FAILURE
      ↓
ANALYZE
      ↓
후보 비교
      ↓
HUMAN GATE
      ↓
DECISION
      ↓
ADR
      ↓
IMPLEMENT
      ↓
RE-MEASURE
      ↓
DESIGN
      ↓
COMMIT

이 흐름이 모든 프로젝트에 완벽하게 적용된 것은 아니다.

실제로 계속 놓친 것도 있었다.

문제 / 해결 Commit 분리

Skill 추출

README 작성 시점

ADR 작성 시점

Scope 변경 기록

통제 가능한 Failure

Client 경로 정의

정책 변경 후 Re-Experiment

하지만 프로젝트가 하나씩 끝날 때마다 그 문제를 다음 프로젝트 Harness에 넣었다.

그래서 이번 다섯 프로젝트의 가장 큰 결과를 하나만 고르면:

더 많은 기술을 사용할 수 있게 됐다.

보다는:

어떤 기술을 언제 사용해야 하고, 언제 사용하지 않아야 하며, 그 판단을 어떤 결과로 설명해야 하는지 조금 더 명확하게 알게 됐다.

에 가깝다.

AI Coding Assistant는 계속 더 많은 코드를 더 빠르게 작성할 수 있다.

그럴수록 개발자가 해야 할 일은 단순히 AI가 만든 코드를 읽는 것만은 아닌 것 같다.

무엇을 만들 것인가

어떤 문제를 해결할 것인가

무엇을 측정할 것인가

무엇을 성공이라고 부를 것인가

어떤 Trade-off를 받아들일 것인가

어떤 기술을 사용하지 않을 것인가

어떤 목표를 끝내지 못했는가

까지 통제해야 한다.

결국 다섯 프로젝트를 지나면서 가장 오래 남은 문장은 처음과 크게 다르지 않다.

AI에게 구현 속도는 맡길 수 있다.

하지만 그 뒤에 붙는 문장은 조금 더 길어졌다.

무엇을 배우려고 하는지, 어떤 문제를 해결하려는지, 무엇을 성공이라고 판단할지, 어떤 기술을 선택하거나 선택하지 않을지, 그리고 그 기준이 중간에 바뀌었는지까지는 개발자가 통제해야 한다.

지금까지의 다섯 프로젝트는 그 방법을 조금씩 만들어가는 과정이었다.

반응형