검색

레이블이 생성형 AI 코딩인 게시물을 표시합니다. 모든 게시물 표시
레이블이 생성형 AI 코딩인 게시물을 표시합니다. 모든 게시물 표시

2026년 6월 16일

사용기 - Large Context Analysis 스킬 적용기: CodeGraph로 컨텍스트를 적게 읽게 만들기

MIT CSAIL의 논문 「Recursive Language Models」는 긴 컨텍스트 문제를 다른 방식으로 접근한다. 이 논문은 OOLONG-Pairs처럼 입력 전체의 많은 쌍을 비교해야 하는 작업에서, GPT-5 기본 모델과 compaction agent가 각각 0.1 F1 수준에 머무른 반면 RLM depth=1은 58.0 F1을 기록했다는 결과를 제시한다.

이를 바탕으로 논문은 긴 프롬프트를 LLM 컨텍스트 창에 직접 넣거나 반복 요약하는 대신, 외부 환경에 두고 필요한 부분을 프로그램적으로 탐색·분해·재귀 호출하는 Recursive Language Models, RLMs를 제안한다.

* OOLONG-Pairs : 논문에서 RLM 성능을 검증하기 위해 사용한 긴 컨텍스트·고밀도 추론 벤치마크
* compaction agent : 긴 입력을 그대로 넣지 않고, 중간에 요약하거나 압축해서 더 짧은 컨텍스트로 만든 뒤 답하게 하는 방식의 에이전트
* F1 : 정답을 얼마나 잘 맞혔는지 보는 평가 지표
* RLM depth=1: 재귀 호출의 깊이를 1단계까지 허용


Headroom을 사용하면서 성능 리포트상으로는 토큰 절감 효과가 크게 표시되었지만, 컨텍스트를 줄인다는 것은 결국 모델에게 전달되는 정보가 줄어든다는 뜻이기도 하다. 특히 성능 지표에서 TOIN retrieval이 0%로 표시되는 상황에서는, 압축된 정보가 필요할 때 원문을 다시 꺼내 검증하는 루프가 실제로 동작하고 있는지 확신하기 어려웠다.

이 지점에서 앞서 언급한 Recursive Language Models 논문이 주는 시사점이 있었다.  논문은 긴 컨텍스트 문제를 단순히 더 큰 컨텍스트 창에 넣거나, 반복적으로 요약·압축하는 방식만으로 해결하기 어렵다고 본다. 특히 OOLONG-Pairs처럼 입력 전체의 많은 조합을 비교해야 하는 고밀도 추론 작업에서, GPT-5 기본 모델과 compaction agent는 각각 0.1 F1 수준에 머무른 반면, RLM depth=1은 58.0 F1을 기록했다. 이 결과는 긴 입력을 무조건 넣거나 줄이는 것보다, 외부 환경에 두고 필요한 부분을 탐색·분해·재귀 호출하는 방식이 더 효과적일 수 있음을 보여준다.

이 내용을 Codex 사용 방식에 그대로 적용하기는 어렵지만, 실무적인 힌트는 얻을 수 있었다. 코딩 에이전트들은 대부분 기본적인 파일 검색, 코드 탐색, 부분 읽기, 요약 기능을 제공한다. 그렇다면 반드시 Headroom처럼 컨텍스트를 프록시에서 압축하지 않더라도, 작업 절차 자체를 바꿔 불필요한 컨텍스트 생성을 줄일 수 있다.

기본적으로 CodeGraph를 사용하고 있기 때문에 CodeGraph를 RLM식으로 쓰는 스킬을 적용해보기로 했다. 

*CodeGraph : 코드베이스를 미리 분석해서 클래스, 함수, 호출 관계, 파일 구조를 그래프로 만들어두고, 코딩 에이전트가 필요한 코드 위치를 빠르게 찾도록 도와주는 도구


1. 스킬 생성하기

스킬 생성은 코덱스 앱을 사용했고 적용 모델은 GPT-5.5 매우 높음을 사용 plugin-creator 플러그인을 사용하여 다음 내용으로 생성을 요청했다. 


코덱스의 create-plugins 에 의하여 생성된 결과물은 ~/plugins/large-context-analysis/.codex-plugin/ 경로에 자동으로 생성되었다.




생성된 사용자 플러그인은 플러그인 메뉴 내가 만든 항목에서 확인 할 수 있고 "추가" 버튼을 클릭하여 바로 사용할 수 있다. 

  1. 새로운 플러그인 생성을 위해 @plugin-creator 채팅으로 이동
  2. 생성 후 플러그인 메뉴에서 내가 만든 항목으로 이동
  3. 생성된 목록에서 새로운 스킬을 추가


2. 스킬 테스트 하기


다음으로 실제 테스트를 진행해보았다. 문제는 Large Context Analysis 스킬의 효과를 확인하기 어렵다는 점이다. 

 

큰 파일/데이터을 다룰때는 large-context-analysis 사용해.

첨부한 로그를 분석해줘.

전체 로그를 한 번에 읽지 말고,

최초 ERROR, 반복 stack trace, root cause 후보를 먼저 분리해줘.

