<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>jh_dev.log</title>
        <link>https://velog.io/</link>
        <description>렛츠고</description>
        <lastBuildDate>Thu, 25 Jun 2026 00:59:22 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>jh_dev.log</title>
            <url>https://velog.velcdn.com/images/jh_devlog/profile/4ccbfee0-dff4-4666-8d36-e41a31f0d1a8/image.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. jh_dev.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jh_devlog" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[Security] 취약점 분석은 어떻게 시작할까?]]></title>
            <link>https://velog.io/@jh_devlog/Security-%EC%B7%A8%EC%95%BD%EC%A0%90-%EB%B6%84%EC%84%9D%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8B%9C%EC%9E%91%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@jh_devlog/Security-%EC%B7%A8%EC%95%BD%EC%A0%90-%EB%B6%84%EC%84%9D%EC%9D%80-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%8B%9C%EC%9E%91%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Thu, 25 Jun 2026 00:59:22 GMT</pubDate>
            <description><![CDATA[<h1 id="security-취약점-분석은-어떻게-시작할까">[Security] 취약점 분석은 어떻게 시작할까?</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>보안 공부를 하다 보면 <strong>CVE, 취약점, 패치, 보안 권고문</strong>이라는 단어를 자주 보게 된다.</p>
<p>처음에는 취약점 분석이라고 하면 직접 공격 코드를 작성하거나 시스템을 해킹해보는 고급 기술처럼 느껴졌다. 하지만 공부를 하다 보니 취약점 분석은 반드시 공격을 수행하는 것만을 의미하지 않는다는 것을 알게 되었다.</p>
<p>실무에서는 공개된 취약점 정보를 확인하고, 어떤 제품이나 버전에 영향을 주는지 파악한 뒤, 현재 운영 중인 시스템에 해당 취약점이 적용되는지 판단하는 과정도 중요하다.</p>
<p>이번 글에서는 취약점 분석이 무엇인지, 어떤 흐름으로 시작하면 좋은지, 그리고 CVE, NVD, GitHub Advisory와 같은 취약점 정보를 어디서 확인할 수 있는지 정리해보려고 한다.</p>
<hr>
<h2 id="2-취약점-분석이란">2. 취약점 분석이란?</h2>
<p>취약점 분석은 시스템, 애플리케이션, 라이브러리, 네트워크 장비 등에 존재하는 <strong>보안 취약점</strong>을 파악하고, 이것이 실제 환경에서 어떤 위험으로 이어질 수 있는지 확인하는 과정이다.</p>
<p>여기서 자주 등장하는 용어로 <strong>CVE</strong>와 <strong>CWE</strong>가 있다.</p>
<p><strong>CVE</strong>는 공개된 개별 보안 취약점에 부여되는 식별 번호이고, <strong>CWE</strong>는 취약점이 발생할 수 있는 보안 약점의 유형을 분류한 것이다.</p>
<p>쉽게 말하면 CVE는 “특정 취약점의 번호표”, CWE는 “그 취약점이 어떤 약점에서 비롯되었는지 분류하는 기준”에 가깝다.</p>
<p>예를 들어 특정 소프트웨어에서 입력값 검증 부족이라는 <strong>보안 약점(CWE)</strong>으로 인해 실제 공격이 가능한 <strong>보안 취약점(CVE)</strong>이 발견될 수 있다.</p>
<p>즉, 취약점 분석은 이러한 CVE와 CWE 정보를 바탕으로 실제 운영 환경의 위험을 파악하는 과정이라고 볼 수 있다.</p>
<hr>
<h2 id="3-취약점-분석은-어떤-흐름으로-진행될까">3. 취약점 분석은 어떤 흐름으로 진행될까?</h2>
<p>취약점 분석은 일정한 흐름에 따라 정리하면 이해하기 쉽다.</p>
<blockquote>
<p>💡 <strong>취약점 분석 핵심 흐름</strong></p>
<ol>
<li><strong>정보 확인</strong>: CVE, NVD, 보안 권고문 확인</li>
<li><strong>영향 범위 파악</strong>: 영향을 받는 제품과 버전 확인</li>
<li><strong>원인 파악</strong>: 취약점 유형과 발생 원인 이해</li>
<li><strong>환경 적용 여부 판단</strong>: 우리 시스템에 해당하는지 확인</li>
<li><strong>대응 방안 정리</strong>: 패치 또는 임시 조치 검토</li>
<li><strong>문서화</strong>: 분석 결과와 대응 방안 정리</li>
</ol>
</blockquote>
<h3 id="1-취약점-정보-확인">1) 취약점 정보 확인</h3>
<p>CVE 번호, NVD, 벤더 공지, GitHub Advisory, 보안 뉴스 등을 통해 취약점이 공개되면, 단순히 번호만 보는 것이 아니라 다음 정보를 함께 수집해야 한다.</p>
<ul>
<li>취약점이 발생한 제품 또는 라이브러리</li>
<li>영향을 받는 버전</li>
<li>취약점 유형</li>
<li>심각도</li>
<li>공격 조건</li>
<li>패치 여부</li>
</ul>
<h3 id="2-영향을-받는-제품과-버전-확인">2) 영향을 받는 제품과 버전 확인</h3>
<p>취약점 분석에서 중요한 부분은 <strong>우리 환경에 영향을 주는지</strong> 판단하는 것이다.</p>
<p>아무리 심각도가 높은 취약점이라도 현재 사용 중인 제품이나 버전에 해당하지 않는다면 직접적인 영향은 없을 수 있다. 반대로 점수가 아주 높지 않더라도 외부에 노출된 서비스에서 실제 사용 중이라면 빠르게 대응해야 할 수 있다.</p>
<p>따라서 다음과 같은 질문을 던져볼 수 있다.</p>
<ul>
<li>우리 시스템에서 해당 제품이나 라이브러리를 사용하고 있는가?</li>
<li>사용 중이라면 취약한 버전인가?</li>
<li>외부에서 접근 가능한 위치에 있는가?</li>
<li>인증 없이 악용 가능한가?</li>
<li>이미 공격에 악용되고 있는 취약점인가?</li>
</ul>
<p>이 과정을 통해 단순히 “위험하다”가 아니라, <strong>우리 환경에서 얼마나 위험한가</strong>를 판단할 수 있다.</p>
<h3 id="3-취약점-원인-파악">3) 취약점 원인 파악</h3>
<p>소스코드 레벨까지 깊게 분석하기 어렵다면, 취약점 설명에 자주 등장하는 핵심 유형(CWE) 키워드를 이해하는 것부터 시작하면 좋다.</p>
<p>대표적인 취약점 유형은 다음과 같다.</p>
<ul>
<li>SQL Injection</li>
<li>XSS</li>
<li>Path Traversal</li>
<li>Remote Code Execution</li>
<li>Privilege Escalation</li>
<li>Authentication Bypass</li>
<li>Buffer Overflow</li>
<li>Insecure Deserialization</li>
</ul>
<p>이러한 키워드는 취약점이 어떤 방식으로 발생하는지 이해하는 데 도움이 된다.</p>
<h3 id="4-대응-방안-정리">4) 대응 방안 정리</h3>
<p>취약점에 대한 영향 범위와 원인을 확인했다면, 대응 방안을 정리해야 한다.</p>
<p>가장 기본적인 대응은 패치 또는 버전 업데이트이다. 하지만 바로 패치가 어려운 환경이라면 임시 대응 방안도 함께 검토해야 한다.</p>
<p>예를 들어 다음과 같은 대응이 있을 수 있다.</p>
<ul>
<li>취약한 버전을 안전한 버전으로 업데이트</li>
<li>취약한 기능 비활성화</li>
<li>외부 접근 차단</li>
<li>방화벽 또는 WAF 정책 적용</li>
<li>권한 설정 점검</li>
<li>로그 모니터링 강화</li>
<li>침해 흔적 확인</li>
</ul>
<p>보안 대응에서는 패치 전후로 영향 범위를 확인하고 이미 악용 흔적이 있는지도 함께 점검하는 것이 중요하다.</p>
<hr>
<h2 id="4-취약점-정보를-어디서-확인할-수-있을까">4. 취약점 정보를 어디서 확인할 수 있을까?</h2>
<p>취약점 분석을 시작할 때는 신뢰할 수 있는 공개 정보를 확인하는 것이 중요하다. 대표적으로 <strong>CVE, NVD, GitHub Advisory Database</strong>를 참고할 수 있다.</p>
<h3 id="1-cve">1) CVE</h3>
<p>CVE는 Common Vulnerabilities and Exposures의 약자로, 공개적으로 알려진 보안 취약점에 고유한 식별 번호를 부여하는 체계이다.</p>
<p>예를 들어 <code>CVE-2024-XXXX</code>와 같은 형식으로 표시된다. CVE 번호가 있으면 여러 보안 문서나 도구에서 같은 취약점을 동일하게 식별할 수 있다.</p>
<p>CVE는 취약점의 이름표와 같은 역할을 한다. 다만 CVE 정보만으로 모든 분석이 끝나는 것은 아니기 때문에, NVD, 벤더 공지, 패치 내역 등을 함께 확인하는 것이 좋다.</p>
<h3 id="2-nvd">2) NVD</h3>
<p>NVD는 National Vulnerability Database의 약자로, CVE 기반의 취약점 정보를 제공하는 데이터베이스이다.</p>
<p>NVD에서는 취약점 설명, 영향받는 제품, CVSS 점수, 취약점 유형, 참고 링크 등을 확인할 수 있다. CVSS 점수는 취약점의 심각도를 수치로 표현한 값으로, 대응 우선순위를 판단할 때 참고할 수 있다.</p>
<p>하지만 점수만 보고 위험도를 판단해서는 안 된다.</p>
<p>CVSS 점수는 취약점 자체의 기술적 심각도를 판단하는 데 도움이 되지만, 우리 환경의 자산 중요도나 외부 노출 여부까지 모두 반영하지는 못한다.</p>
<p>예를 들어 점수가 높은 취약점이라도 해당 시스템이 폐쇄망에 있고 외부 접근이 불가능하다면 대응 우선순위가 상대적으로 낮아질 수 있다. 반대로 점수가 비교적 낮더라도 대고객 서비스 전면에 노출되어 있다면 즉시 대응이 필요할 수 있다.</p>
<p>즉, 취약점의 위험도는 점수만으로 결정되는 것이 아니라 실제 운영 환경과 함께 판단해야 한다.</p>
<h3 id="3-github-advisory-database">3) GitHub Advisory Database</h3>
<p>GitHub Advisory Database는 GitHub에서 제공하는 보안 권고 데이터베이스이다. 특히 오픈소스 라이브러리나 패키지 취약점을 확인할 때 유용하다.</p>
<p>개발 프로젝트에서 npm, Maven, pip 같은 패키지 매니저를 사용한다면, GitHub Advisory를 통해 특정 라이브러리의 취약 버전과 패치 버전을 확인할 수 있다.</p>
<p>또한 GitHub의 Dependabot alerts를 사용하면 프로젝트에서 사용하는 의존성에 알려진 취약점이 있을 경우 알림을 받을 수 있다.</p>
<p>이처럼 GitHub Advisory는 개발 프로젝트에서 사용하는 오픈소스 의존성의 보안 위험을 확인하는 데 도움이 된다.</p>
<hr>
<h2 id="5-취약점-분석-시-주의할-점">5. 취약점 분석 시 주의할 점</h2>
<p>취약점 분석을 할 때는 몇 가지 주의할 점이 있다.</p>
<p>첫째, <strong>공개된 PoC 코드를 무분별하게 실행하면 안 된다.</strong></p>
<p>PoC는 취약점을 재현하기 위한 코드이지만, 공개된 코드라고 해서 항상 안전한 것은 아니다. 일부 PoC에는 악성 행위가 포함되어 있을 수도 있으므로, 반드시 격리된 실습 환경에서만 테스트해야 한다.</p>
<p>둘째, <strong>심각도 점수만으로 위험도를 판단하면 안 된다.</strong></p>
<p>CVSS 점수는 중요한 참고 지표이지만, 실제 대응 우선순위를 결정하는 유일한 기준은 아니다. 자산의 중요도, 외부 노출 여부, 실제 사용 여부, 공격 가능 조건 등을 함께 고려해야 한다.</p>
<p>셋째, <strong>하나의 정보원만 믿지 않는 것이 좋다.</strong></p>
<p>CVE, NVD, GitHub Advisory, 벤더 보안 공지, 패치 노트 등을 함께 확인해야 한다. 특히 벤더 공지에는 실제 패치 버전이나 임시 대응 방안이 더 구체적으로 정리되어 있는 경우가 많다.</p>
<p>넷째, <strong>허가되지 않은 시스템에서 테스트하면 안 된다.</strong></p>
<p>취약점 분석은 반드시 본인이 소유한 환경이나 명시적으로 허가받은 환경에서만 진행해야 한다. 학습 목적이라도 타인의 시스템을 대상으로 스캔하거나 공격 코드를 실행하는 것은 법적·윤리적 문제가 될 수 있다.</p>
<p>다섯째, <strong>분석 결과를 문서화해야 한다.</strong></p>
<p>취약점 이름, CVE 번호, 영향받는 버전, 위험도, 확인 방법, 대응 방안, 참고 링크를 정리해두면 이후 비슷한 취약점을 분석할 때 도움이 된다. CERT나 보안 관제 업무에서도 분석 내용을 명확하게 문서화하는 역량은 중요하다.</p>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p>취약점 분석은 단순히 공격을 시도하는 과정이 아니라, 공개된 취약점 정보를 이해하고 실제 환경에 어떤 영향을 주는지 판단하는 과정이다.</p>
<p>처음에는 CVE 번호를 검색하고, NVD나 GitHub Advisory에서 취약점 설명과 영향을 받는 버전을 확인하는 것부터 시작하면 좋다. 이후 취약점의 원인, 공격 조건, 영향 범위, 대응 방안을 차례대로 정리하면 분석 흐름을 익힐 수 있다.</p>
<p>보안 공부를 하다 보면 어려운 용어와 복잡한 공격 기법이 많이 등장한다. 하지만 취약점 분석의 시작은 거창한 기술보다, 공개된 정보를 정확히 읽고 현재 환경에 맞게 해석하는 것에서 출발한다고 생각한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] CSRF란 무엇일까?]]></title>
            <link>https://velog.io/@jh_devlog/Security-CSRF%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@jh_devlog/Security-CSRF%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Wed, 24 Jun 2026 00:57:46 GMT</pubDate>
            <description><![CDATA[<h1 id="security-csrf란-무엇일까">[Security] CSRF란 무엇일까?</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>웹 보안 취약점은 서버나 데이터베이스에서만 발생하는 것이 아니라, 사용자의 브라우저와 요청 흐름에서도 발생할 수 있다.</p>
<p>로그인, 비밀번호 변경, 게시글 작성, 결제처럼 사용자의 권한으로 처리되는 기능에서는 요청이 정말 사용자의 의도에 의해 발생한 것인지 확인하는 것이 중요하다.</p>
<p>이때 사용자가 로그인된 상태를 악용하여, 사용자가 의도하지 않은 요청을 서버로 보내게 만드는 공격이 <strong>CSRF</strong>이다.</p>
<p>이번 글에서는 CSRF가 무엇인지, 왜 발생하는지, 그리고 어떻게 방어할 수 있는지 정리해보려고 한다.</p>
<hr>
<h2 id="2-csrf란">2. CSRF란?</h2>
<p><strong>CSRF</strong>는 <strong>Cross-Site Request Forgery</strong>의 약자로, 우리말로는 <strong>사이트 간 요청 위조</strong>라고 한다.</p>
<p>CSRF는 사용자가 특정 웹 서비스에 로그인된 상태를 악용하여, 사용자가 원하지 않는 요청을 서버로 보내게 만드는 공격이다.</p>
<p>예를 들어 사용자가 어떤 사이트에 로그인한 상태에서 공격자가 만든 악성 페이지에 접속했다고 가정해보자. 이때 악성 페이지가 해당 사이트로 이메일 변경 요청을 보내도록 만들어져 있다면, 브라우저는 로그인 쿠키를 함께 전송할 수 있다.</p>
<p>서버가 쿠키만 보고 정상 사용자의 요청이라고 판단하면, 사용자는 원하지 않았는데도 이메일 주소가 변경될 수 있다.</p>
<p>즉, CSRF는 공격자가 사용자의 비밀번호를 직접 알아내는 공격이라기보다, <strong>이미 로그인된 사용자의 권한을 이용해 원하지 않는 요청을 실행시키는 공격</strong>이다.</p>
<hr>
<h2 id="3-csrf는-왜-발생할까">3. CSRF는 왜 발생할까?</h2>
<p>CSRF는 서버가 요청을 검증하는 방식이 부족할 때 발생한다.</p>
<p>웹 서비스는 사용자가 로그인하면 브라우저에 인증 쿠키를 저장한다. 이후 같은 사이트에 요청을 보낼 때 브라우저는 쿠키를 자동으로 함께 전송한다.</p>
<p>이 기능은 편리하지만, 서버가 단순히 “쿠키가 있으니 정상 요청이다”라고만 판단하면 문제가 생길 수 있다.</p>
<p>중요한 점은 <strong>인증된 사용자라는 사실과 사용자가 직접 의도한 요청이라는 사실은 다르다</strong>는 것이다.</p>
<p>CSRF가 발생하는 주요 원인은 다음과 같다.</p>
<ul>
<li>인증 쿠키가 요청에 자동으로 포함됨</li>
<li>서버가 요청의 출처나 의도를 충분히 검증하지 않음</li>
<li>중요한 기능에 추가 인증이 없음</li>
<li>CSRF Token 같은 방어 장치가 없음</li>
<li>쿠키의 SameSite 설정이 적절하지 않음</li>
</ul>
<p>최근 일부 모던 브라우저는 SameSite 속성이 명시되지 않은 쿠키를 Lax에 가깝게 처리한다. 이로 인해 단순한 CSRF 공격은 과거보다 어려워졌지만, 브라우저별 동작 차이나 서비스 구조에 따라 여전히 위험이 남을 수 있다. 따라서 SameSite 설정만 믿기보다는 CSRF Token 같은 서버 측 방어도 함께 적용하는 것이 안전하다.</p>
<hr>
<h2 id="4-예시로-이해하기">4. 예시로 이해하기</h2>
<p>예를 들어 어떤 사이트에 다음과 같은 기능이 있다고 가정해보자.</p>
<pre><code class="language-text">POST /change-email
email=attacker@example.com</code></pre>
<p>이 요청은 사용자의 이메일 주소를 변경하는 요청이다.</p>
<p>사용자가 직접 마이페이지에서 이메일을 수정했다면 정상적인 요청이다. 하지만 공격자가 만든 악성 페이지가 사용자의 브라우저를 통해 같은 요청을 보내게 만들 수도 있다.</p>
<p>사용자가 해당 사이트에 로그인된 상태라면 브라우저가 인증 쿠키를 함께 전송할 수 있고, 서버는 이를 정상 요청으로 오해할 수 있다.</p>
<p>이처럼 CSRF는 다음과 같은 동작을 유도할 수 있다.</p>
<ul>
<li>회원 정보 변경</li>
<li>비밀번호 변경 요청</li>
<li>게시글 또는 댓글 작성</li>
<li>상품 주문</li>
<li>결제 요청</li>
</ul>
<p>물론 실제 서비스에서는 결제나 비밀번호 변경 같은 중요한 기능에 추가 인증이 적용되는 경우가 많다. 하지만 이런 검증이 부족하면 CSRF 취약점으로 이어질 수 있다.</p>
<hr>
<h2 id="5-어떻게-방어할-수-있을까">5. 어떻게 방어할 수 있을까?</h2>
<h3 id="1-csrf-token-사용">1) CSRF Token 사용</h3>
<p>가장 대표적인 방어 방법은 <strong>CSRF Token</strong>을 사용하는 것이다.</p>
<p>서버는 사용자에게 예측하기 어려운 임의의 토큰 값을 발급하고, 중요한 요청을 보낼 때 이 값을 함께 전송하도록 한다.</p>
<p>서버는 요청에 포함된 토큰이 올바른지 확인한 뒤 요청을 처리한다. 공격자는 사용자의 CSRF Token 값을 알기 어렵기 때문에 요청을 위조하기 어려워진다.</p>
<hr>
<h3 id="2-samesite-cookie-설정">2) SameSite Cookie 설정</h3>
<p>쿠키에 <strong>SameSite 속성</strong>을 설정하면 외부 사이트에서 발생한 요청에 쿠키가 함께 전송되는 것을 제한할 수 있다.</p>
<p>대표적인 값은 다음과 같다.</p>
<table>
<thead>
<tr>
<th>값</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td>Strict</td>
<td>같은 사이트 요청에만 쿠키 전송</td>
</tr>
<tr>
<td>Lax</td>
<td>외부 사이트에서 들어오더라도 최상위 이동과 GET 같은 안전한 요청 중심으로 쿠키 전송 허용</td>
</tr>
<tr>
<td>None</td>
<td>교차 사이트 요청에도 쿠키 전송 허용. 단, Secure 설정 필요</td>
</tr>
</tbody></table>
<p>최근 일부 브라우저는 SameSite 값을 지정하지 않은 쿠키를 Lax에 가깝게 처리하지만, 중요한 인증 쿠키에는 명시적으로 설정하는 것이 좋다.</p>
<p>예시는 다음과 같다.</p>
<pre><code class="language-text">Set-Cookie: sessionId=...; HttpOnly; Secure; SameSite=Lax</code></pre>
<hr>
<h3 id="3-get-요청으로-상태-변경을-하지-않기">3) GET 요청으로 상태 변경을 하지 않기</h3>
<p>정보 조회처럼 서버 상태를 변경하지 않는 요청은 GET을 사용하고, 회원 정보 변경, 삭제, 결제처럼 서버 상태를 바꾸는 요청은 POST, PUT, PATCH, DELETE 등을 사용해야 한다.</p>
<p>다만 <strong>POST 메서드를 사용한다고 해서 CSRF 공격이 차단되는 것은 아니다.</strong></p>
<p>공격자는 악성 페이지 안에 숨겨진 <code>&lt;form&gt;</code> 태그를 만들고 JavaScript로 <code>submit()</code>을 강제 실행하여 POST 요청도 위조할 수 있다.</p>
<p>따라서 GET 대신 POST를 쓰는 것은 CSRF 자체를 막는 방어책이라기보다, <strong>GET 요청은 서버의 상태를 변경하지 않아야 한다</strong>는 HTTP 메서드의 기본 원칙을 지키기 위함에 가깝다.</p>
<p>즉, 서버 상태를 변경하는 기능은 GET으로 만들지 않고, CSRF Token이나 SameSite 설정과 함께 보호해야 한다.</p>
<hr>
<h3 id="4-origin-referer-헤더-검증">4) Origin, Referer 헤더 검증</h3>
<p>서버는 요청이 어떤 출처에서 왔는지 확인하기 위해 Origin 또는 Referer 헤더를 검증할 수 있다.</p>
<p>예를 들어 회원 정보 변경 요청이 정상 사이트가 아닌 외부 사이트에서 발생했다면 차단할 수 있다.</p>
<p>다만 헤더는 환경에 따라 누락될 수 있으므로 단독 방어책보다는 CSRF Token과 함께 사용하는 것이 좋다.</p>
<hr>
<h3 id="5-중요한-기능에-재인증-적용">5) 중요한 기능에 재인증 적용</h3>
<p>비밀번호 변경, 결제, 회원 탈퇴처럼 중요한 기능은 사용자가 다시 비밀번호를 입력하게 하거나 2차 인증을 요구할 수 있다.</p>
<p>이 방식은 사용자가 실제로 해당 동작을 의도했는지 확인하는 데 도움이 된다.</p>
<hr>
<h3 id="6-jwt-사용-시-저장-위치-주의">6) JWT 사용 시 저장 위치 주의</h3>
<p>최근에는 세션 방식 대신 JWT(JSON Web Token)를 사용하는 서비스도 많다.</p>
<p>JWT를 사용할 때는 토큰을 어디에 저장하느냐에 따라 CSRF와 XSS에 대한 위험도가 달라지므로 주의해야 한다.</p>
<table>
<thead>
<tr>
<th align="left">JWT 저장 위치</th>
<th align="left">CSRF 위험</th>
<th align="left">XSS 위험</th>
<th align="left">특징</th>
</tr>
</thead>
<tbody><tr>
<td align="left">LocalStorage / SessionStorage</td>
<td align="left">낮음</td>
<td align="left">토큰 탈취 위험 높음</td>
<td align="left">브라우저가 토큰을 자동으로 전송하지 않음. JavaScript로 직접 헤더에 실어야 함</td>
</tr>
<tr>
<td align="left">httpOnly Cookie</td>
<td align="left">있음</td>
<td align="left">토큰 직접 탈취 위험은 낮음</td>
<td align="left">쿠키가 자동으로 전송되므로 세션 방식과 동일한 CSRF 방어가 필요함</td>
</tr>
</tbody></table>
<p>핵심은 <strong>인증 정보인 JWT가 브라우저에 의해 자동으로 전송되는 구조인지</strong>이다.</p>
<p>JWT를 LocalStorage나 SessionStorage에 저장하면 브라우저가 토큰을 자동으로 전송하지 않기 때문에 일반적인 CSRF 위험은 낮아진다. 하지만 XSS가 발생하면 JavaScript로 토큰을 탈취당할 수 있다.</p>
<p>반대로 JWT를 httpOnly 쿠키에 저장하면 JavaScript로 토큰 값을 직접 읽기 어렵다. 하지만 쿠키는 요청 시 자동으로 전송되므로 CSRF 위험이 생길 수 있다.</p>
<p>따라서 보안을 위해 JWT를 httpOnly 쿠키에 저장한다면, 전통적인 세션 방식과 마찬가지로 CSRF Token이나 SameSite 설정을 함께 적용해야 한다.</p>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p>CSRF는 사용자가 로그인된 상태를 악용하여, 의도하지 않은 요청을 서버로 보내게 만드는 공격이다.</p>
<p>이를 방어하기 위해서는 CSRF Token, SameSite Cookie 설정, Origin/Referer 검증, 재인증 등을 함께 적용하고, JWT를 사용할 때도 저장 위치에 따른 보안 위험을 고려해야 한다.</p>
<p>이번 글을 정리하면서 가장 중요하다고 느낀 점은 <strong>로그인된 사용자의 요청이라고 해서 항상 사용자가 의도한 요청은 아니라는 것</strong>이다.</p>
<p>단순히 사용자가 인증되었는지만 확인하는 것이 아니라, <strong>그 요청이 정상적인 흐름에서 발생한 요청인지까지 함께 검증해야 한다는 점도 중요하다.</strong></p>
<p>결국 보안은 하나의 기능을 추가하는 문제가 아니라, 서비스의 요청 흐름을 얼마나 신중하게 설계하느냐와 연결되어 있다. 그런 점에서 CSRF는 웹 서비스를 개발할 때 꼭 이해하고 대비해야 할 취약점이라고 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] XSS란 무엇일까?]]></title>
            <link>https://velog.io/@jh_devlog/Security-XSS%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@jh_devlog/Security-XSS%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Tue, 23 Jun 2026 00:56:32 GMT</pubDate>
            <description><![CDATA[<h1 id="security-xss란-무엇일까">[Security] XSS란 무엇일까?</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>이전 글에서 SQL Injection에 대해 정리하면서, 사용자 입력값을 안전하게 처리하지 않으면 보안 취약점으로 이어질 수 있다는 점을 알게 되었다.</p>
<p>웹 보안에서 SQL Injection과 함께 자주 등장하는 공격 중 하나가 <strong>XSS</strong>이다. SQL Injection이 데이터베이스에 영향을 주는 공격이라면, XSS는 사용자의 브라우저에서 악성 스크립트가 실행될 수 있다는 점에서 차이가 있다.</p>
<p>이번 글에서는 XSS가 무엇인지, 왜 발생하는지, 어떤 피해로 이어질 수 있는지, 그리고 어떻게 방어할 수 있는지 정리해보려고 한다.</p>
<hr>
<h2 id="2-xss란">2. XSS란?</h2>
<p>XSS는 <strong>Cross-Site Scripting</strong>의 약자로, 웹 페이지에 악성 스크립트를 삽입해 다른 사용자의 브라우저에서 실행되도록 만드는 공격이다.</p>
<p>게시글, 댓글, 검색창처럼 사용자가 입력한 내용이 다시 웹 페이지에 출력되는 기능은 많은 웹 서비스에서 사용된다. 이때 입력값이 안전하게 처리되지 않으면, 브라우저는 입력값을 단순한 텍스트가 아니라 실행 가능한 스크립트로 해석할 수 있다.</p>
<p>즉, XSS의 핵심은 <strong>사용자 입력값이 브라우저에서 실행 가능한 코드로 해석되는 문제</strong>라고 볼 수 있다.</p>
<hr>
<h2 id="3-xss는-왜-발생할까">3. XSS는 왜 발생할까?</h2>
<p>XSS는 사용자의 입력값을 검증하거나 안전하게 변환하지 않은 채 웹 페이지에 출력할 때 발생할 수 있다.</p>
<p>대표적인 발생 원인은 다음과 같다.</p>
<ul>
<li>사용자 입력값 검증 부족</li>
<li>출력 시 HTML 이스케이프 처리 부족</li>
<li>스크립트 실행이 가능한 위치에 입력값 삽입</li>
<li>외부 입력값을 신뢰하는 코드 작성</li>
</ul>
<p>결국 XSS는 웹 애플리케이션이 사용자 입력값을 안전한 텍스트로 처리하지 못할 때 발생하는 취약점이다.</p>
<hr>
<h2 id="4-예시로-이해하기">4. 예시로 이해하기</h2>
<p>예를 들어 댓글 기능이 있는 웹 페이지를 생각해보자.</p>
<p>사용자가 다음과 같이 댓글을 입력하면,</p>
<pre><code class="language-html">안녕하세요!</code></pre>
<p>화면에는 단순한 텍스트로 표시된다.</p>
<p>하지만 공격자가 다음과 같은 값을 입력하고, 서버가 이를 그대로 출력한다면 문제가 발생할 수 있다.</p>
<pre><code class="language-html">&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;</code></pre>
<p>이 값이 안전하게 처리되지 않고 HTML 문서에 포함되면, 브라우저는 이를 단순한 글자가 아니라 실행 가능한 스크립트로 해석할 수 있다.</p>
<p>예를 들어 HTML에는 다음과 같은 형태로 포함될 수 있다.</p>
<pre><code class="language-html">&lt;p&gt;&lt;script&gt;alert(&#39;XSS&#39;)&lt;/script&gt;&lt;/p&gt;</code></pre>
<p>이 경우 브라우저는 <code>&lt;script&gt;</code> 태그를 코드로 해석하고 실행할 수 있다.</p>
<p>또 다른 예로 이미지 태그의 이벤트 속성을 악용하는 방식도 있다.</p>
<pre><code class="language-html">&lt;img src=&quot;x&quot; onerror=&quot;alert(&#39;XSS&#39;)&quot;&gt;</code></pre>
<p>중요한 점은 특정 공격 코드를 외우는 것이 아니다.
핵심은 <strong>사용자 입력값이 HTML 문서 안에서 코드로 해석될 수 있다는 점</strong>이다.</p>
<p>XSS가 발생하면 다음과 같은 피해로 이어질 수 있다.</p>
<ul>
<li>쿠키 또는 세션 정보 탈취</li>
<li>피싱 페이지 유도</li>
<li>사용자 권한으로 요청 수행</li>
<li>악성 사이트로 리다이렉트</li>
<li>웹 페이지 변조</li>
</ul>
<p>즉, XSS는 서버 자체보다 <strong>사용자의 브라우저와 세션을 노리는 공격</strong>에 가깝다.</p>
<hr>
<h2 id="5-xss의-종류">5. XSS의 종류</h2>
<p>XSS는 발생 방식에 따라 크게 세 가지로 나눌 수 있다.</p>
<h3 id="5-1-stored-xss">5-1. Stored XSS</h3>
<p>Stored XSS는 악성 스크립트가 서버에 저장된 뒤, 다른 사용자가 해당 페이지를 볼 때 실행되는 방식이다.</p>
<p>예를 들어 공격자가 게시글이나 댓글에 악성 스크립트를 저장하면, 이후 해당 글을 보는 사용자들에게 스크립트가 실행될 수 있다.</p>
<p>Stored XSS는 한 번 저장되면 여러 사용자에게 영향을 줄 수 있기 때문에 위험도가 큰 편이다.</p>
<h3 id="5-2-reflected-xss">5-2. Reflected XSS</h3>
<p>Reflected XSS는 사용자의 요청에 포함된 입력값이 서버 응답에 바로 반영되면서 발생하는 방식이다.</p>
<p>예를 들어 검색어가 화면에 그대로 출력되는 기능에서 입력값을 제대로 처리하지 않으면, 조작된 링크를 통해 스크립트가 실행될 수 있다.</p>
<p>Reflected XSS는 보통 사용자가 특정 링크를 클릭하도록 유도하는 방식과 함께 사용될 수 있다.</p>
<h3 id="5-3-dom-based-xss">5-3. DOM-based XSS</h3>
<p>DOM-based XSS는 브라우저에서 실행되는 JavaScript 코드가 DOM을 조작하는 과정에서 발생한다.</p>
<p>서버 응답보다 클라이언트 측 코드에서 입력값을 안전하게 처리하지 못할 때 발생할 수 있다.</p>
<hr>
<h2 id="6-어떻게-방어할-수-있을까">6. 어떻게 방어할 수 있을까?</h2>
<p>XSS를 방어하기 위해서는 사용자의 입력값이 브라우저에서 코드로 실행되지 않도록 처리해야 한다.</p>
<p>가장 중요한 방법은 <strong>출력 시 HTML 이스케이프 처리</strong>이다.</p>
<p>예를 들어 <code>&lt;script&gt;</code>라는 값이 있을 때, 이를 그대로 출력하면 브라우저가 태그로 해석할 수 있다. 하지만 HTML 이스케이프 처리를 하면 다음처럼 변환된다.</p>
<pre><code class="language-html">&amp;lt;script&amp;gt;</code></pre>
<p><code>&lt;</code>는 <code>&amp;lt;</code>, <code>&gt;</code>는 <code>&amp;gt;</code>로 변환되기 때문에 브라우저는 이를 코드가 아니라 문자열로 표시한다.</p>
<p>XSS 방어 방법은 다음과 같다.</p>
<ul>
<li>출력 시 HTML 이스케이프 처리</li>
<li>사용자 입력값 검증</li>
<li>스크립트 실행 위치에 입력값 직접 삽입 금지</li>
<li>쿠키에 <code>HttpOnly</code>, <code>Secure</code>, <code>SameSite</code> 속성 설정</li>
<li>CSP(Content Security Policy) 적용</li>
<li>프레임워크의 자동 이스케이프 기능 활용</li>
<li>WAF를 통한 웹 공격 탐지 및 차단</li>
</ul>
<p>여기서 중요한 점은 입력값 검증만으로는 부족할 수 있다는 것이다.
입력 단계에서 위험한 값을 걸러내는 것도 중요하지만, 최종적으로 화면에 출력할 때 안전하게 변환하는 처리가 필요하다.</p>
<p>또한 WAF는 XSS 공격 시도를 탐지하고 차단하는 데 도움을 줄 수 있지만, 근본적으로는 애플리케이션 코드에서 안전한 출력 처리가 적용되어야 한다.</p>
<p>즉, XSS 방어의 핵심은 <strong>사용자 입력값을 신뢰하지 않고, 브라우저가 코드로 해석하지 못하도록 안전하게 출력하는 것</strong>이다.</p>
<hr>
<h2 id="7-정리">7. 정리</h2>
<p>XSS는 공격자가 웹 페이지에 악성 스크립트를 삽입하여, 다른 사용자의 브라우저에서 실행되도록 만드는 공격이다.</p>
<ul>
<li><strong>원인</strong>: 입력값이 HTML 또는 JavaScript 코드로 해석됨</li>
<li><strong>영향</strong>: 세션 탈취, 피싱, 페이지 변조, 악성 사이트 이동</li>
<li><strong>방어</strong>: 출력 이스케이프, 입력값 검증, 쿠키 보안 설정, CSP, WAF 활용</li>
</ul>
<blockquote>
<p>XSS의 핵심은 사용자 입력값이 단순한 텍스트가 아니라 브라우저에서 실행 가능한 코드로 해석될 수 있다는 점이다.</p>
</blockquote>
<p>따라서 개발자는 사용자 입력값을 신뢰하지 않고, 화면에 출력할 때 브라우저가 코드로 해석하지 못하도록 안전하게 처리해야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] SQL Injection이란 무엇일까?]]></title>
            <link>https://velog.io/@jh_devlog/Security-SQL-Injection%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</link>
            <guid>https://velog.io/@jh_devlog/Security-SQL-Injection%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%BC%EA%B9%8C</guid>
            <pubDate>Mon, 22 Jun 2026 10:23:46 GMT</pubDate>
            <description><![CDATA[<h1 id="security-sql-injection이란-무엇일까">[Security] SQL Injection이란 무엇일까?</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>이전 글에서 WAF에 대해 정리하면서, WAF가 탐지하거나 차단할 수 있는 대표적인 웹 공격 중 하나로 <strong>SQL Injection</strong>을 언급했다.</p>
<p>SQL Injection은 웹 보안에서 자주 등장하는 공격 기법이며, 로그인 우회나 데이터베이스 정보 유출과도 연결될 수 있는 중요한 개념이다.</p>
<p>개발을 공부하면서 SQL을 사용해 데이터를 조회하고 저장하는 과정은 익숙했지만, 사용자의 입력값이 어떻게 공격으로 이어질 수 있는지는 명확히 이해하지 못했다.</p>
<p>그래서 이번 글에서는 SQL Injection이 무엇인지, 왜 발생하는지, 그리고 어떻게 방어할 수 있는지 정리해보려고 한다.</p>
<hr>
<h2 id="2-sql-injection이란">2. SQL Injection이란?</h2>
<p>SQL Injection은 공격자가 입력값에 악의적인 SQL 구문을 삽입하여, 데이터베이스에 의도하지 않은 쿼리가 실행되도록 만드는 공격이다.</p>
<p>웹 애플리케이션은 사용자가 입력한 값을 바탕으로 데이터베이스에 질의하는 경우가 많다. 예를 들어 로그인, 검색, 게시글 조회, 회원 정보 수정 같은 기능에서 SQL 쿼리가 사용될 수 있다.</p>
<p>이때 사용자의 입력값이 제대로 검증되지 않고 SQL 문장에 직접 포함된다면, 공격자는 입력값을 조작해 원래 의도와 다른 SQL이 실행되도록 만들 수 있다.</p>
<p>즉, SQL Injection의 핵심은 <strong>사용자 입력값이 SQL 쿼리의 일부로 해석되는 문제</strong>라고 볼 수 있다.</p>
<hr>
<h2 id="3-sql-injection은-왜-발생할까">3. SQL Injection은 왜 발생할까?</h2>
<p>SQL Injection은 주로 사용자의 입력값을 신뢰하고, 그 값을 SQL 문장에 그대로 연결할 때 발생한다.</p>
<p>예를 들어 로그인 기능에서 사용자가 입력한 아이디와 비밀번호를 이용해 다음과 같은 SQL을 만든다고 가정해보자.</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE username = &#39;입력한 아이디&#39;
AND password = &#39;입력한 비밀번호&#39;;</code></pre>
<p>이 구조 자체가 항상 문제인 것은 아니다.
문제는 사용자가 입력한 값이 검증되지 않은 채 SQL 문자열에 그대로 이어 붙여질 때 발생한다.</p>
<p>공격자가 단순한 아이디나 비밀번호 대신 SQL 문법으로 해석될 수 있는 값을 입력하면, 데이터베이스는 이를 일반 문자열이 아니라 SQL 조건의 일부로 처리할 수 있다.</p>
<p>SQL Injection이 발생하는 대표적인 원인은 다음과 같다.</p>
<ul>
<li>사용자 입력값 검증 부족</li>
<li>SQL 문자열 직접 연결</li>
<li>Prepared Statement 미사용</li>
<li>데이터베이스 권한 과다 부여</li>
<li>에러 메시지를 통한 내부 정보 노출</li>
</ul>
<p>결국 SQL Injection은 웹 애플리케이션이 사용자 입력값을 안전하게 처리하지 못할 때 발생하는 취약점이다.</p>
<hr>
<h2 id="4-예시로-이해하기">4. 예시로 이해하기</h2>
<p>다음과 같은 로그인 쿼리가 있다고 가정해보자.</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE username = &#39;user&#39;
AND password = &#39;1234&#39;;</code></pre>
<p>이 쿼리는 아이디가 <code>user</code>이고, 비밀번호가 <code>1234</code>인 사용자를 찾기 위한 쿼리이다.</p>
<p>그런데 사용자의 입력값이 검증 없이 SQL 문장에 그대로 들어간다면 문제가 발생할 수 있다.</p>
<p>예를 들어 사용자가 다음과 같이 입력했다고 가정해보자.</p>
<ul>
<li>아이디: <code>admin</code></li>
<li>비밀번호: <code>&#39; OR &#39;1&#39;=&#39;1</code></li>
</ul>
<p>이 입력값이 그대로 SQL에 포함되면 다음과 같은 쿼리가 만들어질 수 있다.</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE username = &#39;admin&#39;
AND password = &#39;&#39; OR &#39;1&#39;=&#39;1&#39;;</code></pre>
<p>여기서 <code>&#39;1&#39;=&#39;1&#39;</code>은 항상 참이 되는 조건이다.</p>
<p>원래는 아이디와 비밀번호가 모두 일치해야 하지만, 입력값이 SQL 조건식으로 해석되면서 의도하지 않은 결과가 발생할 수 있다.</p>
<p>실제 공격에서는 뒤에 남는 SQL 구문을 무시하기 위해 주석 문법을 함께 사용하는 경우도 있다. 하지만 여기서 중요한 것은 특정 공격 문자열을 외우는 것이 아니라, <strong>사용자 입력값이 SQL 문법으로 해석되면 위험하다</strong>는 원리를 이해하는 것이다.</p>
<p>SQL Injection을 통해 발생할 수 있는 피해는 다음과 같다.</p>
<ul>
<li>로그인 우회</li>
<li>데이터베이스 정보 조회</li>
<li>개인정보 유출</li>
<li>데이터 변조 또는 삭제</li>
<li>관리자 권한 탈취 가능성 증가</li>
</ul>
<p>따라서 SQL Injection은 단순한 입력값 오류가 아니라, 데이터베이스와 서비스 전체의 보안에 영향을 줄 수 있는 취약점이다.</p>
<hr>
<h2 id="5-어떻게-방어할-수-있을까">5. 어떻게 방어할 수 있을까?</h2>
<p>SQL Injection을 방어하기 위해서는 사용자의 입력값이 SQL 문법으로 해석되지 않도록 처리해야 한다.</p>
<p>가장 기본적이고 중요한 방법은 <strong>Prepared Statement</strong> 또는 <strong>Parameterized Query</strong>를 사용하는 것이다.</p>
<pre><code class="language-sql">SELECT *
FROM users
WHERE username = ?
AND password = ?;</code></pre>
<p>Prepared Statement는 SQL 문장의 구조를 먼저 정해두고, 사용자 입력값은 나중에 파라미터로 바인딩하는 방식이다.</p>
<p>즉, 데이터베이스는 <code>?</code>가 들어간 쿼리의 구조를 먼저 분석하고, 이후 입력값은 SQL 문법이 아니라 단순한 데이터로 처리한다.</p>
<p>따라서 사용자가 <code>&#39; OR &#39;1&#39;=&#39;1</code> 같은 값을 입력하더라도, 데이터베이스는 이를 SQL 조건식이 아니라 하나의 문자열 값으로 인식한다. 입력값이 쿼리의 구조를 바꾸지 못하기 때문에 SQL Injection을 방어할 수 있다.</p>
<p>SQL Injection을 방어하기 위한 주요 방법은 다음과 같다.</p>
<ul>
<li>Prepared Statement 또는 Parameterized Query 사용</li>
<li>사용자 입력값 검증</li>
<li>ORM 사용 시 안전한 쿼리 작성 방식 준수</li>
<li>데이터베이스 계정 권한 최소화</li>
<li>에러 메시지에 내부 SQL 정보 노출 방지</li>
<li>WAF를 통한 웹 공격 탐지 및 차단</li>
</ul>
<p>여기서 중요한 점은 WAF만으로 SQL Injection을 완전히 막을 수는 없다는 것이다.</p>
<p>WAF는 공격 시도를 탐지하고 차단하는 데 도움을 줄 수 있지만, 근본적으로는 애플리케이션 코드에서 안전한 쿼리 작성 방식이 적용되어야 한다.</p>
<p>즉, SQL Injection 방어의 핵심은 <strong>입력값 검증 + 안전한 쿼리 작성 + 최소 권한 원칙</strong>이라고 볼 수 있다.</p>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p>SQL Injection은 사용자의 입력값에 악의적인 SQL 구문이 삽입되어, 데이터베이스에 의도하지 않은 쿼리가 실행되도록 만드는 공격이다.</p>
<p>이 공격은 주로 사용자 입력값을 검증하지 않고 SQL 문자열에 그대로 연결할 때 발생한다.</p>
<ul>
<li><strong>원인</strong>: 입력값이 SQL 쿼리의 일부로 해석됨</li>
<li><strong>영향</strong>: 로그인 우회, 정보 유출, 데이터 변조 또는 삭제</li>
<li><strong>방어</strong>: Prepared Statement, 입력값 검증, 최소 권한, 에러 메시지 관리, WAF 활용</li>
</ul>
<blockquote>
<p>SQL Injection의 핵심은 사용자의 입력값이 단순한 데이터가 아니라 SQL 문법으로 해석될 수 있다는 점이다.</p>
</blockquote>
<p>이번 글을 정리하면서 SQL Injection은 단순히 SQL 문법을 악용하는 공격이 아니라, 입력값을 안전하게 처리하지 못했을 때 발생하는 대표적인 웹 취약점이라는 점을 알 수 있었다.</p>
<p>개발자는 사용자 입력값을 신뢰하지 않고, 입력값이 SQL 문장에 직접 연결되지 않도록 안전한 쿼리 작성 방식을 사용하는 것이 중요하다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] WAF는 방화벽과 무엇이 다를까?]]></title>
            <link>https://velog.io/@jh_devlog/Security-WAF%EB%8A%94-%EB%B0%A9%ED%99%94%EB%B2%BD%EA%B3%BC-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%A4%EB%A5%BC%EA%B9%8C</link>
            <guid>https://velog.io/@jh_devlog/Security-WAF%EB%8A%94-%EB%B0%A9%ED%99%94%EB%B2%BD%EA%B3%BC-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%A4%EB%A5%BC%EA%B9%8C</guid>
            <pubDate>Sat, 20 Jun 2026 09:06:38 GMT</pubDate>
            <description><![CDATA[<h1 id="security-waf는-방화벽과-무엇이-다를까">[Security] WAF는 방화벽과 무엇이 다를까?</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>이전 글에서 방화벽과 IDS/IPS에 대해 정리하면서, 네트워크 보안 장비들이 각각 다른 역할을 가진다는 점을 알게 되었다.</p>
<p>방화벽은 주로 IP 주소, 포트 번호, 프로토콜 같은 정보를 기준으로 트래픽을 허용하거나 차단한다. 하지만 웹 서비스처럼 80번, 443번 포트를 열어두어야 하는 경우에는, 허용된 포트를 통해 들어오는 공격까지 방화벽만으로 모두 막기는 어렵다.</p>
<p>예를 들어 공격자가 정상적인 웹 요청처럼 보이는 형태로 SQL Injection이나 XSS 같은 공격을 시도한다면, 일반적인 방화벽만으로는 요청 안에 담긴 내용을 자세히 분석하기 어렵다.</p>
<p>이때 웹 애플리케이션을 보호하기 위해 사용되는 보안 장비가 <strong>WAF</strong>이다. 이번 글에서는 WAF가 무엇인지, 일반 방화벽과 어떤 차이가 있는지 정리해보려고 한다.</p>
<hr>
<h2 id="2-waf란">2. WAF란?</h2>
<p>WAF는 <strong>Web Application Firewall</strong>의 약자로, 우리말로는 <strong>웹 애플리케이션 방화벽</strong>이라고 한다.</p>
<p>WAF는 웹 애플리케이션으로 들어오는 HTTP/HTTPS 요청을 검사하고, 공격으로 의심되는 요청을 탐지하거나 차단하는 보안 장비이다.</p>
<p>일반적인 방화벽이 네트워크 접근을 통제하는 역할에 가깝다면, WAF는 웹 애플리케이션을 대상으로 하는 공격을 탐지하고 차단하는 데 초점이 있다.</p>
<p>예를 들어 사용자가 웹사이트에 접속할 때 서버로 전달되는 요청에는 URL, 파라미터, 쿠키, 헤더와 같은 정보가 포함된다. WAF는 이러한 웹 요청 내용을 검사하여 악성 스크립트나 비정상적인 SQL 구문 등이 포함되어 있는지 확인할 수 있다.</p>
<p>최근 대부분의 웹사이트는 HTTPS를 사용하기 때문에 요청 내용이 암호화되어 전송된다. 이때 WAF가 SSL/TLS 종단 지점으로 구성되어 있다면, 암호화된 트래픽을 복호화한 뒤 HTTP 요청 내용을 검사할 수 있다. 이를 통해 일반 방화벽이 보기 어려운 웹 요청의 세부 내용을 분석할 수 있다.</p>
<p>즉, WAF의 핵심 역할은 <strong>웹 요청의 내용을 분석하여 웹 애플리케이션 공격을 탐지하고 차단하는 것</strong>이다.</p>
<hr>
<h2 id="3-일반-방화벽과-waf의-차이">3. 일반 방화벽과 WAF의 차이</h2>
<p>일반 방화벽과 WAF는 모두 트래픽을 허용하거나 차단할 수 있다는 점에서 비슷해 보일 수 있다. 하지만 판단하는 기준과 보호 대상이 다르다.</p>
<p>일반 방화벽은 주로 IP 주소, 포트 번호, 프로토콜 같은 네트워크 계층 정보를 기준으로 통신을 제어한다. 예를 들어 특정 IP에서 오는 요청을 차단하거나, 특정 포트만 허용하는 방식이다.</p>
<p>반면 WAF는 웹 요청의 내용을 더 자세히 확인한다. URL, 파라미터, 쿠키, 헤더, 요청 본문 등을 분석하여 웹 공격 패턴이 포함되어 있는지 판단한다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>일반 방화벽</th>
<th>WAF</th>
</tr>
</thead>
<tbody><tr>
<td>의미</td>
<td>네트워크 방화벽</td>
<td>웹 애플리케이션 방화벽</td>
</tr>
<tr>
<td>작동 계층</td>
<td>주로 L3/L4 계층</td>
<td>주로 L7 계층</td>
</tr>
<tr>
<td>주요 기준</td>
<td>IP, 포트, 프로토콜</td>
<td>URL, 파라미터, 쿠키, 헤더, 요청 본문</td>
</tr>
<tr>
<td>보호 대상</td>
<td>네트워크 및 시스템 접근</td>
<td>웹 애플리케이션</td>
</tr>
<tr>
<td>주요 역할</td>
<td>허용되지 않은 네트워크 접근 차단</td>
<td>웹 공격 탐지 및 차단</td>
</tr>
<tr>
<td>대표 대응</td>
<td>불필요한 포트 차단</td>
<td>SQL Injection, XSS 등 웹 공격 차단</td>
</tr>
</tbody></table>
<p>정리하면, 일반 방화벽은 네트워크 접근을 통제하는 장비이고, WAF는 웹 애플리케이션 요청 내용을 분석해 웹 공격을 탐지하거나 차단하는 장비라고 볼 수 있다.</p>
<hr>
<h2 id="4-waf가-탐지하거나-차단할-수-있는-대표적인-공격">4. WAF가 탐지하거나 차단할 수 있는 대표적인 공격</h2>
<p>WAF는 웹 애플리케이션을 대상으로 하는 공격을 탐지하고 차단하는 데 사용된다. 대표적인 공격으로는 SQL Injection, XSS, 파일 업로드 공격 등이 있다.</p>
<h3 id="4-1-sql-injection">4-1. SQL Injection</h3>
<p>SQL Injection은 공격자가 입력값에 악성 SQL 구문을 삽입해 데이터베이스에 비정상적인 쿼리를 실행시키려는 공격이다. </p>
<p>WAF는 요청 파라미터에 비정상적인 SQL 패턴이 포함되어 있는지 확인하고, 공격으로 판단되면 차단할 수 있다.</p>
<h3 id="4-2-xss">4-2. XSS</h3>
<p>XSS는 <strong>Cross-Site Scripting</strong>의 약자로, 웹 페이지에 악성 스크립트를 삽입하는 공격이다.
공격자가 게시글, 댓글, 입력 폼 등에 스크립트를 삽입하면 다른 사용자의 브라우저에서 해당 스크립트가 실행될 수 있다. </p>
<p>WAF는 요청값에 스크립트 태그나 이벤트 핸들러처럼 의심스러운 패턴이 포함되어 있는지 검사할 수 있다.</p>
<h3 id="4-3-파일-업로드-공격">4-3. 파일 업로드 공격</h3>
<p>파일 업로드 기능이 있는 웹 서비스에서는 공격자가 악성 파일을 업로드하려고 시도할 수 있다.</p>
<p>예를 들어 웹쉘과 같은 악성 파일이 서버에 업로드되고 실행된다면, 공격자가 서버를 원격으로 조작할 위험이 있다.</p>
<p>WAF는 업로드 요청의 파일 확장자, Content-Type, 요청 패턴 등을 검사하여 비정상적인 업로드 시도를 탐지할 수 있다.</p>
<hr>
<h2 id="5-waf만-있으면-안전할까">5. WAF만 있으면 안전할까?</h2>
<p>WAF는 웹 공격을 탐지하고 차단하는 데 유용하지만, WAF만 있다고 해서 웹 서비스가 완전히 안전해지는 것은 아니다.</p>
<p>첫째, WAF는 설정된 정책이나 탐지 패턴을 기준으로 동작한다. 따라서 새로운 공격 기법이나 우회 기법이 사용되면 탐지하지 못할 수 있다.</p>
<p>둘째, 오탐이 발생할 수 있다. 정상적인 요청을 공격으로 잘못 판단해 차단할 수도 있고, 반대로 공격 요청을 정상 요청으로 판단할 수도 있다.</p>
<p>셋째, HTTPS 트래픽을 검사하려면 SSL/TLS 복호화 구성이 필요할 수 있다. 이 과정에서 인증서 관리, 복호화 구간의 보안, 성능 부담 등을 함께 고려해야 한다.</p>
<p>넷째, 애플리케이션 자체의 취약점이 해결되는 것은 아니다. WAF는 공격 요청을 막아주는 보안 장치이지만, 근본적으로 취약한 코드나 잘못된 인증·인가 로직을 수정해주는 것은 아니다.</p>
<p>따라서 WAF는 웹 애플리케이션 보안을 강화하는 중요한 장비이지만, 모든 공격을 완벽하게 막아주는 것은 아니므로 안전한 개발, 입력값 검증, 인증·인가 관리, 로그 모니터링, 취약점 점검 등과 함께 사용해야 한다.</p>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p>WAF는 웹 애플리케이션을 대상으로 하는 공격을 탐지하고 차단하기 위한 보안 장비이다.</p>
<ul>
<li><strong>일반 방화벽</strong>: IP, 포트, 프로토콜 등을 기준으로 네트워크 접근을 통제한다.</li>
<li><strong>WAF</strong>: URL, 파라미터, 쿠키, 헤더, 요청 본문 등 HTTP/HTTPS 요청 내용을 분석해 웹 공격 여부를 판단한다.</li>
</ul>
<p>대표적으로 WAF는 SQL Injection, XSS, 파일 업로드 공격과 같은 웹 공격을 탐지하거나 차단하는 데 활용될 수 있다.</p>
<p>다만 WAF만으로 모든 웹 보안 문제를 해결할 수는 없다. WAF는 웹 공격을 줄이는 데 도움을 주는 보안 장비이지만, 안전한 코드 작성, 입력값 검증, 취약점 점검, 로그 모니터링과 함께 사용해야 더 효과적인 웹 보안 체계를 구성할 수 있다.</p>
<p>이번 글을 정리하면서 일반 방화벽과 WAF는 이름은 비슷하지만, 보호하는 대상과 판단 기준이 다르다는 점을 알 수 있었다. WAF는 웹 요청 내용을 분석해 웹 애플리케이션 공격을 줄이는 데 도움을 주는 보안 장비라고 이해할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] IDS와 IPS의 차이]]></title>
            <link>https://velog.io/@jh_devlog/Security-IDS%EC%99%80-IPS%EC%9D%98-%EC%B0%A8%EC%9D%B4</link>
            <guid>https://velog.io/@jh_devlog/Security-IDS%EC%99%80-IPS%EC%9D%98-%EC%B0%A8%EC%9D%B4</guid>
            <pubDate>Thu, 18 Jun 2026 01:54:25 GMT</pubDate>
            <description><![CDATA[<h1 id="security-ids와-ips의-차이">[Security] IDS와 IPS의 차이</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>이전 글에서 방화벽의 역할에 대해 정리하면서, 방화벽은 네트워크 접근을 통제하는 중요한 보안 장치이지만 모든 공격을 막을 수는 없다는 점을 알게 되었다.</p>
<p>예를 들어 허용된 포트를 통해 들어오는 공격이나 정상적인 요청처럼 보이는 공격은 단순 방화벽 규칙만으로 탐지하기 어려울 수 있다.</p>
<p>그래서 방화벽과 함께 자주 언급되는 보안 장비가 <strong>IDS</strong>와 <strong>IPS</strong>이다. 두 용어는 이름도 비슷하고 모두 침입을 탐지한다는 공통점이 있어 헷갈리기 쉽다.</p>
<p>이번 글에서는 IDS와 IPS가 무엇인지, 그리고 두 장비가 어떤 차이가 있는지 정리해보려고 한다.</p>
<hr>
<h2 id="2-ids란">2. IDS란?</h2>
<p>IDS는 <strong>Intrusion Detection System</strong>의 약자로, 우리말로는 <strong>침입 탐지 시스템</strong>이라고 한다.</p>
<p>IDS는 네트워크나 시스템에서 발생하는 트래픽과 로그를 분석하여 비정상적인 행위나 공격 의심 행위를 탐지하는 역할을 한다.</p>
<p>예를 들어 특정 IP에서 짧은 시간 동안 여러 번 로그인 실패가 발생하거나, 알려진 공격 패턴과 유사한 요청이 감지된다면 IDS는 이를 이상 행위로 판단하고 관리자에게 알림을 보낼 수 있다.</p>
<p>IDS의 핵심은 <strong>탐지와 알림</strong>이다. 수상한 징후를 감시하고 알려주지만, 기본적으로 직접 트래픽을 차단하지는 않는다.</p>
<hr>
<h2 id="3-ips란">3. IPS란?</h2>
<p>IPS는 <strong>Intrusion Prevention System</strong>의 약자로, 우리말로는 <strong>침입 방지 시스템</strong>이라고 한다.</p>
<p>IPS는 IDS처럼 공격이나 이상 행위를 탐지하는 기능을 가지고 있다. 하지만 IDS와 달리 탐지한 공격을 차단하거나 패킷을 폐기하는 등 능동적인 대응까지 수행할 수 있다.</p>
<p>예를 들어 악성 트래픽이 탐지되면 IPS는 해당 트래픽을 차단하거나, 특정 IP의 접근을 막거나, 연결을 종료하는 방식으로 공격이 내부 시스템에 도달하지 못하게 할 수 있다.</p>
<p>IPS의 핵심은 <strong>탐지와 차단</strong>이다. 공격을 발견하는 것에서 그치지 않고, 공격이 실제 피해로 이어지지 않도록 중간에서 막는 역할을 한다.</p>
<hr>
<h2 id="4-ids와-ips의-차이">4. IDS와 IPS의 차이</h2>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/07a574a2-d863-4230-8ad7-e7db40c85b49/image.png" alt=""></p>
<blockquote>
<p>IDS는 일반적으로 통신 경로 바깥에서 복사된 트래픽을 분석하고, IPS는 통신 경로 중간에서 트래픽을 직접 검사하고 차단할 수 있다.</p>
</blockquote>
<p>IDS와 IPS는 모두 침입이나 이상 행위를 탐지한다는 공통점이 있다.
하지만 가장 큰 차이는 <strong>차단 기능의 유무</strong>와 <strong>네트워크 상의 배치 방식</strong>이다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>IDS</th>
<th>IPS</th>
</tr>
</thead>
<tbody><tr>
<td>의미</td>
<td>침입 탐지 시스템</td>
<td>침입 방지 시스템</td>
</tr>
<tr>
<td>핵심 역할</td>
<td>탐지 및 알림</td>
<td>탐지 및 차단</td>
</tr>
<tr>
<td>배치 방식</td>
<td>Out-of-band</td>
<td>In-line</td>
</tr>
<tr>
<td>동작 방식</td>
<td>통신 경로 바깥에서 복사본 분석</td>
<td>실제 통신 경로 중간에서 검사 및 차단</td>
</tr>
<tr>
<td>장점</td>
<td>서비스 트래픽에 직접적인 영향을 주지 않음</td>
<td>공격을 실시간으로 차단 가능</td>
</tr>
<tr>
<td>주의점</td>
<td>차단은 별도 대응 필요</td>
<td>오탐 시 정상 트래픽도 차단될 수 있음</td>
</tr>
</tbody></table>
<p><strong>Out-of-band</strong>는 실제 통신 경로 바깥에서 복사된 트래픽을 분석하는 방식이고, <strong>In-line</strong>은 트래픽이 지나가는 경로 중간에 장비가 위치해 직접 검사하고 차단하는 방식이다.</p>
<p>쉽게 비유하면 IDS는 <strong>CCTV와</strong> 비슷하다. 수상한 행동을 발견하고 알려주지만, 직접 막지는 않는다.</p>
<p>반면 IPS는 <strong>출입 통제 시스템</strong>에 가깝다. 수상한 접근을 발견하면 알릴 뿐만 아니라, 필요에 따라 출입 자체를 막을 수 있다.</p>
<h3 id="4-1-idsips는-어떻게-공격을-탐지할까">4-1. IDS/IPS는 어떻게 공격을 탐지할까?</h3>
<p>IDS와 IPS는 공격을 탐지할 때 대표적으로 <strong>시그니처 기반 탐지</strong>와 <strong>이상 행위 기반 탐지</strong> 방식을 사용할 수 있다.</p>
<ul>
<li><p><strong>시그니처 기반 탐지</strong>
이미 알려진 공격 패턴을 기준으로 탐지하는 방식이다. 백신 프로그램이 악성코드 패턴을 비교해 탐지하는 것과 비슷하다.</p>
</li>
<li><p><strong>이상 행위 기반 탐지</strong>
평소와 다른 비정상적인 행위를 기준으로 탐지하는 방식이다. 예를 들어 로그인 실패가 갑자기 많아지거나 특정 트래픽이 급격히 증가하는 경우를 이상 행위로 볼 수 있다.</p>
</li>
</ul>
<hr>
<h2 id="5-ips가-있으면-ids는-필요-없을까">5. IPS가 있으면 IDS는 필요 없을까?</h2>
<p>IPS는 탐지뿐만 아니라 차단까지 수행할 수 있기 때문에, IDS보다 더 강력한 장비처럼 보일 수 있다. 하지만 IPS가 IDS를 완전히 대체한다고 보기는 어렵다.</p>
<p>IPS는 트래픽 경로 중간에 위치해 공격을 실시간으로 차단할 수 있다는 장점이 있다. 다만 오탐이 발생하면 정상 트래픽까지 차단될 수 있고, 장비 장애나 과부하가 발생하면 서비스 흐름에 영향을 줄 수 있다.</p>
<p>반면 IDS는 복사된 트래픽을 분석하므로 직접 차단은 어렵지만, 서비스 흐름에 영향을 주지 않고 이상 행위를 모니터링하는 데 유용하다.</p>
<p>따라서 IDS와 IPS는 우열 관계라기보다 <strong>역할이 다른 보안 장비</strong>라고 볼 수 있다. IPS는 차단에 강점이 있고, IDS는 탐지와 분석에 강점이 있기 때문에 실제 환경에서는 목적에 따라 함께 사용되기도 한다.</p>
<hr>
<h2 id="6-방화벽과-idsips는-어떻게-다를까">6. 방화벽과 IDS/IPS는 어떻게 다를까?</h2>
<p>방화벽, IDS, IPS는 모두 네트워크 보안을 위해 사용되지만 역할은 조금씩 다르다.</p>
<p>방화벽은 미리 정해진 규칙에 따라 트래픽의 통과 여부를 결정한다. (예: 특정 IP 차단, 특정 포트만 허용)</p>
<p>반면 IDS와 IPS는 단순히 IP나 포트만 보는 것이 아니라, 트래픽 내부의 패턴이나 행위 자체를 분석한다. 방화벽이 “22번 포트 접근을 허용할지”를 판단한다면, IDS/IPS는 “허용된 22번 포트 안에서 비정상적인 로그인 시도가 반복되는지”를 감시하는 식이다.</p>
<p>따라서 방화벽은 문지기 역할을 하는 접근 제어의 기본 장치이고, IDS/IPS는 문을 통과한 뒤의 이상 행위를 감시하고 대응하는 보완 장치라고 볼 수 있다.</p>
<hr>
<h2 id="7-정리">7. 정리</h2>
<p>IDS와 IPS는 모두 침입이나 이상 행위를 탐지하기 위한 보안 시스템이다.</p>
<blockquote>
<ul>
<li><strong>IDS</strong>: 네트워크나 시스템을 모니터링하며 이상 행위를 탐지하고 관리자에게 알림</li>
</ul>
</blockquote>
<ul>
<li><strong>IPS</strong>: 이상 행위를 탐지한 뒤 필요하면 실시간으로 트래픽을 차단</li>
</ul>
<p>이번 글을 정리하면서 방화벽, IDS, IPS는 모두 보안을 위한 장치이지만 각각의 역할이 다르다는 점을 알 수 있었다. 방화벽은 정해진 규칙으로 접근을 통제하고, IDS는 이상 행위를 탐지하며, IPS는 탐지한 공격을 차단한다.</p>
<p>따라서 실제 보안 환경에서는 하나의 장비만으로 모든 공격을 막기보다는, 여러 보안 장치를 함께 사용해 <strong>다층적인 방어 체계</strong>를 구성하는 것이 중요하다고 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] HTTPS 사이트는 어떻게 차단될까? SNI 필드 차단 이해하기]]></title>
            <link>https://velog.io/@jh_devlog/Security-HTTPS-%EC%82%AC%EC%9D%B4%ED%8A%B8%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B0%A8%EB%8B%A8%EB%90%A0%EA%B9%8C-SNI-%ED%95%84%EB%93%9C-%EC%B0%A8%EB%8B%A8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jh_devlog/Security-HTTPS-%EC%82%AC%EC%9D%B4%ED%8A%B8%EB%8A%94-%EC%96%B4%EB%96%BB%EA%B2%8C-%EC%B0%A8%EB%8B%A8%EB%90%A0%EA%B9%8C-SNI-%ED%95%84%EB%93%9C-%EC%B0%A8%EB%8B%A8-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0</guid>
            <pubDate>Wed, 17 Jun 2026 00:58:21 GMT</pubDate>
            <description><![CDATA[<h1 id="security-https-사이트는-어떻게-차단될까-sni-필드-차단-이해하기">[Security] HTTPS 사이트는 어떻게 차단될까? SNI 필드 차단 이해하기</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>최근 불법 사이트 긴급차단·접속차단 제도와 관련된 뉴스를 보면서, HTTPS 사이트는 어떤 방식으로 차단되는지 궁금해졌다.</p>
<p>HTTPS는 통신 내용이 암호화된다고 알고 있었기 때문에, 처음에는 “암호화된 사이트도 어떻게 차단할 수 있을까?”라는 의문이 들었다.</p>
<p>조사해보니 HTTPS 통신 내용 자체를 들여다보는 것이 아니라, TLS 연결 초기에 노출될 수 있는 SNI 정보를 이용해 차단할 수 있다는 것을 알게 되었다.</p>
<p>이번 글에서는 SNI가 무엇인지, 그리고 SNI 기반 차단이 어떤 원리로 동작하는지 간단히 정리해보려고 한다.</p>
<blockquote>
<p>참고 뉴스: <a href="https://www.etnews.com/20260510000040">https://www.etnews.com/20260510000040</a></p>
</blockquote>
<hr>
<h2 id="2-https는-모든-정보를-숨겨줄까">2. HTTPS는 모든 정보를 숨겨줄까?</h2>
<p>HTTPS는 <strong>HTTP에 TLS 암호화를 적용한 통신 방식</strong>이다. 사용자가 웹사이트와 주고받는 로그인 정보, 게시글 내용, 결제 정보 등은 암호화되어 보호된다.</p>
<p>하지만 HTTPS라고 해서 모든 정보가 완전히 숨겨지는 것은 아니다. 암호화 통신을 시작하기 전, 클라이언트와 서버는 먼저 TLS Handshake 과정을 거친다.</p>
<p>이 과정에서 클라이언트는 서버에 접속하기 위한 여러 정보를 전달하는데, 
TLS 연결 초기 단계에서는 사용자가 접속하려는 도메인 정보가 SNI 필드에 포함되어 암호화되지 않은 평문(Plaintext) 상태로 노출될 수 있다. </p>
<p>즉, HTTPS는 연결이 확립된 후 주고받는 데이터는 암호화하지만, 연결을 시작하는 과정에서 “어떤 도메인에 접속하려는지”에 대한 정보는 중간 네트워크 장비가 확인할 수 있다.</p>
<hr>
<h2 id="3-sni란-무엇인가">3. SNI란 무엇인가?</h2>
<p>SNI는 <strong>Server Name Indication</strong>의 약자로, 클라이언트가 TLS 연결을 시작할 때 접속하려는 서버 이름을 알려주는 기능이다.</p>
<p>하나의 IP 주소에서 여러 HTTPS 사이트를 운영하는 경우, 서버는 클라이언트가 어떤 도메인에 접속하려는지 알아야 적절한 인증서를 선택할 수 있다. 이때 사용되는 정보가 SNI이다.</p>
<p>예를 들어 같은 IP에서 아래 사이트들이 운영된다고 가정해보자.</p>
<ul>
<li>example.com</li>
<li>shop.example.com</li>
<li>blog.example.com</li>
</ul>
<p>서버는 SNI 값을 보고 클라이언트가 어떤 도메인에 접속하려는지 구분할 수 있다.</p>
<p>문제는 이 SNI 값이 TLS Handshake 단계에서 전달되기 때문에, 기존 TLS 환경에서는 암호화된 데이터 통신이 시작되기 전에 노출될 수 있다는 점이다.</p>
<hr>
<h2 id="4-sni-필드-차단은-어떻게-동작할까">4. SNI 필드 차단은 어떻게 동작할까?</h2>
<p>SNI 필드 차단은 TLS Handshake 초기에 보이는 SNI 값을 확인해, 접속하려는 도메인이 차단 대상인지 판단하는 방식이다.</p>
<p>흐름은 다음과 같다.</p>
<ol>
<li>사용자가 HTTPS 사이트에 접속한다.</li>
<li>브라우저가 서버와 TLS Handshake를 시작한다.</li>
<li>ClientHello 메시지에 SNI 값이 포함된다.</li>
<li>중간 네트워크 장비가 SNI 값을 확인한다.</li>
<li>차단 대상 도메인이면 연결을 차단한다.</li>
</ol>
<p>즉, SNI 차단은 사용자가 사이트에서 어떤 내용을 보는지 확인하는 것이 아니라, <strong>접속하려는 도메인 정보를 기준으로 차단하는 방식</strong>이다.</p>
<blockquote>
<p>📌 참고: SNI 노출을 줄이는 ECH(Encrypted ClientHello)</p>
<p>최근에는 SNI를 포함한 ClientHello 정보를 암호화하려는 ECH(Encrypted ClientHello) 기술도 등장했다. 
ECH가 적용되면 기존 TLS 환경에서 노출될 수 있었던 SNI 정보도 보호할 수 있다.</p>
</blockquote>
<hr>
<h2 id="5-정리">5. 정리</h2>
<p>HTTPS는 웹 통신 내용을 암호화해 보호하는 기술이다. 하지만 기존 TLS 환경에서는 연결 초기에 SNI 값이 노출될 수 있고, 이를 통해 사용자가 어떤 도메인에 접속하려는지 확인할 수 있다.</p>
<p>SNI 차단은 HTTPS 본문을 복호화하는 방식이 아니라, TLS Handshake 과정에서 보이는 SNI 값을 기준으로 접속을 차단하는 방식이다.</p>
<p>정리하면 다음과 같다.</p>
<ul>
<li>HTTPS는 사용자가 주고받는 실제 데이터를 암호화한다.</li>
<li>SNI는 클라이언트가 접속하려는 서버 이름을 알려주는 정보이다.</li>
<li>SNI 차단은 “내용 감청”이라기보다 “도메인 기반 차단”에 가깝다.</li>
</ul>
<p>이번 주제를 통해 HTTPS를 이해할 때는 단순히 “암호화되어 있다”는 사실만 볼 것이 아니라, 어떤 정보가 보호되고 어떤 정보가 노출될 수 있는지 구분해서 보는 것이 중요하다는 점을 알 수 있었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] 방화벽은 어떤 역할을 할까?]]></title>
            <link>https://velog.io/@jh_devlog/Security-%EB%B0%A9%ED%99%94%EB%B2%BD%EC%9D%80-%EC%96%B4%EB%96%A4-%EC%97%AD%ED%95%A0%EC%9D%84-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@jh_devlog/Security-%EB%B0%A9%ED%99%94%EB%B2%BD%EC%9D%80-%EC%96%B4%EB%96%A4-%EC%97%AD%ED%95%A0%EC%9D%84-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Mon, 15 Jun 2026 01:01:27 GMT</pubDate>
            <description><![CDATA[<h1 id="security-방화벽은-어떤-역할을-할까">[Security] 방화벽은 어떤 역할을 할까?</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>이전 글에서 DoS와 DDoS, 정보보안의 3요소에 대해 정리하면서 서비스의 가용성과 접근 제어의 중요성을 알게 되었다.</p>
<p>보안에서 외부의 접근을 통제하는 대표적인 장치 중 하나가 방화벽이다. 방화벽이라는 용어는 자주 들어봤지만, 실제로 어떤 기준으로 트래픽을 허용하거나 차단하는지 명확히 알고 싶어 이번 글에서 정리해보려고 한다.</p>
<hr>
<h2 id="2-방화벽이란">2. 방화벽이란?</h2>
<p>방화벽은 네트워크를 오가는 트래픽을 확인하고, 정해진 보안 정책에 따라 허용하거나 차단하는 보안 장치이다.</p>
<p>쉽게 말하면 네트워크의 <strong>출입문</strong> 역할을 한다고 볼 수 있다.
외부에서 내부 시스템으로 들어오는 요청이나, 내부에서 외부로 나가는 통신을 확인한 뒤 허용할지 차단할지를 결정한다.</p>
<p>예를 들어 회사 내부 서버가 외부 인터넷과 연결되어 있다고 가정해보자. 모든 외부 요청을 그대로 허용하면 불필요한 접근이나 공격 시도가 내부 시스템까지 도달할 수 있다.</p>
<p>이때 방화벽은 미리 설정된 규칙에 따라 허용된 통신만 통과시키고, 허용되지 않은 통신은 차단한다.</p>
<p>즉, 방화벽의 핵심 역할은 <strong>네트워크 트래픽을 통제하여 내부 시스템을 보호하는 것</strong>이다.</p>
<hr>
<h2 id="3-방화벽은-무엇을-기준으로-허용하고-차단할까">3. 방화벽은 무엇을 기준으로 허용하고 차단할까?</h2>
<p>방화벽은 단순히 모든 통신을 막는 것이 아니라, 정해진 조건을 기준으로 트래픽을 허용하거나 차단한다.</p>
<p>이때 방화벽은 네트워크를 통과하는 데이터의 기본 단위인 <strong>패킷(Packet)</strong>의 정보를 확인한다.
패킷에는 출발지와 목적지, 사용되는 프로토콜, 포트 번호와 같은 정보가 포함되어 있다.</p>
<p>방화벽은 이러한 패킷의 헤더 정보를 바탕으로 통신을 허용할지 차단할지 판단한다.</p>
<table>
<thead>
<tr>
<th>기준</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>출발지 IP</td>
<td>어떤 IP 주소에서 요청이 시작되었는지</td>
</tr>
<tr>
<td>목적지 IP</td>
<td>어떤 IP 주소로 요청이 향하는지</td>
</tr>
<tr>
<td>출발지 포트</td>
<td>요청을 보낸 쪽의 포트 번호</td>
</tr>
<tr>
<td>목적지 포트</td>
<td>어떤 서비스 포트로 접근하려는지</td>
</tr>
<tr>
<td>프로토콜</td>
<td>TCP, UDP, ICMP 등 어떤 통신 방식인지</td>
</tr>
</tbody></table>
<p>네트워크 보안에서는 위와 같은 기준 중 <strong>출발지 IP, 목적지 IP, 출발지 포트, 목적지 포트, 프로토콜</strong>을 묶어 <strong>5-Tuple</strong>이라고 부르기도 한다.</p>
<p>예를 들어 SSH는 보통 22번 포트를 사용한다.
외부에서 서버의 22번 포트로 접근하는 것을 허용하면 SSH 접속이 가능하지만, 차단하면 외부에서 SSH로 접속할 수 없다.</p>
<p>또 다른 예로 웹 서비스는 일반적으로 80번 포트와 443번 포트를 사용한다. 따라서 외부 사용자가 웹사이트에 접속해야 한다면 해당 포트에 대한 접근은 허용해야 한다.</p>
<p>이처럼 방화벽은 IP 주소, 포트 번호, 프로토콜 등을 기준으로 네트워크 접근을 제어한다.</p>
<hr>
<h2 id="4-방화벽의-주요-역할">4. 방화벽의 주요 역할</h2>
<h3 id="4-1-불필요한-접근-차단">4-1. 불필요한 접근 차단</h3>
<p>방화벽은 외부에서 내부 시스템으로 들어오는 불필요한 접근을 차단한다.</p>
<p>예를 들어 외부에서 접근할 필요가 없는 관리용 포트나 내부 서비스 포트를 차단하면, 공격자가 해당 서비스에 직접 접근하는 것을 줄일 수 있다.</p>
<h3 id="4-2-허용된-서비스만-접근-가능하게-제한">4-2. 허용된 서비스만 접근 가능하게 제한</h3>
<p>서버에서 웹 서비스를 운영한다면 일반적으로 80번 포트와 443번 포트만 외부에 공개할 수 있다.</p>
<p>반면 SSH와 같은 관리용 포트는 모든 IP에 열어두기보다, 특정 IP에서만 접근할 수 있도록 제한하는 것이 좋다.</p>
<p>이렇게 필요한 서비스만 열어두고 나머지는 차단하면 공격자가 접근할 수 있는 범위, 즉 <strong>공격 표면</strong>을 줄일 수 있다.</p>
<h3 id="4-3-내부망-보호">4-3. 내부망 보호</h3>
<p>방화벽은 외부 인터넷과 내부 시스템 사이에서 통신을 통제해 내부망을 보호한다.</p>
<p>외부의 모든 요청이 내부망으로 바로 들어오지 않도록 중간에서 통제하는 역할을 하며, 내부 시스템이 불필요하게 외부에 노출되는 것을 줄여준다.</p>
<h3 id="4-4-로그-기록">4-4. 로그 기록</h3>
<p>방화벽은 허용되거나 차단된 트래픽을 기록할 수 있다.</p>
<p>이 로그는 이후 보안 분석에 활용될 수 있다. 예를 들어 특정 IP에서 반복적으로 차단된 요청이 발생한다면, 비정상적인 접근 시도로 의심해볼 수 있다.</p>
<hr>
<h2 id="5-방화벽이-모든-공격을-막을-수-있을까">5. 방화벽이 모든 공격을 막을 수 있을까?</h2>
<p>방화벽은 네트워크 접근을 통제하는 중요한 보안 장치이지만, 모든 공격을 막을 수 있는 것은 아니다.</p>
<p>예를 들어 웹 서비스를 운영하려면 80번이나 443번 포트는 열어두어야 한다. 그런데 공격자가 허용된 웹 포트를 통해 SQL Injection과 같은 웹 공격을 시도한다면, 일반적인 L3/L4 방화벽만으로는 이를 탐지하거나 차단하기 어려울 수 있다.</p>
<p>쉽게 말해 일반적인 L3/L4 방화벽은 주로 <strong>출발지/목적지 IP, 포트, 프로토콜</strong> 같은 정보를 기준으로 통신을 판단한다.<br>비유하자면 편지의 <strong>봉투 정보</strong>는 확인할 수 있지만, 편지 안의 <strong>내용물</strong>까지 자세히 검사하는 데는 한계가 있는 것이다.</p>
<p>따라서 웹 공격이나 애플리케이션 취약점을 이용한 공격에 대응하기 위해서는 WAF, IDS, IPS, 로그 모니터링 등 다른 보안 장치와 함께 사용하는 것이 필요하다.</p>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p>방화벽은 네트워크 트래픽을 확인하고, 보안 정책에 따라 허용하거나 차단하는 보안 장치이다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>내용</th>
</tr>
</thead>
<tbody><tr>
<td>방화벽의 역할</td>
<td>네트워크 트래픽을 허용하거나 차단</td>
</tr>
<tr>
<td>판단 기준</td>
<td>IP 주소, 포트 번호, 프로토콜 등</td>
</tr>
<tr>
<td>주요 목적</td>
<td>불필요한 접근 차단, 내부망 보호</td>
</tr>
<tr>
<td>한계</td>
<td>허용된 통신 안에서 발생하는 공격은 별도 대응 필요</td>
</tr>
</tbody></table>
<blockquote>
<p>방화벽은 네트워크의 출입문처럼 트래픽을 통제하는 보안 장치이다. 다만 모든 공격을 막을 수는 없기 때문에, WAF, IDS, IPS, 로그 모니터링 등 다른 보안 장치와 함께 사용해야 한다.</p>
</blockquote>
<p>이번 글을 정리하면서 방화벽은 단순히 “막는 장치”가 아니라, 정해진 정책에 따라 필요한 통신은 허용하고 불필요한 접근은 차단하는 접근 제어 장치라는 점을 이해할 수 있었다.</p>
<p>또한 방화벽은 주로 패킷의 IP, 포트, 프로토콜 정보를 기준으로 통신을 판단하지만, 허용된 통신 안에서 발생하는 모든 공격까지 막을 수는 없다는 점도 알게 되었다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] 정보보안의 3요소: 기밀성, 무결성, 가용성]]></title>
            <link>https://velog.io/@jh_devlog/Security-%EC%A0%95%EB%B3%B4%EB%B3%B4%EC%95%88%EC%9D%98-3%EC%9A%94%EC%86%8C-%EA%B8%B0%EB%B0%80%EC%84%B1-%EB%AC%B4%EA%B2%B0%EC%84%B1-%EA%B0%80%EC%9A%A9%EC%84%B1</link>
            <guid>https://velog.io/@jh_devlog/Security-%EC%A0%95%EB%B3%B4%EB%B3%B4%EC%95%88%EC%9D%98-3%EC%9A%94%EC%86%8C-%EA%B8%B0%EB%B0%80%EC%84%B1-%EB%AC%B4%EA%B2%B0%EC%84%B1-%EA%B0%80%EC%9A%A9%EC%84%B1</guid>
            <pubDate>Fri, 12 Jun 2026 00:55:00 GMT</pubDate>
            <description><![CDATA[<h1 id="security-정보보안의-3요소-기밀성-무결성-가용성">[Security] 정보보안의 3요소: 기밀성, 무결성, 가용성</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>이전 글에서 DoS와 DDoS 공격에 대해 정리하면서, 두 공격이 서비스의 <strong>가용성</strong>을 위협한다는 내용을 다뤘다.</p>
<p>그런데 보안에서 말하는 가용성이 정확히 무엇인지 이해하려면, 정보보안의 기본 개념인 <strong>기밀성, 무결성, 가용성</strong>을 함께 알아둘 필요가 있다고 생각했다.</p>
<p>정보보안의 3요소는 보안 개념을 공부할 때 자주 등장하는 기본 개념이기 때문에, 이번 글에서는 각각의 의미를 간단히 정리해보려고 한다.</p>
<hr>
<h2 id="2-정보보안의-3요소란">2. 정보보안의 3요소란?</h2>
<p>정보보안의 3요소는 <strong>기밀성, 무결성, 가용성</strong>을 의미한다.</p>
<p>영어로는 각각 <strong>Confidentiality, Integrity, Availability</strong>라고 하며, 앞 글자를 따서 <strong>CIA Triad</strong>라고도 부른다.</p>
<ul>
<li><p><strong>기밀성(Confidentiality)</strong>
허가된 사람만 정보에 접근할 수 있어야 한다.</p>
</li>
<li><p><strong>무결성(Integrity)</strong>
정보가 허가 없이 변경되거나 훼손되지 않아야 한다.</p>
</li>
<li><p><strong>가용성(Availability)</strong>
필요한 순간에 정보나 서비스를 정상적으로 사용할 수 있어야 한다.</p>
</li>
</ul>
<p>즉, 정보보안은 단순히 정보를 숨기는 것만을 의미하지 않는다.
정보가 안전하게 보호되고, 변조되지 않으며, 필요할 때 정상적으로 사용할 수 있도록 보장하는 것까지 포함한다.</p>
<hr>
<h2 id="3-기밀성-허가된-사람만-접근할-수-있어야-한다">3. 기밀성: 허가된 사람만 접근할 수 있어야 한다</h2>
<p><strong>기밀성</strong>은 허가된 사람만 정보에 접근할 수 있도록 보호하는 것을 의미한다.</p>
<p>예를 들어 개인정보, 비밀번호, 기업 내부 문서, 고객 정보와 같은 데이터는 아무나 볼 수 있어서는 안 된다. 권한이 없는 사용자가 이러한 정보에 접근한다면 기밀성이 침해된 것이다.</p>
<p>기밀성을 지키기 위한 방법으로는 다음과 같은 것들이 있다.</p>
<ul>
<li>로그인 인증</li>
<li>접근 권한 설정</li>
<li>암호화</li>
<li>계정 및 권한 관리</li>
<li>중요 정보 마스킹</li>
</ul>
<p>예를 들어 관리자만 볼 수 있어야 하는 페이지에 일반 사용자가 접근할 수 있다면, 이는 기밀성 측면에서 문제가 될 수 있다.</p>
<p>기밀성의 핵심은 <strong>정보를 볼 수 있는 사람을 제한하는 것</strong>이다.</p>
<p>이러한 관점에서 개인정보 유출 사고는 대표적인 기밀성 침해 사례로 볼 수 있다. 허가되지 않은 주체가 개인정보에 접근하거나 정보가 외부로 노출되는 것은, 허가된 사람만 정보에 접근할 수 있어야 한다는 기밀성 원칙이 지켜지지 않은 경우이기 때문이다.</p>
<hr>
<h2 id="4-무결성-데이터가-변조되지-않아야-한다">4. 무결성: 데이터가 변조되지 않아야 한다</h2>
<p><strong>무결성</strong>은 정보가 허가 없이 변경되거나 삭제되지 않고, 정확한 상태를 유지하는 것을 의미한다.</p>
<p>데이터는 저장되거나 전송되는 과정에서 의도치 않게 손상될 수도 있고, 공격자에 의해 악의적으로 변경될 수도 있다. 이때 데이터가 원래 상태와 다르게 바뀐다면 무결성이 침해된 것이다.</p>
<p>예를 들어 사용자의 계좌 잔액이 임의로 변경되거나, 게시글 내용이 누군가에 의해 몰래 수정된다면 무결성 문제가 발생한 것이다.</p>
<p>무결성을 지키기 위한 방법으로는 다음과 같은 것들이 있다.</p>
<ul>
<li>데이터 변경 권한 관리</li>
<li>로그 기록</li>
<li>해시 값 검증</li>
<li>전자서명</li>
<li>백업 및 복구 체계</li>
</ul>
<p>무결성의 핵심은 <strong>정보가 허가 없이 바뀌지 않도록 보호하는 것</strong>이다.</p>
<hr>
<h2 id="5-가용성-필요할-때-서비스를-사용할-수-있어야-한다">5. 가용성: 필요할 때 서비스를 사용할 수 있어야 한다</h2>
<p><strong>가용성</strong>은 사용자가 필요할 때 정보나 서비스를 정상적으로 사용할 수 있도록 보장하는 것을 의미한다.</p>
<p>아무리 정보가 안전하게 보호되고 변조되지 않더라도, 정작 필요할 때 서비스를 사용할 수 없다면 보안이 잘 지켜지고 있다고 보기 어렵다.</p>
<p>예를 들어 웹사이트에 접속해야 하는데 서버가 다운되어 접속할 수 없거나, 시스템 장애로 인해 업무 서비스를 사용할 수 없는 상황은 가용성에 문제가 생긴 것이다.</p>
<p>가용성을 지키기 위한 방법으로는 다음과 같은 것들이 있다.</p>
<ul>
<li>서버 이중화</li>
<li>백업 시스템 구성</li>
<li>장애 대응 체계 마련</li>
<li>트래픽 모니터링</li>
<li>DDoS 대응 장비 또는 서비스 활용</li>
</ul>
<p>가용성의 핵심은 <strong>필요할 때 서비스를 정상적으로 사용할 수 있도록 하는 것</strong>이다.</p>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p>정보보안의 3요소를 표로 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>의미</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>기밀성</td>
<td>허가된 사람만 정보에 접근할 수 있어야 한다</td>
<td>권한 없는 사용자가 개인정보를 볼 수 없도록 제한</td>
</tr>
<tr>
<td>무결성</td>
<td>정보가 허가 없이 변경되거나 훼손되지 않아야 한다</td>
<td>계좌 잔액이나 게시글 내용이 임의로 변경되지 않도록 보호</td>
</tr>
<tr>
<td>가용성</td>
<td>필요할 때 정보나 서비스를 정상적으로 사용할 수 있어야 한다</td>
<td>서버 장애나 DDoS 공격으로 서비스가 중단되지 않도록 대응</td>
</tr>
</tbody></table>
<blockquote>
<p>기밀성은 정보에 접근할 수 있는 사람을 제한하는 것, 무결성은 정보를 정확한 상태로 유지하는 것, 가용성은 서비스를 정상적으로 사용할 수 있게 하는 것이라고 이해할 수 있다.</p>
</blockquote>
<p>이번 글을 정리하면서 정보보안은 단순히 정보를 외부로부터 숨기는 것만을 의미하지 않는다는 점을 알 수 있었다.</p>
<p>정보가 허가된 사용자에게만 제공되고, 정확한 상태로 유지되며, 필요할 때 정상적으로 사용할 수 있어야 한다는 점에서 기밀성, 무결성, 가용성은 정보보안의 기본 기준이라고 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Security] DoS와 DDoS는 무엇이 다를까?]]></title>
            <link>https://velog.io/@jh_devlog/Security-DoS%EC%99%80-DDoS%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%A4%EB%A5%BC%EA%B9%8C</link>
            <guid>https://velog.io/@jh_devlog/Security-DoS%EC%99%80-DDoS%EB%8A%94-%EB%AC%B4%EC%97%87%EC%9D%B4-%EB%8B%A4%EB%A5%BC%EA%B9%8C</guid>
            <pubDate>Thu, 11 Jun 2026 13:23:03 GMT</pubDate>
            <description><![CDATA[<h1 id="security-dos와-ddos는-무엇이-다를까">[Security] DoS와 DDoS는 무엇이 다를까?</h1>
<h2 id="1-이-주제를-정리하게-된-이유">1. 이 주제를 정리하게 된 이유</h2>
<p>시큐리티 아카데미 면접에서 “DoS와 DDoS의 차이가 무엇인가요?”라는 질문을 받은 적이 있다.</p>
<p>DDoS는 뉴스나 보안 관련 기사에서 자주 들어본 개념이라 어느 정도 익숙했지만, DoS가 정확히 무엇인지, 그리고 DoS와 DDoS가 어떤 차이가 있는지는 명확하게 설명하기 어려웠다.</p>
<p>그래서 이번 글에서는 DoS와 DDoS의 개념과 차이를 정리해보려고 한다.</p>
<hr>
<h2 id="2-dos란-무엇인가">2. DoS란 무엇인가</h2>
<p>DoS는 <strong>Denial of Service</strong>의 약자로, 우리말로는 <strong>서비스 거부 공격</strong>이라고 한다.</p>
<p>DoS 공격은 특정 서버나 서비스에 과도한 요청을 보내거나 시스템 자원을 소모시켜, 정상적인 사용자가 서비스를 이용하지 못하게 만드는 공격이다.</p>
<p>예를 들어 하나의 시스템에서 특정 웹 서버에 계속해서 많은 요청을 보내면, 서버는 요청을 처리하느라 자원을 사용하게 된다. 이 과정에서 서버가 정상적인 요청을 처리하지 못하면 서비스 지연이나 장애가 발생할 수 있다.</p>
<p>즉, DoS의 핵심은 <strong>서비스의 정상적인 이용을 방해하는 것</strong>이다.</p>
<hr>
<h2 id="3-ddos란-무엇인가">3. DDoS란 무엇인가</h2>
<p>DDoS는 <strong>Distributed Denial of Service</strong>의 약자로, 우리말로는 <strong>분산 서비스 거부 공격</strong>이라고 한다.</p>
<p>DDoS는 DoS 공격이 여러 대의 시스템에서 동시에 발생하는 형태라고 볼 수 있다. 공격자는 여러 시스템을 동원해 대상 서버나 네트워크에 대량의 트래픽을 발생시키고, 이를 통해 서비스를 마비시키려고 한다.</p>
<p>DoS가 단일 출발지에서 발생하는 공격에 가깝다면, DDoS는 여러 출발지에서 동시에 발생하는 분산 공격이다.</p>
<p>따라서 DDoS는 공격 규모가 더 크고, 여러 IP에서 요청이 들어오기 때문에 탐지와 차단이 더 어렵다.</p>
<hr>
<h2 id="4-dos와-ddos의-차이">4. DoS와 DDoS의 차이</h2>
<p>DoS와 DDoS는 모두 정상적인 서비스 이용을 방해한다는 점에서는 같다.</p>
<p>하지만 가장 큰 차이는 <strong>공격에 동원되는 시스템의 수</strong>와 <strong>공격 출발지</strong>이다.</p>
<p>DoS는 보통 하나의 시스템 또는 단일 출발지에서 공격이 발생한다. 반면 DDoS는 여러 대의 시스템이 동시에 공격에 참여하기 때문에 트래픽 규모가 더 크고 방어가 어렵다.</p>
<p>정리하면, <strong>DDoS는 DoS의 분산 형태</strong>라고 이해할 수 있다.</p>
<hr>
<h2 id="5-왜-ddos가-더-대응하기-어려운가">5. 왜 DDoS가 더 대응하기 어려운가</h2>
<p>DDoS가 DoS보다 대응하기 어려운 이유는 공격 트래픽이 여러 곳에서 동시에 발생하기 때문이다.</p>
<p>DoS처럼 단일 IP에서 비정상적인 요청이 반복된다면, 해당 IP를 차단하는 방식으로 어느 정도 대응할 수 있다. 하지만 DDoS는 여러 IP에서 대량의 요청이 동시에 들어오기 때문에 특정 IP 하나만 차단해서는 공격을 막기 어렵다.</p>
<p>또한 여러 출발지에서 요청이 발생하다 보니, 어떤 요청이 정상 사용자 요청이고 어떤 요청이 공격 트래픽인지 구분하기도 쉽지 않다.</p>
<p>결국 DDoS 공격은 서버나 네트워크 자원을 빠르게 소모시켜 서비스 지연이나 장애를 유발할 수 있다. 이처럼 DoS와 DDoS는 시스템의 기밀성이나 무결성보다, 사용자가 서비스를 정상적으로 이용할 수 있는지와 관련된 <strong>가용성을 위협하는 공격</strong>이라고 볼 수 있다.</p>
<hr>
<h2 id="6-정리">6. 정리</h2>
<p>DoS와 DDoS의 주요 차이를 표로 정리하면 다음과 같다.</p>
<table>
<thead>
<tr>
<th>구분</th>
<th>DoS</th>
<th>DDoS</th>
</tr>
</thead>
<tbody><tr>
<td>의미</td>
<td>서비스 거부 공격</td>
<td>분산 서비스 거부 공격</td>
</tr>
<tr>
<td>공격 출발지</td>
<td>단일 시스템 또는 단일 출발지</td>
<td>여러 대의 분산된 시스템</td>
</tr>
<tr>
<td>공격 방식</td>
<td>한 곳에서 자원 소모 유발</td>
<td>여러 곳에서 동시에 자원 소모 유발</td>
</tr>
<tr>
<td>공격 규모</td>
<td>상대적으로 작음</td>
<td>상대적으로 큼</td>
</tr>
<tr>
<td>탐지/차단</td>
<td>상대적으로 쉬움</td>
<td>더 어려움</td>
</tr>
<tr>
<td>영향</td>
<td>서비스 지연 또는 마비</td>
<td>대규모 서비스 장애 가능</td>
</tr>
</tbody></table>
<blockquote>
<p>핵심은 DoS는 단일 출발지에서 발생하는 서비스 거부 공격이고, DDoS는 여러 출발지에서 동시에 발생하는 분산 서비스 거부 공격이라는 점이다.</p>
</blockquote>
<p>이번 글을 정리하면서 DDoS가 DoS의 확장된 형태라는 점을 이해할 수 있었다. 단순히 용어를 외우는 것보다, 공격이 어떤 방식으로 발생하고 서비스에 어떤 영향을 주는지 함께 이해하는 것이 중요하다고 느꼈다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 코딩 테스트 개념 정리: 스택/큐]]></title>
            <link>https://velog.io/@jh_devlog/Java-%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-%EC%8A%A4%ED%83%9D%ED%81%90</link>
            <guid>https://velog.io/@jh_devlog/Java-%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-%EC%8A%A4%ED%83%9D%ED%81%90</guid>
            <pubDate>Sat, 17 Jan 2026 08:19:03 GMT</pubDate>
            <description><![CDATA[<h2 id="1-스택큐-개념">1. 스택/큐 개념?</h2>
<h3 id="1-1-스택">1-1. 스택</h3>
<ul>
<li><strong>LIFO (Last-In, First-Out)</strong>: 나중에 들어온 데이터가 먼저 나가는 “후입선출” 구조</li>
<li><strong>비유</strong>: 접시 더미에서 가장 위 접시부터 꺼내는 것</li>
<li><strong>활용</strong>: 뒤로 가기(Undo), 괄호 짝 맞추기, 재귀 함수 호출 관리, DFS(깊이 우선 탐색) 등</li>
</ul>
<h3 id="1-2-큐">1-2. 큐</h3>
<ul>
<li><strong>FIFO (First-In, First-Out)</strong>: 먼저 들어온 데이터가 먼저 나가는 “선입선출” 구조</li>
<li><strong>비유</strong>: 은행 창구 줄서기</li>
<li><strong>활용</strong>: 프린터 출력 대기열, 은행 창구 업무, 작업/프로세스 스케줄링, BFS(너비 우선 탐색) 등</li>
</ul>
<blockquote>
<p><strong>원형 큐(Circular Queue)</strong>란 ?
일반적인 선형 큐(배열 기반)는 dequeue 할 때 앞부분이 비게 됩니다. 이 빈 공간을 재사용하려면 데이터를 앞으로 당기는 작업(shift, O(n))이 필요할 수 있는데, 이를 해결하기 위해 배열의 끝과 시작이 연결된 것처럼 인덱스를 순환시키는 구조가 원형 큐입니다.</p>
</blockquote>
<ul>
<li>참고: Java의 <code>ArrayDeque</code>는 내부적으로 <strong>원형(서큘러 버퍼) + 자동 리사이즈</strong> 방식이라, 코딩 테스트에서는 별도로 원형 큐를 구현할 일이 거의 없습니다.</li>
</ul>
<h3 id="1-3-시간복잡도">1-3. 시간복잡도</h3>
<p>스택과 큐는 데이터의 입구/출구가 정해져 있어 성능이 매우 뛰어납니다.</p>
<table>
<thead>
<tr>
<th>연산</th>
<th align="right">시간 복잡도</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>삽입 (Push/Offer)</td>
<td align="right">O(1)</td>
<td>끝(또는 위)에 추가 <em>(평균/분할 상환)</em></td>
</tr>
<tr>
<td>삭제 (Pop/Poll)</td>
<td align="right">O(1)</td>
<td>정해진 위치에서 제거</td>
</tr>
<tr>
<td>조회 (Peek)</td>
<td align="right">O(1)</td>
<td>제거 없이 가장 앞/위 확인</td>
</tr>
<tr>
<td>탐색 (Search)</td>
<td align="right">O(n)</td>
<td>특정 값 찾으려면 전체 순회 필요</td>
</tr>
</tbody></table>
<hr>
<h2 id="2-java에서-스택큐-다루기">2. Java에서 스택/큐 다루기</h2>
<h3 id="2-1-생성-및-초기화">2-1. 생성 및 초기화</h3>
<pre><code class="language-java">import java.util.*;

// 스택(LIFO)
Stack&lt;Integer&gt; stack1 = new Stack&lt;&gt;();
Deque&lt;Integer&gt; stack2 = new ArrayDeque&lt;&gt;();

// 큐(FIFO)
Queue&lt;Integer&gt; queue = new ArrayDeque&lt;&gt;();</code></pre>
<ul>
<li>스택처럼 쓸 때: <code>push / pop / peek</code></li>
<li>큐처럼 쓸 때: <code>offer / poll / peek</code> </li>
<li>Deque를 큐로 더 명시적으로 쓰고 싶으면: <code>addLast / pollFirst / peekFirst</code></li>
</ul>
<blockquote>
<p>Java의 <code>Stack</code> 클래스는 내부적으로 <code>Vector</code>를 상속받아 만들어졌고, 많은 메서드가 <code>synchronized</code> 기반이라 코딩 테스트처럼 단일 스레드 환경에서는 불필요한 오버헤드가 생길 수 있습니다. 그래서 보통 <code>ArrayDeque(Deque)</code> 를 스택/큐로 활용하는 것을 추천합니다.</p>
</blockquote>
<h3 id="2-2-자주-쓰는-유틸">2-2. 자주 쓰는 유틸</h3>
<ul>
<li><code>size()</code>: 현재 데이터 개수 확인</li>
<li><code>isEmpty()</code>: 비어있는지 확인 (pop/poll 전 필수)</li>
<li><code>clear()</code>: 전체 초기화</li>
</ul>
<hr>
<h2 id="3-기본-메서드">3. 기본 메서드</h2>
<h3 id="3-1-스택-메서드">3-1. 스택 메서드</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>삽입</td>
<td><code>push(e)</code></td>
<td>맨 위에 요소 추가</td>
</tr>
<tr>
<td>삭제</td>
<td><code>pop()</code></td>
<td>맨 위 요소 제거 후 반환 <strong>(비어있으면 예외 발생)</strong></td>
</tr>
<tr>
<td>확인</td>
<td><code>peek()</code></td>
<td>제거 없이 맨 위 요소 확인</td>
</tr>
</tbody></table>
<h3 id="3-2-큐-메서드">3-2. 큐 메서드</h3>
<table>
<thead>
<tr>
<th>기능</th>
<th>메서드</th>
<th>설명</th>
</tr>
</thead>
<tbody><tr>
<td>삽입</td>
<td><code>offer(e)</code></td>
<td>큐 뒤에 추가 <strong>(실패 시 false)</strong></td>
</tr>
<tr>
<td>삭제</td>
<td><code>poll()</code></td>
<td>큐 앞 요소 제거 후 반환 <strong>(비어있으면 null)</strong></td>
</tr>
<tr>
<td>확인</td>
<td><code>peek()</code></td>
<td>제거 없이 맨 앞 요소 확인</td>
</tr>
</tbody></table>
<h3 id="3-3-비었을-때-동작">3-3. 비었을 때 동작</h3>
<ul>
<li><strong>예외 발생 계열</strong>: <code>pop</code>, <code>remove</code>, <code>element</code> → 비어있으면 예외</li>
<li><strong>null 반환 계열(Queue/Deque)</strong>: <code>poll</code>, <code>peek</code> → 비어있으면 <code>null</code></li>
<li>(참고) <code>Stack</code> 클래스의 <code>pop()/peek()</code> 는 비어있으면 <code>EmptyStackException</code></li>
</ul>
<hr>
<h2 id="4-대표-패턴-및-실전-예제">4. 대표 패턴 및 실전 예제</h2>
<p>코딩 테스트에서 스택과 큐가 어떻게 쓰이는지, 가장 자주 나오는 패턴의 템플릿입니다.</p>
<h3 id="4-1-괄호-검사-stack">4-1. 괄호 검사 (Stack)</h3>
<p>문자열을 순회하며 짝이 맞는지 확인하는 전형적인 스택 문제입니다.</p>
<pre><code class="language-java">public boolean isValid(String s) {
    Deque&lt;Character&gt; stack = new ArrayDeque&lt;&gt;();
    for (char c : s.toCharArray()) {
        if (c == &#39;(&#39;) stack.push(&#39;)&#39;);
        else if (c == &#39;{&#39;) stack.push(&#39;}&#39;);
        else if (c == &#39;[&#39;) stack.push(&#39;]&#39;);
        else if (stack.isEmpty() || stack.pop() != c) return false;
    }
    return stack.isEmpty();
}</code></pre>
<h3 id="4-2-bfs-탐색-queue">4-2. BFS 탐색 (Queue)</h3>
<p>최단 거리나 인접 노드 탐색 시 사용되는 기본 구조입니다.</p>
<pre><code class="language-java">public void bfs(int startNode) {
    Queue&lt;Integer&gt; queue = new ArrayDeque&lt;&gt;();
    queue.offer(startNode);
    visited[startNode] = true;

    while (!queue.isEmpty()) {
        int curr = queue.poll();
        for (int next : adj[curr]) {
            if (!visited[next]) {
                visited[next] = true;
                queue.offer(next);
            }
        }
    }
}</code></pre>
<h3 id="4-3-우선순위-큐-priorityqueue">4-3. 우선순위 큐 (PriorityQueue)</h3>
<p>들어온 순서가 아니라 <strong>우선순위(값의 크기 등)</strong>에 따라 데이터가 나가는 자료구조입니다.
“가장 작은 값/큰 값”을 계속 뽑아야 할 때 유용합니다. (최소힙/최대힙)</p>
<pre><code class="language-java">// 최소 힙 (오름차순)
PriorityQueue&lt;Integer&gt; minHeap = new PriorityQueue&lt;&gt;();

// 최대 힙 (내림차순)
PriorityQueue&lt;Integer&gt; maxHeap = new PriorityQueue&lt;&gt;(Collections.reverseOrder());</code></pre>
<h3 id="4-4-요세푸스-문제-queuedeque">4-4. 요세푸스 문제 (Queue/Deque)</h3>
<p>큐의 <strong>회전(Rotate)</strong> 성질을 이용한 대표적인 문제입니다. 
$N$명의 사람이 원형으로 앉아 있을 때, $K$번째 사람을 계속해서 제거하는 로직입니다.</p>
<pre><code class="language-java">public List&lt;Integer&gt; josephus(int n, int k) {
    Deque&lt;Integer&gt; queue = new ArrayDeque&lt;&gt;();
    List&lt;Integer&gt; result = new ArrayList&lt;&gt;();

    // 1. 큐 초기화 (1번부터 N번까지)
    for (int i = 1; i &lt;= n; i++) queue.addLast(i);

    // 2. 큐가 빌 때까지 반복
    while (!queue.isEmpty()) {
        // K-1번 동안 맨 앞의 요소를 뒤로 보냄
        for (int i = 0; i &lt; k - 1; i++) {
            queue.addLast(queue.pollFirst());
        }
        // K번째 요소를 제거하고 결과 리스트에 추가
        result.add(queue.pollFirst());
    }
    return result;
}</code></pre>
<hr>
<h2 id="5-주의사항">5. 주의사항</h2>
<ol>
<li><p><strong>Empty Check</strong></p>
<ul>
<li><code>poll()/peek()</code>는 비어있을 때 <code>null</code>을 반환</li>
<li><code>pop()/remove()</code>는 비어있을 때 예외 발생
 → 코딩 테스트에서는 안전하게 <code>isEmpty()</code>로 먼저 확인하거나, <code>poll/peek</code> 계열을 선호하는 편이 좋습니다.</li>
</ul>
</li>
<li><p><strong>null 처리</strong></p>
<ul>
<li><code>poll()</code>이나 <code>peek()</code> 반환값은 비어있으면 <code>null</code></li>
<li>기본 타입 <code>int</code>에는 <code>null</code>을 담을 수 없으니 <code>Integer</code>로 받거나, <code>isEmpty()</code>로 먼저 검사하세요.</li>
</ul>
</li>
<li><p><strong>ArrayDeque vs LinkedList</strong></p>
<ul>
<li>단순 삽입/삭제만 반복하는 큐/스택 용도라면 보통 <code>ArrayDeque</code>가 더 빠르고 메모리 효율적입니다.</li>
<li><code>LinkedList</code>는 노드 객체가 많아져 오버헤드가 생길 수 있습니다.</li>
<li><code>ArrayDeque</code>는 null 요소를 허용하지 않습니다. (<code>offer(null)</code> 같은 것 금지)</li>
</ul>
</li>
</ol>
<hr>
<h2 id="마무리">마무리</h2>
<p>오늘은 알고리즘의 기초인 스택과 큐에 대하여 정리해 보았습니다.
공부하다 보면 구현 그 자체보다 <strong>&#39;이 문제에 왜 이 자료구조를 써야 하지?&#39;</strong>를 고민하는 게 가장 어려운 것 같습니다.
아직 어렵지만 문제를 많이 풀다보면, 적절한 자료구조를 찾는 것도 익숙해지지 않을까 싶습니다.☺️
다음 포스팅은 해시(Hash) 관련 내용을 정리해보겠습니다.🔥</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 코딩 테스트 개념 정리: 배열(Array)]]></title>
            <link>https://velog.io/@jh_devlog/Java-%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-%EB%B0%B0%EC%97%B4Array</link>
            <guid>https://velog.io/@jh_devlog/Java-%EC%BD%94%EB%94%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC-%EB%B0%B0%EC%97%B4Array</guid>
            <pubDate>Thu, 08 Jan 2026 06:38:53 GMT</pubDate>
            <description><![CDATA[<h2 id="1-배열이-뭔데">1. 배열이 뭔데?</h2>
<h3 id="1-1-배열의-핵심-특징">1-1. 배열의 핵심 특징</h3>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/5a0f31f8-3c35-4ca1-9c23-41cf529cc854/image.png" alt=""></p>
<p>배열은 <strong>연속된 메모리 공간</strong>에 동일한 타입의 데이터를 순차적으로 저장하는 자료구조입니다.</p>
<ul>
<li><strong>고정 크기</strong>: 생성 시 크기를 지정하며, 한 번 정해진 크기는 변경할 수 없습니다.</li>
<li><strong>인덱스 접근</strong>: 0부터 시작하는 인덱스를 통해 데이터에 직접 접근하므로 속도가 매우 빠릅니다.</li>
<li><strong>논리적 순서 = 물리적 순서</strong>: 메모리 상에 데이터가 붙어 있어 CPU 캐시 효율이 좋습니다.</li>
</ul>
<h3 id="1-2-시간-복잡도">1-2. 시간 복잡도</h3>
<table>
<thead>
<tr>
<th>연산</th>
<th align="right">시간 복잡도</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>인덱스로 접근/수정 (<code>arr[i]</code>)</td>
<td align="right"><strong>O(1)</strong></td>
<td>주소 계산으로 바로 접근</td>
</tr>
<tr>
<td>전체 순회 (<code>for</code>)</td>
<td align="right"><strong>O(n)</strong></td>
<td>모든 원소를 한 번씩 확인</td>
</tr>
<tr>
<td>값 탐색(선형 탐색)</td>
<td align="right"><strong>O(n)</strong></td>
<td>최악의 경우 끝까지 확인 필요</td>
</tr>
<tr>
<td>정렬 (<code>Arrays.sort</code>)</td>
<td align="right">평균 <strong>O(n log n)</strong></td>
<td>비교 기반 정렬</td>
</tr>
<tr>
<td>중간 삽입/삭제(shift 발생)</td>
<td align="right"><strong>O(n)</strong></td>
<td>요소들을 한 칸씩 밀거나 당김</td>
</tr>
</tbody></table>
<blockquote>
<p>코테에서 “삽입/삭제가 자주 발생”한다면 배열 말고 다른 구조(<code>ArrayList</code>, <code>Deque</code>, <code>LinkedList</code> 등)도 같이 고려하는 게 좋습니다.</p>
</blockquote>
<hr>
<h2 id="2-java에서-배열-다루기">2. Java에서 배열 다루기</h2>
<h3 id="2-1-생성-및-초기화">2-1. 생성 및 초기화</h3>
<pre><code class="language-java">import java.util.Arrays;

int[] arr1 = new int[5];          // 0으로 초기화
int[] arr2 = {1, 2, 3, 4, 5};     // 선언과 동시에 초기화

Arrays.fill(arr1, -1);            // 특정 값으로 채우기
</code></pre>
<h3 id="2-2-자주-쓰는-유틸-javautilarrays">2-2. 자주 쓰는 유틸 (java.util.Arrays)</h3>
<ul>
<li>정렬: <code>Arrays.sort(arr);</code> </li>
<li>복사: <code>Arrays.copyOf(arr, newLength);</code> (새로운 배열 객체 생성)</li>
<li>출력: <code>Arrays.toString(arr);</code></li>
<li>리스트 변환: <code>Arrays.asList(arr);</code> (<strong>객체 배열</strong>일 때만 기대한 대로 동작)</li>
</ul>
<h3 id="2-3-다차원-배열-2차원">2-3. 다차원 배열 (2차원)</h3>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/2f0b70d5-b23b-49d9-9af4-0e6e128f7504/image.png" alt=""></p>
<p>2차원 배열에서는 <code>arr[row][col]</code> 순서(행 → 열)를 헷갈리지 않도록 주의해야 합니다. </p>
<pre><code class="language-java">int[][] matrix = new int[3][3]; // 3x3 격자

int rows = matrix.length;       // 행의 개수
int cols = matrix[0].length;    // 열의 개수</code></pre>
<hr>
<h2 id="3-arraylist-정리">3. ArrayList 정리</h2>
<p>크기가 동적으로 변경되는 배열이 필요할 때 <code>ArrayList</code>를 활용합니다.</p>
<h3 id="3-1-arraylist란">3-1. ArrayList란?</h3>
<p><code>ArrayList</code>는 Java의 대표적인 리스트 구현체로, 내부적으로 배열을 사용해 데이터를 저장합니다.
원소가 늘어나면 더 큰 배열을 만들어 복사(리사이징)하며 크기를 자동으로 확장합니다.</p>
<h4 id="리사이징resizing-동작-원리">리사이징(Resizing) 동작 원리</h4>
<ul>
<li>초기 용량(capacity)은 기본 10개입니다.</li>
<li>원소가 늘어나 용량이 가득 차면, 내부 배열을 약 1.5배 크기로 새로 만들고 기존 데이터를 복사합니다.</li>
<li>이 때문에 개별 추가는 평균 O(1)이지만, 리사이징 시점에는 O(n)이 걸립니다. (Amortized O(1))</li>
<li>원소 개수를 미리 알면 <code>new ArrayList&lt;&gt;(expectedSize)</code>로 초기화하여 리사이징을 줄일 수 있습니다.</li>
</ul>
<h3 id="3-2-배열-vs-arraylist-비교">3-2. 배열 vs ArrayList 비교</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>배열(Array)</th>
<th>ArrayList</th>
</tr>
</thead>
<tbody><tr>
<td>크기 변경</td>
<td>불가능</td>
<td>가능(자동 리사이징)</td>
</tr>
<tr>
<td>접근/수정</td>
<td>O(1)</td>
<td>O(1)</td>
</tr>
<tr>
<td>맨 뒤 추가</td>
<td>직접 구현 시 O(n) 복사 필요</td>
<td>평균 O(1), 리사이징 시 O(n)</td>
</tr>
<tr>
<td>중간 삽입/삭제</td>
<td>O(n)</td>
<td>O(n)</td>
</tr>
<tr>
<td>타입</td>
<td>primitive 가능(<code>int[]</code>)</td>
<td>객체만 가능(<code>Integer</code>)</td>
</tr>
<tr>
<td>메모리</td>
<td>상대적으로 효율적</td>
<td>오토박싱/객체 오버헤드 가능</td>
</tr>
</tbody></table>
<blockquote>
<p>고정 길이/성능 중심이면 배열, 가변 길이/구현 편의성이 필요하면 <code>ArrayList</code>를 선택합니다.</p>
</blockquote>
<h3 id="3-3-기본-패턴">3-3. 기본 패턴</h3>
<pre><code class="language-java">import java.util.*;

List&lt;Integer&gt; list = new ArrayList&lt;&gt;();

// 추가
list.add(10);           // 맨 뒤에 추가
list.add(0, 5);         // 인덱스 0에 삽입

// 조회
int v = list.get(0);

// 수정
list.set(0, 99);

// 삭제
list.remove(1);         // 인덱스 1 삭제
list.remove(Integer.valueOf(10)); // 값 10 삭제

// 크기
int size = list.size();

// 비었는지 여부 확인
boolean empty = list.isEmpty();

// 포함 여부 확인
boolean contains = list.contains(10);

// 정렬
Collections.sort(list);                    // 오름차순
list.sort(Comparator.reverseOrder());      // 내림차순
</code></pre>
<h3 id="3-4-주의사항">3-4. 주의사항</h3>
<h4 id="1-remove-오버로드">1) remove() 오버로드</h4>
<p><code>ArrayList&lt;Integer&gt;</code>에서 <code>remove(1)</code>은 “값 1 제거”가 아니라 <strong>인덱스 1 제거</strong>로 동작합니다.
값으로 제거하려면 <code>Integer.valueOf()</code>를 사용해야 합니다.</p>
<pre><code class="language-java">List&lt;Integer&gt; list = new ArrayList&lt;&gt;(List.of(1, 2, 3));

list.remove(1);                    // 인덱스 1 삭제 → 값 2가 삭제됨
list.remove(Integer.valueOf(1));   // 값 1 삭제
</code></pre>
<h4 id="2-primitive-배열-↔-arraylist-변환">2) primitive 배열 ↔ ArrayList 변환</h4>
<pre><code class="language-java">// int[] -&gt; List&lt;Integer&gt;
int[] nums1 = {1, 2, 3};
List&lt;Integer&gt; list1 = Arrays.stream(nums1)
                            .boxed()
                            .collect(Collectors.toList());

// List&lt;Integer&gt; -&gt; int[]
List&lt;Integer&gt; list2 = List.of(1, 2, 3);
int[] nums2 = list2.stream()
                   .mapToInt(Integer::intValue)
                   .toArray();
</code></pre>
<h4 id="3-arraysaslist는-객체-배열에서만-기대한-대로-동작">3) Arrays.asList()는 객체 배열에서만 기대한 대로 동작</h4>
<pre><code class="language-java">// 정상 동작
Integer[] objArr = {1, 2, 3};
List&lt;Integer&gt; ok = Arrays.asList(objArr);

// 주의: 원소 1개짜리 리스트처럼 동작
int[] primArr = {1, 2, 3};
List&lt;int[]&gt; weird = Arrays.asList(primArr);</code></pre>
<hr>
<h2 id="4-스트림stream-활용">4. 스트림(Stream) 활용</h2>
<p>Java 8부터 도입된 스트림은 배열과 컬렉션을 다룰 때 가독성을 높여줍니다. 특히 필터링이나 변환 작업에서 유용합니다.</p>
<pre><code class="language-java">int[] nums = {1, 2, 3, 4, 5};

// 1. 필터링 및 변환
int[] evenSquared = Arrays.stream(nums)
                          .filter(n -&gt; n % 2 == 0)
                          .map(n -&gt; n * n)
                          .toArray();

// 2. 집계 (max, sum 등)
int max = Arrays.stream(nums).max().orElse(0);

// 3. 인덱스 기반 스트림 (IntStream)
int weightedSum = IntStream.range(0, nums.length)
                           .map(i -&gt; nums[i] * i)
                           .sum();</code></pre>
<hr>
<h2 id="5-대표-패턴-및-실전-예제">5. 대표 패턴 및 실전 예제</h2>
<h3 id="5-1-빈도-세기-counting">5-1. 빈도 세기 (Counting)</h3>
<p>값의 범위가 작을 때 배열 인덱스를 키(Key)로 활용합니다. ($O(n)$)</p>
<pre><code class="language-java">int[] count = new int[26];
for (char c : &quot;hello&quot;.toCharArray()) {
    count[c - &#39;a&#39;]++; // 알파벳별 개수 저장
}</code></pre>
<h3 id="5-2-투-포인터-two-pointers">5-2. 투 포인터 (Two Pointers)</h3>
<p>정렬된 배열에서 양끝 포인터를 좁혀가며 조건을 찾습니다. ($O(n)$)</p>
<pre><code class="language-java">Arrays.sort(nums);
int left = 0;
int right = nums.length - 1;

while (left &lt; right) {
    int sum = nums[left] + nums[right];
    if (sum == target) return true;
    if (sum &lt; target) left++;
    else right--;
}
return false;
</code></pre>
<h3 id="5-3-누적합-prefix-sum">5-3. 누적합 (Prefix Sum)</h3>
<p>반복적인 구간 합 쿼리를 $O(1)$에 해결합니다.</p>
<pre><code class="language-java">int[] nums = {2, 1, 5, 3, 4};
int n = nums.length;

// 전처리: prefix[i] = 0부터 i-1까지의 합
int[] prefix = new int[n + 1];
for (int i = 0; i &lt; n; i++) prefix[i + 1] = prefix[i] + nums[i];

// 구간 [l, r] 합
int l = 1, r = 3; // 1 + 5 + 3 = 9
int sum = prefix[r + 1] - prefix[l];

</code></pre>
<h3 id="5-4-슬라이딩-윈도우-sliding-window">5-4. 슬라이딩 윈도우 (Sliding Window)</h3>
<p>고정된 크기 또는 가변 크기의 윈도우를 배열 위에서 이동시키며 조건을 만족하는 구간을 찾습니다. ($O(n)$)</p>
<pre><code class="language-java">public int minSubArrayLen(int target, int[] nums) {
    int left = 0, sum = 0;
    int minLen = Integer.MAX_VALUE;

    for (int right = 0; right &lt; nums.length; right++) {
        sum += nums[right];

        while (sum &gt;= target) {
            minLen = Math.min(minLen, right - left + 1);
            sum -= nums[left++];
        }
    }

    return minLen == Integer.MAX_VALUE ? 0 : minLen;
}</code></pre>
<h3 id="5-5-카데인-알고리즘-kadanes-algorithm">5-5. 카데인 알고리즘 (Kadane&#39;s Algorithm)</h3>
<p><strong>연속된 부분 배열의 최대 합</strong>을 구하는 DP의 기초 알고리즘입니다. ($O(n)$)</p>
<pre><code class="language-java">public int maxSubArray(int[] nums) {
    int maxSum = nums[0];
    int currentSum = nums[0];

    for (int i = 1; i &lt; nums.length; i++) {
        // [DP 로직] 현재 원소부터 새로 시작할지, 기존 합에 더해서 이어갈지 결정
        currentSum = Math.max(nums[i], currentSum + nums[i]);

        // 전체 구간 중 가장 컸던 합을 갱신
        maxSum = Math.max(maxSum, currentSum);
    }

    return maxSum;
}</code></pre>
<h3 id="5-6-그래프-인접-리스트-arraylist-활용">5-6. 그래프 인접 리스트 (ArrayList 활용)</h3>
<p>정점별 연결 정보를 저장해야 하는 그래프 문제에 활용합니다.($O(E)$)</p>
<pre><code class="language-java">import java.util.*;

public List&lt;List&lt;Integer&gt;&gt; buildGraph(int n, int[][] edges) {
    List&lt;List&lt;Integer&gt;&gt; graph = new ArrayList&lt;&gt;();
    for (int i = 0; i &lt; n; i++) graph.add(new ArrayList&lt;&gt;());

    for (int[] e : edges) {
        int u = e[0], v = e[1];
        graph.get(u).add(v);
        graph.get(v).add(u); // 무방향
    }
    return graph;
}</code></pre>
<h3 id="5-7-2차원-배열-상하좌우-탐색">5-7. 2차원 배열 상하좌우 탐색</h3>
<p>BFS/DFS로 격자에서 연결 요소 탐색, 최단거리, 영역 개수 세기, flood fill 등에 사용합니다. ($O(R*C)$)</p>
<pre><code class="language-java">import java.util.*;

public class GridBfsCount {
    static final int[] dr = {-1, 1, 0, 0};
    static final int[] dc = {0, 0, -1, 1};

    public static int countIslands(int[][] grid) {
        int R = grid.length, C = grid[0].length;
        boolean[][] visited = new boolean[R][C];

        int count = 0;
        for (int r = 0; r &lt; R; r++) {
            for (int c = 0; c &lt; C; c++) {
                if (grid[r][c] == 1 &amp;&amp; !visited[r][c]) {
                    bfs(grid, visited, r, c);
                    count++;
                }
            }
        }
        return count;
    }

    private static void bfs(int[][] grid, boolean[][] visited, int sr, int sc) {
        int R = grid.length, C = grid[0].length;

        Queue&lt;int[]&gt; q = new ArrayDeque&lt;&gt;();
        q.add(new int[]{sr, sc});
        visited[sr][sc] = true;

        while (!q.isEmpty()) {
            int[] cur = q.poll();
            int r = cur[0], c = cur[1];

            for (int d = 0; d &lt; 4; d++) {
                int nr = r + dr[d], nc = c + dc[d];
                if (nr &lt; 0 || nr &gt;= R || nc &lt; 0 || nc &gt;= C) continue;

                if (grid[nr][nc] == 1 &amp;&amp; !visited[nr][nc]) {
                    visited[nr][nc] = true;
                    q.add(new int[]{nr, nc});
                }
            }
        }
    }
}

</code></pre>
<hr>
<h2 id="6-자주-하는-실수">6. 자주 하는 실수</h2>
<ol>
<li>배열은 <code>arr.length</code> / <code>리스트는 list.size()</code></li>
<li><strong>2차원 배열 복사</strong>: <code>matrix.clone()</code>은 얕은 복사 -&gt; 행마다 <code>clone()</code> 필요</li>
<li><code>ArrayList.remove(int index)</code>: 값이 아니라 인덱스 삭제임</li>
<li><strong>빈 배열 예외</strong>: <code>max()</code>, <code>min()</code> 같은 연산은 빈 배열 처리를 하지 않으면 에러가 날 수 있음 (<code>orElse</code>, 조건문)</li>
</ol>
<hr>
<h2 id="마무리">마무리</h2>
<p>배열은 코딩 테스트에서 가장 자주 등장하는 기본 자료구조이기 때문에 꼭 알아둘 필요가 있습니다.
이번에 내용을 정리하면서 다시 공부해보았는데, 특히 자주 쓰는 패턴들을 정리하는 과정이 큰 도움이 되었습니다.</p>
<p>다음 포스팅에서는 <strong>Stack/Queue</strong>를 정리해보겠습니다.🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[첫 면접, 도망가고 싶었지만 그래도 보길 잘했다]]></title>
            <link>https://velog.io/@jh_devlog/%EC%B2%AB-%EB%A9%B4%EC%A0%91-%EB%8F%84%EB%A7%9D%EA%B0%80%EA%B3%A0-%EC%8B%B6%EC%97%88%EC%A7%80%EB%A7%8C-%EA%B7%B8%EB%9E%98%EB%8F%84-%EB%B3%B4%EA%B8%B8-%EC%9E%98%ED%96%88%EB%8B%A4</link>
            <guid>https://velog.io/@jh_devlog/%EC%B2%AB-%EB%A9%B4%EC%A0%91-%EB%8F%84%EB%A7%9D%EA%B0%80%EA%B3%A0-%EC%8B%B6%EC%97%88%EC%A7%80%EB%A7%8C-%EA%B7%B8%EB%9E%98%EB%8F%84-%EB%B3%B4%EA%B8%B8-%EC%9E%98%ED%96%88%EB%8B%A4</guid>
            <pubDate>Tue, 06 Jan 2026 16:06:35 GMT</pubDate>
            <description><![CDATA[<p>오늘 첫 번째 면접을 봤고, 기억이 생생할 때 후기를 남겨보려고 한다.
(사실 완전 첫 면접은 아니지만, 백엔드 개발자로서는 처음이었다.)</p>
<p>면접 전에는 솔직히 준비가 많이 부족하다는 걸 스스로 느껴 도망가고 싶은 마음이 컸다.
그럼에도 &quot;실전 경험에서만 얻을 수 있는 게 분명히 있다&quot;고 생각했고,
면접이 끝난 지금은 역시 <strong>보길 잘했다</strong>는 결론이다.</p>
<hr>
<h3 id="어떤-회사였나">어떤 회사였나?</h3>
<p>이번에 면접 본 회사는 <strong>온라인 플랫폼/서비스를 운영하는 B2B 스타트업</strong>이었다.
설립된 지 오래되지는 않았지만, 서비스 방향과 성장 흐름을 보았을 때 성장 가능성이 커 보이는 팀이라는 인상을 받았다.</p>
<hr>
<h3 id="면접-방식">면접 방식</h3>
<p>면접은 크게 아래 3가지로 구성되어 있었다.</p>
<ul>
<li>간단한 실무 테스트</li>
<li>기술 면접(이력서 기반 포함)</li>
<li>기타 질문(컬쳐핏/커뮤니케이션/성향 질문)</li>
</ul>
<p>실무 테스트는 처음이라 정말 무서웠다.
어떻게 대비해야 하는지 감도 잘 안 잡혔고, 그래서 테스트 대비보다는 <strong>기술 질문 / 이력서 기반 질문 / 컬쳐핏 질문</strong> 위주로 준비했다.</p>
<hr>
<h3 id="실무-테스트｜20분-그리고-ai-자유-사용">실무 테스트｜20분, 그리고 AI 자유 사용</h3>
<p>실무 테스트는 메일로 전달받은 링크로 접속해 진행하는 방식이었다.
문제 상황이 주어지고, 질문별로 파트가 나뉘어 있었으며 각 파트마다 <strong>요구사항(무엇을 작성해야 하는지)</strong>도 정리되어 있었다.</p>
<p>가장 특이했던 점은 <strong>AI 사용이 완전히 허용</strong>되었다는 것.
면접관이 “AI를 어떻게 활용하는지도 함께 본다”고 말해주셔서, 요즘은 진짜로 <strong>AI 활용 역량</strong> 자체가 평가 요소가 될 수 있겠다는 걸 체감했다.</p>
<p>(화면 공유 상태로 진행되었고 손이 떨려 계속 오타가 났다. 😨)</p>
<h3 id="결국-주어진-시간에-문제를-다-풀지-못했다⏰">결국 주어진 시간에 문제를 다 풀지 못했다..⏰</h3>
<p>주어진 시간은 20분, 풀어야 하는 문제 상황은 2개였다.</p>
<p>그런데 첫 번째 문제부터 문제 파악 자체가 어려웠었다.</p>
<p>AI를 마음대로 써도 된다고는 했지만, “활용 방식도 본다”는 말이 신경 쓰여서 다음 방식으로 접근했다.</p>
<ul>
<li>먼저 <strong>내가 문제를 최대한 이해</strong>한다</li>
<li>그 다음 <strong>AI에게 상황 분석을 요청</strong>한다</li>
<li>결과 중 <strong>필요한 부분만 선택해서 정리</strong>한다</li>
</ul>
<p>다만 이 방식은 생각보다 시간이 많이 걸렸다.
결국 시간 배분에 실패했고, 두 번째 문제는 아예 손도 못 대고 끝나버렸다… 🥲</p>
<p>다른 지원자 분은 첫 문제를 푸는 동시에 두 번째 문제도 미리 AI에 붙여넣어 두 문제를 동시에 분석해두고 진행했다고 한다.
돌이켜보면 그 방향이 더 좋은 전략이었던 것 같다.
(특히 스타트업 환경에서는 “완벽한 이해”보다 <strong>제한된 시간 안에 요구사항을 빠르게 충족하는 능력</strong>을 더 중요하게 볼 것 같았다.)</p>
<p>면접 중에도 AI 활용 방식에 대한 질문이 있었고, 전반적으로 이 회사가 <strong>AI 도구 활용 역량</strong>을 꽤 중요하게 본다는 느낌을 강하게 받았다.
요즘은 기술 공부뿐 아니라, AI를 ‘어떻게 잘 쓰는지’도 꾸준히 연습해야겠다는 생각이 들었다.</p>
<hr>
<h3 id="기술-면접｜22-그리고-멘붕">기술 면접｜2:2, 그리고 멘붕</h3>
<p>면접은 <strong>2:2</strong>로 진행됐다.</p>
<p>이력서 기반 질문이 시작되자, 함께 면접 본 다른 지원자 분이 말을 너무 잘하셔서 나도 모르게 자신감이 급격히 떨어졌다.
게다가 준비하지 못한 질문도 나와 답변이 깔끔하게 나오지 않았고, 특히 <strong>기술 선택/의사결정 이유</strong>를 묻는 질문에서 준비 부족이 그대로 드러났다.</p>
<p>주로 내가 했던 선택의 이유를 묻는 질문이 많았다.</p>
<ul>
<li>특정 기능 고도화를 위해 기술을 도입했다가 다시 단순화했는데, 그때 <strong>복잡도와 가치의 균형</strong>을 어떤 기준으로 판단했는지</li>
<li>멀티 인스턴스 환경에서 채팅 메시지 동기화에 Redis를 선택한 이유는 무엇인지</li>
</ul>
<p>결국은 내가 내린 판단의 기준과 근거를 설명하는 질문들이었는데, 이 부분을 더 구조적으로 준비해야겠다고 느꼈다.</p>
<blockquote>
<p>“기술을 왜 선택했는지”는
단순히 경험을 나열하는 게 아니라,
<strong>기준과 근거를 구조적으로 설명할 수 있어야 한다.</strong></p>
</blockquote>
<hr>
<h3 id="컬쳐핏-질문｜오히려-더-편하게-답했다">컬쳐핏 질문｜오히려 더 편하게 답했다</h3>
<p>기술 면접 이후에는 기술 외 질문을 하는 시간도 있었다.</p>
<ul>
<li>개발을 시작한 계기</li>
<li>스트레스 해소 방식</li>
<li>갈등 해결 방식 등</li>
</ul>
<p>이런 질문들은 비교적 편하게 답할 수 있었다.
기술 질문보다 오히려 이쪽이 내 경험을 자연스럽게 꺼내기 쉬웠던 것 같다.</p>
<hr>
<h3 id="전체적인-평가">전체적인 평가</h3>
<p>이번 면접은 솔직히 잘 봤다고 말하기는 어렵다.
특히 실무 테스트에서는 시간 배분 실패로 아쉬움이 컸고, 기술 질문 중 일부는 준비가 부족해서 답변이 흔들렸다.</p>
<p>그럼에도 불구하고 이번 면접을 통해 얻은 건 많은 것 같다.</p>
<ul>
<li>실무 테스트는 ‘문제 파악 + 시간 관리’가 핵심</li>
<li>AI 활용 방식 자체가 평가 포인트가 될 수 있음</li>
<li>기술 선택 질문은 기준/근거/대안까지 준비해야 함</li>
<li>“면접 경험”은 준비 부족을 정확히 보여주는 최고의 피드백</li>
</ul>
<p>아쉬움이 남았지만, 다음 면접을 더 잘 보기 위한 확실한 피드백을 얻은 하루였다.</p>
<hr>
<h3 id="추가로">추가로...</h3>
<p>면접을 끝내고, 그냥 집에 가기엔 헛헛해서 교보문고에 들렀다.
장바구니에 담아두었던 책들도 사고, 잠깐 리프레시도 했다.</p>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/7ae6ca22-bf4f-497a-ab83-9f6765d8954e/image.jpeg" alt="">
(우울해서 책 샀슨....🥺)</p>
<p>다음 면접 전까지는 책도 조금씩 읽으면서 CS 공부를 매일 루틴으로 다시 잡아봐야겠다.
그래도 내내 걱정하던 면접이 끝났다는 사실만으로도 한결 후련하다. 🙂👍</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[2025년 회고]]></title>
            <link>https://velog.io/@jh_devlog/2025%EB%85%84%EC%9D%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%A9%B0</link>
            <guid>https://velog.io/@jh_devlog/2025%EB%85%84%EC%9D%84-%EB%8F%8C%EC%95%84%EB%B3%B4%EB%A9%B0</guid>
            <pubDate>Wed, 31 Dec 2025 04:02:45 GMT</pubDate>
            <description><![CDATA[<h2 id="2025년을-돌아보며">2025년을 돌아보며</h2>
<p>2025년은 <strong>내가 가야 할 방향을 정하고, 끝까지 해내는 해</strong>였다.
불안이 있어도 결정을 미루지 않았고, 그 선택의 결과를 스스로 감당하는 법을 배웠다.
올해의 키워드를 따라가며 2025년을 차근차근 정리해보려 한다.</p>
<hr>
<h2 id="올해의-키워드">올해의 키워드</h2>
<p>올해를 대표하는 키워드는 다음 다섯 가지다.</p>
<p><strong>1. 퇴사
2. 코드잇 스프린트
3. 렌즈삽입술
4. 발리 여행
5. 정보처리기사 실기 &amp; 운전면허</strong></p>
<hr>
<h2 id="1-퇴사">1. 퇴사</h2>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/8927f7fa-efc0-438d-b102-2dae5b7bc9cb/image.png" alt=""></p>
<p>올해 가장 큰 사건 중 하나는 2년 동안 다니던 회사 MWW를 퇴사한 일이다.
퇴사를 결심한 이유는 여러 가지가 있었지만, 회사 상황에 대한 불안과 내 커리어 방향에 대한 고민이 가장 컸던 것 같다.</p>
<p>돌이켜보면 MWW는 &#39;커리어를 설계해서 들어간 회사&#39;라기보다, 당장의 취업이라는 목표에 더 가까운 선택이었다.
대학 졸업 후 학교 연계로 회사를 알게 되었고, 당시에는 “내가 앞으로 어떤 개발자가 되고 싶은지”보다 “일단 사회에 나가 일해보자”가 우선이었다. 
(대학 프로젝트가 챗봇과 관련이 있었고 회사도 챗봇 회사라서 더 쉽게 결정했던 것 같다.)</p>
<p>그렇게 인턴을 거쳐 정규직으로 전환되면서 첫 회사 생활을 시작했다.
지금와서 이때를 돌아보니, 나는 취업 준비의 어려움을 겪어보지 못하고 빠르게 사회인이 되었구나하는 생각이 든다.</p>
<p>첫 회사로 MWW를 다닌 건 행운이었다.객관적으로 봐도 복지가 좋았고, 좋은 사람들이 많았다. 
(특히 4.5일제는 앞으로 어떤 회사를 가더라도 종종 떠올리게 될 것 같다. 삶의 질 향상 Goat...✨)</p>
<p>무엇보다 이 회사에서 얻은 가장 큰 건 사람이었다.
7명의 동기들과 함께하며 많은 의지가 되었고, 업무 외에도 개인적으로 자주 놀러다니며 가까워졌다.
물론 프로젝트를 하며 힘든 순간도 있었지만, 그 시간을 포함해서 MWW는 내게 좋은 추억으로 남을 것 같다.</p>
<hr>
<h2 id="2-코드잇-스프린트">2. 코드잇 스프린트</h2>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/756a49c3-e4af-461d-836b-4471387f06ed/image.jpeg" alt="">
<img src="https://velog.velcdn.com/images/jh_devlog/post/81b10aab-4bd8-49e3-8010-2f1d74d90ba7/image.png" alt=""></p>
<p>퇴사를 결심한 뒤, 내가 앞으로 가고 싶은 방향이 <strong>‘백엔드 개발자’</strong>라는 건 분명했다.
다만 그 방향으로 가기엔 내가 아직 많이 부족하다는 것을 알고 있었고, 본격적으로 부트캠프를 알아보기 시작했다. 그중 코드잇 스프린트 과정이 가장 눈에 들어왔다.</p>
<p>지원 과정에서 코딩테스트와 면접을 통해 인원을 선발한다는 점이 인상적이었고, 커리큘럼도 내가 원하던 학습 방식과 잘 맞았다. 
무엇보다 수료 이후에도 커리어 프로그램을 통해 취업을 지원한다는 점이 좋았다.</p>
<p>그렇게 스프린트에 지원했고, Spring Backend 3기로 참여하게 되었다.</p>
<p>처음에는 비대면 수업이 아쉽게 느껴졌다. 그런데 시간이 지날수록 오히려 이 방식에 감사해졌다. 집중해서 수업을 듣고, 과제를 하고, 프로젝트까지 따라가려면 체력과 시간이 정말 많이 필요했기 때문이다.
학교 다니던 시절을 떠올리며 설레는 마음으로 시작했지만, 9시부터 19시까지 이어지는 풀타임 일정은 예상보다 훨씬 힘들었다.🥹 </p>
<p>그렇지만 그만큼 얻은 것도 컸다. 단순히 새로운 지식을 배운 것 이상으로, 내가 지금 어느 수준에 있는지를 정확히 알게 됐다.
(이전의 나는 정말 개발자라 하기엔 민망한 수준이었음을...)
스프린트 과정을 거치며 “이걸 안 들었으면 큰일 날 뻔했다”는 생각을 여러 번 했던 것 같다. </p>
<p>과정을 수행하면서 과제와 프로젝트를 병행하느라 시간이 정말 빠르게 지나갔다.
그 과정 속에서 작은 성취들도 쌓였고, 사진처럼 엉덩이 1톤상도 받았다. 스스로 생각해도 성실하게 참여했던 것 같아서 뿌듯하다. 😎✌️</p>
<p>수료 이후에는 조금 느슨해지긴 했는데, 이제부터는 내가 스스로 루틴을 만들고 유지하는 단계라고 생각한다. 정신차리고 열심히 하자...💪</p>
<hr>
<h2 id="3-렌즈삽입술">3. 렌즈삽입술</h2>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/46758635-ea31-45ef-8815-cd0fecb93c87/image.png" alt=""></p>
<p>올해의 또 다른 큰 이벤트는 렌즈삽입술(ICL) 수술이었다. 수술은 추석 연휴에 진행했다.
나는 평생 고도근시 시력으로 살며 학창 시절엔 3번 압축한 두께로 안경을 맞췄고🤓, 성인이 된 후로는 렌즈 없이는 밖에 나가기가 어려울 정도였다.
(진짜 심각한 고도근시였다. 왼쪽 -9.5 오른쪽 -10.0😱)</p>
<p>그래서 시력 교정 수술은 오래전부터 하고 싶었으나, 돈과 시간이 동시에 맞아떨어지는 시기가 없었다.
이번에 퇴사를 하며 퇴직금이 생겼고, 마침 긴 추석 연휴까지 겹치면서 “지금이 아니면 또 미루겠다”는 생각에 수술을 결심했다.</p>
<p>렌즈삽입술은 다른 수술들보다 회복이 빠르고, 각막을 직접 깎지 않는 방식이라 그나마 안전하다고 생각해 이를 선택했다.
후기를 찾아보면 “크게 아프지 않고 금방 끝난다”는 말이 많아서 큰 걱정 없이 병원에 갔는데.. 그래도 수술은 수술이었다.</p>
<p>내 예상보다 훨씬 아팠고, 수술 다음 날 까지는 하루 종일 선글라스를 끼고 눈물이 계속 났었다.
그렇지만 회복은 정말 빠르게 됐고, 지금은 수술을 후회하진 않는다.</p>
<p>아침에 일어나서 안경을 찾지 않아도 되고, 외출 전에 렌즈부터 챙기지 않아도 된다는 점이 정말 좋다. 삶의 질이 확실히 올라갔다.🙌</p>
<hr>
<h2 id="4-발리-여행">4. 발리 여행</h2>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/aed97702-3173-4c11-b305-e7ee359f93ac/image.png" alt=""></p>
<p>코드잇 스프린트 과정을 수료한 뒤, 친구들과 발리로 여행을 다녀왔다.
올해 초부터 계획했던 일정이었는데, 마침 스프린트를 끝낸 직후라 마음 편히 쉬고 올 수 있었던 여행이었다.</p>
<p>발리는 확실히 휴양지로 좋았지만, 벌레가 꽤 많아서 예상보다 쉽지 않기도 했다. 😅
그래도 지프 투어, 스노클링 같은 액티비티를 즐기고, 사진도 많이 찍으면서 좋은 추억을 많이 남겼다.</p>
<p>한국으로 돌아온 뒤에도 한동안 발리 생각을 자주 하며 마음이 붕 떠 있기도 했다.
그래도 덕분에 에너지를 충전했고, 다시 일상으로 돌아갈 힘도 얻은 것 같다.</p>
<p>내년에는 혼자 떠나는 여행도 한 번 도전해보고 싶다.</p>
<hr>
<h2 id="5-정보처리기사-실기--운전면허">5. 정보처리기사 실기 &amp; 운전면허</h2>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/1840521f-7095-4436-ae49-07e6a331c75f/image.png" alt=""></p>
<p>정보처리기사 실기와 운전면허 등 자격 취득에도 도전했다.
둘 다 “언젠가 해야지” 하고 미뤄두던 목표였는데, 막상 마음먹고 시작하니 생각보다 에너지와 시간이 많이 필요했다.</p>
<p>둘 다 한 번씩은 실패를 경험했지만, 그때 포기하지 않고 다시 준비해서 재도전했다.
그리고 결국 두 개 모두 합격했다. 완벽하게 한 번에 끝냈다기보다, 한 번 실패해도 다시 잡고 끝까지 해낸 결과라서 더 의미가 있는 것 같다.</p>
<p>내년에도 새로운 자격증 도전을 꾸준히 이어가고, 운전은 꾸준히 연습해서 실제 생활에서 자연스럽게 할 수 있는 수준까지 만들어보고 싶다.</p>
<hr>
<h2 id="마무리">마무리</h2>
<p>올해의 나는 <strong>결단, 성장, 회복</strong>으로 표현할 수 있을 것 같다.</p>
<p>무엇보다 “끝까지 해낸다”는 감각을 몸으로 얻은 게 올해의 가장 큰 수확이다.</p>
<p>이렇게 정리해보니 2025년을 나름 알차게 보낸 것 같아 만족스럽다.
내년의 나도 화이팅 🙌
일단 취업부터 하자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[StaleObjectStateException 동시성 이슈 해결기]]></title>
            <link>https://velog.io/@jh_devlog/StaleObjectStateException-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EA%B8%B0</link>
            <guid>https://velog.io/@jh_devlog/StaleObjectStateException-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%9D%B4%EC%8A%88-%ED%95%B4%EA%B2%B0%EA%B8%B0</guid>
            <pubDate>Fri, 19 Dec 2025 07:50:42 GMT</pubDate>
            <description><![CDATA[<p>팀 프로젝트에서 로컬 개발 환경을 세팅하던 중 <strong>동시성 이슈(<code>StaleObjectStateException</code>)로 애플리케이션 기동이 실패</strong>했습니다.</p>
<p>처음엔 단순 트러블슈팅처럼 보였지만, 해결 과정에서 자연스럽게 “실제 서비스라면 어떻게 설계해야 할까?”까지 고민이 확장되어 정리해보았습니다.</p>
<hr>
<h3 id="🚨-사건의-발단--애플리케이션-기동-실패">🚨 사건의 발단 : 애플리케이션 기동 실패</h3>
<p>로컬에서 애플리케이션을 실행하자마자 아래 오류가 발생하며 기동이 실패했습니다.</p>
<blockquote>
<p><strong>StaleObjectStateException</strong>: Row was updated or deleted by another transaction</p>
</blockquote>
<p>원인을 추적해보니 <strong>초기 데이터 자동 입력을 담당하는 데이터 시더(Seeder)</strong>가 문제였습니다.</p>
<h3 id="무엇이-문제였나">무엇이 문제였나?</h3>
<p><code>Comment(댓글)</code> 데이터를 넣는 과정에서, 부모 엔티티인 <code>Article(기사)</code>의 <code>commentCount</code>를 업데이트하는 로직이 함께 실행되고 있었는데요.
이 과정에서 <strong>같은 <code>Article</code> 레코드를 여러 트랜잭션이 동시에 수정</strong>하려고 하면서 충돌이 발생했습니다.</p>
<p>즉, “로컬 실행 → 시딩 → 동일 Row 업데이트 경합 → 예외 → 애플리케이션 종료” 흐름으로 개발 환경 세팅 자체가 막혀버렸습니다.</p>
<blockquote>
<p>JPA에서는 동일 엔티티를 여러 트랜잭션이 동시에 갱신할 경우, 먼저 커밋된 변경으로 인해 이후 트랜잭션의 update가 실패하면서 <code>StaleObjectStateException</code>이 발생할 수 있습니다.</p>
</blockquote>
<hr>
<h3 id="💡-해결책--실행-순서를-명확히-하자">💡 해결책 : “실행 순서”를 명확히 하자</h3>
<p>핵심 원인은 <strong>시더 실행 순서가 보장되지 않는 상태에서</strong>, 동일 레코드를 건드리는 작업이 겹쳤다는 점이었습니다.</p>
<p>그래서 모든 시더를 한 곳에서 통제하는 <code>AllDataSeederRunner</code>를 도입해 <strong>순차 실행을 강제</strong>했습니다.</p>
<pre><code class="language-java">@Profile(&quot;dev&quot;)
@Component
@RequiredArgsConstructor
public class AllDataSeederRunner {
    private final List&lt;DataSeeder&gt; seeders;

    @PostConstruct
    public void runAllSeeders() {
        seeders.stream()
            .sorted(Comparator.comparingInt(this::getOrder))
            .forEach(DataSeeder::seed);
    }

    private int getOrder(DataSeeder seeder) {
        if (seeder instanceof UserDataSeeder) return 1;
        if (seeder instanceof InterestDataSeeder) return 2;
        if (seeder instanceof ArticleDataSeeder) return 3;
        if (seeder instanceof CommentDataSeeder) return 4;
        if (seeder instanceof CommentLikeDataSeeder) return 5;
        return 99;
    }
}</code></pre>
<p>이렇게 실행 순서를 명확히 하니 충돌 없이 데이터가 정상 입력되었고, 애플리케이션도 정상 기동되었습니다. 😎</p>
<hr>
<h3 id="✅-결과-일관된-개발-환경-보장">✅ 결과: 일관된 개발 환경 보장</h3>
<p>순차 실행을 보장함으로써 <strong>동일 레코드에 대한 경합을 원천 차단</strong>할 수 있었습니다.
덕분에 팀원 모두가 로컬에서 매번 동일하고 안정적인 초기 데이터를 기준으로 개발을 진행할 수 있게 되었습니다.</p>
<hr>
<h3 id="🤔-만약-실제-서비스에서-동시성-이슈가-발생한다면">🤔 만약 &quot;실제 서비스&quot;에서 동시성 이슈가 발생한다면?</h3>
<p>시더 문제는 순차 실행으로 해결했지만, &quot;수만 명의 유저가 사용하는 실제 서비스라면?&quot; 이야기가 완전히 달라질 수 있습니다.</p>
<p>예를 들어 회원가입/결제처럼 중복 요청이 들어오면 치명적인 도메인에서는, 단순한 <code>if (exists)</code> 체크만으로 동시성을 막을 수 없습니다.</p>
<h3 id="💭-예시-회원가입-요청이-중복으로-들어온다면">💭 예시: 회원가입 요청이 중복으로 들어온다면?</h3>
<p>유저가 가입 버튼을 여러 번 누르거나 네트워크 문제로 요청이 중복 전송되는 상황을 상상해보겠습니다.</p>
<ul>
<li>요청 A: “홍길동 아이디 있나요?” → 서버: “없네요, 가입 진행!” (처리 중)</li>
<li>요청 B: (A 완료 직전) “홍길동 있나요?” → 서버: “아직 없네요, 가입 진행!”</li>
</ul>
<p>-&gt; 결과: <strong>동일 아이디 유저가 중복 생성될 수 있음</strong> 😱</p>
<hr>
<h3 id="🛠️-동시성-대응-어떤-선택지가-있을까">🛠️ 동시성 대응: 어떤 선택지가 있을까?</h3>
<p>동시성 이슈를 막는 방법은 상황에 따라 달라지지만, 크게 아래 3가지 축으로 정리할 수 있습니다.</p>
<h3 id="1-db-제약-조건unique-index-활용">1) DB 제약 조건(Unique Index) 활용</h3>
<ul>
<li>DB 레벨에서 UNIQUE를 걸어두면, 마지막 커밋 순간에 DB가 중복을 막아줍니다.</li>
<li>애플리케이션은 중복 키 예외를 잡아서 <strong>적절한 에러 응답/재시도 정책</strong>을 설계하면 됩니다.</li>
<li>실무에서는 보통 가장 기본이자 반드시 필요한 안전장치입니다.</li>
</ul>
<h3 id="2-애플리케이션-레벨-락lock">2) 애플리케이션 레벨 락(Lock)</h3>
<ul>
<li><p><strong>낙관적 락(Optimistic Lock)</strong> 
  <code>@Version</code>으로 버전을 비교해, 누군가 먼저 수정했다면 “충돌 → 재시도/실패 처리”로 대응합니다. (충돌이 드물고 재시도 가능한 경우 유리)</p>
</li>
<li><p><strong>비관적 락(Pessimistic Lock)</strong>
  <code>SELECT ... FOR UPDATE</code>처럼 조회 시점부터 락을 걸어 다른 트랜잭션 접근을 막습니다.
(정합성이 극도로 중요한 구간에서 사용하지만, 성능/대기시간 비용이 큼)</p>
</li>
</ul>
<h3 id="3-분산-락-distributed-lock">3) 분산 락 (Distributed Lock)</h3>
<ul>
<li>서버가 여러 대인 분산 환경에서는 DB 락만으로 부족할 수 있어 Redis 같은 외부 저장소로 락을 관리합니다.</li>
<li>“특정 키(예: 회원가입 아이디, 주문번호)에 대해 한 번에 한 요청만 처리” 같은 제어가 가능합니다.</li>
</ul>
<hr>
<h3 id="✨-마치며-결국-핵심은-멱등성">✨ 마치며: 결국 핵심은 “멱등성”</h3>
<p>이번 경험을 통해 가장 크게 느낀 건,</p>
<blockquote>
<p><strong>여러 번 호출돼도 결과는 같아야 한다(멱등성)</strong>
라는 원칙이 시스템 안정성의 핵심이라는 점이었습니다.</p>
</blockquote>
<p>예를 들어 결제 시스템이라면,</p>
<ul>
<li>주문 번호를 유니크하게 관리하고</li>
<li>요청마다 Request-ID 같은 <strong>중복 방지 키</strong>를 두며</li>
<li>중복 요청이 와도 “한 번 처리된 요청이면 같은 결과를 반환”하도록 설계해야 합니다.</li>
</ul>
<p>단순한 시더 오류 해결에서 시작된 고민이었지만, 동시성은 결국 <strong>시스템의 신뢰성을 결정짓는 방어선</strong>이라는 점을 깊이 체감했습니다.
앞으로도 “기능이 돌아가는 코드”를 넘어, 유저의 예측 불가능한 행동과 네트워크 불확실성까지 고려하는 방어적 설계를 습관화해야 할 것 같습니다.🙌</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SB 3기] 코드잇 고급 프로젝트 회고 : 옷장을 부탁해]]></title>
            <link>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EA%B3%A0%EA%B8%89-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-%EC%98%B7%EC%9E%A5%EC%9D%84-%EB%B6%80%ED%83%81%ED%95%B4</link>
            <guid>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EA%B3%A0%EA%B8%89-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-%EC%98%B7%EC%9E%A5%EC%9D%84-%EB%B6%80%ED%83%81%ED%95%B4</guid>
            <pubDate>Thu, 20 Nov 2025 07:20:41 GMT</pubDate>
            <description><![CDATA[<h2 id="코드잇-고급-프로젝트-회고--옷장을-부탁해">코드잇 고급 프로젝트 회고 : 옷장을 부탁해</h2>
<p>고급 프로젝트 종료 후 약 한 달이 지났다. (시간이 너무 빨라요⏰)
이제 더는 미룰 수 없다는 생각으로 회고를 작성해보려 한다..!
기간이 제일 길었던 만큼 새로운 기술 적용도 많이 하고 열정적으로 임했던 프로젝트였다.</p>
<hr>
<h2 id="🗂️-프로젝트-개요">🗂️ 프로젝트 개요</h2>
<blockquote>
<p><strong>옷장을 부탁해(Otboo)</strong>
실시간 날씨와 사용자의 옷장을 기반으로 오늘 입을 옷을 추천해주는 개인화 코디 추천 서비스입니다.
사용자가 보유한 의상을 체계적으로 관리하고, OOTD 피드·팔로우·DM 등 소셜 기능을 통해 코디를 공유할 수 있습니다.</p>
</blockquote>
<h3 id="✅-주요-기능">✅ 주요 기능</h3>
<ul>
<li><p><strong>사용자 · 인증 시스템</strong></p>
<ul>
<li>회원가입/로그인, OAuth2 기반 소셜 로그인(Google, Kakao)</li>
<li>프로필 정보 관리</li>
</ul>
</li>
<li><p><strong>날씨 · 위치 연동</strong></p>
<ul>
<li>기상청 API를 활용한 실시간/예보 날씨 데이터 수집 및 저장</li>
<li>Kakao API 기반 위치 검색</li>
</ul>
</li>
<li><p><strong>옷장 관리 시스템</strong></p>
<ul>
<li>의상 등록/수정/삭제, 속성 관리</li>
<li>외부 쇼핑몰 <strong>구매 링크 연동</strong> 기능</li>
</ul>
</li>
<li><p><strong>의상 추천 시스템</strong></p>
<ul>
<li>날씨, 사용자 프로필, 보유 의상을 조합한 코디 추천 로직 설계</li>
<li>LLM 기반 추천 이유 생성</li>
<li>추천 점수 기반 필터링 + 랜덤 폴백 전략으로 <strong>최소 추천 세트 보장</strong></li>
<li>Redis를 활용해 최근 추천 아이템을 TTL 동안 제외하여 <strong>중복 추천 방지</strong></li>
</ul>
</li>
<li><p><strong>피드 시스템</strong></p>
<ul>
<li>날씨·착장 정보를 포함한 OOTD 피드 등록/수정/삭제</li>
<li>댓글 등록/목록 조회, 좋아요/취소</li>
<li>피드 검색 및 정렬</li>
</ul>
</li>
<li><p><strong>실시간 기능</strong></p>
<ul>
<li>WebSocket 기반 실시간 DM 기능</li>
<li>팔로우/댓글/좋아요 등 이벤트 발생 시 <strong>실시간 알림 발송 및 목록 조회</strong></li>
</ul>
</li>
<li><p><strong>파일 업로드 · 인프라</strong></p>
<ul>
<li>AWS S3를 통한 피드/프로필 이미지 업로드 및 스토리지 관리</li>
<li>Docker 기반 로컬 개발 환경 구성, AWS ECS를 통한 컨테이너 배포</li>
</ul>
</li>
<li><p><strong>팔로우 시스템</strong></p>
<ul>
<li>사용자 간 팔로우/언팔로우 및 팔로잉 피드 조회</li>
</ul>
</li>
</ul>
<h3 id="📌-기술-요구-사항">📌 기술 요구 사항</h3>
<ul>
<li>유효성 검사</li>
<li>커스텀 예외 처리</li>
<li>로그 관리</li>
<li>테스트 주도 개발 (TDD)</li>
<li>테스트 커버리지 80% 이상</li>
<li>CI/CD 파이프라인 구축 (GitHub Actions)</li>
<li>Spring Batch를 통한 배치 작업 관리</li>
<li>분산 환경 구성</li>
<li>Elasticsearch를 활용하여 피드 검색 기능 개선</li>
</ul>
<hr>
<h3 id="👩💻-나의-역할">👩‍💻 나의 역할</h3>
<h4 id="1️⃣-기능-개발">1️⃣ 기능 개발</h4>
<ul>
<li><p><strong>OOTD 피드 도메인</strong></p>
<ul>
<li>피드 등록, 수정, 논리 삭제 기능 구현</li>
<li>정렬 및 커서 기반 페이지네이션 피드 목록 조회 기능 구현 (RDB, Elasticsearch)</li>
<li>댓글 등록, 목록 조회 기능 구현</li>
<li>피드 좋아요/취소 기능 구현</li>
</ul>
</li>
<li><p><strong>의상 추천</strong></p>
<ul>
<li>날씨 데이터, 사용자 보유 의상, 프로필 정보를 활용한 자체 추천 알고리즘 개발</li>
<li><strong>LLM 기반 후처리 레이어</strong> 설계/적용(프롬프트 설계 포함)로 추천 품질 고도화</li>
<li>추천 <strong>임계값 미달 시 랜덤 폴백</strong> 로직 추가로 무추천 케이스 최소화</li>
</ul>
</li>
<li><p><strong>OAuth2 소셜 계정 연동</strong></p>
<ul>
<li>Google 계정 연동 및 인증 구현</li>
<li>Kakao 계정 연동 및 인증 구현</li>
</ul>
</li>
<li><p>DM 대화방 목록 조회 API 설계 구현</p>
</li>
</ul>
<h4 id="2️⃣-개발-외-역할">2️⃣ 개발 외 역할</h4>
<ul>
<li><strong>프로젝트 UI 개선</strong><ul>
<li>로그인 화면 일러스트/레이아웃 수정</li>
<li>로고 리디자인</li>
<li>전반적인 색상 통일</li>
<li>DM 탭 분리 및 DM 대화방 목록 화면 추가</li>
</ul>
</li>
<li><strong>PM 역할</strong><ul>
<li>일정 관리 및 작업 재분배</li>
</ul>
</li>
</ul>
<h3 id="🔗-깃허브-링크">🔗 깃허브 링크</h3>
<ul>
<li><a href="https://github.com/33otot/sb03-otboo-team03">Otboo GitHub Repository</a></li>
</ul>
<hr>
<h2 id="📚-개발-환경-및-사용-스택">📚 개발 환경 및 사용 스택</h2>
<pre><code>⚙️ Backend Stack
📦 Framework
├── Spring Boot 3.5.5        # 메인 애플리케이션 프레임워크
├── Spring Data JPA          # ORM 및 데이터 접근
├── QueryDSL                   # 동적 쿼리 작성
├── Spring Batch             # 대용량 배치 처리 및 스케줄링
├── Spring Security          # 인증·인가 및 보안 설정
├── OAuth2 Client            # 소셜 로그인(OAuth2) 연동
├── Spring WebSocket         # 실시간 양방향 통신
├── Spring WebFlux           # 비동기 HTTP 클라이언트
├── Spring Mail              # 이메일 발송
└── Gradle                   # 빌드 및 의존성 관리 도구

🗄️ Database &amp; Cache
├── PostgreSQL              # 운영 환경 RDBMS
├── H2 Database             # 개발/테스트용 In-memory DB
└── Redis                   # 캐싱 관리

🔎 Search &amp; AI
├── Elasticsearch             # 검색 엔진
└── Spring AI 1.0.3           # OpenAI 연동

📧 Messaging
├── Apache Kafka            # 도메인 이벤트 스트리밍
└── Spring Kafka            # Kafka 퍼블리셔·컨슈머 구현

💿 Storage &amp; External APIs
├── AWS S3 2.31.7           # 파일 스토리지
└── Jsoup 1.18.1            # HTML 파싱

📚 Documentation
├── Swagger/OpenAPI 3.0     # API 문서 자동화
└── Notion                  # 프로젝트 문서 및 협업 기록
└── Github Projects         # 일정 및 이슈 관리

🔧 Development Tools
├── IntelliJ IDEA           # 통합 개발 환경(IDE)
├── Git &amp; GitHub            # 버전 관리 및 협업
├── Discord                 # 팀 커뮤니케이션
└── Postman                 # API 테스트 도구

🚀 Infrastructure &amp; Deployment
├── AWS                     # 클라우드 인프라
└── Docker                  # 컨테이너 기반 실행·배포</code></pre><hr>
<h2 id="🛠️-트러블슈팅-사례">🛠️ 트러블슈팅 사례</h2>
<h3 id="1-피드날씨-연관관계-설계-오류">1) 피드–날씨 연관관계 설계 오류</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>피드 목록 조회 시 <code>weather_id</code>가 <code>null</code>인 레코드가 들어오면서, 프론트에서 렌더링이 실패했다.</li>
<li>weatherId가 null로 설정될 경우 서비스단에서 날씨 정보를 찾을 수 없다는 404에러가 발생했다.</li>
</ul>
</li>
<li><p><strong>원인</strong> </p>
<ul>
<li>초기 스키마에서 <code>weather_id</code>에 NOT NULL 제약을 걸지 않았다.</li>
<li>외래키 제약을 <code>ON DELETE SET NULL</code>로 설정해, 날씨 데이터가 삭제되면 피드의 <code>weather_id</code>가 <code>null</code>로 바뀌도록 설계했다.</li>
<li>서비스 단에서는 항상 유효한 날씨 정보가 있다고 가정하고 조회했다.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li><code>weather_id</code> 컬럼에 <strong>NOT NULL 제약</strong>을 추가했다.</li>
<li>외래키 제약 조건을 <strong><code>ON DELETE RESTRICT</code></strong>로 변경해, 참조 중인 날씨 데이터는 삭제되지 않도록 보호했다.</li>
</ul>
</li>
<li><p><strong>배운 점</strong><br>조회에 <strong>필수적인 연관관계</strong>는 애플리케이션이 아니라 <strong>DB 스키마에서 강하게 보장</strong>하는 것이 안전하다.<br>특히 <code>ON DELETE SET NULL</code>은 편리해 보이지만, 실제 비즈니스 요구사항과 맞지 않으면 오히려 장애의 원인이 될 수 있다.</p>
</li>
</ul>
<h3 id="2-피드-목록-조회---카운트-갱신-시-updatedat-미갱신">2) 피드 목록 조회 - <strong>카운트 갱신 시 <code>updatedAt</code> 미갱신</strong></h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>좋아요/댓글 카운트만 증가시키는 업데이트 후에도 <code>updated_at</code>이 갱신되지 않았다.</li>
</ul>
</li>
<li><p><strong>원인</strong>  </p>
<ul>
<li>카운트 갱신에 <strong>JPQL 벌크 UPDATE</strong>를 사용했다.</li>
<li>벌크 UPDATE는 영속성 컨텍스트를 거치지 않기 때문에, JPA Auditing(@LastModifiedDate)이 적용되지 않는다.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li>쿼리를 <strong>네이티브 UPDATE</strong>로 전환했다.</li>
<li><code>updated_at = CURRENT_TIMESTAMP</code>를 쿼리에서 명시적으로 업데이트하도록 변경했다.</li>
</ul>
</li>
<li><p><strong>배운 점</strong><br>대량 업데이트가 필요해 벌크 UPDATE를 사용할 때는,<br><strong>Auditing/엔티티 리스너가 동작하지 않는다는 점을 항상 염두</strong>에 두고 쿼리에서 필드를 갱신해야 한다.</p>
</li>
</ul>
<h3 id="3-commentrepositorytest-간헐적-실패---시간-의존-테스트">3) CommentRepositoryTest 간헐적 실패 - 시간 의존 테스트</h3>
<ul>
<li><p><strong>현상</strong> </p>
<ul>
<li>댓글 정렬/조회 관련 테스트가 로컬에서는 통과하지만, CI 환경이나 특정 시점에 간헐적으로 실패했다.</li>
</ul>
</li>
<li><p><strong>원인</strong>  </p>
<ul>
<li>테스트 데이터의 시간 값을 <code>Instant.now()</code>로 생성했다.</li>
<li>실행 타이밍에 따라 경계 조건(예: 동일 시각, 정렬 순서)이 달라지는 <strong>비결정적 테스트</strong>가 되었다.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li>테스트에서 사용하는 시간을 <code>Instant.parse(&quot;2025-01-01T00:00:00Z&quot;)</code>처럼 <strong>고정 값</strong>으로 치환하여 <strong>결정적 테스트</strong>를 보장했다.</li>
</ul>
</li>
<li><p><strong>배운 점</strong><br>테스트는 항상 <strong>결정적(deterministic)</strong>이어야 한다.<br>현재 시간, 랜덤 값에 직접 의존하는 코드는 설계 단계에서부터 테스트 가능성을 함께 고려해야 한다.</p>
</li>
</ul>
<h3 id="4-배포-환경에서-피드-조회-실패--es-localdatetime-컨버전-문제">4) 배포 환경에서 피드 조회 실패 – ES LocalDateTime 컨버전 문제</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li><p>운영 환경에서만 피드 조회 API가 다음 에러와 함께 500을 반환했다.</p>
<pre><code class="language-yaml">ConversionException: Unable to convert value &#39;1760968800000&#39; to java.time.LocalDateTime for property &#39;forecastedAt&#39;</code></pre>
</li>
</ul>
</li>
<li><p><strong>원인</strong></p>
<ul>
<li>Elasticsearch 문서의 <code>forecastedAt</code> 필드 타입은 <code>epoch_millis(Long)</code>이었다.</li>
<li>Spring Data Elasticsearch가 이를 도메인 객체의 LocalDateTime로 역직렬화할 때, 숫자 → LocalDateTime 변환 컨버터가 등록되어 있지 않았다.</li>
<li>DTO에 @JsonFormat으로 문자열 포맷을 강제하면서, 타입 변환 경로가 더 복잡해졌다.</li>
</ul>
</li>
<li><p><strong>해결</strong></p>
<ul>
<li>DTO에서 <code>@JsonFormat</code>을 제거해 문자열 포맷 강제를 해제했다.</li>
<li>Long ↔ LocalDateTime 변환용 Converter Bean을 정의하고,
특정 프로파일에만 적용되던 <code>@Profile</code>을 제거해 모든 프로파일에서 공통으로 등록되도록 했다.</li>
</ul>
</li>
<li><p><strong>배운 점</strong><br>검색 인프라(ES)의 타입과 애플리케이션 도메인 타입(LocalDateTime 등) 사이의 매핑은 <strong>명시적으로 설계</strong>해야 한다.<br>특히 테스트/운영 프로파일에 따라 Bean 구성이 달라지지 않도록 관리하는 것이 중요하다.</p>
</li>
</ul>
<h3 id="5-피드-삭제-후에도-검색-결과에-노출되던-문제">5) 피드 삭제 후에도 검색 결과에 노출되던 문제</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>피드를 삭제(soft delete)했는데도, ES 기반 피드 검색 결과에 계속 노출되었다.</li>
</ul>
</li>
<li><p><strong>원인</strong>  </p>
<ul>
<li>색인 시 ES 문서 <code>_id</code>를 DB <code>feed_id</code>가 아닌 자동 생성 ID로 사용했다.
→ <code>delete(id)</code> 호출 시 다른 문서를 바라보게 됨.</li>
<li>soft delete를 도입했지만 ES 검색 쿼리에 <code>is_deleted = false</code> 필터를 까먹고 넣지 않았다.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li>색인 시 ES 문서의 <code>_id</code>를 DB <code>feed_id</code>와 동일한 값으로 고정했다.</li>
<li>soft delete 시 DB뿐 아니라 ES 문서에도 <code>is_deleted = true</code>로 반영했다.</li>
<li>ES 검색 쿼리의 기본 조건에 <code>is_deleted = false</code> 필터를 강제했다.</li>
</ul>
</li>
<li><p><strong>배운 점</strong><br>RDB와 검색 인덱스를 함께 사용할 때는 ID 전략(PK와 _id)과 삭제 전략(soft/hard delete, 조회 필터)을 처음부터 일관성 있게 설계해야 한다.</p>
</li>
</ul>
<h3 id="6-nori-플러그인부트스트랩-타이밍-문제">6) Nori 플러그인/부트스트랩 타이밍 문제</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>초기 기동 시 한글 분석기 부재로 인덱스 생성/매핑이 실패하면서, 최초 기동 시 에러가 발생했다.</li>
</ul>
</li>
<li><p><strong>원인</strong>  </p>
<ul>
<li>Elasticsearch 컨테이너 기동 이후에 <code>analysis-nori</code> 플러그인을 설치하려 했다.</li>
<li>플러그인 설치 실패 시에도 컨테이너가 계속 떠 있어 문제를 조기에 인지하지 못했다.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li>컨테이너 빌드 단계에서 Nori 플러그인을 선 설치하도록 Dockerfile/스크립트를 수정했다.</li>
<li>플러그인 설치가 실패하면 빌드 자체를 실패시키도록 해, 문제를 빠르게 감지할 수 있게 했다.</li>
</ul>
</li>
<li><p><strong>배운 점</strong><br>인프라 의존성이 있는 플러그인은 빌드 타임에 강제하는 것이 전체 시스템 안정성에 유리하다.</p>
</li>
</ul>
<h3 id="7-의상-추천-엔진-품질-개선">7) 의상 추천 엔진 품질 개선</h3>
<h3 id="7-1-softmax-샘플링-품질-이슈">7-1) Softmax 샘플링 품질 이슈</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>점수가 비슷한 후보들이 많거나, 특정 후보만 극단적으로 높을 때 확률이 한 후보로 지나치게 쏠리거나 다양성이 저하되었다.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li>동점 구간은 균등 샘플링으로 처리해 불필요한 연산을 줄이고 공정성을 확보했다.</li>
<li>극단 분포가 감지될 때 로그를 남기도록 해, 품질 이슈를 조기에 파악할 수 있게 했다.</li>
</ul>
</li>
</ul>
<h3 id="7-2-최근-추천-아이템-반복-노출">7-2) 최근 추천 아이템 반복 노출</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>같은 사용자가 짧은 시간 안에 추천을 다시 받을 경우, 동일한 조합이 반복 노출됐다.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li>Redis에 카테고리별 <strong>최근 추천 아이템 목록을 TTL(30분)</strong>로 저장했다.</li>
<li>추천 후보군 생성 시, 해당 목록에 포함된 아이템을 우선 제외해 중복 노출을 줄였다.</li>
</ul>
</li>
</ul>
<h3 id="7-3-추천-임계값-미달-시-빈-리스트-반환">7-3) 추천 임계값 미달 시 빈 리스트 반환</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>사용자의 옷은 존재하지만, 점수 임계값을 넘는 후보가 하나도 없으면 <strong>빈 리스트를 반환</strong>했다.
→ 사용자 입장에서는 “추천이 없는 서비스”처럼 보이는 UX 문제.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li>점수 기반 추천 후보가 없을 경우, <strong>랜덤 추천 fallback</strong>을 추가했다.</li>
<li>각 부위별로 한 벌씩을 무작위로 선택해, 최소한의 추천 조합은 보장되도록 했다.</li>
</ul>
</li>
</ul>
<h3 id="7-4-사용자-의상이-등록되어-있지-않은-경우-404-반환">7-4) 사용자 의상이 등록되어 있지 않은 경우 404 반환</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>사용자가 옷을 한 벌도 등록하지 않은 상태에서 추천 API를 호출하면 404를 반환했다.</li>
</ul>
</li>
<li><p><strong>해결</strong></p>
<ul>
<li>HTTP 200 + 빈 리스트를 반환하도록 변경해, 클라이언트에서 더 자연스럽게 처리할 수 있도록 했다.</li>
</ul>
</li>
</ul>
<h3 id="8-oauth2--google-로그인-클라이언트-유형-설정-오류">8) OAuth2 – Google 로그인 클라이언트 유형 설정 오류</h3>
<ul>
<li><p><strong>현상</strong>  </p>
<ul>
<li>Google OAuth2 로그인 시도 시 인증 실패가 발생했다.</li>
</ul>
</li>
<li><p><strong>원인</strong>  </p>
<ul>
<li>Google Cloud Console에서 OAuth 클라이언트를 <strong>Desktop App 유형</strong>으로 생성했다.</li>
<li>Desktop App은 redirect URI를 등록할 수 없어, Spring Security OAuth2 클라이언트 설정과 맞지 않았다.</li>
</ul>
</li>
<li><p><strong>해결</strong>  </p>
<ul>
<li>클라이언트 유형을 <strong>Web application</strong>으로 새로 생성했다.</li>
<li>로컬/운영 환경별로 승인된 redirect URI를 등록해, 환경에 따라 올바른 콜백 URL로 인증이 이뤄지도록 했다.</li>
</ul>
</li>
</ul>
<hr>
<h2 id="📌-개선-계획">📌 개선 계획</h2>
<ul>
<li>로컬 및 운영 환경에서 분산 환경 적용</li>
<li>전체적인 UI 개선</li>
<li>추가 API 개발<ul>
<li>회원 탈퇴</li>
<li>인기 피드 랭킹</li>
<li>어드민 전체 공지</li>
</ul>
</li>
<li>꼭 필요한 배포환경만 남기고 비용 최소화</li>
<li>피드 목록 조회: <strong>Elasticsearch vs RDB</strong> 의사결정</li>
</ul>
<p>👉 프로젝트 종료 후 피드 목록 조회 RDB 기반 변경, 피드 물리 삭제, 논리 삭제 복구 기능, DM 대화방 생성 등의 리팩토링을 완료했다!🤩</p>
<hr>
<h2 id="💡-프로젝트를-통해-배운-점">💡 프로젝트를 통해 배운 점</h2>
<h3 id="✅-기술적-성과">✅ 기술적 성과</h3>
<ul>
<li><p><strong>검색/탐색 품질 고도화</strong></p>
<ul>
<li>Elasticsearch를 활용하여 피드 검색 기능을 개선했다.</li>
</ul>
</li>
<li><p><strong>추천 품질 향상</strong></p>
<ul>
<li>의상 추천 결과에 대해 LLM 기반 <strong>추천 이유 생성</strong>을 도입하고, 프롬프트를 표준화했다.</li>
<li>사용자가 “왜 이 조합을 추천받았는지” 이해할 수 있는 설명을 제공하면서, <strong>자연스러운 표현과 일관된 톤</strong>을 유지했다.</li>
</ul>
</li>
<li><p><strong>인증 신뢰성 확보 (OAuth2)</strong></p>
<ul>
<li>OAuth2 클라이언트 타입과 리디렉션 URI를 환경별로 정합성 있게 맞추어 설정했다.</li>
<li>잘못된 설정으로 인한 <strong>로그인 실패 케이스를 제거</strong>하고 인증 플로우를 안정화했다.</li>
</ul>
</li>
<li><p><strong>UI 일관성</strong></p>
<ul>
<li>색상·일러스트 재정비로 화면 간 <strong>시각적 일관성</strong>을 강화했다.</li>
</ul>
</li>
<li><p><strong>테스트 품질 향상</strong></p>
<ul>
<li>단위/통합 테스트를 꾸준히 추가해 <strong>테스트 커버리지 80% 이상</strong>을 달성했다.</li>
<li>리팩토링이나 신규 기능 추가 시에도 테스트를 기반으로 <strong>안정적으로 코드를 수정</strong>할 수 있었다.</li>
</ul>
</li>
<li><p><strong>프론트엔드까지 포함한 문제 해결 경험</strong></p>
<ul>
<li>백엔드 API 수정에 그치지 않고, 알림 목록 무한스크롤, DM 탭분리 등 프론트엔드 코드도 함께 수정했다.</li>
<li>화면/UI 문제를 “프론트 영역”으로만 넘기지 않고, <strong>엔드투엔드 관점에서 이슈를 추적하고 해결하는 풀스택 시야</strong>를 갖추게 되었다.</li>
</ul>
</li>
</ul>
<h3 id="🤝-비기술적-성과">🤝 비기술적 성과</h3>
<ul>
<li><p><strong>일정 가시화 &amp; 리스크 관리</strong></p>
<ul>
<li>스프린트 보드와 캘린더로 작업 현황을 가시화하고, 매일 팀 내에서 진행 상황을 공유했다.</li>
<li>팀원의 일정 변경이나 지연 가능성이 보이면 즉시 공유하고, <strong>데드라인 재협의·작업 재배정</strong>으로 리스크를 초기에 줄였다.</li>
</ul>
</li>
<li><p><strong>커뮤니케이션 &amp; 협업 방식</strong></p>
<ul>
<li>요구사항 변경, 버그, 설계 고민 등을 혼자 오래 붙잡지 않고, <strong>초기에 팀원과 공유해 함께 논의</strong>하는 습관을 들였다.</li>
<li>코드 리뷰를 통해 서로의 코드 스타일과 설계 의도를 맞춰 가며, <strong>피드백을 주고받는 문화를 유지</strong>했다.</li>
</ul>
</li>
</ul>
<h3 id="📌-교훈">📌 교훈</h3>
<ul>
<li><p><strong>새로운 기술은 필요할 때 도입하자</strong>
피드 검색 기능에 Elasticsearch를 도입해 보면서, 현재 사용량 기준에서는 과설계에 가까울 수 있다는 점을 체감했다.<br>새로운 기술을 도입할 때는 “왜 필요한지”, “우리 규모에 맞는지”를 먼저 검토하고 도입 범위를 정해야 한다는 교훈을 얻었다.</p>
</li>
<li><p><strong>LLM은 만능이 아니라, 적절하게 써야 한다</strong> 
추천 로직 전체를 LLM으로 처리했을 때는 평균 응답 시간이 4초 이상으로 느려져 실서비스에는 적합하지 않았다.<br>결국 <strong>추천 산출은 자체 알고리즘</strong>, LLM은 <strong>추천 이유 생성</strong>으로 역할을 분리해 성능과 사용자 경험을 모두 만족시킬 수 있었다.<br>이 경험을 통해, 무작정 LLM을 도입하기보다 <strong>성능·비용·사용 시점</strong>을 함께 고려해 역할을 설계하는 것이 중요하다는 것을 배웠다.</p>
</li>
<li><p><strong>일정은 “가시화 + 공유”로 관리해야 한다</strong><br>스프린트 보드와 캘린더로 일정을 가시화하고, 팀 내에서 진척 상황을 꾸준히 공유하자 지연이 발생했을 때도 작업 재분배를 훨씬 빠르고 효율적으로 할 수 있었다.<br>팀원의 일정 변경이나 지연 가능성이 보이면 즉시 공유하고, <strong>데드라인을 재협의하거나 작업을 재배정</strong>하는 방식이 프로젝트 리스크를 줄이는 데 큰 도움이 된다는 점을 배웠다.</p>
</li>
<li><p><strong>백엔드에만 머무르지 않고 프론트까지 챙길 때 완성도가 올라간다</strong><br>백엔드 기능 개발뿐만 아니라, 프론트엔드(UI·화면 흐름)까지 함께 다루면서 버그 대응 속도와 전체 서비스 완성도가 확실히 올라간다는 걸 느꼈다.<br>한쪽 영역에만 머무르기보다, 필요할 때는 프론트도 직접 보고 고칠 수 있는 역량이 협업과 운영에 큰 힘이 된다는 점을 깨달았다.</p>
</li>
<li><p><strong>“내 도메인”을 넘어서 전체 코드베이스를 이해하려는 태도가 필요하다</strong><br>다른 팀원의 도메인과 코드를 평소에도 함께 파악해 두어야, 작업 재배정이 필요할 때 맥락을 빠르게 이해하고 투입될 수 있음을 느꼈다.
협업 환경에서는 개인 담당 영역만 파는 것에서 끝나는 것이 아니라, <strong>서비스 전체 구조와 주요 흐름을 함께 이해하려는 자세</strong>가 중요하다는 걸 느꼈다.</p>
</li>
</ul>
<hr>
<h2 id="✍️-마무리">✍️ 마무리</h2>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/8d25d974-2d97-43d1-b52a-11ed49ddd6ca/image.jpeg" alt=""></p>
<p>이번 프로젝트에서는 Spring Security, Redis, Kafka, OAuth2, WebSocket, Elasticsearch 등 새로운 기술을 많이 도입해 볼 수 있었다.</p>
<p>무엇보다도 이번에도 너무 좋은 팀원들을 만나 재밌게 프로젝트를 진행할 수 있었다.🙌
각자 맡은 영역에서 열심히 하는 모습을 보며 나도 많이 자극을 받았고,<br>의사소통 방식이나 협업 태도에서도 배울 점이 정말 많았다.</p>
<p>이번 프로젝트를 시작하면서 “팀원들의 코드를 꼼꼼히 리뷰해 보자”는 나만의 목표를 세웠는데,<br>꽤 성실하게 지켜낸 것 같아 개인적으로는 만족스럽다.<br>(적어도 내 역량 내에서는 최선을 다했다고 자부한다..! 👀)</p>
<p>물론 아쉬운 부분들도 꽤나 있었다.
우선 PM 역할을 처음 맡아보면서 일정을 더 적극적으로 관리하지 못한 점이 아쉽다.<br>초반부터 팀원들의 일정까지 함께 고려해 전체 스케줄을 설계했어야 하는데,<br>“내 일만 제때 끝나면 되겠지” 하는 안일한 마음으로 시작했던 것 같다.😵‍💫</p>
<p>일정이 조금씩 밀리기 시작했을 때도,<br>“1~2일 정도 밀리는 건 괜찮지 않을까?”라는 생각에 일정 조정을 대부분 팀원들에게 맡겨 두었다. 
그때 강사님께서 <strong>데드라인을 팀과 함께 분명히 정하고, 지키기 어려운 경우에는 작업을 재분배하는 방식</strong>을 추천해주셨고, 이를 반영하면서 일정을 조금 더 타이트하게 관리할 수 있었다.<br>결과적으로는 작업을 재분배한 덕분에 일정 안에 마무리할 수 있었고, PM의 중요성을 다시 한 번 느끼게 되었다.<br>앞으로는 의견을 피력하거나 일정 관련해서 조율·재촉해야 할 때, 좀 더 분명하게 어필하고 적극적인 자세로 임해야겠다고 느꼈다.</p>
<p>의사소통 측면에서도 아쉬움이 남는다.<br>회의 시간에 수동적으로 따르는 태도를 보였던 순간들이 많았는데,<br>다음에는 회의 전에 미리 생각을 정리해 두고, 내 의견을 명확히 정리해 조금 더 주도적으로 참여해 보고 싶다.</p>
<p>개인적으로 이번 프로젝트에서 가장 좋았던 점은,
“프로젝트 끝났으니까 여기까지!”가 아니라 <strong>이후 리팩토링까지 함께 이어갔다는 것</strong>이다.<br>기간이 끝난 뒤에도 팀원들끼리 코어 타임을 정해서 리팩토링과 기능 보완을 이어갔고,
실제로 서비스를 계속 다듬어 가는 경험을 할 수 있었다.</p>
<p>이번 프로젝트는 나에게 꽤 의미 있는 프로젝트로 남을 것 같다.👍</p>
<p><del>실은 정말 회고 쓰기 귀찮았는데</del> 막상 쓰고 나니 정리도 되고, 추억도 새록새록 떠올라서 뿌듯하다. 이제 진짜 취준하러 가야지...💨</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SB 3기] 코드잇 중급 프로젝트 회고 : Monew]]></title>
            <link>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EC%A4%91%EA%B8%89-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-Monew</link>
            <guid>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EC%A4%91%EA%B8%89-%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%9A%8C%EA%B3%A0-Monew</guid>
            <pubDate>Tue, 09 Sep 2025 03:42:49 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/jh_devlog/post/96417467-fd46-4311-8948-e06d3d0d0c44/image.png" alt=""></p>
