<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Evan</title><description>개발하며 배우고 만든 것을 기록합니다</description><link>https://www.shyunii.dev/</link><language>ko</language><item><title>[Git] Fetch vs Pull</title><link>https://www.shyunii.dev/posts/git-fetch-pull/</link><guid isPermaLink="true">https://www.shyunii.dev/posts/git-fetch-pull/</guid><description>git fetch와 git pull에 대한 내용과 차이점, 고려사항에 대한 학습 내용 정리</description><pubDate>Wed, 21 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 들어가며&lt;/h2&gt;
&lt;p&gt;GitHub와 같은 원격 저장소를 사용해 협업하다 보면 다른 개발자가 반영한 코드를 가져오거나, 로컬 브랜치를 최신 상태로 갱신해야 하는 상황이 자주 발생한다.&lt;/p&gt;
&lt;p&gt;이때 사용하는 대표적인 명령어가 &lt;code&gt;git fetch&lt;/code&gt;와 &lt;code&gt;git pull&lt;/code&gt;이다.&lt;/p&gt;
&lt;p&gt;두 명령어는 모두 원격 저장소의 변경 사항을 가져오지만, &lt;strong&gt;가져온 변경 사항을 현재 로컬 브랜치에 자동으로 통합하는지&lt;/strong&gt;에 차이가 있다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 Fetch와 Pull의 동작 원리, 차이점, 적절한 사용 시점과 주요 옵션을 살펴본다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 로컬 브랜치와 원격 추적 브랜치&lt;/h2&gt;
&lt;p&gt;Fetch와 Pull을 이해하기 위해서는 먼저 로컬 브랜치(Local Branch)와 원격 추적 브랜치(Remote-Tracking Branch)를 구분해야 한다.&lt;/p&gt;
&lt;p&gt;GitHub 저장소를 다음 명령어로 복제했다고 가정하자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/example/project.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일반적으로 다음과 같은 브랜치 참조를 확인할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;main
origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;두 참조는 이름이 비슷하지만 서로 다른 역할을 한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;내 컴퓨터에서 직접 작업하는 로컬 브랜치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;origin/main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;마지막으로 가져온 원격 main의 상태를 나타내는 로컬 참조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원격 저장소의 &lt;code&gt;main&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GitHub 서버에 실제로 존재하는 브랜치&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;여기서 &lt;code&gt;origin&lt;/code&gt;은 원격 저장소를 나타내는 이름으로, 일반적으로 &lt;code&gt;git clone&lt;/code&gt;을 실행할 때 기본적으로 설정된다.&lt;/p&gt;
&lt;p&gt;중요한 점은 &lt;strong&gt;&lt;code&gt;origin/main&lt;/code&gt;이 원격 저장소의 최신 상태를 실시간으로 반영하는 것은 아니라는 것&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;다른 개발자가 GitHub에 새로운 커밋을 Push하더라도 내 로컬의 &lt;code&gt;origin/main&lt;/code&gt;은 자동으로 갱신되지 않는다.&lt;/p&gt;
&lt;p&gt;최신 정보를 가져오려면 &lt;code&gt;git fetch&lt;/code&gt; 또는 &lt;code&gt;git pull&lt;/code&gt;과 같은 명령어를 실행해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. git fetch란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git fetch&lt;/code&gt;는 &lt;strong&gt;원격 저장소의 최신 커밋과 브랜치 정보를 가져와 로컬의 원격 추적 브랜치를 갱신하는 명령어&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;가장 큰 특징은 원격 저장소의 변경 사항을 가져오지만, 현재 작업 중인 로컬 브랜치에는 자동으로 통합하지 않는다는 점이다.&lt;/p&gt;
&lt;h3&gt;3.1. 동작 원리&lt;/h3&gt;
&lt;p&gt;다음과 같은 상황을 가정하자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원격 저장소(GitHub)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C --- D --- E  (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;로컬 저장소&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C  (main, origin/main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다른 개발자가 원격 저장소에 D, E 커밋을 Push했지만 내 로컬 저장소에는 아직 반영되지 않은 상태이다.&lt;/p&gt;
&lt;p&gt;이때 다음 명령어를 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch origin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Fetch 이후&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C --- D --- E  (origin/main)
             ↑
            main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Git은 원격 저장소의 D, E 커밋을 가져오고 &lt;code&gt;origin/main&lt;/code&gt;을 E 커밋으로 갱신한다.&lt;/p&gt;
&lt;p&gt;하지만 로컬 &lt;code&gt;main&lt;/code&gt;은 여전히 C 커밋을 가리킨다.&lt;/p&gt;
&lt;p&gt;즉, &lt;strong&gt;원격 저장소의 최신 변경 사항을 가져왔지만 현재 작업 브랜치에는 적용하지 않은 상태&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;3.2. 가져온 변경 사항 확인하기&lt;/h3&gt;
&lt;p&gt;Fetch의 장점은 원격 저장소의 변경 사항을 현재 브랜치에 반영하기 전에 미리 검토할 수 있다는 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 원격 저장소의 최신 정보 가져오기
git fetch origin

# 로컬 main에는 없는 원격 커밋 확인
git log main..origin/main --oneline

# 로컬 main과 원격 main의 코드 차이 확인
git diff main origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;git log main..origin/main&lt;/code&gt;은 로컬 &lt;code&gt;main&lt;/code&gt;에서는 도달할 수 없지만 &lt;code&gt;origin/main&lt;/code&gt;에서는 도달할 수 있는 커밋을 보여준다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git diff main origin/main&lt;/code&gt;은 두 브랜치가 가리키는 커밋의 파일 상태 차이를 보여준다.&lt;/p&gt;
&lt;p&gt;이를 통해 다른 개발자가 어떤 코드를 변경했는지 확인할 수 있다.&lt;/p&gt;
&lt;h3&gt;3.3. 변경 사항을 실제로 반영하려면?&lt;/h3&gt;
&lt;p&gt;Fetch만 실행하면 로컬 &lt;code&gt;main&lt;/code&gt;은 변경되지 않는다.&lt;/p&gt;
&lt;p&gt;따라서 가져온 변경 사항을 실제로 반영하려면 별도의 명령어가 필요하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch main
git merge origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;앞선 예시에서는 로컬 &lt;code&gt;main&lt;/code&gt;이 원격 &lt;code&gt;main&lt;/code&gt;보다 단순히 뒤처져 있으므로 Fast-Forward Merge가 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Merge 이후&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C --- D --- E
                         ↑
                        main
                        origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 로컬 &lt;code&gt;main&lt;/code&gt;과 &lt;code&gt;origin/main&lt;/code&gt;이 동일한 커밋을 가리킨다.&lt;/p&gt;
&lt;p&gt;즉, Fetch는 &lt;strong&gt;원격 변경 사항을 가져오는 단계와 실제로 반영하는 단계를 분리할 수 있다&lt;/strong&gt;는 특징이 있다.&lt;/p&gt;
&lt;h3&gt;3.4. git fetch는 언제 사용할까?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 원격 저장소의 변경 사항을 미리 확인할 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;다른 개발자가 Push한 코드를 검토한 후 현재 브랜치에 적용할지 결정할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 현재 작업 브랜치를 변경하고 싶지 않을 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기능 개발 중 원격 저장소의 최신 정보만 가져오고, 실제 통합은 나중에 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ Merge 또는 Rebase 전에 원격 정보를 갱신할 때&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git fetch origin

# 현재 브랜치에 최신 main을 반영
git rebase origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이처럼 최신 원격 정보를 가져온 후 원하는 통합 방식을 직접 선택할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. git pull이란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git pull&lt;/code&gt;은 &lt;strong&gt;원격 저장소의 변경 사항을 가져온 후 현재 로컬 브랜치에 통합하는 명령어&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;Fetch가 변경 사항을 가져오는 작업만 수행한다면, Pull은 가져오기와 통합을 함께 수행한다.&lt;/p&gt;
&lt;p&gt;일반적인 동작 과정은 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git pull
    │
    ▼
git fetch
    │
    ▼
Merge 또는 Rebase
    │
    ▼
로컬 브랜치 갱신
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;단, 실제 통합 방식은 Git 설정과 명령어 옵션에 따라 달라진다.&lt;/p&gt;
&lt;h3&gt;4.1. 동작 원리&lt;/h3&gt;
&lt;p&gt;다음과 같은 상황을 가정하자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;원격 저장소&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C --- D --- E  (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;로컬 저장소&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C  (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 다음 명령어를 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch main
git pull origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Git은 원격 저장소의 D, E 커밋을 가져온 후 현재 로컬 브랜치에 통합한다.&lt;/p&gt;
&lt;p&gt;로컬 브랜치가 원격 브랜치보다 단순히 뒤처져 있으므로 Fast-Forward가 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Pull 이후&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C --- D --- E
                         ↑
                        main
                        origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;새로운 Merge Commit을 생성하지 않고 로컬 &lt;code&gt;main&lt;/code&gt;의 포인터가 E 커밋으로 이동한다.&lt;/p&gt;
&lt;h3&gt;4.2. 로컬과 원격 브랜치가 분기되었다면?&lt;/h3&gt;
&lt;p&gt;Pull이 항상 Fast-Forward 방식으로 동작하는 것은 아니다.&lt;/p&gt;
&lt;p&gt;다음과 같이 로컬과 원격 브랜치에 서로 다른 커밋이 추가된 상황을 살펴보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;          D --- E  (origin/main)
         /
A --- B --- C      (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로컬에는 C 커밋이 추가되었고, 원격에는 D, E 커밋이 추가되었다.&lt;/p&gt;
&lt;p&gt;이 경우에는 단순히 브랜치 포인터를 이동하는 것만으로 변경 사항을 통합할 수 없다.&lt;/p&gt;
&lt;p&gt;따라서 Merge 또는 Rebase와 같은 통합 전략이 필요하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Merge 방식을 선택한 경우&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git pull --no-rebase origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;          D --- E
         /       \
A --- B --- C --- M  (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기존 커밋 히스토리를 유지하면서 새로운 Merge Commit M을 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rebase 방식을 선택한 경우&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git pull --rebase origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- D --- E --- C&apos;  (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로컬 C 커밋의 변경 사항을 원격 브랜치의 최신 커밋 E 위에 다시 적용한다.&lt;/p&gt;
&lt;p&gt;따라서 &lt;strong&gt;&lt;code&gt;git pull&lt;/code&gt;은 단순히 &lt;code&gt;fetch + merge&lt;/code&gt;가 아니라, &lt;code&gt;fetch + 선택된 통합 방식&lt;/code&gt;이라고 이해하는 것이 정확하다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;또한 Git 설정에 따라 분기된 브랜치에서 통합 방식을 지정하지 않으면 Pull이 중단되고 전략을 선택하라는 메시지가 나타날 수 있다.&lt;/p&gt;
&lt;h3&gt;4.3. git pull의 주요 옵션&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① git pull --ff-only&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git pull --ff-only origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fast-Forward가 가능한 경우에만 로컬 브랜치를 갱신한다.&lt;/p&gt;
&lt;p&gt;로컬과 원격 브랜치가 분기된 경우 작업을 중단한다.&lt;/p&gt;
&lt;p&gt;따라서 로컬 &lt;code&gt;main&lt;/code&gt;에 직접 커밋하지 않고 원격 &lt;code&gt;main&lt;/code&gt;의 최신 변경 사항만 안전하게 반영하려는 경우 유용하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② git pull --rebase&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git pull --rebase origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;원격 변경 사항을 가져온 후 로컬 커밋을 그 위에 다시 적용한다.&lt;/p&gt;
&lt;p&gt;개인 작업 브랜치에서 직선적인 커밋 히스토리를 유지하고 싶을 때 적합하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ git pull --no-rebase&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git pull --no-rebase origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;원격 변경 사항을 가져온 후 Merge 방식으로 통합한다.&lt;/p&gt;
&lt;p&gt;기존 커밋 히스토리를 유지하면서 변경 사항을 반영하고 싶을 때 적합하다.&lt;/p&gt;
&lt;h3&gt;4.4. git pull은 언제 사용할까?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 로컬 브랜치를 최신 상태로 갱신할 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;원격 저장소의 변경 사항을 현재 브랜치에 바로 반영하고 싶을 때 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 변경 사항을 별도로 검토할 필요가 없을 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;원격 변경 내용을 이미 알고 있다면 Pull을 사용하여 가져오기와 통합을 한 번에 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;다만 현재 브랜치의 상태에 따라 충돌이나 의도하지 않은 병합이 발생할 수 있으므로 주의해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. git fetch vs git pull 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;비교 항목&lt;/th&gt;
&lt;th&gt;git fetch&lt;/th&gt;
&lt;th&gt;git pull&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;원격 변경 사항 가져오기&lt;/td&gt;
&lt;td&gt;원격 변경 사항 가져오기 및 통합&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원격 커밋 다운로드&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;원격 추적 브랜치 갱신&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;현재 로컬 브랜치 자동 변경&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O (필요한 경우)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;현재 작업 파일 변경&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O (변경이 적용되는 경우)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merge 또는 Rebase 수행&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;설정에 따라 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;변경 사항을 검토한 뒤 통합 가능&lt;/td&gt;
&lt;td&gt;명령어 한 번으로 동기화 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;td&gt;별도 통합 작업 필요&lt;/td&gt;
&lt;td&gt;의도하지 않은 충돌이나 병합 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적절한 상황&lt;/td&gt;
&lt;td&gt;최신 변경 사항 확인&lt;/td&gt;
&lt;td&gt;로컬 브랜치 최신화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;두 명령어의 핵심 차이는 &lt;strong&gt;Fetch가 원격 변경 사항을 가져오는 데서 멈추는 반면, Pull은 가져온 변경 사항을 현재 브랜치에 통합한다는 점&lt;/strong&gt;이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 실무 활용 예시&lt;/h2&gt;
&lt;p&gt;Spring Boot 기반 팀 프로젝트에서 &lt;code&gt;feature/notification&lt;/code&gt; 브랜치로 알림 기능을 개발하고 있다고 가정하자.&lt;/p&gt;
&lt;p&gt;다른 개발자가 사용자 관련 코드를 &lt;code&gt;main&lt;/code&gt;에 병합했고, 해당 변경 사항을 알림 기능에 반영해야 하는 상황이다.&lt;/p&gt;
&lt;h3&gt;STEP 1. 원격 변경 사항 가져오기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git fetch origin
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;현재 기능 브랜치를 변경하지 않고 최신 원격 정보를 가져온다.&lt;/p&gt;
&lt;h3&gt;STEP 2. 변경 사항 확인하기&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# 현재 브랜치에는 없는 원격 main의 커밋 확인
git log HEAD..origin/main --oneline

# 공통 조상 이후 원격 main의 변경 사항 확인
git diff HEAD...origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;git diff HEAD...origin/main&lt;/code&gt;은 두 브랜치의 공통 조상을 기준으로 원격 &lt;code&gt;main&lt;/code&gt;에서 발생한 코드 변경 사항을 보여준다.&lt;/p&gt;
&lt;h3&gt;STEP 3. 변경 사항 반영하기&lt;/h3&gt;
&lt;p&gt;개인 기능 브랜치에서 작업 중이며 Rebase가 허용된다면 다음과 같이 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch feature/notification
git rebase origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여러 개발자가 공유하는 브랜치라면 기존 히스토리를 유지하는 Merge를 선택할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch feature/notification
git merge origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;변경 사항을 통합한 이후에는 테스트를 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;./gradlew test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Git에서 충돌이 발생하지 않았더라도 다른 개발자의 코드 변경으로 인해 컴파일 오류나 기능 문제가 발생할 수 있으므로 테스트가 필요하다.&lt;/p&gt;
&lt;p&gt;반대로 로컬 &lt;code&gt;main&lt;/code&gt;을 최신 상태로 갱신하기만 하면 되는 상황이라면 다음과 같이 Pull을 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch main
git pull --ff-only origin main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이처럼 &lt;strong&gt;변경 사항을 먼저 확인하고 통합 방식을 선택하고 싶다면 Fetch, 원격 변경 사항을 바로 반영하고 싶다면 Pull&lt;/strong&gt;을 사용하는 것이 적절하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 마무리&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git fetch&lt;/code&gt;와 &lt;code&gt;git pull&lt;/code&gt;은 모두 원격 저장소의 변경 사항을 가져오는 명령어이지만, 가져온 내용을 현재 로컬 브랜치에 자동으로 통합하는지에 차이가 있다.&lt;/p&gt;
&lt;p&gt;Fetch는 원격 저장소의 최신 정보를 가져와 원격 추적 브랜치를 갱신하지만 현재 작업 브랜치는 변경하지 않는다.&lt;/p&gt;
&lt;p&gt;반면 Pull은 원격 변경 사항을 가져온 후 Merge 또는 Rebase 등의 방식으로 현재 브랜치에 통합한다.&lt;/p&gt;
&lt;p&gt;따라서 원격 변경 사항을 먼저 확인하고 싶다면 Fetch를, 변경 사항을 바로 반영하고 싶다면 Pull을 사용하는 것이 적절하다.&lt;/p&gt;
&lt;p&gt;결국 중요한 것은 &lt;strong&gt;현재 로컬 브랜치와 원격 브랜치의 상태를 이해하고, 변경 사항을 가져오는 것과 실제로 통합하는 작업을 구분하는 것&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;참고 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-fetch&quot;&gt;Git 공식 문서 — git fetch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-pull&quot;&gt;Git 공식 문서 — git pull&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Basics-Working-with-Remotes&quot;&gt;Pro Git — Working with Remotes&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[Git] merge vs rebase</title><link>https://www.shyunii.dev/posts/git-merge-rebase/</link><guid isPermaLink="true">https://www.shyunii.dev/posts/git-merge-rebase/</guid><description>협업에 필수적인 git merge, rebase 사용 방법 및 고려사항에 대한 학습 내용 정리</description><pubDate>Tue, 20 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 들어가며&lt;/h2&gt;
&lt;p&gt;Git을 사용해 여러 개발자가 협업하다 보면 서로 다른 브랜치에서 작업한 코드를 하나로 통합해야 하는 상황이 발생한다.&lt;/p&gt;
&lt;p&gt;이때 사용하는 대표적인 명령어가 &lt;code&gt;git merge&lt;/code&gt;와 &lt;code&gt;git rebase&lt;/code&gt;이다.&lt;/p&gt;
&lt;p&gt;두 명령어는 모두 브랜치의 변경 사항을 통합하는 데 사용되지만, &lt;strong&gt;기존 커밋 히스토리를 유지하는지, 새로운 기준점에서 재구성하는지&lt;/strong&gt;에 차이가 있다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 Merge와 Rebase의 동작 원리, 차이점, 적절한 사용 시점과 주의사항을 살펴보려고 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. git merge란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git merge&lt;/code&gt;는 &lt;strong&gt;서로 다른 브랜치에서 발생한 변경 사항을 하나로 통합하는 명령어&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;가장 큰 특징은 기존 커밋 히스토리를 재작성하지 않고 유지한다는 점이다.&lt;/p&gt;
&lt;h3&gt;2.1. 동작 원리&lt;/h3&gt;
&lt;p&gt;다음과 같은 브랜치 상태를 가정하자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Merge 이전&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;          D --- E  (feature/login)
         /
A --- B --- C      (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt;: A → B → C 순서로 커밋이 생성되었다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;feature/login&lt;/code&gt;: B 커밋에서 분기하여 D, E 커밋이 추가되었다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로그인 기능 브랜치를 &lt;code&gt;main&lt;/code&gt;에 병합하려면 다음 명령어를 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch main
git merge feature/login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Merge 이후&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;          D --- E
         /       \
A --- B --- C --- M  (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &lt;code&gt;M&lt;/code&gt;은 새롭게 생성된 &lt;strong&gt;Merge Commit(병합 커밋)&lt;/strong&gt; 이다.&lt;/p&gt;
&lt;p&gt;Git은 기존 커밋 A, B, C, D, E를 그대로 유지하면서 두 브랜치의 변경 사항을 통합한 새로운 커밋 M을 생성한다.&lt;/p&gt;
&lt;p&gt;일반적인 커밋은 하나의 부모 커밋을 가지지만, 이러한 Merge Commit은 두 개의 부모 커밋을 가진다.&lt;/p&gt;
&lt;p&gt;따라서 어느 브랜치에서 어떤 작업이 진행되었고, 어느 시점에 병합되었는지 확인할 수 있다.&lt;/p&gt;
&lt;h3&gt;2.2. 3-Way Merge란?&lt;/h3&gt;
&lt;p&gt;위와 같이 두 브랜치가 서로 분기된 상태에서 병합할 때 Git은 일반적으로 &lt;strong&gt;3-Way Merge(3방향 병합)&lt;/strong&gt; 방식을 사용한다.&lt;/p&gt;
&lt;p&gt;Git은 다음 세 커밋을 기준으로 변경 사항을 비교한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;커밋&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;두 브랜치의 공통 조상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Current&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;현재 브랜치의 최신 커밋&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Target&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;병합 대상 브랜치의 최신 커밋&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;공통 조상인 B를 기준으로 C와 E에서 각각 어떤 변경 사항이 발생했는지 비교한 후 이를 통합한다.&lt;/p&gt;
&lt;p&gt;자동으로 통합할 수 없는 변경 사항이 있다면 충돌(Conflict)이 발생한다.&lt;/p&gt;
&lt;h3&gt;2.3. Fast-Forward Merge란?&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git merge&lt;/code&gt;를 실행한다고 해서 항상 Merge Commit이 생성되는 것은 아니다.&lt;/p&gt;
&lt;p&gt;다음과 같은 상황을 살펴보자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Merge 이전&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C          (main)
             \
              D --- E  (feature/login)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기능 브랜치가 생성된 이후 &lt;code&gt;main&lt;/code&gt;에는 새로운 커밋이 추가되지 않았다.&lt;/p&gt;
&lt;p&gt;이 상태에서 Merge를 실행하면 다음과 같이 변경된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C --- D --- E
                         ↑
                        main
                        feature/login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;새로운 Merge Commit을 생성하지 않고 &lt;code&gt;main&lt;/code&gt; 브랜치의 포인터만 E 커밋으로 이동한다.&lt;/p&gt;
&lt;p&gt;이를 &lt;strong&gt;Fast-Forward Merge(빨리 감기 병합)&lt;/strong&gt; 라고 한다.&lt;/p&gt;
&lt;p&gt;현재 브랜치가 병합 대상 브랜치의 조상이기 때문에 포인터를 이동하는 것만으로 변경 사항을 통합할 수 있다.&lt;/p&gt;
&lt;p&gt;반대로 Fast-Forward가 가능한 상황에서도 Merge Commit을 남기고 싶다면 다음 옵션을 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git merge --no-ff feature/login
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--no-ff&lt;/code&gt;는 기능 단위의 병합 이력을 명확하게 기록하고 싶을 때 유용하다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;--ff-only&lt;/code&gt; 라는 옵션도 있는데 이 옵션은 Fast-Forward가 가능한 상황에서는 병합이 되고 불가능한 상황이라면 병합이 거부된다.&lt;/p&gt;
&lt;h3&gt;2.4. git merge는 언제 사용할까?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 여러 개발자의 작업을 통합할 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기능별 브랜치에서 개발을 완료한 후 &lt;code&gt;main&lt;/code&gt;이나 &lt;code&gt;develop&lt;/code&gt; 같은 공통 브랜치에 병합할 때 적합하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② 기존 커밋 히스토리를 보존해야 할 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;커밋이 생성된 순서와 브랜치가 병합된 과정을 유지하므로 작업 이력을 추적하기 쉽다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;③ 여러 개발자가 공유하는 브랜치를 관리할 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기존 커밋을 재작성하지 않기 때문에 공유 브랜치에서 상대적으로 안전하게 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;다만 병합이 반복되면 Merge Commit이 누적되어 커밋 그래프가 복잡해질 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. git rebase란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git rebase&lt;/code&gt;는 &lt;strong&gt;현재 브랜치의 커밋을 다른 브랜치의 최신 커밋을 기준으로 다시 적용하는 명령어&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;Rebase는 커밋의 기반(Base)을 변경한다는 의미로, 기존 커밋을 새로운 기준점 위에 다시 적용하여 히스토리를 재구성한다.&lt;/p&gt;
&lt;p&gt;이를 통해 직선적인 커밋 히스토리(Linear History)를 만들 수 있다.&lt;/p&gt;
&lt;h3&gt;3.1. 동작 원리&lt;/h3&gt;
&lt;p&gt;다음과 같은 상황을 가정하자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rebase 이전&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;          D --- E  (feature/login)
         /
A --- B --- C      (main)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로그인 기능을 개발하는 동안 &lt;code&gt;main&lt;/code&gt;에 새로운 C 커밋이 추가되었다.&lt;/p&gt;
&lt;p&gt;이때 최신 &lt;code&gt;main&lt;/code&gt;의 변경 사항을 기능 브랜치에 반영하려고 다음 명령어를 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch feature/login
git rebase main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Rebase 이후&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C          (main)
             \
              D&apos; --- E&apos;  (feature/login)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Git은 다음 순서로 동작한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;두 브랜치의 공통 조상인 B 커밋을 찾는다.&lt;/li&gt;
&lt;li&gt;기능 브랜치에만 존재하는 D, E 커밋의 변경 사항을 확인한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;main&lt;/code&gt;의 최신 커밋 C를 새로운 기준점으로 설정한다.&lt;/li&gt;
&lt;li&gt;D, E의 변경 사항을 순서대로 다시 적용하여 D&apos;, E&apos; 커밋을 생성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;결과적으로 기능 브랜치의 히스토리는 다음과 같이 정리된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C --- D&apos; --- E&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;중요한 점은 &lt;strong&gt;기존 D, E 커밋이 단순히 이동한 것이 아니라 새로운 커밋 D&apos;, E&apos;로 재작성되었다는 것&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;Git의 커밋 ID는 부모 커밋 등의 정보를 기반으로 생성되므로, Rebase로 부모 커밋이 변경되면 커밋 ID도 달라진다.&lt;/p&gt;
&lt;p&gt;단, 이미 적용된 변경 사항이나 빈 커밋을 처리하는 과정에서 일부 커밋이 생략될 수도 있다.&lt;/p&gt;
&lt;h3&gt;3.2. git rebase는 언제 사용할까?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;① 개인 기능 브랜치에 최신 main을 반영할 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기능을 개발하는 동안 다른 개발자가 &lt;code&gt;main&lt;/code&gt;에 새로운 변경 사항을 반영했다면 다음과 같이 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git switch feature/login

# 원격 저장소 최신 정보 가져오기
git fetch origin

# 최신 원격 main을 기준으로 Rebase
git rebase origin/main
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;git fetch origin&lt;/code&gt;으로 원격 추적 브랜치를 갱신한 후 &lt;code&gt;origin/main&lt;/code&gt;을 기준으로 Rebase를 수행한다.&lt;/p&gt;
&lt;p&gt;이를 통해 최신 &lt;code&gt;main&lt;/code&gt;의 변경 사항 위에 자신의 커밋을 다시 적용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;② Pull Request 생성 전에 커밋을 정리할 때&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;기능 개발 과정에서 다음과 같은 커밋이 생성되었다고 가정하자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B --- C --- D
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;B: 로그인 API 구현&lt;/li&gt;
&lt;li&gt;C: 오타 수정&lt;/li&gt;
&lt;li&gt;D: 로그인 API 예외 처리 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Interactive Rebase를 활용하면 세 개의 커밋을 하나로 정리할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git rebase -i HEAD~3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;편집 화면에서 다음과 같이 변경한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pick   &amp;lt;B의 해시&amp;gt; 로그인 API 구현
squash &amp;lt;C의 해시&amp;gt; 오타 수정
squash &amp;lt;D의 해시&amp;gt; 로그인 API 예외 처리 수정
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;squash&lt;/code&gt;는 해당 커밋을 이전 커밋과 합치는 명령어이다.&lt;/p&gt;
&lt;p&gt;결과적으로 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A --- B&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 통해 불필요한 수정 커밋을 줄이고 PR의 변경 사항을 이해하기 쉽게 만들 수 있다.&lt;/p&gt;
&lt;h3&gt;3.3. git rebase 사용 시 주의사항&lt;/h3&gt;
&lt;p&gt;Rebase는 기존 커밋을 다시 작성하기 때문에 &lt;strong&gt;다른 개발자가 의존하는 공유 커밋에 함부로 사용해서는 안 된다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;다른 개발자가 이미 해당 커밋을 기준으로 작업하고 있다면, Rebase 이후 커밋 ID가 변경되면서 히스토리 불일치가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;따라서 개인 브랜치에서는 유용하지만 공유 브랜치에서는 주의해야 한다.&lt;/p&gt;
&lt;p&gt;이미 Push한 개인 브랜치를 Rebase했다면 일반적인 Push가 거부될 수 있다.&lt;/p&gt;
&lt;p&gt;팀에서 히스토리 재작성을 허용하는 경우에는 다음 명령어를 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git push --force-with-lease
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;--force-with-lease&lt;/code&gt;는 예상한 원격 참조 상태가 유지되고 있는지 확인한 뒤 Push하므로 일반적인 &lt;code&gt;--force&lt;/code&gt;보다 안전하다.&lt;/p&gt;
&lt;p&gt;다만 다른 개발자의 작업을 항상 보호하는 것은 아니므로 신중하게 사용해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Merge와 Rebase에서 충돌이 발생한다면?&lt;/h2&gt;
&lt;p&gt;Merge와 Rebase는 모두 변경 사항을 통합하는 과정에서 충돌(Conflict)이 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 두 브랜치에서 동일한 코드를 서로 다르게 수정했다고 가정하자.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;main 브랜치&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public String getMessage() {
    return &quot;Hello&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;feature/login 브랜치&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public String getMessage() {
    return &quot;Welcome&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Git이 두 변경 사항을 자동으로 통합하지 못하면 다음과 같은 충돌 표시가 나타날 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public String getMessage() {
&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt;&amp;lt; HEAD
    return &quot;Hello&quot;;
=======
    return &quot;Welcome&quot;;
&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt; feature/login
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;개발자는 어떤 코드를 유지할지 결정한 후 충돌 표시를 제거해야 한다.&lt;/p&gt;
&lt;h3&gt;Merge 충돌 해결&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git status

# 충돌 파일 수정 후
git add &amp;lt;파일명&amp;gt;
git merge --continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;병합을 취소하려면 다음 명령어를 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git merge --abort
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Rebase 충돌 해결&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;git status

# 충돌 파일 수정 후
git add &amp;lt;파일명&amp;gt;
git rebase --continue
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rebase를 취소하려면 다음 명령어를 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git rebase --abort
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rebase는 커밋을 순서대로 다시 적용하므로 여러 커밋에서 충돌이 반복될 수 있다.&lt;/p&gt;
&lt;p&gt;또한 Rebase에서는 충돌 표시의 &lt;code&gt;HEAD&lt;/code&gt;와 &lt;code&gt;ours&lt;/code&gt;, &lt;code&gt;theirs&lt;/code&gt;가 가리키는 변경의 의미가 일반적인 Merge와 다르게 해석될 수 있으므로 주의해야 한다.&lt;/p&gt;
&lt;p&gt;충돌을 해결한 후에는 코드가 정상적으로 동작하는지 테스트하는 것이 중요하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. git merge vs git rebase 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;비교 항목&lt;/th&gt;
&lt;th&gt;git merge&lt;/th&gt;
&lt;th&gt;git rebase&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;브랜치 변경 사항 통합&lt;/td&gt;
&lt;td&gt;커밋의 기준점 변경 및 재적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동작 방식&lt;/td&gt;
&lt;td&gt;기존 커밋을 유지하면서 병합&lt;/td&gt;
&lt;td&gt;새로운 기준점에 커밋 재적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기존 커밋 ID&lt;/td&gt;
&lt;td&gt;유지&lt;/td&gt;
&lt;td&gt;재작성된 커밋은 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Merge Commit&lt;/td&gt;
&lt;td&gt;필요한 경우 생성&lt;/td&gt;
&lt;td&gt;일반적인 Rebase에서는 생성하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;커밋 히스토리&lt;/td&gt;
&lt;td&gt;분기 및 병합 이력 보존&lt;/td&gt;
&lt;td&gt;직선적인 히스토리 구성 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;작업 이력 추적 및 협업에 유리&lt;/td&gt;
&lt;td&gt;깔끔한 커밋 히스토리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;td&gt;병합 커밋이 누적될 수 있음&lt;/td&gt;
&lt;td&gt;공유 커밋 재작성 시 문제 발생 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적절한 상황&lt;/td&gt;
&lt;td&gt;공유 브랜치 및 기능 통합&lt;/td&gt;
&lt;td&gt;개인 브랜치 최신화 및 커밋 정리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;두 명령어의 핵심 차이는 &lt;strong&gt;기존 커밋 히스토리를 유지하는지, 새로운 기준점에서 재구성하는지&lt;/strong&gt;에 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 마무리&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git merge&lt;/code&gt;와 &lt;code&gt;git rebase&lt;/code&gt;는 모두 브랜치의 변경 사항을 통합하는 명령어이지만, 커밋 히스토리를 처리하는 방식이 다르다.&lt;/p&gt;
&lt;p&gt;Merge는 기존 커밋을 유지하면서 변경 사항을 통합하므로 공유 브랜치나 기능 브랜치를 병합할 때 유용하다.&lt;/p&gt;
&lt;p&gt;반면 Rebase는 커밋을 새로운 기준점 위에 다시 적용하여 히스토리를 정리하므로 개인 기능 브랜치를 최신화하거나 PR을 생성하기 전에 커밋을 정리할 때 적합하다.&lt;/p&gt;
&lt;p&gt;결국 중요한 것은 &lt;strong&gt;어떤 명령어가 더 좋은지가 아니라, 브랜치가 공유되고 있는지와 어떤 커밋 히스토리를 유지하려는지에 따라 적절한 방식을 선택하는 것&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;참고 자료&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-merge&quot;&gt;Git 공식 문서 — git merge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-rebase&quot;&gt;Git 공식 문서 — git rebase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/book/en/v2/Git-Branching-Rebasing&quot;&gt;Pro Git — Rebasing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[CS] CI/CD</title><link>https://www.shyunii.dev/posts/ci-cd/</link><guid isPermaLink="true">https://www.shyunii.dev/posts/ci-cd/</guid><description>CI/CD에 대한 학습 내용 정리</description><pubDate>Thu, 15 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1. CI/CD는 왜 필요할까?&lt;/h1&gt;
&lt;p&gt;개발자가 코드를 작성했다고 해서 바로 사용자에게 서비스를 제공할 수 있는 것은 아니다.&lt;/p&gt;
&lt;p&gt;일반적으로 다음과 같은 과정이 필요하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;코드 작성
 ↓
빌드
 ↓
테스트
 ↓
배포 파일 생성
 ↓
서버 배포
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 과정을 사람이 매번 직접 수행하면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- 테스트 누락

- 잘못된 버전 배포

- 배포 과정 실수

- 개발자마다 다른 배포 방식
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;또 여러 개발자가 하나의 프로젝트를 개발하면 각자의 코드를 합쳤을 때 문제가 없는지도 계속 확인해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Developer A ─┐
             ├→ 코드 통합
Developer B ─┤
             │
Developer C ─┘

→ 빌드가 되는가?
→ 테스트가 성공하는가?
→ 서로 충돌하지 않는가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 &lt;strong&gt;코드의 통합, 빌드, 테스트, 배포 과정을 자동화&lt;/strong&gt;할 필요가 있다.&lt;/p&gt;
&lt;p&gt;이것이 CI/CD의 핵심이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;2. CI란?&lt;/h1&gt;
&lt;p&gt;CI는 &lt;strong&gt;Continuous Integration&lt;/strong&gt;, 즉 &lt;strong&gt;지속적 통합&lt;/strong&gt;을 의미한다.&lt;/p&gt;
&lt;p&gt;여러 개발자가 작성한 코드 변경사항을 공유 저장소에 자주 통합하고, 변경이 발생할 때마다 자동으로 빌드와 테스트를 수행하는 방식이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Developer
   ↓
Git Push / Pull Request
   ↓
Repository
   ↓
Build
   ↓
Test
   ↓
성공 / 실패 Feedback
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CI의 목적은 단순히 코드를 Git에 합치는 것이 아니다.&lt;/p&gt;
&lt;p&gt;변경된 코드가&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;정상적으로 빌드되는가?

테스트를 통과하는가?

기존 코드와 합쳐도 문제가 없는가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;를 자동으로 검증하고 &lt;strong&gt;문제를 빠르게 발견하는 것&lt;/strong&gt;이 핵심이다.&lt;/p&gt;
&lt;h3&gt;CI Pipeline&lt;/h3&gt;
&lt;p&gt;대표적인 CI Pipeline은 다음과 같은 흐름을 가진다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source
  ↓
Build
  ↓
Test
  ↓
Analyze
  ↓
Artifact
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Source&lt;/strong&gt;: Git Repository에서 코드 가져오기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Build&lt;/strong&gt;: 소스 코드를 JAR 등의 실행 가능한 결과물로 만들기&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Test&lt;/strong&gt;: Unit Test, Integration Test 등 실행&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Analyze&lt;/strong&gt;: 코드 품질, Coverage, 보안 검사&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Artifact&lt;/strong&gt;: JAR, Docker Image 등의 배포 결과물 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;모든 Pipeline이 반드시 이 단계를 전부 사용하는 것은 아니며 프로젝트에 필요한 단계만 구성할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;3. CD란?&lt;/h1&gt;
&lt;p&gt;CI를 통해 코드가 정상적으로 동작한다는 것을 확인했다면 다음 단계는 &lt;strong&gt;배포&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;CD는 두 가지 의미로 사용된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Continuous Delivery
Continuous Deployment
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3.1 Continuous Delivery&lt;/h2&gt;
&lt;p&gt;Continuous Delivery는 &lt;strong&gt;지속적 제공&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;CI를 통과한 애플리케이션을 언제든 운영 환경에 배포할 수 있는 상태까지 자동으로 준비한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Build
 ↓
Test
 ↓
Artifact
 ↓
배포 준비 완료
 ↓
사람의 승인
 ↓
Production 배포
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉 &lt;strong&gt;배포 가능한 상태까지는 자동화하지만 실제 운영 배포는 사람이 결정한다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.2 Continuous Deployment&lt;/h2&gt;
&lt;p&gt;Continuous Deployment는 &lt;strong&gt;지속적 배포&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;CI를 통과한 변경사항을 운영 환경까지 자동으로 배포한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Build
 ↓
Test
 ↓
Artifact
 ↓
Deploy
 ↓
Production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 차이는 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Continuous Delivery
→ 배포 가능한 상태까지 자동화
→ 운영 배포는 사람의 승인

Continuous Deployment
→ 운영 환경 배포까지 자동화
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;4. CI/CD 전체 흐름&lt;/h1&gt;
&lt;p&gt;CI와 CD를 연결하면 다음과 같은 Pipeline을 만들 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Developer
   ↓
Git Push / PR
   ↓
──────── CI ────────
   ↓
Build
   ↓
Test
   ↓
Artifact
   ↓
──────── CD ────────
   ↓
Deploy
   ↓
Production
   ↓
User
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Docker를 사용하는 환경이라면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source Code
    ↓
Build / Test
    ↓
Docker Image 생성
    ↓
Container Registry Push
    ↓
Server에 배포
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 형태가 될 수 있다.&lt;/p&gt;
&lt;p&gt;즉&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;CI는 변경된 코드를 지속적으로 검증하는 과정이고, CD는 검증된 결과물을 사용자에게 전달하는 과정이다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;5. CI/CD는 무엇으로 구현할까?&lt;/h1&gt;
&lt;p&gt;CI/CD는 하나의 기술 이름이 아니라 &lt;strong&gt;개발 및 배포 과정을 자동화하는 방식&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;이를 구현하기 위해 다양한 도구를 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GitHub Actions
Jenkins
GitLab CI/CD
ArgoCD 등
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어 &lt;strong&gt;GitHub Actions&lt;/strong&gt;를 사용하면 GitHub에서 Push나 Pull Request가 발생했을 때&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Pull Request
     ↓
GitHub Actions
     ↓
Build
     ↓
Test
     ↓
성공 / 실패 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 자동으로 CI Pipeline을 실행할 수 있다.&lt;/p&gt;
&lt;p&gt;또 필요하다면 Docker Image를 만들거나 서버에 배포하는 CD 과정까지 자동화할 수도 있다.&lt;/p&gt;
&lt;p&gt;즉 중요한 것은 특정 도구의 이름보다&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;어떤 이벤트가 Pipeline을 실행하는가?

어떤 Build와 Test를 수행하는가?

검증된 결과물을 어떻게 배포하는가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;를 이해하는 것이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;6. 배포 전략은 왜 필요할까?&lt;/h1&gt;
&lt;p&gt;CD를 구축했다고 해서 기존 서버를 바로 종료하고 새 버전을 실행하면 되는 것은 아니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;v1 종료
   ↓
서비스 중단
   ↓
v2 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 배포하면 서비스가 중단될 수 있고, 새로운 버전에 문제가 있다면 모든 사용자에게 영향을 줄 수도 있다.&lt;/p&gt;
&lt;p&gt;따라서 서비스를 유지하면서 안전하게 새로운 버전을 배포하기 위한 &lt;strong&gt;배포 전략&lt;/strong&gt;이 필요하다.&lt;/p&gt;
&lt;p&gt;대표적으로&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Rolling Update
Blue-Green
Canary
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;7. Rolling Update&lt;/h1&gt;
&lt;p&gt;Rolling Update는 기존 서버를 한 번에 모두 교체하지 않고 &lt;strong&gt;점진적으로 새로운 버전으로 변경하는 방식&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;v1  v1  v1  v1

        ↓

v2  v1  v1  v1

        ↓

v2  v2  v1  v1

        ↓

v2  v2  v2  v2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;별도의 환경을 하나 더 구성하지 않아도 되기 때문에 리소스를 효율적으로 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;다만 배포 중에는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;v1 + v2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가 동시에 실행될 수 있으므로 두 버전 사이의 호환성을 고려해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;8. Blue-Green Deployment&lt;/h1&gt;
&lt;p&gt;Blue-Green은 &lt;strong&gt;기존 환경과 새로운 환경 두 개를 준비한 뒤 트래픽을 전환하는 방식&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Blue
v1
현재 운영

      ↓ 트래픽 전환

Green
v2
새 버전
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;새로운 Green 환경에 먼저 배포하고 문제가 없는지 확인한 뒤 트래픽을 전환한다.&lt;/p&gt;
&lt;p&gt;문제가 발생하면 다시 Blue 환경으로 빠르게 돌릴 수 있어 &lt;strong&gt;Rollback이 쉽다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;반면 두 환경을 동시에 유지해야 하기 때문에 추가적인 인프라 비용이 필요할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;9. Canary Deployment&lt;/h1&gt;
&lt;p&gt;Canary는 새로운 버전을 &lt;strong&gt;일부 사용자에게 먼저 배포하는 방식&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;v1 → 95%

v2 → 5%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;문제가 없는지 확인하면서&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;5%
 ↓
20%
 ↓
50%
 ↓
100%
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 새 버전의 트래픽을 점차 증가시킨다.&lt;/p&gt;
&lt;p&gt;따라서 문제가 발생하더라도 처음에는 일부 사용자만 영향을 받기 때문에 배포 위험을 줄일 수 있다.&lt;/p&gt;
&lt;p&gt;다만 트래픽 분배와 지속적인 모니터링이 필요해 운영이 복잡해질 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;10. 배포 전략 비교&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;전략&lt;/th&gt;
&lt;th&gt;방식&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rolling Update&lt;/td&gt;
&lt;td&gt;서버를 순차적으로 교체&lt;/td&gt;
&lt;td&gt;리소스 효율적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blue-Green&lt;/td&gt;
&lt;td&gt;두 환경을 준비하고 트래픽 전환&lt;/td&gt;
&lt;td&gt;빠른 Rollback&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canary&lt;/td&gt;
&lt;td&gt;일부 트래픽부터 새 버전 적용&lt;/td&gt;
&lt;td&gt;장애 영향 최소화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;간단하게 구분하면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;서버를 하나씩 바꾼다
→ Rolling Update

새 환경을 준비하고 한 번에 전환한다
→ Blue-Green

일부 사용자에게 먼저 적용한다
→ Canary
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;11. 핵심 정리&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;개념&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CI&lt;/td&gt;
&lt;td&gt;코드 변경을 자주 통합하고 자동으로 Build/Test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continuous Delivery&lt;/td&gt;
&lt;td&gt;배포 가능한 상태까지 자동화, 최종 배포는 승인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continuous Deployment&lt;/td&gt;
&lt;td&gt;운영 환경 배포까지 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pipeline&lt;/td&gt;
&lt;td&gt;Build, Test, Deploy 등이 이어지는 자동화 과정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Artifact&lt;/td&gt;
&lt;td&gt;검증이 완료된 배포 결과물&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions / Jenkins 등&lt;/td&gt;
&lt;td&gt;CI/CD Pipeline을 구현하는 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rolling Update&lt;/td&gt;
&lt;td&gt;인스턴스를 점진적으로 교체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blue-Green&lt;/td&gt;
&lt;td&gt;두 환경을 준비하고 트래픽 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canary&lt;/td&gt;
&lt;td&gt;일부 사용자에게 먼저 새 버전 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;전체 흐름은 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;코드 변경
   ↓
CI
   ↓
Build / Test
   ↓
Artifact
   ↓
CD
   ↓
Deploy
   ↓
Production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결국 CI/CD의 핵심은&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;개발자가 작성한 코드를 지속적으로 자동 검증하고, 검증된 결과물을 반복 가능하고 안전하게 사용자에게 전달하는 것&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이라고 이해할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[CS] 트랜잭션 ACID · 격리수준</title><link>https://www.shyunii.dev/posts/transaction-acid-isolation-levels/</link><guid isPermaLink="true">https://www.shyunii.dev/posts/transaction-acid-isolation-levels/</guid><description>트랜잭션 ACID와 격리수준, MVCC에 대한 학습내용 정리</description><pubDate>Wed, 14 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1. 트랜잭션은 왜 필요할까?&lt;/h1&gt;
&lt;p&gt;데이터베이스에서는 하나의 작업을 처리하기 위해 여러 SQL을 실행해야 하는 경우가 많다.&lt;/p&gt;
&lt;p&gt;대표적인 예로 &lt;strong&gt;계좌 이체&lt;/strong&gt;가 있다.&lt;/p&gt;
&lt;p&gt;A 계좌에서 B 계좌로 10만 원을 이체한다고 생각해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE accounts
SET balance = balance - 100000
WHERE id = &apos;A&apos;;

UPDATE accounts
SET balance = balance + 100000
WHERE id = &apos;B&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정상적으로 처리된다면,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A 계좌에서 10만 원 차감
        ↓
 B 계좌에 10만 원 추가
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;되어야 한다.&lt;/p&gt;
&lt;p&gt;그런데 첫 번째 SQL은 성공했지만 두 번째 SQL을 실행하는 도중 서버에 문제가 발생한다면 어떻게 될까?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A 계좌 -10만원 O

      ↓

   서버 장애

      ↓

B 계좌 +10만원 X
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A의 돈은 사라졌지만 B에게는 돈이 들어오지 않는 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;따라서 두 작업을 각각 별개의 작업으로 처리해서는 안 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A 계좌 출금
	+
B 계좌 입금

= 하나의 작업
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 묶어서 처리해야 한다.&lt;/p&gt;
&lt;p&gt;이때 사용하는 것이 **Transaction(트랜잭션)**이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;2. 트랜잭션이란?&lt;/h1&gt;
&lt;p&gt;트랜잭션은 데이터베이스에서 수행되는 &lt;strong&gt;하나의 논리적인 작업 단위&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;하나의 트랜잭션은 여러 SQL로 구성될 수 있으며 기본적인 원칙은:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;모든 작업이 성공하거나, 모든 작업이 실패해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;즉, &lt;strong&gt;All or Nothing&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;BEGIN;

UPDATE accounts
SET balance = balance - 100000
WHERE id = &apos;A&apos;;

UPDATE accounts
SET balance = balance + 100000
WHERE id = &apos;B&apos;;

COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;모든 작업이 정상적으로 실행되면 &lt;code&gt;COMMIT&lt;/code&gt;을 수행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;COMMIT
→ 트랜잭션의 변경사항 확정
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;반대로 문제가 발생하면 &lt;code&gt;ROLLBACK&lt;/code&gt;을 수행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ROLLBACK
→ 트랜잭션에서 수행한 변경사항 취소
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예를 들어,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   BEGIN

A 계좌 -10만원
     ↓
B 계좌 +10만원
     ↓
  COMMIT

→ 모두 반영
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;도중 문제가 발생하면,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;   BEGIN

A 계좌 -10만원
     ↓
  오류 발생
     ↓
  ROLLBACK

→ A 계좌 변경도 취소
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h1&gt;3. ACID란?&lt;/h1&gt;
&lt;p&gt;트랜잭션은 데이터의 신뢰성을 보장하기 위해 일반적으로 &lt;strong&gt;ACID&lt;/strong&gt;라고 불리는 네 가지 성질을 가진다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A → Atomicity
C → Consistency
I → Isolation
D → Durability
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3.1 Atomicity - 원자성&lt;/h2&gt;
&lt;p&gt;원자성은 트랜잭션에 포함된 작업이:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;전부 실행되거나 전부 실행되지 않아야 한다&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;는 성질이다.&lt;/p&gt;
&lt;p&gt;예를 들어 주문을 생성한다고 생각해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;주문 생성
   ↓
주문 상품 저장
   ↓
재고 감소
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 세 작업이 하나의 트랜잭션이라면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;주문 생성       O
주문 상품 저장   O
재고 감소       O
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;전부 성공하거나&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;주문 생성       X
주문 상품 저장   X
재고 감소       X
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;전부 취소되어야 한다.&lt;/p&gt;
&lt;p&gt;중간까지만 반영되면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;주문은 생성됐는데
재고는 줄지 않음
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 데이터 불일치가 발생할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;DBMS는 Undo Log 등의 메커니즘을 통해 실패한 작업을 되돌릴 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.2 Consistency - 일관성&lt;/h2&gt;
&lt;p&gt;일관성은:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;트랜잭션 실행 전과 실행 후에도 데이터베이스의 규칙이 유지되어야 한다&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;는 성질이다.&lt;/p&gt;
&lt;p&gt;예를 들어 계좌의 잔액은 음수가 될 수 없다는 규칙이 있다고 하자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;balance &amp;gt;= 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;잔액이 5만 원인데 10만 원을 출금해서&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;balance = -50000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 된다면 데이터베이스의 규칙이 깨진 것이다.&lt;/p&gt;
&lt;p&gt;또한&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Primary Key
Foreign Key
Unique
Not Null
Check
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 무결성 제약조건이나 애플리케이션의 비즈니스 규칙 역시 일관성을 유지하는 데 사용된다.&lt;/p&gt;
&lt;p&gt;즉,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Consistency

트랜잭션 전
정상적인 DB 상태

     ↓

트랜잭션 실행

     ↓

트랜잭션 후
정상적인 DB 상태
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;를 유지해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.3 Isolation - 격리성&lt;/h2&gt;
&lt;p&gt;여러 사용자가 동시에 요청을 보내면 데이터베이스에서도 여러 트랜잭션이 동시에 실행된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Transaction A ─┐
               ├→ Database
Transaction B ─┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 한 트랜잭션의 작업 때문에 다른 트랜잭션이 예상하지 못한 데이터를 읽게 된다면 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;격리성은&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;동시에 실행되는 트랜잭션들이 서로에게 잘못된 영향을 주지 않도록 보장하는 성질&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이다.&lt;/p&gt;
&lt;p&gt;이론적으로는 각 트랜잭션이 마치 혼자 실행되는 것과 같은 결과를 만드는 것이 가장 안전하다.&lt;/p&gt;
&lt;p&gt;하지만 모든 트랜잭션을 실제로 하나씩 실행한다면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1 실행
 ↓
T1 완료
 ↓
T2 실행
 ↓
T2 완료
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;동시성이 크게 떨어져 성능이 나빠질 수 있다.&lt;/p&gt;
&lt;p&gt;그래서 DBMS는 **Isolation Level(격리 수준)**을 통해 격리성과 동시성 사이의 균형을 조절한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.4 Durability - 지속성&lt;/h2&gt;
&lt;p&gt;지속성은&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;한번 COMMIT된 데이터는 시스템에 문제가 발생해도 유지되어야 한다&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;는 성질이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Transaction
    ↓
 COMMIT
    ↓
서버 장애
    ↓
재시작
    ↓
COMMIT된 데이터 유지
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;DBMS는 WAL(Write-Ahead Logging), Redo Log 등의 로그를 이용해 장애가 발생했을 때 데이터를 복구할 수 있다.&lt;/p&gt;
&lt;p&gt;즉,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;COMMIT 완료
→ 변경사항이 영구적으로 보장
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;되는 것이 지속성이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;4. 왜 격리 수준이 필요할까?&lt;/h1&gt;
&lt;p&gt;가장 안전한 방법은 모든 트랜잭션을 완전히 격리하는 것이다.&lt;/p&gt;
&lt;p&gt;하지만 격리를 강하게 할수록 다른 트랜잭션을 기다리거나 충돌을 검사해야 하는 경우가 늘어날 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;격리 수준 낮음

동시성 ↑
성능 ↑
데이터 이상 현상 가능성 ↑
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;반대로&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;격리 수준 높음

동시성 ↓
충돌/대기 가능성 ↑
데이터 안정성 ↑
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 균형을 조절하기 위해 SQL 표준에서는 대표적으로 네 가지 격리 수준을 정의한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;READ UNCOMMITTED
        ↓
READ COMMITTED
        ↓
REPEATABLE READ
        ↓
SERIALIZABLE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;아래로 갈수록 격리 수준이 높아진다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;5. 트랜잭션에서 발생할 수 있는 이상 현상&lt;/h1&gt;
&lt;p&gt;격리 수준을 이해하려면 먼저 동시에 트랜잭션을 실행할 때 어떤 문제가 발생할 수 있는지 알아야 한다.&lt;/p&gt;
&lt;p&gt;대표적으로&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Dirty Read
Non-Repeatable Read
Phantom Read
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가 있다.&lt;/p&gt;
&lt;p&gt;추가로 실무에서 자주 언급되는 &lt;strong&gt;Lost Update&lt;/strong&gt;도 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5.1 Dirty Read&lt;/h2&gt;
&lt;p&gt;Dirty Read는 다른 트랜잭션이 &lt;strong&gt;아직 COMMIT하지 않은 데이터를 읽는 현상&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;예를 들어&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;초기 balance = 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Transaction A가&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A: balance = 500으로 변경

아직 COMMIT하지 않음
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;상태라고 하자.&lt;/p&gt;
&lt;p&gt;이때 Transaction B가&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;B: balance 조회

→ 500
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;을 읽어버린다.&lt;/p&gt;
&lt;p&gt;그런데 이후 A가&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ROLLBACK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;한다면 실제 데이터는 다시&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;balance = 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 된다.&lt;/p&gt;
&lt;p&gt;결국 B는 실제로 확정된 적이 없는 &lt;code&gt;500&lt;/code&gt;이라는 값을 기반으로 작업한 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1

1000 → 500
      아직 COMMIT X

          ↓

T2
500 읽음

          ↓

T1 ROLLBACK

          ↓

실제 값 = 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 &lt;strong&gt;Dirty Read&lt;/strong&gt;라고 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5.2 Non-Repeatable Read&lt;/h2&gt;
&lt;p&gt;Non-Repeatable Read는&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;하나의 트랜잭션 안에서 같은 데이터를 두 번 읽었는데 값이 달라지는 현상&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이다.&lt;/p&gt;
&lt;p&gt;예를 들어&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;balance = 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일 때&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1
SELECT balance
→ 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그 사이 T2가&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T2
UPDATE balance = 500
COMMIT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;한다.&lt;/p&gt;
&lt;p&gt;그리고 T1이 다시 조회한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1
SELECT balance
→ 500
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그러면 같은 트랜잭션 안에서&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;첫 번째 조회 → 1000

두 번째 조회 → 500
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 결과가 바뀐다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1            T2

SELECT
→ 1000

              UPDATE
              → 500

              COMMIT

SELECT
→ 500
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 &lt;strong&gt;Non-Repeatable Read&lt;/strong&gt;라고 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5.3 Phantom Read&lt;/h2&gt;
&lt;p&gt;Phantom Read는 하나의 트랜잭션에서 같은 &lt;strong&gt;범위 조건&lt;/strong&gt;으로 조회했는데 없던 행이 나타나거나 기존 행이 사라지는 현상이다.&lt;/p&gt;
&lt;p&gt;예를 들어&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT *
FROM products
WHERE price &amp;gt;= 10000;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;을 실행했더니 10개의 상품이 조회됐다고 하자.&lt;/p&gt;
&lt;p&gt;그 사이 다른 트랜잭션이&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;INSERT INTO products(name, price)
VALUES (&apos;New Product&apos;, 20000);

COMMIT;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;을 수행한다.&lt;/p&gt;
&lt;p&gt;첫 번째 트랜잭션에서 같은 조건으로 다시 조회했더니&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;첫 번째 조회 → 10개

두 번째 조회 → 11개
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;가 될 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1                         T2

price &amp;gt;= 10000 조회
→ 10개

                           새로운 상품 INSERT
                           COMMIT

다시 조회
→ 11개
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마치 없던 데이터가 갑자기 나타난 것처럼 보이기 때문에 &lt;strong&gt;Phantom Read&lt;/strong&gt;라고 한다.&lt;/p&gt;
&lt;p&gt;Non-Repeatable Read와 차이는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Non-Repeatable Read
→ 기존 행의 값이 변경됨

Phantom Read
→ 조회되는 행 자체가 추가되거나 사라짐
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5.4 Lost Update&lt;/h2&gt;
&lt;p&gt;Lost Update는 두 트랜잭션이 같은 데이터를 동시에 수정하면서 &lt;strong&gt;한쪽의 변경사항이 사라지는 현상&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;초기 값이&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;balance = 1000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이라고 하자.&lt;/p&gt;
&lt;p&gt;두 트랜잭션이 동시에 읽는다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1 → 1000 조회
T2 → 1000 조회
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;T1은 100을 추가한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1000 + 100
→ 1100
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;T2는 200을 추가한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1000 + 200
→ 1200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 각각 저장하면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1 UPDATE → 1100

T2 UPDATE → 1200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;최종 값은&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 된다.&lt;/p&gt;
&lt;p&gt;하지만 실제로 두 변경사항이 모두 적용됐다면&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1000 + 100 + 200
= 1300
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 되어야 한다.&lt;/p&gt;
&lt;p&gt;즉 T1의 수정이 T2에게 덮어씌워져 사라진 것이다.&lt;/p&gt;
&lt;p&gt;이를 &lt;strong&gt;Lost Update&lt;/strong&gt;라고 한다.&lt;/p&gt;
&lt;p&gt;이런 문제는 상황에 따라&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT ...
FOR UPDATE;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;와 같은 명시적 Lock이나,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;UPDATE accounts
SET balance = balance + 100
WHERE id = 1;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 DB에서 직접 원자적으로 값을 변경하는 방법 등을 사용할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;6. Isolation Level&lt;/h1&gt;
&lt;h2&gt;6.1 READ UNCOMMITTED&lt;/h2&gt;
&lt;p&gt;가장 낮은 격리 수준이다.&lt;/p&gt;
&lt;p&gt;다른 트랜잭션이 아직 COMMIT하지 않은 데이터도 읽을 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1
UPDATE
아직 COMMIT X

       ↓

T2
변경된 값 조회 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 &lt;strong&gt;Dirty Read가 발생할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;READ UNCOMMITTED

Dirty Read             O
Non-Repeatable Read    O
Phantom Read           O
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;데이터의 신뢰성이 낮기 때문에 일반적인 서비스에서는 거의 사용하지 않는다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6.2 READ COMMITTED&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;COMMIT된 데이터만 읽을 수 있는 격리 수준&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;즉, Dirty Read를 방지할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 하나의 트랜잭션이 실행되는 동안 다른 트랜잭션이 데이터를 수정하고 COMMIT하면 다음 조회에서 변경된 값이 보일 수 있다.&lt;/p&gt;
&lt;p&gt;따라서&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Non-Repeatable Read
Phantom Read
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;는 발생할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;READ COMMITTED

Dirty Read             X
Non-Repeatable Read    O
Phantom Read           O
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PostgreSQL과 Oracle 등에서 기본 격리 수준으로 사용된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6.3 REPEATABLE READ&lt;/h2&gt;
&lt;p&gt;REPEATABLE READ는 같은 트랜잭션 안에서 &lt;strong&gt;이미 읽은 데이터가 반복해서 동일하게 보이도록 하는 격리 수준&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;따라서&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Dirty Read
→ 방지

Non-Repeatable Read
→ 방지
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;한다.&lt;/p&gt;
&lt;p&gt;SQL 표준 기준으로는 Phantom Read가 발생할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;REPEATABLE READ

Dirty Read             X
Non-Repeatable Read    X
Phantom Read           O
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다만 실제 DBMS의 구현은 SQL 표준과 완전히 동일하지 않을 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 PostgreSQL의 &lt;code&gt;REPEATABLE READ&lt;/code&gt;는 MVCC 기반 Snapshot을 사용하기 때문에 일반적인 Phantom Read도 발생하지 않는다.&lt;/p&gt;
&lt;p&gt;MySQL InnoDB 역시 MVCC와 Lock을 이용해 SQL 표준보다 강한 동작을 제공하는 경우가 있다.&lt;/p&gt;
&lt;p&gt;따라서 격리 수준을 사용할 때는 &lt;strong&gt;사용하는 DBMS의 실제 구현을 함께 확인해야 한다.&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6.4 SERIALIZABLE&lt;/h2&gt;
&lt;p&gt;가장 높은 격리 수준이다.&lt;/p&gt;
&lt;p&gt;동시에 실행되는 트랜잭션의 결과가 마치&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1 전체 실행
↓
T2 전체 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T2 전체 실행
↓
T1 전체 실행
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;한 것과 같은 결과가 나오도록 보장한다.&lt;/p&gt;
&lt;p&gt;따라서 대표적인 이상 현상을 가장 강하게 방지한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SERIALIZABLE

Dirty Read             X
Non-Repeatable Read    X
Phantom Read           X
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;다만 중요한 것은&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SERIALIZABLE이 모든 트랜잭션을 실제로 하나씩 실행한다는 뜻은 아니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;가능하면 동시에 실행하되 그 결과가 &lt;strong&gt;어떤 직렬 실행과 동일하도록 보장&lt;/strong&gt;하는 것이다.&lt;/p&gt;
&lt;p&gt;충돌을 안전하게 처리할 수 없는 경우 DBMS는 한 트랜잭션을 실패시키고 애플리케이션에 재시도를 요구할 수도 있다.&lt;/p&gt;
&lt;p&gt;격리 수준이 높아질수록 이러한 충돌이나 대기 비용이 증가할 수 있기 때문에 무조건 높은 격리 수준을 사용하는 것이 정답은 아니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;7. 격리 수준 정리&lt;/h1&gt;
&lt;p&gt;SQL 표준을 기준으로 대표적인 이상 현상을 정리하면 다음과 같다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;격리 수준&lt;/th&gt;
&lt;th&gt;Dirty Read&lt;/th&gt;
&lt;th&gt;Non-Repeatable Read&lt;/th&gt;
&lt;th&gt;Phantom Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;READ UNCOMMITTED&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;READ COMMITTED&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REPEATABLE READ&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SERIALIZABLE&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;외우기보다는 단계적으로 이해하는 것이 쉽다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;READ UNCOMMITTED
→ 아무것도 제대로 막지 않음

READ COMMITTED
→ COMMIT된 데이터만 읽음
→ Dirty Read 방지

REPEATABLE READ
→ 같은 데이터를 반복해서 읽어도 동일
→ Non-Repeatable Read 방지

SERIALIZABLE
→ 직렬 실행과 동일한 결과 보장
→ 가장 강한 격리
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;단, 이 표는 &lt;strong&gt;SQL 표준에서 정의한 최소 기준&lt;/strong&gt;이고 실제 동작은 PostgreSQL, MySQL 등의 DBMS 구현에 따라 달라질 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;8. Serializable은 정말 하나씩 실행하는 것일까?&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;SERIALIZABLE&lt;/code&gt;이라는 이름 때문에&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Transaction 1 끝날 때까지
Transaction 2는 무조건 기다린다
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;라고 생각하기 쉽다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;strong&gt;Serial Execution과 Serializable은 다른 개념&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;Serial Execution&lt;/h3&gt;
&lt;p&gt;트랜잭션을 실제로 하나씩 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1
READ
WRITE
COMMIT

      ↓

T2
READ
WRITE
COMMIT
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Serializable&lt;/h3&gt;
&lt;p&gt;트랜잭션을 동시에 실행할 수도 있지만 최종 결과가 어떤 Serial Execution과 동일하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;T1 ─────────┐
            ├→ 동시에 실행
T2 ─────────┘

결과

=

T1 → T2

또는

T2 → T1

순서로 실행한 결과
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉 Serializable의 핵심은&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;동시에 실행하더라도 데이터의 결과는 순차 실행한 것처럼 올바르게 만들자&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;는 것이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;9. MVCC는 왜 등장할까?&lt;/h1&gt;
&lt;p&gt;격리성을 높이기 위해 모든 읽기와 쓰기에 Lock을 사용하면 데이터의 안전성은 높아질 수 있지만 동시성이 떨어질 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Writer가 데이터 수정 중

       ↓

Reader가 기다림
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 상황이 계속 발생하면 많은 요청을 동시에 처리하기 어렵다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해 PostgreSQL과 MySQL InnoDB 같은 DBMS에서는 **MVCC(Multi-Version Concurrency Control)**를 사용한다.&lt;/p&gt;
&lt;p&gt;MVCC의 핵심 아이디어는&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;하나의 데이터에 여러 Version을 유지하고 각 트랜잭션에게 자신이 볼 수 있는 Version을 보여준다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;예를 들어&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;기존 데이터

User
name = Kim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;T2가 이름을 수정한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;새 Version

User
name = Lee
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 이전 Version을 바로 없애지 않고 유지한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Version 1
name = Kim

Version 2
name = Lee
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그러면 먼저 시작한 트랜잭션은 상황에 따라:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Version 1 → Kim
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;을 계속 읽고,&lt;/p&gt;
&lt;p&gt;나중에 시작한 트랜잭션은&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Version 2 → Lee
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;를 읽을 수 있다.&lt;/p&gt;
&lt;p&gt;따라서 일반적인 Snapshot Read에서는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Reader
→ 이전 Version 읽기

Writer
→ 새로운 Version 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;방식으로 읽기와 쓰기 사이의 불필요한 Blocking을 줄일 수 있다.&lt;/p&gt;
&lt;p&gt;즉 MVCC는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;여러 Version 유지
        ↓
트랜잭션별 Snapshot 제공
        ↓
읽기와 쓰기의 충돌 감소
        ↓
높은 동시성
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;을 위한 동시성 제어 방식이다.&lt;/p&gt;
&lt;p&gt;다만 MVCC가 있다고 해서 모든 동시성 문제가 자동으로 해결되는 것은 아니다.&lt;/p&gt;
&lt;p&gt;같은 데이터를 동시에 수정하는 경우에는 여전히 Lock이나 충돌 검사 등이 필요할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;10. 격리 수준이 높으면 무조건 좋은가?&lt;/h1&gt;
&lt;p&gt;그렇지는 않다.&lt;/p&gt;
&lt;p&gt;격리 수준이 높으면 데이터의 안전성은 높아지지만 그만큼&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Lock 대기

트랜잭션 충돌

Rollback

Retry
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;등이 증가해 처리량이 떨어질 수 있다.&lt;/p&gt;
&lt;p&gt;따라서 실제 시스템에서는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;얼마나 강한 데이터 일관성이 필요한가?

동시에 얼마나 많은 요청을 처리해야 하는가?

충돌이 얼마나 자주 발생하는가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;를 고려해 격리 수준을 선택해야 한다.&lt;/p&gt;
&lt;p&gt;예를 들어 단순한 게시글 조회와 금융 거래가 요구하는 데이터 일관성 수준은 다를 수 있다.&lt;/p&gt;
&lt;p&gt;따라서&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;가장 높은 격리 수준을 선택하는 것이 목적이 아니라 서비스에 필요한 수준의 격리성을 선택하는 것이 중요하다.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;11. 핵심 정리&lt;/h1&gt;
&lt;p&gt;트랜잭션과 격리 수준을 정리하면 다음과 같다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;개념&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Transaction&lt;/td&gt;
&lt;td&gt;여러 DB 연산을 하나의 논리적인 작업으로 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;COMMIT&lt;/td&gt;
&lt;td&gt;트랜잭션의 변경사항 확정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROLLBACK&lt;/td&gt;
&lt;td&gt;트랜잭션의 변경사항 취소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Atomicity&lt;/td&gt;
&lt;td&gt;모두 성공하거나 모두 실패&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consistency&lt;/td&gt;
&lt;td&gt;DB의 규칙과 일관성 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Isolation&lt;/td&gt;
&lt;td&gt;동시 트랜잭션 사이의 간섭 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Durability&lt;/td&gt;
&lt;td&gt;COMMIT된 데이터 영구 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dirty Read&lt;/td&gt;
&lt;td&gt;COMMIT되지 않은 데이터 읽음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-Repeatable Read&lt;/td&gt;
&lt;td&gt;같은 행을 다시 읽었는데 값이 변경됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Phantom Read&lt;/td&gt;
&lt;td&gt;같은 범위 조회에서 행이 추가되거나 사라짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lost Update&lt;/td&gt;
&lt;td&gt;한 트랜잭션의 수정이 다른 수정에 의해 사라짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MVCC&lt;/td&gt;
&lt;td&gt;여러 Version을 이용해 동시성을 높이는 방식&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;격리 수준은&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;READ UNCOMMITTED
        ↓
READ COMMITTED
        ↓
REPEATABLE READ
        ↓
SERIALIZABLE

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;순으로 격리성이 높아진다고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;가장 중요한 흐름은&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;여러 SQL을 하나의 작업으로 묶는다
            ↓
        Transaction
            ↓
신뢰할 수 있는 처리를 위해 ACID 보장
            ↓
하지만 여러 Transaction이 동시에 실행됨
            ↓
       이상 현상 발생 가능
            ↓
   Isolation Level로 제어
            ↓
MVCC / Lock 등의 방식으로 실제 구현
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이다.&lt;/p&gt;
&lt;p&gt;즉 트랜잭션의 핵심은 단순히 &lt;code&gt;@Transactional&lt;/code&gt;을 붙이는 것이 아니라&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;여러 작업의 원자성을 보장하고, 동시에 실행되는 트랜잭션 사이에서 데이터의 일관성을 어느 수준까지 지킬 것인지 결정하는 것&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;이라고 이해할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[CS] Redis 자료구조</title><link>https://www.shyunii.dev/posts/redis-data-structure/</link><guid isPermaLink="true">https://www.shyunii.dev/posts/redis-data-structure/</guid><description>Redis에 대한 학습 내용 정리</description><pubDate>Sat, 10 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1. Redis는 왜 사용할까?&lt;/h1&gt;
&lt;p&gt;일반적인 애플리케이션에서는 데이터를 데이터베이스에 저장하고 필요할 때 조회한다.&lt;/p&gt;
&lt;p&gt;예를 들어 사용자 정보를 조회한다고 생각해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client
 ↓
Spring Server
 ↓
Database
 ↓
사용자 조회
 ↓
응답
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그런데 동일한 데이터를 매우 자주 조회한다면 매 요청마다 DB에 접근하는 것은 비효율적일 수 있다.&lt;/p&gt;
&lt;p&gt;이때 자주 사용하는 것이 &lt;strong&gt;Cache&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;캐시는 자주 사용하는 데이터를 더 빠른 저장소에 임시로 저장해 두었다가 다시 사용하는 방식이다.&lt;/p&gt;
&lt;p&gt;캐시를 사용하면 3가지의 장점을 발생할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. 자주 접근하는 데이터의 빠른 검색
→ 사용자가 자주 요청하는 데이터를 미리 저장해두어, 재요청시 빠르게 제공

2. 원본 데이터 소스의 부하 감소
→ 캐시가 유효한 동안 반복적인 DB 조회를 줄여 DB의 부하 감소

3. 네트워크 지연 시간 최소화
→ 물리적으로 가까운 곳에 데이터를 꺼내기 때문에 응답 속도가 비약적으로 향상
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;대표적으로 Redis를 캐시로 사용하면 다음과 같은 구조를 만들 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client
  ↓
Server
  ↓
Redis
 │
 ├─ 데이터 있음 → 바로 반환
 │
 └─ 데이터 없음
        ↓
       DB 조회
        ↓
   Redis에 저장
        ↓
       반환
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;캐시에 데이터가 있는 경우를 &lt;strong&gt;Cache Hit&lt;/strong&gt;, 없는 경우를 &lt;strong&gt;Cache Miss&lt;/strong&gt;라고 한다.&lt;/p&gt;
&lt;p&gt;Cache Miss가 발생하면 DB에서 데이터를 가져온 뒤 Redis에 저장하는 방식이 대표적인 &lt;strong&gt;Cache-Aside&lt;/strong&gt; 패턴이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;2. Redis란?&lt;/h1&gt;
&lt;p&gt;**Redis(Remote Dictionary Server)**는 데이터를 주로 메모리에 저장하는 &lt;strong&gt;In-Memory Data Structure Store&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;기본적으로 Key-Value 형태로 데이터를 저장하지만 Value에 단순 문자열뿐 아니라 다양한 자료구조를 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;String
Hash
List
Set
Sorted Set
Stream
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그래서 Redis는 단순한 캐시뿐 아니라 세션, Counter, Ranking, 메시징 등에도 활용할 수 있다.&lt;/p&gt;
&lt;h3&gt;Redis는 왜 빠를까?&lt;/h3&gt;
&lt;p&gt;가장 큰 이유는 데이터를 주로 **Memory(RAM)**에서 처리하기 때문이다.&lt;/p&gt;
&lt;p&gt;또한 많은 Redis 명령은 하나의 메인 실행 흐름에서 순차적으로 처리되기 때문에 복잡한 Lock 경쟁을 줄일 수 있다.&lt;/p&gt;
&lt;p&gt;흔히 Redis를 &lt;strong&gt;싱글 스레드&lt;/strong&gt;라고 표현하지만 모든 작업이 하나의 스레드에서 실행된다는 의미는 아니다. Redis 6 이후에는 네트워크 데이터를 읽고 쓰는 I/O 작업에 여러 스레드를 사용할 수 있다. 하지만 GET, SET, INCR처럼 실제 Redis 데이터를 읽거나 수정하는 명령 실행은 주로 메인 스레드에서 순차적으로 처리된다. 따라서 하나의 Redis 명령은 실행 도중 다른 명령이 끼어들지 않아 원자적으로 처리할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;3. Redis의 주요 자료구조&lt;/h1&gt;
&lt;p&gt;Redis에서는 데이터의 특성에 맞는 자료구조를 선택하는 것이 중요하다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;자료구조&lt;/th&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;th&gt;대표 사용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;String&lt;/td&gt;
&lt;td&gt;하나의 값을 통째로 저장&lt;/td&gt;
&lt;td&gt;캐시, Counter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hash&lt;/td&gt;
&lt;td&gt;Field-Value 단위 저장&lt;/td&gt;
&lt;td&gt;필드별 조회, 수정이 필요한 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;List&lt;/td&gt;
&lt;td&gt;순서 있는 데이터&lt;/td&gt;
&lt;td&gt;Queue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Set&lt;/td&gt;
&lt;td&gt;중복 없는 집합&lt;/td&gt;
&lt;td&gt;좋아요, 태그&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sorted Set&lt;/td&gt;
&lt;td&gt;Score 기반 정렬&lt;/td&gt;
&lt;td&gt;Ranking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stream&lt;/td&gt;
&lt;td&gt;메시지 기록&lt;/td&gt;
&lt;td&gt;이벤트 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;3.1 String&lt;/h2&gt;
&lt;p&gt;가장 기본적인 자료구조이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SET user:1:name &quot;Evan&quot;

GET user:1:name
→ &quot;Evan&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;문자열뿐 아니라 직렬화된 객체나 숫자 등도 저장할 수 있다.&lt;/p&gt;
&lt;p&gt;특히 숫자에는 &lt;code&gt;INCR&lt;/code&gt;, &lt;code&gt;DECR&lt;/code&gt; 같은 연산을 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SET views 100

INCR views
→ 101
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 &lt;strong&gt;캐시, 조회수, 요청 횟수, Counter&lt;/strong&gt; 등에 사용할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.2 Hash&lt;/h2&gt;
&lt;p&gt;하나의 Key 안에 여러 &lt;strong&gt;Field-Value&lt;/strong&gt;를 저장하는 자료구조이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;user:1
 │
 ├─ name → Evan
 ├─ age  → 30
 └─ job  → Backend
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;HSET user:1 name Evan age 30

HGET user:1 name
→ Evan
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;객체 전체가 아니라 특정 Field만 조회하거나 수정할 수 있기 때문에 사용자 정보나 세션처럼 여러 속성을 가진 데이터를 표현하기 좋다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.3 List&lt;/h2&gt;
&lt;p&gt;List는 &lt;strong&gt;순서가 유지되는 자료구조&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;양쪽 끝에서 값을 추가하거나 제거할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LPUSH / RPUSH
LPOP  / RPOP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 이용하면 FIFO 방식의 Queue 등을 만들 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;task1 → task2 → task3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Redis는 데이터가 들어올 때까지 기다리는 &lt;code&gt;BLPOP&lt;/code&gt;, &lt;code&gt;BRPOP&lt;/code&gt; 같은 Blocking 연산도 지원하기 때문에 간단한 작업 Queue에 활용할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.4 Set&lt;/h2&gt;
&lt;p&gt;Set은 &lt;strong&gt;중복을 허용하지 않는 집합&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;post:1:likes

user1
user2
user3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;같은 &lt;code&gt;user1&lt;/code&gt;을 다시 추가해도 중복 저장되지 않는다.&lt;/p&gt;
&lt;p&gt;또한 교집합, 합집합, 차집합 등의 연산을 지원한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;A = {Redis, Kafka, Spring}
B = {Redis, Docker, Spring}

교집합
= {Redis, Spring}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 좋아요 사용자, 태그, 친구 목록 등 &lt;strong&gt;중복되지 않는 데이터&lt;/strong&gt;를 다룰 때 유용하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.5 Sorted Set&lt;/h2&gt;
&lt;p&gt;Sorted Set은 Set처럼 Member의 중복을 허용하지 않으면서 각 Member에 &lt;strong&gt;Score&lt;/strong&gt;를 부여한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;userA → 1200
userB → 1700
userC → 1500
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Score를 이용하면:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. userB → 1700
2. userC → 1500
3. userA → 1200
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 순위를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;따라서 &lt;strong&gt;게임 Ranking, 인기 검색어, 점수 기반 순위&lt;/strong&gt; 등에 적합하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Set
→ 중복 X
→ 정렬 기준 X

Sorted Set
→ 중복 X
→ Score 기준 정렬 O
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3.6 Stream&lt;/h2&gt;
&lt;p&gt;Stream은 메시지나 이벤트를 순서대로 저장하는 &lt;strong&gt;로그 형태의 자료구조&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 → 주문 생성
2 → 결제 완료
3 → 배송 시작
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;List와 달리 단순히 메시지를 꺼내는 것뿐 아니라 &lt;strong&gt;메시지를 기록하고 처리 상태를 관리할 수 있다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;여러 Consumer가 메시지를 처리할 때는 &lt;strong&gt;Consumer Group&lt;/strong&gt;을 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;             Stream
                ↓
        Consumer Group
         /           \
 Consumer A       Consumer B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처리가 완료되면 &lt;code&gt;XACK&lt;/code&gt;를 보내고, 아직 ACK되지 않은 메시지는 Pending 상태로 관리할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;XACK&lt;/code&gt;는 메시지 자체를 삭제하는 것이 아니라 &lt;strong&gt;해당 Consumer Group에서 처리가 완료됐음을 기록하는 것&lt;/strong&gt;이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;4. TTL은 왜 필요할까?&lt;/h1&gt;
&lt;p&gt;캐시에는 DB와 다른 오래된 데이터가 남을 수 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 DB의 사용자 이름은 변경됐는데 Redis에는 이전 이름이 계속 저장되어 있을 수 있다.&lt;/p&gt;
&lt;p&gt;이런 데이터를 &lt;strong&gt;Stale Data&lt;/strong&gt;라고 한다.&lt;/p&gt;
&lt;p&gt;Redis에서는 Key에 **TTL(Time To Live)**을 설정해 일정 시간이 지나면 자동으로 만료시킬 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SET user:1 &quot;data&quot; EX 300
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;캐시 저장
   ↓
300초
   ↓
자동 만료
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 실제 캐시에서는 TTL 설정이나 캐시 삭제·갱신 등을 통해 DB와 캐시 사이의 데이터를 관리해야 한다.&lt;/p&gt;
&lt;h3&gt;Eviction&lt;/h3&gt;
&lt;p&gt;TTL이 시간이 지나면서 데이터를 만료시키는 기능이라면,
Eviction은 Redis의 메모리가 부족할 때 어떤 데이터를 제거할지 결정하는 정책이다.&lt;/p&gt;
&lt;p&gt;대표적으로:&lt;/p&gt;
&lt;p&gt;allkeys-lru
→ 가장 오래 사용되지 않은 Key 제거&lt;/p&gt;
&lt;p&gt;allkeys-lfu
→ 사용 빈도가 낮은 Key 제거&lt;/p&gt;
&lt;p&gt;noeviction
→ Key를 제거하지 않고 새로운 쓰기 요청에 오류 반환&lt;/p&gt;
&lt;p&gt;따라서 Redis를 캐시로 사용할 때는 TTL뿐 아니라
메모리 제한과 Eviction 정책도 함께 고려해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;5. Pub/Sub과 Stream의 차이&lt;/h1&gt;
&lt;p&gt;Redis에서는 &lt;strong&gt;Pub/Sub(Publish/Subscribe)&lt;/strong&gt; 방식의 메시징도 사용할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Publisher
    ↓
 Channel
  /     \
 ↓       ↓
A         B
Subscriber
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Publisher가 메시지를 발행하면 현재 해당 Channel을 구독 중인 Subscriber에게 바로 전달된다.&lt;/p&gt;
&lt;p&gt;하지만 &lt;strong&gt;Pub/Sub은 메시지를 저장하지 않는다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;따라서 Subscriber가 연결되어 있지 않았다면 해당 메시지를 나중에 다시 받을 수 없다.&lt;/p&gt;
&lt;p&gt;Stream과 비교하면:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Pub/Sub&lt;/th&gt;
&lt;th&gt;Stream&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;실시간 수신&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메시지 저장&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;과거 메시지 조회&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consumer Group&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACK / Pending&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;가벼운 실시간 Broadcast&lt;/td&gt;
&lt;td&gt;기록, 재처리가 필요한 이벤트 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;즉:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;저장이나 재처리가 필요 없는 실시간 메시지 전파
→ Pub/Sub

메시지를 기록하고 처리 상태까지 관리
→ Stream
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이라고 이해하면 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;6. Redis의 원자성과 Transaction&lt;/h1&gt;
&lt;p&gt;Redis의 &lt;code&gt;INCR&lt;/code&gt;처럼 &lt;strong&gt;하나의 명령은 원자적으로 처리&lt;/strong&gt;할 수 있다.&lt;/p&gt;
&lt;p&gt;하지만:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GET stock
↓
값 계산
↓
SET stock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 여러 명령을 조합하면 그 사이에 다른 Client의 명령이 실행될 수 있다.&lt;/p&gt;
&lt;p&gt;즉:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Redis가 명령을 순차적으로 처리한다&lt;br /&gt;
= 여러 명령으로 만든 로직 전체가 자동으로 원자적이다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;는 아니다.&lt;/p&gt;
&lt;p&gt;이런 경우 Redis의 **Transaction(MULTI/EXEC)**이나 Lua Script 등을 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;또 &lt;strong&gt;Pipeline&lt;/strong&gt;도 자주 등장하는데 Transaction과 목적이 다르다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Pipeline
→ 여러 명령을 묶어서 전송
→ 네트워크 RTT 감소
→ 성능 최적화

Transaction
→ 여러 명령을 Queue에 저장한 뒤 실행
→ 실행 중 다른 Client의 명령이 끼어들지 않음
→ 단, Redis Transaction은 일반적인 RDB Transaction과 달리 실행 중 오류가 발생했다고 해서 이미 실행된 명령을 Rollback하지 않는다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 Pipeline을 사용했다고 해서 자동으로 Transaction이 되는 것은 아니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;7. RDB와 AOF&lt;/h1&gt;
&lt;p&gt;Redis는 In-Memory 기반이지만 데이터를 디스크에 저장하는 &lt;strong&gt;Persistence(영속성)&lt;/strong&gt; 기능도 제공한다.&lt;/p&gt;
&lt;p&gt;대표적인 방법이 &lt;strong&gt;RDB와 AOF&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;RDB&lt;/h3&gt;
&lt;p&gt;특정 시점의 Redis 데이터를 &lt;strong&gt;Snapshot&lt;/strong&gt;으로 저장한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Redis 상태
   ↓
Snapshot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;파일이 비교적 작고 복구가 빠른 편이지만 마지막 Snapshot 이후 변경된 데이터는 장애 발생 시 잃을 수 있다.&lt;/p&gt;
&lt;h3&gt;AOF&lt;/h3&gt;
&lt;p&gt;Redis에서 수행된 &lt;strong&gt;쓰기 명령을 Log 형태로 기록&lt;/strong&gt;한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SET A 1
INCR A
SET B 10
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;RDB보다 많은 변경 내용을 보존할 수 있지만 파일 크기가 커질 수 있고 쓰기 및 복구 비용이 증가할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RDB
→ Snapshot

AOF
→ 쓰기 명령 Log
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;두 방식을 함께 사용하는 것도 가능하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;8. Sentinel과 Cluster&lt;/h1&gt;
&lt;p&gt;Redis 서버 하나에 장애가 발생하면 서비스를 계속 사용할 수 없을 수 있다.&lt;/p&gt;
&lt;h3&gt;Sentinel&lt;/h3&gt;
&lt;p&gt;Sentinel은 Redis 서버를 감시하고 Primary 장애 시 Replica를 새로운 Primary로 승격하는 &lt;strong&gt;Failover&lt;/strong&gt;를 지원한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Primary 장애
    ↓
Sentinel 감지
    ↓
Replica 승격
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉 **고가용성(High Availability)**이 주요 목적이다.&lt;/p&gt;
&lt;h3&gt;Cluster&lt;/h3&gt;
&lt;p&gt;Cluster는 데이터를 여러 Redis Node에 나누어 저장하는 &lt;strong&gt;Sharding&lt;/strong&gt;을 지원한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Node A → 데이터 일부
Node B → 데이터 일부
Node C → 데이터 일부
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Sentinel
→ 장애 대응 / 고가용성

Cluster
→ 데이터 분산 / 수평 확장 + 고가용성
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이라고 구분할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;9. Redis를 이용한 분산 락&lt;/h1&gt;
&lt;p&gt;여러 서버가 동일한 자원을 동시에 수정해야 한다면 한 서버 내부의 Lock만으로는 충분하지 않다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Server A ─┐
          ├→ 동일한 자원
Server B ─┘
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이때 모든 서버가 접근할 수 있는 Redis를 이용해 &lt;strong&gt;분산 락&lt;/strong&gt;을 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;Redis의 &lt;code&gt;SET NX&lt;/code&gt; 등을 이용해 Lock이 없는 경우에만 획득하도록 만들 수 있다.&lt;/p&gt;
&lt;p&gt;다만 단순히 &lt;code&gt;SETNX&lt;/code&gt;만 사용해서는 충분하지 않다.&lt;/p&gt;
&lt;p&gt;안전하게 사용하려면:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Lock 소유자 식별

TTL 설정

해제 시 소유권 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;등을 함께 고려해야 한다.&lt;/p&gt;
&lt;p&gt;원본 자료에서도 Redis 분산 락에서는 소유자를 식별하는 값과 만료 시간을 사용하고, 해제 시 소유권을 원자적으로 확인해야 한다고 설명한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;10. 핵심 정리&lt;/h1&gt;
&lt;p&gt;Redis 기능을 요구사항 기준으로 정리하면 다음과 같다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;요구사항&lt;/th&gt;
&lt;th&gt;사용할 수 있는 기능&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;단순 캐시&lt;/td&gt;
&lt;td&gt;String + TTL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;객체의 여러 속성&lt;/td&gt;
&lt;td&gt;Hash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;간단한 Queue&lt;/td&gt;
&lt;td&gt;List&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;중복 제거&lt;/td&gt;
&lt;td&gt;Set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ranking&lt;/td&gt;
&lt;td&gt;Sorted Set&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메시지 기록 및 처리&lt;/td&gt;
&lt;td&gt;Stream&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;실시간 메시지 전달&lt;/td&gt;
&lt;td&gt;Pub/Sub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 복구&lt;/td&gt;
&lt;td&gt;RDB / AOF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장애 대응&lt;/td&gt;
&lt;td&gt;Sentinel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 분산&lt;/td&gt;
&lt;td&gt;Cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;여러 서버의 동시성 제어&lt;/td&gt;
&lt;td&gt;Distributed Lock&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>[CS] TCP vs UDP · Handshake</title><link>https://www.shyunii.dev/posts/tcp-udp-handshake/</link><guid isPermaLink="true">https://www.shyunii.dev/posts/tcp-udp-handshake/</guid><description>TCP와 UDP, Handshake 대한 학습 및 TCP와 UDP에 대한 비교 학습 </description><pubDate>Mon, 05 Oct 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1. TCP와 UDP는 왜 필요한가?&lt;/h1&gt;
&lt;p&gt;우리가 브라우저에서 서버의 API를 호출한다고 생각해보자&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;브라우저 → HTTP 요청 → TCP → IP → Wi-Fi / Ethernet → 인터넷 → 서버
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 각각 역할은 아래와 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP
&quot;GET /users를 요청할게&quot;
			↓
TCP
&quot;이 데이터를 상대 프로세스까지 순서대로 안전하게 전달할게&quot;
			↓
IP
&quot;어느 컴퓨터로 보낼지 찾아낼게&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &lt;strong&gt;TCP와 UDP는 전송 계층(Transport Layer)의 대표적인 프로토콜&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;전송 계층이란?&lt;/h3&gt;
&lt;p&gt;IP가 데이터를 목적지 &lt;strong&gt;컴퓨터&lt;/strong&gt;까지 전달한다면, 전송 계층은 그 컴퓨터 안의 &lt;strong&gt;어떤 프로세스에 데이터를 전달할 것인지&lt;/strong&gt;를 담당한다.&lt;/p&gt;
&lt;p&gt;여기서 프로세스는 쉽게 말하면 현재 실행 중인 프로그램이라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;예를 들어 하나의 서버 컴퓨터에서 다음과 같은 프로그램이 동시에 실행될 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;8080 → Spring Boot
5432 → PostgreSQL
6379 → Redis
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이를 구분하기 위해 사용하는 것이 &lt;strong&gt;Port 번호&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IP   → 어떤 컴퓨터인가?
Port → 그 컴퓨터의 어떤 프로그램과 통신할 것인가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;전송 계층은 이렇게 Port를 이용해 프로세스 간 통신을 지원한다. TCP의 경우 여기에 연결 관리, 신뢰성 있는 전송, 흐름 제어, 혼잡 제어 등의 기능도 제공한다.&lt;/p&gt;
&lt;h3&gt;그런데 왜 TCP와 UDP 두 종류가 필요할까?&lt;/h3&gt;
&lt;p&gt;모든 네트워크 데이터의 요구사항은 같지 않다.&lt;/p&gt;
&lt;p&gt;예를 들어 생각해보자.&lt;/p&gt;
&lt;p&gt;은행 송금 데이터의 경우,&lt;code&gt;&quot;10만원 송금&quot;&lt;/code&gt; 데이터가 중간에 사라지면 안된다.&lt;/p&gt;
&lt;p&gt;또한, 순서가 중요한 데이터의 경우 &lt;code&gt;&quot;1 → 2 → 3&quot;&lt;/code&gt;이 &lt;code&gt;&quot;3 → 1 → 2&quot;&lt;/code&gt;로 도착하면 곤란하다.&lt;/p&gt;
&lt;p&gt;위와 같은 경우는 &lt;strong&gt;정확성&lt;/strong&gt;이 중요하다.&lt;/p&gt;
&lt;p&gt;반면, 온라인 게임에서 캐릭터 위치 데이터를 생각해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;10:00:00 → x=100
10:00:01 → x=110
10:00:02 → x=120
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같은 위치 데이터가 있을 때, x=110이 유실되었다고 해도 최신 데이터인 x=120가 도착했다면, 굳이 과거 데이터를 다시 받아오기 위해 기다리는 것이 좋은 것은 아닐 가능성이 높다.&lt;/p&gt;
&lt;p&gt;즉 어떤 데이터는 &lt;strong&gt;정확한 과거 데이터&lt;/strong&gt;가 중요하고, 어떤 데이터는 &lt;strong&gt;빠르게 도착하는 최신 데이터&lt;/strong&gt;가 중요하다.&lt;/p&gt;
&lt;p&gt;이러한 서로 다른 요구사항을 처리하기 위해 성격이 다른 &lt;strong&gt;TCP&lt;/strong&gt;와 &lt;strong&gt;UDP&lt;/strong&gt;가 사용된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;2. TCP란?&lt;/h1&gt;
&lt;p&gt;**TCP(Transmission Control Protocol)**는 연결지향적이며, 순서가 보장되는 신뢰성 있는 데이터 전송을 제공하는 프로토콜이다.&lt;/p&gt;
&lt;p&gt;대표적인 특징은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;연결 지향(Connection-Oriented)&lt;/li&gt;
&lt;li&gt;신뢰성 있는 데이터 전송&lt;/li&gt;
&lt;li&gt;순서 보장&lt;/li&gt;
&lt;li&gt;손실 데이터 재전송&lt;/li&gt;
&lt;li&gt;흐름 제어(Flow Control)&lt;/li&gt;
&lt;li&gt;혼잡 제어(Congestion Control)&lt;/li&gt;
&lt;li&gt;전이중(Full-Duplex) 통신&lt;/li&gt;
&lt;li&gt;Byte Stream 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2.1 연결 지향적이라는 것은 무엇일까?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;연결 지향(Connection-Oriented)&lt;/code&gt;이라는 표현에서 말하는 연결은 통신하려는 양쪽이 데이터를 보내기 전에 &lt;strong&gt;논리적인 연결 상태를 만들고, 통신이 끝날 때까지 그 상태를 관리한다는 의미&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;예를 들어 브라우저와 웹 서버가 TCP로 통신한다면 TCP는 연결마다 다음과 같은 정보를 관리한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;현재 어떤 Sequence Number까지 보냈는가?
어디까지 ACK를 받았는가?
상대방이 얼마나 더 받을 수 있는가?
현재 연결이 수립된 상태인가?
종료 중인 상태인가?
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉 TCP는 단순히&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;데이터 보내기 → 끝
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 아니라,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;연결 설정
   ↓
연결 상태 유지
   ↓
데이터 송수신
   ↓
연결 종료
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;과정을 거친다.&lt;/p&gt;
&lt;p&gt;이때 &lt;strong&gt;연결을 설정하는 과정이 3-way Handshake&lt;/strong&gt;이고, 일반적인 정상 종료 과정이 &lt;strong&gt;4-way Handshake&lt;/strong&gt;이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2.2 TCP의 &apos;신뢰성&apos;은 무엇을 의미할까?&lt;/h2&gt;
&lt;p&gt;네트워크에서는 데이터가 항상 완벽하게 전달되는 것이 아니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;데이터가 유실될 수 있음
순서가 바뀔 수 있음
같은 데이터가 중복될 수 있음
전송 중 데이터가 손상될 수 있음
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TCP는 이러한 문제를 처리하기 위해 여러 기능을 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;순서 확인
→ Sequence Number

수신 확인
→ ACK

데이터 유실
→ 재전송

데이터 오류
→ Checksum
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 TCP의 신뢰성이란 &lt;strong&gt;어떤 상황에서도 무조건 전송에 성공한다는 의미가 아니라&lt;/strong&gt;, 연결 안에서 손실 복구와 순서 제어 등을 제공하여 애플리케이션에 순서가 맞는 데이터 스트림을 제공한다는 의미이다.&lt;/p&gt;
&lt;h3&gt;Sequence Number&lt;/h3&gt;
&lt;p&gt;TCP는 데이터의 위치와 순서를 확인하기 위해 &lt;strong&gt;Sequence Number&lt;/strong&gt;를 사용한다.&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;code&gt;SEQ=1000&lt;/code&gt;부터 100바이트를 보냈다면,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SEQ = 1000
Data = 100 bytes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 데이터는 &lt;code&gt;1000~1099&lt;/code&gt;에 해당하고 다음 데이터는 &lt;code&gt;1100&lt;/code&gt;부터 시작한다.&lt;/p&gt;
&lt;p&gt;이렇게 번호가 있기 때문에 TCP는 데이터가 빠지거나 순서가 바뀐 것을 판단할 수 있다.&lt;/p&gt;
&lt;h3&gt;ACK&lt;/h3&gt;
&lt;p&gt;**ACK(Acknowledgment)**는 수신자가 어디까지 데이터를 정상적으로 받았는지 알려주는 확인 응답이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client                        Server

SEQ=1000, 100 bytes
	-----------------------------&amp;gt;

                              ACK=1100
  &amp;lt;-------------------------------
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;ACK=1100&lt;/code&gt;은&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;1099까지 받았으므로 다음에는 1100부터 보내주세요.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;라는 의미이다.&lt;/p&gt;
&lt;h3&gt;재전송&lt;/h3&gt;
&lt;p&gt;ACK가 일정 시간 동안 오지 않는 등 데이터 손실이 감지되면 TCP는 해당 데이터를 다시 전송한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;데이터 전송
   ↓
ACK가 오지 않음
   ↓
손실로 판단
   ↓
재전송
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또한 같은 ACK가 반복되는 경우 Timeout을 기다리지 않고 빠르게 재전송하는 &lt;strong&gt;Fast Retransmit&lt;/strong&gt;도 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;단, TCP에서 ACK를 받았다는 것이 꼭 &lt;strong&gt;상대 애플리케이션의 업무 처리가 성공했다는 의미는 아니다.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TCP 데이터 전달 성공
        ≠
결제 / DB 처리 성공
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TCP는 어디까지나 전송 계층에서의 데이터 전달을 담당한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2.3 3-way Handshake&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Handshake&lt;/strong&gt;는 본격적인 통신 전에 양쪽이 필요한 정보를 주고받으며 상태를 맞추는 과정이다.&lt;/p&gt;
&lt;p&gt;TCP에서는 연결을 설정하기 위해 &lt;strong&gt;3-way Handshake&lt;/strong&gt;를 수행한다.&lt;/p&gt;
&lt;p&gt;여기서 주로 사용하는 TCP 제어 플래그는 &lt;code&gt;SYN&lt;/code&gt;과 &lt;code&gt;ACK&lt;/code&gt;이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SYN(Synchronize)&lt;/strong&gt;: 연결을 시작하고 Sequence Number를 동기화하기 위한 플래그&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;ACK(Acknowledgment)&lt;/strong&gt;: 상대가 보낸 정보를 정상적으로 받았음을 알리는 플래그&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;과정은 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client                         Server

             SYN (seq=x)
  --------------------------------&amp;gt;

             SYN + ACK
          (seq=y, ack=x+1)
  &amp;lt;--------------------------------

            ACK (ack=y+1)
  --------------------------------&amp;gt;

             ESTABLISHED
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;① SYN&lt;/h3&gt;
&lt;p&gt;클라이언트가 자신의 초기 Sequence Number인 **ISN(Initial Sequence Number)**과 함께 연결 요청을 보낸다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;연결하고 싶습니다.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;② SYN + ACK&lt;/h3&gt;
&lt;p&gt;서버는 클라이언트의 SYN을 받았다는 ACK와 자신의 SYN을 함께 보낸다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;네 요청을 받았고, 나도 연결할 준비가 됐습니다.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;③ ACK&lt;/h3&gt;
&lt;p&gt;클라이언트가 서버의 SYN도 정상적으로 받았다는 ACK를 보낸다.&lt;/p&gt;
&lt;p&gt;이후 양쪽 모두 &lt;code&gt;ESTABLISHED&lt;/code&gt; 상태가 되어 데이터를 주고받을 수 있다.&lt;/p&gt;
&lt;h3&gt;왜 2-way가 아니라 3-way일까?&lt;/h3&gt;
&lt;p&gt;단순하게 생각하면 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client
&quot;내 말 들려?&quot;
      ↓
Server
&quot;응. 내 말도 들려?&quot;
      ↓
Client
&quot;응. 잘 들려.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;조금 더 정확하게는 다음 목적이 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;양방향 통신이 가능한지 확인&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;양측의 초기 Sequence Number(ISN) 동기화&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;과거에 지연된 중복 연결 요청이 새로운 연결로 잘못 처리되는 것을 방지&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;따라서 3-way Handshake는 단순히 순서를 외우기보다는 &lt;strong&gt;양쪽의 TCP 연결 상태와 Sequence Number를 맞추는 과정&lt;/strong&gt;이라고 이해하는 것이 중요하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2.4 4-way Handshake&lt;/h2&gt;
&lt;p&gt;통신이 끝나면 TCP 연결도 종료해야 한다.&lt;/p&gt;
&lt;p&gt;정상적인 TCP 연결 종료에서는 주로 &lt;code&gt;FIN&lt;/code&gt; 플래그를 사용한다.&lt;/p&gt;
&lt;p&gt;**FIN(Finish)**은&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&quot;나는 이제 이 방향으로 더 이상 보낼 데이터가 없습니다.&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;라는 의미이다.&lt;/p&gt;
&lt;p&gt;일반적인 종료 과정은 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client                         Server

  FIN
   ------------------------------&amp;gt;

                                ACK
   &amp;lt;------------------------------

                                FIN
   &amp;lt;------------------------------

  ACK
   ------------------------------&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;왜 3-way가 아니라 4-way일까?&lt;/h3&gt;
&lt;p&gt;TCP는 &lt;strong&gt;Full-Duplex(전이중)&lt;/strong&gt; 통신을 지원한다.&lt;/p&gt;
&lt;p&gt;전이중이란 하나의 TCP 연결에서 양쪽이 동시에 데이터를 보낼 수 있다는 의미이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client --------&amp;gt; Server
       데이터

Client &amp;lt;-------- Server
            데이터
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;따라서 Client가 FIN을 보냈다고 해서 Server도 즉시 데이터 전송을 끝내야 하는 것은 아니다.&lt;/p&gt;
&lt;p&gt;Server는 우선 ACK를 보내고, 남아 있는 데이터를 모두 전송한 뒤 자신의 FIN을 보낼 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client FIN
    ↓
Server ACK
    ↓
남은 데이터 전송
    ↓
Server FIN
    ↓
Client ACK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이처럼 한쪽 방향의 전송만 먼저 종료되는 것을 &lt;strong&gt;Half-Close&lt;/strong&gt;라고 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2.5 TIME_WAIT과 CLOSE_WAIT&lt;/h2&gt;
&lt;p&gt;TCP 연결 종료와 함께 자주 등장하는 상태이다.&lt;/p&gt;
&lt;h3&gt;TIME_WAIT&lt;/h3&gt;
&lt;p&gt;마지막 ACK를 보낸 측은 바로 연결 정보를 삭제하지 않고 일정 시간 &lt;code&gt;TIME_WAIT&lt;/code&gt; 상태에 머문다.&lt;/p&gt;
&lt;p&gt;주요 이유는 두 가지이다.&lt;/p&gt;
&lt;p&gt;첫째, 마지막 ACK가 유실되면 상대방이 FIN을 다시 보낼 수 있기 때문에 다시 ACK할 수 있도록 하기 위해서이다.&lt;/p&gt;
&lt;p&gt;둘째, 이전 연결에서 늦게 도착한 데이터가 새로운 연결에 영향을 주는 것을 방지하기 위해서이다.&lt;/p&gt;
&lt;p&gt;TCP에서는 전통적으로 이를 &lt;strong&gt;MSL(Maximum Segment Lifetime)&lt;/strong&gt; 대기라고 설명한다.&lt;/p&gt;
&lt;p&gt;TCP 세그먼트가 네트워크에 존재할 수 있는 최대 시간을 MSL라고 부르고, 일반적으로 TIME_WAIT 상태에서는 2MSL만큼 대기한다.&lt;/p&gt;
&lt;h3&gt;CLOSE_WAIT&lt;/h3&gt;
&lt;p&gt;상대에게 FIN을 받아 ACK는 보냈지만 &lt;strong&gt;자신의 애플리케이션이 아직 연결을 완전히 닫지 않은 상태&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;따라서 서버에서 CLOSE_WAIT이 계속 많이 남아 있다면 연결 종료 누락이나 자원 정리 문제 등을 의심해볼 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2.6 TCP의 Byte Stream&lt;/h2&gt;
&lt;p&gt;TCP는 데이터를 &lt;strong&gt;Byte Stream&lt;/strong&gt;으로 처리한다.&lt;/p&gt;
&lt;p&gt;Stream은 데이터를 개별 메시지가 아니라 &lt;strong&gt;연속된 바이트의 흐름&lt;/strong&gt;으로 본다는 의미이다.&lt;/p&gt;
&lt;p&gt;예를 들어 애플리케이션이&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;send(&quot;HELLO&quot;)
send(&quot;WORLD&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;라고 두 번 보냈다고 해서 수신자가 반드시&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HELLO
WORLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;두 번으로 나누어 받는 것은 아니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HELLOWORLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;한 번으로 받을 수도 있고,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HEL
LOWORLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 나누어 받을 수도 있다.&lt;/p&gt;
&lt;p&gt;즉, TCP가 보장하는 것은 &lt;strong&gt;바이트의 순서&lt;/strong&gt;이지 애플리케이션이 보낸 메시지의 경계가 아니다.&lt;/p&gt;
&lt;p&gt;TCP의 Byte Stream으로 인해 TCP 기반 애플리케이션에서는 메시지의 시작과 끝을 구분하기 위해 &lt;code&gt;Length Prefix, 구분자, Content-Length&lt;/code&gt; 등의 &lt;strong&gt;Framing 방식&lt;/strong&gt;을 별도로 정의해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2.7 흐름 제어와 혼잡 제어&lt;/h2&gt;
&lt;p&gt;TCP는 데이터를 정확하게 전달하는 것뿐 아니라 &lt;strong&gt;얼마나 많이 전송할 것인지&lt;/strong&gt;도 조절한다.&lt;/p&gt;
&lt;h3&gt;흐름 제어(Flow Control)&lt;/h3&gt;
&lt;p&gt;송신자가 수신자보다 너무 빠르게 데이터를 보내면 수신자의 버퍼가 가득 찰 수 있다.&lt;/p&gt;
&lt;p&gt;그래서 TCP는 수신자가 알려주는 **Receive Window(rwnd)**를 이용해 수신자가 감당할 수 있는 만큼 전송한다.&lt;/p&gt;
&lt;p&gt;즉 흐름 제어의 목적은 &lt;strong&gt;수신자를 보호하는 것&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;혼잡 제어(Congestion Control)&lt;/h3&gt;
&lt;p&gt;수신자는 충분히 빠르더라도 데이터가 지나가는 네트워크 자체가 혼잡할 수 있다.&lt;/p&gt;
&lt;p&gt;TCP는 &lt;strong&gt;Congestion Window(cwnd)&lt;/strong&gt; 등을 이용해 네트워크 상황에 따라 전송량을 조절한다.&lt;/p&gt;
&lt;p&gt;즉 혼잡 제어의 목적은 &lt;strong&gt;네트워크 과부하를 방지하는 것&lt;/strong&gt;이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;흐름 제어&lt;/th&gt;
&lt;th&gt;혼잡 제어&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;수신자 보호&lt;/td&gt;
&lt;td&gt;네트워크 보호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기준&lt;/td&gt;
&lt;td&gt;수신 처리 능력&lt;/td&gt;
&lt;td&gt;네트워크 혼잡&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 값&lt;/td&gt;
&lt;td&gt;rwnd&lt;/td&gt;
&lt;td&gt;cwnd&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h1&gt;3. UDP란?&lt;/h1&gt;
&lt;p&gt;**UDP(User Datagram Protocol)**는 TCP와 달리 연결 상태를 미리 만들지 않고 데이터를 전송하는 &lt;strong&gt;비연결형(Connectionless) 프로토콜&lt;/strong&gt;이다.&lt;/p&gt;
&lt;p&gt;하지만 비연결형이라고 해서 상대방의 주소를 모른다는 뜻은 아니다.&lt;/p&gt;
&lt;p&gt;UDP도 데이터를 보내려면 목적지의 IP와 Port가 필요하다.&lt;/p&gt;
&lt;p&gt;차이는 TCP처럼 먼저 3-way Handshake를 수행해,&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;현재 두 Endpoint가 하나의 연결을 통해 통신하고 있다.&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;라는 연결 상태를 만들고 지속적으로 관리하지 않는다는 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TCP

연결 설정
  ↓
데이터 송수신
  ↓
연결 상태 유지
  ↓
연결 종료
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;UDP

목적지 지정
  ↓
Datagram 전송
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;UDP의 주요 특징은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비연결형(Connectionless)&lt;/li&gt;
&lt;li&gt;별도의 Handshake 없음&lt;/li&gt;
&lt;li&gt;데이터 도착 보장 없음&lt;/li&gt;
&lt;li&gt;순서 보장 없음&lt;/li&gt;
&lt;li&gt;TCP와 같은 자동 재전송 없음&lt;/li&gt;
&lt;li&gt;TCP식 흐름/혼잡 제어 없음&lt;/li&gt;
&lt;li&gt;기본 헤더 8Byte&lt;/li&gt;
&lt;li&gt;Datagram 단위 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3.1 UDP의 Datagram&lt;/h2&gt;
&lt;p&gt;UDP는 데이터를 &lt;strong&gt;Datagram&lt;/strong&gt;이라는 독립된 메시지 단위로 처리한다.&lt;/p&gt;
&lt;p&gt;예를 들어&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;send(&quot;HELLO&quot;)
send(&quot;WORLD&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;라고 두 번 전송했다면 UDP에서는&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Datagram 1 → HELLO
Datagram 2 → WORLD
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;라는 두 개의 독립된 데이터그램으로 처리된다.&lt;/p&gt;
&lt;p&gt;한마디로 UDP는 &lt;strong&gt;메시지의 경계를 유지한다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;TCP와 비교하면 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TCP
→ 연속된 바이트의 흐름
→ 메시지 경계 보장 X

UDP
→ 독립된 Datagram
→ 메시지 경계 유지
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3.2 UDP는 데이터가 유실되면?&lt;/h2&gt;
&lt;p&gt;UDP 자체는 손실된 데이터를 TCP처럼 다시 보내주지 않는다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;송신
1 2 3 4 5

3 유실

수신
1 2 4 5
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;또한&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1 3 2 5 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처럼 순서가 달라질 수도 있다.&lt;/p&gt;
&lt;p&gt;UDP는 기본적으로 &lt;strong&gt;Best-Effort&lt;/strong&gt;, 즉 전달을 시도하지만 도착이나 순서를 보장하지 않는 방식으로 동작한다.&lt;/p&gt;
&lt;p&gt;TCP와 UDP 모두 Checksum을 이용해 데이터 오류를 검출할 수 있지만, UDP는 TCP처럼 손실 데이터를 자동 재전송하는 기능을 자체적으로 제공하지 않는다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3.3 그렇다면 UDP는 왜 사용할까?&lt;/h2&gt;
&lt;p&gt;UDP는 TCP의 연결 설정이나 ACK 기반 신뢰성 제어 등의 기능을 자체적으로 제공하지 않는 대신 구조가 단순하고 오버헤드가 작다.&lt;/p&gt;
&lt;p&gt;따라서 &lt;strong&gt;일부 데이터 손실보다 낮은 지연이나 최신성이 중요한 상황&lt;/strong&gt;에서 유용할 수 있다.&lt;/p&gt;
&lt;p&gt;대표적으로 다음과 같은 곳에서 사용된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;DNS&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;실시간 음성·영상 통신&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;온라인 게임의 실시간 위치 정보&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;RTP&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;QUIC&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;게임의 위치 데이터를 다시 생각해보자.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x=100
x=110 ← 유실
x=120
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이미 &lt;code&gt;x=120&lt;/code&gt;이라는 최신 값이 도착했다면 과거 값인 &lt;code&gt;x=110&lt;/code&gt;을 기다리는 것이 오히려 불필요할 수 있다.&lt;/p&gt;
&lt;p&gt;그래서 실제 서비스에서도 데이터의 성격에 따라 프로토콜을 다르게 선택할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;실시간 위치 정보 → UDP 사용 가능

인증 / 결제 / 채팅
→ TCP 사용 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;3.4 UDP에서도 신뢰성을 구현할 수 있을까?&lt;/h2&gt;
&lt;p&gt;가능하다.&lt;/p&gt;
&lt;p&gt;정확히는&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UDP에서는 신뢰성 있는 통신이 불가능하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;가 아니라&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;UDP 프로토콜 자체가 TCP와 같은 신뢰성 기능을 제공하지 않는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;가 맞다.&lt;/p&gt;
&lt;p&gt;필요하다면 UDP 위의 애플리케이션이나 다른 프로토콜에서&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Sequence Number
ACK
재전송
순서 재조립
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;등을 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;대표적인 예가 &lt;strong&gt;QUIC&lt;/strong&gt;이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HTTP/1.1 → TCP
HTTP/2   → TCP

HTTP/3
  ↓
QUIC
  ↓
UDP
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;QUIC은 UDP 위에서 동작하지만 신뢰성, 손실 복구, 흐름 제어, 혼잡 제어 등의 기능을 제공한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;4. TCP와 UDP의 차이&lt;/h1&gt;
&lt;p&gt;지금까지의 내용을 정리하면 다음과 같다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;TCP&lt;/th&gt;
&lt;th&gt;UDP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;연결 방식&lt;/td&gt;
&lt;td&gt;연결 지향&lt;/td&gt;
&lt;td&gt;비연결형&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결 설정&lt;/td&gt;
&lt;td&gt;3-way Handshake&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결 종료&lt;/td&gt;
&lt;td&gt;일반적으로 4-way Handshake&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;신뢰성&lt;/td&gt;
&lt;td&gt;제공&lt;/td&gt;
&lt;td&gt;자체적으로 제공하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;순서 보장&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재전송&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;흐름 제어&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;혼잡 제어&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;데이터 형태&lt;/td&gt;
&lt;td&gt;Byte Stream&lt;/td&gt;
&lt;td&gt;Datagram&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메시지 경계&lt;/td&gt;
&lt;td&gt;보장 X&lt;/td&gt;
&lt;td&gt;유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기본 헤더&lt;/td&gt;
&lt;td&gt;20Byte&lt;/td&gt;
&lt;td&gt;8Byte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표 사용&lt;/td&gt;
&lt;td&gt;웹, 파일 전송 등&lt;/td&gt;
&lt;td&gt;DNS, 게임, 실시간 통신 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;따라서 단순히&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TCP = 느리지만 안전
UDP = 빠르지만 불안정
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이라고 외우는 것은 정확하지 않다.&lt;/p&gt;
&lt;p&gt;조금 더 정확하게 이해하면 다음과 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TCP
→ 연결 상태를 관리하고
  ACK, 재전송, 순서 제어,
  흐름/혼잡 제어 등을 제공한다.


UDP
→ 이러한 기능을 최소화하고
  Datagram을 단순하게 전송한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;결국 어떤 프로토콜을 사용할지는 &lt;strong&gt;어떤 데이터 특성이 더 중요한지&lt;/strong&gt;에 따라 결정한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;정확성
순서
데이터 유실 방지
    ↓
   TCP
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;낮은 지연
최신성이 중요
일부 손실 허용 가능
    ↓
   UDP
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>