와트니
경력근거: 서비스에서 발생하는 이슈에 스스로 판단하고 대응할 수 있는 독립적인 개발자
공고는 개발자 세 명이 수십만 이용자를 운영하는 구조를 함께 만들 사람을 찾고, 이슈에 스스로 판단해 대응하는 독립성을 요구하며, AI 가 잘 일할 환경 자체를 설계하라고 적습니다. 이 포트폴리오는 그 운영 부담을 줄이는 자리를 골랐습니다. 오류를 사람이 읽을 묶음과 도구가 읽을 형식으로 함께 내어, 다음 단계에서 에이전트가 이어받을 수 있게 둡니다. 다만 이 포트폴리오는 묶음 규칙과 화면 구현을 보여줍니다. 대규모 트래픽 운영이나 장애 대응 체계에 대한 전문성은 주장하지 않습니다. 모르는 깊이는 면접에서 모른다고 말하는 편이 낫습니다.
개발자 세 명이 수십만 이용자를 운영하고 있고 그 구조를 함께 만들고 확장할 사람을 찾는다고 회사 소개에 적습니다. 사람 손이 적은 만큼 무엇부터 볼지 정해 주는 도구가 곧 생산성입니다.
서비스에서 발생하는 이슈에 스스로 판단하고 대응할 수 있는 독립적인 개발자여야 한다고 역할 설명에 적습니다. 알림을 받는 사람이 곧 고치는 사람인 자리입니다.
AI 도구를 잘 쓰는 사람이 아니라 AI 가 잘 일할 수 있는 환경 자체를 설계하는 사람을 찾는다고 못박습니다. 사람이 읽을 목록만이 아니라 도구가 읽을 형식도 함께 만들어야 합니다.
당번 개발자가 아침에 화면을 열면, 밤사이 쌓인 오류를 메시지 모양과 발생 지점으로 묶어 영향 범위가 큰 묶음부터 보여줍니다. 어느 묶음인지는 메시지 정규화 규칙이 정하고, 모델은 묶음마다 대표 메시지를 골라 무슨 오류인지 간추립니다.
저는 캐릭터 채팅 앱을 운영하는 세 명 중 한 명입니다. 아침에 오류 알림이 수백 건 쌓여 있는데 대부분 같은 문제의 반복이라, 어디부터 봐야 할지 고르는 데만 시간이 갑니다. 급한 것을 놓치고 사소한 것부터 손댄 적도 있습니다.
대응이 늦는 이유는 오류가 많아서가 아니라 같은 원인이 흩어져 있어 크기가 안 보이기 때문입니다. 메시지에서 변하는 값을 지워 묶고 영향받은 이용자 수를 세어 주면, 오늘 하나만 고쳐도 되는 자리가 정해집니다.
당번 개발자가 쓰는 화면 하나가 완결됩니다. 오류 로그를 메시지 모양으로 묶어 영향받은 이용자 수와 발생 지점을 같이 보여주고, 묶음마다 대표 사례를 답니다. 실제 수집기 연동은 로그 파일을 올리는 것으로 대신하고 3주차로 미룹니다.
처리 완료로 표시한 묶음 90건이 다음 날 다시 나타났는지 맞춰 보고, 다시 나타난 묶음을 유형별로 냅니다. 임시 조치와 실제 수정이 그 표에서 갈립니다.
로그를 받아 계속 돌고, 전에 없던 묶음이 기준을 넘게 생기면 당번에게 알립니다. 알림에는 대표 사례와 영향 범위가 함께 담깁니다.
당번 개발자가 아침에 화면을 열면 오류가 원인별로 묶이고, 영향받은 이용자 수와 대표 사례가 같이 보입니다.
메시지가 비거나 발생 지점이 없거나 분류에 없는 화면 이름이 섞인 로그 30종을 올려도 화면이 죽지 않고, 묶을 수 없는 로그는 미분류로 남습니다.
처리 완료 묶음 90건이 다음 날 다시 나타났는지 맞춰 봅니다. 단, 정답과 데이터를 모두 작성자가 만든 합성 기준이라 성능 검증이 아니라 데모 조건 동작입니다.
위 수치는 더미데이터 자가채점이며 실제 대응 시간 단축은 와트니의 실 오류 로그와 운영 기록이 있어야 측정됩니다. 이 포트폴리오는 그 검증을 바로 돌릴 수 있는 상태로 만듭니다.
수치는 합성 데이터 자가채점이라 성능 증명이 아닙니다. 면접에서는 데모 동작과 실검증 준비 상태로 솔직히 설명합니다.
정답을 작성자가 만들었으니 잘 맞는 것이 당연합니다. 메시지 결측·발생 지점 누락·분류에 없는 화면 이름을 더미에 일부러 섞어 쉬운 문제만 푸는 함정을 피합니다.
묶음이 크다는 사실이 곧 급하다는 뜻은 아닙니다. 재시도로 저절로 풀리는 오류도 크게 잡히므로 판정을 결론으로 읽지 않게 대표 사례와 근거 값을 항상 같이 보여줍니다.
실제 도입에서는 라이브러리마다 메시지 형식이 달라 지문이 흔들립니다. 패턴이 늘어나면 규칙을 코드에서 빼 설정으로 옮겨야 하며, 그것은 도입 단계 과제로 분리합니다.
오류를 자동으로 고치거나 배포를 되돌리는 일은 운영 권한이 걸려 있어 이 포트폴리오의 범위 밖입니다. 여기서는 먼저 볼 것을 정하는 데까지만 다룹니다.
① 인라인 샘플 · ② seed.json 다운로드 · ③ 증강 프롬프트. 세 층 모두 동일한 schema 에서 파생.
밤사이 쌓인 오류 로그입니다. 묶음의 입력이며 메시지 결측과 발생 지점 누락, 분류에 없는 화면 이름을 의도적으로 포함합니다.
| log_no | occurred_at | platform | screen_raw | message | status_code | user_key | app_ver | retryable |
|---|---|---|---|---|---|---|---|---|
| EL-0001 | 2026-08-18T02:11:00 | ios | chat_room | request timeout after 30000ms (session… | 504 | U-1001 | 2.4.1 | true |
| EL-0002 | 2026-08-18T02:14:00 | android | chat_room | request timeout after 30000ms (session… | 504 | U-1002 | 2.4.1 | true |
| EL-0003 | 2026-08-18T02:20:00 | ios | chat_room | request timeout after 30000ms (session… | 504 | U-1003 | 2.4.1 | true |
| EL-0004 | 2026-08-18T03:02:00 | web | character_list | cannot read property 'name' of undefined | — | U-1004 | 2.4.0 | false |
| EL-0005 | 2026-08-18T03:40:00 | web | character_list | cannot read property 'name' of undefined | — | U-1005 | 2.4.0 | false |
아래 JSON Schema 에 맞는 오류 로그 행을 더 만들어 주세요. {"type":"object","properties":{"log_no":{"type":"string"},"occurred_at":{"type":"string"},"platform":{"type":"string","enum":["ios","android","web"]},"screen_raw":{"type":["string","null"]},"message":{"type":["string","null"]},"status_code":{"type":["integer","null"]},"user_key":{"type":["string","null"]},"app_ver":{"type":"string"},"retryable":{"type":["boolean","null"]}},"required":["log_no","occurred_at","platform","app_ver"]} 쉬운 데이터만 만들지 말 것 — 다음 하드 케이스를 의도적으로 섞어라: (1) message 나 screen_raw 가 null 인 행, (2) 같은 메시지에서 세션 값만 다른 행 여러 건, (3) user_key 가 null 이라 영향 범위를 셀 수 없는 행, (4) 화면 분류에 없는 screen_raw. 생성한 행은 반드시 위 스키마를 통과해야 하며, 형식을 깨뜨리는 시도는 별도로 3건만 표시해 두세요.{"type":"object","properties":{"log_no":{"type":"string"},"occurred_at":{"type":"string"},"platform":{"type":"string","enum":["ios","android","web"]},"screen_raw":{"type":["string","null"]},"message":{"type":["string","null"]},"status_code":{"type":["integer","null"]},"user_key":{"type":["string","null"]},"app_ver":{"type":"string"},"retryable":{"type":["boolean","null"]}},"required":["log_no","occurred_at","platform","app_ver"]}| TypeScript | 묶음 규칙과 화면을 함께 쓰는 언어입니다. 메시지 정규화 규칙을 한 곳에 두고 러너와 화면이 같은 코드를 씁니다. |
| SvelteKit | 당번 개발자가 보는 화면입니다. 묶음 목록과 대표 사례를 한 화면에 둡니다. |
| PostgreSQL | 오류 로그와 묶음 결과를 담습니다. 원문 메시지와 정규화 지문을 분리한 스키마가 묶음의 정확도를 좌우합니다. |
| Expo | 앱에서 올라온 오류도 같은 형식으로 받도록 필드 이름을 맞춥니다. |
| LLM | 묶음이 정해진 뒤 대표 메시지와 영향 이용자 수를 한 문장으로 간추립니다. 어느 오류가 어느 묶음인지는 메시지 정규화 규칙이 정합니다. |
✓ pass → 그대로 멈춰도 완결입니다. 더 키우려면 고친 묶음이 다음 날 줄었는지 확인하는 단계로 넘어갑니다.
✓ pass → 여기까지도 완결입니다. 더 키우려면 새 묶음이 생길 때 알리는 단계로 확장합니다.
위 예측 UI 는 3주차까지 완주했을 때의 화면입니다. 1주차에 실제로 만드는 것은 아래 표시된 화면 하나입니다.
group_code사유 라벨 · sample_log_no + 한 줄 요약(근거 수치 포함)화면·코드·면접에서 이 단어를 그대로 씁니다. 키는 출력 스키마의 enum 과 같습니다.
upstream_timeout응답 대기 초과client_exception클라이언트 예외quota_exceeded호출 한도 초과input_rejected입력 거절unclassified미분류record_missing기록 결측묶인 오류 한 무리의 판정입니다. 묶음 코드와 대표 사례, 영향 범위를 함께 남깁니다.
{"type":"object","properties":{"group_id":{"type":"string"},"sample_log_no":{"type":"string"},"group_code":{"type":"string","enum":["upstream_timeout","client_exception","quota_exceeded","input_rejected","unclassified","record_missing"]},"affected_users":{"type":["integer","null"]},"evidence":{"type":"array","items":{"type":"object","properties":{"field":{"type":"string"},"agg":{"type":["string","null"]},"value":{"type":["string","number","null"]}},"required":["field","value"]}},"summary":{"type":"string"},"hedge":{"type":["string","null"]}},"required":["group_id","sample_log_no","group_code","evidence","summary"]}[
{
"group_id": "eg_0001",
"sample_log_no": "EL-0001",
"group_code": "upstream_timeout",
"affected_users": 4,
"evidence": [
{
"field": "status_code",
"agg": null,
"value": 504
},
{
"field": "user_key",
"agg": "count_distinct",
"value": 4
}
],
"summary": "채팅 화면에서 응답 대기 초과가 네 명에게 반복돼 가장 먼저 볼 묶음입니다.",
"hedge": null
},
{
"group_id": "eg_0002",
"sample_log_no": "EL-0004",
"group_code": "client_exception",
"affected_users": 2,
"evidence": [
{
"field": "message",
"agg": null,
"value": "cannot read property 'name' of undefined"
},
{
"field": "app_ver",
"agg": null,
"value": "2.4.0"
}
],
"summary": "구버전 웹에서 캐릭터 목록이 비어 있을 때 화면이 깨집니다.",
"hedge": null
},
{
"group_id": "eg_0003",
"sample_log_no": "EL-0009",
"group_code": "quota_exceeded",
"affected_users": 2,
"evidence": [
{
"field": "status_code",
"agg": null,
"value": 429
},
{
"field": "message",
"agg": null,
"value": "model quota exceeded for tenant 41"
}
],
"summary": "같은 테넌트에서 모델 호출 한도를 넘겨 두 명이 답을 못 받았습니다.",
"hedge": "합성 기준 판정이라 실제로는 한도 설정이 이미 조정됐을 수 있습니다."
},
{
"group_id": "eg_0004",
"sample_log_no": "EL-0011",
"group_code": "input_rejected",
"affected_users": 1,
"evidence": [
{
"field": "status_code",
"agg": null,
"value": 400
},
{
"field": "screen_raw",
"agg": null,
"value": "settings"
}
],
"summary": "설정 저장이 거절돼 한 명이 값을 못 바꿨습니다.",
"hedge": null
},
{
"group_id": "eg_0005",
"sample_log_no": "EL-0008",
"group_code": "unclassified",
"affected_users": 1,
"evidence": [
{
"field": "screen_raw",
"agg": null,
"value": "새화면_실험"
},
{
"field": "app_ver",
"agg": null,
"value": "2.5.0"
}
],
"summary": "화면 분류에 없는 실험 화면이라 어느 묶음에도 넣지 못했습니다.",
"hedge": null
},
{
"group_id": "eg_0006",
"sample_log_no": "EL-0006",
"group_code": "record_missing",
"affected_users": null,
"evidence": [
{
"field": "screen_raw",
"agg": null,
"value": null
},
{
"field": "user_key",
"agg": null,
"value": null
}
],
"summary": "화면과 이용자 정보가 모두 없어 묶지도 세지도 못합니다.",
"hedge": null
},
{
"group_id": "eg_0007",
"sample_log_no": "EL-0007",
"group_code": "record_missing",
"affected_users": 1,
"evidence": [
{
"field": "message",
"agg": null,
"value": null
},
{
"field": "status_code",
"agg": null,
"value": 500
}
],
"summary": "결제 화면에서 상태 코드만 남고 메시지가 없어 원인을 좁히지 못합니다.",
"hedge": null
}
][자동 검증 · 코드가 판정합니다] 1. group_code 가 enum 여섯 값 중 하나인지 확인합니다. 2. group_id·sample_log_no·group_code·evidence·summary 가 모두 채워졌는지 확인합니다. 3. affected_users 가 0 이상인지 확인합니다. 4. evidence 의 field 이름이 오류 로그에 실재하는 컬럼인지 대조합니다. 5. agg 가 없는 evidence 의 value 가 그 sample_log_no 행의 값과 같은지 확인합니다. 6. message 가 null 이면 group_code 는 record_missing 인지 확인합니다. 로그에는 메시지가 있는 행과 없는 행이 모두 있습니다. 7. summary 가 존댓말로 끝나는지 확인합니다. [평가 러너 · 2주차] 처리 완료로 표시한 묶음 90건이 다음 날 다시 나타났는지 비교해 재발률과 다시 나타난 유형을 냅니다. [수동 검증 · 사람이 봅니다] 1. 재시도로 저절로 풀리는 오류를 상위에 둘지 표본 20건을 눈으로 확인합니다. 2. 이용자 정보가 없는 로그를 영향 범위 계산에서 어떻게 다룰지는 운영 기준을 따라 정합니다.