<h2 id="코드잇-중급-프로젝트-회고--monew">코드잇 중급 프로젝트 회고 : Monew</h2>
<p>고급 프로젝트을 앞두고, 그 전 진행했던 코드잇 중급 프로젝트를 정리해보려 한다..! 👀
초급 프로젝트에서는 기본기를 다지고 협업을 경험해보았다면, 이번에는 새로운 기술들을 도입하고 조금 더 깊은 개발 경험을 쌓을 수 있었다. 
배우는 게 정말 많았고, 재밌게 몰입할 수 있었던 프로젝트였다!👍</p>
<h2 id="🗂️-프로젝트-개요">🗂️ 프로젝트 개요</h2>
<blockquote>
<p><strong>모뉴(MoNew)</strong></p>
<p>모뉴는 다양한 뉴스 출처를 통합하여 관심사 기반으로 뉴스를 저장하는 뉴스 통합 관리 플랫폼입니다.
관심 있는 주제의 기사가 등록되면 실시간 알림을 받고, 댓글과 좋아요를 통해 다른 사용자와 의견을 나눌 수 있는 소셜 기능도 함께 제공됩니다.</p>
</blockquote>
<h3 id="✅-주요-기능">✅ 주요 기능</h3>
<ul>
<li>사용자 관리 (등록/조회/수정/삭제/로그인)</li>
<li>관심사 키워드 관리 (등록/조회/수정/삭제/목록/구독)</li>
<li>뉴스 기사 관리 (수집/조회/삭제/목록/백업 및 복구)</li>
<li>댓글 관리 (등록/조회/수정/삭제/목록/좋아요)</li>
<li>활동 내역 관리 (조회)</li>
<li>알림 관리 (등록/조회/수정/삭제/목록)</li>
</ul>
<h3 id="📌-기술-요구-사항">📌 기술 요구 사항</h3>
<ul>
<li>유효성 검사</li>
<li>커스텀 예외 처리</li>
<li>로그 관리</li>
<li>테스트 주도 개발 (TDD)</li>
<li>CI/CD 파이프라인 구축</li>
</ul>
<hr>
<h3 id="👩💻-나의-역할">👩‍💻 나의 역할</h3>
<h4 id="1️⃣-기능-개발">1️⃣ 기능 개발</h4>
<ul>
<li><strong>댓글 도메인</strong><ul>
<li>댓글 등록, 수정, 논리 삭제, 물리 삭제 기능</li>
<li>정렬 및 커서 기반 페이지네이션 목록 조회 기능</li>
<li>좋아요/좋아요 취소 기능</li>
</ul>
</li>
<li><strong>알림 도메인</strong><ul>
<li>좋아요 알림 등록 기능</li>
<li>전체 알림 확인/개별 알림 확인 기능</li>
</ul>
</li>
</ul>
<h4 id="2️⃣-개발-외-역할">2️⃣ 개발 외 역할</h4>
<ul>
<li><strong>Jira ↔ Git 연동</strong><ul>
<li>Jira → Git : Jira 백로그 이슈를 GitHub Issue로 자동 등록 (수동 트리거버튼 활용)</li>
<li>Git → Jira : GitHub 이슈 close 시 Jira 이슈 자동 완료 처리</li>
</ul>
</li>
<li><strong>CI 파이프라인 구축 (GitHub Actions)</strong><ul>
<li>PR 생성 시 자동 테스트 및 커버리지 리포트 실행</li>
<li>PR 댓글에 테스트 커버리지 요약 자동 추가</li>
</ul>
</li>
<li><strong>CD 파이프라인 구축 (GitHub Actions)</strong><ul>
<li>Docker 이미지 ECR 빌드 -&gt; AWS ECR 푸시</li>
<li>ECS 서비스 자동 배포 구현</li>
</ul>
</li>
</ul>
<h3 id="🔗-깃허브-링크">🔗 깃허브 링크</h3>
<ul>
<li><a href="https://github.com/sb3-monew-team1/sb03-monew-team1">Monew GitHub Repository</a></li>
</ul>
<hr>
<h2 id="📚-개발-환경-및-사용-스택">📚 개발 환경 및 사용 스택</h2>
<pre><code>⚙️ Backend Stack
📦 Framework
├── Spring Boot 3.x          # 메인 애플리케이션 프레임워크
├── Spring Data JPA          # ORM 및 데이터 접근
├── Spring Batch             # 대용량 배치 처리
└── Gradle                   # 빌드 및 의존성 관리 도구


