<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>RegularKim</title>
        <link>https://velog.io/</link>
        <description>What doesn't kill you, makes you stronger</description>
        <lastBuildDate>Sun, 09 Aug 2026 11:07:48 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>RegularKim</title>
            <url>https://velog.velcdn.com/images/regular_jk_kim/profile/832447fc-ff25-4733-b894-af65cbbd7048/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. RegularKim. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/regular_jk_kim" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[260809]]></title>
            <link>https://velog.io/@regular_jk_kim/260809</link>
            <guid>https://velog.io/@regular_jk_kim/260809</guid>
            <pubDate>Sun, 09 Aug 2026 11:07:48 GMT</pubDate>
            <description><![CDATA[<h2 id="260809-회고-💬">260809 회고 💬</h2>
<h2 id="실행-중-⚙️">실행 중 ⚙️</h2>
<ul>
<li><input disabled="" type="checkbox"> (출퇴근 인강) 헥사고날, 클린코드<ul>
<li><input checked="" disabled="" type="checkbox"> 개발환경설정</li>
<li><input checked="" disabled="" type="checkbox"> 패키지 구조</li>
<li><input disabled="" type="checkbox"> 헥사고날 아키텍처</li>
<li><input disabled="" type="checkbox"> JPA 애그리거트</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> 자바 성능 튜닝 이야기<ul>
<li><input checked="" disabled="" type="checkbox"> <ol>
<li>디자인 패턴, 꼭 써야 한다</li>
</ol>
</li>
<li><input checked="" disabled="" type="checkbox"> <ol start="2">
<li>내가 만든 프로그램의 속도를 알고 싶다</li>
</ol>
</li>
<li><input checked="" disabled="" type="checkbox"> <ol start="3">
<li>왜 자꾸 String을 쓰지 말라는 거야?</li>
</ol>
</li>
<li><input checked="" disabled="" type="checkbox"> <ol start="4">
<li>어디에 담아야 하는지...</li>
</ol>
</li>
<li><input checked="" disabled="" type="checkbox"> <ol start="5">
<li>지금까지 사용하던 for 루프를 더 빠르게 할 수 있다고?</li>
</ol>
</li>
<li><input checked="" disabled="" type="checkbox"> <ol start="6">
<li>static 제대로 한번 써 보자</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="7">
<li>클래스 정보, 어떻게 알아낼 수 있나?</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="8">
<li>synchronized는 제대로 알고 써야 한다</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="9">
<li>IO에서 발생하는 병목 현상</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="10">
<li>로그는 반드시 필요한 내용만 찍자</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="11">
<li>JSP와 서블릿, Spring에서 발생할 수 있는 여러 문제점</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="12">
<li>DB를 사용하면서 발생 가능한 문제점들</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="13">
<li>XML과 JSON도 잘 쓰자</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="14">
<li>서버를 어떻게 세팅해야 할까?</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="15">
<li>안드로이드 개발하면서 이것만은 피하자</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="16">
<li>JVM은 도대체 어떻게 구동될까?</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="17">
<li>도대체 GC는 언제 발생할까?</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="18">
<li>GC가 어떻게 수행되고 있는지 보고 싶다</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="19">
<li>GC 튜닝을 항상 할 필요는 없다</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="20">
<li>모니터링 API인 JMX</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="21">
<li>반드시 튜닝해야 하는 대상은?</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="22">
<li>어떤 화면이 많이 쓰이는지 알고 싶다</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="23">
<li>튜닝의 절차는 그때그때 달라요</li>
</ol>
</li>
<li><input disabled="" type="checkbox"> <ol start="24">
<li>애플리케이션에서 점검해야 할 대상들</li>
</ol>
</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> 정처기 결과 확인 (0911)</li>
</ul>
<h2 id="백로그-🪵">백로그 🪵</h2>
<ul>
<li>Spring AI 학습<ul>
<li>벡터 DB</li>
<li>RAG</li>
<li>MCP</li>
</ul>
</li>
<li>SwiftUI 학습<ul>
<li>Firebase</li>
<li>login, loginView</li>
</ul>
</li>
</ul>
<h2 id="독서">독서</h2>
<h3 id="읽을-책">읽을 책</h3>
<ol>
<li>그림으로 이해하는 가상화와 컨테이너</li>
<li>그림으로 이해하는 도커와 쿠버네티스</li>
<li>이펙티브 자바 3/E</li>
</ol>
<h3 id="읽고-기록한-책">읽고 기록한 책</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[260802]]></title>
            <link>https://velog.io/@regular_jk_kim/260802</link>
            <guid>https://velog.io/@regular_jk_kim/260802</guid>
            <pubDate>Sun, 02 Aug 2026 12:50:49 GMT</pubDate>
            <description><![CDATA[<h2 id="260802-회고-💬">260802 회고 💬</h2>
<h2 id="실행-중-⚙️">실행 중 ⚙️</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> (출퇴근 인강) DB 설계 패턴 강의<ul>
<li><input checked="" disabled="" type="checkbox"> 데이터 변경 이력</li>
<li><input checked="" disabled="" type="checkbox"> SOFT DELETE</li>
<li><input checked="" disabled="" type="checkbox"> 통계</li>
<li><input checked="" disabled="" type="checkbox"> EAV</li>
<li><input checked="" disabled="" type="checkbox"> JSON</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> (출퇴근 인강) 헥사고날, 클린코드<ul>
<li><input checked="" disabled="" type="checkbox"> 개발환경설정</li>
<li><input disabled="" type="checkbox"> 패키지 구조</li>
<li><input disabled="" type="checkbox"> 헥사고날 아키텍처</li>
<li><input disabled="" type="checkbox"> JPA 애그리거트</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> 자바 성능 튜닝 이야기</li>
<li><input disabled="" type="checkbox"> 정처기 결과 확인 (0911)</li>
</ul>
<h2 id="백로그-🪵">백로그 🪵</h2>
<ul>
<li>Spring AI 학습<ul>
<li>벡터 DB</li>
<li>RAG</li>
<li>MCP</li>
</ul>
</li>
<li>SwiftUI 학습<ul>
<li>Firebase</li>
<li>login, loginView</li>
</ul>
</li>
</ul>
<h2 id="독서">독서</h2>
<h3 id="읽을-책">읽을 책</h3>
<ol>
<li>그림으로 이해하는 가상화와 컨테이너</li>
<li>그림으로 이해하는 도커와 쿠버네티스</li>
<li>이펙티브 자바 3/E</li>
</ol>
<h3 id="읽고-기록한-책">읽고 기록한 책</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[260726]]></title>
            <link>https://velog.io/@regular_jk_kim/260726</link>
            <guid>https://velog.io/@regular_jk_kim/260726</guid>
            <pubDate>Sun, 26 Jul 2026 09:48:01 GMT</pubDate>
            <description><![CDATA[<h2 id="260726-회고-💬">260726 회고 💬</h2>
<h2 id="실행-중-⚙️">실행 중 ⚙️</h2>
<ul>
<li><input disabled="" type="checkbox"> (출퇴근 인강) DB 설계 패턴 강의<ul>
<li><input checked="" disabled="" type="checkbox"> 데이터 변경 이력</li>
<li><input checked="" disabled="" type="checkbox"> SOFT DELETE</li>
<li><input checked="" disabled="" type="checkbox"> 통계</li>
<li><input disabled="" type="checkbox"> EAV</li>
<li><input disabled="" type="checkbox"> JSON</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> 자바 성능 튜닝 이야기</li>
<li><input disabled="" type="checkbox"> 정처기 결과 확인 (0911)</li>
</ul>
<h2 id="백로그-🪵">백로그 🪵</h2>
<ul>
<li>Spring AI 학습<ul>
<li>벡터 DB</li>
<li>RAG</li>
<li>MCP</li>
</ul>
</li>
<li>SwiftUI 학습<ul>
<li>Firebase</li>
<li>login, loginView</li>
</ul>
</li>
</ul>
<h2 id="독서">독서</h2>
<h3 id="읽을-책">읽을 책</h3>
<ol>
<li>그림으로 이해하는 가상화와 컨테이너</li>
<li>그림으로 이해하는 도커와 쿠버네티스</li>
<li>이펙티브 자바 3/E</li>
</ol>
<h3 id="읽고-기록한-책">읽고 기록한 책</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[260719]]></title>
            <link>https://velog.io/@regular_jk_kim/260719</link>
            <guid>https://velog.io/@regular_jk_kim/260719</guid>
            <pubDate>Sun, 19 Jul 2026 13:41:48 GMT</pubDate>
            <description><![CDATA[<h2 id="260719-회고-💬">260719 회고 💬</h2>
<h2 id="실행-중-⚙️">실행 중 ⚙️</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 정처기 실기 응시 (0719)<ul>
<li>시험 공부 한다고 밤샌건 대학 졸업하고 처음이다. 컨디션 다 망치고 낮밤 바뀌어서 큰일이다.</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> (출퇴근 인강) DB 설계 패턴 강의<ul>
<li><input disabled="" type="checkbox"> 데이터 변경 이력</li>
<li><input disabled="" type="checkbox"> SOFT DELETE</li>
<li><input disabled="" type="checkbox"> 통계</li>
</ul>
</li>
<li><input disabled="" type="checkbox"> 자바 성능 튜닝 이야기</li>
<li><input disabled="" type="checkbox"> 정처기 결과 확인 (0911)</li>
</ul>
<h2 id="백로그-🪵">백로그 🪵</h2>
<ul>
<li>Spring AI 학습<ul>
<li>벡터 DB</li>
<li>RAG</li>
<li>MCP</li>
</ul>
</li>
<li>SwiftUI 학습<ul>
<li>Firebase</li>
<li>login, loginView</li>
</ul>
</li>
</ul>
<h2 id="독서">독서</h2>
<h3 id="읽을-책">읽을 책</h3>
<ol>
<li>그림으로 이해하는 가상화와 컨테이너</li>
<li>그림으로 이해하는 도커와 쿠버네티스</li>
<li>이펙티브 자바 3/E</li>
</ol>
<h3 id="읽고-기록한-책">읽고 기록한 책</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[260712]]></title>
            <link>https://velog.io/@regular_jk_kim/260712</link>
            <guid>https://velog.io/@regular_jk_kim/260712</guid>
            <pubDate>Sun, 12 Jul 2026 07:12:16 GMT</pubDate>
            <description><![CDATA[<h2 id="260712-회고-💬">260712 회고 💬</h2>
<p>오랜만에 회고글을 쓴다! 반년이 조금 넘는 동안 회고를 작성하지 않았다. 보여주기식 공부, 의미없는 기록, 주객전도된 버릇에 회의를 느껴 그만뒀다.</p>
<p>다시 회고를 시작하게 된 이유는 해야 할 일을 매번 다시 고민하는 시간이 아까웠기 때문이다. 앞으로는 회고에 TODO 형식으로 할 일을 정리하고, 매주 조금씩 처리해보려 한다. 독서, 개발, 자격증, 주요 할 일을 기록하고 주간 실행 목록으로 활용할 생각이다.</p>
<p>아마 이렇게 주저리주저리 쓰는 회고는 이번이 마지막이지 않을까 싶다. TODO 목록 정리, 조그맣게 내용 1줄, 아니면 블로그 링크 정도 써놓을듯 하다. 기록 자체가 목적이 되지 않도록, 실제로 다음 행동을 결정하는 데 도움이 되는 회고를 만들고 싶다.</p>
<h2 id="실행-중-⚙️">실행 중 ⚙️</h2>
<ul>
<li><input checked="" disabled="" type="checkbox"> 정처기 실기 등록 (0712)</li>
<li><input disabled="" type="checkbox"> 정처기 실기 응시 (0719)</li>
</ul>
<h2 id="백로그-🪵">백로그 🪵</h2>
<ul>
<li>Spring AI 학습<ul>
<li>벡터 DB</li>
<li>RAG</li>
<li>MCP</li>
</ul>
</li>
<li>SwiftUI 학습<ul>
<li>Firebase</li>
<li>login, loginView</li>
</ul>
</li>
<li>DB 설계 패턴 학습<ul>
<li>데이터 변경 이력</li>
<li>SOFT DELETE</li>
<li>통계</li>
</ul>
</li>
</ul>
<h2 id="독서">독서</h2>
<h3 id="읽는-중">읽는 중</h3>
<ul>
<li>정처기 준비로 중단</li>
</ul>
<h3 id="읽을-책">읽을 책</h3>
<ol>
<li>자바 성능 튜닝 이야기</li>
<li>그림으로 이해하는 가상화와 컨테이너</li>
<li>그림으로 이해하는 도커와 쿠버네티스</li>
<li>이펙티브 자바 3/E</li>
</ol>
<h3 id="읽고-기록한-책">읽고 기록한 책</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SpringAI - Advisors]]></title>
            <link>https://velog.io/@regular_jk_kim/SpringAI-Advisors</link>
            <guid>https://velog.io/@regular_jk_kim/SpringAI-Advisors</guid>
            <pubDate>Sun, 21 Jun 2026 09:56:00 GMT</pubDate>
            <description><![CDATA[<p>Spring AI의 Advisor는 AI 호출 전후에 공통 로직을 삽입하는 파이프라인 계층이다. 로깅, 메모리, 컨텍스트 보강을 통해 ChatClient 요청과 응답 흐름을 체계적으로 제어한다.</p>
<h1 id="springai---advisors">SpringAI - Advisors</h1>
<p>Spring AI의 <code>Advisor</code>는 AI 모델 호출 전후에 개입해 요청을 보강하고 응답을 후처리하는 계층이다. 로깅, 컨텍스트 추가, 검증 등 공통 로직을 파이프라인에서 처리한다.</p>
<p><img src="https://velog.velcdn.com/images/regular_jk_kim/post/907fe4f7-e552-49cb-a54f-907492777763/image.png" alt=""></p>
<h2 id="advisors-프로세스">Advisors 프로세스</h2>
<p align="center">
  <img src="https://docs.spring.io/spring-ai/reference/_images/advisors-flow.jpg" width="650">
</p>

<ol>
<li>사용자가 입력한 질문을 Spring AI 내부에서 처리하고 다루기 쉬운 <code>ChatClientRequest</code> 객체로 변환</li>
<li>등록된 Advisor들이 개입하여 요청 데이터를 검사하거나 변경<ul>
<li><code>SimpleLoggerAdvisor</code>는 이 단계에서 AI에게 어떤 질문을 보낼 예정인지 로그 출력</li>
<li>다른 Advisor는 질문에 추가적인 컨텍스트나 시스템 프롬프트를 덧붙이는 작업을 진행</li>
</ul>
</li>
<li>Advisor들을 거치며 최종적으로 완성된 요청이 실제 AI모델로 전송됨</li>
<li>답변을 사용자에게 최종 반환하기 전에 Advisor들이 다시 개입<ul>
<li><code>SimpleLoggerAdvisor</code>는 이 단계에서 AI가 어떤 답변을 생성했는지, 토큰은 얼마나 사용했는지 등을 로그로 출력</li>
<li>다른 Advisor들은 답변을 특정 포맷으로 파싱하거나 검증함</li>
</ul>
</li>
<li>모든 Advisor의 처리가 끝난 내부 응답 객체를 최종적으로 개발자가 받게 될 표준 <code>ChatResponse</code> 형태로 변환하여 반환하면서 전체 프로세스 종료</li>
</ol>
<h2 id="simpleloggeradvisor">SimpleLoggerAdvisor</h2>
<ul>
<li>로깅을 담당하는 어드바이저</li>
<li>프롬프트가 실제로 어떤 형태의 JSON으로 변환되어 AI에게 전달되는지, 그리고 AI가 대답을 생성하는 데 걸린 시간과 정확한 응답 데이터가 무엇인지를 디버깅 콘솔에 출력해줌</li>
</ul>
<pre><code class="language-java">@Configuration
public class ChatConfig {

    @Bean
    public SimpleLoggerAdvisor simpleLoggerAdvisor() {
        return SimpleLoggerAdvisor.builder().build();
    }
}</code></pre>
<h2 id="chatmemory">ChatMemory</h2>
<ul>
<li>대화 기록을 저장해두는 객체</li>
<li>스프링에서는 기본적으로 메모리에 저장하는 <code>InMemoryChatMemory</code> 제공</li>
</ul>
<pre><code class="language-java">// ChatMemory 생성
@Bean
public ChatMemory chatMemory() {
    return MessageWindowChatMemory.builder().maxMessage(10).build();
}</code></pre>
<pre><code class="language-java">// ChatMemoryAdvisor 생성
@Bean
public MessageChatMemoryAdvisor messageChatMemoryAdvisor(ChatMemory chatMemory) {
    return MessageChatMemoryAdvisor.builder(chatMemory).build();
}</code></pre>
<h3 id="q-chatmemory는-왜-사용하는-거예요">Q. ChatMemory는 왜 사용하는 거예요?</h3>
<p>API를 통해 백엔드 서버에서 AI를 직접 호출 할 때, LLM API는 무상태로 동작한다. 즉, 이전 대화 기록을 알지 못한다. 그래서 새로운 질문을 할 때마다 과거에 나누었던 대화 기록을 프롬프트에 덧붙여서 같이 보내야한다. 수동으로 덧붙이기는 불가능하기 때문에 Advisors를 통해 구현한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SpringAI - Prompts]]></title>
            <link>https://velog.io/@regular_jk_kim/SpringAI-Prompts-ep3v3gp7</link>
            <guid>https://velog.io/@regular_jk_kim/SpringAI-Prompts-ep3v3gp7</guid>
            <pubDate>Sun, 21 Jun 2026 06:57:29 GMT</pubDate>
            <description><![CDATA[<h1 id="springai---prompts">SpringAI - Prompts</h1>
<p>Spring AI의 Prompt 구조와 ChatOptions 설정, 그리고 다양한 프롬프트 엔지니어링 패턴을 정리한다.</p>
<p><img src="https://velog.velcdn.com/images/regular_jk_kim/post/fa9cf979-266d-42db-96a6-4bf050edb3a2/image.png" alt=""></p>
<h2 id="prompts">Prompts</h2>
<h3 id="prompt-객체-내부-구조">Prompt 객체 내부 구조</h3>
<ul>
<li><code>List&lt;Message&gt;</code> : 대화의 맥락을 구성하는 여러 종류의 메시지 묶음</li>
<li><code>ChatOptions</code> : 온도, 사용할 모델명과 같은 AI 실행 옵션</li>
</ul>
<h2 id="chatoptions">ChatOptions</h2>
<blockquote>
<p>AI가 답변을 생성할 때 얼마나 창의적일지, 얼마나 길게 말할지 등 응답의 특성을 제어할 수 있는 공통 옵션 설정</p>
</blockquote>
<h3 id="temperature">Temperature</h3>
<p>응답이 얼마나 창의적이고 무작위적으로 생성될지 결정한다.</p>
<ul>
<li>0.0 ~ 0.3 (낮은 온도)<ul>
<li>매우 결정적이고 일관된 응답</li>
<li>사실 기반 답변, 데이터 분류, 코드 생성에 적합</li>
</ul>
</li>
<li>0.4 ~ 0.7 (중간 온도)<ul>
<li>균형 잡힌 응답</li>
<li>일반적인 대화나 정보 제공용 챗봇에 많이 쓰는 표준 값</li>
</ul>
</li>
<li>0.8 ~ 1.0 (높은 온도)<ul>
<li>창의적이고 다양한 응답</li>
<li>스토리텔링, 소설 쓰기, 아이디어 브레인스토밍에 적합</li>
</ul>
</li>
</ul>
<h3 id="output-length">Output Length</h3>
<p>모델이 한번에 생성할 수 있는 최대 토큰 수를 제한한다.</p>
<ul>
<li>5 ~ 25<ul>
<li>한 두 단어, 짧은 구절, &quot;긍정/부정&quot;같은 분류 라벨</li>
</ul>
</li>
<li>50 ~ 100<ul>
<li>하나의 문단이나 짧은 설명</li>
</ul>
</li>
<li>1000+<ul>
<li>장문의 글, 소설 스토리, 복잡한 로직의 아키텍쳐 설명</li>
</ul>
</li>
</ul>
<h3 id="sampling-controls">Sampling Controls</h3>
<p>AI가 다음에 올 단어 후보군을 추려내는 필터링 기법</p>
<ul>
<li><strong>TOP-K</strong><ul>
<li>상위 K개 필터링</li>
<li>다음 토큰을 선택할 때, 확률이 가장 높은 상위 K개의 토큰만으로만 후보군을 제한</li>
<li>값이 클수록 다양한 단어가 섞여 다양성이 증가하고, 작을수록 늘 쓰던 단어만 써서 결정적인 답변 도출</li>
<li>OpenAI의 모델들은 Top-K 옵션 미제공</li>
</ul>
</li>
<li><strong>TOP-P</strong><ul>
<li>누적 확률 필터링</li>
<li>후보 단어들을 확률 높은 순으로 줄 세운 뒤, 누적 확률이 P를 초과하기 전까지만 동적으로 단어 후보군을 선택</li>
<li>상위 90%의 안전한 단어들 안에서만 고르게 하므로 문맥이 꼬이지 않으면서도 자연스러운 변화 가능</li>
<li>일반적으로 0.8 ~ 0.95 사이의 값을 사용</li>
</ul>
</li>
</ul>
<h2 id="프롬프트-엔지니어링-패턴">프롬프트 엔지니어링 패턴</h2>
<h3 id="1-zero-shot-prompting">1. Zero-Shot Prompting</h3>
<p>사전 정보나 예시 없이 AI에게 곧바로 질문을 던지는 방식</p>
<pre><code class="language-java">@AiService
public interface QAService {

    @Prompt(&quot;이탈리아의 수도는?&quot;)
    String answer();
}</code></pre>
<h3 id="2-few-shot-prompting">2. Few-Shot Prompting</h3>
<p>1개 (Ont-Shot) 또는 여러 개의 정답 예시를 미리 보여주고 패턴을 학습시킨 뒤 질문을 던지는 방식</p>
<pre><code class="language-java">@Prompt(&quot;&quot;&quot;
Q: 2 + 2는? A: 4
Q: 3 + 5는? A: 8
Q: {{question}} A:
&quot;&quot;&quot;)
String solve(@V(&quot;question&quot;) String question);</code></pre>
<h3 id="3-role--system-prompting">3. Role &amp; System Prompting</h3>
<p>시스템 프롬프트를 사용해 AI에게 페르소나를 부여하는 방식</p>
<pre><code class="language-java">@Prompt(system = &quot;너는 핵심만 말하는 10년차 외과 의사야&quot;)
String respondTO(String userQuestion);</code></pre>
<h3 id="4-step-back-prompting">4. Step-Back Prompting</h3>
<p>AI가 대답하기 전에, 상황을 먼저 객관적으로 분석하고 성찰하도록 유도하는 방식</p>
<pre><code class="language-java">@Prompt(&quot;&quot;&quot;
답변하기 전에, 다음 상황을 주의 깊게 먼저 생각해 봐: {{situation}}
자, 이제 이 상황에서 가장 좋은 조언은 무엇일까?
&quot;&quot;&quot;)
String analyze(@V(&quot;question&quot;) String situation);</code></pre>
<h3 id="5-chain-of-though-cot">5. Chain-of-Though, CoT</h3>
<p>답만 뱉는게 아니라, 문제를 해결하는 과정을 단계별로 풀어서 설명하도록 지시하는 방식</p>
<pre><code class="language-java">@Prompt(&quot;&quot;&quot;
다음 문제를 단계별로 차근차근(step-by-step) 해결해 줘:
{{problem}}

정답:
&quot;&quot;&quot;)
String solveStepwise(@V(&quot;problem&quot;) String problem);</code></pre>
<h3 id="6-self-consistency-prompt">6. Self-Consistency Prompt</h3>
<p>CoT 프롬프트를 여러 번 반복해서 호출한 뒤, 가장 많이 나온(일관된) 답변을 최종 정답으로 채택하는 방식</p>
<h3 id="7-tree-of-thoughts-tot">7. Tree-of-Thoughts, ToT</h3>
<p>하나의 문제에 대해 여러 가지 해결책을 먼저 제안하게 하고, 그중 가장 좋은 것을 스스로 선택해 평가하게 만드는 방식</p>
<pre><code class="language-java">@Prompt(&quot;&quot;&quot;
이 문제를 해결할 수 있는 3가지 다른 접근법을 제안해 봐:
{{challenge}}

그런 다음, 3가지 중 가장 좋은 방법을 하나 고르고 그 이유를 설명해 줘.
&quot;&quot;&quot;)
String solveWithToT(@V(&quot;challenge&quot;) String challenge);</code></pre>
<h3 id="8-automatic-prompt-engineering">8. Automatic Prompt Engineering</h3>
<p>내가 쓴 부실한 프롬프트를 AI에게 전달해서 &quot;네가 더 완벽한 프롬프트로 다듬어봐&quot;라고 시키는 방식</p>
<h3 id="9-code-prompting">9. Code Prompting</h3>
<p>사용자의 입력을 기반으로 코드를 생성하거나 분석하도록 요청하는 방식</p>
<pre><code class="language-java">@Prompt(&quot;&quot;&quot;
다음 요구사항을 수행하는 Java 함수를 작성해 줘:
{{description}}

Java Code:
&quot;&quot;&quot;)
String generateCode(@V(&quot;description&quot;) String description);</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[SpringAI 구성요소]]></title>
            <link>https://velog.io/@regular_jk_kim/SpringAI-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C</link>
            <guid>https://velog.io/@regular_jk_kim/SpringAI-%EA%B5%AC%EC%84%B1%EC%9A%94%EC%86%8C</guid>
            <pubDate>Sat, 20 Jun 2026 10:50:28 GMT</pubDate>
            <description><![CDATA[<p>SpringAI는 ChatClient, Advisors, Prompt, Model, Structured Output을 통해 AI 대화 흐름을 스프링 방식으로 구성하게 해주는 프레임워크이다. 대화, RAG, 객체 변환까지 백엔드 로직 안에서 일관되게 처리할 수 있다.</p>
<h1 id="springai-구성요소">SpringAI 구성요소</h1>
<h2 id="chatclient">ChatClient</h2>
<ul>
<li><p>AI 대화의 시작과 끝을 관리하는 컨트롤러 역할</p>
<pre><code class="language-java">@RestController
class MyController {

  private final ChatClient chatClient;

  public MyController(ChatClient.Builder chatClientBuilder) {
      this.chatClient = chatClientBuilder.build();
  }

  @GetMapping(&quot;/ai&quot;)
  String generation(String userInput) {
      return this.chatClient
          .prompt() // prompt() 메서드를 시작으로 전체 대화 과정을 제어
          .user(userInput)
          .call()
          .content();
  }
}</code></pre>
</li>
<li><p>AI 모델과 대화를 주고받는 모든 과정을 총괄하고 제어하는 최상위 핵심 인터페이스</p>
</li>
<li><p>ChatClient는 대화 파이프라인을 스프링에서 쉽게 관리하기 위해 제작됨</p>
</li>
<li><p>대화 기록을 기억해야 하거나 사내 데이터 추가, AI 답변을 자바 객체로 변환하는 등 수많은 전후 처리 작업 가능</p>
</li>
</ul>
<h2 id="advisors">Advisors</h2>
<ul>
<li>스프링 웹 개발 시 인터셉터, 필터 등의 역할과 동일</li>
<li>유저가 보낸 질문이 AI 모델로 넘어가기 직전, 중간에 개입하여 질문을 가공하거나 필요한 배경지식(Context)을 주입함</li>
<li>주로 이전 대화 기록을 불러와 질문에 붙여주거나, Vector DB 관련 문서를 찾아와 참고 자료로 결합해주는 역할을 수행</li>
</ul>
<pre><code class="language-java">ChatMemory chatMemory = ...
VectorStore vectorStore = ...

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat memory
        QuestionAnswerAdvisor.builder(vectorStore).build() // RAG
    )
    .build();

var converationId = &quot;123&quot;;

String response = this.chatClient.prompt()
    .advisors(advisor -&gt; advisor.param(ChatMemory.CONVERSATION_ID, converstionId))
    .user(&quot;지난번에 물어본 장소의 날씨 다시 알려줘&quot;)
    .call()
    .content();</code></pre>
<h2 id="prompts">Prompts</h2>
<ul>
<li>최종 프롬프트 생성</li>
<li>Advisor가 수집한 배경지식과 유저가 처음 입력한 내용을 결합하여, AI 모델이 완벽하게 이해할 수 있는 최종 프롬프트를 완성하는 단계</li>
<li>SpringAI에서 AI MODEL에 전달되는 데이터의 형태는 문자열이 아니라 <strong>Prompt</strong>라는 전용 객체</li>
</ul>
<h3 id="prompt-객체-내부-구조">Prompt 객체 내부 구조</h3>
<ul>
<li><code>List&lt;Message&gt;</code> : 대화의 맥락을 구성하는 여러 종류의 메시지 묶음</li>
<li><code>ChatOptions</code> : 온도, 사용할 모델명과 같은 AI 실행 옵션</li>
</ul>
<h3 id="message-type">Message Type</h3>
<p>Prompt 안에 담기는 Message는 역할에 따라 4가지 타입이 존재한다.</p>
<ol>
<li><strong>SYSTEM</strong><ul>
<li>AI의 성격, 직업, 행동 지침, 제약 사항 등을 설정하는 메시지</li>
<li>&quot;당신은 10년차 자바 개발자이다. 모든 답변은 마크다운으로 출력해줘&quot;</li>
</ul>
</li>
<li><strong>USER</strong><ul>
<li>실제 서비스를 사용하는 유저가 입력한 질문이나 지시 내용</li>
<li>&quot;SpringAI에서 ChatClient가 뭐야?&quot;</li>
</ul>
</li>
<li><strong>ASSISTANT</strong><ul>
<li>AI가 이전에 대답했던 응답 내용을 챗봇이 기억하고 이어가기 위해 ChatMemory에 덧붙이는 메시지</li>
</ul>
</li>
<li><strong>TOOL</strong><ul>
<li>AI가 외부 API나 함수를 호출하고 돌아온 결과값을 담는 특수 메시지</li>
</ul>
</li>
</ol>
<h3 id="prompttemplate">PromptTemplate</h3>
<p>유저의 질문 구조는 고정되어 있지 않다. 유저의 입력값에 따라 질문 내용이 매번 바뀌어야 하므로 PromptTemplate을 사용한다. 자바 웹 개발시 사용하는 타임리프처럼 프롬프트 중간에 구멍(<code>{변수명}</code>)을 뚫어놓고 실행 시점에 값을 채워 넣는 기능이다.</p>
<ul>
<li>비즈니스 로직과 프롬프트 문구 분리로 유지보수성 증가</li>
</ul>
<pre><code class="language-java">// 1. 구멍이 뚫려 있는 템플릿 문자열 준비
String templateText = &quot;초보자를 위해 {topic}의 핵심 개념을 3줄로 요약해줘&quot;;
PromptTemplate promptTemplate = new PromptTemplate(templateText);

// 2. 런타임 변수 값을 MAP으로 매핑하여 완전한 Prompt 객체 생성
Map&lt;String, Object&gt; variables = Map.of(&quot;topic&quot;, &quot;SpringAI의 PromptTemplate&quot;);
Prompt prompt = promptTemplate.create(variables);

// 3. AI 모델에 전달
ChatResponse response = chatModel.call(prompt);</code></pre>
<h2 id="models">Models</h2>
<ul>
<li>AI 모델 실행 인터페이스</li>
<li>완성된 최종 프롬프트를 실제 외부 AI API에 던져 답변을 받음</li>
<li>이 단계에서 설정에 따라 데이터를 한 번에 통째로 받을지, 글자 단위 실시간 스트리밍으로 받을지 응답 규격이 결정됨</li>
<li><code>application.yml</code>과 같은 설정 파일에서 API 키와 의존성 라이브러리만 교체하여 사용 가능</li>
</ul>
<h3 id="중요한-4가지-model-타입">중요한 4가지 Model 타입</h3>
<p>AI가 처리할 수 있는 데이터의 형태에 따라 인터페이스가 나누어져 있다.</p>
<ol>
<li>ChatModel<ul>
<li>텍스트 대화 엔진</li>
<li>텍스트를 입력받아 텍스트를 반환하는 모델</li>
</ul>
</li>
<li>EmbeddingModel<ul>
<li>Vector 변환 엔진</li>
<li>RAG 시스템의 핵심</li>
<li>텍스트를 입력받아 Vector DB에 저장할 수 있도록 숫자 배열로 변환해주는 모델</li>
</ul>
</li>
<li>ImageModel<ul>
<li>이미지 생성 엔진</li>
<li>텍스트 프롬프트를 입력받아 이미지 생성</li>
</ul>
</li>
<li>AudioModel<ul>
<li>음성 처리 엔진</li>
<li>음성을 텍스트로 변환하거나, 텍스트를 음성으로 변환하는 모델</li>
</ul>
</li>
</ol>
<p>위 4가지 타입 외에도 더 있으니 참고!</p>
<h2 id="structured-output">Structured Output</h2>
<ul>
<li>후처리 및 객체 변환</li>
<li>AI 모델이 뱉어낸 줄글 형태의 텍스트 답변을 받아, 백엔드 로직에서 즉시 사용할 수 있도록 정해진 자바 객체나 JSON 규격으로 자동 파싱해주는 변환 단계</li>
</ul>
<h3 id="동작-방식">동작 방식</h3>
<ol>
<li>프롬프트 주입 방식<ul>
<li>프롬프트 마지막에 <code>JSON 스키마 규격에 맞춰 응답</code>이라는 지시문을 합성하여 보내는 방식</li>
<li>구형 모델이나 JSON 모드를 켜는 API가 없는 모델에서도 범용적으로 사용 가능</li>
</ul>
</li>
<li>Built-in JSON Mode<ul>
<li>API 호출 시 <code>response_format: {type : &quot;json_object&quot;}</code>라는 JSON 전용 하드웨어 스위치를 켜서 사용하는 방식</li>
<li>OpenAI 등 이 기능을 지원하는 특정 LLM 모델에서만 사용 가능</li>
<li>응답을 원하는 형태로 100% 강제할 수 있어 변환 실패율이 0에 수렴</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift Error Handling]]></title>
            <link>https://velog.io/@regular_jk_kim/Swift-Error-Handling</link>
            <guid>https://velog.io/@regular_jk_kim/Swift-Error-Handling</guid>
            <pubDate>Sat, 16 May 2026 13:39:52 GMT</pubDate>
            <description><![CDATA[<h1 id="swift-error-handling">Swift Error Handling</h1>
<p>스위프트에서 에러 처리를 할 때 사용하는 <code>try</code>, <code>try?</code>, <code>try!</code>는 에러가 발생할 수 있는 상황을 어떻게 다룰지에 따라 선택해서 사용한다.</p>
<h2 id="try"><code>try</code></h2>
<p><code>try</code>는 에러가 발생할 수 있는 메서드를 호출할 때 사용하는 기본적인 방법이다. 이 방식을 사용할 때는 do-catch 블록을 함께 사용해야한다.</p>
<pre><code class="language-swift">func fetchData() throws -&gt; String {
    throw NSError()
}

do {
    let data = try fetchData()
    print(&quot;성공: \(data)&quot;)
} catch {
    print(&quot;에러 발생: \(error)&quot;)
}</code></pre>
<p><code>catch</code> 구문 뒤에 별도의 이름을 지정하지 않으면, Swift가 알아서 암시적 변수인 <code>error</code>에 발생한 에러를 할당해 준다.</p>
<h2 id="try-1"><code>try?</code></h2>
<p><code>try?</code>는 옵셔널로 결과값을 받는다. 에러가 발생하면 nil이 들어있고 성공하면 값이 들어있다. do-catch문을 사용하지 않아도 된다.</p>
<pre><code class="language-swift">// 기본
let data: String? = try? fetchData() // 성공/실패 상관 없이 옵셔널 타입

// 응용
if let safeData = try? fetchData() {
    print(&quot;성공: \(safeData)&quot;)
} else {
    print(&quot;실패해서 nil 반환&quot;)
}</code></pre>
<p>보통 if-let이나 guard-let과 함께 사용해서 안전하게 값을 꺼내 사용한다.</p>
<h2 id="try-2"><code>try!</code></h2>
<p><code>try!</code>는 결과를 강제 언래핑한다. 에러가 절대 안 난다고 확신할 때 사용한다. 당연히 에러가 발생하면 앱이 강제 종료된다.</p>
<pre><code class="language-swift">let data: String = try! fetchData()
print(data)</code></pre>
<h2 id=""></h2>
<ul>
<li>에러 원인을 파악, 처리해야한다? -&gt; <code>try</code></li>
<li>에러 원인은 몰라도 된다, 실패시 nil 처리만 하면 된다? -&gt; <code>try?</code></li>
<li>에러는 절대 발생하지 않는다? -&gt; <code>try!</code></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Swift Optional, OptionalUnwrapping]]></title>
            <link>https://velog.io/@regular_jk_kim/Swift-Optional-OptionalUnwrapping</link>
            <guid>https://velog.io/@regular_jk_kim/Swift-Optional-OptionalUnwrapping</guid>
            <pubDate>Sat, 16 May 2026 13:02:23 GMT</pubDate>
            <description><![CDATA[<h1 id="swift-optional-optionalunwrapping">Swift Optional, OptionalUnwrapping</h1>
<h2 id="optional">Optional</h2>
<blockquote>
<p>값이 없을 수도 있는 변수를 다루는 방법</p>
</blockquote>
<pre><code class="language-swift">let number1: Int = 10 // 가능
let number2: Int = nil // 불가능</code></pre>
<p>number1은 문제 없다. 하지만 number2는 에러가 출력된다. nil값을 number2에 할당할 수 없기 때문이다.</p>
<pre><code class="language-swift">let number2: Int? = nil // 가능
let number3: Int? = 3 // 가능</code></pre>
<p>이제 에러가 출력되지 않는다. 타입 뒤에 물음표를 추가하여 해당 변수에 nil 값이 할당될 수 있음을 표시한다. 이를 Optional이라고 부른다. 물론 nil 뿐만 아니라 실제 숫자도 할당 가능하다.</p>
<h2 id="optional-unwrapping">Optional Unwrapping</h2>
<p>아래와 같은 함수가 있다.</p>
<pre><code class="language-swift">func square(number: Int) {
    print(&quot;\(number)의 제곱값은 \(number * number)입니다.&quot;)
}

square(number: number1) // 가능 &gt; 10의 제곱값은 100입니다.
square(number: number2) // 불가능 &gt; error: value of optional type must be unwrapped to a value of type &#39;Int&#39;</code></pre>
<p>이 함수에 위에서 정의한 number1을 할당하면 <code>10의 제곱값은 100입니다.</code>의 결과를 출력한다. 하지만 number2을 투입할 수 없다. 왜냐? 파라미터 타입으로 Optional을 받는다고 선언하지 않았기 때문이다. number2를 할당하고 싶다면 Optional을 벗겨내고(Unwrapping) 할당해야한다.</p>
<p>Optional Unwrapping을 하려면 4가지 방법을 사용할 수 있다.</p>
<ol>
<li>nil-coalescing : 기본값 넣어주기</li>
<li>forced-unwrapping : 강제로 벗기기</li>
<li>optional binding1(if-let) : 조심히 벗기기1</li>
<li>optional binding2(guard-let) : 조심히 벗기기2</li>
</ol>
<p>4가지 방법을 알아보자.</p>
<h3 id="nil-coalescing--기본값-넣어주기">nil-coalescing : 기본값 넣어주기</h3>
<pre><code class="language-swift">square(number: number2 ?? 0) // &gt; 0의 제곱값은 0입니다.
square(number: number3 ?? 0) // &gt; 3의 제곱값은 9입니다.</code></pre>
<p><code>??</code> 연산자를 사용하여 옵셔널 값이 nil일 경우 우측에 있는 기본값을 대신 사용하도록 한다. number2의 값이 nil이면 0을 파라미터로 지정한다.</p>
<h3 id="forced-unwrapping--강제로-벗기기">forced-unwrapping : 강제로 벗기기</h3>
<pre><code class="language-swift">square(number: number2!) // &gt; error: Unexpectedly found nil while unwrapping an Optional value
square(number: number3!) // &gt; 3의 제곱값은 9입니다.</code></pre>
<p>값 뒤에 느낌표를 붙여 강제로 옵셔널을 벗겨내는 방법이다. 당연하게도 벗겨낸 값이 nil이라면 에러가 발생하고 앱이 강제종료된다. 개발할 때만 사용해야하고, 배포할 때는 당연히 지양해야 하는 코드다.</p>
<h3 id="optional-binding1if-let--조심히-벗기기1">optional binding1(if-let) : 조심히 벗기기1</h3>
<pre><code class="language-swift">if let num = number2 {
    square(number: num) // &gt; (실행되지 않음)
} else {
    print(&quot;언래핑 실패&quot;) // &gt; 언래핑 실패
}

if let num = number3 {
    square(number: num) // &gt; 3의 제곱값은 9입니다.
}</code></pre>
<p>Optional의 값이 nil이 아니라면 num 변수에 해당 값을 할당하는 문법이다. 그리고 할당이 됐다면 중괄호 안의 코드가 진행된다(num 상수는 if문 블록 안에서만 사용 가능). 할당 되지 않았다면 else문의 코드가 실행된다. else문 코드가 없다면 아무 동작도 하지 않는다.</p>
<h3 id="optional-binding2guard-let--조심히-벗기기2">optional binding2(guard-let) : 조심히 벗기기2</h3>
<pre><code class="language-swift">func guardLetExample(input: Int?) {
    guard let num = input else {
        print(&quot;언래핑 실패&quot;)
        return
    }
    square(number: num)
}

guardLetExample(input: number2) // &gt; 언래핑 실패
guardLetExample(input: number3) // &gt; 3의 제곱값은 9입니다.</code></pre>
<p>함수나 반복문 등 제어문 안에서만 사용할 수 있는 문법이다. 언래핑에 실패하는 경우 else문에 지정된 코드가 실행된다. 언래핑에 성공하면 다음 코드로 넘어간다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[API 목표 식별]]></title>
            <link>https://velog.io/@regular_jk_kim/API-%EB%AA%A9%ED%91%9C-%EC%8B%9D%EB%B3%84-dnmgi1v7</link>
            <guid>https://velog.io/@regular_jk_kim/API-%EB%AA%A9%ED%91%9C-%EC%8B%9D%EB%B3%84-dnmgi1v7</guid>
            <pubDate>Wed, 13 May 2026 14:44:05 GMT</pubDate>
            <description><![CDATA[<h1 id="api-목표-식별">API 목표 식별</h1>
<p>API의 목표를 식별하는 것은 단순히 무엇을 할 수 있느냐로 끝나는 것이 아니라, 마찬가지로 과정 중 어떠한 데이터가 다뤄질 것인지도 함께 알아야 한다. 그래서 질문 목록은 아래와 같다.</p>
<ul>
<li>누가 사용자인가?</li>
<li>사용자는 무엇을 할 수 있는가?</li>
<li>사용자는 그걸 어떻게 하는가?</li>
<li>그것을 하기 위해 무엇이 필요한가?</li>
<li>사용자는 무엇을 반환받는가?</li>
<li>(누락된 목표를 식별하기 위한 질문) 입력은 어디를 통해서 들어오는가?</li>
<li>(누락된 목표를 식별하기 위한 질문) 출력은 어디에서 어떻게 쓰이는가?</li>
</ul>
<p>이러한 조사 과정을 진행할 수 있는 API 목표 캔버스를 살펴보자.</p>
<h3 id="api-목표-캔버스">API 목표 캔버스</h3>
<p>API 목표 캔버스는 여섯 개의 열로 이루어진 표이다.</p>
<ul>
<li>누가 : API를 사용하는 사용자들 (또는 프로파일들)을 나열</li>
<li>무엇을 : API로 사용자들이 할 수 있는 것을 나열</li>
<li>어떻게 : 무엇을 단계별로 분해해 나열</li>
<li>입력(원천) : 각 단계를 진행하기 위해 필요한 요소들과 그것들의 원천을 나열 (누락된 누가, 무엇을, 또는 어떻게를 찾기 위함)</li>
<li>출력(사용처) : 각 단계의 반환과 그 쓰임새를 나열 (누락된 누가, 무엇 또는 어떻게를 찾기 위함)</li>
<li>목표 : 명시적이고 간결하게 각각의 어떻게 + 입력 + 출력을 재구성</li>
</ul>
<table>
<thead>
<tr>
<th>누가</th>
<th>무엇을</th>
<th>어떻게</th>
<th>입력(원천)</th>
<th>출력(사용처)</th>
<th>목표</th>
</tr>
</thead>
<tbody><tr>
<td>누가 사용자인가요?</td>
<td>그들은 무엇을 할 수 있나요?</td>
<td>그들이 그걸 어떻게 하나요?</td>
<td>무엇이 필요하나요?</td>
<td>그들은 무엇을 반환받나요?</td>
<td>재구성된 어떻게 + 입력 + 출력</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
<td>그것은 어디서 오나요?</td>
<td>그것은 어떻게 쓰이나요?</td>
<td>정말로 사용자에게 필요한가요?</td>
</tr>
</tbody></table>
<ul>
<li><code>입력(원천)</code>과 <code>출력</code> 사용처를 조사하여 누락된 <code>누가</code>, <code>무엇을</code>, 또는 <code>어떻게</code>를 찾는다.</li>
<li>사용자에게 내부 동작과 관련된 요소(데이터, 코드 구조, 비즈니스 로직 등)를 드러내는지 확인해야한다.</li>
<li>&quot;정말 사용자가 이런 걸 궁금해 할까?&quot; 이 질문 하나로 API가 조금이라도 사용자 관점을 지니고 있는지 확인할 수 있다.</li>
</ul>
<p>API의 목표를 나열하는 것은 반복적인 과정이다. 이 과정은 한 단계씩 수행해야 한다. 한번에 모든 것을 해결하려 하면 안 된다. 이렇게 만들어진 리스트는 사용성, 성능, 보안성과 같은 고려사항 또는 제약사항에 따라 다듬거나 수정할 필요가 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[로그]]></title>
            <link>https://velog.io/@regular_jk_kim/%EB%A1%9C%EA%B7%B8</link>
            <guid>https://velog.io/@regular_jk_kim/%EB%A1%9C%EA%B7%B8</guid>
            <pubDate>Sun, 08 Mar 2026 12:43:48 GMT</pubDate>
            <description><![CDATA[<p>#logger</p>
<h1 id="로그">로그</h1>
<h2 id="주로-저장되는-로그-내용">주로 저장되는 로그 내용</h2>
<ol>
<li>요청/응답 <ul>
<li>목적 : 사용자가 어떤 요청을 보냈고, 시스템이 어떻게 응답했는지 기록</li>
<li>디버깅, 성능 분석</li>
</ul>
</li>
<li>오류 및 예외<ul>
<li>목적 : 시스템에서 발생한 오류와 예외 상황을 기록하여 디버깅에 활용</li>
<li>장애 대응</li>
</ul>
</li>
<li>사용자 활동<ul>
<li>목적 : 사용자 행동 기록을 통한 서비스 이용 흐름 및 보안 감사 추적</li>
<li>법적으로 남겨야 하는 로그들이 이에 해당</li>
<li>감사, 사용 분석</li>
</ul>
</li>
<li>시스템 상태 : 모니터링</li>
<li>데이터베이스 쿼리 : 성능 최적화</li>
<li>보안 : 침해 대응</li>
<li>배치 작업 : 모니터링</li>
<li>디버깅 : 추적</li>
</ol>
<h2 id="로그-레벨">로그 레벨</h2>
<ol>
<li>TRACE<ul>
<li>추적</li>
<li>가장 상세한 정보를 남김</li>
<li>코드가 한 줄 한 줄 어떻게 실행 되는지 경로를 추적함</li>
<li>평소에는 상시로 켜두지 않음 (용량 문제)</li>
<li>일시적으로 켜서 확인하고 다시 끄는 용도</li>
<li>3일치 로그만 보관 (예시)</li>
</ul>
</li>
<li>DEBUG<ul>
<li>디버그</li>
<li>시스템의 작동 상태를 상세하게 기록함</li>
<li>서비스 개발 과정에서 주요하게 확인해야 하는 값들이나 버그들을 기록함</li>
<li>주로 개발 환경에서 코드를 테스트하고, 버그를 찾을 때 사용하고 운영환경에서는 보통 꺼두는 것이 일반적</li>
</ul>
</li>
<li>INFO<ul>
<li>정보</li>
<li>시스템이 예상대로 정상적으로 작동하고 있음을 알려줌</li>
<li>운영 환경에서 가장 기본적으로 켜두는 레벨</li>
<li>서버 시작, 사용자 로그인 완료, 배치 작업 완료와 같이 의미있는 주요 흐름을 기록함</li>
<li>어떤 레벨로 로그를 찍어야할지 모르겠다면 INFO 레벨로 찍어도 무방</li>
</ul>
</li>
<li>WARN<ul>
<li>경고</li>
<li>당장 시스템이 멈추거나 에러가 난 것은 아니지만, 잠재적인 문제가 될 수 있는 상황</li>
<li>데이터가 쌓일수록 외부 API 응답이 점점 지연되는 경우처럼 추후에 문제가 일어날 것 같은 부분을 미리 표시</li>
<li>1분간 10회 이상 발생시 알람 + 일단위 리포트 (예시)</li>
</ul>
</li>
<li>ERROR<ul>
<li>에러</li>
<li>시스템의 특정 기능에 오류가 발생하여, 요청 작업을 정상적으로 처리하지 못하는 상황</li>
<li>DB 저장 중 예외 발생, 결제 API 호출 실패 등의 장애 상황</li>
<li>1회라도 발생시 알람 (예시)</li>
</ul>
</li>
<li>FATAL<ul>
<li>치명적 오류</li>
<li>애플리케이션이나 시스템 전체가 다운되어 더 이상 서비스를 제공할 수 없는 상태</li>
<li>DB 서버 중단/장애, 메모리 부족으로 서버 강제 종료 등 심각한 오류 발생</li>
<li>1회라도 발생시 알람 (예시)</li>
</ul>
</li>
</ol>
<h2 id="slf4j">Slf4j</h2>
<blockquote>
<p>Simple Logging Facade for Java</p>
</blockquote>
<ul>
<li>Facade : 껍데기, 인터페이스</li>
<li>간단하게 로그를 찍어볼 수 있는 인터페이스이다.</li>
</ul>
<h3 id="예전에-로그-설정하던-방법">예전에 로그 설정하던 방법</h3>
<pre><code class="language-java">import org.slf4j.Logger
import org.slf4j.LoggerFactory

public class TestClass {

    private static final Logger log = LoggerFactory.getLogger(TestClass.class)
}</code></pre>
<h3 id="롬복을-활용한-요즘-설정-방법">롬복을 활용한 요즘 설정 방법</h3>
<pre><code class="language-java">
@Slf4j
public class TestClass {
}</code></pre>
<h2 id="코드로-로그-레벨-이해하기">코드로 로그 레벨 이해하기</h2>
<p>웨이팅 등록 서비스 예시를 통해서 로그 레벨을 이해해보자.</p>
<pre><code class="language-java">@Slf4j
@Service
public class TestClass {

    private int currentWaitingCount = 0; // 현재 대기 번호
    private final Set&lt;String&gt; registeredPhones = new HashMap&lt;&gt;(); // 중복 등록 방지

    public String registerWaiting(String name, String phone) {
        String maskedPhone = maskPhoneNumber(phone);

        // 1. [INFO] : 일반적인 비즈니스 흐름 기록
        log.info(&quot;[웨이팅 요청] 이름 : {}, 연락처 : {}&quot;, name, maskedPhone);

        // 2. [ERROR] : 비즈니스 오류 (중복 등록 시도)
        if (registeredPhones.contains(phone)) {
            log.error(&quot;[웨이팅 실패] 중복 등록 시도 발생! 연락처 : {}&quot;, maskedPhone);
            throw new IllegalArgumentException(&quot;이미 대기 등록된 연락처입니다.&quot;);
        }

        // 3. [WARN] 시스템 경고 (대기열 마감 임박)
        if (currentWaitingCount &gt;= 45) {
            log.warn(&quot;[웨이팅 경고] 대기열 마감 임박! 현재 대기 인원 : {}명&quot;, currentWaitingCount);
        }

        registeredPhones.add(phone);
        currentWaitingCount++;

        // 4. [INFO] 비즈니스 정상 처리 완료
        log.info(&quot;[웨이팅 완료] 대기번호 {}번 발급 완료 (고객명 : {})&quot;, currentWaitingCount, name);

        return name + &quot;님, 대기번호 &quot; + currentWaitingCount + &quot;번이 발급되었습니다.&quot;;
    }

    private String maskPhoneNumver(String phone) {
        if (phone == null || phone.length() &lt; 13) return &quot;****&quot;;
        return phone.substring(0, 4) + &quot;****&quot; + phone.substring(8);
    }
}</code></pre>
<h2 id="로그-구성-요소">로그 구성 요소</h2>
<p>로그는 8개의 조각으로 나뉜다. 아래 로그 예시를 통해 살펴보자.</p>
<pre><code class="language-text">2026-03-08 17:12:03.421+09:00  INFO 18324 --- [test-server] [nio-8080-exec-1] c.k.api.user.UserController              : Get user request. userId=42
2026-03-08 17:12:03.438+09:00  INFO 18324 --- [test-server] [nio-8080-exec-1] c.k.service.user.UserService             : Fetching user from database. userId=42
2026-03-08 17:12:03.462+09:00  INFO 18324 --- [test-server] [nio-8080-exec-1] c.k.repository.user.UserRepository       : User entity loaded. id=42
2026-03-08 17:12:03.471+09:00  INFO 18324 --- [test-server] [nio-8080-exec-1] c.k.api.user.UserController              : Response success. userId=42</code></pre>
<h3 id="1-시간-timestamp">1. 시간 (Timestamp)</h3>
<pre><code class="language-text">2026-03-08 17:12:03.421+09:00
2026-03-08 17:12:03.438+09:00
2026-03-08 17:12:03.462+09:00
2026-03-08 17:12:03.471+09:00</code></pre>
<ul>
<li>로그가 발생한 시간 (제일 중요한 정보 중 하나)</li>
<li>+는 시간대(Timezone)를 의미한다. 여기서는 세계 표준시보다 9시간 빠르다는 뜻이다.</li>
</ul>
<h3 id="2-로그-레벨-log-level">2. 로그 레벨 (Log Level)</h3>
<pre><code class="language-text">INFO
INFO
INFO
INFO</code></pre>
<ul>
<li>로그 레벨을 확인하여, 얼마나 중요도 있는 로그인지 파악</li>
</ul>
<h3 id="3-프로세스-아이디-pid">3. 프로세스 아이디 (PID)</h3>
<pre><code class="language-text">18324
18324
18324
18324</code></pre>
<ul>
<li>서버 하나에 여러 개의 프로그램이 띄워져 있을 때, PID로 구분할 수 있다.</li>
</ul>
<h3 id="4-구분선-separator">4. 구분선 (Separator)</h3>
<pre><code class="language-text">---
---
---
---</code></pre>
<ul>
<li>시각적인 구분선</li>
<li>구분선 앞은 메타데이터</li>
<li>구분선 뒤는 애플리케이션 내부 정보</li>
</ul>
<h3 id="5-애플리케이션-이름">5. 애플리케이션 이름</h3>
<pre><code class="language-text">[test-server]
[test-server]
[test-server]
[test-server]</code></pre>
<ul>
<li>대괄호 안에 있는 이름이 애플리케이션의 이름</li>
<li>여러 서버의 로그가 한 곳에 모였을 때 이름을 보고 출처를 확인할 수 있음</li>
</ul>
<h3 id="6-스레드-이름">6. 스레드 이름</h3>
<pre><code class="language-text">[nio-8080-exec-1]
[nio-8080-exec-1]
[nio-8080-exec-1]
[nio-8080-exec-1]</code></pre>
<ul>
<li>서버는 동시에 여러명의 요청을 처리하기 위해 스레드를 두고 작동</li>
<li>8080포트에서 들어온 요청을 처리하는 1번 스레드라는 뜻</li>
<li>어떤 요처을 처리하다가 에러가 났다면, 해당 스레드만 확인하면 됨</li>
</ul>
<h3 id="7-클래스명">7. 클래스명</h3>
<pre><code class="language-text">c.k.api.user.UserController
c.k.service.user.UserService
c.k.repository.user.UserRepository
c.k.api.user.UserController</code></pre>
<ul>
<li>로그를 남긴 구체적인 위치</li>
</ul>
<h3 id="8-로그-메시지">8. 로그 메시지</h3>
<pre><code class="language-text">: Get user request. userId=42
: Fetching user from database. userId=42
: User entity loaded. id=42
: Response success. userId=42</code></pre>
<ul>
<li>콜론 뒤에 이어지는 부분이 실제 메시지 본문</li>
</ul>
<h2 id="로그-관리-방법">로그 관리 방법</h2>
<blockquote>
<p>단순히 로그를 쌓기만 하는 것이 아니라, 모니터링 도구를 연동하여 관리한다.</p>
</blockquote>
<ol>
<li>Elasticsearch : 방대한 양의 텍스트 로그 데이터 속에서 원하는 에러 메시지나 특정 기록을 빠르게 검색할 수 있도록 저장하는 분산 검색 엔진</li>
<li>Kibana : 엘라스틱서치에 저장된 복잡한 로그 데이터를 편리하게 검색하고, 차트나 통계 화면으로 시각화하여 분석할 수 있게 돕는 웹 인터페이스</li>
<li>Prometheus : 시스템의 리소스 사용량, 에러 발생 횟수 등 상태를 파악할 수 있는 메트릭 및 로그 수집</li>
<li>Grafana : Prometheus가 수집한 데이터를 한눈에 파악할 수 있도록 다양한 그래프와 대시보드 형태로 시각화</li>
</ol>
<h2 id="mdcloggingfilter">MdcLoggingFilter</h2>
<h3 id="mdc-필터를-만드는-이유">MDC 필터를 만드는 이유</h3>
<ul>
<li>웨이팅 API에서 100명의 손님이 동시에 웨이팅을 요청하면 어떨까?</li>
<li>여러 로그들이 섞이며 로그가 복잡해지고, 에러 발생시 찾기 어려워진다.</li>
<li>서버에 들어오는 모든 요청에 대해 대기번호 (Trace ID)를 붙여줄 수 있게 필터를 만들자!</li>
<li>이때 발급받은 대기번호를 담아두는 주머니가 바로 MDC(Mapped Diagnostic Context)이다.</li>
<li>각각의 요청(스레드)이 끝날 때까지 고유한 상태를 기억해 놓은 것이 특징이다.</li>
</ul>
<h3 id="필터-생성">필터 생성</h3>
<pre><code class="language-java">import jakarta.servlet.Filter;

@Component 
public class MdcLoggingFilter implements Filter { 

    @Override 
    public void init(FilterConfig filterConfig) throws ServletException { 
        Filter.super.init(filterConfig); 
    } 

    @Override 
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { 
        // 1. 요청이 들어오면 고유한 8글자 Trace ID를 생성 
        String traceId = UUID.randomUUID().toString().substring(0, 8); 
        // 2. MDC라는 곳에 해당 아이디를 보관 
        MDC.put(&quot;traceId&quot;, traceId); 

        try { 
            // 3.Controller로 요청을 넘김. 
            chain.doFilter(request, response); 
        } finally { 
            // 요청 처리가 끝나면 반드시 비워줘야 함 
            // 톰캣은 재사용되기 때문에, 안 비우면 다음 사람 로그에 이전 사람 ID가 섞일 수 있음 
            MDC.clear(); 
        } 
    } 

    @Override 
    public void destroy() { 
        Filter.super.destroy(); 
    } 
}</code></pre>
<h3 id="logback-설정">Logback 설정</h3>
<p>MDC 필터 생성 후 로그백 설정을 추가로 진행해야한다.</p>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;configuration&gt;
    &lt;appender name=&quot;CONSOLE&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&gt;
        &lt;encoder&gt;
            &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss.SSS} %highlight(%-5level) %magenta([%X{traceId}]) %yellow([%thread]) %cyan(%logger{36}) : %msg%n&lt;/pattern&gt;
        &lt;/encoder&gt;
    &lt;/appender&gt;

    &lt;root level=&quot;INFO&quot;&gt;
        &lt;appender-ref ref=&quot;CONSOLE&quot; /&gt;
    &lt;/root&gt;
&lt;/configuration&gt;</code></pre>
<h1 id="로그를-파일로-보관하는-방법---logback">로그를 파일로 보관하는 방법 -&gt; LOGBACK</h1>
<h2 id="appender">APPENDER</h2>
<p>Logback에서는 로그를 배송하는 목적지(출력 위치)를 APPENDER가 결정한다.</p>
<ol>
<li>ConsoleAppender<ul>
<li>개발자의 모니터에 로그를 출력함</li>
<li>휘발성</li>
</ul>
</li>
<li>FileAppender<ul>
<li>로그를 텍스트 파일(.log) 안에 기록</li>
<li>삭제하지 않는 한 영구 보관</li>
</ul>
</li>
<li>RollingFileAppender<ul>
<li>FileAppender만 사용하면 1년 치 로그가 하나의 파일에 다 쌓여서 용량이 커짐</li>
<li>매일 자정이 되면 어제 쓴 로그를 저장하고, 오늘 날짜가 적힌 새로운 로그를 만들어냄</li>
<li>현업, 실무에서는 대부분 해당 Appender 사용</li>
</ul>
</li>
</ol>
<h2 id="logback-설정-파일-구성">Logback 설정 파일 구성</h2>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;configuration&gt;

    &lt;!-- property : 자바의 변수 같은 것 --&gt;
    &lt;!-- 로그를 저장할 경로 --&gt;
    &lt;property name=&quot;LOG_DIR&quot; value=&quot;./logs&quot; /&gt;
    &lt;property name=&quot;LOG_FILE_NAME&quot; value=&quot;waiting-api-log&quot; /&gt;

    &lt;!-- 로그를 출력할 곳을 정하는것이 appender --&gt;
    &lt;appender name=&quot;CONSOLE&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&gt;
        &lt;encoder&gt;
            &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) [%X{traceId}] %cyan(%logger{36}) : %msg%n&lt;/pattern&gt;
        &lt;/encoder&gt;
    &lt;/appender&gt;

    &lt;appender name=&quot;FILE&quot; class=&quot;ch.qos.logback.core.rolling.RollingFileAppender&quot;&gt;
        &lt;file&gt;${LOG_DIR}/${LOG_FILE_NAME}.log&lt;/file&gt;

        &lt;encoder&gt;
            &lt;pattern&gt;%d{yyyy-MM-dd HH:mm:ss} %-5level [%X{traceId}] %logger{36} : %msg%n&lt;/pattern&gt;
        &lt;/encoder&gt;

        &lt;rollingPolicy class=&quot;ch.qos.logback.core.rolling.TimeBasedRollingPolicy&quot;&gt;
&lt;!--            &lt;fileNamePattern&gt;${LOG_DIR}/${LOG_FILE_NAME}-%d{yyyy-MM-dd}.log.gz&lt;/fileNamePattern&gt;--&gt;
&lt;!--            이름이 MM-dd 기준으로 생성된다 즉, 날마다 로그파일을 새로 만든다! --&gt;
&lt;!--            gz는 압축 하겠다라는 의미이다 --&gt;
&lt;!--            &lt;maxHistory&gt;30&lt;/maxHistory&gt;--&gt;
&lt;!--            로그를 최대 30일치만 남기고 31일째 로그부터는 알아서 지워줘!--&gt;
            &lt;fileNamePattern&gt;${LOG_DIR}/${LOG_FILE_NAME}-%d{yyyy-MM-dd_HH-mm}.log.gz&lt;/fileNamePattern&gt;
&lt;!--            이름이 HH-mm 기준으로 생성된다 즉, 분마다 로그파일을 새로 만든다! ---&gt;            
            &lt;maxHistory&gt;3&lt;/maxHistory&gt;
&lt;!--            로그를 최대 3분치만 남기고 4분째 로그부터는 알아서 지워줘! ---&gt;
        &lt;/rollingPolicy&gt;
    &lt;/appender&gt;

    &lt;root level=&quot;INFO&quot;&gt;
        &lt;appender-ref ref=&quot;CONSOLE&quot; /&gt; 
        &lt;appender-ref ref=&quot;FILE&quot; /&gt;    
    &lt;/root&gt;

&lt;/configuration&gt;</code></pre>
<h1 id="elk">ELK</h1>
<h2 id="서버가-여러-대일-때는-로그를-어떻게-찾을까">서버가 여러 대일 때는 로그를 어떻게 찾을까?</h2>
<p><img src="https://velog.velcdn.com/images/regular_jk_kim/post/c70222f1-1f1c-43de-98b0-ff7582959cb0/image.png" alt=""></p>
<ul>
<li>서버가 3대라고 가정</li>
<li>고객 문의가 들어와 로그를 찾아야 한다고 하면 몇 번 서버를 탐색해야 할까?</li>
<li>에러를 찾기 위해 서버 1번부터 서버 3번까지 압축된 로그 파일을 하나하나 열어봐야한다.</li>
<li>귀찮으니 여러 서버에 흩어진 로그를 하나의 거대한 중앙 저장소로 모아두자!</li>
<li>이 기술을 ELK라고 부른다.</li>
</ul>
<h2 id="elk-스택이란">ELK 스택이란</h2>
<blockquote>
<p>방대한 로그 데이터를 실시간으로 수집, 검색, 분석 및 시각화하는 오픈소스 플랫폼</p>
</blockquote>
<ul>
<li>E(Elasticsearch) : 수많은 로그를 저장하고, 빠른 속도로 검색할 수 있게 지원</li>
<li>L(Logstash) : 각 서버에서 로그를 수집, 변환하여 Elasticsearch로 전달하는 도구</li>
<li>K(Kibana) : Elasticsearch에 쌓인 데이터를 시각화하여 그래프와 대시보드로 보여주는 분석 도구</li>
</ul>
<h2 id="logstash">Logstash</h2>
<blockquote>
<p>Logstash는 세 가지 단계(input, filter, output)를 통해 움직인다.</p>
</blockquote>
<h3 id="핵심-특징-및-기능">핵심 특징 및 기능</h3>
<ul>
<li>input<ul>
<li><code>.log</code> 파일을 한 줄씩 실시간으로 읽어온다.</li>
</ul>
</li>
<li>filter<ul>
<li>로그를 그냥 통째로 텍스트로 넣지 않고 가공한다.</li>
<li>시간, 로그레벨, Trace ID 등 정보에 라벨링을 해서 구분한다.</li>
</ul>
</li>
<li>output<ul>
<li>Logstash의 최종 목적지인 Elasticsearch로 데이터를 보낸다.</li>
</ul>
</li>
</ul>
<h3 id="설정-예시">설정 예시</h3>
<p><code>logstash.conf</code> 파일 예시를 살펴보자.</p>
<pre><code class="language-conf"># 1. Input
input {
  file {
    # 도커 컨테이너 안에서 바라보는 로그 파일의 위치
    path =&gt; &quot;/usr/share/logstash/logs/waiting-api-log.log&quot;

    # 처음 켤 때 파일의 맨 처음부터 끝까지 싹 다 읽어오라는 뜻
    start_position =&gt; &quot;beginning&quot;

    # 파일이 끝났는지 확인하는 주기 (기본 1초에 한 번씩 감시)
    stat_interval =&gt; 1
  }
}

# 2. Filter
filter {
  # logstash grok 검색후 적용해보기
}

# 3. Output
output {
  # 목적지인 엘라스틱서치로 보냄
  elasticsearch {
    hosts =&gt; [&quot;http://elasticsearch:9200&quot;]
    # 엘라스틱서치에 저장될 인덱스 이름! 날짜별로 분류함
    index =&gt; &quot;waiting-api-logs-%{+YYYY.MM.dd}&quot;
  }

  # 잘 전송되고 있는지 도커 콘솔 화면에서도 확인하기 위해 추가
  stdout { codec =&gt; rubydebug }
}</code></pre>
<h2 id="elasticsearch를-로그-수집에-사용하는-이유">Elasticsearch를 로그 수집에 사용하는 이유</h2>
<h3 id="로그-파일-저장에-db를-사용하면-안-되는-이유">로그 파일 저장에 DB를 사용하면 안 되는 이유</h3>
<ul>
<li>로그는 하루에 수천만 건씩 쌓이는 텍스트 데이터이다.</li>
<li>MySQL과 같은 관계형 DB는 이런 방대한 텍스트에서 특정 단어를 검색하는 게 매우 오래 걸린다.</li>
<li>따라서 Elasticsearch와 같은 검색 엔진을 사용하는 것이 더 적절한다.</li>
</ul>
<h3 id="elasticsearch가-적절한-이유">Elasticsearch가 적절한 이유</h3>
<ul>
<li>Elasticsearch는 태생이 검색 엔진이다.</li>
<li>관계형 DB의 경우 특정단어를 찾기 위해 첫 번째 페이지부터 끝까지 넘기면서 해당 단어를 찾는다 -&gt; 느림</li>
<li>Elasticsearch의 경우 Index를 활용하여 데이터를 아주 빠르게 찾을 수 있다.<ul>
<li>데이터가 1억 건이어도 0.1초 만에 Index를 보고 찾아낸다.</li>
<li>이 개념을 Inverted Index(역인덱스)라고 부른다.</li>
</ul>
</li>
</ul>
<h2 id="kibana의-역할">Kibana의 역할</h2>
<blockquote>
<p>Elasticsearch에 쌓인 데이터를 시각화하여 그래프와 대시보드로 보여주는 분석 도구</p>
</blockquote>
<p>Elasticsearch에 쿼리를 실행하고, 결과를 다양한 형태로 보여준다.</p>
<h2 id="elk-docker-compose">ELK Docker Compose</h2>
<pre><code class="language-yml">services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:9.1.0
    container_name: elasticsearch
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - xpack.security.http.ssl.enabled=false
      - &quot;ES_JAVA_OPTS=-Xms512m -Xmx512m&quot;
    ports:
      - &quot;9200:9200&quot;
  logstash:
    image: docker.elastic.co/logstash/logstash:9.1.0
    container_name: logstash
    volumes:
      - ./logs:/usr/share/logstash/logs
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
    ports:
      - &quot;5044:5044&quot;
    depends_on:
      - elasticsearch

  kibana:
    image: docker.elastic.co/kibana/kibana:9.1.0 # 무조건 ES, Logstash와 버전을 맞춰야 한다! (9.1.0)
    container_name: kibana
    ports:
      - &quot;5601:5601&quot; # 키바나 포트 번호
    environment:
      # 키바나가 데이터를 가져올 주소
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[251130]]></title>
            <link>https://velog.io/@regular_jk_kim/251130</link>
            <guid>https://velog.io/@regular_jk_kim/251130</guid>
            <pubDate>Sun, 30 Nov 2025 10:01:20 GMT</pubDate>
            <description><![CDATA[<p>#회고 </p>
<h2 id="251130-회고-💬">251130 회고 💬</h2>
<p> 미세먼지가 심해졌다. 또 시작이구나,,, 하늘이 회색빛이다. 나가기 싫다... 💀 퇴근하는 길에 전직장 동료분을 만났다. 처음에 먼저 알아보시고 먼저 말을 걸어주셨다. 이런 우연이라니! 신기하다. 이래서 있을 때 잘 하라는 말이 있는건가... 이런 저런 이야기하면서 신기한 경험을 했다.</p>
<p>싸피 2학기가 끝났다고한다. 오랜만에 싸피 친구들과 만나서 즐거운 시간을 보냈다. 다들 좋은곳에 취업하길 응원한다!</p>
<h2 id="keep-👍">Keep 👍</h2>
<h3 id="출퇴근-자투리-공부">출퇴근 자투리 공부</h3>
<p>장고 공부를 하고있다. </p>
<h3 id="개인-공부">개인 공부</h3>
<p>우분투 공부를 하고 있다. 지금까지 윈도우, 맥 두 종류의 os 만 사용해봤다. 다행히 맥을 오래 사용했었기 때문에 이런저런 명령어를 몇가지는 알고 있다. 그럼에도 어려운 점이 있다면 터미널로 모든 일을 처리한다는 부분이다. 물론 브라우저도 있고 이런저런 인터페이스가 있기는 하다. 그럼에도 원하는 작업을 하려면 명령어로 처리를 해야하다보니 어색한 부분이 많다.</p>
<p>수동이긴 하지만 우분투에 서버 실행 후 외부에서 접속하는 과정까지 성공했다. 접속이 된다는 테스트를 완료한 것이다. 이제 cicd 구축만 남았다. 사실 cicd 구축이 안 되면 헛수고가 되버리는건 사실이다. 그럼에도 aws 사용하지 않고 로컬에서 구동하는 경험을 했다.</p>
<h3 id="운동">운동</h3>
<p>월화수목금 운동나가기를 도전 중이다. 이번 주에는 1일 성공했다!</p>
<ul>
<li>월요일 어깨</li>
<li>화요일 가슴</li>
<li>수요일 하체</li>
<li>목요일 등</li>
<li>금요일 이두삼수 (잘 안 함)</li>
</ul>
<h3 id="독서">독서</h3>
<h2 id="try-🧚">Try 🧚</h2>
<ul>
<li>장고 공부하기</li>
<li>도커 공부하기</li>
<li>쿠버네티스 공부하기</li>
<li>혼자 공부하는 네트워크 읽기</li>
</ul>
<h2 id="독서-목록">독서 목록</h2>
<h3 id="서평-완료-목록">서평 완료 목록</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
<h3 id="서평-예정-목록-읽는-중">서평 예정 목록 (읽는 중)</h3>
<ul>
<li>혼자 공부하는 네트워크 (19%)</li>
</ul>
<h3 id="으악-재미없어-🤪">으악 재미없어,,, 🤪</h3>
<ul>
<li><del>면접을 위한 CS 전공지식 노트</del></li>
<li><del>한 권으로 읽는 컴퓨터 구조와 프로그래밍</del></li>
</ul>
<h3 id="독서-예정-목록">독서 예정 목록</h3>
<ul>
<li>혼자 공부하는 네트워크</li>
<li>오브젝트</li>
<li>HTTP 완벽 가이드</li>
<li>자바/스프링 개발자를 위한 실용주의 프로그래밍</li>
<li>모던 자바 인 액션</li>
<li>자바 성능 튜닝 이야기 </li>
<li>헤드 퍼스트 서블릿</li>
<li>파이브 라인스 오브 코드</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[251123]]></title>
            <link>https://velog.io/@regular_jk_kim/251123</link>
            <guid>https://velog.io/@regular_jk_kim/251123</guid>
            <pubDate>Sun, 23 Nov 2025 10:53:25 GMT</pubDate>
            <description><![CDATA[<p>#회고 </p>
<h2 id="251123-회고-💬">251123 회고 💬</h2>
<p>갑자기 엄청 추워졌다! 진짜 겨울날씨가 돼버렸다. 사무실이 좀 더웠는데 오히려 좋을지도? 회사 분들과 회식도 했다! 고기 먹었다. 단백질 냠냠냠 🍖 회사에서 인프라 역할을 같이 맡아 작업을 하게 됐다. 우효! 언젠가는 그리고 한번쯤은 해보고 싶다고 늘 생각만 하고 있었다. 인프라 작업을 내가 도맡아서 하게 되다니! 설레는 순간이다. 리눅스부터 설치하고, 이것저것 내가 하고 싶은대로 맘대로 할 수 있을듯 싶다! 신난다!</p>
<h2 id="keep-👍">Keep 👍</h2>
<h3 id="출퇴근-자투리-공부">출퇴근 자투리 공부</h3>
<p>장고 공부를 하고있다. </p>
<ul>
<li>페이지네이션</li>
<li>자기 참조 모델</li>
<li>애플리케이션 제작 실습</li>
</ul>
<h3 id="개인-공부">개인 공부</h3>
<p>요즘 도커와 씨름하는 중이다. 환경 변수 주입하기가 잘 안 된다. 대충 개념만 숙지하고 실전으로 부딪히는 중이다. 잘 안된다. GPT 답변이 자꾸 변하니까 뭐가 맞는지도 잘 모르겠다. 후 어렵다...</p>
<h3 id="운동">운동</h3>
<p>월화수목금 운동나가기를 도전 중이다. 이번 주에는 3일 성공했다!</p>
<ul>
<li>월요일 어깨</li>
<li>화요일 가슴</li>
<li>수요일 하체</li>
<li>목요일 등</li>
<li>금요일 이두삼수 (잘 안 함)</li>
</ul>
<h3 id="독서">독서</h3>
<p>다시 출퇴근 시간에 판타지 소설을 읽기 시작했다. 너무 재밌다. 드래곤 라자를 읽고 있다. 학생일 때 읽을까 말까 고민했던 기억이 있다. 왜냐하면 눈물을 마시는 새를 그때 너무 재밌게 읽어서 김영도 작가에 관심이 많았었다. 근데 안 읽었다. 왜냐하면 돈이 없었거든,,, 그렇지만 이제는 읽을 수 있다! 출퇴근이 순식간에 끝나는 마법같은 경험을 하는 중이다. 너무 재밌어!</p>
<h2 id="try-🧚">Try 🧚</h2>
<ul>
<li>장고 공부하기</li>
<li>도커 공부하기</li>
<li>쿠버네티스 공부하기</li>
<li>혼자 공부하는 네트워크 읽기</li>
</ul>
<h2 id="독서-목록">독서 목록</h2>
<h3 id="서평-완료-목록">서평 완료 목록</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
<h3 id="서평-예정-목록-읽는-중">서평 예정 목록 (읽는 중)</h3>
<ul>
<li>혼자 공부하는 네트워크 (19%)</li>
</ul>
<h3 id="으악-재미없어-🤪">으악 재미없어,,, 🤪</h3>
<ul>
<li><del>면접을 위한 CS 전공지식 노트</del></li>
<li><del>한 권으로 읽는 컴퓨터 구조와 프로그래밍</del></li>
</ul>
<h3 id="독서-예정-목록">독서 예정 목록</h3>
<ul>
<li>혼자 공부하는 네트워크</li>
<li>오브젝트</li>
<li>HTTP 완벽 가이드</li>
<li>자바/스프링 개발자를 위한 실용주의 프로그래밍</li>
<li>모던 자바 인 액션</li>
<li>자바 성능 튜닝 이야기 </li>
<li>헤드 퍼스트 서블릿</li>
<li>파이브 라인스 오브 코드</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[251116]]></title>
            <link>https://velog.io/@regular_jk_kim/251116</link>
            <guid>https://velog.io/@regular_jk_kim/251116</guid>
            <pubDate>Sun, 16 Nov 2025 08:40:40 GMT</pubDate>
            <description><![CDATA[<p>#회고 </p>
<h2 id="251116-회고-💬">251116 회고 💬</h2>
<p>주말에 창립기념일 행사가 있었다. 회사 사람들과 친해지는 시간이 되었다. 오래오래 다녀보자! 이번에는 제발!</p>
<h2 id="keep-👍">Keep 👍</h2>
<h3 id="출퇴근-자투리-공부">출퇴근 자투리 공부</h3>
<p>장고 공부를 하고있다. </p>
<ul>
<li>DRF</li>
<li>Serializer</li>
<li>Mixin</li>
<li>GenericAPIView</li>
<li>GenericViewSet</li>
</ul>
<h3 id="개인-공부">개인 공부</h3>
<ul>
<li>도커 사용할 일이 생겨서 평일에 도커 공부를 했다. 마법같이 동작하는 신기한 기술,,, 여전히 뭔지 잘 모르겠다. 내용은 <a href="https://velog.io/@regular_jk_kim/Docker-Compose">여기서</a> 확인 가능하다.</li>
</ul>
<h3 id="운동">운동</h3>
<p>매일 헬스장에 나가 운동하려고 노력 중이다. 이번 평일에는 <strong>3</strong>일 성공했다. 평소에 데드리프트를 안 한다. 이유는 귀찮아서. 근데 등 운동 하는 날에 사람이 너무 많아서 데드리프트를 도전해봤다. 몇년 전에 40kg이 무거워서 상당히 힘들게 들었던 기억이 있다. 그런데 놀랍게도 50kg이 상당히 쉬웠다. 자세가 불안해서 몇번 안 들었는데 주말동안 자세 공부하면 다음주 쯤에 60kg 정도까지도 가능할거 같다! 우효!</p>
<p>금요일에 처음으로 팔운동을 했다. 어깨, 가슴 운동하는 날에 팔에 힘이 많이 들어가서 평소에는 팔운동을 안 했다. 그런데 이번 주에는 운동 못한 날이 많아서 금요일까지 운동을 해버렸다. 후후 이거 운동이 루틴이 된듯 싶다.</p>
<p>평일에 하체 운동을 못했다. 약속이 생겨서 친구들을 만났다. 안 하고 넘어가기에는 아쉬운 운동이라 계속 신경쓰였다. 신경쓰면서 찝찝하게 있기보다는 운동을 해버리기로 결정! 토요일에 헬스장에 출근했다. 그리고 하체 운동을 했다. 후 앉았다 일어나기가 너무 힘들다... 🦵</p>
<ul>
<li>월요일 어깨</li>
<li>화요일 가슴</li>
<li>수요일 하체</li>
<li>목요일 등</li>
<li>금요일 이두삼수 (잘 안 함)</li>
</ul>
<h3 id="독서">독서</h3>
<p>혼자 공부하는 네트워크를 읽고있다. 회사에서 힘을 못 내는 순간이다. 이러려고 이렇게 긴 시간을 공부했나 의문이 든다. 후 열심히 해야지 화이팅이다 제발...</p>
<h2 id="try-🧚">Try 🧚</h2>
<ul>
<li>장고 공부하기</li>
<li>도커 공부하기</li>
<li>쿠버네티스 공부하기</li>
<li>혼자 공부하는 네트워크 읽기</li>
</ul>
<h2 id="problem-🤢">Problem 🤢</h2>
<p>요즘 독서를 못(안)하고 있다. 책읽기가 왜이렇게 싫지,,, 주말 만이라도 읽으려고 노력 중인데 참 쉽지가 않다.</p>
<h2 id="독서-목록">독서 목록</h2>
<h3 id="서평-완료-목록">서평 완료 목록</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
<h3 id="서평-예정-목록-읽는-중">서평 예정 목록 (읽는 중)</h3>
<ul>
<li>혼자 공부하는 네트워크 (19%)</li>
</ul>
<h3 id="으악-재미없어-🤪">으악 재미없어,,, 🤪</h3>
<ul>
<li><del>면접을 위한 CS 전공지식 노트</del></li>
<li><del>한 권으로 읽는 컴퓨터 구조와 프로그래밍</del></li>
</ul>
<h3 id="독서-예정-목록">독서 예정 목록</h3>
<ul>
<li>혼자 공부하는 네트워크</li>
<li>오브젝트</li>
<li>HTTP 완벽 가이드</li>
<li>자바/스프링 개발자를 위한 실용주의 프로그래밍</li>
<li>모던 자바 인 액션</li>
<li>자바 성능 튜닝 이야기 </li>
<li>헤드 퍼스트 서블릿</li>
<li>파이브 라인스 오브 코드</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker Compose]]></title>
            <link>https://velog.io/@regular_jk_kim/Docker-Compose</link>
            <guid>https://velog.io/@regular_jk_kim/Docker-Compose</guid>
            <pubDate>Fri, 14 Nov 2025 14:40:15 GMT</pubDate>
            <description><![CDATA[<p>#docker </p>
<p>도커 컴포즈 개념과 사용 방법을 간단하게 살펴보자!</p>
<h2 id="docker-compose란">Docker Compose란?</h2>
<blockquote>
<p>여러 개의 Docker 컨테이너들을 하나의 서비스로 정의하고 구성해 하나의 묶음으로 관리할 수 있게 도와주는 툴</p>
</blockquote>
<h2 id="왜-사용하는거에요">왜 사용하는거에요?</h2>
<ol>
<li><p>여러 개의 컨테이너를 관리하는 데 용이
여러 개의 컨테이너로 이루어진 복잡한 애플리케이션을 한 번에 관리할 수 있게 해준다. 여러 컨테이너를 하나의 환경에서 실행하고 관리하는 데 도움이 된다.</p>
</li>
<li><p>복잡한 명령어로 실행시킨 과정을 간소화 할 수 있다.
Docker Compose를 사용하면 컨테이너를 실행시킬 때마다 복잡한 명령어를 입력하지 않아도 된다. 단순히 <code>docker compose up</code> 명령어만 실행시키면 된다.</p>
</li>
</ol>
<h2 id="자주-사용하는-docker-compose-cli-명령어">자주 사용하는 Docker Compose CLI 명령어</h2>
<h3 id="composeyml-파일-작성">compose.yml 파일 작성</h3>
<pre><code class="language-yml">services:
    webserver:
        container_name: webserver
        image: nginx
        ports:
            - 80:80</code></pre>
<ul>
<li>Docker Compose에서는 하나의 컨테이너를 <code>service</code>라고 부른다. </li>
<li><code>services</code> : service를 나열하기 전 입력하는 명령어</li>
<li><code>webserver</code> : services 바로 밑에 위치한 이름은 service의 이름을 나타낸다. 임의로 작성하면 된다.</li>
<li><code>container_name</code> : 서비스가 컨테이너가 될 때 가지게될 이름을 의미한다.</li>
<li><code>image</code> : 서비스의 기반이될 이미지를 의미한다.</li>
<li><code>ports</code> : 서비스가 컨테이너가 됐을 때 사용할 <code>호스트:컨테이너</code> 포트를 의미한다.</li>
</ul>
<h3 id="composeyml에서-정의한-컨테이너-실행">compose.yml에서 정의한 컨테이너 실행</h3>
<pre><code class="language-sh">docker compose up    # 포그라운드에서 실행
docker compose up -d # 백그라운드에서 실행</code></pre>
<h3 id="docker-compose로-실행시킨-컨테이너-확인하기">Docker Compose로 실행시킨 컨테이너 확인하기</h3>
<pre><code class="language-sh"># compose.yml에 정의된 컨테이너 중 실행 중인 컨테이너만 보여준다.
docker compose ps
# compose.yml에 정의된 모든 컨테이너를 보여준다.
docker compose ps -a</code></pre>
<h3 id="docker-compose-로그-확인하기">Docker Compose 로그 확인하기</h3>
<pre><code class="language-sh"># compose.yml에 정의된 모든 컨테이너의 로그를 모아서 출력한다.
docker compose logs</code></pre>
<h3 id="컨테이너-실행-전에-이미지-재빌드하기">컨테이너 실행 전에 이미지 재빌드하기</h3>
<pre><code class="language-sh">docker compose up --build    # 포그라운드에서 실행
docker compose up -d --build # 백그라운드에서 실행</code></pre>
<ul>
<li><code>compose.yml</code>에서 정의한 이미지 파일에서 코드가 변경 됐을 경우, 이미지를 다시 빌드해서 컨테이너를 실행시켜야 변경된 부분이 적용된다. 그러므로 <code>--build</code> 옵션을 추가해서 사용해야 한다.</li>
<li><code>docker compose up</code> : 이미지가 없을 때만 빌드해서 컨테이너를 실행시킨다. 이미지가 존재하는 경우 컨테이너만 실행시킨다.</li>
<li><code>docker compose up --build</code> : 이미지가 있더라도 빌드를 다시해서 컨테이너를 실행시킨다.</li>
</ul>
<h3 id="이미지-다운받기-업데이트하기">이미지 다운받기, 업데이트하기</h3>
<pre><code class="language-sh">docker compose pull</code></pre>
<ul>
<li><code>compose.yml</code>에서 정의된 이미지를 다운 받거나 업데이트 한다.<ul>
<li>로컬 환경에 이미지가 없다면 이미지를 다운 받는다.</li>
<li>로컬 환경에 이미 이미지가 있는데, Dockerhub의 이미지와 다른 이미지일 경우 이미지를 업데이트 한다.</li>
</ul>
</li>
</ul>
<h3 id="docker-compose에서-이용한-컨테이너-종료하기">Docker Compose에서 이용한 컨테이너 종료하기</h3>
<pre><code class="language-sh">docker compose down</code></pre>
<h2 id="docker-compose-사용해서-redis-띄워보기">Docker Compose 사용해서 Redis 띄워보기</h2>
<pre><code class="language-yml">services:
    my-cache-server:
        image: redis
        ports:
            - 6379:6379</code></pre>
<ul>
<li>도커 컴포즈 파일 생성</li>
</ul>
<pre><code class="language-sh">docker compose up -d</code></pre>
<ul>
<li>도커 컴포즈 실행</li>
</ul>
<pre><code class="language-sh">docker compose ps
docker ps
docker compose down</code></pre>
<ul>
<li>도커 컨테이너 확인</li>
</ul>
<h2 id="docker-compose-사용해서-mysql-띄워보기">Docker Compose 사용해서 MySQL 띄워보기</h2>
<pre><code class="language-yml">services:
    my-db:
        image: mysql
        environment:
            MYSQL_ROOT_PASSWORD: password123
        volumes:
            - ./mysql_data:/var/lib/mysql # docker compose는 상대경로 허용
        ports:
            - 3306:3306</code></pre>
<h2 id="docker-compose-사용해서-springboot-띄워보기">Docker Compose 사용해서 SpringBoot 띄워보기</h2>
<pre><code class="language-yml">services:
    my-server:
        build: . # Dockerfile 과 같은 경로에 있다고 가정 (상대경로)
        ports:
            - 8080:8080</code></pre>
<ul>
<li>docker-compose.yml 파일과 Dockerfile 파일이 같은 경로에 있다고 가정한다. build 명령어는 Dockerfile을 찾아 내용에 맞게 빌드를 시작한다.</li>
</ul>
<pre><code class="language-sh">docker compose up -d --build</code></pre>
<ul>
<li>이후 Docker Compose 명령어를 실행할 때 <code>--build</code> 옵션을 추가한다. </li>
<li>스프링 부트 프로젝트에 패치가 있다면 <code>--build</code> 명령어로 해당 내역을 반영한다.</li>
</ul>
<h2 id="redis와-mysql-동시에-실행시키기">Redis와 MySQL 동시에 실행시키기</h2>
<pre><code class="language-yml">services:
    my-db:
        image: mysql
        environment:
            MYSQL_ROOT_PASSWORD: password123
        volumes:
            - ./mysql_data:/var/lib/mysql
        ports:
            - 3306:3306
    my-cache-server:
        image: redis
        ports:
            - 6379:6379</code></pre>
<h2 id="springboot와-mysql-동시에-실행시키기">SpringBoot와 MySQL 동시에 실행시키기</h2>
<p><code>compose.yml</code> (스프링 프로젝트에 포함)</p>
<pre><code class="language-yml">services:
    my-server:
        build: .
        ports:
            - 8080:8080
        depends_on:
            my-db:
                condition: service_healthy

    my-db:
        image: mysql
        environment:
            MYSQL_ROOT_PASSWORD: password123
            MYSQL_DATABASE: mydb              # db 이름
        volumes:
            - ./mysql_data:/var/lib/mysql
        ports:
            - 3306:3306
        healthckeck:
            test: [ &quot;CMD&quot;, &quot;mysqladmin&quot;, &quot;ping&quot; ]
            interval: 5s
            retries: 10</code></pre>
<ul>
<li><code>depends_on</code> : 스프링 애플리케이션은 MySQL에 의존한다. MySQL 컨테이너가 아직 실행 전이라면 스프링 서버도 실행될 수 없다. 따라서 컨테이너 실행 순서를 지정해야한다. 이때 사용하는 명령어이다.</li>
</ul>
<h2 id="springboot와-mysql-redis-동시에-실행시키기">SpringBoot와 MySQL, Redis 동시에 실행시키기</h2>
<pre><code class="language-yml">services:
    my-server:
        build: .
        ports:
            - 8080:8080
        depends_on:
            my-db:
                condition: service_healthy
            my-cache-server:
                condition: service_health

    my-db:
        image: mysql
        environment:
            MYSQL_ROOT_PASSWORD: password123
            MYSQL_DATABASE: mydb              # db 이름
        volumes:
            - ./mysql_data:/var/lib/mysql
        ports:
            - 3306:3306
        healthckeck:
            test: [ &quot;CMD&quot;, &quot;mysqladmin&quot;, &quot;ping&quot; ]
            interval: 5s
            retries: 10

    my-cache-server:
        image: redis
        ports:
            - 6379:6379
        healthcheck:
            test: [ &quot;CMD&quot;, &quot;redist-cli&quot;, &quot;ping&quot; ]
            interval: 5s
            retries: 10</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[Dockerfile]]></title>
            <link>https://velog.io/@regular_jk_kim/Dockerfile</link>
            <guid>https://velog.io/@regular_jk_kim/Dockerfile</guid>
            <pubDate>Wed, 12 Nov 2025 14:42:55 GMT</pubDate>
            <description><![CDATA[<p>#docker </p>
<p>도커 파일 개념과 사용 방법을 간단하게 살펴보자!</p>
<h2 id="dockerfile이란">Dockerfile이란?</h2>
<p>Docker 이미지는 Dockerhub를 통해 다운받아 사용할 수 있다. 이때 사용되는 이미지를 만드는 기술이다.</p>
<blockquote>
<p>Dockerfile을 활용해 Docker 이미지를 만들 수 있다.</p>
</blockquote>
<p>Dockerhub에 올려놓은 Docker 이미지가 아닌, 나만의 Docker 이미지를 만들고 싶을 수 있다. 예를 들어 내가 만든 Django 프로젝트가 있다. 내가 만든 프로젝트 자체를 Dockerfile을 활용하여 Docker 이미지로 만들 수 있다.</p>
<h2 id="from-베이스-이미지-생성"><code>FROM</code> 베이스 이미지 생성</h2>
<p><code>FROM</code>은 베이스 이미지를 생성하는 역할을 한다. Docker 컨테이너를 특정 초기 이미지를 기반으로 추가적인 세팅을 할 수 있다. 특정 초기 이미지가 곧 베이스 이미지이다.</p>
<p>윈도우 컴퓨터를 새로 사서 실행시키면 기본 프로그램들이 이미 설치돼있다. 베이스 이미지도 이와 똑같다. 컨테이너를 새로 띄워서 미니 컴퓨터 환경을 구축할 때 기본 프로그램이 어떤게 깔려있을지 선택하는 옵션이라고 생각하자.</p>
<p>누군가는 JDK가 깔려있는 컴퓨터 환경이 세팅되기를 바랄 수도 있고, 누군가는 Python이 깔려있는 컴퓨터 환경이 세팅되기를 바랄 수도 있다. 필요에 따라 베이스 이미지를 고르면 된다.</p>
<pre><code class="language-docker">FROM [이미지 이름]
FROM [이미지 이름]:[태그 이름]</code></pre>
<ul>
<li><code>태그 이름</code>을 명시하지 않으면 해당 이미지의 최신 버전을 사용한다.</li>
</ul>
<h2 id="베이스-이미지를-생성해보자">베이스 이미지를 생성해보자.</h2>
<h3 id="1-dockerfile-만들기">1. <code>Dockerfile</code> 만들기</h3>
<pre><code class="language-docker">FROM openjdk:17-jdk</code></pre>
<h3 id="2-dockerfile을-기반으로-이미지-만들기">2. Dockerfile을 기반으로 이미지 만들기</h3>
<pre><code class="language-sh">docker build -t [이미지 이름]:[태그 이름] [Dockerfile 디렉터리 경로]
# docker build -t my-jdk-server .
# docker build -t my-jdk-server:beta .</code></pre>
<h3 id="3-생성한-이미지로-컨테이너-띄우기">3. 생성한 이미지로 컨테이너 띄우기</h3>
<pre><code class="language-sh">docker run -d my-jdk-server</code></pre>
<h3 id="4-컨테이너-조회하기">4. 컨테이너 조회하기</h3>
<pre><code class="language-sh">docker ps # 실행되고 있는 컨테이너가 없다
docker ps -a # 컨테이너가 종료돼 있음</code></pre>
<p>왜 종료돼있지? -&gt; Docker 컨테이너는 내부적으로 필요한 명령을 다 수행했을 경우 자동으로 종료된다. 내부에 명령이 없기 때문에 자동으로 종료되었다.</p>
<h2 id="copy-파일-복사이동"><code>COPY</code> 파일 복사(이동)</h2>
<p><code>COPY</code> 명령어는 호스트 컴퓨터에 있는 파일을 복사해서 컨테이너로 전달한다.</p>
<pre><code class="language-docker">COPY [호스트 컴퓨터에 있는 복사할 파일의 경로] [컨테이너에서 파일이 위치할 경로]
# COPY app.txt /app.txt</code></pre>
<h3 id="폴더-안에-있는-모든-파일들-복사하기">폴더 안에 있는 모든 파일들 복사하기</h3>
<pre><code class="language-docker">COPY my-folder /my-folder/</code></pre>
<ul>
<li><code>/my-folder</code> 형식으로 적으면 안 되고 <code>/my-folder/</code> 형식으로 입력해야 <code>my-folder</code> 경로에 파일들이 정상적으로 복사된다.</li>
</ul>
<h2 id="와일드-카드-사용하기">와일드 카드 사용하기</h2>
<p>디렉터리에 여러 txt 파일을 만들어보자.
<code>current working directory</code></p>
<pre><code class="language-md">- tmp.txt
- my_text.txt
- readme.txt</code></pre>
<p>위와 같이 3개의 txt 파일이 있다고 가정하자. 이 모든 파일을 모두 COPY 하고자 한다면 와일드 카드 문법을 사용해 한번에 이동시킬 수 있다.</p>
<pre><code class="language-docker">COPY *.txt /my_text_files/</code></pre>
<p>txt 확장자를 가진 모든 파일들은 <code>my_text_files</code> 폴더로 이동된다.</p>
<h2 id="dockerignore-사용하기"><code>.dockerignore</code> 사용하기</h2>
<p>특정 파일 또는 폴더만 <code>COPY</code> 하고 싶지 않을 수도 있다. 그럴 때 <code>.dockerignore</code>를 사용한다.</p>
<h3 id="1-dockerignore-파일-만들기">1. <code>.dockerignore</code> 파일 만들기</h3>
<p><code>.dockerignore</code></p>
<pre><code class="language-txt">readme.txt</code></pre>
<h3 id="2-이동-명령어-작성">2. 이동 명령어 작성</h3>
<pre><code class="language-docker">COPY *.txt /my_text_files/</code></pre>
<p>txt 확장자 파일 전부를 이동하는 명령어를 입력하고 실행한다.</p>
<h3 id="3-이동된-파일-확인">3. 이동된 파일 확인</h3>
<p>이동된 파일을 살펴보면 readme.md 파일은 <code>.dockerignore</code>에 의해서 이동되지 않았음을 확인할 수 있다.</p>
<h2 id="entrypoint-컨테이너가-시잘될-때-실행되는-명령어"><code>ENTRYPOINT</code> 컨테이너가 시잘될 때 실행되는 명령어</h2>
<p><code>ENTRYPOINT</code>는 컨테이너가 실행되고 최초로 실행되는 명령어를 의미한다.</p>
<pre><code class="language-docker">ENTRYPOINT [명령문...]</code></pre>
<ul>
<li>띄어쓰기를 기준으로 쉼표로 구분하여 문자열을 입력하면 된다.</li>
</ul>
<h3 id="예시">예시</h3>
<pre><code class="language-docker">FROM ubuntu
ENTRYPOINT [&quot;/bin/bash&quot;, &quot;-c&quot;, &quot;echo hello&quot;]</code></pre>
<p>위와 같은 Dockerfile을 만들고 이미지 생성, 컨테이너를 실행시킨다.</p>
<pre><code class="language-sh">docker build -t my-server . 
docker run -d my-server 
docker ps -a 
docker logs [Container ID]</code></pre>
<p>컨테이너는 종료되었지만 로그를 확인하면 <code>hello</code> 문자열을 확인할 수 있다.</p>
<h2 id="run-이미지를-생성하는-과정에서-사용할-명령문-실행"><code>RUN</code> 이미지를 생성하는 과정에서 사용할 명령문 실행</h2>
<p><code>RUN</code>은 이미지 생성 과정에서 명령어를 실행시켜야 할 때 사용한다.</p>
<pre><code class="language-docker">RUN [명령문]
# RUN npm install</code></pre>
<h3 id="run-과-entrypoint"><code>RUN</code> 과 <code>ENTRYPOINT</code>?</h3>
<p><code>RUN</code> 명령어와 <code>ENTRYPOINT</code> 명령어는 비슷한 동작을 수행하기 때문에 헷갈릴 때가 있다. </p>
<ul>
<li><code>RUN</code> : 이미지 생성 과정에서 필요한 명령어를 실행한다.</li>
<li><code>ENTRYPOINT</code> : 생성된 이미지를 기반으로 컨테이너를 생성한 직후에 명령어를 실행시킬 때 사용한다.</li>
</ul>
<h3 id="예시-1">예시</h3>
<p>미니 컴퓨터 환경이 ubuntu로 되어있고, git이 설치되어있으면 좋겠다고 가정해보자. 이런 환경을 구성하기 위해 <code>Dockerfile</code>을 활용해 ubuntu, git이 설치된 이미지를 만들면 된다.</p>
<ol>
<li><p>Dockerfile 작성하기</p>
<pre><code class="language-docker">FROM ubuntu
RUN apt update &amp;&amp; apt install -y git
ENTRYPOINT [&quot;/bin/bash&quot;, &quot;-c&quot;, &quot;sleep 500&quot;]</code></pre>
</li>
<li><p>이미지 빌드 및 컨테이너 실행</p>
<pre><code class="language-sh">docker build -t my-server .
docker run -d my-server
docker exec -it [컨테이너 id] bash
git -v # 컨테이너 내부에 git이 설치되었는지 확인</code></pre>
</li>
</ol>
<h2 id="workdir-작업-디렉토리-지정"><code>WORKDIR</code> 작업 디렉토리 지정</h2>
<p><code>WORKDIR</code>명령어로 작업 디렉터리를 전환하면 그 이후에 등장하는 모든 <code>RUN</code>, <code>CMD</code>, <code>ENTRYPOINT</code>, <code>COPY</code>, <code>ADD</code> 명령문은 해당 디렉터리를 기준으로 실행된다. 작업 디렉터리를 굳이 지정해주는 이유는 컨테이너 내부의 폴더를 깔끔하게 관리하기 위해서이다. 컨테이너도 작은 컴퓨터와 같기 때문에 <code>Dockerfile</code>을 통해 생성되는 파일들을 특정 폴더에 정리해두는 것이 추후에 관리가 쉽다. </p>
<pre><code class="language-docker">WORKDIR [작업 디렉터리로 사용할 절대 경로]
# WORKDIR /usr/src/app</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[도커 볼륨]]></title>
            <link>https://velog.io/@regular_jk_kim/%EB%8F%84%EC%BB%A4-%EB%B3%BC%EB%A5%A8</link>
            <guid>https://velog.io/@regular_jk_kim/%EB%8F%84%EC%BB%A4-%EB%B3%BC%EB%A5%A8</guid>
            <pubDate>Mon, 10 Nov 2025 14:30:50 GMT</pubDate>
            <description><![CDATA[<p>#docker</p>
<p><strong>컨테이너 재배포에도 안전한 데이터 관리를 위한 Docker 볼륨 활용 가이드</strong></p>
<h2 id="컨테이너가-가진-문제점">컨테이너가 가진 문제점</h2>
<p>Docker를 활용하면 특정 프로그램을 컨테이너로 띄울 수 있다. 이 프로그램에 기능이 추가되면 새로운 이미지를 만들어서 컨테이너를 실행시켜야 한다. 이 때, Docker는 기존 컨테이너에서 변경된 부분을 수정하지 않고, 새로운 컨테이너를 만들어서 통째로 갈아끼우는 방식으로 교체를 한다.</p>
<p>이런 특징으로 기존 컨테이너를 새로운 컨테이너로 교체하면, 기존 컨테이너 내부에 있던 데이터도 같이 삭제된다. 만약 이 컨테이너가 DB를 실행시키는 컨테이너였다면 DB에 저장된 데이터도 같이 삭제 된다.</p>
<h2 id="도커-볼륨">도커 볼륨</h2>
<blockquote>
<p>도커 컨테이너에서 데이터를 영속적으로 저장하기 위한 방법</p>
</blockquote>
<p>볼륨은 컨테이너 자체의 저장 공간을 사용하지 않고, 호스트 자체의 저장 공간을 공유해서 사용하는 형태이다.</p>
<h2 id="명령어">명령어</h2>
<pre><code class="language-sh">docker run -v [호스트의 디렉토리 절대경로]:[컨테이너의 디렉토리 절대경로] [이미지명]:[태그명]</code></pre>
<ol>
<li>[호스트의 디렉토리 절대경로]에 디렉토리가 이미 존재할 경우 : 호스트의 디렉터리가 컨테이너의 디렉터리를 덮어씌운다.</li>
<li>[호스트의 디렉토리 절대경로]에 디렉토리가 존재하지 않을 경우 : 호스트의 디렉터리 절대 경로에 디렉터리를 새로 만들고 컨테이너의 디렉터리에 있는 파일들을 호스트의 디렉터리로 복사해온다.</li>
</ol>
<h2 id="docker로-mysql-실행시키기">Docker로 MySQL 실행시키기</h2>
<h3 id="mysql-이미지로-컨테이너-실행시키기">MySQL 이미지로 컨테이너 실행시키기</h3>
<pre><code class="language-sh">docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -d mysql</code></pre>
<ul>
<li><code>docker run</code> : 컨테이너를 생성하고 실행 (이미지가 없다면 자동으로 풀링)</li>
<li><code>-e MYSQL_ROOT_PASSWORD=password123</code> : 컨테이너 내부 MySQL 환경 변수 설정</li>
<li><code>-p 3306:3306</code> : 포트 매핑 설정, <code>[호스트 포트]:[컨테이너 포트]</code> 순서로 매핑하여 로컬에서 컨테이너 접속</li>
<li><code>-d</code> : detached 모드 실행, 컨테이너 로그를 터미널에 출력하지 않고 백그라운드에서 실행</li>
<li><code>mysql</code> : 실행할 docker 이미지 이름</li>
</ul>
<h3 id="mysql-컨테이너-접속">MySQL 컨테이너 접속</h3>
<pre><code class="language-sh">docker exec -it [MySQL 컨테이너 id] bash</code></pre>
<ul>
<li><code>docker exec</code> : 실행 중이면서 선택된 컨테이너 내부에서 명령 실행</li>
<li><code>-it</code> : <code>-i</code> (interactive), <code>-t</code> (TTY), 터미널 입력을 활성화, 쉘 형태로 상호작용</li>
<li><code>bash</code> : 컨테이너 내부에서 실행할 명령, bash 셸을 실행해 컨테이너 내부로 접속</li>
</ul>
<h3 id="컨테이너에서-mysql-접근">컨테이너에서 MySQL 접근</h3>
<pre><code class="language-sh">mysql -u root -p</code></pre>
<h3 id="mysql-데이터베이스-만들기">MySQL 데이터베이스 만들기</h3>
<pre><code class="language-sql">create database mydb;
show databases;</code></pre>
<h3 id="컨테이너-종료">컨테이너 종료</h3>
<pre><code class="language-sh">docker stop [MySQL 컨테이너 id]
docker rm [MySQL 컨테이너 id]</code></pre>
<h3 id="다시-컨테이너를-생성하여-데이터베이스를-조회해보자">다시 컨테이너를 생성하여 데이터베이스를 조회해보자</h3>
<pre><code class="language-sh">docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -d mysql 
docker exec -it [MySQL 컨테이너 ID] bash 

mysql -u root -p 
show databases;</code></pre>
<p>명령어 결과로 이전에 생성한 mydb 데이터베이스가 출력되지 않음을 확인할 수 있다.</p>
<h2 id="볼륨을-활용해-mysql-컨테이너-띄우기">볼륨을 활용해 MySQL 컨테이너 띄우기</h2>
<pre><code class="language-sh">mkdir [MySQL 데이터를 저장하고 싶은 폴더를 원하는 경로에 만들기] # mysql_data 라고 가정</code></pre>
<pre><code class="language-sh"># docker run -v [호스트의 디렉토리 절대경로]:[컨테이너의 디렉토리 절대경로] [이미지명]:[태그명]
docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v [호스트의 절대 경로]/mysql_data:/var/lib/mysql -d mysql</code></pre>
<p>이제 호스트에 컨테이너의 데이터가 저장된다!</p>
<h3 id="varlibmysql--이-경로는-어디서-알아낸거임"><code>/var/lib/mysql</code>  이 경로는 어디서 알아낸거임?</h3>
<p>해당 위치 설정은 도커 허브의 MySQL 공식 문서 <a href="https://hub.docker.com/_/mysql#user-content-where-to-store-data">Where to Store Data</a> 부분에서 확인할 수 있다.</p>
<p>postgresql 혹은 mongoDB 를 사용할 때도 공식 문서에서 해당 값을 확인할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[251109]]></title>
            <link>https://velog.io/@regular_jk_kim/251109</link>
            <guid>https://velog.io/@regular_jk_kim/251109</guid>
            <pubDate>Sun, 09 Nov 2025 07:23:54 GMT</pubDate>
            <description><![CDATA[<p>#회고 </p>
<h2 id="251109-회고-💬">251109 회고 💬</h2>
<p>11월 두 번째 주차를 회고한다.</p>
<h2 id="keep-👍">Keep 👍</h2>
<h3 id="출퇴근-자투리-공부">출퇴근 자투리 공부</h3>
<p>장고 공부를 시작했다. 장고는 정말 처음이다. 그런데 회사에서는 장고를 사용한다. 모르는데 해야한다? 공부해야지 뭐,,, 그래서 출퇴근 길에 장고 수업을 듣고 있다. 기본적으로 풀스택 프레임워크이기 때문에 화면 관련된 내용이 많다. 하지만 다행히도 api 서버 제작만 하면 되기 때문에 프론트 내용은 모두 스킵하고, 필요한 내용만 취사선택해서 학습 중이다.</p>
<ul>
<li>가상환경 (venv, poetry, uv)</li>
<li>앱 등록 (python manage.py startapp)</li>
<li>view 작성</li>
<li>마이그레이션</li>
<li>쿼리셋</li>
<li>Class-based View</li>
<li>로그인 세션</li>
<li>M2M<ul>
<li>ManyToManyField</li>
<li>through</li>
</ul>
</li>
<li>Lazy Loading</li>
<li>N + 1</li>
</ul>
<h3 id="개인-공부">개인 공부</h3>
<p>회사 온보딩 과정 겸, 실제 업무 겸 해서 장고 프로젝트를 진행 중이다. 이번에야말로 TDD를 해보겠다! 라는 큰 다짐과 함께 실제로 작업 중이다. 하지만 현실의 벽을 느끼고서 TDD는 못했다. 일단 요구사항이 디테일하지 않아서 모든 작업 사항들을 내가 먼저 추측하여 진행해야 한다. 그러면 더 TDD하기에 좋은 상황 아니냐고? 아니다. 기획 디테일을 내가 작업하다 보니 정작 코딩할 시간이 부족하다. 그래서 TDD까지는 아니더라도 API 제작 후 API에 필요한 시나리오를 작성하여 해당 시나리오에 부합하도록 테스트 코드를 작성 중이다. 대략 40개 정도 API를 구현했고 약 240개 정도의 테스트를 작업했다. 그러니까 대충 API 1개당 6개 정도의 테스트 코드 작업을 해냈다. 이것만 해도 어떤가! 🙆</p>
<h3 id="운동">운동</h3>
<p>매일 헬스장에 나가 운동하려고 노력 중이다. 이번 평일에는 <strong>2</strong>일 성공했다. </p>
<ul>
<li>월요일 어깨</li>
<li>화요일 가슴</li>
<li>수요일 하체</li>
<li>목요일 등</li>
<li>금요일 이두삼수 (잘 안 함)</li>
</ul>
<h3 id="독서">독서</h3>
<p>혼자 공부하는 네트워크를 읽고있다.</p>
<h2 id="try-🧚">Try 🧚</h2>
<ul>
<li>장고 공부하기</li>
<li>도커 공부하기</li>
<li>쿠버네티스 공부하기</li>
<li>혼자 공부하는 네트워크 읽기</li>
</ul>
<h2 id="problem-🤢">Problem 🤢</h2>
<p>요즘 독서를 못(안)하고 있다. 책읽기가 왜이렇게 싫지,,, 주말 만이라도 읽으려고 노력 중인데 참 쉽지가 않다.</p>
<h2 id="독서-목록">독서 목록</h2>
<h3 id="서평-완료-목록">서평 완료 목록</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
<h3 id="서평-예정-목록-읽는-중">서평 예정 목록 (읽는 중)</h3>
<ul>
<li>혼자 공부하는 네트워크 (19%)</li>
</ul>
<h3 id="으악-재미없어-🤪">으악 재미없어,,, 🤪</h3>
<ul>
<li><del>면접을 위한 CS 전공지식 노트</del></li>
<li><del>한 권으로 읽는 컴퓨터 구조와 프로그래밍</del></li>
</ul>
<h3 id="독서-예정-목록">독서 예정 목록</h3>
<ul>
<li>혼자 공부하는 네트워크</li>
<li>오브젝트</li>
<li>HTTP 완벽 가이드</li>
<li>자바/스프링 개발자를 위한 실용주의 프로그래밍</li>
<li>모던 자바 인 액션</li>
<li>자바 성능 튜닝 이야기 </li>
<li>헤드 퍼스트 서블릿</li>
<li>파이브 라인스 오브 코드</li>
</ul>
<h2 id="extras">Extras</h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[251102]]></title>
            <link>https://velog.io/@regular_jk_kim/251102</link>
            <guid>https://velog.io/@regular_jk_kim/251102</guid>
            <pubDate>Sun, 02 Nov 2025 10:43:29 GMT</pubDate>
            <description><![CDATA[<p>#회고 </p>
<h2 id="251102-회고-💬">251102 회고 💬</h2>
<p>아침에는 춥고, 저녁에는 덥고! 또 어느 장단에 맞춰야할지 모르겠는 날씨가 찾아왔다. 후 출퇴근마다 열받는다. </p>
<h2 id="keep-👍">Keep 👍</h2>
<h3 id="출퇴근-자투리-공부">출퇴근 자투리 공부</h3>
<p>DataBase 공부를 하고있다. 그간 애플리케이션만 공부하느라 DB 공부할 시간이 없었는데 이번 기회로 오랜만에 DB 복습을 하고 있다.</p>
<p>인덱스 부분을 공부했다. 이런말 하기 부끄럽지만 인덱스를 처음 공부했다. 매번 시작할 때 처음 부분만 공부하고 포기해버려서 인덱스까지 공부해본적이 없다. 그래서 이번에 처음으로 알게되었다. 쿼리 최적화를 신경써야할 일이 아직까지는 없었어서(<del>캐싱으로 다 해결!</del>) 인덱스 공부에 큰 흥미가 없기도 했다.</p>
<ul>
<li>인덱스</li>
<li>check 제약 조건</li>
<li>트랜잭션</li>
</ul>
<h3 id="개인-공부">개인 공부</h3>
<ul>
<li>장고 공부를 하고있다. 진도가 생각보다 안 나간다. 술만 줄이면 열공할거 같은데.... 🤪</li>
<li>장고 프로젝트를 제작하고 있다. 처음에는 장고 스타일로 제작을 하려고 했다. 그래서 스타일에 맞춰서 개발을 좀 해봤다. 그런데 하면 할수록 스타일이 맘에 안 들었다. 계층적으로 분리된 스타일을 선호한다. 그런데 장고에서는 내가 원하는 기능을 어느 레이어에서건 선언해서 사용할 수 있다. 이런 자유로움이라니! 자유로움에 불편함을 아이러니한 상황이다. 이런 상황이다보니 시간이 지날수록 점점 내 원래 스타일에 맞춰서 개발이 되고 있다. 처음에는 장고 스타일, 좀 지나고 보니 layered 스타일, 지금은 유스 케이스 스타일로 개발이 되고 있다... 이거 동료들한테 미안해지는데... 날잡고 빨리 리팩토링을 해야겠다.</li>
</ul>
<h3 id="운동">운동</h3>
<p>매일 헬스장에 나가 운동하려고 노력 중이다. 이번 평일에는 <strong>4</strong>일 성공했다. 운동 기록 어플로 무게와 횟수를 기록하기 시작했다. </p>
<h3 id="독서">독서</h3>
<p>혼자 공부하는 네트워크를 읽고있다.</p>
<h2 id="try-🧚">Try 🧚</h2>
<ul>
<li>장고 공부하기</li>
<li>도커 공부하기</li>
<li>쿠버네티스 공부하기</li>
<li>혼자 공부하는 네트워크 읽기</li>
</ul>
<h2 id="problem-🤢">Problem 🤢</h2>
<p>요즘 독서를 못(안)하고 있다. 책읽기가 왜이렇게 싫지,,, 주말 만이라도 읽으려고 노력 중인데 참 쉽지가 않다.</p>
<h2 id="독서-목록">독서 목록</h2>
<h3 id="서평-완료-목록">서평 완료 목록</h3>
<ul>
<li><a href="https://velog.io/@regular_jk_kim/%ED%98%BC%EC%9E%90-%EA%B3%B5%EB%B6%80%ED%95%98%EB%8A%94-%EC%BB%B4%ED%93%A8%ED%84%B0-%EA%B5%AC%EC%A1%B0-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">혼자 공부하는 컴퓨터 구조 + 운영체제</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%9D%98-%EA%B8%B8-%EB%A9%98%ED%86%A0%EC%97%90%EA%B2%8C-%EB%AC%BB%EB%8B%A4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0-24jpq345">프로그래머의 길, 멘토에게 묻다</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%ED%95%A8%EA%BB%98-%EC%9E%90%EB%9D%BC%EA%B8%B0-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">함께 자라기 애자일로 가는 길</a></li>
<li><a href="https://velog.io/@regular_jk_kim/%EA%B0%9D%EC%B2%B4%EC%A7%80%ED%96%A5%EC%9D%98-%EC%82%AC%EC%8B%A4%EA%B3%BC-%EC%98%A4%ED%95%B4-%EB%A5%BC-%EC%9D%BD%EA%B3%A0">객체지향의 사실과 오해</a></li>
</ul>
<h3 id="서평-예정-목록-읽는-중">서평 예정 목록 (읽는 중)</h3>
<ul>
<li>혼자 공부하는 네트워크 (19%)</li>
</ul>
<h3 id="으악-재미없어-🤪">으악 재미없어,,, 🤪</h3>
<ul>
<li><del>면접을 위한 CS 전공지식 노트</del></li>
<li><del>한 권으로 읽는 컴퓨터 구조와 프로그래밍</del></li>
</ul>
<h3 id="독서-예정-목록">독서 예정 목록</h3>
<ul>
<li>혼자 공부하는 네트워크</li>
<li>오브젝트</li>
<li>HTTP 완벽 가이드</li>
<li>자바/스프링 개발자를 위한 실용주의 프로그래밍</li>
<li>모던 자바 인 액션</li>
<li>자바 성능 튜닝 이야기 </li>
<li>헤드 퍼스트 서블릿</li>
<li>파이브 라인스 오브 코드</li>
</ul>
]]></description>
        </item>
    </channel>
</rss>