그 다음 CodeGraph로 관련 클래스와 호출 경로를 찾고,

필요한 소스 구간만 확인해줘.

마지막에는 원인, 수정 후보, 검증 방법을 정리해줘.


보통은 간단하게 아래와 같은 작업 기준을 전달하는 것으로도 충분했다. 

큰 파일·긴 로그·대형 diff·복잡한 데이터 분석 시 `large-context-analysis` 스킬을 사용하고, CodeGraph로 작업 대상 코드를 먼저 선정한 뒤, 작업 완료 후 `Context Usage Report`를 반드시 제시한다.

Headroom처럼 토큰 절감률이나 압축률이 숫자로 표시되는 것이 아니기 때문이다. 그래서 작업 요청에 추가 조건을 넣었다. 

단순히 로그를 분석하게 하는 것이 아니라, 작업 완료 후 `Context Usage Report`를 제출하도록 했다. 이 보고서에는 사용한 검색어, CodeGraph 탐색 단계, 읽은 파일 목록, 전체 파일을 읽었는지 또는 일부 구간만 읽었는지, 확인한 주요 라인·메서드·SQL ID, 불필요하다고 판단해 읽지 않은 파일, 최종 원문 검증 여부를 포함하도록 했다. 

이렇게 하면 스킬의 효과를 “토큰이 얼마나 줄었는가”가 아니라 “Codex가 얼마나 불필요한 파일과 로그를 덜 읽었는가”, “관련 후보를 먼저 좁혔는가”, “최종 결론을 원문 기준으로 검증했는가”로 판단할 수 있을 것으로 기대했다. ( 테스트 과정에서 SKILL 에 Report 기능을 추가하여 아래 추가 설명없이도 확인이 가능하게 했다). 


"Context Usage Report"를 작성해줘.

Context Usage Report에는 다음을 포함해줘.

- 사용한 검색어

- 사용한 CodeGraph 쿼리 또는 탐색 단계

- 읽은 파일 목록

- 전체 파일을 읽었는지, 일부 구간만 읽었는지

- 확인한 주요 라인/메서드/SQL ID

- 불필요하다고 판단해 읽지 않은 파일

- 최종 판단 전 원문 검증 여부

Context Useage Report 

이번 테스트 결과를 보면 Large Context Analysis 스킬은 어느 정도 의도한 방식으로 동작했다. Codex는 처음부터 전체 코드베이스를 읽지 않고, search-visualization, VectorSearchVisualizationService, VectorStorePort, PgVectorStoreAdapterV2, PgVectorMapper, metadataFilter, includeText, includeMetadata 같은 기능과 관련된 구체적인 검색어를 사용해 관련 범위를 좁혀 갔다.

CodeGraph 탐색도 /search-visualization에서 시작해 Controller → Service → VectorStorePort → PgVectorStoreAdapterV2 → PgVectorJdbcMapper → PgVectorMapper.xml 경로로 진행되었다. 즉 단순히 파일을 많이 읽는 방식이 아니라, 기능의 호출 흐름을 먼저 따라가며 후보 파일을 좁힌 것이다.

실제로 읽은 파일도 구분되었다. DefaultVectorSearchVisualizationService.java와 PgVectorSearchParameter.java는 전체를 확인했고, PgVectorJdbcMapper.java, PgVectorMapper.xml, 테스트 파일은 관련 SQL 또는 테스트 구간만 부분적으로 확인했다. 반면 RAG 일반 검색 Controller/DTO, SkillGraph/Markdown 파일, projection 생성 및 point 저장 관련 파일은 이번 작업 범위와 직접 관련성이 낮다고 판단해 재검토 대상에서 제외했다.

이 점은 Large Context Analysis 스킬의 효과를 보여주는 부분이다. 효과가 토큰 절감률처럼 숫자로 표시되지는 않지만, Codex가 검색과 CodeGraph를 먼저 사용해 후보를 좁히고, 필요한 파일과 구간만 확인했으며, 불필요한 파일을 읽지 않도록 유도했다는 점에서 컨텍스트 사용을 줄이는 방향으로 동작했다고 볼 수 있다.

또한 최종 판단 전 CodeGraph 결과를 원문으로 확인하고, rg로 SQL 패턴을 재확인했으며, 대상 Gradle 테스트와 git diff --check까지 통과시켰다는 점도 중요하다. 이는 요약이나 CodeGraph 결과만 믿지 않고 원본 파일과 테스트 결과를 기준으로 검증했다는 의미다.

다만 효과를 더 명확히 보려면 다음부터는 후보 파일 수, 실제 읽은 파일 수, 전체 읽기 파일 수, 부분 읽기 파일 수, 제외한 파일 수를 함께 기록하는 것이 좋다. 그렇게 하면 Large Context Analysis 스킬의 효과를 “느낌”이 아니라, 실제 탐색 범위 축소 결과로 비교할 수 있을 것 같다.  

* 앞에서 언급되는 이름들은 테스트에 적용된 프로젝트 소스 이름이다.