🗄️ Database
├── H2 Database             # 개발/테스트용 In-memory DB
├── PostgreSQL              # 운영 환경 RDBMS
└── MongoDB                 # 문서 지향 NoSQL DB

📚 Documentation
├── Swagger/OpenAPI 3.0     # API 문서 자동화
└── Notion                  # 프로젝트 문서 및 협업 기록
└── Jira                    # 일정 및 이슈 관리

🔧 Development Tools
├── IntelliJ IDEA           # 통합 개발 환경(IDE)
├── Git &amp; GitHub            # 버전 관리 및 협업
├── Discord                 # 팀 커뮤니케이션
└── Postman                 # API 테스트 도구

🚀 Deployment &amp; Monitoring
├── AWS                     # 클라우드 인프라
├── Docker                  # 컨테이너 기반 배포
├── Grafana                 # 모니터링 시각화
└── Prometheus              # 메트릭 수집 및 모니터링</code></pre><hr>
<h2 id="🛠️-트러블슈팅-사례">🛠️ 트러블슈팅 사례</h2>
<h3 id="1-댓글-목록-조회---n1-쿼리-문제">1) 댓글 목록 조회 - <strong>N+1 쿼리 문제</strong></h3>
<ul>
<li><strong>문제</strong>: 댓글 조회 시 각 댓글마다 좋아요 여부를 확인하면서 N+1 문제가 발생</li>
<li><strong>해결</strong>: 댓글 ID를 한 번에 수집 → 좋아요 여부를 한 번의 쿼리로 조회 → 매핑 처리</li>
<li><strong>결과</strong>: 쿼리 수 <code>N+1</code> → <code>2</code>로 최적화</li>
</ul>
<h3 id="2-댓글-논리-삭제---where-필터링의-한계">2) 댓글 논리 삭제 - <code>@Where</code> 필터링의 한계</h3>
<ul>
<li><strong>문제</strong>: 엔티티 단에서 <code>@Where</code>로 <code>is_deleted=false</code>를 필터링 → 모든 조회에 일괄 적용되어 유연성 부족</li>
<li><strong>해결</strong>: <code>@Where</code> 제거 후 QueryDSL, JPA 메서드 기반으로 조건을 명시적으로 관리</li>
<li><strong>결과</strong>: 조회 조건을 상황에 맞게 제어 가능, Hibernate 의존도 감소</li>
</ul>
<h3 id="3-데이터-시더-실행---낙관적-락-충돌">3) 데이터 시더 실행 - 낙관적 락 충돌</h3>
<ul>
<li><strong>문제</strong>: 동일한 엔티티(<code>Article</code>)를 반복 수정하면서 <code>StaleObjectStateException</code> 발생</li>
<li><strong>해결</strong>: Seeder 클래스 간 실행 순서를 명시적으로 보장하여 의존 관계에 맞게 순차 실행</li>
<li><strong>결과</strong>: 충돌 제거 및 안정적인 초기 데이터 세팅 확보</li>
</ul>
<h3 id="4-cd-파이프라인---task-definitionjson-인식-불가">4) CD 파이프라인 - <code>task-definition.json</code> 인식 불가</h3>
<ul>
<li><strong>문제</strong>: GitHub Actions의 Job 간 독립 실행 환경 때문에 <code>deploy</code> Job에서 파일을 찾지 못함</li>
<li><strong>해결</strong>: <code>deploy</code> Job에도 <code>checkout</code> 단계 추가</li>
<li><strong>결과</strong>: CD 파이프라인 정상 동작 및 자동 배포 성공</li>
</ul>
<h3 id="5-jpql---current_timestamp-타입-불일치">5) JPQL - <code>CURRENT_TIMESTAMP</code> 타입 불일치</h3>
<ul>
<li><strong>문제</strong>: <code>Instant</code> 필드에 <code>CURRENT_TIMESTAMP</code>를 매핑하면서 H2에서 타입 오류 발생</li>
<li><strong>해결</strong>: Native Query로 수정하여 DB 환경에 관계없이 일관 처리</li>
<li><strong>결과</strong>: 테스트/운영 환경 모두에서 정상 동작</li>
</ul>
<hr>
<h2 id="💡-프로젝트를-통해-배운-점">💡 프로젝트를 통해 배운 점</h2>
<h3 id="✅-기술적-성과">✅ 기술적 성과</h3>
<ul>
<li><code>Spring Boot</code>, <code>JPA</code>, <code>QueryDSL</code>,<code>Spring Batch</code>, <code>PostgreSQL</code>, <code>Docker</code>, <code>AWS</code> 등 다양한 기술 스택 활용</li>
<li>QueryDSL 기반 커서 페이지네이션 도입</li>
<li>GitHub Actions 기반 CI/CD 구축 -&gt; 빌드/배포 과정을 자동화하여 배포 시간 단축 및 안정적 배포 환경 확보</li>
<li>로드밸런서(ALB) + 롤링 업데이트 기반 <strong>무중단 배포</strong> 경험</li>
<li>댓글 서비스 테스트 커버리지 달성<ul>
<li>서비스 계층: 라인 98% / 브랜치 90%</li>
<li>레포지토리 계층: 라인 100% / 브랜치 92%</li>
</ul>
</li>
<li>이벤트 리스너 기반 알림 처리 구현</li>
</ul>
<h3 id="🤝-비기술적-성과">🤝 비기술적 성과</h3>
<ul>
<li>Jira를 통한 스프린트/이슈 관리 → 팀 내 작업 가시성 확보</li>
<li>Git ↔ Jira 연동으로 워크플로우 자동화</li>
<li>체크리스트 기반 PR 템플릿 + 피드백 반영 → 리뷰 효율성과 코드 품질 향상</li>
</ul>
<hr>
<h2 id="📌-개선-계획">📌 개선 계획</h2>
<ul>
<li>댓글 물리 삭제 시 관리자 권한 검증 추가 (Spring Security)</li>
<li>기사별 댓글 정렬 옵션 추가 (최신순 / 좋아요순) -&gt; UX 개선</li>
</ul>
<hr>
<h2 id="✍️-마무리">✍️ 마무리</h2>
<p>이번 프로젝트는 여러모로 새롭게 경험해보는 게 많아서 재미있었다.😊</p>
<p>특히 테스트 주도 개발(TDD), CI/CD 자동화, 무중단 배포 같은 실무 기술들을 직접 경험해볼 수 있었는데, 이론으로만 접했을 때는 잘 와닿지 않던 개념들이 실제로 구현해보니 훨씬 이해가 잘됐다.💡 (TDD는 정말 쉽지 않은 녀석이었다...🥹)</p>
<p>새로운 툴도 다양하게 활용해봤다. 멘토님 추천으로 처음 도입한 <strong>Jira는</strong> 작업 흐름을 한눈에 파악할 수 있어서 협업에 유용했고, AI 코드 리뷰 툴인 <strong>CodeRabbit은</strong> PR 요약, 수정 제안, 다이어그램 생성까지 자동으로 해줘서 코드 리뷰가 한결 편해졌다. 덕분에 코드 품질도 더 좋아졌다. (코드래빗 is God🧎‍♀️)</p>
<p>무엇보다도 기억에 남는 건 <strong>팀원들과의 협업</strong>이었다.🤝 
이슈가 생겼을 때 다같이 고민하고 해결해 나가는 과정이 인상 깊었고, 새벽이나 주말에도 적극적으로 참여하는 팀원들을 보며 자극도 많이 받았다.🔥 
서로 새로운 기술이나 지식을 적극적으로 공유하는 분위기 속에서 배우는 것도 많았고, 팀 분위기가 좋아서 프로젝트 기간 내내 즐겁게 개발할 수 있었다. 🙌 (1조 할머니 보쌈팀 최고...👍)</p>
<p>아쉬운 점이 있다면, 팀원들의 모든 코드 리뷰를 꼼꼼히 해보며 코드들을 전부 파악하고 싶었는데 그러지는 못한 것 같아 조금 아쉽다.</p>
<p>이제 고급 프로젝트를 앞두고 있는데, 중급 프로젝트의 경험을 바탕으로 더욱 수월하게 진행할 수 있을 것 같아 기대가 된다. 정말 많이 배우고 성장할 수 있었던 프로젝트였다!☺️</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SB 3기] 코드잇 스프린트 위클리페이퍼 17주차]]></title>
            <link>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-17%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-17%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 31 Aug 2025 13:11:09 GMT</pubDate>
            <description><![CDATA[<h3 id="q1-tcpip-4계층-모델과-osi-7계층-모델에-대해-각각-설명하고-두-모델을-비교해보세요">Q1. TCP/IP 4계층 모델과 OSI 7계층 모델에 대해 각각 설명하고, 두 모델을 비교해보세요.</h3>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/ff8f0fba-acee-4c0b-8957-27056dc989be/image.png" alt="">
<sub>이미지 출처 : cheapsslsecurity</sub></p>
<hr>
<h3 id="✅-tcpip-4계층-모델">✅ TCP/IP 4계층 모델</h3>
<ul>
<li>인터넷 표준인 <strong>TCP/IP 프로토콜</strong>을 계층화한 실용적인 네트워크 모델</li>
<li>현재 실제 인터넷 통신에서 사용되는 구조</li>
</ul>
<h4 id="1-네트워크-연결-계층-network-access-layer">1. 네트워크 연결 계층 (Network Access Layer)</h4>
<ul>
<li>물리적인 데이터 전송 담당 (LAN, WAN, Ethernet, Wi-Fi 등)</li>
<li><strong>프레임 단위</strong> 통신</li>
<li>예: Ethernet, Wi-Fi, ARP</li>
</ul>
<h4 id="2-인터넷-계층-internet-layer">2. 인터넷 계층 (Internet Layer)</h4>
<ul>
<li>출발지 -&gt; 목적지까지 I<strong>P 주소 기반 패킷 전달(라우팅)</strong></li>
<li><strong>패킷 단위</strong> 통신</li>
<li>예: IP, ICMP, ARP, RARP</li>
</ul>
<h4 id="3-전송-계층-transport-layer">3. 전송 계층 (Transport Layer)</h4>
<ul>
<li>송수신 프로세스 간 데이터 전송 제어</li>
<li><strong>TCP(연결 지향, 신뢰성 보장) / UDP(비연결, 빠른 전송)</strong></li>
<li><strong>세그먼트 단위</strong> 통신</li>
<li>예: TCP, UDP</li>
</ul>
<h4 id="4-애플리케이션-계층-application-layer">4. 애플리케이션 계층 (Application Layer)</h4>
<ul>
<li>사용자가 직접 접하는 서비스 제공</li>
<li>예: HTTP, FTP, SMTP, DNS</li>
</ul>
<hr>
<h3 id="✅-osi-7계층-모델">✅ OSI 7계층 모델</h3>
<ul>
<li>국제 표준화 기구(ISO)에서 정의한 네트워크 표준 참조 모델</li>
<li><strong>네트워크 통신 과정을 7단계로 구분</strong></li>
</ul>
<h4 id="1-물리-계층-physical-layer">1. 물리 계층 (Physical Layer)</h4>
<ul>
<li>역할 : 네트워크의 가장 하위 계층으로, 데이터를 전기 신호(0과 1) 형태로 주고받음</li>
<li>목적 : 전송 매체를 통해 신호를 <strong>그대로 잘 전달하는</strong> 것</li>
<li>단위: 비트(Bit)</li>
<li>장비 예시 : 허브, 리피터, 케이블, 커넥터</li>
</ul>
<h4 id="2-데이터-링크-계층-data-link-layer">2. 데이터 링크 계층 (Data Link Layer)</h4>
<ul>
<li>역할 : 물리 계층에서 받은 신호를 <strong>프레임(Frame)</strong> 단위로 묶어 처리</li>
<li>목적 : 오류 검출 및 수정, 출발지/목적지 <strong>MAC 주소 확인</strong></li>
<li>특징 : 스위치는 MAC 주소 기반으로 올바른 포트를 선택해 전달</li>
<li>단위 : 프레임(Frame)</li>
<li>장비 예시 : 스위치, 네트워크 인터페이스 카드(NIC)</li>
</ul>
<h4 id="3-네트워크-계층-network-layer">3. 네트워크 계층 (Network Layer)</h4>
<ul>
<li>역할 : 목적지까지의 경로를 설정하고 데이터 전달</li>
<li>특징 : IP 주소(논리 주소)를 사용하여 라우팅 수행</li>
<li>단위 : 패킷(Packet)</li>
<li>장비 예시 : 라우터</li>
</ul>
<h4 id="4-전송-계층-transport-layer">4. 전송 계층 (Transport Layer)</h4>
<ul>
<li>역할 : 애플리케이션 간 데이터가 <strong>정상적으로 전달되도록 보장</strong></li>
<li>기능 : 데이터 분할, 순서 제어, 오류 복구<ul>
<li>TCP : 신뢰성 있는 연결 (세그먼트 단위) </li>
<li>UDP : 빠른 전송, 비연결성 (데이터그램 단위)</li>
</ul>
</li>
<li>단위 : 세그먼트(TCP), 데이터그램(UDP)</li>
<li>장비 예시 : 방화벽, 로드 밸런서</li>
</ul>
<h4 id="5-세션-계층-session-layer">5. 세션 계층 (Session Layer)</h4>
<ul>
<li>역할 : 두 애플리케이션 간의 <strong>연결(세션) 생성, 유지, 종료 관리</strong></li>
<li>기능 : 통신 중단 시 에러 복구 및 재전송</li>
</ul>
<h4 id="6-표현-계층-presentation-layer">6. 표현 계층 (Presentation Layer)</h4>
<ul>
<li>역할 : 데이터의 표현 형식 통일</li>
<li>기능 : 암호화, 압축, 인코딩/디코딩</li>
<li>예시: JPEG, GIF, MIME 인코딩</li>
</ul>
<h4 id="7-응용-계층-application-layer">7. 응용 계층 (Application Layer)</h4>
<ul>
<li>역할 : 사용자가 직접 접근하는 네트워크 서비스 제공</li>
<li>특징 : UI와 가장 밀접한 계층, 다양한 애플리케이션 프로토콜 포함</li>
<li>대표 프로토콜 : TTP, FTP, SMTP, TELNET</li>
</ul>
<h4 id="📌-계층-구분">📌 계층 구분</h4>
<ul>
<li>데이터 플로 계층 (1~4, 하위 계층) : 데이터 전송에 초점</li>
<li>애플리케이션 계층 (5~7, 상위 계층) : 데이터 해석과 표현에 초점</li>
</ul>
<h3 id="🆚-tcpip-4계층과-osi-7계층-비교">🆚 TCP/IP 4계층과 OSI 7계층 비교</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>TCP/IP 4계층</th>
<th>OSI 7계층</th>
</tr>
</thead>
<tbody><tr>
<td><strong>계층 수</strong></td>
<td>4계층</td>
<td>7계층</td>
</tr>
<tr>
<td><strong>기준</strong></td>
<td>실제 인터넷 프로토콜 설계 기반</td>
<td>이론적 표준 모델</td>
</tr>
<tr>
<td><strong>구조</strong></td>
<td>단순하고 실용적</td>
<td>세분화·개념적</td>
</tr>
<tr>
<td><strong>매핑 관계</strong></td>
<td>애플리케이션 계층 ↔ 응용·표현·세션 계층<br>전송 계층 ↔ 전송 계층<br>인터넷 계층 ↔ 네트워크 계층<br>네트워크 연결 계층 ↔ 데이터링크·물리 계층</td>
<td>계층별로 독립적으로 구분</td>
</tr>
<tr>
<td><strong>활용</strong></td>
<td>실제 인터넷 통신에 사용</td>
<td>네트워크 개념 학습·분석용</td>
</tr>
</tbody></table>
<hr>
<h3 id="q2-전송-계층에서-tcp와-udp의-차이점은-무엇이며-각각-어떤-상황에서-사용하는-것이-적절한가요">Q2. 전송 계층에서 TCP와 UDP의 차이점은 무엇이며, 각각 어떤 상황에서 사용하는 것이 적절한가요?</h3>
<p><img src="https://velog.velcdn.com/images/jh_devlog/post/7e86fd48-9d4c-4a09-a21a-2a40b7b5db06/image.png" alt="">
<sub>이미지 출처 : cheapsslsecurity</sub></p>
<hr>
<h3 id="✅-tcp와-udp란">✅ TCP와 UDP란?</h3>
<h4 id="tcp-transmission-control-protocol">TCP (Transmission Control Protocol)</h4>
<ul>
<li><strong>연결 지향형 프로토콜</strong> -&gt; 송·수신 측이 먼저 연결(3-way handshake)을 맺은 후 데이터 전송</li>
<li>데이터가 순서대로 정확하게 도착하도록 <strong>흐름 제어, 오류 제어, 재전송</strong> 제공</li>
<li>신뢰성이 높지만 속도가 상대적으로 느리고, 헤더 크기가 크다.</li>
<li>주로 <strong>유니캐스트(Unicast, 1:1 통신)</strong> 기반으로 동작하며, <strong>전이중(Full Duplex)</strong> 통신 지원</li>
</ul>
<h4 id="udp-user-datagram-protocol">UDP (User Datagram Protocol)</h4>
<ul>
<li><strong>비연결형 프로토콜</strong> -&gt; 별도의 연결 과정 없이 빠르게 데이터 전송</li>
<li><strong>데이터그램 단위로 처리</strong>, 순서 보장·오류 복구 없음 </li>
<li>오버헤드가 적어 속도는 빠르지만, 신뢰성은 낮다.</li>
<li><strong>유니캐스트(1:1), 멀티캐스트(1:N), 브로드캐스트(1:모두)</strong> 모두 지원</li>
<li>기본적으로 단방향이지만, 응용에 따라 양방향(반이중/전이중)도 가능  </li>
</ul>
<hr>
<h3 id="🆚-차이점">🆚 차이점</h3>
<table>
<thead>
<tr>
<th>구분</th>
<th>TCP (Transmission Control Protocol)</th>
<th>UDP (User Datagram Protocol)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>연결 방식</strong></td>
<td>연결 지향형 (3-way handshake)</td>
<td>비연결형</td>
</tr>
<tr>
<td><strong>신뢰성</strong></td>
<td>전송 보장 (순서·오류 제어)</td>
<td>보장하지 않음 (손실 가능)</td>
</tr>
<tr>
<td><strong>속도</strong></td>
<td>상대적으로 느림</td>
<td>빠름</td>
</tr>
<tr>
<td><strong>단위</strong></td>
<td>세그먼트 (Segment)</td>
<td>데이터그램 (Datagram)</td>
</tr>
<tr>
<td><strong>오버헤드</strong></td>
<td>큼 (헤더 20바이트 이상)</td>
<td>작음 (헤더 8바이트)</td>
</tr>
<tr>
<td><strong>통신 형태</strong></td>
<td>유니캐스트</td>
<td>유니캐스트, 멀티캐스트, 브로드캐스트</td>
</tr>
<tr>
<td><strong>전송 모드</strong></td>
<td>전이중(양방향 동시 통신)</td>
<td>단방향 위주 (응용에 따라 양방향도 가능)</td>
</tr>
</tbody></table>
<hr>
<h3 id="👍-적절한-사용-시점">👍 적절한 사용 시점</h3>
<ul>
<li><p>TCP: 신뢰성과 정확성이 중요한 경우</p>
<ul>
<li>웹 브라우징(HTTP/HTTPS)</li>
<li>이메일(SMTP/IMAP)</li>
<li>파일 전송(FTP)</li>
</ul>
</li>
<li><p>UDP: 지연이 적고 빠른 전송이 중요한 경우</p>
<ul>
<li>실시간 화상 통화 </li>
<li>온라인 게임 </li>
<li>실시간 스트리밍 서비스</li>
<li>DNS 요청</li>
</ul>
</li>
</ul>
<hr>
<h3 id="📄-참고-문서">📄 참고 문서</h3>
<ul>
<li><a href="https://westahn.com/osi-4-%EA%B3%84%EC%B8%B5%EC%9D%B4%EB%9E%80/">OSI 4 계층이란?</a></li>
<li><a href="https://cheapsslsecurity.com/blog/what-is-the-tcp-model-an-exploration-of-tcp-ip-layers/?utm_source=chatgpt.com">What Is the TCP Model? An Exploration of TCP/IP Layers</a></li>
<li><a href="https://www.wallarm.com/what/transmission-control-protocol-tcp">Transmission control protocol - TCP</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SB 3기] 코드잇 스프린트 위클리페이퍼 16주차]]></title>
            <link>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-16%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-16%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 24 Aug 2025 14:07:53 GMT</pubDate>
            <description><![CDATA[<h3 id="q1-spring-cache에서-cacheable-cacheput-cacheevict의-차이점과-각각을-어떤-상황에서-사용하는-것이-적절한지-설명해주세요">Q1. Spring Cache에서 @Cacheable, @CachePut, @CacheEvict의 차이점과 각각을 어떤 상황에서 사용하는 것이 적절한지 설명해주세요.</h3>
<hr>
<h4 id="✅-spring-cache란">✅ Spring Cache란?</h4>
<ul>
<li>Spring Cache는 반복적인 DB 조회나 계산 비용이 큰 메서드 실행 결과를 캐시에 저장하고 재활용할 수 있도록 도와주는 <strong>캐시 추상화 레이어</strong>이다.</li>
<li>어노테이션을 통해 <strong>선언적으로 캐시를 관리</strong>할 수 있다.</li>
<li>사용 전 <code>@EnableCaching</code> 설정으로 <strong>캐시 기능을 활성화</strong>해야 한다.</li>
<li>주요 어노테이션으로는 <code>@Cacheable</code>, <code>@CachePut</code>, <code>@CacheEvict</code>가 있다.</li>
</ul>
<blockquote>
<p>💡 Tip</p>
</blockquote>
<ul>
<li>Spring Cache는 <strong>AOP 프록시 기반</strong>이라 같은 클래스 내에서 자기 자신 메서드를 호출할 땐 적용되지 않는다. → 자기 자신을 주입받아 호출하거나, 캐시 적용 메서드를 별도 Bean으로 분리하여 해결</li>
<li>Spring Cache는 TTL, 만료 정책, 최대 크기 설정을 직접 제공하지 않는다. → 구현체(Caffeine/Redis 등)에서 설정해야 함</li>
</ul>
<hr>
<h4 id="✅-cacheable">✅ @Cacheable</h4>
<ul>
<li>캐시 조회 후 히트되는게 없으면 메서드를 실행한다. -&gt; 메서드 실행 결과를 <strong>캐시에 저장</strong>한다.</li>
<li>캐시가 히트되면 <strong>메서드를 실행하지 않고 캐시 값을 반환</strong>한다.</li>
<li>특징<ul>
<li>조건부 캐싱 지원 (<code>condition</code>, <code>unless</code>)</li>
<li>캐시 키 커스터마이징 가능 (<code>key</code> 속성에 SpEL 활용)</li>
<li>null 결과는 캐싱하지 않도록 설정 가능 (<code>unless = &quot;#result == null&quot;</code>)</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Service
public class MenuService {

    @Cacheable(value = &quot;menus&quot;, key = &quot;#id&quot;, unless = &quot;#result == null&quot;)
    public Menu getMenu(Long id) {
        return menuRepository.findById(id)
            .orElseThrow(() -&gt; new MenuNotFoundException(id));
    }
}</code></pre>
<hr>
<h4 id="✅-cacheput">✅ @CachePut</h4>
<ul>
<li><strong>항상 메서드를 실행</strong>하고 결과를 <strong>캐시에 갱신(덮어쓰기)</strong>한다.</li>
<li>조회시 사용한 <code>@Cacheable</code>과 키를 동일하게 지정해야 한다. (캐시 일관성 유지)</li>
<li>기본적으로 트랜잭션이 커밋 후 반영된다.</li>
</ul>
<pre><code class="language-java">@Service
public class MenuService {

    @CachePut(value = &quot;menus&quot;, key = &quot;#menu.id&quot;)
    public Menu updateMenu(Menu menu) {
        Menu updatedMenu = menuRepository.save(menu);
        return updatedMenu;
    }
}</code></pre>
<hr>
<h4 id="✅-cacheevict">✅ @CacheEvict</h4>
<ul>
<li><strong>특정 캐시 항목을 제거</strong>하거나 <strong>전체 캐시 무효화</strong> 역할을 한다. </li>
<li>기본적으로 트랜잭션이 커밋 후 반영된다.</li>
<li><strong>주요 속성</strong><ul>
<li><code>allEntries = true</code>: 캐시 전체 삭제</li>
<li><code>beforeInvocation</code>: 메서드 실행 전 캐시 제거 여부로, true로 설정하면 <strong>예외 발생 시에도 캐시가 제거됨</strong> (기본값 : <code>false</code>) </li>
</ul>
</li>
</ul>
<pre><code class="language-java">// 특정 캐시 항목 제거
@CacheEvict(value = &quot;menus&quot;, key = &quot;#id&quot;)
@Transactional
public void deleteMenu(Long id) {
    menuRepository.deleteById(id);
}

// 모든 캐시 항목 제거
@CacheEvict(value = &quot;menus&quot;, allEntries = true)
@Transactional
public void clearAllMenuCache() {
    menuRepository.bulkUpdateStatus();
}

// 예외가 발생해도 캐시 무효화
@CacheEvict(value = &quot;menus&quot;, allEntries = true, beforeInvocation = true)
public void clearAllMenuCacheForce() {
    ...
}
</code></pre>
<hr>
<h4 id="🆚-차이점">🆚 차이점</h4>
<table>
<thead>
<tr>
<th>어노테이션</th>
<th>메서드 실행</th>
<th>캐시 동작</th>
</tr>
</thead>
<tbody><tr>
<td><code>@Cacheable</code></td>
<td>캐시 히트 시 생략</td>
<td>캐시 미스 시 저장</td>
</tr>
<tr>
<td><code>@CachePut</code></td>
<td>항상 실행</td>
<td>항상 캐시를 갱신(덮어쓰기)</td>
</tr>
<tr>
<td><code>@CacheEvict</code></td>
<td>실행 여부와 무관</td>
<td>특정 키 또는 전체 캐시 제거</td>
</tr>
</tbody></table>
<hr>
<h4 id="📌-활용-예시">📌 활용 예시</h4>
<ul>
<li><code>@Cacheable</code> : 읽기 비중이 크고 자주 변경되지 않는 데이터  <ul>
<li>ex. 상품 상세 보기, 설정 값 조회</li>
</ul>
</li>
<li><code>@CachePut</code> : 최신 상태를 즉시 캐시에 반영해야 하는 수정/생성 작업<ul>
<li>ex. 상품 수정, 신규 등록 시 캐시 갱신</li>
</ul>
</li>
<li><code>@CacheEvict</code> : 데이터 삭제/벌크 변경 후 캐시 무효화<ul>
<li>ex. 상품 삭제, 배치로 대량 상태 변경 후 캐시 초기화</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 <code>@Caching</code>  : 한 메서드에서 여러 캐시 동작을 조합하여 사용할 수 있다.</p>
</blockquote>
<pre><code class="language-java">@Caching(
    put = { @CachePut(value = &quot;menus&quot;, key = &quot;#menu.id&quot;) },
    evict = { @CacheEvict(value = &quot;menuList&quot;, allEntries = true) }
)
public Menu updateAndInvalidateList(Menu menu) {
    return menuRepository.save(menu);
}</code></pre>
<hr>
<h3 id="q2-로컬-캐시와-분산-캐시의-개념-차이와-각각의-장단점-그리고-실무에서-어떤-기준으로-선택해야-하는지-설명해주세요">Q2. 로컬 캐시와 분산 캐시의 개념 차이와 각각의 장단점, 그리고 실무에서 어떤 기준으로 선택해야 하는지 설명해주세요.</h3>
<hr>
<h4 id="✅-캐시cache란">✅ 캐시(Cache)란?</h4>
<ul>
<li>자주 사용하는 데이터를 빠르게 가져오기 위해 임시 저장소에 저장하는 기술이다.</li>
<li><strong>핵심 원리</strong><ul>
<li>시간 지역성(Temporal Locality) : 최근 접근한 데이터는 다시 접근될 가능성이 높다.</li>
<li>공간 지역성(Spatial Locality) : 접근한 데이터 근처의 데이터도 접근될 가능성이 높다.</li>
</ul>
</li>
</ul>
<hr>
<h4 id="✅-로컬-캐시-local-cache">✅ 로컬 캐시 (Local Cache)</h4>
<ul>
<li>애플리케이션과 같은 <strong>JVM 메모리 공간</strong>에서 동작하는 캐시이다.</li>
<li>대표적으로 Caffeine, Guava 등이 있다.</li>
<li>특징<ul>
<li><strong>네트워크를 거치지 않고</strong> 메모리에 직접 접근 -&gt; 가장 빠름</li>
<li>네트워크 장애와 무관하게 동작</li>
<li>JVM 힙 메모리에 의존 -&gt; <strong>메모리 크기 제약</strong></li>
<li>다중 서버 환경에서 <strong>서버 간 캐시 불일치</strong> 발생</li>
<li>애플리케이션 재시작 시 캐시 데이터 손실</li>
</ul>
</li>
</ul>
<hr>
<h4 id="✅-분산-캐시-distributed-cache">✅ 분산 캐시 (Distributed Cache)</h4>
<ul>
<li><strong>별도의 캐시 서버</strong>에 데이터를 저장하고 여러 서버 인스턴스가 공유한다.</li>
<li>대표적으로 Redis, Memcached 등이 있다.<ul>
<li>Redis는 영속화 옵션 제공 -&gt; 보조 데이터 저장소로 활용 가능</li>
</ul>
</li>
<li>특징<ul>
<li><strong>네트워크를 통해 접근</strong> -&gt; 로컬 캐시보다 상대적으로 느림</li>
<li>여러 서버가 <strong>동일한 데이터 공유</strong> -&gt; 다중 서버 환경에 적합</li>
<li>캐시 서버는 독립적으로 <strong>확장/클러스터링</strong> 가능</li>
<li><strong>네트워크 지연</strong> 및 <strong>추가 인프라 비용</strong> 발생</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 클러스터링(Clustering) : 여러 캐시 서버를 하나의 클러스터(집합)로 묶어 부하 분산과 고가용성을 제공하는 기술</p>
</blockquote>
<hr>
<h4 id="🆚-장단점">🆚 장단점</h4>
<table>
<thead>
<tr>
<th>구분</th>
<th>로컬 캐시(Local Cache)</th>
<th>분산 캐시(Distributed Cache)</th>
</tr>
</thead>
<tbody><tr>
<td><strong>속도</strong></td>
<td>매우 빠름</td>
<td>상대적으로 느림</td>
</tr>
<tr>
<td><strong>구현 난이도</strong></td>
<td>단순 (라이브러리 추가)</td>
<td>별도 캐시 서버 구축 및 운영 필요</td>
</tr>
<tr>
<td><strong>일관성</strong></td>
<td>서버별 불일치 발생 가능</td>
<td>여러 서버가 데이터 공유 -&gt; 일관성 ↑</td>
</tr>
<tr>
<td><strong>확장성</strong></td>
<td>서버 인스턴스마다 캐시 중복</td>
<td>캐시 서버 확장/클러스터링으로 처리량 증가</td>
</tr>
<tr>
<td><strong>장애 대응</strong></td>
<td>앱 재시작 시 데이터 손실</td>
<td>복제/클러스터링으로 장애 대응 가능</td>
</tr>
<tr>
<td><strong>비용</strong></td>
<td>별도 인프라 불필요</td>
<td>추가 인프라 운영 비용 발생</td>
</tr>
</tbody></table>
<hr>
<h4 id="💡-실무-선택-기준">💡 실무 선택 기준</h4>
<ul>
<li><p><strong>로컬 캐시가 적합한 경우</strong></p>
<ul>
<li>단일 서버 / 소규모 애플리케이션</li>
<li>읽기 중심, 데이터 변경이 드문 경우</li>
<li>TTL 짧은 데이터 (ex. 1~2초 주기)</li>
<li>네트워크 비용/복잡도를 줄이고 싶은 경우</li>
</ul>
</li>
<li><p><strong>분산 캐시가 적합한 경우</strong></p>
<ul>
<li>다중 서버 / 대규모 트래픽 환경</li>
<li>쓰기/갱신 작업이 빈번하고, 데이터 일관성이 중요한 경우</li>
<li>고가용성, 확장성이 요구되는 클라우드 환경</li>
</ul>
</li>
</ul>
<blockquote>
<p>💡 <strong>혼합 전략 (다중 레벨 캐시, L1 + L2)</strong></p>
</blockquote>
<ul>
<li>L1(로컬) : 애플리케이션 내부 초고속 응답</li>
<li>L2(분산) : 서버 간 데이터 일관성 확보</li>
<li>ex. Spring Boot : Caffeine(로컬, L1) + Redis(분산, L2) </li>
<li>실무에서는 혼합전략을 가장 많이 활용함</li>
</ul>
<hr>
<h3 id="📄-참고-문서">📄 참고 문서</h3>
<ul>
<li>코드잇 강의 교안</li>
<li><a href="https://docs.spring.io/spring-framework/reference/integration/cache/annotations.html">Spring Framework 공식 문서 - 선언적 주석 기반 캐싱</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SB 3기] 코드잇 스프린트 위클리페이퍼 15주차]]></title>
            <link>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-15%EC%A3%BC%EC%B0%A8</link>
            <guid>https://velog.io/@jh_devlog/SB-3%EA%B8%B0-%EC%BD%94%EB%93%9C%EC%9E%87-%EC%8A%A4%ED%94%84%EB%A6%B0%ED%8A%B8-%EC%9C%84%ED%81%B4%EB%A6%AC%ED%8E%98%EC%9D%B4%ED%8D%BC-15%EC%A3%BC%EC%B0%A8</guid>
            <pubDate>Sun, 17 Aug 2025 08:34:16 GMT</pubDate>
            <description><![CDATA[<h3 id="q1-멀티스레드-환경에서-발생하는-대표적인-문제-중-하나인-경쟁-상태race-condition에-대해-설명하고-이를-해결하기-위한-다양한-전략을-설명해보세요">Q1. 멀티스레드 환경에서 발생하는 대표적인 문제 중 하나인 경쟁 상태(Race Condition)에 대해 설명하고, 이를 해결하기 위한 다양한 전략을 설명해보세요.</h3>
<hr>
<h4 id="✅-경쟁-상태race-condition란">✅ 경쟁 상태(Race Condition)란?</h4>
<ul>
<li>여러 스레드나 프로세스가 <strong>동시에 공유 자원(변수, 메모리, 파일 등)</strong>에 접근할 때, 실행 순서에 따라 결과가 달라질 수 있는 상태를 말한다.</li>
</ul>
<blockquote>
<p>👉 예시 :</p>
</blockquote>
<ul>
<li>A의 계좌에 15만원이 있을 때, 두 명이 동시에 1만원씩 입금하면 정상 결과는 17만원이어야 한다.</li>
<li>하지만 두 스레드가 동시에 15만원을 읽어 각각 1만원을 더해 쓰면 최종 결과가 16만원으로 잘못 저장될 수 있다.</li>
</ul>
<hr>
<h4 id="✅-임계-구역critical-section이란">✅ 임계 구역(Critical Section)이란?</h4>
<ul>
<li>공유 자원에 접근하는 코드 블록을 말한다.</li>
<li>경쟁 상태가 발생할 수 있으므로 공유 자원의 독점을 보장해줘야 하는 영역이다.</li>
</ul>
<hr>
<h4 id="📌--경쟁-상태-해결-조건-3가지">📌  경쟁 상태 해결 조건 (3가지)</h4>
<p><strong>1. 상호 배제(Mutual Exclusion)</strong> : 한 스레드가 임계 구역을 실행 중이면 다른 스레드는 진입할 수 없어야 한다.</p>
<p><strong>2. 진행(Progress)</strong> : 임계 구역에 실행 중인 스레드가 없을 때는, 대기 중인 스레드 중 하나가 반드시 진입할 수 있어야 한다.</p>
<p><strong>3. 한정 대기(Bounded Waiting)</strong> : 특정 스레드가 무한정 대기하지 않도록 보장해야 한다. (기아 상태 방지)</p>
<hr>
<h4 id="🛠️-경쟁-상태-해결-전략">🛠️ 경쟁 상태 해결 전략</h4>
<ul>
<li>해결 방법은 크게 <strong>1) 락 기반 동기화, 2) 원자적 연산 활용, 3) Thread-safe 자료구조 사용</strong>으로 나눌 수 있다.</li>
</ul>
<p><strong>⭐️ 1. 동기화 전략 (Lock 기반)</strong></p>
<ul>
<li><p>락을 걸어 순서를 보장하는 전통적인 방식</p>
</li>
<li><p>스레드가 임계 구역에 <strong>순차적으로 진입</strong>하도록 제어한다.</p>
</li>
<li><p>** 대표적인 해결 방법:**</p>
<ul>
<li><strong>뮤텍스(Mutex)</strong> <ul>
<li>한 번에 하나의 스레드만 공유 자원에 접근 가능</li>
<li>다른 스레드는 해당 자원이 해제될 때까지 대기해야 함</li>
<li>단순하고 안정적이지만, 동시에 많은 스레드가 몰리면 성능 저하 발생</li>
</ul>
</li>
<li><strong>세마포어(Semaphore)</strong> <ul>
<li>동시에 접근할 수 있는 스레드의 최대 개수를 제한</li>
<li>ex. 최대 3개 스레드까지만 DB 접근 허용</li>
<li>뮤텍스는 세마포어의 개수가 1인 특수한 경우라고 볼 수 있음</li>
</ul>
</li>
<li><strong>모니터(Monitor)</strong><ul>
<li>공유 자원과 접근 메서드를 객체 단위로 캡슐화하여 관리</li>
<li>자바의 <code>synchronized</code> 블록이나 메서드가 대표적인 구현체</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>2. 원자적 연산 사용 (Lock-free)</strong></p>
<ul>
<li>락 대신 CPU 명령어로 원자성을 보장하는 방식</li>
<li>CAS(Compare-And-Swap) 기반으로, 공유 자원 수정이 쪼갤 수 없는 단일 연산처럼 처리된다.</li>
<li>다른 스레드가 끼어들 수 없다.</li>
<li>ex. <code>AtomicInteger.incrementAndGet()</code></li>
</ul>
<p><strong>3. Thread-Safe 자료 구조 사용</strong> </p>
<ul>
<li>동기화가 내장된 자료구조 활용하는 방식</li>
<li>멀티스레드 환경에서도 안전하게 데이터에 접근 가능하다.</li>
<li>ex. <code>ConcurrentHashMap</code>, <code>CopyOnWriteArrayList</code>, <code>ConcurrentLinkedQueue</code></li>
</ul>
<blockquote>
<p>💡 상황에 따라 성능과 안정성을 고려해 적절한 방법을 선택해야 한다. </p>
</blockquote>
<ul>
<li>락 기반 방식은 직관적이지만 성능 저하가 있을 수 있다.</li>
<li>원자적 연산은 빠르지만 복잡한 로직에는 한계가 있다. </li>
<li>복잡한 데이터 구조는 Thread-safe 자료구조를 사용하는 것이 더 안전하다.</li>
</ul>
<hr>
<h3 id="q2-비동기-환경에서-mdclogback-mapped-diagnostic-context나-securitycontext-같은-컨텍스트-정보를-스레드-간에-전달해야-할-경우-처리하는-방법에-대해-설명하세요">Q2. 비동기 환경에서 MDC(Logback Mapped Diagnostic Context)나 SecurityContext 같은 컨텍스트 정보를 스레드 간에 전달해야 할 경우, 처리하는 방법에 대해 설명하세요.</h3>
<hr>
<h4 id="✅-비동기async-환경이란">✅ 비동기(Async) 환경이란?</h4>
<ul>
<li>여러 작업들이 서로 영향을 주지 않고 독립적으로 실행될 수 있는 환경이다.</li>
<li>요청 처리 스레드와 별도 스레드풀에서 작업을 실행함</li>
<li>이는 응답 시간을 줄여 시간이 오래 걸리는 작업을 처리할 때 효율적이다.</li>
</ul>
<hr>
<h4 id="⚠️-발생할-수-있는-문제">⚠️ 발생할 수 있는 문제</h4>
<ul>
<li><code>ThreadLocal</code> 기반 컨텍스트(MDC, <code>SecurityContextHolder</code>)는 스레드가 바뀌면 자동 전파되지 않는다. (사라짐)</li>
<li>ex. 로그(traceId)가 유실될 수 있음</li>
<li>ex. 인증/인가 정보가 사라져 권한체크가 실패할 수 있음</li>
</ul>
<blockquote>
<p><code>ThreadLocal</code>은 스레드의 로컬 컨텍스트 변수로 스레드가 살아있는 동안 계속해서 유지되는 변수를 말한다. (스레드의 글로벌 변수)</p>
</blockquote>
<hr>
<h4 id="🛠️-문제-해결-방법--taskdecorator-활용">🛠️ 문제 해결 방법 : TaskDecorator 활용</h4>
<ul>
<li><code>TaskDecorator</code>는 스레드풀에서 실행되는 Runnable을 감싸 전·후 처리를 할 수 있게 해주는 인터페이스이다.</li>
<li>이를 이용하면 비동기 실행 시에도 MDC와 SecurityContext같은 <code>ThreadLocal</code> 기반 컨텍스트를 안전하게 전파할 수 있다.</li>
<li>원리 : 컨텍스트를 <strong>캡처</strong> → 비동기 태스크에 <strong>래핑</strong> → 실행 스레드에서 <strong>복원</strong> → 실행 후 <strong>정리</strong> </li>
</ul>
<p><strong>1. <code>TaskDecorator</code> 구현</strong></p>
<pre><code class="language-java">public class ContextCopyingTaskDecorator implements TaskDecorator {
    @Override
    public Runnable decorate(Runnable task) {
        // 1) Capture
        Map&lt;String, String&gt; callerMdc = MDC.getCopyOfContextMap();
        SecurityContext callerSec = SecurityContextHolder.getContext();

        return () -&gt; {
            try {
                // 2) Restore
                if (callerMdc != null) MDC.setContextMap(callerMdc);
                SecurityContextHolder.setContext(callerSec);

                // 3) Run
                task.run();
            } finally {
                // 4) Clear
                MDC.clear();
                SecurityContextHolder.clearContext();
            }
        };
    }
}
</code></pre>
<p><strong>2. 스레드풀에 등록 + <code>@Async</code> 메서드에서 활용</strong></p>
<pre><code class="language-java">@EnableAsync
@Configuration
public class AsyncConfig {

    @Bean(name = &quot;appExecutor&quot;)
    public ThreadPoolTaskExecutor appExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setTaskDecorator(new ContextCopyingTaskDecorator());
        executor.initialize();
        return executor;
    }
}</code></pre>
<pre><code class="language-java">@Service
public class MyService {
    @Async(&quot;appExecutor&quot;)
    public void doAsyncWork() {
        // 여기서 MDC와 SecurityContext 정상 동작
    }
}
</code></pre>
<blockquote>
<h4 id="🔁-스레드-간-컨텍스트-전달-원칙">🔁 스레드 간 컨텍스트 전달 원칙</h4>
</blockquote>
<ul>
<li><strong>캡처(Capture)</strong>: 현재 스레드의 컨텍스트 저장</li>
<li><strong>래핑(Wrap)</strong>: 비동기 태스크를 컨텍스트 포함 형태로 감싸기</li>
<li><strong>복원(Restore)</strong>: 실행 스레드에서 컨텍스트 복원</li>
<li><strong>정리(Clear)</strong>: 실행 후 반드시 초기화 (스레드풀 재사용 시 누수 방지)</li>
</ul>
<hr>
<h3 id="📄-참고-문서">📄 참고 문서</h3>
<ul>
<li><a href="https://charles098.tistory.com/88">[운영체제] 경쟁상태(Race Condition)와 동기화(Synchronization)의 필요성, 임계 구역(Critical Section)</a></li>
<li><a href="https://zangzangs.tistory.com/115">[OS] Race Condition 경쟁상태란?</a></li>
<li><a href="https://turtledev.tistory.com/47">[CS]경쟁 상태(Race Condition)과 교착 상태(Dead Lock)
</a></li>
<li><a href="https://moonong.tistory.com/129">[Java/Spring] 비동기 처리 시 ThreadContext 를 관리하는 방법</a></li>
<li><a href="https://blog.gangnamunni.com/post/mdc-context-task-decorator/">Spring 의 동기, 비동기, 배치 처리시 항상 context 를 유지하고 로깅하기</a></li>
<li><a href="https://yeonyeon.tistory.com/318">@Async와 함께 사라지다 (feat. TaskDecorator)</a></li>
<li><a href="https://velog.io/@kk95610/%ED%8A%B8%EB%9F%AC%EB%B8%94%EC%8A%88%ED%8C%85-Async%EC%8B%A4%ED%96%89-%EC%8B%9C-TaskDecorator-%EC%84%A4%EC%A0%95">[트러블슈팅] @Async실행 시 TaskDecorator 설정</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>