NGRAS
완료자연어로 네트워크 인증 시험을 지시하고 진행 상황을 실시간으로 확인하는 시험 자동화 콘솔
JWT 인증 SSE 스트리밍
표준 EventSource는 인증 헤더를 실을 수 없어, fetch + ReadableStream으로 SSE를 직접 파싱하고 끊기면 자동 재연결하도록 구현.
fetch + ReadableStreamReact 19 다중 스토어 Tearing 해결
두 Zustand 스토어를 같은 동기 블록에서 연속 업데이트하면 useSyncExternalStore가 tearing을 감지해 무한 리렌더에 빠짐. 두 번째 업데이트를 await 경계 뒤로 옮겨 해결.
useSyncExternalStore동시 401 → 토큰 갱신 1회로 단일화
여러 요청이 동시에 401을 받아도, 이미 진행 중인 토큰 갱신 요청을 재사용해 중복 갱신을 막음.
요청 큐잉Overview
네트워크 장비 인증 시험은 정보 수집 → 시험 계획 → 프리셋 → 작업 실행 → 리포트 생성 → 완료까지 여러 단계를 거치는데, 이 과정이 대부분 수동이었다. NGRAS는 이 워크플로우를 자연어로 지시하고 AI가 시험 계획을 세워 실행하며, 진행 상황을 실시간으로 볼 수 있는 콘솔이다. SSAFY 자율 프로젝트(기업연계)로 실제 하드웨어 사업부와 연계해 현업 멘토 피드백을 받으며 진행했고(보안망 내부 개발이라 외부 AI·협업 도구 사용에 제약이 있었다), 6명이 참여해 프로젝트 1위·전국 113개 팀 중 전시발표회 1위를 수상했다.
핵심 기능
- 자연어 시험 실행: 자연어로 시험 목적을 입력하면 AI가 시험 계획(plan)을 세운다.
- AI 결과 실시간 스트리밍: 시험 계획과 결과 리포트가 토큰 단위로 실시간 스트리밍되어, 전체 응답을 기다리지 않고 바로 보인다.
- 6단계 인증 시험 워크플로우: 정보 수집 → 시험 계획 → 프리셋 → 작업 실행 → 리포트 생성 → 완료까지 탭 기반으로 이어지는 흐름을 구현했다.
- 실시간 작업 모니터링: 시험이 실행되는 동안 진행률과 상태를 실시간으로 확인할 수 있다.
내 역할
디자인, 프론트엔드 및 백엔드 개발을 맡았다. 이 글은 그중에서도 가장 깊게 파고들었던 프론트엔드 문제 세 가지 — SSE 스트리밍, 리렌더링 버그, 토큰 갱신 race condition — 위주로 정리한다. 백엔드(FastAPI·Spring Boot) 쪽은 팀 공통 작업으로 함께 구현했다.
기술 선택 이유
- fetch + ReadableStream (EventSource 대신): 표준
EventSource는 커스텀Authorization헤더를 실을 수 없어서, JWT 인증이 필요한 이 프로젝트에서는 애초에 쓸 수 없었다.fetch로 스트림을 열고TextDecoder로 청크 단위로 디코딩하며 SSE 이벤트를 직접 파싱해, 인증 헤더 문제와 스트리밍 요구사항을 동시에 해결했다. 연결이 비정상 종료되면 일정 시간 뒤 자동으로 재연결하도록 처리했다. - 요청 큐잉 (토큰 갱신 중복 방지): 여러 API가 동시에 폴링하다 보니 토큰이 만료된 순간 여러 요청이 한꺼번에 401을 받는 상황이 생겼다. 요청마다 개별적으로 토큰을 갱신하면 갱신 API가 중복 호출되므로, 이미 진행 중인 갱신 요청이 있으면 그 결과를 기다렸다가 재사용하는 방식으로 갱신을 1회로 묶었다.
- await 경계로 스토어 업데이트 분리 (React 19 tearing 대응): 서로 다른 Zustand 스토어 두 개를 같은 동기 블록에서 연달아 업데이트하면, React 19의
useSyncExternalStore가 두 스토어 스냅샷 사이의 일관성이 깨졌다고 판단해(tearing) 계속 재렌더를 시도하다Maximum update depth exceeded로 이어졌다. 두 번째 스토어 업데이트를 첫await이후로 옮겨 같은 동기 블록에서 벗어나게 했더니 해결됐다. 이후 여러 스토어를 동시에 구독하는 컴포넌트는 구독 지점을 하나로 모으거나, 셀렉터가 매 렌더마다 새 객체를 반환하지 않도록(프리미티브 값으로 분리) 하는 패턴으로 같은 문제를 반복해서 방지했다.
NGRAS 선행 시스템 유지보수
NGRAS 이전에 있던 규제인증 시스템(NGRMS)의 Reference 모듈도 함께 유지보수했다. 프론트엔드에서는 국가마다 다른 동적 필드 입력과, 새 항목을 추가하면 자동으로 선택되는 인라인 추가 기능을 구현했다. 백엔드에서는 화면 그룹(국가) 단위로 페이지네이션을 맞추고, 국가별로 제각각인 필드를 JSONB로 담아 스키마 변경 없이 확장할 수 있도록 구성했다.
배운 점
- 표준 API(
EventSource)가 요구사항(커스텀 인증 헤더)을 못 채워줄 때는, 더 낮은 레벨의 API(fetch+ 스트림)로 내려가 직접 구현하는 선택지가 있다는 걸 실전에서 경험했다. - 동시성 문제(중복 토큰 갱신, 리렌더링 폭주)는 기능이 다 완성된 뒤에야 드러나는 경우가 많아서, “정상 동작”과 “동시에 여러 번 실행됐을 때도 정상 동작”은 별개로 검증해야 한다는 걸 배웠다.
- 보안망 내부에서 외부 AI·협업 도구 없이 개발하며, 평소 익숙했던 작업 방식에 의존할 수 없는 제약 속에서도 문제를 풀어내는 경험을 했다.