마지막으로 이번 실험의 결론은 Headroom이 불필요하다는 것이 아니다. Headroom은 긴 로그나 반복 tool output을 줄이는 데 여전히 유용할 수 있다. 다만 압축된 컨텍스트 품질이 찜찜하다면 (개인적인 생각), 먼저 CodeGraph와 스킬을 이용해 “처음부터 필요한 것만 읽게 만드는 방식”을 적용해볼 만하다. Large Context Analysis 스킬은 토큰 절감 도구라기보다, 코딩 에이전트의 조사 습관을 바꾸는 절차적 안전장치에 가깝다.

2026년 6월 15일

사용기 - Headroom으로 Codex 토큰 사용량 줄이기

코딩 에이전트를 사용하여 코드를 분석하고 수정하다보면 가장 먼저 부딪치는 문제는 컨텍스트(모델이 답변을 만들기 위해 한 번에 참고하는 입력 정보 전체)와 토큰 사용량이다. 

LLM 은 텍스트를 글자 단위가 아닌 토큰(token) 단위로 처리하는데 토큰은 대략 단어 조각, 기호, 코드 조각에 해당한다. 즉 컨텍스트가 길어질 수록 LLM 에 전달되는 토큰 수도 증가하게 된다.


1. Headroom

Headroom은 AI 코딩 에이전트가 LLM에 전달하는 컨텍스트를 압축해 토큰 사용량을 줄이기 위한 오픈소스 프로젝트다. 공식 README에서는 Headroom을 다음 문장으로 설명한다.

Compress everything your AI agent reads.

Same answers, fraction of the tokens.

즉, AI 에이전트가 읽는 파일, 로그, tool output, RAG chunk, 대화 이력 등을 LLM에 전달하기 전에 압축해서, 가능한 한 같은 답변을 더 적은 토큰으로 얻는 것이 목표다.

Codex 같은 AI 코딩 에이전트는 사용자의 질문만 LLM에 보내지 않는다. 문제 해결을 위해 소스 파일, 오류 로그, 터미널 명령 결과, git diff, 프로젝트 문서, 이전 대화 이력 등을 함께 전달한다. 이 정보들이 모두 컨텍스트가 되고, 컨텍스트가 길어질수록 토큰 사용량과 비용도 증가한다.

Headroom은 이 지점에서 Codex와 LLM 사이에 위치하는 컨텍스트 압축 레이어로 동작한다. 기술적으로 보면 Headroom은 Codex가 LLM Provider로 요청을 보내기 전에 중간에서 요청을 받아 컨텍스트를 정리한다.

 Codex가 파일을 읽거나, 터미널 명령을 실행하거나, 검색 결과와 로그를 수집하면 이 결과들은 그대로 LLM 입력에 포함될 수 있다. Headroom은 이 입력을 가로채서 중복 제거, 요약, 필터링, 구조화 같은 처리를 수행한 뒤 더 짧은 형태의 컨텍스트로 다시 구성한다.

아키텍처 및 동작방식

이 구조에서 중요한 점은 Headroom이 최종 답변을 직접 생성하는 모델이 아니라는 것이다. Headroom은 Codex와 LLM 사이에서 입력 컨텍스트를 최적화하는 계층이다. 즉, “무엇을 답할 것인가”는 여전히 LLM이 담당하고, Headroom은 “LLM에게 어떤 정보를 얼마나 효율적으로 전달할 것인가”를 담당한다.

예를 들어 Codex가 테스트 실패 원인을 찾기 위해 수천 줄의 로그와 여러 개의 소스 파일을 읽었다고 가정해보자. 이 정보를 그대로 LLM에 보내면 토큰 사용량이 크게 증가한다. Headroom은 이 중 반복되는 stack trace, 중복 로그, 관련성이 낮은 출력 등을 줄이고, 오류 원인 분석에 필요한 핵심 정보를 중심으로 컨텍스트를 다시 구성한다.

따라서 Headroom의 기술적 역할은 단순히 텍스트 길이를 줄이는 것이 아니다. 컨텍스트 안에서 의미 있는 정보는 유지하고, 반복되거나 덜 중요한 정보는 줄여서 LLM이 더 적은 토큰으로 문제를 이해하도록 돕는 것이다.

이 때문에 Headroom은 특히 다음과 같은 작업에서 효과를 기대할 수 있다.

  • 긴 빌드 로그 분석
  • 테스트 실패 원인 추적
  • 대형 코드베이스의 여러 파일 분석
  • 큰 git diff 검토
  • 검색 결과가 많은 작업
  • 이전 대화 이력이 길게 누적된 Codex 세션

반대로 다음과 같은 경우라면 굳이 사용할 필요가 없다.

  • 짧은 파일 수정
  • 간단한 질문
  • 작은 버그 수정
  • 정확한 원문 로그 전체가 중요한 장애 분석
  • 보안/권한/결제/마이그레이션처럼 작은 차이가 중요한 작업




2. 설치하기 

 환경

  • Model : MacBook Pro (14-inch, 2021)
  • CPU : Apple M1 Pro
  • MENORY : 16GB
  • DISK : 512 GB SSD
  • OS : macOS 15.3.1 (24D70)
  • AI Coding Agent : Codex

