<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>ssunn_ni.log</title>
        <link>https://velog.io/</link>
        <description>방황중인 서버개발자</description>
        <lastBuildDate>Fri, 10 Apr 2026 03:03:43 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>ssunn_ni.log</title>
            <url>https://velog.velcdn.com/images/ssunn_ni/profile/6dee034f-109b-49de-91d6-1b6736aa9b64/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. ssunn_ni.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/ssunn_ni" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[langgrpaph vs deepagent - 도입고민하기]]></title>
            <link>https://velog.io/@ssunn_ni/langgrpaph-vs-deepagent-%EB%8F%84%EC%9E%85%EA%B3%A0%EB%AF%BC%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ssunn_ni/langgrpaph-vs-deepagent-%EB%8F%84%EC%9E%85%EA%B3%A0%EB%AF%BC%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 10 Apr 2026 03:03:43 GMT</pubDate>
            <description><![CDATA[<p>항상 프로덕트를 만들다보면.. 새로운게 나오고
새로운게 나오면 이걸 우리꺼에다 적용시켰을 때 뭐가 더 유리할지에 대해 고민하는 시간이 길어지는것같다.</p>
<p>예전에는 하나하나 검색하고 찾아보면서 판단했었는데..
지금은 일단 claude에게 물어보는중.. </p>
<p>이미 예전에 했었던 고민이였지만 그당시에는 이렇게 점점 고도화가 되지 않았었기 때문에
deep-agent deploy가 나온 시점.. 함게 deep-agent도 다시 고민해보는 시간을 갖었다..</p>
<p>새로운게 계속 나오니까 나올때마다 이고민중이다 ㅠ 설계너무어려워..</p>
<hr>
<h3 id="create_react_agent-vs-deep-agent--실제-실행-메커니즘-차이"><code>create_react_agent</code> vs Deep Agent — 실제 실행 메커니즘 차이</h3>
<pre><code>User: &quot;강남역 지하철 도착 시간 알려줘&quot;

LLM (1회 호출)
├─ System Prompt: &quot;너는 도우미야&quot;
├─ Tool descriptions: [a,b,c ..... ]  ← 20개 전부 프롬프트에
├─ User: &quot;강남역 지하철 도착 시간&quot;
└─ LLM 판단: tool_call → search(search=&quot;강남&quot;)
   └─ 결과 받아서 → LLM 최종 응답</code></pre><p><strong>이게 전부입니다.</strong> ReAct 루프는:</p>
<pre><code>Think → Act(tool call) → Observe(결과) → Think → ... → 최종 답변</code></pre><p>LLM <strong>한 놈이 모든 걸 결정</strong>합니다. 도구 20개의 description을 전부 읽고, 어떤 도구를 쓸지 스스로 고릅니다.</p>
<hr>
<h3 id="deep-agent-패턴-supervisor--skill-agents">Deep Agent 패턴 (Supervisor + Skill Agents)</h3>
<pre><code>User: &quot;강남역 지하철 도착 시간 알려줘&quot;

Supervisor (LLM 1회 호출) — 가벼운 라우터
├─ System Prompt: &quot;아래 skill 중 적절한 것을 골라라&quot;
├─ Skill 목록 (도구가 아니라 에이전트 이름):
│   ├─ &quot;a&quot; - search
│   ├─ &quot;b&quot; 
│   ├─ &quot;c&quot;
│   ├─ &quot;d&quot;— 
│   └─ &quot;e&quot; — 그냥 대화
├─ LLM 판단: → &quot;search&quot;
│
└─ Agent (별도 LLM 호출)
   ├─ System Prompt: &quot;너는 한국 서비스 전문가야&quot;
   ├─ Tool descriptions: [a] ← 1개만!
   ├─ Context: 원래 질문 + Supervisor가 전달한 맥락
   └─ LLM 판단: tool_call → search(search=&quot;강남&quot;)
      └─ 결과 받아서 → 최종 응답</code></pre><hr>
<h3 id="대체-뭐가-다른-건데">대체 뭐가 다른 건데?</h3>
<table>
<thead>
<tr>
<th></th>
<th><code>create_react_agent</code></th>
<th>Supervisor + Skill</th>
</tr>
</thead>
<tbody><tr>
<td><strong>LLM 호출 수</strong></td>
<td>1회 (도구 사용 시 +1)</td>
<td>2회 (분류 1회 + 실행 1회)</td>
</tr>
<tr>
<td><strong>도구 선택</strong></td>
<td>LLM이 20개 중 직접 선택</td>
<td>Supervisor가 분야 선택 → Skill Agent가 3개 중 선택</td>
</tr>
<tr>
<td><strong>프롬프트 크기</strong></td>
<td>20개 도구 description 전부 (~2K 토큰)</td>
<td>Supervisor: 5줄 목록 (<del>200 토큰), Skill: 3개 description (</del>300 토큰)</td>
</tr>
<tr>
<td><strong>오선택 확률</strong></td>
<td>비슷한 도구 많으면 혼동</td>
<td>단계적 축소 → 혼동 적음</td>
</tr>
</tbody></table>
<p><strong>본질적 차이는 &quot;2단계 분류&quot;입니다.</strong> 그 이상도 이하도 아닙니다.</p>
<hr>
<h4 id="하네스harness란">&quot;하네스(Harness)&quot;란?</h4>
<p>Deep Agents 블로그에서 말하는 harness는 거창한 게 아닙니다:</p>
<pre><code class="language-python"># 이게 하네스의 전부입니다
harness = {
    &quot;supervisor&quot;: supervisor_agent,      # 라우팅 에이전트
    &quot;skills&quot;: {
        &quot;search&quot;: search_agent,          # Skill Agent 1
        &quot;math&quot;: math_agent,              # Skill Agent 2
    },
    &quot;memory&quot;: memory_store,              # 대화 기록/학습된 패턴
    &quot;config&quot;: agents_md                  # AGENTS.md 설정 파일
}

# 실행
async def run(user_input):
    skill_name = await harness[&quot;supervisor&quot;].invoke(user_input)  # 분류
    result = await harness[&quot;skills&quot;][skill_name].invoke(user_input)  # 실행
    return result</code></pre>
<p><strong>하네스 = Supervisor + Skill Agents + Memory + Config를 묶어놓은 실행 컨테이너</strong></p>
<p>LangChain 코드로 치면:</p>
<pre><code class="language-python">from langgraph.prebuilt import create_react_agent

# Skill Agent들 정의
search_agent = create_react_agent(
    model=llm,
    tools=[a, b, c]
)
math_agent = create_react_agent(
    model=llm,
    tools=[d, f]
)

# Supervisor = 그냥 또 다른 에이전트인데, &quot;tools&quot; 대신 &quot;agents&quot;를 호출
supervisor = create_react_agent(
    model=llm,
    tools=[
        Tool(name=&quot;search&quot;, func=search_agent.ainvoke, description=&quot;웹 검색&quot;),
        Tool(name=&quot;math&quot;, func=math_agent.ainvoke, description=&quot;수학 계산&quot;),
    ]
)</code></pre>
<p><strong>Supervisor의 &quot;도구&quot;가 다른 에이전트를 호출하는 함수</strong>인 겁니다.</p>
<hr>
<h3 id="솔직한-평가">솔직한 평가</h3>
<p>현재 시스템에서 도구 20개 중 오선택이 자주 발생하나요?</p>
<ul>
<li><strong>오선택 자주 발생</strong> → Skill Router 도입 가치 있음</li>
<li><strong>잘 작동하고 있음</strong> → LLM 호출 1회 추가 비용만 생기는 오버엔지니어링</li>
</ul>
<p>도구가 50개, 100개로 늘어날 예정이라면 지금 구조화해두는 게 맞고, 현재 20개 선에서 잘 돌아가고 있다면 굳이 바꿀 필요 없습니다.</p>
<hr>
<p>이렇게 보면.. 또 클로드 코드분석했을때의 tool사용하는 방법이랑 비슷한것같기도 한데..
아마 좀 더 기능을 붙이면 나중에는 deep-agent를 써야하지 않을 까 싶다..</p>
<p>제대로 알아보고 이해한게 맞는지 여러번 되묻고 찾아보기도했는데
혹시나 알게되는 다른내용이라던지.. 잘못이해한 내용있다면 언제든 피드백 환영입니다!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Claude code에서 맥락 유지관리 파해쳐보기]]></title>
            <link>https://velog.io/@ssunn_ni/Claude-code%EC%97%90%EC%84%9C-%EC%9C%A0%EC%A7%80%EA%B4%80%EB%A6%AC-%ED%8C%8C%ED%95%B4%EC%B3%90%EB%B3%B4%EA%B8%B0</link>
            <guid>https://velog.io/@ssunn_ni/Claude-code%EC%97%90%EC%84%9C-%EC%9C%A0%EC%A7%80%EA%B4%80%EB%A6%AC-%ED%8C%8C%ED%95%B4%EC%B3%90%EB%B3%B4%EA%B8%B0</guid>
            <pubDate>Thu, 02 Apr 2026 08:20:19 GMT</pubDate>
            <description><![CDATA[<p>현재 linkai 라는 LLM 멀티오케스트레이션 서비스를 구축하고 운영중이다.
항상 고민중인게 이 파이프라인과 문맥관리인데,
이번에 클로드 코드의 관련 내용을 백엔드 서버개발자 관점에서 탈탈 털어보았다.
<img src="https://velog.velcdn.com/images/ssunn_ni/post/223f4df4-c3fc-4b56-9822-f0ff47eb1502/image.png" alt=""></p>
<p>우선 위의 사진에처럼, 질문별로 문서를 작성하고, 관련해서 꼬리에 꼬리를 무는 질문을하여
문서를 구체화 한 뒤에, 블로그에 작성할 내용을 정리하도록 하였다.</p>
<hr>
<h1 id="llm-에이전트-맥락관리와-tool-orchestration-fastapi--python-실전-가이드">LLM 에이전트 맥락관리와 Tool Orchestration: FastAPI + Python 실전 가이드</h1>
<p>최근 에이전트 시스템을 운영해보면, 성능 이슈의 대부분은 모델 자체보다 두 가지에서 터집니다.</p>
<ul>
<li>맥락관리(Context Management)</li>
<li>도구 실행 제어(Tool Orchestration)</li>
</ul>
<p>이 글은 실제 CLI 에이전트 구조를 바탕으로, 왜 이 두 축이 중요한지와 FastAPI + Python으로 어떻게 최소 구현할 수 있는지 정리합니다.</p>
<h3 id="빠른-네비게이션">빠른 네비게이션</h3>
<ul>
<li>문제 정의: 왜 이 주제가 중요한가</li>
<li>설계 핵심: 설계 원칙 5가지, 아키텍처 한 장 요약</li>
<li>동작 예시: 구체 시나리오 1~4</li>
<li>구현 예시: FastAPI 미니 예제 1~3</li>
<li>운영 가이드: Prompt Caching, 운영 체크리스트, 전체 흐름 순서도</li>
</ul>
<h3 id="3줄-요약">3줄 요약</h3>
<ul>
<li>맥락관리의 핵심은 &quot;기록을 늘리는 것&quot;이 아니라 &quot;핵심 상태를 남기며 압축하는 것&quot;입니다.</li>
<li>Tool Orchestration의 핵심은 &quot;많이 병렬화&quot;가 아니라 &quot;안전한 툴만 병렬화&quot;입니다.</li>
<li>Prompt Caching의 핵심은 &quot;모든 블록 캐시&quot;가 아니라 &quot;안정된 경계 1개&quot;입니다.</li>
</ul>
<h3 id="왜-이-주제가-중요한가">왜 이 주제가 중요한가</h3>
<p>모델은 잘 대답하는데 서비스는 불안정한 이유는 보통 아래 패턴입니다.</p>
<ul>
<li>메시지 히스토리가 계속 누적되어 컨텍스트 윈도우를 압박한다.</li>
<li>툴 호출이 병렬/직렬 기준 없이 섞여 레이스 컨디션이 난다.</li>
<li>권한 확인 없이 위험한 툴이 즉시 실행된다.</li>
<li>캐시 경계가 불안정해서 요청 비용이 매 턴 급증한다.</li>
</ul>
<p>핵심은 간단합니다.</p>
<ul>
<li>토큰은 예산이고,</li>
<li>툴은 상태를 바꾸는 트랜잭션이며,</li>
<li>캐시는 경계가 안정적일 때만 이득이 납니다.</li>
</ul>
<h3 id="설계-원칙-5가지">설계 원칙 5가지</h3>
<ol>
<li>최근 대화만 남기는 것이 아니라, 중요한 상태를 요약본으로 보존한다.</li>
<li>툴은 무조건 병렬이 아니라, 안전한 툴만 병렬로 돌린다.</li>
<li>위험한 툴은 권한 게이트를 통과해야 실행한다.</li>
<li>캐시 마커는 많이 붙이는 것이 아니라, 안정된 경계 한 곳에 둔다.</li>
<li>실패(PTL, timeout, permission deny)를 정상 경로로 취급하고 재시도 정책을 둔다.</li>
</ol>
<h3 id="아키텍처-한-장-요약">아키텍처 한 장 요약</h3>
<ul>
<li>API 레이어: 모델 호출, 스트리밍 처리, usage 수집</li>
<li>Context 레이어: 히스토리 누적, 압축(snip/microcompact/autocompact), 첨부 재주입</li>
<li>Orchestration 레이어: tool_use 파싱, 병렬/직렬 실행, 결과 병합</li>
<li>Safety 레이어: 권한 정책, 사용자 승인, 위험도 분류</li>
<li>Observability 레이어: cache hit/miss, 토큰 사용량, 실패 카운트</li>
</ul>
<h3 id="구체-시나리오-1-srcauthpy-고쳐줘-요청이-들어오면">구체 시나리오 1: &quot;src/auth.py 고쳐줘&quot; 요청이 들어오면</h3>
<p>아래는 실제 운영에서 가장 자주 보는 1턴 내부 흐름입니다.</p>
<pre><code class="language-text">[1] UserMessage(&quot;src/auth.py 고쳐줘&quot;) append
        -&gt; 모델 호출

[2] AssistantMessage(text + tool_use: read_file)
        stop_reason = tool_use

[3] 런타임이 read_file 실행
        -&gt; UserMessage(tool_result: 파일 내용)
        -&gt; 같은 턴에서 모델 재호출

[4] AssistantMessage(text + tool_use: write_file)
        stop_reason = tool_use

[5] 권한 게이트 ask 발생 (write_file은 위험도 중간 이상)
        -&gt; 사용자 승인 후 실행
        -&gt; UserMessage(tool_result: 수정 완료)

[6] AssistantMessage(&quot;수정 완료&quot;)
        stop_reason = end_turn</code></pre>
<p>핵심:</p>
<ul>
<li>tool_use가 나오면 턴이 끝나는 게 아니라 follow-up 루프가 이어집니다.</li>
<li>tool_result는 user role로 다시 들어가 다음 모델 호출 컨텍스트가 됩니다.</li>
</ul>
<h3 id="구체-시나리오-2-스트리밍-이벤트가-메시지로-조립되는-방식">구체 시나리오 2: 스트리밍 이벤트가 메시지로 조립되는 방식</h3>
<p>아래처럼 네트워크 이벤트(델타)를 받아 상위 메시지로 조립합니다.</p>
<pre><code class="language-text">message_start
-&gt; content_block_start(text)
-&gt; content_block_delta(text_delta: &quot;파일을&quot;)
-&gt; content_block_delta(text_delta: &quot; 확인할게요&quot;)
-&gt; content_block_stop
    =&gt; AssistantMessage(text) 방출

-&gt; content_block_start(tool_use)
-&gt; content_block_delta(input_json_delta: &#39;{&quot;file_path&quot;:&quot;src/auth.py&quot;&#39;)
-&gt; content_block_delta(input_json_delta: &#39;}&#39;)
-&gt; content_block_stop
    =&gt; AssistantMessage(tool_use) 방출

-&gt; message_delta(stop_reason: tool_use, usage: ...)
    =&gt; 직전 메시지 메타 확정(usage/stop_reason)</code></pre>
<p>핵심:</p>
<ul>
<li>사용자 체감 출력은 빠르게 먼저 내보내고,</li>
<li>usage/stop_reason은 message_delta에서 최종 보강됩니다.</li>
</ul>
<h3 id="구체-시나리오-3-병렬직렬-오케스트레이션-결과-비교">구체 시나리오 3: 병렬/직렬 오케스트레이션 결과 비교</h3>
<p>요청된 tool plan이 아래와 같다고 가정합니다.</p>
<pre><code class="language-json">{
    &quot;calls&quot;: [
        {&quot;name&quot;: &quot;read_file&quot;, &quot;args&quot;: {&quot;path&quot;: &quot;src/auth.py&quot;}},
        {&quot;name&quot;: &quot;search_code&quot;, &quot;args&quot;: {&quot;query&quot;: &quot;login&quot;}},
        {&quot;name&quot;: &quot;write_file&quot;, &quot;args&quot;: {&quot;path&quot;: &quot;src/auth.py&quot;, &quot;patch&quot;: &quot;...&quot;}}
    ]
}</code></pre>
<p>실행 정책:</p>
<ul>
<li>read_file + search_code: 병렬 배치</li>
<li>write_file: 직렬 단독 배치</li>
</ul>
<p>결과적으로 읽기/검색은 빠르게 합쳐지고, 상태 변경은 안전하게 뒤에서 실행됩니다.</p>
<h3 id="구체-시나리오-4-캐시-마커는-많이가-아니라-안정적으로-1개">구체 시나리오 4: 캐시 마커는 &quot;많이&quot;가 아니라 &quot;안정적으로 1개&quot;</h3>
<p>잘못된 예시(경계를 여러 군데 흔듦):</p>
<pre><code class="language-ts">messages.map(m =&gt; ({
    ...m,
    content: m.content.map(c =&gt; ({
        ...c,
        cache_control: { type: &#39;ephemeral&#39;, ttl: &#39;1h&#39; },
    })),
}))</code></pre>
<p>권장 예시(요청당 message-level marker 1개):</p>
<pre><code class="language-ts">const markerIndex = skipCacheWrite ? messages.length - 2 : messages.length - 1
const payload = messages.map((m, i) =&gt; toMessageParam(m, i === markerIndex))</code></pre>
<p>핵심:</p>
<ul>
<li>cache_control은 캐시 데이터 본문이 아니라 경계 힌트입니다.</li>
<li>경계가 자주 흔들리면 cache hit rate가 급락하고 비용/지연이 증가합니다.</li>
</ul>
<h3 id="fastapi-예제-실행-방법-로컬">FastAPI 예제 실행 방법 (로컬)</h3>
<p>아래 예제들은 각각 독립 파일로 저장해서 실행할 수 있습니다.</p>
<ol>
<li>가상환경 및 패키지 설치</li>
</ol>
<pre><code class="language-bash">python -m venv .venv
source .venv/bin/activate
pip install fastapi uvicorn pydantic</code></pre>
<ol start="2">
<li>예제 파일 저장</li>
</ol>
<ul>
<li><code>example_context.py</code></li>
<li><code>example_orchestrator.py</code></li>
<li><code>example_guard.py</code></li>
</ul>
<ol start="3">
<li>서버 실행</li>
</ol>
<pre><code class="language-bash">uvicorn example_context:app --reload --port 8000
uvicorn example_orchestrator:app --reload --port 8001
uvicorn example_guard:app --reload --port 8002</code></pre>
<ol start="4">
<li>빠른 테스트</li>
</ol>
<pre><code class="language-bash">curl -X POST http://127.0.0.1:8000/chat \
    -H &#39;Content-Type: application/json&#39; \
    -d &#39;{&quot;session_id&quot;:&quot;s1&quot;,&quot;user_input&quot;:&quot;문서 20개 요약해줘&quot;}&#39;</code></pre>
<h3 id="fastapi-미니-예제-1-맥락관리-파이프라인">FastAPI 미니 예제 1: 맥락관리 파이프라인</h3>
<p>아래 예제는 턴마다 히스토리를 누적하고, 임계값을 넘으면 자동 요약(compact)을 수행합니다.</p>
<pre><code class="language-python">from fastapi import FastAPI
from pydantic import BaseModel
from typing import List, Dict

app = FastAPI()


class Turn(BaseModel):
    role: str
    content: str


class ChatRequest(BaseModel):
    session_id: str
    user_input: str


SESSIONS: Dict[str, List[Turn]] = {}

TOKEN_LIMIT = 12000
AUTO_COMPACT_THRESHOLD = 9000


def rough_token_count(turns: List[Turn]) -&gt; int:
    return sum(len(t.content) // 3 for t in turns)


def compact_history(turns: List[Turn]) -&gt; List[Turn]:
    if len(turns) &lt; 6:
        return turns

    head = turns[:-4]
    tail = turns[-4:]

    summary_text = &quot; &quot;.join(t.content for t in head)[:1200]
    summary = Turn(role=&quot;system&quot;, content=f&quot;Summary of earlier context: {summary_text}&quot;)
    return [summary] + tail


@app.post(&quot;/chat&quot;)
def chat(req: ChatRequest):
    turns = SESSIONS.setdefault(req.session_id, [])

    turns.append(Turn(role=&quot;user&quot;, content=req.user_input))

    token_est = rough_token_count(turns)
    if token_est &gt; AUTO_COMPACT_THRESHOLD:
        turns[:] = compact_history(turns)

    # 여기서 실제 LLM 호출을 수행한다고 가정
    answer = f&quot;Echo: {req.user_input}&quot;
    turns.append(Turn(role=&quot;assistant&quot;, content=answer))

    if rough_token_count(turns) &gt; TOKEN_LIMIT:
        return {
            &quot;ok&quot;: False,
            &quot;reason&quot;: &quot;context_window_exceeded&quot;,
            &quot;hint&quot;: &quot;compact 강도를 높이거나 입력 배치를 분할하세요&quot;,
        }

    return {
        &quot;ok&quot;: True,
        &quot;answer&quot;: answer,
        &quot;token_estimate&quot;: rough_token_count(turns),
        &quot;turns&quot;: len(turns),
    }</code></pre>
<p>포인트:</p>
<ul>
<li>자동 압축을 늦게 걸면 이미 PTL 근처까지 가서 실패 확률이 커집니다.</li>
<li>압축 후에도 첨부/파일 컨텍스트를 선택적으로 재주입하는 단계가 필요합니다.</li>
</ul>
<h3 id="fastapi-미니-예제-2-tool-orchestration-병렬직렬-분리">FastAPI 미니 예제 2: Tool Orchestration (병렬/직렬 분리)</h3>
<p>도구는 모두 병렬 실행하면 빨라 보이지만, 상태 변경 툴이 섞이면 깨집니다.</p>
<pre><code class="language-python">import asyncio
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Any, Dict, List

app = FastAPI()


class ToolCall(BaseModel):
    name: str
    args: Dict[str, Any]


class ToolPlan(BaseModel):
    calls: List[ToolCall]


TOOL_META = {
    &quot;read_file&quot;: {&quot;concurrency_safe&quot;: True},
    &quot;search_code&quot;: {&quot;concurrency_safe&quot;: True},
    &quot;run_terminal&quot;: {&quot;concurrency_safe&quot;: False},
    &quot;write_file&quot;: {&quot;concurrency_safe&quot;: False},
}


async def execute_tool(call: ToolCall) -&gt; Dict[str, Any]:
    # 실제 구현에서는 도구별 dispatcher 연결
    await asyncio.sleep(0.05)
    return {&quot;tool&quot;: call.name, &quot;ok&quot;: True, &quot;result&quot;: call.args}


@app.post(&quot;/orchestrate&quot;)
async def orchestrate(plan: ToolPlan):
    safe_calls = [
        c for c in plan.calls if TOOL_META.get(c.name, {}).get(&quot;concurrency_safe&quot;, False)
    ]
    unsafe_calls = [c for c in plan.calls if c not in safe_calls]

    safe_results = await asyncio.gather(*(execute_tool(c) for c in safe_calls))

    unsafe_results = []
    for c in unsafe_calls:
        unsafe_results.append(await execute_tool(c))

    return {
        &quot;parallel_count&quot;: len(safe_calls),
        &quot;serial_count&quot;: len(unsafe_calls),
        &quot;results&quot;: safe_results + unsafe_results,
    }</code></pre>
<p>포인트:</p>
<ul>
<li>읽기 전용/조회성 툴은 병렬, 상태 변경 툴은 직렬이 기본값입니다.</li>
<li>이 분리만 해도 툴 충돌과 재현 어려운 버그가 크게 줄어듭니다.</li>
</ul>
<h3 id="fastapi-미니-예제-3-권한-게이트approval-loop">FastAPI 미니 예제 3: 권한 게이트(Approval Loop)</h3>
<p>실무에서는 자동화보다 안전이 우선인 경우가 많습니다.</p>
<pre><code class="language-python">from enum import Enum
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel

app = FastAPI()


class Decision(str, Enum):
    allow = &quot;allow&quot;
    deny = &quot;deny&quot;
    ask = &quot;ask&quot;


class GuardRequest(BaseModel):
    tool_name: str
    risk: str
    user_approved: bool = False


def policy(tool_name: str, risk: str) -&gt; Decision:
    if risk == &quot;low&quot;:
        return Decision.allow
    if tool_name in {&quot;write_file&quot;, &quot;run_terminal&quot;}:
        return Decision.ask
    return Decision.deny


@app.post(&quot;/guard&quot;)
def guard(req: GuardRequest):
    d = policy(req.tool_name, req.risk)

    if d == Decision.allow:
        return {&quot;decision&quot;: &quot;allow&quot;}

    if d == Decision.deny:
        return {&quot;decision&quot;: &quot;deny&quot;, &quot;reason&quot;: &quot;policy_blocked&quot;}

    if req.user_approved:
        return {&quot;decision&quot;: &quot;allow&quot;, &quot;reason&quot;: &quot;user_approved&quot;}

    raise HTTPException(
        status_code=409,
        detail={
            &quot;decision&quot;: &quot;ask&quot;,
            &quot;reason&quot;: &quot;need_user_approval&quot;,
        },
    )</code></pre>
<p>포인트:</p>
<ul>
<li>allow/deny/ask의 3상태 모델이 운영 난이도를 크게 낮춥니다.</li>
<li>ask 상태를 UI 승인 루프로 연결하면 안전성과 자동화를 함께 가져갈 수 있습니다.</li>
</ul>
<h3 id="prompt-caching은-어떻게-봐야-하나">Prompt Caching은 어떻게 봐야 하나</h3>
<p>캐싱 관련 오해를 한 줄로 정리하면:</p>
<ul>
<li>cache_control은 캐시 경계 마커이지, 캐시 데이터 본문이 아닙니다.</li>
</ul>
<p>즉 클라이언트는 평소처럼 메시지를 보내고, 서버가 캐시를 저장/재사용합니다.
그래서 중요한 건 다음입니다.</p>
<ul>
<li>경계를 안정적으로 유지할 것</li>
<li>시스템 프롬프트/툴 스키마/베타 헤더를 자주 흔들지 말 것</li>
<li>hit rate는 cache read 토큰과 응답 지연으로 관측할 것</li>
</ul>
<h3 id="운영-체크리스트">운영 체크리스트</h3>
<ol>
<li>압축 트리거 임계값이 PTL 전에 작동하는가</li>
<li>병렬 툴과 직렬 툴의 기준이 명시되어 있는가</li>
<li>위험 툴에 대해 사용자 승인 루프가 있는가</li>
<li>캐시 경계를 턴마다 바꾸고 있지 않은가</li>
<li>실패 시 재시도와 circuit breaker가 있는가</li>
<li>비용/지연/실패 지표를 대시보드에서 보는가</li>
</ol>
<h3 id="마무리">마무리</h3>
<p>에이전트 품질은 모델 선택보다 운영 구조에서 갈립니다.</p>
<ul>
<li>맥락관리: 오래된 정보를 버리는 기술이 아니라, 중요한 상태를 보존하는 기술</li>
<li>툴 오케스트레이션: 많이 실행하는 기술이 아니라, 안전하게 실행 순서를 제어하는 기술</li>
</ul>
<p>이 두 축만 제대로 잡아도, 같은 모델로도 체감 품질과 비용 효율이 크게 달라집니다.</p>
<p>실제 운영에서 가장 효과가 큰 개선 순서는 보통 아래와 같습니다.</p>
<ol>
<li>PTL 전 자동 압축 임계값 조정</li>
<li>concurrency-safe 기준 정리(병렬/직렬 분리)</li>
<li>위험 툴 approval loop 의무화</li>
<li>캐시 경계 안정화(마커 정책 고정)</li>
</ol>
<p>즉, 모델 교체보다 먼저 오케스트레이션과 맥락정책을 고정하면, 실패율과 비용이 함께 내려갑니다.</p>
<h3 id="전체-흐름-순서도">전체 흐름 순서도</h3>
<pre><code class="language-text">[User Input]
    -&gt; messages에 UserMessage append
    -&gt; 토큰 추정 / 임계값 체크
            -&gt; (초과) compact 실행: snip/microcompact/autocompact
            -&gt; 모델 호출

모델 응답 처리:
    -&gt; Assistant 응답에 tool_use 존재?
            -&gt; No: stop_reason=end_turn -&gt; 턴 종료 / 다음 턴 대기
            -&gt; Yes:
                    -&gt; Tool plan 생성
                    -&gt; concurrency_safe 기준으로 병렬/직렬 분리
                    -&gt; 권한 정책 분기
                            -&gt; allow: tool 실행
                            -&gt; deny: 에러 tool_result 생성
                -&gt; ask: 사용자 승인 대기
                    -&gt; (백그라운드) classifier/hook 판정 작업 진행
                    -&gt; 승인 시 실행 / 거절 시 에러 tool_result
            -&gt; tool 실행 결과를 UserMessage(tool_result)로 append
            -&gt; 같은 턴에서 모델 재호출
            -&gt; tool_use가 사라질 때까지 반복

캐시 처리:
    -&gt; cache_control 경계 마커 적용
    -&gt; 요청당 message-level marker 1개 유지
    -&gt; TTL 정책 결정(사용자 eligibility + querySource allowlist)
    -&gt; skipCacheWrite면 marker를 second-to-last로 이동
    -&gt; 서버측 prompt cache read/write

백그라운드 실행 처리:
    -&gt; 장시간 작업인가?
        -&gt; Yes: run_in_background 경로로 전환, task id 반환, 턴은 계속 진행
        -&gt; No: foreground 실행 후 즉시 tool_result 반환</code></pre>
<h3 id="캐시정책백그라운드동의-루프-운영-포인트">캐시정책/백그라운드/동의 루프 운영 포인트</h3>
<ol>
<li>캐시 정책</li>
</ol>
<ul>
<li>cache_control은 캐시 데이터 본문이 아니라 경계 힌트입니다.</li>
<li>마커를 여러 개 두기보다, 요청당 1개를 안정적으로 유지해야 hit rate가 높습니다.</li>
<li>TTL 1h는 항상 적용이 아니라 eligibility + allowlist 조건을 통과해야 적용됩니다.</li>
</ul>
<ol start="2">
<li>동의(ask) 루프</li>
</ol>
<ul>
<li>ask 상태는 실행 전 대기 상태입니다.</li>
<li>대기 중에는 정책 판정 보조 작업(classifier/hook)을 백그라운드로 돌려 응답 시간을 줄일 수 있습니다.</li>
<li>승인되면 동일 tool call을 이어서 실행하고, 거절되면 에러 tool_result를 user role로 반환합니다.</li>
</ul>
<ol start="3">
<li>백그라운드 실행</li>
</ol>
<ul>
<li>백그라운드 전환은 권한 대기와 다릅니다.</li>
<li>권한 대기는 &quot;아직 실행 전&quot;, 백그라운드는 &quot;실행은 시작했고 분리 실행&quot;입니다.</li>
<li>장시간 명령은 task id 기반으로 추적하고, 후속 턴에서 상태/로그를 조회하도록 설계하는 것이 안전합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[DB쿼리에 대한 나의 생각변화]]></title>
            <link>https://velog.io/@ssunn_ni/DB%EC%BF%BC%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EB%82%98%EC%9D%98-%EC%83%9D%EA%B0%81%EB%B3%80%ED%99%94</link>
            <guid>https://velog.io/@ssunn_ni/DB%EC%BF%BC%EB%A6%AC%EC%97%90-%EB%8C%80%ED%95%9C-%EB%82%98%EC%9D%98-%EC%83%9D%EA%B0%81%EB%B3%80%ED%99%94</guid>
            <pubDate>Thu, 07 Aug 2025 05:45:55 GMT</pubDate>
            <description><![CDATA[<p>최근에 하고있는 고민들에 대해서 기록을 남아보려한다.
이전에도 계속 무엇인가를 개발할때마다 생겼던 질문들인데</p>
<blockquote>
<p>대체 좋은 쿼리는 뭘까?</p>
</blockquote>
<hr>
<h3 id="1-내가-기억하기로는-처음에-생겼던-질문중-하나는-">1. 내가 기억하기로는 처음에 생겼던 질문중 하나는 ..</h3>
<blockquote>
<p><strong>select * from 으로 해서 전부 조회한다음에 코드로 필요한부분을 쓰는게 좋나? 아니면 필요한 부분만 select하는게 좋을까?</strong></p>
</blockquote>
<p>처음 입사하고나서 이 부분에대해 사수분께 여쭤본적이 있는데, 
사실 지금 엄청 복잡한걸 건들지 않으니 크게 상관은 없을거고, 실행시켜서 해봐~</p>
<p>라는 답변을 받았었는데, 테스트를 해봐도 엄청 복잡한게 아니다보니 시간차이를 알아볼수 없어서 실패했었다. ㅋㅋㅋㅋ</p>
<p><strong>GPT</strong>는 </p>
<pre><code>운영 환경이나 실서비스 코드에서는 SELECT *를 지양하고, 필요한 컬럼만 선택하는 것이 일반적인 베스트 프랙티스입니다. 
성능, 유지보수성, 코드 명확성 측면 모두에서 이점이 크기 때문입니다.</code></pre><p>라고 답을 해주었고, 일부 동의한다.
이제는 그저 상황에 그때그때 상황에 맞게끔 판단하고있다.</p>
<hr>
<h3 id="2-두번째-생긴-질문은">2. 두번째 생긴 질문은,</h3>
<blockquote>
<p><strong>그래서 좋은 쿼리는 뭔데?</strong></p>
</blockquote>
<p>굉장히 근본적인 질문이다.
이때는 한참 쿼리 최적화에 빠져서, 여기저기 컨퍼런스가서 관련주제있으면 듣고, 
explain는 꼭 확인해보고 했었던 시기였는데, </p>
<p>그래서인가, 이때는 index잘 타고, 원하는값 나오고, 빠르고, 부하가 적고  그게 좋은쿼리 아닌가? 라고 결론을 지었던 것 같다.</p>
<hr>
<h3 id="3-현재-생긴-질문은">3. 현재 생긴 질문은..</h3>
<blockquote>
<p><strong>원하는 값은 잘 나오고 index잘 탔는데.. 그래서 그렇게 복잡한 쿼리가 나을까?
두번 조회하더라도 알아보기 편한게 좋을까?</strong></p>
</blockquote>
<p>2번때의 내가 짰던 쿼리들을 보면 한참.. join이랑 각종 것들을 엄청해서
나름 최적화시키고, db조회는 최소화로 하는 방법으로 쿼리를 작성했었다.</p>
<p>그러다보니, 이후에 해당 쿼리에 수정을 해야한다 하면.. 정말 수정하기가 복잡해졌다..
거기다가 동적쿼리도 하려고했다보니 한번 수정하기가 너무 힘들었다.
GPT가 요즘엔 도와줘서 그래도 괜찮다 싶다가도, GPT의 코딩은 좀 더 빡셀때가 많다 ㅋㅋㅋ</p>
<p>그래서 하면서도.. 이거 만든 나도 힘든데, 다른사람이 알아보기 좀 빡셀 것 같은데 라는 생각을 하고있었는데,</p>
<p>최근데 다녀왔던 <strong>2025토스컨퍼런스</strong>에서, 레거시 코드를 뜯어고친 것을 주제로 한 연사를 들었다.
다른부분은 아 그냥 묶고 분리하고 뭐 그런거군 하면서 듣고있다가,</p>
<pre><code>레거시 query가 정말 너무길고 join도 여러번에 너무 복잡해서 
다 끊어내고 알아보기쉽게했다. 그래야 에러도 덜나고 디버깅도 쉽다</code></pre><p>이런말을 하셨었는데,
아.. 나만하던 고민이 아니였구나 라는걸 깨닫고, 이 질문에 대해 다시한번 생각해보게되었다.</p>
<p>그래서 지금 현재 나의 생각은..</p>
<p>*<em>그치, 한번에 조회하는게 connection차지도 덜하고 좋지만, 
하지만 그것이 협업을 할때 유지보수에 있어 지대한 영향을 준다면 성능보다 더 앞으로 크게 작용을 할 수 있겠구나 *</em></p>
<p>이다.</p>
<hr>
<p>앞으로의 나의 생각이 어떻게 변하게 될지 나도 궁금하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[동기 비동기 그리고 async await..]]></title>
            <link>https://velog.io/@ssunn_ni/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EA%B3%A0-async-await</link>
            <guid>https://velog.io/@ssunn_ni/%EB%8F%99%EA%B8%B0-%EB%B9%84%EB%8F%99%EA%B8%B0-%EA%B7%B8%EB%A6%AC%EA%B3%A0-async-await</guid>
            <pubDate>Wed, 11 Jun 2025 02:32:35 GMT</pubDate>
            <description><![CDATA[<h3 id="1-들어가기-앞서서">1. 들어가기 앞서서..</h3>
<p>들어가기 앞서서..</p>
<p>처음 공부는 java spring으로 입문을 해서, 
회사에 들어가니 바로 javascript nodejs를 급하게 익혀서 사용해야했어서, 언어나 프레임워크의 이해도가 다소 떨어진다 느껴저서 강의를 보고....했지만.. 
따로 공부를 해도 사실 크게 체감이 되는 부분이 없어서</p>
<p>부끄럽지만 그동안 나는 단순하게</p>
<blockquote>
<p>동기 = 순서대로진행
비동기 = 처리순서가 빠른대로 진행
async await = 비동기 환경에서 동기처럼 처리하기위한 것</p>
</blockquote>
<p>이렇게만 생각을 해왔다..</p>
<h3 id="2-발단">2. 발단</h3>
<p>이번에 python을 사용해서 프로젝트를 진행하게 되어, 맞는 프레임워크를 찾아보다가
fastAPI를 입문을 하게 되었다.</p>
<p>python이 느린건.. 사실 코테공부를 python으로 했어서 느린건 익히 알고있었는데,
fastAPI의 장점이 Django나 다른 친구들보다 빠르고 간편하게 사용할 수 있다는 점이 와닿아 사용을 하게 되었는데( 급하게 시작을했어야해서 새로운 프레임워크 공부에 큰 시간을 쓸 수가 없었다)</p>
<p>사용을 하다보니 알겠더라..</p>
<p>FastAPI의 장점중 하나 &quot;빠르다&quot;가 async await을 지원한다 라는게 하나의 주요 원인중 하나였는데,</p>
<p>처음에 너무 <strong>단순</strong>하게 생각했던 나는..</p>
<p><em><strong>어차피 함수 앞에 async안붙이면 javascript랑은 다르게 동기함수가 되는건데,
비동기로 갈거  아니면 async 붙일 필요가 있어..? 라는 오만한 생각을 하게 된다..</strong></em></p>
<p>그렇게 코드를 완성하고.. 서버 api막 만들어서 프론트랑 작업을 하고 배포도 해서 테스트를 하는데..
한 부분이 느리면.. <strong>전체가 멈춰버리는 것이다..</strong></p>
<h3 id="3-전개">3. 전개</h3>
<p>아니.. 자바스크립트에서는 이런적이없는데.. 여긴 기본이 동기여서그런가..? 
하고 원인을 분석하다가</p>
<p>문뜩, async await쪽을 건들여보기 시작했다..
그러다보니 문뜩.. NodeJS공부할때 EventLoop쪽이 생각이났다..
공부했을때는.. </p>
<p>아.. 그냥 그렇구나~ 저래서 동기처럼 작동을 하는구나~
저래서 빠르구나~~  </p>
<p>하고 그냥 달달암기만 하고있었는데.. 이번에 뽝 느낌이 왔다.. 이거였구나..</p>
<h3 id="4-깨달음">4. 깨달음</h3>
<p>결론부터 얘기하자면</p>
<blockquote>
<p><strong>동기는 결과를 “기다리면서 멈추는 것”이고, 
await는 “기다리지만 멈추지 않고 다른 일을 할 수 있게 하는 것”</strong></p>
</blockquote>
<p>async await가 동기&quot;처럼&quot; 에 <strong>&quot;처럼&quot;</strong>을 깨닫는 순간이였다..</p>
<p>그러니까, fastapi의 장점중에 &quot;빠르다&quot; 가
비동기를 지원하기때문에, await를 사용하면 동기처럼 행동해도 다른곳에서 멈추지 않는다
즉, 동기만 가능한 프레임워크보다 빠르다 였구나 라는걸 한번에 확 체감 가능한 순간이였다..</p>
<p>그동안.. 자바스크립트 await이거 고생을 엄청해서.. 조금 미워했는데..
너에겐 큰 뜻이 있었구나..</p>
<h3 id="5-그래서">5. 그래서..</h3>
<p>그래서 시간이 오래걸리고, 굳이 async를 붙이지 않게해서 동기로 만든 함수들
전부 async붙이고 await사용해서 수정을했고,</p>
<p>그렇게 처리가 오래걸림으로 인해, 서비스가 멈추던 모든 이슈들이 해결되었답니다.. ㅎ</p>
<p><del>나는 진짜.. 바본가보다.. 아무리 공부해도 겪어봐야.. 아 이게 그거구나.. 하고있다 ㅠㅠ</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[langchain에서 openai토큰수 받기 고생기]]></title>
            <link>https://velog.io/@ssunn_ni/langchain%EC%97%90%EC%84%9C-openai%ED%86%A0%ED%81%B0%EC%88%98-%EB%B0%9B%EA%B8%B0-%EA%B3%A0%EC%83%9D%EA%B8%B0</link>
            <guid>https://velog.io/@ssunn_ni/langchain%EC%97%90%EC%84%9C-openai%ED%86%A0%ED%81%B0%EC%88%98-%EB%B0%9B%EA%B8%B0-%EA%B3%A0%EC%83%9D%EA%B8%B0</guid>
            <pubDate>Mon, 19 May 2025 06:39:18 GMT</pubDate>
            <description><![CDATA[<p>이번에 llm관련 프로젝트를 맡아서 하게된 이후로..
langchain에서 gpt토큰수를 받기위해 고군분투를 했던 경험기이다..
그동안 새로운거 공부하고 적용하고 일하느라 고생기가 많이 적을것이 많았는데.. 
생각나는것 먼저 작성해본다..</p>
<h3 id="1-langchain에서-지원하는-callback매니저를-사용해보기">1. langchain에서 지원하는 callback매니저를 사용해보기.</h3>
<p>무슨이유에서인지  나는 예제대로 해도  리턴받은 토큰소모량이 전부 0으로 나왔다.
그래서 이리저리 막 검색하고 해봤는데 결국.. 실패했었다..
더욱이 이때는 이제 막 langchain에 입문했던 시기였기에 다음단계로 넘어가게 되었다.</p>
<h3 id="2-ticktoken-라이브러리-사용하기">2. Ticktoken 라이브러리 사용하기.</h3>
<p>해당 라이브러리는 openai에서 제공해주는 토큰세기 라이브러리여서
이걸사용하면 되겠다~ 하고 사용하게 되었다.</p>
<p>정말 편하고 좋았는데, 문제가있다면.. 아무리 최신을 해도 o1시리즈같은건 없는 모델이라고.. 떠서
내가 임의지정한 default모델의 토큰으로 세진다는 거였다.. 정확성이 떨어졌지만 이때는 우선 이방식을 사용했다.</p>
<h3 id="3-return값으로-받았다">3. Return값으로 받았다..</h3>
<p>프로젝트가 어느정도 진행되고, langchain에 익숙해지고 langgraph도 사용해봤을 때,
1번을 다시 시도하였으나.. 실패를 했다.. <del>공식문서랑 검색이랑 진짜 다해봤는데 나한테 왜그래 ㅠㅠ</del></p>
<p>무튼 그래서 2번상태 유지중일때 의외에 곳에서 해결이 되었는데..
langchain을 주로 사용하다보니, gpt에 endpoint를 나는 잘 살펴볼 일이 없었다.
근데, 잘 만들어서 이것저것 테스트하다가 보니, o1-pro모델, o1-mini등등 
<strong>o시리즈에서 에러가 나는 것이었다...</strong></p>
<p>첫번째로는, 라이브러리랑 하나하나 뜯어 보니까 langchain자체 모듈에서 o시리즈일때, role을
developer라고 변경하는 부분이 있었는데.. o1에서는 또 자긴 그런 role없다고 에러가 났고..</p>
<p>두번째로는, langchain에서 기본적으로 invoke혹은 stream할때 사용하는 endpoint
<em><strong>v1/chat/completions</strong></em>  o1-pro 나 o1-mini에서는 제공이 안된다는 것이였다..</p>
<hr>
<p>그래서 이리저리 테스트와 검색을하다가</p>
<pre><code>  chat_model = ChatOpenAI(   
              model= 모델명,
            ...
            use_responses_api=True //이거
        )</code></pre><p>모델정의부분에서 use_responses_api = True를 설정하면, endpoint가 <strong>v1/responses</strong>
로 변경되어 나갈 수 있게 된다는 사실을 알았고, 이 방법을 사용했더니 글쎄..</p>
<p>.
.
*<em>return값으로.. 사용토큰값이 전달되었다.. *</em>
이렇게.. 사용한 토큰값 리턴받기 성공...</p>
<p>진짜 하면 할수록 느끼는건데 정말 엄청빨리 새로운게 나오고 또 변하는구나 싶다..</p>
<hr>
<h3 id="4-기타">4. 기타</h3>
<p>langgraph에서 openai시에 중간 agent응답에 대한 토큰값이 안오기 시작했다.</p>
<p>그래서 이것저것 해보다가 혹시나 하고 확인을 했더니..</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>토큰 수</th>
</tr>
</thead>
<tbody><tr>
<td>return값</td>
<td>4541</td>
</tr>
<tr>
<td>langsmith</td>
<td>4957</td>
</tr>
<tr>
<td>openai 확인</td>
<td>4541</td>
</tr>
</tbody></table>
<p>로.. langsmith의 토큰값이 안맞는다는 사실을 알았다..</p>
<p>혹시나 싶어서 다른 모델들도 각 홈페이지에서 실 토큰 사용량과 langsmith값 그리고 return값의 토큰 사용량을 비교해봤는데 미묘하게 살짝씩 다르다는걸 발견했다..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[한빛미디어 NLP와 LLM실전 튜토리얼 세미나 후기]]></title>
            <link>https://velog.io/@ssunn_ni/%ED%95%9C%EB%B9%9B%EB%AF%B8%EB%94%94%EC%96%B4-NLP%EC%99%80-LLM%EC%8B%A4%EC%A0%84-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EC%84%B8%EB%AF%B8%EB%82%98-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@ssunn_ni/%ED%95%9C%EB%B9%9B%EB%AF%B8%EB%94%94%EC%96%B4-NLP%EC%99%80-LLM%EC%8B%A4%EC%A0%84-%ED%8A%9C%ED%86%A0%EB%A6%AC%EC%96%BC-%EC%84%B8%EB%AF%B8%EB%82%98-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Thu, 10 Apr 2025 12:52:25 GMT</pubDate>
            <description><![CDATA[<p>최근 회사에서 LLM프로젝트를 담당하고있고, 기간이 넉넉치 않아서 머리싸메며 공부하면서 작업을 하고 있었는데
마침! 관련된 세미나가 있어서 다녀왔다.
(나에게 너무 필요해 ㅠㅠ)</p>
<hr>
<p><img src="https://velog.velcdn.com/images/ssunn_ni/post/d7799c98-09a8-4b7d-8c3b-9bbe59a99959/image.png" alt=""></p>
<hr>
<ul>
<li><p>책 한권을 다 놓고 돌렸을때 gpt 3.5로는 3달러, 4o mini로는 0.4달러(...?)였다고 한다.. 너무신기.. (구현에만 치우쳐서 가격을 몰랐는데 생각보다 저렴한걸..?)</p>
</li>
<li><p>api들 쓰면 다들 말 안듣는건 같구나..</p>
</li>
<li><p>가끔 코드를 llm에게 예시코드를 받기위해서 물어보면, 예전코드를 줄 때가 있다.
이때는,  <a href="https://aistudio.google.com/prompts/new_chat">https://aistudio.google.com/prompts/new_chat</a> 이와같은 사이트에 있는 코드 예제를 던져서 해당 버전으로 해달라고 하면 훨씬 최신의 정보를 얻을 수 있다.</p>
</li>
<li><p>크롤링코드 요청시, 응답, 헤더 등의 다양한 소스를 주고 요청을 하자.</p>
</li>
<li><p>hugging face : 학습시킨 모델을 공유하는 플렛폼</p>
</li>
<li><p>데이더셋의 용량이 크다면 hugging에 올려서 사용해보자.</p>
</li>
<li><p>hugging에서 한국어지원하는 모델 찾고싶다면..?
다른 한국어 모델 찾아보기 : <a href="https://huggingface.co/models?search=korean">https://huggingface.co/models?search=korean</a></p>
</li>
<li><p>RDBMS(답변 그때그때 저장용) + 벡터DB 를 사용해서 프로젝트를 하셨다고..</p>
</li>
<li><p>너무 긴 문단은 langchain chunk spliter이용해보자.</p>
</li>
<li><p>한국어 embedding 모델 </p>
<pre><code>embeddings = HuggingFaceEmbeddings(model_name=&quot;upskyy/e5-small-korean&quot;)</code></pre></li>
<li><p>vectorDB로 검색하면 정확도는 떨어진다(유사도 검색이기 때문)</p>
</li>
<li><p>embedding projector에서 유사도 ui보기 쉽다.
<a href="https://projector.tensorflow.org">https://projector.tensorflow.org</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/498b439c-f5ac-415d-bebd-2f4b00e4aa32/image.png" alt=""></p>
</li>
</ul>
<ul>
<li><p>벡터화(텍스트 숫자변환)랑 임베딩(의미포함 숫자변환)은 다르구나..</p>
</li>
<li><p>임베딩할때 참고하면 좋은 사이트(시각화)
<a href="https://damien0x0023.github.io/rnnExplainer/">https://damien0x0023.github.io/rnnExplainer/</a></p>
</li>
<li><p>오.. 벡터 토큰화 하는코드 ..</p>
<pre><code># prompt: texts 내용 tf-idf로 토큰화
</code></pre></li>
</ul>
<p>from sklearn.feature_extraction.text import TfidfVectorizer</p>
<p>tfidf_vectorizer = TfidfVectorizer()
tfidf_matrix = tfidf_vectorizer.fit_transform(texts)</p>
<h1 id="tf-idf-행렬-출력">TF-IDF 행렬 출력</h1>
<p>print(tfidf_matrix.toarray())</p>
<h1 id="tf-idf-어휘-목록-출력">TF-IDF 어휘 목록 출력</h1>
<p>print(tfidf_vectorizer.get_feature_names_out())</p>
<p>[[0.         0.         0.         ... 0.46383605 0.         0.        ]
 [0.         0.         0.         ... 0.         0.         0.        ]
 [0.         0.         0.         ... 0.36661165 0.         0.        ]
 ...
 [0.         0.         0.         ... 0.         0.         0.        ]
 [0.         0.         0.         ... 0.         0.         0.        ]
 [0.         0.         0.         ... 0.         0.         0.        ]]
[&#39;20달러로&#39; &#39;2판&#39; &#39;agi의&#39; &#39;ai&#39; &#39;ai를&#39; &#39;api를&#39; &#39;cs&#39; &#39;git&#39; &#39;github&#39; &#39;gpt&#39; &#39;llm&#39;
 &#39;mysql이다&#39; &#39;nlp와&#39; &#39;sql&#39; &#39;with&#39; &#39;가이드&#39; &#39;가이드북&#39; &#39;개발&#39; &#39;개발을&#39; &#39;개발자&#39; &#39;개발자를&#39; &#39;게임&#39;
 &#39;견고한&#39; &#39;고작&#39; &#39;공략집&#39; &#39;공부하는&#39; &#39;과학이다&#39; &#39;구조&#39; &#39;권으로&#39; &#39;그림으로&#39; &#39;글쓰기&#39; &#39;기술&#39; &#39;네트워크&#39; &#39;노트&#39;
 &#39;다섯&#39; &#39;대신&#39; &#39;데이터&#39; &#39;도메인&#39; &#39;도커&#39; &#39;디자인&#39; &#39;딥러닝&#39; &#39;라이브러리를&#39; &#39;러스트&#39; &#39;리팩터링&#39; &#39;만렙&#39; &#39;말을&#39;
 &#39;매니지먼트&#39; &#39;머신러닝&#39; &#39;면접&#39; &#39;명이&#39; &#39;모두를&#39; &#39;몽고db&#39; &#39;믿어요&#39; &#39;밑바닥부터&#39; &#39;배우는&#39; &#39;분석&#39; &#39;비밀&#39;
 &#39;비즈니스&#39; &#39;사토시의&#39; &#39;선형대수학&#39; &#39;소프트웨어&#39; &#39;수학&#39; &#39;시대&#39; &#39;시대의&#39; &#39;시작하기&#39; &#39;시작하는&#39; &#39;실무로&#39; &#39;실전&#39;
 &#39;알고리즘이다&#39; &#39;애저&#39; &#39;양자&#39; &#39;어떻게&#39; &#39;언어&#39; &#39;업무&#39; &#39;엑셀&#39; &#39;엔지니어&#39; &#39;엔지니어링&#39; &#39;완벽&#39; &#39;운영체제&#39; &#39;위한&#39;
 &#39;이것이&#39; &#39;이다&#39; &#39;이지&#39; &#39;인공지능&#39; &#39;인과추론&#39; &#39;일잘러의&#39; &#39;일타강사의&#39; &#39;자동화하기&#39; &#39;자료구조&#39; &#39;자바&#39; &#39;전문가를&#39;
 &#39;제대로&#39; &#39;주도&#39; &#39;직장인&#39; &#39;챗gpt&#39; &#39;챗gpt와&#39; &#39;처음&#39; &#39;첫걸음&#39; &#39;취업을&#39; &#39;컴퓨터&#39; &#39;코딩&#39; &#39;쿠버네티스&#39;
 &#39;테스트다&#39; &#39;통하는&#39; &#39;파이썬&#39; &#39;파이썬으로&#39; &#39;패턴&#39; &#39;퍼스트&#39; &#39;프로그래머&#39; &#39;프로그래밍&#39; &#39;프로덕트&#39; &#39;플랫폼&#39; &#39;필수&#39;
 &#39;핸즈온&#39; &#39;헤드&#39; &#39;혼자&#39; &#39;활용&#39; &#39;활용한&#39;]</p>
<pre><code>* 보통 유사도를 검색할때는, cos방식으로 사용한다(거리)방식 
-&gt; 사실이게 추천시스템이다.

* 차원축소 : 모델압축 및 모델 학습시에 주로 활용됨 (통계기법)
* 임베딩시 차원이 크면 클수록 좋지만, 모델이 계산할때 오래걸린다.
* 임베딩 모델에서 사용하는 모델을 그대로 쓰자~
* 여러문서를 llm에서 쓰고싶다면 notebookllm을 사용하자.

---

### 후기
* 그동안 내가 혼자서 했던것들이 영 틀린것은 아니였구나 하고 뿌듯..
* 흥미로운 부분도 새로운 부분도 많았다(특히 통계기법등)
* 정말 궁금한 부분이 사실 들어서 해결되지는 않았지만, 들으면 들을수록 답이 없구나.. 생각이 들었다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[nginx에서의 ip별 limit 설정하기]]></title>
            <link>https://velog.io/@ssunn_ni/nginx%EC%97%90%EC%84%9C%EC%9D%98-ip%EB%B3%84-limit-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@ssunn_ni/nginx%EC%97%90%EC%84%9C%EC%9D%98-ip%EB%B3%84-limit-%EC%84%A4%EC%A0%95%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 24 Feb 2025 06:11:12 GMT</pubDate>
            <description><![CDATA[<p>어느날 log를 보는데 대량의 api가 같은 ip로(해외) 난사가 되어있는걸 발견했다.</p>
<p>beta같은 환경은 이미 nginx에서 ip제한을 걸어두었는데,
live환경에서는 ip제한을 걸어두기가 애매해서 어떻게 할까 하다가 nginx에서 시간당 ip별 횟수제한을 걸 수 있는 방법을 찾아서 시도해보았다.</p>
<h3 id="1-설정하기">1. 설정하기</h3>
<p><code>nginx.conf</code> 또는 해당 서버의 설정 파일에서 아래와 같이  설정한다.</p>
<pre><code class="language-nginx">http {
    # IP 기반 요청 제한 설정 (초당 4번 허용)
    limit_req_zone $binary_remote_addr zone=perip:10m rate=4r/s;

    server {
        listen 80;
        server_name example.com;

        location / {
            # IP 기반 요청 제한 적용, 초당 4회 요청 이상 시 503 응답
            limit_req zone=perip burst=4 nodelay;

            proxy_pass http://backend;
        }
    }
}</code></pre>
<blockquote>
<p><code>limit_req_zone $binary_remote_addr zone=perip:10m rate=4r/s;</code></p>
</blockquote>
<ul>
<li><code>$binary_remote_addr</code>: 클라이언트의 IP 주소를 기준으로 제한.</li>
<li><code>zone=perip:10m</code>: 공유 메모리 영역(perip)을 10MB 크기로 설정(약 16만 개의 IP 저장 가능).</li>
<li><code>rate=4r/s</code>: 초당 4번의 요청만 허용.</li>
</ul>
<blockquote>
<p> <code>limit_req zone=perip burst=4 nodelay;</code></p>
</blockquote>
<ul>
<li><code>burst=4</code>: 초과 요청을 최대 4개까지 허용하고 이후부터는 제한.</li>
<li><code>nodelay</code>: burst 허용 내에서도 즉시 처리(지연 없음) / 
 nodelay가 없다면 초과 요청이 발생하면 순차적으로 대기 후 처리된다.</li>
<li>요청이 <code>4r/s</code>를 초과하면 <code>503</code>(서비스 사용 불가) 응답.</li>
</ul>
<hr>
<p>이렇게 설정해주면 된다.. (from GPT)</p>
<h3 id="이후-이슈">이후 이슈</h3>
<p>이렇게 설정을 했더니.. 해당 도메인을 타고오는 모든 url에서 ip제한이 걸리기 시작했다..
대표적으로.. 프론트화면도 이미지같은것 렌더링시 지연 혹은 에러 이슈가 발생했다.</p>
<p>그래서, 처음에 이것저것 설정을 해보았고 결과적으로 서버에 오는것만 ip제한을 걸도록 범위를 구체화하였다.</p>
<pre><code class="language-nginx">    location / {
        limit_req zone=perip burst=4 nodelay;
        proxy_pass http://backend;
    }</code></pre>
<p>이렇게 설정하면, 해당 도메인 하위단의 주소 모두 ip제한이 걸려서</p>
<pre><code>    location /api {
        limit_req zone=perip burst=4 nodelay;
        proxy_pass http://backend_api;
    }
}</code></pre><p>이렇게, 만약 서버가로 가는 주소가 /api 라 한다면.. 
위에처럼 서버주소로 가는 주소들만 ip제한이 걸리도록 수정했다.</p>
<hr>
<p>이렇게 설정하면, 부하도 방지할 수 있고, ip제한도 걸 수 있다.
설정하지 않았을 때, default값은 서버가 처리할수 있는 양만큼의 값들을 모두 수용한다고 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[mysql 프로시저.. 묵시적 형변환을 조심하자..]]></title>
            <link>https://velog.io/@ssunn_ni/mysql-%ED%94%84%EB%A1%9C%EC%8B%9C%EC%A0%80..-%EB%AC%B5%EC%8B%9C%EC%A0%81-%ED%98%95%EB%B3%80%ED%99%98%EC%9D%84-%EC%A1%B0%EC%8B%AC%ED%95%98%EC%9E%90</link>
            <guid>https://velog.io/@ssunn_ni/mysql-%ED%94%84%EB%A1%9C%EC%8B%9C%EC%A0%80..-%EB%AC%B5%EC%8B%9C%EC%A0%81-%ED%98%95%EB%B3%80%ED%99%98%EC%9D%84-%EC%A1%B0%EC%8B%AC%ED%95%98%EC%9E%90</guid>
            <pubDate>Fri, 14 Feb 2025 07:18:26 GMT</pubDate>
            <description><![CDATA[<p>이번에 서버 업데이트를 하나 했는데, 
갑자기 엉뚱한 부분에서 에러가 터졌다.</p>
<p>그저 프로시저실행하는 코드고 해당 코드는 건든적이 없었는데..
프로시저 안에 일이기 때문에 아무리 디버깅을 해봐도 소용이 없었다..</p>
<p>이리 저리 로그를 찍어보다가...  깨달았는데</p>
<p>&quot;1234455673939&quot;이런식으로 <strong>string</strong>으로 전달되었던 인자가 프로시저 내부에서
&quot;1.xxxxxx&quot;형태의 <strong>부동소수점형태</strong>로 변환되어 전달되고 있었다..</p>
<h2 id="1-문제-원인">1. 문제 원인</h2>
<p>해당 내용에 대해 gpt에게 물어보니
<img src="https://velog.velcdn.com/images/ssunn_ni/post/53f39c8a-5bb0-43e1-80fc-33291bf43483/image.png" alt=""></p>
<p>이러한 대답을 받을 수 있었다.. 
내부적으로 mysql에서 <strong>숫자</strong>형태의 varchar을 <strong>부동소수점방식</strong>으로 변환한다는것이다..</p>
<h2 id="2-해결방법">2. 해결방법</h2>
<p>mysql의 묵시적 형 변환에 대해 구글링을 해봐도, 요즘에 프로시저를 잘 사용하지 않는것인지
많은 검색결과를 얻을 수 없었다.</p>
<p>그래서 gpt에게 다시 물어본 결과</p>
<hr>
<p>✅ 방법 1: 명확하게 문자열로 감싸기
쿼리 실행 시 강제로 문자열로 취급하도록 따옴표(&#39;&#39;)를 사용하세요.</p>
<pre><code>INSERT INTO my_table (my_varchar_column) VALUES (&#39;109958166097066644827&#39;);
</code></pre><p>✅ 방법 2: CAST() 또는 CONVERT() 사용하기
MySQL에서 명시적으로 VARCHAR로 변환하여 저장</p>
<pre><code>INSERT INTO my_table (my_varchar_column) VALUES (CAST(109958166097066644827 AS CHAR));
INSERT INTO my_table (my_varchar_column) VALUES (CONVERT(109958166097066644827, CHAR));
SET my_varchar_column = CAST(my_varchar_column AS CHAR);

</code></pre><hr>
<p>의  대답을 얻을 수 있었다.</p>
<p>처음엔 프로시저를 먼저 고쳐봤다.
인자를 받을 varchar을 64 -&gt; 255 로 늘려도보고, 2번의 변환도 시도해보았다.</p>
<p>다만 문제는.. 프로시저에 인자를 넘어올때 <strong>이미 mysql에서 변환을 해서</strong> 넘어와서, 
프로시저 내부에서 변환을 해도 <strong>이미 변환된 값을 string형태</strong>로 받을 뿐이였다.</p>
<p>그래서 다음으로 방법1을 선택을 했지만.. 
해당 방법또한 나에게 는 해결방법이 되어주지 않았다.</p>
<h2 id="3-결론">3. 결론</h2>
<p>그래서 마지막으로 나랑 사수님과 같이 해결방법을 모색하였는데..</p>
<blockquote>
<ol>
<li>문제가 되는 부분이 첫문단이니, 첫 문단을 프로시저에서 분리하자.</li>
<li>숫자형태가 문제라면, base64로 값을 변환해서 값을 보내고, 프로시저내부에서 풀어서 해결</li>
</ol>
</blockquote>
<p>이 중에서사실 어떤걸 먼저해도 상관없었지만..
기존의 프로시저 형태는 계속 가져가고싶어서 2번을 택하였고 (1번을 최후통첩으로 남겨놓았다)</p>
<p>2번방법은 동작하였다.</p>
<h2 id="4-마지막으로">4. 마지막으로..</h2>
<p>아직도 해결되지 않은 의문점이 있는데..
<img src="https://velog.velcdn.com/images/ssunn_ni/post/0bbb814c-814a-481d-9dd0-494359ed6aab/image.png" alt=""></p>
<p>서버를 껐다 키면 또 프로시저 내부에서 묵시적 형 변환이 일어나지 않는다..
처음엔 <strong>30분</strong>정도 있다가부터 에러가 났고,</p>
<p>그 다음에  2번 해결과정의 
gpt가 말해준 1번 방법(명확하게 string형태로 바인딩하기) 으로
인자를 바인딩할때 String()으로 형태를 지정해서 넘겨눴다니 <strong>4시간</strong> 이후부터 에러가 나기 시작했다.</p>
<p>그래서 문제를 해결할때 진짜 해결이 된건지 테스트하기가 정말.. 어려웠다...
소스코드 하나만 바꿔서 테스트할라해도 몇시간을 기다려야하니..</p>
<p>정말 왜 그런거지..? 
gpt에 물어보면 세션을 날려보던지 해봐라 그러던데.. 음..</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[첫 오픈소스 도전기! (Open Contribution Jam 2024)]]></title>
            <link>https://velog.io/@ssunn_ni/Open-Contribution-Jam-2024</link>
            <guid>https://velog.io/@ssunn_ni/Open-Contribution-Jam-2024</guid>
            <pubDate>Wed, 27 Nov 2024 14:57:39 GMT</pubDate>
            <description><![CDATA[<p>다양한 컨퍼런스를 다니며, 오픈소스에 관해서 관심이 많이 생겼었던것 같다.
GDC에서 하는 오픈소스 컨퍼런스를 깜빡하고 신청하지 못해 절망하던와중에,
오픈소스를 무려 참여까지 도와주는 <strong>Open Contribution Jam 2024</strong> 를 발견하고 곧바로 신청하게 되었다.</p>
<hr>
<h3 id="-이번에-참여한-오픈소스">* 이번에 참여한 오픈소스</h3>
<p><img src="https://velog.velcdn.com/images/ssunn_ni/post/feeea8e1-4b4b-42bc-8b03-5d95639497bb/image.png" alt=""></p>
<p>해당 오픈소스는 ActivityPub 및 기타 표준(소위 fediverse) 으로 구동되는 연합 서버 앱을 구축하기 위한 TypeScript 라이브러리이다.
패더버스라는 개념도 activity pub이란 개념도 매우 생소하였지만 꽤나 흥미로워서 해당 오픈소스로 도전하게 되었다.
<a href="https://fedify.dev/">홈페이지</a> <a href="https://github.com/dahlia/fedify">Github</a></p>
<hr>
<h3 id="-후기">* 후기</h3>
<p>처음 오픈소스에 도전해보는거고, 사용을 안해본건 아니지만 대부분 내가 사용했던것들은 이미 많은 레퍼런스들이 있던 것들이였어서 참고할것들이 많았었는데 문서 하나하나 뜯어보는건 굉장히 낮선 경험이였고 또 한편으로는 진짜 메인테이너분 5년차라고 들었는데 대단하다.. 라는 생각밖에 들지 않았다.</p>
<p>내가 정말 엄청난것을 기여한것도 아니고 그저 쉬운 이슈들을 메인테이너분의 도움을 받아서..
하나하나해보고 PR을 올려 머지까지 해보았는데, 색다른 기분이 들었다.</p>
<p><img src="https://velog.velcdn.com/images/ssunn_ni/post/306ab403-1c82-4474-9ba4-a880e116fdfa/image.png" alt=""></p>
<p>메인테이너분의도움으로 PR도 해보았는데 이렇게 내 이름도 릴리즈 노트에 작성해주시다니..
너무 감사했습니다...!! </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[모니터링 구축하기 ( Grafana + Prometheus + query-exporter + node-exporter )]]></title>
            <link>https://velog.io/@ssunn_ni/%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EA%B5%AC%EC%B6%95-Grafana-Prometheus-query-exporter-node-exporter</link>
            <guid>https://velog.io/@ssunn_ni/%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EA%B5%AC%EC%B6%95-Grafana-Prometheus-query-exporter-node-exporter</guid>
            <pubDate>Wed, 27 Nov 2024 07:20:01 GMT</pubDate>
            <description><![CDATA[<p>이전에는 그냥 로깅만 하면 되겠지 했었는데, 시간이 지나고 사이즈가 커지고
그와중에 서버비용의 최적화를 위해 서버스팩을 줄이다보니.. 점점 알 수 없는 것들이 생겼다.</p>
<p>우아콘에 컨퍼런스에 갔을 때, 항상 고민이였던 모니터링툴에대해 듣게 되었고 검색했고
한번 해볼만 하다는 생각이 들어 시도하게 되었다.</p>
<p>구축할 환경은 
실제 코드들이 돌아가는 서버들, 그리고 모니터링 서버 하나이다.</p>
<p>처음에 가장 헷갈렸던것들이 어떤걸 어느 서버에 깔아야하는가였지만
여러번 삽질과.. 시도 끝해 어느정도 감을 잡은 것 같다.</p>
<p>query-exporter, node-exporter 와 같은 관측을 위한 것들은 모니터링 대상 서버에,
Grafana, Prometheus 와 같은 모니터링을 위한 도구는 모니터링서버에서 실행시키면 된다.</p>
<blockquote>
<p><strong>로그</strong>는 서버가 동작할 때 서버의 상태와 동작 정보를 시간 경과에 따라 기록된 결과입니다. 로그는 시스템의 오류와 문제들을 쉽게 찾아낼 수 있도록 도와줍니다. 
반면, <strong>메트릭</strong>은 시스템의 성능과 상태에 대한 통계적인 정보를 의미합니다. 메트릭을 잘 수집하면 시스템의 현재 상태를 손쉽게 파악할 수 있고, 사업 현황에 관한 유용한 정보를 얻을 수 있습니다. 가령, 메트릭은 DAU, Retension, CPU 사용량, 메모리 사용량 등이 있습니다.</p>
</blockquote>
<p><del>아직은 베타에서 테스트중이라 완벽하게 정리되면 마저 작성하겠다.</del>
.</p>
<hr>
<h3 id="1-모니터링-서버에-그라파나-프로메테우스-로키-파일을-설정해주기">1. 모니터링 서버에 그라파나, 프로메테우스, 로키 파일을 설정해주기.</h3>
<p>소스를 받아서, 각자의 환경에 맞는 셋팅을 해주기
readme를 (한국어로도 있음) 통해서 알맞게 셋팅을 해준다.
<a href="https://github.com/Heesunni/grafana_loki_prometheus">https://github.com/Heesunni/grafana_loki_prometheus</a></p>
<p>이후에 잘 켜졌는지각각 확인</p>
<pre><code>http://localhost:3003        //그라파나 확인
http://localhost:9100        //프로메테우스 확인
http://localhost:3100/ready //로키 확인</code></pre><p>별다른 포트를 설정하지 않았다면 위의 포트들이 기본 포트이다.</p>
<p>이후에, 그라파나에 들어가서</p>
<pre><code>Home &gt; Connections &gt; Add new connection</code></pre><p>에 들어가 Loki, Prometheus를 각각 연결해주고( url 각각을 킨 주소를 작성하면 됨),
하단에 Save&amp;test까지 눌러서 통과하면 끝!</p>
<h3 id="2-관측할-서버에-promtail-exporter을-심어서-실행시켜주기">2. 관측할 서버에 promtail, exporter을 심어서 실행시켜주기</h3>
<p>관측대상 서버에 promtail, exporter을 알맞게 실행시킨다.</p>
<p>원래는 각각의 프로그램들을 따로 다운받아서 켜야하는데, 나의 경우 다 다운받아서 한 파일에 정리해두고 다른 서버에서도 같은 설정으로 사용하려고 이렇게 만들어놨다.</p>
<p>다른 블로그들을 보면 systemctl이나 service등록해서 사용하던데 
나는 pm2로 프로그램들을 켰다.</p>
<p><a href="https://github.com/Heesunni/promtail-exporter">https://github.com/Heesunni/promtail-exporter</a></p>
<hr>
<h3 id="3-결론">3. 결론</h3>
<p>그라파나에서 제공해주는 예시 대시보드들로 mysql, linux서버, nodejs 모니터링을 구축을 했고, loki는 원하는 대시보드가 없어서 한참 만지작만지작 거리다가 gpt와 구글링으로 대시보드를 하나 내가 직접 구축했고, 지금은 회사에 큰 모니터에 띄어져있다 ㅎ
아직 계속 연구중이지만 하나하나 대시보드가 업데이트가 되어가는중</p>
<p>아직 모르는거 투성이라 계속 연구중이지만, 회사에 띄어져있는 대시보드를 보면 뿌듯하다 ㅎ</p>
<p><img src="https://velog.velcdn.com/images/ssunn_ni/post/4a832584-2b91-4bc4-be31-2c5043f46bee/image.png" alt="">
<del>이건 로키이용해서 쌓인 log분석해주는거 만들었다</del></p>
<hr>
<p>++추가내용</p>
<blockquote>
<p>docker로 그라파나, 로키, 프로메테우스를 작동시켰었는데.. 어느순간부터 용량이슈가 발생했다.</p>
</blockquote>
<pre><code>sudo du -sh /var/lib/docker/containers/* | sort -h</code></pre><p>해당 명령어를 통해, docker에서 어떤파일이 가장 많은 용량을 차지하는지 확인을 해봤는데
로키의 로그파일이 21기가나 먹고있었다..</p>
<p>우선 불필요한 내용들을 삭제해주고</p>
<pre><code>
// docker 컨테이너별 Root FS 사용량 확인하기
docker system df

// docker 전체 정리( 사용하지 않는정보 정리)
docker system prune -af                

// 사용하지 않는 볼륨 확인
docker volume ls -f dangling=true   

//사용하지 않는 볼륨 삭제
docker volume prune -f                

// 로그파일 모두 삭제
for log in $(docker inspect --format=&#39;{{.LogPath}}&#39; $(docker ps -aq)); do
  sudo truncate -s 0 &quot;$log&quot;;
done
</code></pre><p>용량제한 설정 docker.yml파일에 추가하고, </p>
<pre><code>--storage.tsdb.retention.time=90d
--storage.tsdb.retention.size=5GB</code></pre><p>도커 데몬 설정도 변경</p>
<pre><code>sudo nano /etc/docker/daemon.json

{
  &quot;log-driver&quot;: &quot;json-file&quot;,
  &quot;log-opts&quot;: {
    &quot;max-size&quot;: &quot;10m&quot;,
    &quot;max-file&quot;: &quot;3&quot;
  }
}

sudo systemctl restart docker
</code></pre><hr>
<p>[참고링크] </p>
<p><a href="https://solo5star.dev/posts/50/">사용자 수 모니터링 빠르게 시작하기 (Grafana + Prometheus)</a>
<a href="https://brunch.co.kr/@hansungdev/36">Prometheus/Grafana 모니터링 구축</a>
<a href="https://velog.io/@hansung/PrometheusGrafana-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EA%B5%AC%EC%B6%95">Prometheus/Grafana 모니터링 구축</a>
<a href="https://velog.io/@sojukang/%EC%84%B8%EC%83%81%EC%97%90%EC%84%9C-%EC%A0%9C%EC%9D%BC-%EC%89%AC%EC%9A%B4-Prometheus-Grafana-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EC%84%A4%EC%A0%95">세상에서 제일 쉬운 Prometheus - Grafana 모니터링 설정</a>
<a href="https://medium.com/naver-biz-dev/node-js-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EA%B5%AC%EC%B6%95%EA%B3%BC-%ED%8A%B8%EB%9F%AC%EB%B8%94-%EC%8A%88%ED%8C%85-%EC%82%AC%EB%A1%80-part-1-%EB%AA%A8%EB%8B%88%ED%84%B0%EB%A7%81-%ED%99%98%EA%B2%BD%EA%B5%AC%EC%B6%95-b91246d9977f">Node.js 모니터링 시스템 구축과 트러블 슈팅 사례(Part 1 — 모니터링 환경구축)</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024 현대카드 Tech Talk]]></title>
            <link>https://velog.io/@ssunn_ni/2024-%ED%98%84%EB%8C%80%EC%B9%B4%EB%93%9C-Tech-Talk</link>
            <guid>https://velog.io/@ssunn_ni/2024-%ED%98%84%EB%8C%80%EC%B9%B4%EB%93%9C-Tech-Talk</guid>
            <pubDate>Thu, 07 Nov 2024 03:00:52 GMT</pubDate>
            <description><![CDATA[<p>컨퍼런스 일정 2개가 동시에 있었는데, 다행이 시간대가 겹치지 않아서
저녁에 현대카드 Tech Talk에 다녀올 수 있게 되었다!</p>
<blockquote>
<p>2024.11.05 2024 현대카드 Tech Talk
서울 용산구 이태원로 246 언더스테이지</p>
</blockquote>
<p>.</p>
<h3 id="간단정리">간단정리</h3>
<hr>
<ul>
<li>현대카드에서 ai기술을 접목시켜 내부에서의 데이터를 분류 및 커스텀할 수 있는 기능들을 개발함. </li>
<li>개발한 기술을 내부에서만이 아니라 해외회사에도 판매.</li>
<li>원 데이터들을 갖고 → 1차가공 → 2차가공 등등을 거쳐 태그로 데이터들을 분류</li>
<li>시뮬레이션 결과 세일러분들이 해당 ai를 이긴적은 없음</li>
<li>kafka대신 에어플로우 사용</li>
<li>로그들을 모아 엘라스틱 서치 사용</li>
</ul>
<p>.</p>
<h3 id="후기">후기</h3>
<hr>
<p>간단하게 정리하자면 </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2024 우아콘 정리]]></title>
            <link>https://velog.io/@ssunn_ni/2024-%EC%9A%B0%EC%95%84%EC%BD%98-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@ssunn_ni/2024-%EC%9A%B0%EC%95%84%EC%BD%98-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 07 Nov 2024 02:55:32 GMT</pubDate>
            <description><![CDATA[<p>2024년도 컨퍼런스 인프콘.. 카카오콘, 우아콘 전부 떨어지고 절망하고있었는데
우연히 양도받을 수 있어서 올해 첫 컨퍼런스에 갈 수 있게 되었다. 감사합니다😭</p>
<blockquote>
<p>2024.10.25 우아콘
삼성역 인터네셔널 호텔
<a href="https://2024.woowacon.com/">https://2024.woowacon.com/</a></p>
</blockquote>
<p>.</p>
<h3 id="간단정리">간단정리</h3>
<hr>
<ul>
<li>백엔드세션기준으로 kafka를 많이 사용하는것같다.</li>
<li>kafka를 사용하면서의 에로사항 및 대용량서버 핸들링 방법 → 비동기처리</li>
<li>대부분의 백엔드 세션이 대용량처리 및 워커에 치중된듯</li>
</ul>
<p>.</p>
<h3 id="후기">후기</h3>
<hr>
<p>나름 듣고싶던 세션 전부 다 들을 수 있었고, 음향도 좋았고 정말 열심히 준비하셨다는게 보였다는 행사였다.
대용량 처리 및 메세지 큐 관련해서 고민이 많았는데 kafka라는걸 정말 많이 쓰는구나라는걸 깨달을 수 있었고 관련해서 한번 공부해야겠다라는 생각이 많이 들었다. 여러 열정적인 사람들과 같은공간에서의 좋은 에너지를 받을 수 있어서 좋았다.</p>
<p>그리고 개인적으로는 나름 3년차이고 곧 4년차가 다되어가는데 내가 하는건 빙산의 일각이라는 생각이 들었다.
관련해서 내가 앞으로는 더 어떻게 해야할까 라는 생각이 많이 들었던 하루였다. </p>
<p>.</p>
<h3 id="세션을-들으며-간단-메모내용">세션을 들으며 간단 메모내용</h3>
<hr>
<p><a href="https://www.notion.so/2024-12fa910a64f080b38321ca003ca8d4e3?pvs=4">https://www.notion.so/2024-12fa910a64f080b38321ca003ca8d4e3?pvs=4</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[메가존소프트&GCP세미나]]></title>
            <link>https://velog.io/@ssunn_ni/%EB%A9%94%EA%B0%80%EC%A1%B4%EC%86%8C%ED%94%84%ED%8A%B8GCP%EC%84%B8%EB%AF%B8%EB%82%98</link>
            <guid>https://velog.io/@ssunn_ni/%EB%A9%94%EA%B0%80%EC%A1%B4%EC%86%8C%ED%94%84%ED%8A%B8GCP%EC%84%B8%EB%AF%B8%EB%82%98</guid>
            <pubDate>Thu, 07 Nov 2024 02:29:54 GMT</pubDate>
            <description><![CDATA[<h3 id="ai-시대-레거시-시스템을-cloud-native-ai-서비스로">AI 시대, 레거시 시스템을 Cloud Native AI 서비스로!</h3>
<blockquote>
<p>2024.11.05 구글&amp;메가존소프트 주최
삼성역 스페이스쉐어 
세미나 정보  <a href="https://www.megazonesoft.com/appmodseminar-20241105/">https://www.megazonesoft.com/appmodseminar-20241105/</a></p>
</blockquote>
<h3 id="간단정리"><strong>간단정리</strong></h3>
<hr>
<ul>
<li>컨테이너 기반 서비스들, AI의 서버 배포 및 마이그레이션툴로는 GKE와 Cloud Run이 있다.</li>
<li>GKE와 CloudRun의 각 특장점</li>
</ul>
<p>.</p>
<h3 id="내용-필기"><strong>내용 필기</strong></h3>
<hr>
<p><a href="https://www.notion.so/AI-Cloud-Native-AI-135a910a64f080138d41f736d86bef58?pvs=4">https://www.notion.so/AI-Cloud-Native-AI-135a910a64f080138d41f736d86bef58?pvs=4</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[websocket을 이용한 채팅서버개발 고생기(2)]]></title>
            <link>https://velog.io/@ssunn_ni/websocket%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B1%84%ED%8C%85%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EC%83%9D%EA%B8%B02</link>
            <guid>https://velog.io/@ssunn_ni/websocket%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B1%84%ED%8C%85%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EC%83%9D%EA%B8%B02</guid>
            <pubDate>Tue, 24 Sep 2024 02:34:23 GMT</pubDate>
            <description><![CDATA[<p><a href="https://velog.io/@ssunn_ni/websocket%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B1%84%ED%8C%85%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EC%83%9D%EA%B8%B0">websocket을 이용한 채팅서버개발 고생기(1)</a>
1편에 이어 .. 마저 작성</p>
<h3 id="3-읽음처리하기">3. 읽음처리하기</h3>
<p>.
상대방 읽음처리가 가장 어려운 난제중에 하나였다.
이 부분에서 정말 많이 헤맸는데 우선 헤맸던 포인트가 있다.</p>
<blockquote>
<ol>
<li>앱이라면 괜찮지만 웹은 유저가 이 방에서 나가도 프론트에서 정확하게 트래킹이 어려웠다.</li>
<li>그렇다보니 해당 채팅방에서 나가서 다른 채팅방에 갔는데, socket을 쏘니 방 a의 채팅에 방 b에서의 채팅이 가기시작하고, 혹은 a를 읽었는데 b방의 채팅을 읽음이 전송되었다.</li>
</ol>
</blockquote>
<p>이러한 이슈들로 인해 앞선 고생기(1)에서의 2번(전면 구조변경) 을 채택하고, 
socket의 room에 저장하는 방식들을 전면 수정을 했다.</p>
<blockquote>
<ol>
<li>상대방이 A방에 채팅을 보낸다. 그러면 A방의 다른유저에게 new채팅에 대한 socket을 보내게 된다. </li>
<li>A방에 내가 있다면(채팅창에 활성화 상태라면) 이때 내가 해당 채팅방에서 읽었음에 대한 정보를 서버로 보내게끔 했다.</li>
<li>이렇게 &quot;나 읽음&quot; 이라는 socket을 수신받는다면 서버에서는 내가 아닌 상대방이 보낸 메세지를 모두 읽음처리하고, A방에 읽음처리가 완료되었음을 내가아닌 다른사람에게보내, 상대방이 내 채팅을 읽었음을 알 수 있게 한다. </li>
<li>내가 해당 파티채팅을 리로딩할때도 마찬가지로 읽음처리 루틴을 실행시킨다.</li>
</ol>
</blockquote>
<p>이 외에도,</p>
<p>최대한 서로 다른방의 <strong>간섭</strong>을 막아주기 위해, 채팅방에서 나와 <strong>목록</strong>으로 들어갈때 <strong>모든 방에서 leave</strong>를 했고, 새로운 <strong>채팅방에 접속</strong>할때 또한 <strong>그 방을 제외한 나머지 방에서 leave</strong>하도록 하는 등의 다양한 방법을 써서 구현을 했다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[대규모 서버 업데이트를 끝마치고.. 회고 (링키드 2.5에서 3.0 버전업)]]></title>
            <link>https://velog.io/@ssunn_ni/%EB%8C%80%EA%B7%9C%EB%AA%A8%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%EB%A5%BC-%EB%81%9D%EB%A7%88%EC%B9%98%EA%B3%A0..-%ED%9A%8C%EA%B3%A0-%EB%A7%81%ED%82%A4%EB%93%9C-2.5%EC%97%90%EC%84%9C-3.0-%EB%B2%84%EC%A0%84%EC%97%85</link>
            <guid>https://velog.io/@ssunn_ni/%EB%8C%80%EA%B7%9C%EB%AA%A8%EC%97%85%EB%8D%B0%EC%9D%B4%ED%8A%B8%EB%A5%BC-%EB%81%9D%EB%A7%88%EC%B9%98%EA%B3%A0..-%ED%9A%8C%EA%B3%A0-%EB%A7%81%ED%82%A4%EB%93%9C-2.5%EC%97%90%EC%84%9C-3.0-%EB%B2%84%EC%A0%84%EC%97%85</guid>
            <pubDate>Thu, 12 Sep 2024 06:41:17 GMT</pubDate>
            <description><![CDATA[<p>2년정도 나를 괴롭히던 대규모 업데이트를 드디어
2024-09-10일에 끝마췄다.</p>
<p><del>물론 끝마쳤다고 끝은 아니고.. 뒤에 잔 버그들 관리는 계속해야하지만..</del></p>
<p>링링이들은 더 귀엽고 예쁘게 변했고,
처음에는 똑같다고 생각했던 UI도 더 예뻐진게 쓰면쓸수록 체감하게 된다. 
<del>( 버전업 마이그레이션 테스트한다고 이전버전으로 돌렸다가 버전업시키고 했을때마다 예전께 투박하다고 느껴졌다 )</del></p>
<h3 id="버전업시-가장-어려웠던점">버전업시 가장 어려웠던점.</h3>
<ol>
<li>기존에 돌아갔던 로직들은 그대로 잘 돌아가야한다.</li>
<li>기존의 DB구조 틀에서 많이 벗어나면 안된다. 쓰레기데이터도 품고가야함..</li>
<li>운영을 위한 데이터셋이 전부 변경되어 그 데이터셋 구조를 맞춰야하지만.. 기존의 값들도 잘 돌아가게해야한다..</li>
</ol>
<p>등등.. 즉, </p>
<blockquote>
<p><strong>달리는 마차의 바퀴는 바꾸지만, 바꾸는 과정에서도 잘 돌아가게 하는것이 가장 힘든 작업이었다.</strong></p>
</blockquote>
<p>로직들, 저장하는 방법, 불러오는방법 모~~ 두 변경되었는데 기존꺼는 기존대로 잘 돌아가야한다니.. <strong>정말 새로운걸 만드는것보다 이게 몇배는 힘들었다.</strong></p>
<p>거기다가, 2.5라이브는 계속 돌아가고있는데 2.5라이브 서버대로 업데이트 해야하지.. 그러면 그 파일은 3.0서버에도 적용시키고.. 로컬에도 적용시키고 3.0 베타에도 적용시키고..</p>
<blockquote>
<p><strong>한번 업데이트 할때마다 온 파일들을 전부 적용시켜야했다..</strong>
<del>하나만이 아니고 어드민, 채팅 전부 그랬다..</del> </p>
</blockquote>
<p>한번에 여러 파일들을 관리하다보니 정말 너무 헷갈리고 어려움이 많았는데
인력이 부족한 상황에, 기존의 라이브버전은 계속 돌아가고 있으며 추가적으로 업데이트가 지속적으로 필요했던 상황이라 3.0 업데이트가 많이 딜레이가 지속되어 정말 힘들었다..</p>
<hr>
<p>그 과정에서 얻는것도 있었고, 정말 건들기 무서웠었던 레거시코드도 다 분해해서 수정하고 다양한 경험을 했다. <del>( 정말 무서운 경험이었다)</del></p>
<p>원래 코드는 개인의 자유이자 돌아가기만 하면 그만이라 하시던 사수님이 내 코드가 점점 잼있어지고있다고했다. 
점점 활용성을 기반으로 코드를 만들고있다고 
( 모듈화를 하려고 노력했더니 그런듯! )</p>
<hr>
<p>아직 계속 이슈 모니터링을 하고 최적화 및 수정을 해야하지만, 
정말 나를 너무나도 길었던 작업 끝내서 너무 뿌듯하고
이 경험이 나를 더 성장시켰을것이라고 믿는다!</p>
<p>예쁜 링키드 구경오세요
<a href="https://linkid.pw/">https://linkid.pw/</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/88fe3413-f6c0-4f3a-aeb5-a5d66e06f081/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[gcp에서 갑자기 nginx가 안먹는다면..?]]></title>
            <link>https://velog.io/@ssunn_ni/gcp%EC%97%90%EC%84%9C-%EA%B0%91%EC%9E%90%EA%B8%B0-nginx%EA%B0%80-%EC%95%88%EB%A8%B9%EB%8A%94%EB%8B%A4%EB%A9%B4</link>
            <guid>https://velog.io/@ssunn_ni/gcp%EC%97%90%EC%84%9C-%EA%B0%91%EC%9E%90%EA%B8%B0-nginx%EA%B0%80-%EC%95%88%EB%A8%B9%EB%8A%94%EB%8B%A4%EB%A9%B4</guid>
            <pubDate>Tue, 16 Jul 2024 07:49:15 GMT</pubDate>
            <description><![CDATA[<p>gcp서버 업데이트를 위해 서버를 껐다가 켰다.
그리고 pm2를 사용하여 안에 서버들을 띄었는데,</p>
<p>분명 평소와 같이 서버들을 띄우는데 외부에서 접속이 안되었다..
로그를 봐도 api가 날라오는 흔적도 안보이고
nginx의 로그를 봐도 어떠한 패킷도 넘어오지 않았다.</p>
<p>이럴땐, tomcat이나 아파치가 켜저있지 않은지 한번 보자.
그리고 nginx를 restart를 시켜보자!</p>
<p>그랬더니.. 접속이 되었다.. </p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SSH 연결방법]]></title>
            <link>https://velog.io/@ssunn_ni/SSH-%EC%97%B0%EA%B2%B0%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@ssunn_ni/SSH-%EC%97%B0%EA%B2%B0%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 05 Jul 2024 03:22:15 GMT</pubDate>
            <description><![CDATA[<p>ssh 연결 <strong>.ssh</strong>파일에다가 <strong>config파일을</strong> 생성한다.</p>
<pre><code>mkdir config .ssh/</code></pre><p>config파일에 아래 코드들을 상황에 맞게 작성한다.</p>
<hr>
<h4 id="일반-ssh연결">일반 ssh연결</h4>
<pre><code>Host ${원하는 이름}
Hostname ${hostname}
User ${접속할 유저 이름}
IdentityFile {만약에 key가 있다면 key위치}
</code></pre><h4 id="포워딩-ssh-연결">포워딩 ssh 연결</h4>
<pre><code>Host ${원하는 이름} A
  HostName ${hostname}
  Port ${포트번호}
  User ${접속할 유저 이름}


Host ${원하는 이름} B
  HostName ${hostname}
  User ${접속할 유저 이름}
  IdentityFile {만약에 key가 있다면 key위치}
  ProxyJump ${중간 포워딩 서버이름} A
</code></pre><p>이렇게하면 로컬 -&gt; A -&gt; B 로, 즉 결국 로컬에서 B로 SSH 접속이 가능하다.
hostname에는 ip주소든 도메인이든 작성하면 된다.</p>
<p>이렇게 설정 이후에 </p>
<pre><code>ssh ${내가 설정한 연결 이름}</code></pre><p>을 cmd창에 작성하면 해당 서버로 접속 가능하다.</p>
<hr>
<p>번외. mysql workbench에서 포워드해서 접속
<img src="https://velog.velcdn.com/images/ssunn_ni/post/0533aff8-c546-41ff-88e1-32a22c1a90d1/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[vscode 내가쓰는 유용한 익스텐션 모음]]></title>
            <link>https://velog.io/@ssunn_ni/vscode-%EC%9C%A0%EC%9A%A9%ED%95%9C-%EC%9D%B5%EC%8A%A4%ED%85%90%EC%85%98-%EB%AA%A8%EC%9D%8C</link>
            <guid>https://velog.io/@ssunn_ni/vscode-%EC%9C%A0%EC%9A%A9%ED%95%9C-%EC%9D%B5%EC%8A%A4%ED%85%90%EC%85%98-%EB%AA%A8%EC%9D%8C</guid>
            <pubDate>Fri, 05 Jul 2024 03:04:07 GMT</pubDate>
            <description><![CDATA[<hr>
<h3 id="rest-client">REST Client</h3>
<p>Id: humao.rest-client
Description: REST Client for Visual Studio Code
Version: 0.25.1
Publisher: Huachao Mao
VS Marketplace Link: <a href="https://marketplace.visualstudio.com/items?itemName=humao.rest-client">https://marketplace.visualstudio.com/items?itemName=humao.rest-client</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/49d2235a-e087-4bda-a861-9088625d5a89/image.png" alt=""></p>
<hr>
<h3 id="prettier---code-formatter">Prettier - Code formatter</h3>
<p>Id: esbenp.prettier-vscode
Description: Code formatter using prettier
Version: 10.4.0
Publisher: Prettier
VS Marketplace Link: <a href="https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode">https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/4e2fbe91-cdff-4844-ba58-3944ca52f4f6/image.png" alt=""></p>
<hr>
<h3 id="path-intellisense">Path Intellisense</h3>
<p>Id: christian-kohler.path-intellisense
Description: Visual Studio Code plugin that autocompletes filenames
Version: 2.9.0
Publisher: Christian Kohler
VS Marketplace Link: <a href="https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense">https://marketplace.visualstudio.com/items?itemName=christian-kohler.path-intellisense</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/1626a3ec-678a-4483-bb19-5dc2d577eba9/image.png" alt=""></p>
<hr>
<h3 id="wsl">WSL</h3>
<p>Id: ms-vscode-remote.remote-wsl
Description: Open any folder in the Windows Subsystem for Linux (WSL) and take advantage of Visual Studio Code&#39;s full feature set.
Version: 0.88.2
Publisher: Microsoft
VS Marketplace Link: <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl">https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/50133a2d-ee58-457c-896a-b0e7d75bdfbd/image.png" alt=""></p>
<hr>
<h3 id="todo-highlight">TODO Highlight</h3>
<p>Id: wayou.vscode-todo-highlight
Description: highlight TODOs, FIXMEs, and any keywords, annotations...
Version: 1.0.5
Publisher: Wayou Liu
VS Marketplace Link: <a href="https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight">https://marketplace.visualstudio.com/items?itemName=wayou.vscode-todo-highlight</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/7169f002-a2c0-4a39-9b53-4b519d2a7503/image.png" alt=""></p>
<hr>
<h3 id="remote---ssh">Remote - SSH</h3>
<p>Id: ms-vscode-remote.remote-ssh
Description: Open any folder on a remote machine using SSH and take advantage of VS Code&#39;s full feature set.
Version: 0.112.0
Publisher: Microsoft
VS Marketplace Link: <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh">https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/1b169f9f-cb8b-43d0-8095-bdca95e7bc1f/image.png" alt=""></p>
<hr>
<h3 id="remote---tunnels">Remote - Tunnels</h3>
<p>Id: ms-vscode.remote-server
Description: Connect to a remote machine through a Tunnel
Version: 1.5.2
Publisher: Microsoft
VS Marketplace Link: <a href="https://marketplace.visualstudio.com/items?itemName=ms-vscode.remote-server">https://marketplace.visualstudio.com/items?itemName=ms-vscode.remote-server</a>
<img src="https://velog.velcdn.com/images/ssunn_ni/post/6b86f40b-5960-4266-a80a-f42aa54ae9a1/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[websocket을 이용한 채팅서버개발 고생기(1)]]></title>
            <link>https://velog.io/@ssunn_ni/websocket%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B1%84%ED%8C%85%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EC%83%9D%EA%B8%B0</link>
            <guid>https://velog.io/@ssunn_ni/websocket%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B1%84%ED%8C%85%EC%84%9C%EB%B2%84%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EC%83%9D%EA%B8%B0</guid>
            <pubDate>Tue, 18 Jun 2024 03:43:02 GMT</pubDate>
            <description><![CDATA[<p>노션에다가만 적어놓았다가
블로그시작하니 예전에 고생했던 기록이 하나둘씩 떠오른다.</p>
<p>이전에 취준생 및 대학생 시절..
인터넷 강의로 socket을 활용한 채팅을 만들어본적이있다.</p>
<p>회사에서 작년에 채팅서버를 새로 만들어야할것같다 라는 말을 들었을 때</p>
<blockquote>
<p><strong>그때랑 별다를것없고 어렵지 않을것같은데..</strong></p>
</blockquote>
<p>라는 아주 무시무시한 착각을 하고서 덜컥 받아버린다.
그때 깨달았었어야 했다.. 왜.. 나의 사수가 나를 그런눈을 처다봤는지..</p>
<p>아무튼간에 작년에 채팅서버를 만들고 1년이지난지금에서 생각해보면 가장 헤맸던건</p>
<p><strong>1. 왜.. 인강 따라했을때처럼 소켓연결이 부드럽지도 않고 연결이 안될까...?
2. 요구사항에 맞게 내가 배웠던걸 응용만 하면 될 뿐인데 이게 난이도가 이렇게 올라간다고..?</strong></p>
<p>딱 이 2가지였다. </p>
<hr>
<h3 id="1-webocket의-프론트와의-연결">1. webocket의 프론트와의 연결</h3>
<p>혼자서했을땐 로컬서버를 띄우고 pug파일이나 대강 만들어서 테스트를 했었기에 연결이 어렵지 않았는데..
막상 베타서버에 올려놓으니 통신이 안되는거다..</p>
<p>몇일에 걸쳐 이것도 했다가 저겄도 했다가 뻘짓하고 구글링해서 검색한 결과
nginx셋팅을 통해 해결할 수 있었다.</p>
<pre><code>    location /socket.io/ {
        ....
        proxy_set_header Host $host;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &quot;upgrade&quot;;
    }
</code></pre><p>이후에 </p>
<pre><code>sudo systemctl restsart nginx</code></pre><p>를 통해 변경한 설정을 적용시키면 된다.</p>
<p><strong>이때 nginx를 처음 만져보았었는데</strong>
여기서 나와같은경우는 해결법을 찾고도 저 많은 nginx설정중에 어디다가 넣어야하는지 몰라서
정말 nginx설정을 계속 하나하나 바꿔가며 적용시키면서 했었다. 
<del>사실 지금도 저게 맞는건지는 모르겠으니 각자 상황에 따라서 변경해보길 바란다.</del></p>
<p>다른건 모르겠지만 <strong>가장 중요한 코드</strong>는 location이 아니라 <strong>그 내부에 4줄</strong>이니 저 4줄을 어디다가 놓아야할지를 각자 상황에 맞추도록 하자.</p>
<p><a href="https://velog.io/@habins226/Nginx-WebSocket%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%84%A4%EC%A0%95-%EB%B0%A9%EB%B2%95">https://velog.io/@habins226/Nginx-WebSocket%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%84%A4%EC%A0%95-%EB%B0%A9%EB%B2%95</a></p>
<p>블로그보니까 나말고도 진짜 다들 고생많이한것같다.. 저기에 더  자세한 설명이 있어 첨부한다.</p>
<h3 id="2-webocket관련-설계-다시">2. webocket관련 설계 다시..</h3>
<p>거기다가, 대강 설계를 조금만 틀어서 저장구조랑 만들면 되겠지 했지만.. 소켓은 연결될때마다 socket id가 변경되었고.. 사람들이 권장하지는 않는 방법이지만, socket과 api통신을 둘다 하려니 정말 막막했다.. </p>
<p>api통신을 하자니 broadcast로써만 작동을 했다.. target을 지정하려해도 reqest값에서 socket id 받는것에 실패를 하는바람에 나빼고 보내는것도 실패 ....</p>
<p><del>그래서 그냥 api는 broadcast만으로 동작할 수 있게 해볼까..?</del>
라는 헛생각을 잠시마나 했었다.</p>
<p>거기다가... 이전에는 <strong>node + socket</strong>으로 만들었었는데 이번엔 <strong>nest + websocket</strong> 이라
레퍼런스 또한 많이 없었어서 공식문서와 맨땅의 헤딩으로 겨우겨우 만들었다.
회사내에 다른분들도 socket쪽은 아에 모른다고하시니.. 우선은 내가 간 길이 정답이다 하는수밖에..</p>
<p>이 부분에 대해서는 말로만작성하기 너무 힘들어서 추후에 시간이 된다면 ... 작성하는걸로..!</p>
<p>.
.
.
.</p>
<blockquote>
<p><strong>진짜 만들면서 느꼈던건.. 카카오톡.. 라인.. 등등  진짜 엄청난거구나....</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[docker 기본 셋팅 및 협업하기]]></title>
            <link>https://velog.io/@ssunn_ni/docker%EA%B3%B5%EB%B6%80</link>
            <guid>https://velog.io/@ssunn_ni/docker%EA%B3%B5%EB%B6%80</guid>
            <pubDate>Fri, 14 Jun 2024 08:57:45 GMT</pubDate>
            <description><![CDATA[<p>docker에서 내게 필요한 부분만 모아보자.</p>
<h3 id="1-ubuntu에-도커-설치docker-hub계정생성">1. Ubuntu에 도커 설치/docker hub계정생성</h3>
<pre><code>//우분투 시스템 패키지 업데이트
$ sudo apt-get update

//필요한 패키지 설치
$ sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

//Docker의 공식 GPG키를 추가
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

//Docker의 공식 apt 저장소를 추가
$ sudo add-apt-repository &quot;deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable&quot;

// 시스템 패키지 업데이트
$ sudo apt-get update

//도커 실행상태 확인
$ sudo systemctl status docker

//도커 실행
$ sudo docker run hello-world
</code></pre><p><a href="https://velog.io/@osk3856/Docker-Ubuntu-22.04-Docker-Installation">https://velog.io/@osk3856/Docker-Ubuntu-22.04-Docker-Installation</a> 참조
<a href="https://docs.docker.com/desktop/install/windows-install/">https://docs.docker.com/desktop/install/windows-install/</a> 윈도우 데스트탑 깔기 주소</p>
<p>.</p>
<h3 id="2-한-프로젝트를-image로-저장시키기">2. 한 프로젝트를 image로 저장시키기</h3>
<pre><code>// {project1} root 폴더로 이동
$ cd project

// root에 Dockerfile 파일 만들기
$ mkdir Dockerfile

// Dockerfile파일에 붙여넣기 (아래 파일은 상황에 맞게 조정시켜주기, 노드기준임)
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN yarn install --production
CMD [&quot;node&quot;, &quot;src/index.js&quot;]
EXPOSE 3000

//도커로 해당 파일 빌드하기
$ docker build -t {Dockerhub 이름(user 이름)/이미지 이름}:tag이름

//도커로 이미지 실행시키기(실행시킬 포트:연결될 포트)
$ docker run -dp 3000:3000 {Dockerhub 이름(user 이름)/이미지 이름}:tag이름

//container생성 확인, 제대로 실행되었는지 {localhost:포트} 로 이동하여 확인하기
$ docker ps

</code></pre><h3 id="3-docker-hub에-업로드하기">3. docker hub에 업로드하기</h3>
<pre><code>//로그인
$ docker login

//hub에 만든 container push하기
$ docker push {Dockerhub 이름(user 이름)/이미지 이름}:tag이름
</code></pre><h3 id="4-협업하기">4. 협업하기</h3>
<p>1) cmd로 하기 : 아래 명령어로 pull받아서 실행하기</p>
<pre><code>$ docker pull {Dockerhub 이름(user 이름)/이미지 이름}:tag이름
$ docker run -p 3000:8080 {Dockerhub 이름(user 이름)/이미지 이름}:tag이름</code></pre><p>2) docker desktop에서 pull받아서 실행하기</p>
<p>(<a href="https://velog.velcdn.com/images/ssunn_ni/post/76138857-68c6-4891-8f44-7f80cdd5e2fd/image.png">https://velog.velcdn.com/images/ssunn_ni/post/76138857-68c6-4891-8f44-7f80cdd5e2fd/image.png</a> 출처</p>
]]></description>
        </item>
    </channel>
</rss>