Headroom은 Codex 앱에 직접 붙는 도구라기보다, Codex CLI를 감싸거나 proxy로 연결하는 도구이다. 공식 README도 Headroom을 “AI agent가 읽는 tool output, logs, RAG chunks, files, conversation history를 LLM에 도달하기 전에 압축하는 도구”로 설명하고, Codex 같은 coding agent와 함께 사용할 수 있다고 안내하고 있다. 

먼저 Codex CLI 와 파이선이 설치되었는지를 아래 명령으로 확인한다.

python3 --version

pip3 --version

which codex

codex --version



참고로 Codex 앱만 사용하고 있더라도 Codex CLI 설치는 별도이다.  설치는 아래와 같은 명령으로 진행한다. (OpenAI 공식 문서 기준)

curl -fsSL https://chatgpt.com/codex/install.sh | sh

다음으로 python 패키지 형태로 headroom 을 설치한다. 

python3 -m pip install -U "headroom-ai[all]"

3. 사용하기

설치가 완료 되었다면 아래 명령으로 실행할 수 있다. 
headroom wrap codex
성능 리포트 기능을 사용하여 컨텍스트 압축이 효과가 있는지를 확인해 볼 수 있다. 
headroom perf



위 리포트 결과를 보면 압축후 토큰 사용량이 0 이 된점을 보아 좀 과장되었다고 생각을 하게 되었다. 또한 Retrievals 비율이 0% 인것으로 보아 패턴을 학습하고는 있지만, 그 학습된 패턴을 다시 꺼내 재사용하지는 못하고 있음을 알 수 있다. 이 결과는 사용 시간이 너무 잛아서 그런거라고 예상하고 있다.

좀더 긴 시간을 사용후에 확인 해보았지만  필요할 때 원문을 꺼내 사용한 기록은 존재하지 않았다. 

아쉬운 점은 /status 을 사용해도 요금 한도부분을 확인 할 수 없었다. (추측하건데 headroom warp API 가 이를 지원하지 않아서 인것 같다.) 사용하다 갑짜기 limit 에 도달했다는 메시지를 받았다. 

headroom wrap 명령이 codex 설정을 자동으로 모델 provider , openai_base_url 정보를 수정하기 때문에 headroom 을 사용하지 않는 경우에는 unwrap 으로 다시 설정을 원복 해줘야 한다. 
headroom unwrap codex  

 

4. 결론

개인적으로 Headroom을 사용하면서도 품질에 대한 찜찜함을 완전히 버리기는 어려웠다. 성능 리포트상으로는 토큰 절감 효과가 크게 표시되었지만, 컨텍스트를 줄인다는 것은 결국 모델에게 전달되는 정보가 줄어든다는 뜻이기도 하다. 특히 TOIN retrieval이 0%로 표시되는 상황에서는, 압축된 정보가 필요할 때 원문을 다시 꺼내 검증하는 루프가 실제로 동작하고 있는지 확신하기 어려웠다.

2026년 5월 6일

사용기 - ChatGPT 에서 설계 하고 개발은 Codex 에서

Codex 와 대화 하면서 구체적인 설계를 진행하는 것 보다 ChatGPT 에서 설계를 하고 대화 결과를 코덱스에 공유하여 구현을 하는 것을 테스트 해보았다. 실험의 목적은 코덱스의 토큰 사용량을 최소화하기 위한 경제성이다.

모델은 5.5 Pro 을 사용하였고 대화를 통하여 원하는 설계를 구체화 했다. Codex 전달용 작업 계획을 요청했지만 한번에 복사하여 사용하기는 어려운 점이 있었다. 



대화에 대한 공유 링크를 생성하고 링크를 Codex 에 전달하여 진행을 시도하였다. 다만 이경우 전체 대화 내용을 읽어서 작업 계획을 수립하기 때문에 진행 과정의 대화 까지 읽어 들여 비효율적이며 토큰을 낭비하게 된다. 


좀더 효과적은 작업 내용 전달을 위해서 ChatGPT 대화에서 최종 결과에 대한 md 파일 생성을 요청하여 Codex 에 공유하여 작업을 진행하게 했다. 




생성된 md 파일을 통한 작업 지시의 장점은 md 파일 계획이 1차, 2차 구현으로 구분되어 있는 경우 Codex 는 1차 구현만 진행하게 되는데 작업이 완료된 이후에 다시 2차 를 구현하도록 지시할 수 있다는 점이다. (또한 미리 작업 계획 초안을 검토하는 것이 용이)



이번 작업을 통해 느낀 점은 AI 코딩에서 중요한 것은 단순히 좋은 모델을 사용하는 것이 아니라, AI가 안정적으로 이해하고 작업할 수 있도록 작업 자체를 구조화하는 것이라는 점이다.

특히 ChatGPT에서 충분히 설계와 정리를 수행한 뒤, 최종 내용을 md 기반 작업 계획으로 압축하여 Codex에 전달하는 방식은 예상보다 효율적이었다.

공유 링크 방식은 전체 대화 흐름까지 모두 포함되므로 불필요한 컨텍스트 증가와 토큰 낭비가 발생하기 쉽다.

반면 md 기반 작업 문서는:

  • 작업 범위를 제한하고
  • 구현 단계를 분리하며
  • 필요한 정보만 압축 전달하고
  • 검토 가능한 형태로 유지할 수 있다.

결국 AI 시대의 개발 생산성은 “얼마나 긴 프롬프트를 작성했는가”보다, AI가 일하기 좋은 구조와 흐름을 어떻게 설계했는가 에 더 크게 좌우된다고 느꼈다.




2026년 4월 30일

사용기 - 프롬프트는 시작이고, AI는 환경에서 일한다

Feedback Loop Engineering은 2026년 1월 다니엘 데멜(Daniel Demmel)이 AI 코딩 에이전트의 품질을 높이는 핵심을 “검증 가능한 반복 구조”로 설명하면서 제시한 개념이다. 


이후 2026년 2월 OpenAI는 Codex 활용 사례를 통해 하네스 엔지니어링(Harness Engineering)이라는 표현을 사용하며, 에이전트가 안정적으로 일할 수 있도록 실행 환경·검증 체계·피드백 구조를 설계하는 접근을 소개했다. 

두 개념은 별개라기보다, Feedback Loop Engineering이 “무엇을 반복해야 하는가”를 설명하고 하네스 엔지니어링(Harness Engineering)이 “그 반복이 가능하도록 어떤 환경을 설계해야 하는가”를 설명한다고 볼 수 있다.

많은 사람들이 AI 코딩 에이전트를 잘 사용하는 방법은 “프롬프트를 잘 쓰는 것”이라고 생각한다.
하지만 최근 흐름은 다르다. “스스로 검증하고 개선할 수 있는 구조를 만드는 것”이다.

프롬프트 중심에서는 결과가 프롬프트 품질에 크게 의존하며 사람이 계속 개입해야 한다. Feedback Loop Engineering은 완전히 다른 방향을 제시한다.
AI가 스스로 결과를 개선하는 구조를 통하여, 결과 품질이 프롬프트가 아니라 테스트와 검증에 의해 결정되도록 만든다. 이러한 구조에서는 사람이 모든 과정을 직접 통제할 필요가 없다. AI는 실행 결과와 오류를 기반으로 스스로 수정하고, 반복적으로 품질을 높여간다.

결국 중요한 것은 더 정교한 질문이 아니라, 잘못된 결과를 빠르게 발견하고 수정할 수 있는 피드백 루프를 설계하는 것이다. 



결정적으로 최근 Codex에서 지원하기 시작한 “Computer Use”, “Browser Use” 기능을 직접 경험하면서 생각이 완전히 바뀌었다.

그동안은 좋은 프롬프트, 정교한 스킬, 다양한 서브에이전트를 만드는 것이 핵심이라고 생각했다.
하지만 실제로는 전혀 다른 결과를 보게 되었다.

단순한 명령만으로도

  • 테스트 후 수정하고
  • 서버를 재시작하고
  • 브라우저에서 직접 화면을 확인하고
  • 테스트 결과를 바탕으로 다시 수정하는

전체 작업 흐름을 AI가 스스로 수행하는 경험을 하게 되었다. 이 과정에서 느낀 것은 명확했다.

중요한 것은 프롬프트가 아니라 “일을 할 수 있는 환경”이었다.

프롬프트를 아무리 잘 작성해도 AI가 실행하고, 확인하고, 실패를 인지하고, 다시 수정할 수 없다면
결국 사람이 계속 개입해야 한다. 반대로, 환경만 제대로 갖추어지면 아주 간단한 명령만으로도 AI는 스스로 작업을 이어가고 결과를 개선해 나간다.

이 경험은 정말 충격적이었다.


에이전트가 테스트하고 수정하고 벡엔트 파트 작업는 이슈로 등록

에이전트가 이슈를 확인해서 처리하고 서버를 재시작해서 확인

결국 중요한 것은 질문을 잘하는 능력이 아니라,AI가 스스로 실행하고 검증하며 반복할 수 있도록프로젝트와 환경을 설계하는 것이다.


 

2024년 7월 18일

사용기 - 생성형 AI 를 이용한 코딩 후기

ChatGPT 4o

1. 코드 생성 

 존재하는 코드 개선 JWTFilter 

🆀 Spring Security 필터에 커스텀 JWTFilter 을 추가 하여 사용하고 있다. JWTFilter에서 오류가 발생하면 login 페이지가 아닌 오류 메시지를 JSON 데이터로 응답하게 하려면

🅰 GitHub Copilot 의 경우 JWTFilter에서 오류가 발생하면 HttpServletResponse 을 사용하여 JSON 데이터를 만들어 응답하라고 답변. 

✓ 제시된 코드는 문제를 해결하지 못함. Spring Security 에 의해서 설정된 login 페이지로 이동한다. 


🅰 ChatGPT 의 경우 동일 질문에 대한 답으로 CustomAuthenticationEntryPoint 을 구현하는 방법으로 답변  

✓ 문제를 해결.  


GitHub Copilot 

1. 문서자동화

함수 블록 코드를 기반으로 JavaDoc 문서 만들기 

함수에 문서화를 위하여 JavaDoc 주석 기호를 입력 /** */

함수 블록의 코드 로직을 기술 하려면 1. 입력하면 로직 설명을 제시. 순차적으로 2. , 3. 을 입력하여 이어 로직 설명을 제시.


JavaDoc 주석의 첫줄로 이동 공백을 입력하고 기다리면 함수에 대한 요약을 제시.


2. 코드 생성 

 소스 코드 기능 개선

🆀  alert.value.message.response.data.error.exception 값과 스트링 배열을 비교하여 일치하는 경우 authAtore.loguot () 을 실행하려면

🅰 언어의 특성을 고려하여 비교할 문자열 배열을 정의하고 존재 여부를 검사하여 일치하는 경우 함수를 호출하는 설명과 예제를 제시. 

✓ 참조된 소스가 Vue 언어로 작성된 코드인 것을 확인하여 자바 스크립트 언어 문법으로  정확한 답변을 함





2024년 1월 25일

코딩 - Data(동영상) Streaming in Spring Boot

◼︎ 환경
  • Model : MacBook Pro (14-inch, 2021)
  • CPU : Apple M1 Pro
  • MENORY : 16GB
  • DISK : 512 GB SSD
  • OS : macOS 13.2.4 (22F66)
  • TOOLS : Visual Studio Code, Java 11, Gradle, Docker
  • Version Control : GitHub
  • Programming Language : Java
  • Framework : Spring Boot 2.7.12, Spring Security 5.7.7
  • DBMS : MySql 8.0.33

동영상 같이 크기가 큰 파일을 서버에서 다운로드 하는 경우 좀더 효과적인 방법이 없을까 고민을 하다 스트리밍 기술을 사용하는 것을 고민해보았다.

기술 적용에 앞서 구글 검색을 통하여 관련 자료를 찾아보았다.  (검색어 : data streaming in java, data streaming in spring boot). 

검색 결과 제시된 "Streaming Data with Spring Boot RESTful Web Service" 자료가 많은 도움이 되었다.

이어 ChatGPT 와  Bard 을 사용하여 좀더 알아 보았다. 참고로 한글 질의 응답 역시 만족할 만한 수준이었다.
 
"how to implements data streaming in spring boot. "

  • Bard : Kafka (실시간 데이터 스트리밍을 위한 분산 메시징 플랫폼) 또는 HTTP 이용하는 비동기 방법으로 답변.
  • ChatGPT: Spring Boot 환경에서 WebFlux 기술과 Reactor 을 이용한 비동기 방법으로 답변.

웹 기반의 스트리밍 구현을 목표로 하기 때문에 "HTTP Streaming" 키워드로 추가 질의를 했다,
Bard 보다는 ChatGPT 가 좀더 원하는 답변을 제시했다.

"how to implements data streaming in spring boot."

  • Bard : @StreamingResponseBody 와 stream() 함수를 사용하는 방업으로 답변.
  • ChatGPT: Spring Boot 환경에서 WebFlux 기술과 Reactor 을 이용한 비동기 방법으로 답변.

ChatGPT 답변이 적절해보였고 단일 파일을 스트리밍하는 경우이기 때문에 제시된 Flex 가 아닌 Mono 을 사용하는 것이 적절해 보였다. 추가로 "HTTP 기술에서 파일을 이어서 받기 위한 기술"을 추가로 질의 해서 "Range 요청 헤더" 을 사용하여 가능함을 확인했다. (이 질의에 대해서는 Bard 가 좀더 유용했다. Bard가 언급한 Transfer-Encoding 헤더에 "chunked" 값 설정은 Spring Boot 에서는 자동으로 처리한다고 한다. )

Spring boot 에서 데이터 스트리밍을 구현하는 방법에는 크게 3가지가 있다.

Streaming in Spring

1. HttpServletResponse’s OutputStream 

가장 오랜 방법으로 콘텐츠를 HttpServletResponse 객체의 OutputStream 에 데이터를 쓰기를하는 방법이다.  


2. StreamingResponseBody as return type

Spring Boot 환경에서 콘텐츠를 스트리밍하는 쉬운 방법은 StreamingResponseBody 객체를 사용하는 것이다. 보통 ResponseEntity 와 함께 사용하는데, 이렇게 하면 헤더와 HTTP 상태 값을 설정할 수 있어 보다 세밀하게 동작을 제어할 수 있다.

StreamingResponseBody 를 사용하는 경우 Spring MVC 에서 비동기 요청 실행을 위한 TaskExecutor 를 구성하는 것이 권장된다. 운영 환경이라면 비동기 처리를 위한 스레드 수를 제한하도록 TaskExecutor 를 구성하는 것이 좋다. 그렇지 않으면 서버 리소스에 과부하가 걸릴 수도 있다.

TaskExecutor@Configuration 어노테이션이 정의된 클래스에서 TaskExecutor 빈을 생성하고 구성하면 된다. 일반적으로 ThreadPoolTaskExecutor 구현을 사용하여 정의한다. 또는 YAML 설정을 사용할 수 있다.


YAML 파일 설정은 Java Config 에서 정의한 TaskExecutor 빈을 대체한다. (Java Config 에서 설정한 값과 YAML 파일에서 설정한 값이 충돌할 경우, YAML 파일의 값이 우선된다.) 

3. Spring WebFlux publishers

Spring WebFlux 는 Spring 5 버전에 도입된 기술로 Spring MVC 와 유사하게 동작한다. Spring WebFlux는 완전한 "Non-blocking reactive streams" 을 지원하고 있다.

"Non-blocking reactive streams" 는 비동기적이고 이벤트 기반 프로그래밍을 강조하는 리액티브 프로그래밍 패러다임과 관련된 개념이다. 이는 데이터 흐름을 비차단(Non-blocking) 방식으로 처리할 수 있는 방법을 제공하는데, 주로 Reactive Streams API 와 관련이 있다.

Reactive Streams API 에 대하여 설명하기전에 "Reactive Streams" 의 주요한 용어에 대하여 알아보자. (LINE Engineering 블로그에서 가져온 내용) 

3.1 Stream

왼쪽의 전통적 방식은 요청 메시지를 메모리에 저장한 이후에 다음 절차를 진행할 수 있으며 응답을 위하여 요구되는 모든 데이터가 메모리에 적재 되어야 응답 메시지를 만들수 있다. 이 방식은 요청 메시지 처리 과정에서 필요한 메모리가 가용 메모리 용량을 초과하게 된다면 "Out of memory" 에러가 발생하거나 지나치게 많은 요청으로 인한 GC 로 성능 이슈가 발생하게 된다.  

우측 이미지와 같이 스트림 방식을 적용하면 적은 메모리 환경에서도 많은 양의 데이터를 처리할 수 있게된다. 가상의 작업 파이프라인을 만들고 요청 데이터를 구독(subscribe) 이벤트 과정을 통하여 파이프라인에 주입하고  작업이 종료되면 발행(publish) 이벤트를 통하여 최종적으로 처리할 수 있다. 이렇게 하면 서버는 많은 양의 데이터도 탄력적으로 처리할 수 있다.

3.2 Non-blocking

동기(synchronous) 방식에선 클라이언트가 서버에 요청을 보내면 응답을 받기 전까지 블로킹(blocking) 된다. 반대로 비동기(asynchronous) 방식에서는 블로킹(blocking) 발생되지 않기 때문에 다른 일을 계속 할 수 있다. 

3.3 백 프레셔(back pressure)

데이터 스트림을 처리하는 방식 중 하나로, 소비자(consumer)가 데이터를 처리하는 속도를 생산자(producer)에게 통지하여 데이터의 생성 속도를 조절하는 메커니즘이다. 이는 데이터의 과도한 생성으로 인한 자원 소모나 성능 저하를 방지하기 위한 중요한 개념 중 하나로, 아래와 같은 상황에서 필요하다:

  • 데이터 소비자가 처리 속도가 느린 경우: 생산자는 무한히 데이터를 생성할 수 있지만, 소비자가 처리하지 못하면 메모리가 과도하게 소비되고 성능이 저하될 수 있다.
  • 네트워크 통신에서의 부하: 네트워크 통신에서 데이터를 전송하는 경우, 데이터의 양이 처리 속도를 초과할 수 있다. 이런 경우 백 프레셔를 통해 생산자와 소비자 간의 조절이 필요하다.




백 프레셔는 Reactive Streams 프로토콜에서 주로 사용되며, Reactive Streams API는 이를 구현한 표준을 제공한다. API는 백 프레셔의 세 가지 상태를 정의하고 있다.

  • REQUESTED: 소비자가 몇 개의 아이템을 처리할 준비가 되었다고 통지하는 상태. 이 상태에서만 생산자는 데이터를 전송할 수 있다.
  • PENDING: 데이터를 요청한 상태이지만 아직 처리되지 않은 상태. 이 상태에서는 생산자가 데이터를 생성하지 않아야 한다.
  • CANCELLED: 소비자가 더 이상 데이터를 원하지 않거나 구독을 취소한 상태.

이러한 상태 전이를 통해 생산자와 소비자 간의 균형을 맞추어 데이터의 안정적인 전송을 가능하게 한다.

3.4 Reactive Streams API

Reactive Streams API는 자바 생태계에서 비동기적이고 이벤트 기반 프로그래밍을 지원하기 위한 표준 인터페이스와 프로토콜을 제공하는 API 이다. 이 API는 자바 9에서 java.util.concurrent.flow 패키지의 일부로 소개되었다.


Reactive Streams API의 주요 목표는 다음과 같다:
  • 비동기 및 이벤트 기반 프로그래밍: Reactive Streams는 비동기적인 환경에서 데이터나 이벤트의 효율적인 처리를 위한 기본 도구를 제공.
  • 백프레셔(Backpressure) 관리: 백프레셔는 소비자(구독자)가 생산자(발행자)에게 데이터를 처리할 수 있는 속도를 제어할 수 있는 메커니즘을 의미한다. Reactive Streams API는 백프레셔를 지원하여 데이터의 효율적인 처리를 가능하게 한다.
Reactive Streams API는 다음과 같은 핵심 인터페이스로 구성되어 있다:
  • Publisher (발행자): 데이터나 이벤트의 생산자를 나타내며, 여러 개의 구독자에게 데이터를 전송.
  • Subscriber (구독자): 데이터나 이벤트의 소비자를 나타내며, 발행자로부터 데이터를 받음.
  • Subscription (구독): 발행자와 구독자 간의 연결을 제어하며, 백프레셔를 통해 데이터 흐름을 조절.
Reactive Streams API는 다양한 자바 라이브러리와 프레임워크에서 지원되고 있으며, 대표적으로는 Project Reactor, RxJava, Akka Streams, LINE Armeria 등이 있다. 이러한 라이브러리들은 Reactive Streams API의 기본 규약을 따르면서 각각의 특성과 확장성을 제공한다.

3.5 WebFlux

Spring WebFlux 는 스프링 5에서 소개된 리액티브 프로그래밍을 지원하는 모듈 중 하나로, 비동기 및 이벤트 기반 애플리케이션을 개발하기 위한 스프링의 리액티브 스택의 일부이다. 주로 Reactive Streams API를 기반으로 한 Project Reactor 를 사용하여 리액티브 프로그래밍을 지원하고 있다.

Spring WebFlux는 반응형 웹 애플리케이션을 구축하기 위한 다양한 컴포넌트와 기능을 제공한다. 예를 들어, reactive 한 컨트롤러, WebClient를 통한 비동기 HTTP 클라이언트, Flux 및 Mono와 같은 Project Reactor의 타입을 지원한다.

Spring Boot에서 reactive 프로그래밍을 하려면 일반적으로 다음과 같이 의존성을 추가하면 된다. 


Streaming in Spring

아래와 같은 가상의 목표를 설정하고 스트리밍 기술을 적용해보았다.

"큰용량의 파일을 스트리밍 기술을 적용하여 서버 자원을 독점하지 않게 하는 동시에 클라이언트(브라우져) 에서 역시 더 나은 경험을 제공한다." 

데이터를 스트리밍하는 여러가지 방법이 있었지만 간단하게 구현이 가능한 Reactive Mono 기술을 적용 동영상 파일을 스트리밍하는 코드를 작성해보았다. 

Reactive Mono는 Project Reactor 라이브러리에서 제공하는 개념 중 하나로 Reactive Streams API 스펙의 Publisher 구현체 중 하나이다. (Reactor는 Mono와 Flux라는 두 가지 주요 유형의 Publisher를 제공한다.)

Mono: Mono는 0 또는 1개의 요소를 발행할 수 있는 Reactive Streams 이다. Mono 는 단일 값 또는 오류를 발행(Publish)하고, 성공적으로 완료되었거나 아무 값도 발행하지 않았을 때 스트림(Stream)이 완료된다. 주로 비동기 작업의 결과물을 처리하거나 단일 값의 처리를 위해 사용된다.

파일 스트리밍은 비동기 방식의 단일 응답을 위한 Mono 와 RFC 7233 - Hypertext Transfer Protocol (HTTP/1.1): Range Requests 기술을 적용 클라이언트는 데이터 범위를 지정하여 요청하고 서버는 해당 범위의 데이터를 응답하도록 구현했다.

 
구현결과 확인을 위하여 간단하게 Vue 을 기반으로 동영상 파일을 video.js 을 사용하여 재생하는 기능을 구현하여 테스트 해보았다. 서버는 디폴트로 5M 단위로 나눠 응답하도록 했다. 
  • 나눠서 응답을 하는지 여부 ( 5M 단위로 ) :  나누어 스트리밍 방식으로 응답
  • 브라우저는 전체 파일을 응답받은 이후에 영상을 재생하는지 여부 : 나누어 응답을 받으면 바로 재생


작업을 하면서 한가지 이슈는 서버에서 5MB 단위로 나눠서 시도를 하는 경우에 클라이언트에 의하여 스트림이 강제로 종료되는 현상이 발생하였다. 클라이언트는 크롬 브라우저를 사용했는데 2번째 요청 건의 Content-Range: 값에서 특이점을 발견하였다. 예상했던것과 다르게 순차적으로 데이터를 요청하지 않는 것이다. 서버 역시 응답을 하는 동안에 클라이언트에 의해서 스트림이 종료되는 오류가 발생되었다. 
  1. Range:bytes=0- Content-Range:bytes 0-5242880/32625996
  2. Range: bytes=5242881- Content-Range: bytes 5242881-10485761/32625996
  3. Range: bytes=3604480- Content-Range: bytes 3604480-8847360/32625996
  4. Range: bytes=4390912- Content-Range: bytes 4390912-9633792/32625996
  5. Range : bytes=6291456- Content-Range: bytes 6291456-11534336/32625996
원인을 알수는 없었지만 서버가 응답 데이터의 크기를 3.59M (3768320) 한정하여 응답하는 경우 정상적으로 동작하는 것을 확인 할 수 있었다. 서버 역시 클라이언트에 의한 스트림이 종료되는 오류 없이 동작하였다.