<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>주니어 개발 노트</title>
    <link>https://juganote.tistory.com/</link>
    <description>더이상 주니어가 아니게 되어버린 Python Backend Engineer가 풀어가는 이런저런 개발/일상 이야기</description>
    <language>ko</language>
    <pubDate>Thu, 28 May 2026 15:09:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>발짜개</managingEditor>
    <image>
      <title>주니어 개발 노트</title>
      <url>https://tistory1.daumcdn.net/tistory/4419142/attach/79e099db291642a98c22d3347ee8eac9</url>
      <link>https://juganote.tistory.com</link>
    </image>
    <item>
      <title>Matrix(매트릭스) and Synapse(사이냅스) Homeserver</title>
      <link>https://juganote.tistory.com/entry/Matrix%EB%A7%A4%ED%8A%B8%EB%A6%AD%EC%8A%A4-and-Synapse%EC%82%AC%EC%9D%B4%EB%83%85%EC%8A%A4-Homeserver</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Matrix란 무엇인가?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Matrix는 탈중앙화된 실시간 커뮤니케이션을 위한 개방형 표준이자 프로토콜입니다. Matrix의 주요 목표는 이메일이 작동하는 방식처럼, 서로 다른 도메인(서비스 제공업체) 간에 안전한 메시징, 음성 및 비디오 통신을 가능하게 하는 것입니다. Matrix는 홈서버(homeserver)라고 하는 여러 서버에 있는 사용자가 중앙 기관에 의존하지 않고 원활하게 통신할 수 있도록 함으로써 이를 달성합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Matrix의 핵심 특징은 &lt;b&gt;탈중앙성&lt;/b&gt;(Decentralization)과 &lt;b&gt;상호 운용성&lt;/b&gt;(Interoperability)입니다. 이는 중앙 서버에 의존하지 않고, 여러 서버가 서로 연결되어 메시지를 주고받는 방식을 의미합니다. 이를 통해 사용자는 자신이 원하는 서버를 선택하거나, 여러 서버와 연결된 커뮤니케이션 네트워크를 구성할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Matrix의 주요 기능:&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Federation(통합/연결):&lt;/b&gt; 여러 독립 서버(홈서버)가 메시지와 데이터를 동기화하므로 단일 서버가 대화나 사용자 데이터를 제어하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Decentralization(탈중앙화):&lt;/b&gt; 누구나 자신의 홈서버를 운영할 수 있어 사용자가 자신의 데이터와 개인 정보를 제어할 수 있습니다. 각 서버는 독립적으로 운영되며, 서버 간에 메시지나 데이터를 전송할 수 있습니다. 사용자는 자신이 원하는 서버에 가입하여 커뮤니케이션을 시작할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Interoperability(상호운용성):&lt;/b&gt; 매트릭스는 다른 채팅 네트워크에 연결할 수 있으며, 다양한 클라이언트 및 통합을 지원합니다. 예를 들어, Matrix를 사용하면 Slack이나 Discord와 같은 다른 시스템과도 메시지를 주고받을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Security(보안):&lt;/b&gt; 메시지에 대한 end-to-end(종단 간) 암호화를 지원하며, 보안 HTTP API를 사용하여 데이터를 전송 및 복제합니다. Matrix는 데이터를 중앙집중식 서버에 저장하지 않으며, 개별 서버가 데이터를 관리하고 사용자의 프라이버시를 보호합니다. 또한, 모든 메시지 통신에 암호화가 적용됩니다. 따라서 오직 수신자만 내용을 읽을 수 있도록 보장됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Rooms and Events(룸 및 이벤트):&lt;/b&gt; 커뮤니케이션은 '룸'에서 이루어지며, 메시지 및 기타 작업은 공유 타임라인에서 '이벤트'로 표시됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용 방법:&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트(채팅 앱)는 Matrix Client-Server API를 사용하여 홈서버에 연결합니다. 그런 다음 홈서버는 Matrix Server-Server API를 사용하여 서로 데이터를 동기화하고, 따라서 서로 다른 서버나 클라이언트를 사용하더라도 대화방의 모든 참가자가 동일한 대화를 볼 수 있도록 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Matrix의 시작과 발전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Matrix는 2014년에 Matthew Hodgson과 Amandine Le Pape에 의해 시작된 오픈소스 메시징 프로토콜입니다. 이들은 기존의 중앙 집중식 메시징 시스템들이 가진 문제점들, 즉 상호 운용성 부족, 데이터 프라이버시 문제, 사용자 락인(Lock-In strategy: 기업이 고객을 자사 서비스에 묶어두는 비즈니스 전략) 등을 해결하고자 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Matrix는 다양한 메시징 플랫폼들이 서로 상호작용할 수 있도록 하는 것을 목표로, 독립적인 오픈소스 프로젝트로 시작되었습니다. 사용자가 Slack, WhatsApp, Telegram 같은 다양한 메신저를 사용하더라도, 그들 간에 메시지를 주고받을 수 있어야 한다는 생각에서 출발한 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Matrix는 이를 위해 분산형 시스템을 채택하여 서버 간 상호운용성을 가능하게 하고, End-to-End 암호화 기능도 제공하여 보안을 강화했습니다. 이 프로토콜은 점차적으로 사용자와 개발자들 사이에서 인기를 끌게 되었고, 2017년부터는 Matrix Foundation이라는 비영리 단체가 Matrix의 관리 및 개발을 담당하게 되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;현재 Matrix를 사용하는 기업 및 단체들&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Matrix는 주로 보안과 프라이버시를 중요시하는 다양한 기업, 정부 기관, 및 비영리 단체에서 사용됩니다. 일부 주요 사용 예시는 다음과 같습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Mozilla&lt;/b&gt;: Mozilla는 Matrix를 사용하여 내부 커뮤니케이션을 처리하고 있습니다. Firefox 개발 팀과 다른 팀들이 Matrix를 통해 효율적으로 소통합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Siemens&lt;/b&gt;: 독일의 대기업 Siemens는 Matrix 기반의 내부 커뮤니케이션 플랫폼을 운영하고 있습니다. 이를 통해 직원들 간의 보안성이 높은 메시징과 협업을 지원합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;New Zealand's Government&lt;/b&gt;: 뉴질랜드 정부는 Matrix를 사용하여 공공 서비스와 내부 커뮤니케이션을 진행합니다. 정부 기관 간의 상호 운용성을 높이고 보안을 강화하기 위해 사용됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Swiss Federal Railways (SBB)&lt;/b&gt;: 스위스 연방 철도는 Matrix를 기반으로 한 커뮤니케이션 시스템을 운영하여 효율적인 메시징을 지원하고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Slack, Discord와의 연동&lt;/b&gt;: 일부 기업들은 Matrix를 사용하여 Slack, Discord와 같은 기존 플랫폼과의 통합을 진행하고 있습니다. 이로 인해 여러 다른 플랫폼에서 발생하는 메시지를 하나의 중앙화된 시스템에서 관리할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;개인 및 커뮤니티 사용&lt;/b&gt;: Matrix는 개인 사용자나 특정 커뮤니티에서도 활발히 사용됩니다. 예를 들어, &lt;a href=&quot;https://element.io/&quot;&gt;Element&lt;/a&gt;는 Matrix를 기반으로 한 메시징 애플리케이션으로, 사용자들이 안전하고 프라이빗하게 대화를 나눌 수 있게 해 줍니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Matrix는 기존의 중앙집중형 시스템에 의존하지 않는 분산형 메시징 프로토콜로, 상호 운용성, 보안성, 프라이버시 보호에 중점을 두고 설계되었습니다. 오늘날, 다양한 기업과 조직들이 Matrix를 사용하여 안전하고 효율적인 커뮤니케이션을 이루고 있으며, 오픈소스 및 분산형 네트워크의 장점을 활용하고 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;그렇다면 Synapse Homeserver는 무엇일까요?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synapse는 Matrix 프로토콜을 구현한 오픈소스 homeserver 구현체입니다. 즉, Matrix 네트워크에서 메시징과 통신을 주고받기 위한 homeserver를 Synapse를 통해 운영할 수 있습니다. Python으로 작성되었으며, &lt;a href=&quot;https://github.com/element-hq/synapse&quot;&gt;Element&lt;/a&gt;에서 개발 및 유지 관리하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synapse를 통해 개인이나 조직이 직접 자신만의 서버를 구축하여 Matrix 네트워크에 참여할 수 있습니다. 따라서 Matrix 네트워크를 구동하는 주요 백엔드 소프트웨어 역할을 합니다. 이를 통해 해당 서버는 사용자의 데이터를 저장하고, 메시지를 처리하며, 다른 서버들과 통신을 중계하는 역할을 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Synapse의 핵심 기능:&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Matrix 프로토콜의 완전한 구현&lt;/b&gt;: 시냅스는 매트릭스 생태계에서 서버 구성요소 역할을 합니다. 텍스트 채팅, 음성 및 영상 통화, 파일 전송 등 다양한 메시징 기능뿐만 아니라, 사용자 계정, 메시지 기록, 룸 데이터를 저장하고 다른 홈서버와의 통합/연결(federation)을 처리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오픈 소스:&lt;/b&gt; Synapse는 오픈 소스이며 AGPL 라이선스에 따라 누구나 자신의 인스턴스를 실행하거나 개발에 기여할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성과 성능, 및 커스터마이제이션:&lt;/b&gt; 조직이나 단체는 Synapse를 배포하여 커뮤니케이션을 제어하고 기존 시스템과 통합하며 자체 보안 정책을 시행할 수 있습니다. Synapse는 확장성이 뛰어나며, 필요에 따라 대규모 서버로 성장할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴플라이언스&lt;/b&gt;: Synapse는 GDPR(EU의 개인정보 보호법)과 같은 데이터 보호 규정을 준수할 수 있도록 설계되었습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;기업 옵션:&lt;/b&gt; 고급 관리, ID 관리 및 규정 준수 요구 사항을 위해 Synapse를 기반으로 하는 엔터프라이즈용 버전(예: Element Server Suite)이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Synapse 사용 예시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synapse는 다양한 환경에서 사용될 수 있습니다. 몇 가지 예시를 들어 보겠습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 기업용 내부 커뮤니케이션&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안이 중요한 기업에서는 Synapse를 자체 서버로 설정하여 내부 커뮤니케이션을 진행합니다. 예를 들어, Mozilla나 Siemens와 같은 대기업은 Synapse 기반의 Matrix 서버를 활용하여 내부 메시징, 협업 및 파일 공유를 안전하게 처리합니다. 분산형 네트워크에서 운영되므로, 개인정보 보호와 보안 측면에서 더 유리합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 대규모 커뮤니티&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커뮤니티 또는 오픈소스 프로젝트에서는 Synapse를 사용하여 사용자들이 참여할 수 있는 공공 서버를 운영합니다. 예를 들어, OpenStack 커뮤니티는 Synapse 기반의 메시징 서버를 사용하여 전 세계의 개발자들 간에 효과적으로 소통하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 정부 및 공공기관&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정부나 공공기관에서는 Synapse를 사용하여 안전한 커뮤니케이션을 진행할 수 있습니다. 예를 들어, 스위스 정부는 내부 통신에 Matrix 기반의 시스템을 사용하고 있습니다. 또한 병원 및 보건 시스템에서도 환자 관리, 의료 데이터 공유 등을 안전하게 처리하는 데 사용될 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Synapse 구현 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synapse는 Python으로 작성된 서버 애플리케이션으로, Linux 환경에서 주로 설치됩니다. 기본적인 설치 방법은 다음과 같습니다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 기본 요구사항&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synapse를 설치하기 위해서는 Python 3.7 이상, PostgreSQL 또는 SQLite 데이터베이스가 필요합니다. 또한, 서버에 TLS/SSL 인증서가 필요합니다. &lt;a href=&quot;https://element-hq.github.io/synapse/latest/setup/installation.html#installing-as-a-python-module-from-pypi&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;에서 더 자세하게 확인할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 설치 방법&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1. 패키지 설치 및 가상환경 설정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, Synapse를 설치할 수 있는 패키지를 시스템에 설치합니다. Linux 시스템에서는 다음과 같은 명령어로 설치할 수 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745934852062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mkdir -p ~/synapse

virtualenv -p python3 ~/synapse/env
source ~/synapse/env/bin/activate

pip install --upgrade pip
pip install --upgrade setuptools
pip install matrix-synapse

source ~/synapse/env/bin/activate
pip install -U matrix-synapse&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2. 설정 파일 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synapse 설치가 완료되면, 설정 파일을 생성합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1745934946494&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd ~/synapse
python -m synapse.app.homeserver \
    --server-name my.domain.name \
    --config-path homeserver.yaml \
    --generate-config \
    --report-stats=[yes|no]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3. 설정 파일 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버 실행 설정이 완료된 후 서버를 실행할 수 있습니다&lt;/p&gt;
&lt;pre id=&quot;code_1745934982542&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd ~/synapse
source env/bin/activate
synctl start&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 운영 및 관리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synapse는 웹 기반 관리 대시보드 또는 커맨드 라인에서 관리할 수 있습니다. 시스템 관리자는 사용자 관리, 채팅방 설정, 서버 보안, API 연동 등을 설정하고 유지 관리할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Synapse는 Matrix 프로토콜을 기반으로 한 핵심 홈서버로, 분산형 메시징 시스템을 실현하는 중요한 역할을 합니다. 이를 통해 기업, 정부, 커뮤니티 등에서 보안성 높은 실시간 메시징 시스템을 구축할 수 있습니다. 또한, 오픈소스로 제공되어 자유롭게 커스터마이징하고 사용할 수 있다는 점에서 매우 유용한 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마무리하며..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Matrix&lt;/b&gt;는 분산형 오픈소스 메시징 프로토콜로, 다양한 시스템 간의 실시간 커뮤니케이션을 가능하게 합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Synapse&lt;/b&gt;는 Matrix 프로토콜을 구현한 홈서버로, 사용자가 자신의 서버를 설정하여 Matrix 네트워크에 참여하고 메시징을 관리할 수 있게 도와줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꽤 오래됐지만 저도 처음 들어보는 서비스라 글을 쓰면서 배울 수 있어서 너무 재미있었고, 언제 꼭 써볼 수 있는 기회가 있으면 좋겠습니다^^&lt;/p&gt;</description>
      <category>Tech Trends</category>
      <category>homeserver</category>
      <category>homeserver 구현</category>
      <category>Matrix</category>
      <category>matrix 무엇</category>
      <category>synapse</category>
      <category>synapse 홈서버</category>
      <category>매트릭스</category>
      <category>사이냅스</category>
      <category>시냅스</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/22</guid>
      <comments>https://juganote.tistory.com/entry/Matrix%EB%A7%A4%ED%8A%B8%EB%A6%AD%EC%8A%A4-and-Synapse%EC%82%AC%EC%9D%B4%EB%83%85%EC%8A%A4-Homeserver#entry22comment</comments>
      <pubDate>Tue, 29 Apr 2025 22:58:05 +0900</pubDate>
    </item>
    <item>
      <title>MCP, Model Context Protocol 도대체 뭐길래!</title>
      <link>https://juganote.tistory.com/entry/MCP-Model-Context-Protocol-%EB%8F%84%EB%8C%80%EC%B2%B4-%EB%AD%90%EA%B8%B8%EB%9E%98</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp; MCP (Model Context Protocol)란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Model Context Protocol&lt;/b&gt;은 &lt;b&gt;모델이 작동하는 문맥(Context)과 환경을 명확하게 정의하고 주고받기 위한 규약입니다.&lt;/b&gt; 특히 여러 시스템이 함께 작동하거나, 다양한 모델이 함께 협업해야 할 때 모델 간의 인터페이스를 정리해 주는 &amp;ldquo;약속된 포맷&amp;rdquo;이라고 볼 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp; 왜 필요한가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 AI 모델이나 서비스가 &lt;b&gt;동일한 컨텍스트를 공유&lt;/b&gt;해야 하는 상황이 많아졌고, 또 사용자, 환경, 목적 등에 따라 모델의 응답이나 행동이 달라져야 하기 때문에 그 &amp;ldquo;문맥&amp;rdquo;을 정확히 전달하는 방식이 필요해졌습니다. 예를 들어 사용자 위치, 언어, 이전 대화 내용, 시스템 설정 등을 다른 모델에 넘겨줄 수 있어야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt; &amp;nbsp; 어디에 사용되나?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;멀티모달(Multi Modal) 시스템 (예: 텍스트 + 음성 + 이미지)&lt;/li&gt;
&lt;li&gt;다중 에이전트 시스템 (여러 AI가 동시에 작동하는 구조)&lt;/li&gt;
&lt;li&gt;LangChain, AutoGen, OpenAI Function Calling 등에서 내부적으로 사용&lt;/li&gt;
&lt;li&gt;AI SaaS 플랫폼 간 모델 호출 시, 일관성 있는 설정 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt; &amp;nbsp; &lt;b&gt;MCP는 어떤 정보를 포함할까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 보통 다음과 같은 정보들을 담을 수 있습니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;user_context&lt;/td&gt;
&lt;td&gt;사용자 ID, 언어, 지역 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;session_context&lt;/td&gt;
&lt;td&gt;이전 상호작용 기록, 현재 세션 상태 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;model_capabilities&lt;/td&gt;
&lt;td&gt;이 모델이 처리할 수 있는 기능 정보&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;environment&lt;/td&gt;
&lt;td&gt;실행되는 하드웨어, OS, 모델 버전 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;policy&lt;/td&gt;
&lt;td&gt;프라이버시 정책, 필터링 규칙 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  요약하자면&amp;hellip;&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MCP는 &amp;ldquo;모델이 내가 누구인지, 어떤 상황인지 이해할 수 있도록 도와주는 정보 전달 포맷&amp;rdquo;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;점점 더 복잡해지는 AI 시스템을 안정적으로 통합하고 협업시키기 위해 매우 중요한 개념입니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;MCP의 등장&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP(Model Context Protocol)는 &lt;b&gt;Anthropic&lt;/b&gt;이 &lt;a href=&quot;https://www.anthropic.com/news/model-context-protocol&quot;&gt;2024년 11월에 발표한 오픈소스 프로토콜&lt;/a&gt;로, 애플리케이션과 대형 언어 모델(LLM) 간의 정보 공유를 위한 표준을 제공하여, AI 시스템이 더 풍부하고 정확한 정보를 기반으로 응답할 수 있도록 설계되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Anthropic는 MCP를 &lt;a href=&quot;https://github.com/modelcontextprotocol&quot;&gt;오픈 소스&lt;/a&gt;로 공개하여, 개발자들이 다양한 데이터 소스와 AI 도구 간의 통합을 간소화하고, AI 시스템이 다양한 도구와 데이터셋 간의 컨텍스트를 유지하면서 상호 작용할 수 있도록 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 JSON-RPC 2.0 메시지를 사용하여 LLM 애플리케이션(호스트), 호스트 애플리케이션 내의 커넥터(클라이언트), 그리고 컨텍스트와 기능을 제공하는 서비스(서버) 간의 통신을 설정합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  MCP의 핵심 개념&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;컨텍스트 공유 표준화&lt;/b&gt;: MCP는 LLM 애플리케이션과 데이터 소스 간의 상호작용을 위한 표준 프로토콜을 제공하며, 구조화된 방식으로 컨텍스트 정보를 전달하고 관리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;도구와 기능 노출&lt;/b&gt;: AI 시스템에 로컬 또는 원격 도구들을 안전하게 노출하며, 표준화된 방식으로 기능을 정의하고 호출할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;통합 워크플로우 구축&lt;/b&gt;: 여러 데이터 소스와 도구를 조합한 워크플로우를 생성하고, 재사용 가능한 프롬프트 템플릿을 제공하여 모듈식 구성을 통한 유연한 확장이 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  실제 활용 사례&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Claude Desktop&lt;/b&gt;: MCP를 통해 로컬 파일 시스템, GitHub, 캘린더 등 다양한 외부 리소스에 접근하여 작업을 수행합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Replit, Codeium, Sourcegraph&lt;/b&gt;: 이들 플랫폼은 MCP를 활용하여 AI 에이전트를 구축하고, 사용자의 작업을 자동화합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ChatGPT의 Custom GPTs&lt;/b&gt;: 사용자가 UI 상에서 지정한 시스템 프롬프트, 도구, 파일, 지식 등이 MCP 형태로 내부에서 처리됩니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;  보안 및 개인정보 보호&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 사용자 데이터에 대한 무단 액세스를 제한하며, 명시적으로 허용되지 않는 한 AI가 특정 정보에 접근할 수 없도록 설계되어 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 기능을 통해 MCP는 AI 시스템이 다양한 도구와 데이터를 안전하게 통합하고, 더 풍부한 컨텍스트를 기반으로 사용자에게 응답할 수 있도록 지원합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;MCP를 활용 예시:&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;&lt;b&gt;개인의 몸 컨디션에 맞는 러닝 훈련 플랜을 짜주는 앱&lt;/b&gt;&amp;rdquo;에 MCP를 적용하는 게 &lt;b&gt;과도한 설계&lt;/b&gt;일까?&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;✅ &amp;ldquo;퍼스널 코칭, 장기적 사용자 맞춤, 지속적 인터랙션&amp;rdquo;이 필요한 서비스라면 MCP는 오버 엔지니어링이 아니라 오히려 적절한 선택입니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt; &amp;nbsp; 왜 러닝 코치 앱에 MCP가 적절할까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 &quot;모델이 사용자의 상태를 이해하고, 문맥을 바탕으로 똑똑하게 반응하는 것&quot;에 최적화된 방식입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;✅&amp;nbsp; 이 앱의 특성과 MCP의 궁합:&lt;/b&gt;&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&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;/tr&gt;
&lt;tr&gt;
&lt;td&gt;훈련 데이터, 컨디션, 부상 여부 등 다양한 정보 사용&lt;/td&gt;
&lt;td&gt;MCP 컨텍스트로 통합 관리 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장기적 코칭 흐름 (예: 3개월 마라톤 준비)&lt;/td&gt;
&lt;td&gt;이전 기록 기반 계획을 모델이 이해 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 즉, &lt;b&gt;사용자 맞춤화 + 장기 대화 흐름 + 복합적인 데이터&lt;/b&gt;가 필요한 구조라면 MCP는 자연스럽고 강력한 솔루션이 될 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;  오버 엔지니어링이 되는 경우는?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 앱이라면 &lt;b&gt;MCP는 과하고 단순한 시스템이 더 나음&lt;/b&gt;:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단발성 질문/응답 위주의 챗봇&lt;/li&gt;
&lt;li&gt;개인화가 거의 필요 없는 정적 정보 제공 앱&lt;/li&gt;
&lt;li&gt;컨텍스트 변화가 거의 없는 단순 추천기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: &quot;AI가 하루에 한 문장 영어 회화를 알려주는 서비스&quot;같은 경우는 MCP 없이도 충분히 가능합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;✅ MCP 도입이 특히 효과적인 AI 서비스 예시&lt;/b&gt;&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;AI 튜터&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;학생의 학습 기록, 취약점, 목표에 따라 맞춤 과외&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;심리 상담 챗봇&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사용자의 감정 변화, 지난 대화 맥락 고려&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;AI 피트니스/영양 코치&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;건강 상태, 목표, 식단 이력 기반 조언&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;AI 비서/스케줄러&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사용자의 일정, 우선순위, 지난 작업 상태 인식&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;  &lt;b&gt;AI 건강 모니터링 도우미&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;바이탈 기록, 복약 이력 등 복합 데이터 기반 코칭&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. &lt;b&gt;MCP가 필요한 이유&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MCP는 &lt;b&gt;LLM(Large Language Model)&lt;/b&gt;이 사용자의 현재 컨텍스트를 이해하고 일관된 경험을 제공할 수 있도록 돕는 프로토콜입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자의 최근 훈련 기록&lt;/li&gt;
&lt;li&gt;체중, 심박수, 컨디션 변화&lt;/li&gt;
&lt;li&gt;목표 (5km 완주, 10km 기록 단축 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 것들이 모델이 참고할 &lt;b&gt;컨텍스트가&lt;/b&gt; 되고, MCP를 이용하면 이 컨텍스트를 모델과 효과적으로 주고받을 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. MCP를 서비스에 적용하는 개념적 구조&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;User Context 생성기 (Context Provider)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유저 데이터를 정리해서 컨텍스트로 정의하기 (JSON, YAML 등으로 표현 가능)&lt;/li&gt;
&lt;li&gt;예: {&quot;user_id&quot;: 123, &quot;recent_runs&quot;: [...], &quot;resting_hr&quot;: 60, &quot;goal&quot;: &quot;10K race&quot;}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;MCP Context Store&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위의 컨텍스트 문서를 저장하고 업데이트함&lt;/li&gt;
&lt;li&gt;이건 내부 DB나 vector DB, 혹은 MCP를 지원하는 저장소(API 형태일 수도 있음)&lt;/li&gt;
&lt;li&gt;PostgreSQL / Redis / JSON blob in S3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LLM 호출 시 MCP 포함&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM API를 MCP 지원하는 형태로 연동 (예: OpenAI Assistants API는 일부 지원 예정)&lt;/li&gt;
&lt;li&gt;프롬프트에 MCP Context를 자동으로 포함시키는 로직 작성&lt;/li&gt;
&lt;li&gt;유저가 &quot;이번 주 훈련 어떻게 할까?&quot;라고 질문하면, 앱은 LLM에게 요청을 보낼 때, 해당 유저의 MCP Context도 같이 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결과 출력&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM이 context-aware 하게 훈련 계획을 제안해 줌&lt;/li&gt;
&lt;li&gt;예: &quot;최근에 5K를 3번 달렸고, 심박수가 안정적이니 이번 주에는 7K 페이스런을 추가하세요!&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. User Context 구성 예시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;MCP 컨텍스트는&lt;/b&gt; 기본적으로 AI가 &lt;b&gt;사용자의 상황을 더 잘 이해할 수 있도록 도와주는 구조화된 정보 묶음&lt;/b&gt;입니다. 러닝 훈련 앱에 적용한다면 다음과 같은 요소들을 포함할 수 있습니다:&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  예시: training_context.json&lt;/h3&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;user_profile&quot;: {
    &quot;name&quot;: &quot;Kim&quot;,
    &quot;age&quot;: 29,
    &quot;gender&quot;: &quot;female&quot;,
    &quot;fitness_level&quot;: &quot;beginner&quot;,
    &quot;goals&quot;: &quot;increase endurance and reduce body fat&quot;
  },
  &quot;health_metrics&quot;: {
    &quot;height_cm&quot;: 165,
    &quot;weight_kg&quot;: 58,
    &quot;resting_heart_rate&quot;: 62,
    &quot;max_heart_rate&quot;: 185,
    &quot;body_fat_percentage&quot;: 24.5
  },
  &quot;training_history&quot;: {
    &quot;recent_runs&quot;: [
      {&quot;date&quot;: &quot;2025-04-05&quot;, &quot;distance_km&quot;: 3, &quot;duration_min&quot;: 24},
      {&quot;date&quot;: &quot;2025-04-07&quot;, &quot;distance_km&quot;: 5, &quot;duration_min&quot;: 40}
    ],
    &quot;weekly_frequency&quot;: 2
  },
  &quot;constraints&quot;: {
    &quot;injury&quot;: false,
    &quot;available_days&quot;: [&quot;Monday&quot;, &quot;Wednesday&quot;, &quot;Saturday&quot;]
  },
  &quot;preferences&quot;: {
    &quot;indoor_running&quot;: false,
    &quot;preferred_time&quot;: &quot;morning&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구성한 컨텍스트를 AI에 요청을 보낼 때 함께 전달하면, &lt;b&gt;훈련 계획이 더 정밀하게 개인화&lt;/b&gt;될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러닝 훈련 앱에 MCP(Model Context Protocol)를 적용하면, 단순한 템플릿형 추천을 넘어서 &lt;b&gt;개인의 상태와 목표에 최적화된 AI 훈련 플랜&lt;/b&gt;을 만들 수 있습니다. 사용자에 대한 컨텍스트를 구조화해 AI에 전달함으로써, 서비스는 훨씬 더 &lt;b&gt;정확하고 신뢰할 수 있는 개인화 경험&lt;/b&gt;을 제공하게 됩니다. 앞으로 더 많은 AI 기반 서비스들이 이러한 프로토콜을 도입해 &lt;b&gt;정말 유용한 스마트 서비스&lt;/b&gt;로 발전할 수 있기를 기대해 봅니다.  &amp;zwj;♀️✨&lt;/p&gt;</description>
      <category>Tech Trends</category>
      <category>MCP</category>
      <category>mcp context</category>
      <category>mcp store</category>
      <category>model context protocol</category>
      <category>user context</category>
      <category>클로드</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/21</guid>
      <comments>https://juganote.tistory.com/entry/MCP-Model-Context-Protocol-%EB%8F%84%EB%8C%80%EC%B2%B4-%EB%AD%90%EA%B8%B8%EB%9E%98#entry21comment</comments>
      <pubDate>Tue, 15 Apr 2025 22:15:13 +0900</pubDate>
    </item>
    <item>
      <title>알아두면 은근히 쓸 곳 많은 파이썬 기본기능: 코테 활용 꿀팁</title>
      <link>https://juganote.tistory.com/entry/%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%9D%80%EA%B7%BC%ED%9E%88-%EC%93%B8-%EA%B3%B3-%EB%A7%8E%EC%9D%80-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B8%B0%EB%B3%B8%EA%B8%B0%EB%8A%A5-%EC%BD%94%ED%85%8C-%ED%99%9C%EC%9A%A9-%EA%BF%80%ED%8C%81</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시작하며:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 평소에 쓰던 기능만 계속 쓰게 되는 경향이 있습니다. 파이썬은 배우기 쉽고 활용도가 높은 언어인 만큼, 다양하고 편리한 기능이 기본적으로 제공되고 있습니다. 이 글에서는 평소에는 놓치고 있던 파이썬의 기능을 다시 한번 짚어보고, 실제 개발 과정이나 코딩테스트에서 유용하게 활용할 수 있는 팁을 소개하고자 합니다. &quot;알아두면 은근히 쓸 곳이 많은&quot; 파이썬 기능들을 함께 살펴보며, 파이썬 활용 능력을 한 단계 업그레이드해 보아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 반복 작업의 마법사, itertools&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복적인 작업을 효율적으로 처리하고 싶다면 itertools 모듈을 적극 활용해 보세요. 다양한 반복 패턴을 생성하는 함수들을 제공하여, 복잡한 반복 로직을 간결하고 직관적으로 만들 수 있습니다. 순열(permutations), 조합(combinations), 곱집합(product) 등을 쉽게 생성하여 특정 조건에 맞는 데이터를 필터링하거나, 모든 경우의 수를 탐색하는 데 활용할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2025-03-28 at 17.21.58.png&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;1032&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYdfwo/btsM12ir7QU/EHpOI7WhtrxCoUg7aYY2a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYdfwo/btsM12ir7QU/EHpOI7WhtrxCoUg7aYY2a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYdfwo/btsM12ir7QU/EHpOI7WhtrxCoUg7aYY2a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYdfwo%2FbtsM12ir7QU%2FEHpOI7WhtrxCoUg7aYY2a1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1622&quot; height=&quot;1032&quot; data-filename=&quot;Screenshot 2025-03-28 at 17.21.58.png&quot; data-origin-width=&quot;1622&quot; data-origin-height=&quot;1032&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 combinations()와 permutations()를 잘 알아두면 유용합니다. 아래 예시를 살펴보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 리스트 arr에서 2개의 원소를 선택하여 그 합이 소수인 경우의 수를 구하는 것인데, combinations()는 중복되지 않는 조합을 만들어주어 편리하게 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from itertools import combinations

def is_prime(n):
    if n &amp;lt; 2:
        return False
    for i in range(2, int(n ** 0.5) + 1):
        if n % i == 0:
            return False
    return True

arr = [3, 1, 6, 4]
answer = 0

for items in combinations(arr, 2):  # 2개 원소 선택
    num = sum(items)  # 합 구하기
    if is_prime(num):  # 소수 판별
        answer += 1  # 소수 개수 증가

print(answer)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 효율적인 데이터 관리, collections&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;collections 모듈은 특수한 목적을 가진 다양한 자료구조를 제공하여, 데이터를 효율적으로 관리하고 처리하는 데 도움을 줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/41dQe/btsM0H7G2yx/HkfuThimKYAg3pPKaKdWN1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/41dQe/btsM0H7G2yx/HkfuThimKYAg3pPKaKdWN1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/41dQe/btsM0H7G2yx/HkfuThimKYAg3pPKaKdWN1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F41dQe%2FbtsM0H7G2yx%2FHkfuThimKYAg3pPKaKdWN1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;630&quot; height=&quot;340&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;690&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;deque:&lt;/b&gt; 양방향 큐(double-ended queue)로, 리스트의 append()와 pop() 연산보다 빠르며, FIFO(First-In, First-Out) 및 LIFO(Last-In, First-Out) 큐 구현에 유용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OrderedDict:&lt;/b&gt; 삽입 순서를 기억하는 딕셔너리로, 순서가 중요한 데이터를 다룰 때 유용합니다. Python 3.7부터 일반 dict도 삽입 순서를 유지하지만, 이전 버전에서는 OrderedDict가 필요합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Counter:&lt;/b&gt; 해시 가능한 객체의 개수를 세는 딕셔너리 서브클래스로, 빈도 분석이나 통계 처리 등에 활용할 수 있습니다.&lt;img id=&quot;img_1743178542747_1&quot; /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Counter를 활용한 예시를 살펴보면 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from collections import Counter

record = [2,1,2,6,2,4,3,3]

record_count = Counter(record)
# Counter({2:3, 3:2, 1:1, 6:1, 4:1})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음으로는 OrderdDict와 deque를 활용하여 LRU 캐시를 구현하는 예시를 살펴봅시다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;LRU(Least Recently Used) 캐시란?&lt;br /&gt;가장 최근에 사용되지 않은 항목을 먼저 제거하는 캐시 기법입니다. functools 모듈의 @lru_cache 데코레이터를 사용하여 간단하게 구현할 수 있습니다.&lt;/blockquote&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# deque 사용하여 LRU cache 구현하기
from collections import deque

cache = deque([], maxlen=cache_size)

for key in searches:
    if key in cache:
        cache.remove(key)  # 기존 위치에서 제거
        cache.append(key)  # 가장 최근 위치(맨 끝)로 이동
    else:
        cache.append(key)  # 새로운 검색어 추가 (캐시 크기 초과 시 자동 제거)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;deque를 사용하여 LRU cache 구현할 때에는 `&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;maxlen=cache_size`&lt;span&gt;&amp;nbsp;설정을 통해&amp;nbsp;&lt;/span&gt;&lt;/span&gt;삭제 연산이 자동으로 구현되어 편리합니다. 또한 deque의 특성상 O(1) 연산으로 검색어를 추가하거나 삭제할 수 있습니다. 하지만 `cache.remove(key)` 를 수행할 때 O(n) 연산이 발생합니다. 이를 개선하려면 OrderedDict를 사용하여 모든 연산을 O(1) 시간 복잡도로 구현하는 것이 더 효율적입니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# OrderedDict 사용하여 LRU cache 구현하기
from collections import OrderedDict

class LRUCache:
    def __init__(self, capacity: int):
        self.capacity = capacity  # 캐시의 최대 크기
        self.cache = OrderedDict()

    def get(self, key: str) -&amp;gt; str:
	    if key not in self.cache:
	        return -1  # 키가 존재하지 않으면 -1 반환
	    else:
	        self.cache.move_to_end(key, last=True)
	        # 키가 존재할 경우 해당 키를 가장 최근으로 이동 후 반환
	        return self.cache[key]

		def put(self, key: str, value: str) -&amp;gt; None:
		    if key in self.cache:
				    # 캐쉬에 키 존재하는 경우 값 업데이트 후 가장 최근으로 이동
		        self.cache[key] = value  
		        self.cache.move_to_end(key, last=True)
		    else:
		        if len(self.cache) == self.capacity:
				        # 캐시가 가득 찬 경우 가장 오래된 항목 제거 후 새 키 추가
		            self.cache.popitem(last=False)
		        self.cache[key] = value&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 숫자 변환의 달인: 정수 &amp;lt;-&amp;gt; 이진수, 알파벳 &amp;lt;-&amp;gt; 숫자&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래밍을 하다 보면 정수와 이진수 간의 변환, 또는 알파벳과 숫자 간의 변환이 필요한 경우가 종종 있습니다. &lt;b&gt;비트맵을 활용한 문제&lt;/b&gt;(예: 게임 맵, 암호 해독)에서 자주 활용됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;정수 &amp;lt;-&amp;gt; 이진수:&lt;/b&gt; bin(), int(binary_string, 2) 함수를 사용하여 손쉽게 변환할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;알파벳 &amp;lt;-&amp;gt; 숫자:&lt;/b&gt; ord(), chr() 함수를 활용하여 ASCII 코드 값을 얻거나 문자로 변환할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;bin(9) # 9를 2진수로 변환한 결과인 '0b1001'을 반환합니다.

# 비트 2진연산 하는법
# 9 &amp;rarr; 1001 (2진수)
# 30 &amp;rarr; 11110 (2진수)

9 | 30 # 비트 OR 연산 &amp;rarr; 0b11111 (2진수) -&amp;gt; 10진수 31
bin(9|30)[2:] # 0b 접두사 제거 -&amp;gt; 11111

bin(3|5)[2:] # '0b111' -&amp;gt; '111'
bin(3|5)[2:].zfill(5)
# zfill(5)은 문자열 길이를 5로 맞추고 앞을 0으로 채움 -&amp;gt; '00111'
bin(3|5)[2:].zfill(5).replace('1', '#').replace('0', ' ')
# 1을 #로 변환, 0을 공백( )으로 변환 -&amp;gt; '  ###'

ord('a')  # 97
ord('b')  # 98
ord('c')  # 99

def alphabet_index(ch):
    return ord(ch) - ord('a') + 1
    # a -&amp;gt; 1, b -&amp;gt; 2, ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 문자열 다루기의 기본: String method&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열은 프로그래밍에서 가장 많이 다루는 데이터 타입 중 하나입니다. 파이썬은 다양한 문자열 처리 메서드를 제공하며, 그중 join() 메서드는 여러 문자열을 특정 구분자를 기준으로 연결할 때 매우 유용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;join()&lt;/b&gt;: 리스트나 튜플의 문자열 요소들을 특정 구분자(예: 쉼표, 공백 등)를 사용하여 하나의 문자열로 합칠 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;words = [&quot;Hello&quot;, &quot;World&quot;]
print(&quot; &quot;.join(words))  # Hello World
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;strip()&lt;/b&gt; 은 문자열의 공백을 제거해 줍니다. &lt;b&gt;lstrip()&lt;/b&gt;이나 &lt;b&gt;rstrip()&lt;/b&gt;으로 왼쪽 또는 오른쪽의 공백을 제거할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;replace()를&lt;/b&gt; 활용하여 특정 문자열을 치환할 수 있습니다.&lt;/li&gt;
&lt;li&gt;그 외에도 &lt;b&gt;find()&lt;/b&gt;, &lt;b&gt;index()&lt;/b&gt;, &lt;b&gt;count()&lt;/b&gt;등으로 특정 문자열을 찾거나, 문자 개수를 세는데 활용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;rjust()&lt;/b&gt;, &lt;b&gt;ljust()&lt;/b&gt;, &lt;b&gt;center()&lt;/b&gt;를 활용하여 문자열을 오른쪽, 왼쪽, 또는 가운데로 정렬하거나 공백을 채울 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;livecodeserver&quot;&gt;&lt;code&gt;text = &quot;Hello&quot;
print(text.rjust(10))  # 기본값(공백)
print(text.rjust(10, '-'))  # '-'로 채우기

# 출력 결과
# `     Hello`
# `-----Hello`
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 리스트 활용의 묘미: zip()&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리스트는 데이터를 저장하고 관리하는 데 필수적인 자료구조입니다. 파이썬의 리스트 메서드 중 zip() 함수는 여러 개의 리스트를 묶어 튜플의 리스트로 만들어주는 기능을 합니다. 두 개 이상의 리스트를 병렬적으로 순회하거나, 두 리스트의 요소를 짝지어 처리할 때 유용합니다.&lt;/p&gt;
&lt;pre class=&quot;prolog&quot;&gt;&lt;code&gt;list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']

zipped = zip(list1, list2)
print(list(zipped))  
# [(1, 'a'), (2, 'b'), (3, 'c')]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 zip()을 사용하여 쉽게 dictionary를 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;keys = [&quot;name&quot;, &quot;age&quot;, &quot;city&quot;]
values = [&quot;Alice&quot;, 25, &quot;Berlin&quot;]

my_dict = dict(zip(keys, values))
print(my_dict)
# {'name': 'Alice', 'age': 25, 'city': 'Berlin'}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;zip(*zipped)&lt;/b&gt;을 사용하면 다시 원래 리스트로 분해할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;zipped = [(1, 'a'), (2, 'b'), (3, 'c')]

list1, list2 = zip(*zipped)
print(list1)  # (1, 2, 3)
print(list2)  # ('a', 'b', 'c')
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;행렬에서 zip(*matrix)를 사용하면 행(row)과 열(column)이 바뀝니다.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

transposed = list(zip(*matrix))
print(transposed)
# [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;6. 간결하고 효율적인 데이터 변환: map()&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map() 함수는 주어진 함수를 리스트나 다른 이터러블의 각 요소에 적용하여 새로운 이터러블을 생성하는 기능을 제공합니다. 리스트의 모든 숫자를 제곱하거나, 문자열 리스트의 각 요소를 정수로 변환하는 등, 간결하고 효율적인 데이터 변환에 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;map()의 기본적인 사용 방법은 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;map(함수, iterable)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 인자는 적용할 함수이고, 두 번째 인자는 반복 가능한(iterable) 객체 (리스트, 튜플 등)입니다. 이때 map()의 반환값은 map 객체이므로, list()나 tuple()로 변환해야 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자열을 정수로 변환하는 예시를 살펴봅시다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;string_numbers = ['1', '2', '3', '4', '5']
int_numbers = list(map(int, string_numbers))
print(int_numbers) # 출력:[1, 2, 3, 4, 5]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 리스트의 모든 요소에 함수를 적용하는 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;def square(x):
    return x * x

numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(square, numbers))
print(squared_numbers)  # 출력: [1, 4, 9, 16, 25]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 굳이 square() 함수를 따로 정의하지 않고, &lt;b&gt;lambda &lt;/b&gt;함수를 활용하면 더 간결하게 표현할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;numbers = [1, 2, 3, 4, 5]
squared_numbers = list(map(lambda x: x * x, numbers))
print(squared_numbers)  # 출력: [1, 4, 9, 16, 25]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lambda 대신 &lt;b&gt;리스트 컴프리헨션&lt;/b&gt; 방식을 사용하는 경우입니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;numbers = [1, 2, 3, 4, 5]
squared_numbers = [x * x for x in numbers]
print(squared_numbers)  # 출력: [1, 4, 9, 16, 25]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 가지 리스트를 사용하는 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;numbers1 = [1, 2, 3]
numbers2 = [4, 5, 6]
sum_numbers = list(map(lambda x, y: x + y, numbers1, numbers2))
print(sum_numbers)  # 출력: [5, 7, 9]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;filter()와 조합하여 조건에 맞는 데이터 변환 시에도 사용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# 양수만 제곱하여 변환하는 프로그램
numbers = [-2, -1, 0, 1, 2]
positive_squared = list(map(lambda x: x ** 2, filter(lambda x: x &amp;gt; 0, numbers)))

# 리스트 컴프리헨션 스타일
# positive_squared = [x ** 2 for x in numbers if x &amp;gt; 0]

print(positive_squared)  # 출력: [1, 4]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;7. 간결한 함수 정의: 람다 펑션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다 함수는 익명 함수를 간결하게 정의할 수 있는 방법입니다. 간단한 연산이나 함수를 일시적으로 사용할 때 유용합니다. map(), filter(), sorted() 등과 함께 사용하여 코드를 더욱 간결하게 만들 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;gml&quot;&gt;&lt;code&gt;calculator = {
    'S': lambda x: x,       # Single power (그대로)
    'D': lambda x: x ** 2,  # Double power (제곱)
    'T': lambda x: x ** 3   # Triple power (세제곱)
}
symbol = 'D'
number = 4

result = calculator[symbol](number)
print(result)  # 출력: 16  (4&amp;sup2;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;lambda를 활용하면 튜플 리스트 정렬을 쉽게 커스텀할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;sas&quot;&gt;&lt;code&gt;students = [(&quot;Alice&quot;, 25), (&quot;Bob&quot;, 20), (&quot;Charlie&quot;, 23)]

# 나이 기준으로 정렬 (두 번째 요소를 기준으로 정렬)
sorted_students = sorted(students, key=lambda x: x[1])

print(sorted_students)  
# 출력: [('Bob', 20), ('Charlie', 23), ('Alice', 25)]
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;8. 중복 없는 데이터 관리: set()&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;set() 자료형은 중복된 요소를 허용하지 않고, 순서가 없는 데이터 집합을 표현하는 데 사용됩니다. 리스트에서 중복된 요소를 제거하거나, 두 집합 간의 합집합, 교집합, 차집합 등을 쉽게 구할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;isdisjoint()&lt;/b&gt;: 공통된 요소가 없다면&amp;nbsp;True를 리턴합니다. (교집합이 공집합일 때)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;issubset(other)&lt;/b&gt; (&amp;lt;=): 모든 요소가 other에 있는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;issuperset(other)&lt;/b&gt; (&amp;gt;=): other의 모든 요소가 집합에 있는지 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;union&lt;/b&gt;(|), &lt;b&gt;intersection&lt;/b&gt;(&amp;amp;), &lt;b&gt;difference&lt;/b&gt;(-), &lt;b&gt;symmetric_difference&lt;/b&gt;(^)로 집합 연산을 수행할 수 있습니다,&lt;/li&gt;
&lt;li&gt;&lt;b&gt;update&lt;/b&gt;, &lt;b&gt;intersection_update&lt;/b&gt;, &lt;b&gt;difference_update&lt;/b&gt;, &lt;b&gt;symmetric_difference_update&lt;/b&gt;를 통해 손쉽게 집합에 데이터를 추가하거나 제거할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;remove()&lt;/b&gt;와 &lt;b&gt;discard()&lt;/b&gt;는 집합에서 요소를 삭제하는 같은 기능을 하지만, remove()는 찾는 요소가 없을 때 KeyError를 발생시킵니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;9. 강력한 행렬 처리: numpy Array&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수치 계산 및 과학 기술 분야에서 널리 사용되는 numpy 라이브러리는 다차원 배열(행렬)을 효율적으로 처리할 수 있는 기능을 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;np.zeros(shape)&lt;/b&gt;, &lt;b&gt;np.ones(shape)&lt;/b&gt;, &lt;b&gt;np.full(shape, value)&lt;/b&gt; 등을 사용하여 0, 1 또는 원하는 값으로 채워진 배열을 생성할 수 있습니다. 또한 배열 인덱싱 및 슬라이싱이 보다 쉽게 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;import numpy as np

s = [['C', 'C', 'B', 'D', 'E'],
	 ['A', 'A', 'A', 'D', 'E'],
	 ['A', 'A', 'A', 'B', 'F'],
	 ['C', 'C', 'B', 'B', 'F']]

s = np.array(s)

col = 5 # 열
row = 4 # 행

for i in range(row-1):
	for j in range(col-1):
    	print(s[i:i+2, j:j+2]) # 2x2 사각형 출력

		# 모든 문자가 똑같은 사각형 찾기
		if np.all(s[i:i+2, j:j+2] == 'A'): # true or false
        	print(&quot;Square found&quot;)
						
# np array에서 원소 개수 구하기
num_of_a = (s=='A').sum()
num_of_a = np.count_nonzero(s == 'A')&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;10. 데이터 분석의 핵심: pandas DataFrame&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pandas 라이브러리는 데이터 분석 및 조작, 전처리를 위한 강력한 도구인 DataFrame을 제공합니다. 엑셀 스프레드시트와 유사한 형태로 데이터를 구조화하고 분석하는 데 유용합니다. 모든 요소가 동일한 데이터 타입을 가져야 하는 NumPy Array와는 달리, DataFrame은 각 열(column)마다 &lt;b&gt;서로 다른 데이터 타입&lt;/b&gt;을 가질 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;import pandas as pd

data = pd.DataFrame()

# 빈 데이터 프레임에 데이터 채우기
data = pd.DataFrame({
    0: [1, 2, 3, 4, 5],
    1: [1, 2, 3, 3, 4]
})

data.value_counts()
# 각 행을 하나의 &quot;튜플&quot;로 간주하고, 그 &quot;튜플&quot;이 데이터프레임에서 몇 번 나타나는지 세어줍니다.
# (1, 1)    1
# (2, 2)    1
# (3, 3)    1
# (4, 4)    1
# (5, 4)    1

data[0].value_counts()
# 첫 번째 열에서 중복되는 값의 개수를 확인

if len(data[1]) == len(data[1].value_counts()):
		# 두 번째 열에서 중복되는 아이템이 있는지 확인하기
		print(&quot;no duplication&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;11. 패턴 매칭의 힘: 정규표현식&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정규표현식(Regular Expression)은 문자열에서 특정 패턴을 검색하고 조작하는 강력한 도구입니다. 복잡한 문자열 처리 작업을 효율적으로 수행할 수 있습니다. 텍스트 데이터에서 특정 패턴을 찾거나, 문자열을 파싱 하고, 유효성을 검사하는 등 다양한 작업에 활용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import re

testcase = ['1S2D*3T', '1D2S#10S']
pattern = re.compile(r'([0-9]|10)([SDT])([\\*\\#]?)') 

# 그룹핑 설명
# ([0-9]|10): 숫자(0부터 9까지의 숫자) 또는 10을 첫 번째 그룹으로 캡처합니다. 이 패턴은 1자리 숫자 또는 2자리 숫자(10)를 포함할 수 있습니다.
# ([SDT]): 문자 'S', 'D', 'T' 중 하나를 두 번째 그룹으로 캡처합니다.
# ([\\*\\#]?): ?는 해당 기호가 있을 수도 없을 수도 있다는 의미입니다. * 또는 # 기호가 있을 수도, 없을 수도 있음을 나타냅니다.

pattern.findall(testcase[0])
# findall은 문자열에서 정규 표현식과 일치하는 모든 항목을 찾아 리스트로 반환합니다.
# [('1', 'S', ''), ('2', 'D', '*'), ('3', 'T', '')]

pattern = re.compiler(r'[a-z]{2}')
# [a-z]: 소문자 알파벳(a부터 z까지)을 의미합니다.
# {2}: 바로 앞의 패턴이 2번 반복되는 것을 의미합니다. 
# 즉, 이 정규 표현식은 소문자 알파벳 두 개가 연속적으로 나타나는 부분을 찾습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;12. 나눗셈 및 숫자 연산:&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 연산을 할 때, 주의해야 할 점과 그에 관련된 함수들을 살펴보겠습니다. math 패키지는 다양한 수학 연산을 위한 함수를 제공하며, 복잡한 수학적 작업을 할 때 유용합니다. ZeroDivisionError를 피하려면 try-except를 사용하거나, 나누기 전에 0을 체크하는 것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;나눗셈 연산자&lt;/b&gt;: &lt;b&gt;/&lt;/b&gt; (division) 연산자는 &lt;u&gt;소수점까지 포함한&lt;/u&gt; 나눗셈을 수행합니다. 두 숫자를 나누었을 때 소수점 이하까지 결과를 반환합니다. &lt;b&gt;//&lt;/b&gt; (floor division) 연산자는 나눗셈 후 소수점 이하를 버리고 &lt;u&gt;정수 부분만 반환&lt;/u&gt;합니다. 즉, 나누고 난 후 소수점 이하를 버리고 &lt;b&gt;내림(floor)&lt;/b&gt; 처리됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;print(7 / 2)  # 출력: 3.5
print(7 // 2)  # 출력: 3
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;소수점 반올림&lt;/b&gt; (&lt;b&gt;round()&lt;/b&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;print(round(3.14159, 2))  # 출력: 3.14
print(round(2.71828, 3))  # 출력: 2.718
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;버림&lt;/b&gt; (&lt;b&gt;int()&lt;/b&gt; 또는&lt;b&gt; math.floor()&lt;/b&gt;): int() 함수는 소수점 이하를 버리고 정수 부분만 남깁니다. 이 함수는 floor와 유사하게 동작하지만, floor는 항상 소수점 아래를 내리는 반면, int()는 그냥 소수점 이하를 제거하는 방법입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1743179562046&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;print(int(3.99))  # 출력: 3
print(int(-3.99))  # 출력: -3

import math

print(math.floor(3.7))  # 출력: 3
print(math.floor(-3.7))  # 출력: -4&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;올림&lt;/b&gt; (math.ceil()): 숫자의 &lt;b&gt;소수점 이하를 올려서&lt;/b&gt; 가장 작은 정수로 변환합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;import math

print(math.ceil(3.2))  # 출력: 4
print(math.ceil(-3.2))  # 출력: -3
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;13. 모든 요소 또는 일부 요소 확인: any() all() 내장 함수&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;any() 함수는 이터러블의 요소 중 하나라도 참인 경우 True를 반환하고, all() 함수는 모든 요소가 참인 경우 True를 반환합니다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;# 리스트에 양수만 포함되어 있는지 확인 (모든 값이 양수일 경우 True)
numbers = [1, 2, 3, 4]
print(all(num &amp;gt; 0 for num in numbers))  # 출력: True (모두 양수)

# 리스트에 하나라도 음수가 포함되어 있는지 확인 (하나라도 음수이면 True)
numbers = [1, 2, 3, -1]
print(any(num &amp;lt; 0 for num in numbers))  # 출력: True (하나라도 음수인 값이 있으므로)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;마무리하며:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서 소개한 파이썬의 기능들은 제가 평소에 잘 활용하지 않고 있다가 코딩 테스트 문제를 풀어보며 배우게 된 것들입니다. 이 글이 도움이 되어서 day-to-day 개발이나 코딩 테스트에 도움이 되셨으면 좋겠습니다.&lt;/p&gt;</description>
      <category>개발 이모저모</category>
      <category>combinations</category>
      <category>itertools</category>
      <category>Lambda</category>
      <category>LRU Cache</category>
      <category>map</category>
      <category>permutations</category>
      <category>python join</category>
      <category>zip</category>
      <category>파이썬</category>
      <category>파이썬 코테</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/20</guid>
      <comments>https://juganote.tistory.com/entry/%EC%95%8C%EC%95%84%EB%91%90%EB%A9%B4-%EC%9D%80%EA%B7%BC%ED%9E%88-%EC%93%B8-%EA%B3%B3-%EB%A7%8E%EC%9D%80-%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EA%B8%B0%EB%B3%B8%EA%B8%B0%EB%8A%A5-%EC%BD%94%ED%85%8C-%ED%99%9C%EC%9A%A9-%EA%BF%80%ED%8C%81#entry20comment</comments>
      <pubDate>Sat, 29 Mar 2025 01:36:10 +0900</pubDate>
    </item>
    <item>
      <title>Spring Security, 5분 만에 핵심 개념 완전 정복! 초보자를 위한 친절한 가이드</title>
      <link>https://juganote.tistory.com/entry/Spring-Security-5%EB%B6%84-%EB%A7%8C%EC%97%90-%ED%95%B5%EC%8B%AC-%EA%B0%9C%EB%85%90-%EC%99%84%EC%A0%84-%EC%A0%95%EB%B3%B5-%EC%B4%88%EB%B3%B4%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%B9%9C%EC%A0%88%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spring Security 소개&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 강력하고 매우 유연하게 커스터마이징 할 수 있는 인증(Authentication) 및 접근 제어(Access-Control) 프레임워크입니다. Spring 기반 애플리케이션을 안전하게 보호하기 위한 사실상의 표준이라고 할 수 있습니다. Spring Security의 진정한 강점은 사용자 정의한 요구 사항을 충족할 수 있도록 쉽게 커스터마이징 할 수 있다는 점입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주요 특징은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포괄적이고 확장 가능한 인증(Authentication) 및 권한 부여(Authorization) 지원: 다양한 인증 방식과 권한 관리 기능을 제공하며, 필요에 따라 쉽게 확장하고 커스터마이징 할 수 있습니다.&lt;/li&gt;
&lt;li&gt;세션 고정 공격, 클릭재킹(clickjacking), 교차 사이트 요청 위조(CSRF: Cross-Site Request Forgery) 등과 같은 공격으로부터 보호: 웹 애플리케이션에서 발생할 수 있는 다양한 보안 위협에 대한 기본적인 보호 기능을 내장하고 있습니다.&lt;/li&gt;
&lt;li&gt;Servlet API 통합: Servlet 기반의 웹 애플리케이션과 원활하게 통합되어 Spring Security의 기능을 쉽게 적용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Spring Web MVC와의 선택적 통합: Spring Web MVC 프레임워크와 함께 사용하여 더욱 강력하고 편리한 웹 보안 기능을 구현할 수 있습니다.&lt;/li&gt;
&lt;li&gt;위에서 언급된 내용 외에도 다양한 고급 보안 기능을 제공하여 애플리케이션의 보안 수준을 높일 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티에 대해서 더 자세히 알아보기 전에 배워두면 좋은 보안의 기본 6 원칙입니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;6 Security Principles&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Trust nothing (아무것도 믿지 마세요.)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 리퀘스트를 검증해야 합니다.&lt;/li&gt;
&lt;li&gt;시스템으로 들어오는 모든 데이터들을 validation 해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Assign least privileges (최소한의 권한만 부여하기)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템을 디자인할 때부터 보안 요구사항을 고려하는 것이 좋습니다.&lt;/li&gt;
&lt;li&gt;사용자 역할(role)과 그 권한을 명확하게 정의해두어야 합니다.&lt;/li&gt;
&lt;li&gt;어떤 레벨(application, database, server)에서든 가능한 최소한의 권한만을 할당해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Have complete Mediation
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Complete Mediation: 모든 정보에 대한 모든 접근은 승인된 접근이어야 한다는 원칙.&lt;/li&gt;
&lt;li&gt;모든 접근 시도는 단일하고 중앙 집중화된 보안 게이트를 통과해야 합니다.&lt;/li&gt;
&lt;li&gt;잘 구현된 보안 필터를 적용하고, 각 사용자의 역할과 접근 권한을 테스트하세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Have defense in depth
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 계층의 보안 메커니즘을 적용하여 하나의 보안 계층이 실패하더라도 다른 계층이 방어할 수 있도록 합니다. Transport, Network, Infrastructure, OS, App 계층 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Have Economy of Mechanism (메커니즘의 경제성 확보)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안 아키텍처는 단순해야 합니다. 단순한 시스템이 보호하기 더 쉽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Ensure openness of design (설계의 개방성 보장)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보안 설계는 OAuth, JWT(JSON 웹 토큰)와 같은 보안 표준을 사용하여 공개적으로 이루어져야 합니다. 보안 결함을 식별하고 수정하기 더 쉽습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Spring MVC는 다음과 같이 작동합니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Request =&amp;gt; Dispatcher Servlet =&amp;gt; Controller(s)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dispatcher Servlet은 최전방 컨트롤러로서 작동합니다.&lt;/li&gt;
&lt;li&gt;Dispatcher Servlet은 리퀘스트를 적절한 컨트롤러로 보내는(라우팅) 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;그렇다면 Spring Security는 어떻게 작동할까요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Request =&amp;gt; Spring Security =&amp;gt; Dispatcher Servlet =&amp;gt; Controller(s)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring security는 들어오는 모든 리퀘스트를 인터셉트합니다.&lt;/li&gt;
&lt;li&gt;오직 검증된 요청(리퀘스트)만이 dispatcher servlet으로 전달됩니다.&lt;/li&gt;
&lt;li&gt;이것은 Security Principle 3번, complete mediation 조건을 충족합니다.&lt;/li&gt;
&lt;li&gt;Spring Security는 여러 겹의 필터(filter chain)를 실행시킵니다. 대략 다음과 같습니다. &quot;보안 검문소 라인&quot;이라고 볼 수 있습니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Authentication(인증)&lt;/li&gt;
&lt;li&gt;Authorization(권한부여)&lt;/li&gt;
&lt;li&gt;CORS(Cross Origin Resource Sharing)&lt;/li&gt;
&lt;li&gt;CSRF(Cross Site Request Forgery)&lt;/li&gt;
&lt;li&gt;Default login/logout page&lt;/li&gt;
&lt;li&gt;Translating exceptions into HTTP Responses(Exception Translation filter)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이 필터 체인의 순서는 아주 중요합니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;기본 확인 filters - CORS, CSRF, &amp;hellip;&lt;/li&gt;
&lt;li&gt;Authentication filter&lt;/li&gt;
&lt;li&gt;Authorization filter&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Cross-Origin Request Sharing(CORS)는 무엇이며 왜 필요할까요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 http://my-frontend.com이라는 웹사이트를 보고 있는데 이 웹사이트의 JavaScript 코드가 http://my-backend.com/api/data라는 다른 엔드포인트에 데이터를 요청하려고 하면, 브라우저는 기본적으로 이 요청을 차단합니다. 즉 브라우저가 현재 웹페이지의 출처(Origin)와 다른 출처의 리소스에 대한 AJAX 호출을 허용하지 않습니다. 이는 악의적인 웹사이트가 사용자의 민감한 정보를 다른 도메인의 서버로 몰래 전송하는 것을 방지하기 위한 중요한 보안 메커니즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 때로는 의도적으로 다른 도메인 간에 리소스 공유가 필요한 경우가 있습니다. 예를 들어, 프런트엔드 웹 애플리케이션은 별도의 백엔드 API 서버와 통신해야 할 수 있습니다. 이때 CORS 메커니즘이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CORS는 웹 서버가 어떤 출처의 웹 앱이 자신의 리소스에 접근하는 것을 허용할지 브라우저에게 알려주는 표준입니다. 즉, 브라우저가 다른 출처의 리소스에 AJAX 요청을 보내기 전에 서버에게 &quot;이 요청을 보내도 괜찮은가요?&quot;라고 먼저 물어보고, 서버가 허락하면 요청을 진행하는 방식입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot에서는 크게 두 가지 방법으로 CORS를 설정할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 전역 설정 (Global Configuration):&lt;/b&gt; 모든 REST 컨트롤러에 적용&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping(&quot;/api/**&quot;) // 특정 URL 패턴에 대해 CORS 설정
                .allowedOrigins(&quot;&amp;lt;http://my-frontend.com&amp;gt;&quot;, &quot;&amp;lt;https://another-frontend.net&amp;gt;&quot;) // 허용할 출처 명시
                .allowedMethods(&quot;GET&quot;, &quot;POST&quot;, &quot;PUT&quot;, &quot;DELETE&quot;) // 허용할 HTTP 메서드 명시
                .allowedHeaders(&quot;*&quot;) // 허용할 요청 헤더 명시
                .allowCredentials(true) // 인증 정보 (쿠키, HTTP 인증 등) 허용 여부
                .maxAge(3600); // Preflight 요청 결과 캐싱 시간 (초)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 로컬 설정 (Local Configuration): &lt;/b&gt;특정 REST 컨트롤러 또는 메서드에 적용&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@RestController
@CrossOrigin(origins = &quot;&amp;lt;http://yet-another-frontend.com&amp;gt;&quot;) // 클래스 레벨에서 특정 출처 허용
public class MyController {

    @GetMapping(&quot;/public&quot;)
    public String publicEndpoint() {
        return &quot;This endpoint is publicly accessible.&quot;;
    }

    @GetMapping(&quot;/private&quot;)
    @CrossOrigin // 메서드 레벨에서 모든 출처 허용
    public String privateEndpoint() {
        return &quot;This endpoint has default CORS settings (allowing all origins).&quot;;
    }

    @GetMapping(&quot;/admin&quot;)
    @CrossOrigin(origins = &quot;&amp;lt;https://admin-panel.com&amp;gt;&quot;, methods = { &quot;GET&quot;, &quot;POST&quot; }) // 메서드 레벨에서 특정 출처 및 메서드 허용
    public String adminEndpoint() {
        return &quot;This endpoint is only accessible from the admin panel.&quot;;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Cross-Site Request Forgery(CSRF)는 무엇인가요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 로그아웃 하지 않았을 시 유해 웹사이트가 브라우저 쿠키 정보를 훔쳐가서 사용자의 의도와는 다른 일들을 벌이는 공격입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의&lt;/b&gt;: 세션을 사용하는 게 아니라면 CSRF는 고려 사항이 아닙니다. 만당 stateless API를 사용한다면 CSRF에 대해서 걱정할 필요가 없고 설정을 꺼두어도 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설정 끄는 법: SecurityConfiguration 클래스의 SecurityFilterChain bean을 오버라이딩 하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .csrf(csrf -&amp;gt; csrf.disable())  // Disable CSRF
            .authorizeHttpRequests(auth -&amp;gt; auth
                .anyRequest().authenticated()  // Require authentication for all requests
            )
            .formLogin()  // Enable form login
            .and()
            .httpBasic();  // Enable basic authentication

        return http.build();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;어떻게 &lt;b&gt;CSRF를&lt;/b&gt;&amp;nbsp;예방할 수 있나요?&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Synchronizer token pattern(동기화 토큰 패턴) 사용하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;업데이트 (POST, PUT 등) 요청을 보내려면 이전 요청에서 받은 CSRF 토큰을 함께 포함해야 합니다. 서버는 요청과 함께 전달된 CSRF 토큰이 이전 요청에서 발급한 토큰과 일치하는지 확인하여 정상적인 요청인지 판단합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SameSite 쿠키 (Set-Cookie: SameSite=Strict)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SameSite=Strict로 설정된 쿠키는 &lt;b&gt;동일한 웹사이트 내에서 발생한 요청에만&lt;/b&gt; 전송됩니다.&lt;/li&gt;
&lt;li&gt;다른 웹사이트에서 해당 웹사이트로 요청을 보낼 때 (예: &amp;lt;form&amp;gt; 태그를 이용한 POST 요청), SameSite=Strict 쿠키는 전송되지 않습니다.&lt;/li&gt;
&lt;li&gt;설정 방법: application.properties 파일에 server.servlet.session.cookie.same-site=strict를 추가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spring Security 기본 설정값 살펴보기&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전부 authentication이 필요합니다.&lt;/li&gt;
&lt;li&gt;Form Authentication 이 사용됩니다.&lt;/li&gt;
&lt;li&gt;Basic Authentication이 사용됩니다.&lt;/li&gt;
&lt;li&gt;테스트 유저 &quot;user&quot;가 생성됩니다.&lt;/li&gt;
&lt;li&gt;CSRF 보호가 활성화됩니다.&lt;/li&gt;
&lt;li&gt;CORS 요청은 거부합니다.&lt;/li&gt;
&lt;li&gt;X-Frame-Options의 값은 0입니다. Frames 사용은 불가능하도록 되어있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Form Authentication과 Basic Authentication은 어떤 차이인가요?&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Form-based Authentication&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보통 웹에서 사용&lt;/li&gt;
&lt;li&gt;세션 쿠키인 JSESSIONID 사용&lt;/li&gt;
&lt;li&gt;기본 로그인/로그아웃 페이지 url과 그에 맞는 UI를 제공합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Basic Authentication&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 이름과 비밀번호를 Base64 인코딩한 값을 Authorization: Basic &amp;hellip; 헤더로 추가여 리퀘스트를 보냅니다.&lt;/li&gt;
&lt;li&gt;REST API의 가장 기본적인 보안 옵션이지만 쉽게 디코딩할 수 있고, 무제한 시간 동안 valid 하기 때문에 안전하지 않습니다. 또한 authorization 정보(접근권한)는 포함하고 있지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;클릭재킹(Clickjacking)과 Frame&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클릭재킹은 웹 사용자를 속여서 자신이 의도하지 않은 행동 (예: 버튼 클릭, 링크 클릭, 정보 입력 등)을 수행하도록 만드는 악의적인 기법입니다. 공격자는 투명하거나 거의 보이지 않는 &lt;b&gt;Frame&lt;/b&gt; (주로 &amp;lt;iframe&amp;gt;) 을 이용하여 사용자가 보고 있는 정상적인 웹페이지 위에 겹쳐 놓습니다. 사용자는 겉으로 보이는 콘텐츠 (예: 동영상 재생 버튼)를 클릭하려고 하지만, 실제로는 그 위에 겹쳐진 투명한 프레임 속의 공격 대상 웹사이트의 버튼이나 링크를 클릭하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이유로 Spring Security가 Frame을 기본적으로 차단하는 것이며, 기본적으로 X-Frame-Options 헤더를 DENY 또는 SAMEORIGIN으로 설정하여 클릭재킹 공격을 방지합니다. 즉, 애플리케이션의 페이지가 다른 웹사이트의 프레임 내에 임베딩되는 것을 막아, 공격자가 투명한 프레임을 겹쳐 사용자를 속이는 것을 어렵게 만듭니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인코딩(Encoding), 해싱(Hashing), 암호화(Encryption) 비교&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;인코딩 (Encoding)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 한 형태에서 다른 형태로 변환하는 과정입니다. 키나 비밀번호를 사용하지 않습니다. 가역적이기 때문에 &lt;b&gt;원래의 데이터로 다시 되돌릴 수 있습니다&lt;/b&gt;. 데이터 보안보다는 압축, 스트리밍, &lt;b&gt;효율적인 데이터 전송/저장&lt;/b&gt;과 같은 목적으로 사용됩니다. 예시: Base64, Wav, MP3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해싱 (Hashing)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 고정된 길이의 해시 문자열로 변환하는 과정입니다. &lt;b&gt;단방향&lt;/b&gt; 프로세스이므로, 해시된 결과로부터 원래의 데이터를 되돌릴 수 없습니다. 주로 데이터의 &lt;b&gt;무결성 검증&lt;/b&gt;에 사용됩니다. 예시: bcrypt, scrypt 사용 사례: 데이터베이스에 비밀번호의 해시 값을 저장합니다. 사용자가 로그인할 때 입력한 비밀번호를 해시하여 데이터베이스에 저장된 해시 값과 비교합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;암호화 (Encryption)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;키 또는 비밀번호를 사용하여 데이터를 암호화하는 과정입니다. 데이터를 안전하게 보호하는 것이 목표입니다. 암호화된 데이터를 해독하려면 키 또는 비밀번호가 필요합니다. 가역적이며, 암호화에 사용된 키나 비밀번호를 알면 원래의 데이터를 복원할 수 있습니다. 예시: RSA&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SHA-256과 같은 기존의 해시 알고리즘은 더 이상 안전하지 않습니다. 현대적인 시스템은 초당 수십억 번의 해시 계산을 수행할 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권장 사항:&lt;/b&gt; 검증에 1초의 &quot;작업량(Work factor)&quot;이 소요되는 적응형 단방향 함수를 사용하세요. 예시: bcrypt, scrypt, argon2 등&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;적응형 (adaptive):&lt;/b&gt; 함수가 시간이 지남에 따라 변화하는 환경이나 조건에 맞춰 자동으로 조정될 수 있음을 의미합니다. 예를 들어, 컴퓨팅 파워가 증가함에 따라 해시 계산에 더 많은 시간이 소요되도록 자동으로 조정될 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;단방향 함수 (one-way function):&lt;/b&gt; 암호학에서 사용되는 함수로, 계산하기는 쉽지만 역으로 계산하기는 매우 어려운 특성을 가진 함수입니다. 비밀번호를 해시하는 데 적합합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;작업량 (Work factor):&lt;/b&gt; 비밀번호를 검증하는 데 필요한 계산 시간 또는 자원을 의미합니다. 1초의 작업량은 현재 수준의 컴퓨팅 파워에서 안전하다고 여겨지는 값입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Spring Security에서의 암호화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 비밀번호의 단방향 변환을 수행하기 위해서 &lt;b&gt;PasswordEncoder&lt;/b&gt;라는 인터페이스를 제공합니다. (이름이 Encoder여서 다소 혼란스러울 수 있지만, 암호화뿐만 아니라 해싱에도 사용됩니다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;권장 사항:&lt;/b&gt; &lt;b&gt;BCryptPasswordEncoder&lt;/b&gt;를 사용하는 것입니다. BCrypt는 강력하고 널리 사용되는 적응형 해시 함수 중 하나입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 인증 정보(Credential)는 다음과 같은 곳에 저장할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인-메모리 (In-memory):&lt;/b&gt; 애플리케이션 실행 중에 메모리에 저장하는 방식입니다. 예를 들어, application.properties 파일에 설정할 수 있으며, 주로 테스트 목적으로 사용됩니다. 또는 InMemoryUserDetailsManager를 사용하여 코드에 정의할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 (Database):&lt;/b&gt; JDBC나 JPA와 같은 기술을 이용하여 관계형 데이터베이스에 저장하는 방식입니다. 실제 운영 환경에서 가장 일반적인 방법입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;LDAP (Lightweight Directory Access Protocol):&lt;/b&gt; 디렉터리 서비스 및 인증을 위한 개방형 프로토콜입니다. 중앙 집중식 사용자 관리가 필요한 환경에서 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JSON 웹 토큰(JWT)의 등장&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Basic Authentication의 한계&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만료 시간이 없습니다.&lt;/li&gt;
&lt;li&gt;사용자 상세 정보를 포함하지 않습니다.&lt;/li&gt;
&lt;li&gt;쉽게 인코딩 될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 커스텀 토큰 시스템은 어떤가요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;잠재적인 보안 결함&lt;/b&gt;이 있을 수 있습니다.&lt;/li&gt;
&lt;li&gt;서비스 제공자와 소비자 모두 커스터마이징 된 토큰 구조를 이해해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 웹 토큰(JWT)은 서비스 프로바이더/컨슈머 간에 안전하게 정보를 교환하기 위한 &lt;b&gt;개방형 산업 표준&lt;/b&gt;입니다. 사용자 디테일과 권한 정보(authorization)를 포함할 수 있습니다. JSON 웹 토큰은 크게 세 부분(Header, Payload, Signature)으로 나뉘는데 이 중 Payload는 만료 날짜 (expire date)나 발급 시간(when was issued)등에 대한 정보를 가지고 있습니다. Signature는 헤더와 페이로드를 특정 알고리즘과 secret을 사용하여 암호화한 값입니다. 이 서명을 통해 토큰의 무결성을 검증할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;대칭(Symmetric) 키 암호화&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;암호화와 복호화에 &lt;b&gt;동일한 키&lt;/b&gt;를 사용하는 암호화 알고리즘입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비대칭(Asymmetric) 키 암호화 == public key 방식&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;두 개의 키(퍼블릭 키와 프라이빗 키)를 사용하는 암호화 알고리즘입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공개 키로 데이터를 암호화&lt;/b&gt;하고 &lt;b&gt;개인 키로 암호화된 데이터를 복호화&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공개 키는 모든 사람에게 공유&lt;/b&gt;하고 &lt;b&gt;개인 키는 안전하게 보관&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JSON 웹 토큰 작동 방식&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;JSON 웹 토큰 &lt;b&gt;생성:&lt;/b&gt; 다음 정보를 인코딩합니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 credential&lt;/li&gt;
&lt;li&gt;데이터 (페이로드)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RSA 키 쌍&lt;/b&gt;: JWT를 생성하기 위해 RSA 키 쌍이 필요합니다. (여기서 RSA는 비대칭 키 암호화 알고리즘의 예시입니다. JWT 서명 알고리즘으로 HS512와 같은 대칭 키 알고리즘도 사용될 수 있습니다.) JWT 생성을 위한 리소스 (API 엔드포인트)를 만들어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT 전송:&lt;/b&gt; 요청 헤더에 JWT를 포함하여 전송합니다. 형식: Authorization: Bearer ${JWT_TOKEN}&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT 검증:&lt;/b&gt; RSA 키 쌍의 &lt;b&gt;공개 키&lt;/b&gt;를 사용하여 토큰을 디코딩하고 서명을 검증합니다. (HS512와 같은 대칭 키 알고리즘을 사용했다면 동일한 비밀 키로 검증합니다.)&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JWT 보안 설정&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot의 OAuth2 Resource Server를 사용하여 JWT Authentication을 설정할 수 있습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;KeyPairGenerator나 openssl을 사용하여 키 쌍을 생성합니다.&lt;/li&gt;
&lt;li&gt;생성된 키 쌍을 사용하여 자바에서 사용할 수 있는 RSA Key 객체를 생성합니다. java.security.interfaces.RSAPrivateKey, RSAPrivateKey&lt;/li&gt;
&lt;li&gt;JSON 웹 토큰의 인코딩 (서명) 및 디코딩 (검증)에 필요한 키를 제공하는 역할을 하는 객체인 JSON 웹 키 소스(JWKSource, JSON Web Key Source)를 생성합니다. 이를 위해서는 다음과 같은 단계를 거칩니다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;JWKSet 생성:&lt;/b&gt; JSON 웹 키(JWK, JSON Web Key) 객체들을 담는 집합입니다. JWK(JSON 웹 키)는 암호화 키를 JSON 형식으로 표현하는 표준화된 방법입니다. 생성한 RSA 키를 사용하여 JWK 객체를 만듭니다. 여러 개의 키를 관리해야 할 경우 (예: 키 순환) 여러 개의 JWK 객체를 JWKSet에 담을 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWKSource 생성:&lt;/b&gt; 생성된 JWKSet을 이용하여 JWKSource 객체를 만듭니다. JWKSource는 필요에 따라 JWKSet에서 적절한 키를 선택하여 제공하는 역할을 합니다. 열쇠 꾸러미라고 생각할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Public 키를 이용한 디코딩 (검증):&lt;/b&gt; 클라이언트로부터 받은 JWT의 서명을 검증할 때는 JWKSource에서 제공하는 &lt;b&gt;공개 키&lt;/b&gt;를 사용합니다. 브라우저는 JWT를 서버에 보낼 때 서명을 포함하여 보내는데, 서버는 이 서명이 해당 JWT가 개인 키로 서명되었고 변조되지 않았음을 증명하는지 공개 키를 통해 확인합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWKSource를 이용한 인코딩 (서명):&lt;/b&gt; JWT를 새로 생성하고 서명할 때는 JWKSource에서 제공하는 &lt;b&gt;개인 키&lt;/b&gt;를 사용합니다. 서버만이 개인 키에 접근할 수 있으므로, 클라이언트가 임의로 유효한 JWT를 생성하는 것을 방지할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWKSource와 JWKSet은 JWT 기반 인증 시스템에서 &lt;b&gt;키 관리의 유연성과 보안성을 크게 향상하는&lt;/b&gt; 중요한 개념입니다. 특히 여러 서비스가 JWT를 공유하고 검증해야 하는 마이크로서비스 아키텍처 환경에서 중앙 집중적인 키 관리와 일관된 토큰 검증을 가능하게 해 줍니다. 개인 키는 안전하게 보관하고, 공개 키는 JWT를 검증하는 서비스들에게 공유하여 JWT의 무결성과 신뢰성을 보장하는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;JWT Resource: 처음으로 JSON 웹 토큰을 획득하려면?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;b&gt;Basic Authentication 요청&lt;/b&gt;을 통해 이루어집니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;클라이언트는 사용자 아이디와 비밀번호를 담은 기본 인증 요청을 &lt;b&gt;JWT 리소스&lt;/b&gt;라고 불리는 특정 API 엔드포인트로 보냅니다. 이 요청은 보통 HTTP Authorization 헤더에 &quot;Basic [인코딩 된 아이디:비밀번호]&quot; 형식으로 담겨 전송됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT 리소스&lt;/b&gt;는 이 요청을 받으면 다음과 같은 과정을 거칩니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제공된 아이디에 해당하는 사용자가 존재하는지 확인합니다.&lt;/li&gt;
&lt;li&gt;제공된 비밀번호가 해당 사용자의 비밀번호와 일치하는지 확인합니다. 이 과정에서 비밀번호는 일반적으로 해싱되어 저장되어 있으므로, 입력된 비밀번호를 해시하여 저장된 해시 값과 비교합니다.&lt;/li&gt;
&lt;li&gt;인증에 성공하면, JWT를 생성합니다. 이 JWT에는 사용자의 정보 (예: 아이디, 권한 등)가 페이로드에 담기고, 서버의 개인 키 (비대칭 키 방식 사용 시) 또는 비밀 키 (대칭 키 방식 사용 시)로 서명됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;JWT 리소스&lt;/b&gt;는 생성된 JWT를 응답으로 클라이언트에게 반환합니다. 이 응답은 보통 JSON 형태이며, JWT는 특정 필드 (예: &quot;access_token&quot;)에 담겨서 전달됩니다.&lt;/li&gt;
&lt;li&gt;이제 클라이언트는 이 JWT를 사용하여 이후의 요청들을 인증할 수 있습니다. 각 요청의 헤더 (일반적으로 Authorization 헤더)에 &quot;Bearer [JWT 토큰 값]&quot; 형식으로 JWT를 포함하여 서버에 보냅니다. 서버는 이 JWT를 검증하여 요청을 보낸 사용자가 누구인지, 어떤 권한을 가지고 있는지 확인하고 요청을 처리합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spring Security의 Authentication 관련 컴포넌트&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;AuthenticationManager&lt;/b&gt;: authentication의 전반적인 책임을 담당하는 인터페이스입니다. 실제 인증 로직은 이 인터페이스를 구현한 클래스에서 이루어집니다. 이 AuthenticationManager는 여러 개의 AuthenticationProvider와 상호작용하여 다양한 방식의 인증을 처리할 수 있습니다. 마치 여러 종류의 자물쇠를 열 수 있는 마스터 키와 같은 역할을 한다고 생각할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AuthenticationProvider&lt;/b&gt;: 특정 유형의 인증을 실제로 수행하는 역할을 합니다. 예를 들어,
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;JwtAuthenticationProvider: JWT 기반 인증&lt;/li&gt;
&lt;li&gt;DaoAuthenticationProvider: 아이디/비밀번호 기반 인증&lt;/li&gt;
&lt;/ul&gt;
AuthenticationManager는 요청된 인증 방식에 맞는 AuthenticationProvider에게 인증을 위임합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UserDetailsService&lt;/b&gt;: 사용자 이름과 같은 정보를 기반으로 데이터베이스나 LDAP과 같은 저장소에서 사용자 정보를 가져오는 역할을 합니다. AuthenticationProvider는 UserDetailsService를 사용하여 인증에 필요한 사용자 상세 정보를 얻습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authentication&lt;/b&gt;: 현재 인증 주체 (Principal), 자격 증명 (Credentials), 그리고 권한 (Authorities)을 나타내는 객체입니다. 인증 과정의 다양한 상태를 담고 있습니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Principal: 인증된 사용자에 대한 상세 정보 (예: 사용자 객체, 아이디 등)를 담고 있습니다. &quot;누구인가?&quot;에 대한 정보입니다.&lt;/li&gt;
&lt;li&gt;Credentials: 사용자의 자격 증명 정보 (예: 비밀번호)를 담고 있습니다. 인증 시 사용되는 정보입니다.&lt;/li&gt;
&lt;li&gt;Authorities: 인증된 사용자가 가지고 있는 권한 (역할, 스코프 등) 목록을 담고 있습니다. &quot;무엇을 할 수 있는가?&quot;에 대한 정보입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;인증(Authentication) 결과는 어디에 저장될까요?&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증이 성공적으로 완료되면, 그 결과(인증된 Authentication 객체)는 &lt;b&gt;SecurityContextHolder&lt;/b&gt;에 저장됩니다. 리퀘스트가 도착할 때, Authentication 객체는 credentials 만을 포함하고 있습니다. 그러나 AuthenticationManager 가 인증 과정을 끝낸 후에 Principal과 Authorities 정보를 마저 포함합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;SecurityContextHolder:&lt;/b&gt; SecurityContext 객체를 보관하는 일종의 보관소 역할을 합니다. 스레드 로컬 (ThreadLocal) 방식으로 구현되어 있어, 각 요청을 처리하는 스레드마다 독립적인 SecurityContext를 유지할 수 있습니다. 각 요청을 처리하는 동안 임시로 신분증을 보관하는 지갑과 같습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SecurityContext:&lt;/b&gt; 현재 보안 콘텍스트를 나타냄. Authentication 객체(현재 사용자의 인증 정보와 권한 정보)를 가지고 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Authentication:&lt;/b&gt; 앞서 설명한 인증 객체입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GrantedAuthority:&lt;/b&gt; Authentication 객체 내의 권한 정보. 사용자가 어떤 역할이나 스코프를 가지고 있는지 표현합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Authentication 과정 요약&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;요청이 도착하면, Spring Security 필터 체인의 인증 관련 필터가 실행됩니다. 이 필터는 요청에서 인증 정보를 추출합니다 (예: JWT 토큰, 아이디/비밀번호).&lt;/li&gt;
&lt;li&gt;추출된 인증 정보 (처음에는 주로 자격 증명)를 담은 Authentication 객체가 생성됩니다.&lt;/li&gt;
&lt;li&gt;AuthenticationManager에게 인증을 시도하도록 요청합니다.&lt;/li&gt;
&lt;li&gt;AuthenticationManager는 적절한 AuthenticationProvider를 선택하여 인증을 위임합니다.&lt;/li&gt;
&lt;li&gt;AuthenticationProvider는 UserDetailsService를 통해 사용자 정보를 로딩하고, 제공된 자격 증명과 로딩된 사용자 정보를 비교하여 인증을 수행합니다.&lt;/li&gt;
&lt;li&gt;인증에 성공하면, AuthenticationProvider는 Principal과 Authorities 정보를 담은 완전히 채워진 Authentication 객체를 생성합니다.&lt;/li&gt;
&lt;li&gt;이 성공적인 Authentication 객체는 SecurityContextHolder의 SecurityContext에 저장됩니다.&lt;/li&gt;
&lt;li&gt;이후의 요청 처리 과정에서 Spring Security는 SecurityContextHolder에서 인증 정보를 확인하여 사용자의 권한을 검사하고 필요한 작업을 수행합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 Authentication의 과정을 살펴보았습니다. 이제부터는 authentication이 이루어지고 난 뒤 Authorization 과정에 대해서 살펴봅시다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Spring Security 권한 부여 (Authorization) 작동 방식&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 전역 보안 설정 (Global Security): &lt;/b&gt;authorizeHttpRequests&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WebSecurityConfigurerAdapter를 상속받은 설정 클래스 내에서 configure(HttpSecurity http) 메서드를 오버라이드하여 설정합니다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeHttpRequests((authz) -&amp;gt; authz
                .requestMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;) // &quot;/admin/&quot;으로 시작하는 경로는 &quot;ADMIN&quot; 역할을 가진 사용자만 접근 가능
                .requestMatchers(&quot;/users&quot;).hasRole(&quot;USER&quot;)    // &quot;/users&quot; 경로는 &quot;USER&quot; 역할을 가진 사용자만 접근 가능
                .requestMatchers(&quot;/public&quot;).permitAll()      // &quot;/public&quot; 경로는 모든 사용자 접근 가능
                .anyRequest().authenticated()               // 그 외의 모든 요청은 인증된 사용자만 접근 가능
        );
        // ... 다른 설정 (폼 로그인, 로그아웃 등)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2.&amp;nbsp;메서드 보안 (Method Security): &lt;/b&gt;@EnableMethodSecurity&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 메서드가 실행되기 전후에 권한 검사를 수행하는 방식입니다. 이를 사용하면 컨트롤러의 특정 핸들러 메서드나 서비스 레이어의 메서드에 대해 세밀한 접근 제어를 적용할 수 있습니다. 설정 클래스에 @EnableMethodSecurity 어노테이션을 사용하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@EnableMethodSecurity // 메서드 보안 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // ... 기존 설정
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활성화 후에는 다음과 같은 여러 가지 어노테이션을 사용하여 다양한 방식으로 특정 메서드에 권한 규칙을 적용할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;@PreAuthorize(&quot;표현식&quot;)&lt;/b&gt;: 메서드 실행 전에 권한 검사를 수행합니다. 표현식이 true를 반환하면 메서드가 실행됩니다. Spring EL (SpEL)을 사용하여 다양한 조건으로 권한 검사를 수행할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@GetMapping(&quot;/users/{username}&quot;)
@PreAuthorize(&quot;hasRole('USER') and #username == authentication.name&quot;)
public User getUserDetails(@PathVariable String username) {
    // 현재 사용자가 'USER' 역할을 가지고 있고, 요청된 username이 현재 인증된 사용자의 이름과 같은 경우에만 접근 허용
    return userService.getUserByUsername(username);
}

@PostMapping(&quot;/admin/create&quot;)
@PreAuthorize(&quot;hasAuthority('WRITE_ADMIN')&quot;)
public ResponseEntity&amp;lt;String&amp;gt; createUser(User newUser) {
    // 'WRITE_ADMIN' 권한을 가진 사용자만 접근 허용
    userService.createUser(newUser);
    return ResponseEntity.ok(&quot;User created successfully&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;@PostAuthorize(&quot;표현식&quot;)&lt;/b&gt;: 메서드 &lt;b&gt;실행 후&lt;/b&gt;에 권한 검사를 수행합니다. 표현식이 true를 반환하면 결과를 반환하고, false를 반환하면 예외가 발생합니다. 주로 반환 값에 기반하여 권한을 제어할 때 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@GetMapping(&quot;/profile&quot;)
@PostAuthorize(&quot;returnObject.username == authentication.name&quot;)
public User getProfile() {
    // 메서드 실행 후 반환된 User 객체의 username이 현재 인증된 사용자의 이름과 같은 경우에만 결과 반환
    return userService.getCurrentUser();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;JSR-250 어노테이션 (@RolesAllowed)&lt;/b&gt;: JSR-250 표준에서 제공하는 어노테이션으로, 특정 역할을 가진 사용자만 메서드 접근을 허용합니다. 사용하려면 @EnableMethodSecurity 어노테이션에 jsr250Enabled = true 속성을 설정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@EnableMethodSecurity(jsr250Enabled = true) // JSR-250 어노테이션 활성화
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // ...
}

@GetMapping(&quot;/admin/dashboard&quot;)
@RolesAllowed({&quot;ADMIN&quot;})
public String adminDashboard() {
    // &quot;ADMIN&quot; 역할을 가진 사용자만 접근 허용
    return &quot;Admin Dashboard&quot;;
}

@GetMapping(&quot;/data&quot;)
@RolesAllowed({&quot;ADMIN&quot;, &quot;USER&quot;})
public String getData() {
    // &quot;ADMIN&quot; 또는 &quot;USER&quot; 역할을 가진 사용자만 접근 허용
    return &quot;Data&quot;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Spring Security 필터 체인과 Authentication(인증) 필터, Authorization(권한부여) 필터 작동방식, CORS(Cross Origin Resource Sharing) 및 CSRF(Cross Site Request Forgery)에 대해서 살펴보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 처음에는 어렵게 느껴질 수 있지만, 웹 보안을 위해 꼭 필요한 프레임워크입니다. 포기하지 말고 꾸준히 학습하시면, 여러분의 웹 애플리케이션을 더욱 안전하게 만들 수 있을 거예요! 궁금한 점이나 이해가 안 되는 부분은 언제든지 댓글로 질문해 주세요! 여러분의 피드백은 언제나 환영합니다.&lt;/p&gt;</description>
      <category>개발 이모저모</category>
      <category>BCryptPasswordEncoder</category>
      <category>Clickjacking</category>
      <category>CORS</category>
      <category>CSRF</category>
      <category>java auth</category>
      <category>java authorization</category>
      <category>PasswordEncoder</category>
      <category>SecurityFilterChain</category>
      <category>Spring Security</category>
      <category>스프링 시큐리티</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/19</guid>
      <comments>https://juganote.tistory.com/entry/Spring-Security-5%EB%B6%84-%EB%A7%8C%EC%97%90-%ED%95%B5%EC%8B%AC-%EA%B0%9C%EB%85%90-%EC%99%84%EC%A0%84-%EC%A0%95%EB%B3%B5-%EC%B4%88%EB%B3%B4%EC%9E%90%EB%A5%BC-%EC%9C%84%ED%95%9C-%EC%B9%9C%EC%A0%88%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C#entry19comment</comments>
      <pubDate>Sat, 15 Mar 2025 06:32:21 +0900</pubDate>
    </item>
    <item>
      <title>시스템 디자인 인터뷰: 배달 시스템 디자인하기</title>
      <link>https://juganote.tistory.com/entry/%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%9D%B8%ED%84%B0%EB%B7%B0-%EB%B0%B0%EB%8B%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%94%94%EC%9E%90%EC%9D%B8%ED%95%98%EA%B8%B0</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 디자인 면접에서 배달 서비스를 설계하는 질문은 지원자의 전체적인 시스템 이해도와 핵심 기술에 대한 통찰력을 평가하는 데 적합합니다. 이러한 면접은 보통 45분 정도 진행되며, 다음과 같은 방식으로 대답하면 좋습니다:&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 요구사항 파악&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 면접관에게 서비스의 구체적인 요구사항을 물어보아야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예상 사용자 수&lt;/li&gt;
&lt;li&gt;지원해야 할 지역 범위&lt;/li&gt;
&lt;li&gt;주요 기능 (주문, 결제, 배달 추적 등)&lt;/li&gt;
&lt;li&gt;성능 요구사항 (응답 시간, 처리량 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 시스템 아키텍처 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;high-level 시스템 아키텍처를 설명합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 애플리케이션 (모바일 앱, 웹)&lt;/li&gt;
&lt;li&gt;백엔드 서버 (API 서버, 데이터베이스)&lt;/li&gt;
&lt;li&gt;외부 서비스 통합 (결제, 지도, 알림 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 핵심 기능 설계&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;주문 관리&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주문 생성, 수정, 취소 프로세스&lt;/li&gt;
&lt;li&gt;주문 상태 추적 시스템&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;사용자 관리&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;고객, 음식점, 배달원 계정 관리&lt;/li&gt;
&lt;li&gt;인증 및 권한 부여 시스템&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;결제 시스템&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;안전한 결제 처리&lt;/li&gt;
&lt;li&gt;다양한 결제 방식 지원&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;배달 관리&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;배달원 할당 알고리즘&lt;/li&gt;
&lt;li&gt;실시간 위치 추적 시스템&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 데이터 모델 설계&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 (고객, 음식점, 배달원)&lt;/li&gt;
&lt;li&gt;주문&lt;/li&gt;
&lt;li&gt;메뉴 아이템&lt;/li&gt;
&lt;li&gt;결제 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. API 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RESTful API나 GraphQL API의 주요 엔드포인트를 설명.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 확장성 및 성능 고려사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터베이스 샤딩 전략&lt;/li&gt;
&lt;li&gt;캐싱 시스템 (Redis 등)&lt;/li&gt;
&lt;li&gt;로드 밸런싱&lt;/li&gt;
&lt;li&gt;CDN 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 보안 고려사항&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 암호화&lt;/li&gt;
&lt;li&gt;인증 및 권한 부여&lt;/li&gt;
&lt;li&gt;HTTPS 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;면접에서는 이러한 포인트들을 논리적으로 설명하고, 필요에 따라 다이어그램을 그리는 것이 좋습니다. 또한, 내가 설정한 방식의 트레이드오프에 대해서도 설명하고 다양한 옵션을 제시하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 디자인 인터뷰를 앞두고 있을 때 팁:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;바로 설계를 시작하지 않습니다.&lt;/li&gt;
&lt;li&gt;문제를 천천히 생각해 보고&amp;nbsp;좀 더 명확한 정보를 얻을 수 있도록 질문을 합니다. 그렇다고 해서 20분 동안 질문만 하거나, 문제를 회피하거나 쓸데없는 잡담으로 시간을 낭비하는 것은 No.&lt;/li&gt;
&lt;li&gt;모든 것을 다 설계하려고 하지 말고, 문제의 범위에 대해 면접관과 논의하는 것이 좋습니다. 45분밖에 없다는 걸 기억하고 가장 중요한 사용 사례만 파악하고 집중해야 합니다.&lt;/li&gt;
&lt;li&gt;애플리케이션의&amp;nbsp;사용자를 파악하는 것이 중요합니다. 고객에 집중하면 좋은 인상을 줄 수 있습니다.&lt;/li&gt;
&lt;li&gt;규모와 성장에 대해 이야기하세요. 꼭 처음부터 확장성을 고려해야 하는 것은 아니지만, 면접관이 &quot;이 시스템을 10억 사용자를 위해 어떻게 스케일 업을 할 수 있을까요?&quot;라고 물었을 때 당황하지 않고 대답할 수 있도록 합니다.&lt;/li&gt;
&lt;li&gt;말하면서 생각하세요! 면접관과 커뮤니케이션을 많이 할수록 좋습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이제 음식 배달 서비스를 디자인하는 시스템 디자인 인터뷰를 풀어봅시다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시스템의 주요 사용자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자, 음식점, 배달원, 시스템 어드민&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;가정사항&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 음식점의 모든 메뉴는 항시 주문 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 특정 radius 내의 음식점에서만 주문 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자는 다음과 같은 액션을 취할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;음식점 종류, 메뉴 등으로 검색하기&lt;/li&gt;
&lt;li&gt;카트에 메뉴를 담고 주문하기&lt;/li&gt;
&lt;li&gt;주문 후 주문 상태 업데이트 알림 수신하기&lt;/li&gt;
&lt;li&gt;어플로 주문 상태 추적하기&lt;/li&gt;
&lt;li&gt;주문 취소하기&lt;/li&gt;
&lt;li&gt;주문 결제하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음식점은 다음과 같은 액션을 취할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;음식점의 프로필을 설정하고 메뉴 아이템을 업데이트하기&lt;/li&gt;
&lt;li&gt;주문 수령 시 알림 받기, 배달원 배정하기, 주문 상태 업데이트하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배달원은 다음과 같은 액션을 취할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;본인이 선택한 배송 지역 내 가능한 배달에 대한 알림 받기&lt;/li&gt;
&lt;li&gt;조리 완료 후 배송 가능 알림 받기&lt;/li&gt;
&lt;li&gt;문제 발생 시 고객이나 식당에 연락하기&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Non-Functional Requirement&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Latency(지연시간): 빠른 검색 기능을 제공해야 합니다.&lt;/li&gt;
&lt;li&gt;Consistency(일관성): 주문이 접수되면 사용자, 음식점, 배달원 모두가 문제없이 동일한 주문을 볼 수 있어야 합니다.&lt;/li&gt;
&lt;li&gt;Availability(가용성): 시스템이 다운되어도 서비스가 중단되지 않아야 합니다.&lt;/li&gt;
&lt;li&gt;High Throughput(높은 처리량): 주문이 몰려도 장애 없이 높은 피크 부하를 처리할 수 있어야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Out of Scope&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고객이 음식점 또는 배달원을 평가할 수 있는 기능.&lt;/li&gt;
&lt;li&gt;판매 보고, 대시보드 기능.&lt;/li&gt;
&lt;li&gt;배달원 온보딩 및 대금 지급.&lt;/li&gt;
&lt;li&gt;음식점 대금 지급.&lt;/li&gt;
&lt;li&gt;고객 서비스/관리.&lt;/li&gt;
&lt;li&gt;페이스북/구글/애플 등을 통한 OAuth 2.0 인증.&lt;/li&gt;
&lt;li&gt;고객의 주문 내역/프로필 및 기타 설정을 기반으로 한 추천 시스템.&lt;/li&gt;
&lt;li&gt;매핑 기능(Google 지도 형태)을 통해 배달원 또는 음식점의 위치를 표시.&lt;/li&gt;
&lt;li&gt;도착 예정 시간 계산 서비스.&lt;/li&gt;
&lt;li&gt;서비스 관리자 기능.&lt;/li&gt;
&lt;li&gt;수요 공급 logistics 및 의사 결정.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Capacity estimation &amp;amp; Constraints&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 1M(백만) 우편번호(zip code)의 사용자에게 서비스를 제공한다고 가정하겠습니다. 평균적으로 각 우편번호에는 100개의 음식점이 있다고 가정합니다. 각 음식점에서 제공할 수 있는 요리는 15가지로 제한한다면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 메뉴 수 = 1M * 100 * 15 = 1.5B(15억) 건이 될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객 수를 대략 20M(이천만) 정도라고 가정하고, 각 고객이 평균적으로 매일 2건의 주문을 하는 경우 하루 주문 수는 대략 4천만 건 정도라고 예상할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주문이 가장 많은 시간은 보통 요일에 따라 달라지며, 피크 시간은 지역마다 정오 또는 저녁 시간대가 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 메뉴 및 음식점 검색은 &lt;b&gt;읽기 중심&lt;/b&gt;이고, 주문은 &lt;b&gt;쓰기 중심&lt;/b&gt;입니다. 음식이 배달된 후 사용자가 주문 이력을 조회할 가능성은 낮습니다. 이런 특징에 따라서 설계하면 좋습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Data Model&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터뷰 중에 데이터 모델을 광범위하게 분석할 필요는 없으며, 주요 엔티티와 속성을 나열하는 것만으로도 맥락에서 충분합니다. 디테일한 다이어그램을 작성할 필요는 없습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Data Storage&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스의 선택은 일반적으로 저장되는 데이터의 양, 확장 용이성, 파티셔닝, 복제 및 기타 여러 가지 요인에 따라 달라집니다. 특정한 사용 사례를 만족하기 위해서는 여러 다른 종류의 데이터베이스를 혼합하여 사용할 수 있습니다. ACID(원자성, 일관성, 고립성, 지속성) 요구 사항의 경우, NoSQL보다 SQL 데이터베이스가 더 좋은 선택지입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Capacity Estimation에서 알 수 있듯이 음식점, 메뉴 설명, 사용자 데이터, 배달원 데이터 등 데이터 양이 방대할 것이므로 Cassandra 같은 NoSQL 또는 칼럼형 데이터베이스를 사용하기 좋습니다. 데이터의 구조, 특히 속성은 음식점마다 다를 수 있기에 SQL 스키마에 데이터를 맞추기가 어려울 수 있습니다. 이미지 데이터는 아마존 S3와 같은 Object Storage 서비스에 저장할 수 있습니다. 주문 정보는 Oracle/MySQL/Postgres 등에 저장할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Component Design &amp;amp; Architecture&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 아키텍처는 마이크로서비스 기반으로 Pub-Sub 패턴을 많이 사용하며, 카프카, RabbitMQ, ActiveMQ, Amazon SNS 또는 Amazon MQ와 같은 Queue 기술을 사용할 수 있습니다. 각 마이크로서비스는 메시지를 게시하고 채널/토픽을 구독하는 방식을 사용하여 다른 마이크로서비스와 대화할 수 있습니다. 이렇게 하면 서비스가 가능한 최선의 방법으로 서로 분리됩니다. 따라서 마이크로서비스가 서로의 엔드포인트를 알 필요가 없고 메시지 Queue에 새로운 정보를 게시하기만 하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 각 마이크로서비스는 자체 데이터베이스와 상호 작용하고 다른 누구와도 공유하지 않는 것이 필수적입니다. 이 접근 방식은 서비스별 데이터베이스 패러다임에 기반합니다. 서비스 A는 서비스 B의 데이터베이스에 직접적으로 액세스 할 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 모든 정보를 가지고 있는 하나의 큰 스키마를 디자인할 수도 있지만, 마이크로서비스 아키텍처는 이 개념과는 반대되는 개념입니다. 각 테이블/테이블 그룹의 소유권을 하나의 마이크로서비스에 부여하기 위해서는 기능적 파티셔닝이 필요합니다. 데이터를 마이크로서비스 아키텍처에 맞게 분할할 수 있는 방법을 생각해 보는 것이 좋습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시스템의 기타 구성요소들&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;UI 클라이언트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스는 모바일, 웹, 태블릿 등을 통해 액세스 할 수 있습니다. 사용 객체가 누구인지에 따라(사용자, 음식점, 배달원, 시스템 관리자)에 따라 제공되는 기능이 다르기 때문에 각기 다른 버전의 인터페이스가 제공될 것입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;검색 클러스터(Read-Heavy)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템에서 제공해야 하는 가장 중요한 기능이 바로 메뉴, 음식점에 대한 검색 기능입니다. 따라서 고객의 과거 검색 및 주문 내역을 기반으로 개인화된 검색을 제공해야 합니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자 검색 파라미터에 따른 빠른 조회를 위해 Elasticsearch 또는 Apache Solr와 같이 잘 알려진 기술을 사용할 수 있습니다. 두 가지 모두 오픈 소스, 분산형이며 Apache Lucene을 기반으로 하며 각자의 장단점이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;검색 클러스터에 대한 비동기 업데이트를 처리하려면 Queue가 있어야 합니다. `음식점 프로필 서비스`가 데이터베이스에서 CRUD 작업을 수행하여 음식점이나 메뉴 데이터를 생성/업데이트할 때, Queue에 이벤트로 작업 요청을 할 수도 있습니다. 이렇게 하려면 Queue의 이벤트를 수신(listner)하는 Data Indexer가 필요합니다. Data Indexer가 이벤트를 선택하고 데이터베이스에 대한 쿼리를 실행하여 결과물을 포맷한 다음, 그 결과를 검색 클러스터에 게시합니다. 또한 사용자 입력을 받아 검색 클러스터에서 쿼리를 실행하고 UI에 표시할 결과를 반환하는 `음식점 검색 서비스`가 있어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch의 Geo-distance query를 활용하면 사용자 위치에서 특정 반경 내의 검색결과를 반환할 수 있습니다. 즉, 사용자의 주소에서 도달 가능한 음식점만 사용자에게 표시된다는 뜻입니다. 이와 비슷하게 Apache Solr는 Spatial Search 기능을 제공하고 있습니다. Elasticsearch는 결과 검색 속도가 매우 빠르며 이보다 더 latency를 줄이기 위해서는 음식점 검색 서비스와 함께 캐시를 사용해야 합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2001&quot; data-origin-height=&quot;1125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/czKYDY/btsMzfXF9bd/9CUaIke0n5Px8CtekbFZy1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/czKYDY/btsMzfXF9bd/9CUaIke0n5Px8CtekbFZy1/img.png&quot; data-alt=&quot;대략적인 검색 생태계&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/czKYDY/btsMzfXF9bd/9CUaIke0n5Px8CtekbFZy1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FczKYDY%2FbtsMzfXF9bd%2F9CUaIke0n5Px8CtekbFZy1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2001&quot; height=&quot;1125&quot; data-origin-width=&quot;2001&quot; data-origin-height=&quot;1125&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대략적인 검색 생태계&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주문 서비스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메뉴 선택, 장바구니 관리 및 음식 주문 배치를 관리합니다. 외부 Payment Gateway를 사용하여 결제를 처리하고 그 결과를 `Orders` 데이터베이스에 유지합니다. 주문하는 것은 트랜잭션이므로 관계형 데이터베이스를 사용하는 것이 가장 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객은 주문에 대한 기타 세부 정보와 영수증을 받을 수 있습니다. 또한 주문을 취소할 수도, 과거 주문 내역도 확인할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;주문 처리 서비스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스가 처리하는 몇 가지 주요 기능은 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;레스토랑에서 주문을 수락합니다(음식점 클라이언트 앱 사용).&lt;/li&gt;
&lt;li&gt;필요한 경우 고객에게 주문 지연 또는 변경 사항을 알립니다(알림 서비스 사용).&lt;/li&gt;
&lt;li&gt;고객은 주문 상태를 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;배달원은 주문이 수령할 준비가 되었는지 확인할 수 있습니다. 또한 현재 픽업/배송 중인 주문의 세부 정보를 확인할 수 있습니다.&lt;/li&gt;
&lt;li&gt;주문이 픽업될 준비가 되면 배달원에게 알림을 보냅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사용자 프로필 관리 및 설정 서비스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템의 Actor들(사용자, 음식점 직원, 배달원, 시스템 관리자)은 개인 정보, 연락처, 주소로 프로필을 생성할 수 있으며 프로필에 따라 역할이 할당됩니다. 개별 행위자는 역할에 따라 기본 설정도 갖게 됩니다. 예를 들어, 사용자는 정해진 레스토랑이나 우편 번호 또는 요리 중에서 선택하도록 기본 설정을 지정할 수 있습니다. 배달원은 특정 우편번호 내에서만 배달하는 것을 선호하거나 음식점을 선택하는 등의 기본 설정이 있을 수 있습니다. 각 Actor들은 주문 또는 대금 수령 방법을 적절하게 설정할 수 있습니다. 또한 알림 기본 설정도 다양할 것입니다. 이 서비스는 이러한 모든 Actor들의 프로필과 기본 설정을 전반적으로 관리합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배달원 배치 서비스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스는 배달원과 관련된 사용 사례를 달성하는 데 사용됩니다. 배달원은 다음을 수행할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수락할 수 있는 픽업 주문을 목록에서 확인합니다.&lt;/li&gt;
&lt;li&gt;주문을 수락합니다.&lt;/li&gt;
&lt;li&gt;수락한 과거 주문을 봅니다.&lt;/li&gt;
&lt;li&gt;배달과 관련하여 연락이 필요한 경우 주문에 포함된 고객 정보를 확인하거나 음식 픽업 또는 배달을 방해하는 문제에 대해 음식점에 알립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;음식점 프로필 서비스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스는 음식점, 메뉴, 제공 사항 등과 관련된 데이터를 관리합니다.:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;온보딩&lt;/li&gt;
&lt;li&gt;프로필을 업데이트/삭제할 수 있습니다.&lt;/li&gt;
&lt;li&gt;메뉴, 요리 등을 생성/업데이트/보기/삭제할 수 있습니다.&lt;/li&gt;
&lt;li&gt;레스토랑 또는 요리/메뉴의 이미지를 객체 스토리지 서비스에 업로드합니다.&lt;/li&gt;
&lt;li&gt;과거 주문 등을 기반으로 재무 정보를 확인합니다.&lt;/li&gt;
&lt;li&gt;송금을 위한 결제 수단을 설정합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;외부 결제 게이트웨이&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구성 요소는 페이팔, 애플페이, 비자, 마스터카드 등의 개별 신용카드 제공업체와 같은 인기 있는 결제 게이트웨이와 연동할 수 있습니다. 주문 서비스는 이 구성 요소와 상호 작용하여 주문 확인 시 결제가 이루어지도록 합니다. 이러한 상호 작용은 본질적으로 synchronous(동기) 해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;알림 서비스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스는 시스템을 사용하는 모든 Actor에게 알림을 전달하는 역할을 합니다. 알림은 개별 Actor가 설정한 수신 환경 설정에 따라 푸시 알림, 문자 혹은 이메일로 전송될 수 있습니다. 이 서비스는 알림이 전송되는 매체를 추상화하도록 설계되었습니다. 즉, 이동통신사, 이메일 서비스 제공업체 등과의 기본 상호 작용이 추상화됩니다. 액터는 인앱(In-App) 알림을 받을 수도 있습니다. 알림의 책임은 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자: 주문이 성공적으로 접수되었거나, 음식점에서 주문을 수락했거나, 배달원이 주문을 픽업하여 배달 중이라는 등 주문의 다양한 단계에 대해 알립니다.&lt;/li&gt;
&lt;li&gt;음식점: 주문이 접수되었거나, 배달원이 배정되었거나, 배달원이 주문을 픽업하러 가는 중입니다.&lt;/li&gt;
&lt;li&gt;배달원: 주문이 대기 중이거나 주문이 픽업될 준비가 되었거나 수락한 주문이 지연되고 있음을 알려줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1606&quot; data-origin-height=&quot;1092&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nmIun/btsMAHMr2ox/Ommahsp2y97jrSDPNzDKOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nmIun/btsMAHMr2ox/Ommahsp2y97jrSDPNzDKOk/img.png&quot; data-alt=&quot;대략적 컴포넌트 디자인&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nmIun/btsMAHMr2ox/Ommahsp2y97jrSDPNzDKOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnmIun%2FbtsMAHMr2ox%2FOmmahsp2y97jrSDPNzDKOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1606&quot; height=&quot;1092&quot; data-origin-width=&quot;1606&quot; data-origin-height=&quot;1092&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대략적 컴포넌트 디자인&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;주문 처리 서비스 Workflow&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 모바일/웹 클라이언트를 주문을 하면 주문 처리 서비스의 워크플로우는 다음과 같습니다:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;주문 서비스가 주문에 대한 메시지(1)를 대기열에 게시하여 다운스트림 서비스가 처리를 시작할 수 있도록 합니다.&lt;/li&gt;
&lt;li&gt;알림 서비스가 메시지(1)를 읽고 해당 음식점과 고객에게 주문이 접수되어 수락 대기 중임을 알립니다.&lt;/li&gt;
&lt;li&gt;음식점에서 주문이 수락되면 주문 처리 서비스는 주문 수락 메시지(2)를 대기열에 게시합니다.&lt;/li&gt;
&lt;li&gt;배달원 배치 서비스가 주문 수락 메시지(2)를 읽고 지역 특정 메시지(3)를 대기열에 추가합니다. 또한 배달원 배치 서비스는 사용 가능한 배달원에게 주문을 자동 할당할 수 있으며, 이 경우 그와 관련한 메시지를 게시합니다.&lt;/li&gt;
&lt;li&gt;알림 서비스는 주문 수락 메시지(2)를 읽고 레스토랑에서 주문이 수락되었음을 고객에게 알립니다.&lt;/li&gt;
&lt;li&gt;알림 서비스는 지역 특정 메시지(3)를 읽고 해당 지역의 배달원에게 주문이 수락되어 처리할 수 있는지 여부를 알립니다.&lt;/li&gt;
&lt;li&gt;배달원은 배달원 배치 서비스를 사용하여 주문을 수락합니다. 배달원 배치 서비스는 배달원 배정/수락에 대한 큐에 배달 수락 메시지(4)를 게시합니다.&lt;/li&gt;
&lt;li&gt;알림 서비스가 배달 수락 메시지(4)를 읽고 배달원 배정에 대해 사용자와 음식점에 알립니다.&lt;/li&gt;
&lt;li&gt;음식이 픽업될 준비가 되면 레스토랑은 주문 처리 서비스를 사용하여 주문 상태를 업데이트합니다. 주문 처리 서비스는 음식이 픽업될 준비가 되었다는 음식 준비 완료 메시지(5)를 대기열에 게시합니다.&lt;/li&gt;
&lt;li&gt;알림 서비스가 음식 준비 완료 메시지(5)를 읽고 할당된 배달원과 고객에게 음식 수령 준비가 완료되었음을 알립니다.&lt;/li&gt;
&lt;li&gt;배달원이 음식을 픽업한 후 레스토랑 직원이 주문 처리 서비스를 사용하여 주문 상태를 업데이트합니다. 그러면 주문 처리 서비스는 주문이 픽업되었다는 배달 개시 메시지(6)를 대기열에 게시합니다.&lt;/li&gt;
&lt;li&gt;알림 서비스가 배달 개시 메시지(6)를 읽고 고객에게 주문이 픽업되었음을 알립니다.&lt;/li&gt;
&lt;li&gt;배달원이 고객에게 도착하여 음식을 배달하고 주문이 완료된 것으로 표시합니다. 배달원 배치 서비스가 배달 완료 메시지(7)를 대기열에 게시합니다.&lt;/li&gt;
&lt;li&gt;주문 처리 서비스가 배달 완료 메시지(7)를 읽고 주문 상태를 완료로 업데이트합니다.&lt;/li&gt;
&lt;li&gt;알림 서비스는 배달 완료 메시지(7)를 읽고 음식점과 고객에게 음식 배달에 대해 알립니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;System APIs&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자에게 노출되어야 하는 고객 대면 API는 대략 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;음식 주문:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;search (array of search terms)&lt;/li&gt;
&lt;li&gt;addToCart(menu-item)&lt;/li&gt;
&lt;li&gt;order(cart)&lt;/li&gt;
&lt;li&gt;status(orderId)&lt;/li&gt;
&lt;li&gt;retrieveOrder(orderId)&lt;/li&gt;
&lt;li&gt;cancelOrder(orderId)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로필 관리:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;createProfile&lt;/li&gt;
&lt;li&gt;updateProfile&lt;/li&gt;
&lt;li&gt;updateAddress&lt;/li&gt;
&lt;li&gt;updatePaymentMethod&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sometimes the technical discussion could veer towards defining rest APIs, URIs, identifying HTTP methods, sample entities, sample payloads, request-response, etc.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외의 API, URI, HTTP method, entity, payload, request response 예시 등을 정하면서 더 자세한 기술적인 논의를 진행할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Data Partitioning&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터가 증가함에 따라 데이터베이스의 한 인스턴스에 모든 데이터를 저장하는 것은 불가능해집니다. 레스토랑 데이터는 다음을 기준으로 파티셔닝 할 수 있습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우편번호&lt;/li&gt;
&lt;li&gt;음식점Id&lt;/li&gt;
&lt;li&gt;메뉴/요리/종류&lt;/li&gt;
&lt;li&gt;우편번호와 음식점Id의 조합&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 각 파티셔닝 방식에는 각각의 장단점이 있습니다. 파티셔닝을 구현하기 전에 가능한 부작용과 성능을 신중하게 고려해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Replication &amp;amp; Fault Tolerance&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템의 장애 지점을 파악하고 특정 서비스가 다운되는 경우 각각에 대한 대책을 마련하는 것이 필수적입니다. 이걸 가능하게 하기 위해서 각 서비스는 수평적 확장이 가능해야 합니다. NoSQL 역시 여러 개의 노드로 이루어져 있습니다. 검색 시스템도 여러 개의 노드를 설정할 수 있고, 메시지 Queue도 파티션과 복제본이 있습니다. 피크 로드일 때 더 많은 인스턴스를 가동하는 Autoscaling을 사용할 수 있습니다. 이렇게 된다면, 노드 중 하나가 다운되거나 Queue의 파티션이 다운된다고 해도 다른 인스턴스가 작업을 계속 수행할 수 있습니다. 다운된 노드는 self-healing이라는 프로세스를 사용하여 다시 시작할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;캐싱&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역별 최근 주문이나 가장 많이 주문한 항목, 검색된 항목을 기반으로 데이터를 캐싱하여 음식점 검색 서비스가 실제 인프라를 사용하는 대신 분산된 캐시에서 해당 정보를 조회하고 즉시 몇 가지 추천 항목을 반환할 수 있습니다. 음식점과 메뉴 이미지도 항상 Object Storage에 저장하는 대신 캐시에 저장할 수 있습니다. 캐시는 특정 지역에서 인기 있거나 가장 많이 주문된 메뉴 항목/레스토랑을 보유하며, 검색 화면에는 기본적으로 이러한 옵션이 표시되어야 합니다. 캐시를 사용하면 검색 속도가 확실히 빨라집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;널리 사용되는 캐시 기술로는 Redis, hazelcast, Memcache가 있습니다. LRU(Least Recently Used) 또는 LFU(Least Frequently Used), 또는 이 두 가지를 조합하여 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지역에 따라 사용자가 로컬 콘텐츠를 사용할 수 있도록 하기 위해 CDN(Content Delivery Network)를 캐시로 사용할 수도 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;보안&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTPS/SSL -TLS. 웹 또는 모바일 클라이언트 간의 모든 통신은 TLS를 사용해야 합니다.&lt;/li&gt;
&lt;li&gt;토큰 인증/갱신/생성을 위한 OAuth 2.0.&lt;/li&gt;
&lt;li&gt;큐잉 시스템으로 Kafka를 사용하는 경우 Topic별 SASL/SSL.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;로드 밸런싱&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 서비스는 여러 개의 인스턴스로 실행되며, https POST/GET 등을 통해 통신하는 시나리오에서는 로드 밸런서가 각 서비스 앞에 자리 잡고 있어야 합니다. 로드밸런싱은 한 인스턴스에 요청이 폭주하는 것을 방지하고 전체적으로 응답 시간을 단축하는 데 도움이 됩니다. Least Connection, Round Robin 방식 등 여러 가지 로드 밸런싱 기술을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 특정 Queue의 채널/토픽을 구독하는 경우, 로드 밸런싱의 책임은 큐 자체에 있습니다. 예를 들어, Kafka에서는 소비자 그룹 내에서 소비자 인스턴스 간에 파티션을 분배하여 로드 밸런싱을 수행합니다. 소비자 그룹 내 각 소비자는 공정한 몫(fair share)의 파티션을 독점적으로 소비하게 됩니다. 이러한 방식으로 Kafka는 소비자 그룹 내에서 로드 밸런싱을 수행합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 디자인 인터뷰는 어렵지만 지원자의 기술적 역량을 엿볼 수 있는 좋은 기회입니다. 이런 인터뷰를 준비하는 가장 좋은 방법은 널리 사용되는 대규모 애플리케이션을 직접 설계해 보는 것입니다. 수백만 명의 사용자, 방대한 데이터셋, 장애 지점, 트랜잭션 등을 고려하며 사고하는 것이 중요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;출처&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://medium.com/partha-pratim-sanyal/system-design-doordash-a-prepared-food-delivery-service-bf44093388e2&quot;&gt;https://medium.com/partha-pratim-sanyal/system-design-doordash-a-prepared-food-delivery-service-bf44093388e2&lt;/a&gt;&lt;/p&gt;</description>
      <category>개발 이모저모</category>
      <category>로드밸런서</category>
      <category>배달 서비스</category>
      <category>배달 서비스 구현</category>
      <category>시스템 디자인</category>
      <category>시스템디자인인터뷰</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/18</guid>
      <comments>https://juganote.tistory.com/entry/%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%94%94%EC%9E%90%EC%9D%B8-%EC%9D%B8%ED%84%B0%EB%B7%B0-%EB%B0%B0%EB%8B%AC-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EB%94%94%EC%9E%90%EC%9D%B8%ED%95%98%EA%B8%B0#entry18comment</comments>
      <pubDate>Sat, 1 Mar 2025 23:27:07 +0900</pubDate>
    </item>
    <item>
      <title>동시성 프로그래밍: Python 코루틴과 Go 고루틴</title>
      <link>https://juganote.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-Python-%EC%BD%94%EB%A3%A8%ED%8B%B4%EA%B3%BC-Go-%EA%B3%A0%EB%A3%A8%ED%8B%B4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Python과 Go는 모두 동시성(Concurrency) 프로그래밍을 지원하지만, 그 방식은 코루틴(Coroutine)과 고루틴(Goroutine)으로 각각 다릅니다. 이번 글에서는 둘의 주요 차이점을 살펴보겠습니다.&lt;/p&gt;
&lt;h1&gt;Python 코루틴 (Coroutine)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴(Coroutine)은 비동기 프로그래밍을 위한 특수한 함수로, 실행을 중단(await)했다가 다시 이어서 실행할 수 있는 기능을 제공합니다. 즉, 한 번 호출되면 끝까지 실행되는 일반 함수와 달리, 실행을 잠시 멈추었다가 필요할 때 다시 실행할 수 있습니다. 따라서 비동기(Async) 프로그램에서 효율적으로 리소스를 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python의 코루틴은 보통 이벤트 루프(Event Loop)에서 단일 스레드로 실행되지만, asyncio.to_thread()나 run_in_executor()를 사용하면 별도의 스레드 또는 프로세스를 활용할 수도 있습니다. 따라서 Python의 코루틴은 기본적으로 단일 스레드에서 동시 실행(Concurrency)을 지원하지만, 적절한 방법을 사용하면 병렬 실행(Parallelism)도 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴은 협력적(Cooperative) 스케줄링을 사용하며, 개발자가 async def 및 await 키워드를 사용하여 실행 흐름을 제어할 수 있습니다. 하지만, 이벤트 루프(asyncio.run() 등)를 활용하면 await을 직접 호출하지 않고도 코루틴을 실행할 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;협력적 스케줄링 (Cooperative Scheduling)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 중인 작업이 스스로 CPU를 양보해야 하는 방식. OS나 런타임이 강제로 중단하지 않고 스스로 실행을 중단하고 다음 작업에게 CPU를 넘겨줌. 개발자가 직접 yield() 또는 await 같은 키워드를 사용하여 CPU를 반환해야 함. 멀티스레딩 환경에서도 하나의 실행 단위가 너무 오래 실행되면 다른 작업이 기다려야 함.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;선점형 스케줄링 (Preemptive Scheduling)&lt;/b&gt;&lt;br /&gt;OS나 런타임이 강제로 실행 중인 작업을 중단하고 다른 작업을 실행하는 방식. 특정한 시간(타임 슬라이스)이 지나면 자동으로 다른 작업으로 교체(context switch). 개발자가 직접 제어할 필요 없음. 한 작업이 CPU를 독점하지 않아서 공정성(Fairness) 보장.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 간단한 파이썬 비동기 코드입니다. `await asyncio.sleep(delay)` 에서 현제 함수의 실행을 중단하고 다른 작업을 수행할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import asyncio

async def task(name, delay):
    print(f&quot;{name} 시작&quot;)
    await asyncio.sleep(delay)  # 대기하면서 다른 작업 수행 가능
    print(f&quot;{name} 완료&quot;)

async def main():
    await asyncio.gather(
        task(&quot;작업1&quot;, 2),
        task(&quot;작업2&quot;, 1),
        task(&quot;작업3&quot;, 3),
    ) # 여러개의 코루틴 동시 실행

asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과는 다음과 같이 순서대로 실행되지 않고, 작업 2가 1초 만에 끝나고, 작업 1과 작업 3이 계속 실행됩니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;작업1 시작!
작업2 시작!
작업3 시작!
작업2 완료!
작업1 완료!
작업3 완료!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 예시로, 웹 서버에서 /async_task 엔드포인트는 비동기 함수로 실행되어서 클라이언트가 요청을 보내도 서버가 멈추지 않고 다른 요청을 처리할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;from fastapi import FastAPI
import asyncio

app = FastAPI()

async def slow_task():
    await asyncio.sleep(3)
    return {&quot;message&quot;: &quot;완료!&quot;} # I/O 작업이 끝날 때까지 논블로킹으로 대기

@app.get(&quot;/async-task&quot;)
async def async_task():
    return await slow_task()

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코루틴의 실행을 제어할 수 있는 여러 가지 방법들에 대해서 간략하게 살펴봅시다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;asyncio.create_task()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 작업을 스케줄링하고 실행 상태를 관리하는 방법. 비동기 작업이 동시에 실행됨&lt;/li&gt;
&lt;li&gt;asyncio.create_task()를 사용하면 코루틴을 백그라운드에서 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;asyncio.gather()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개의 코루틴을 동시에 실행하고 한꺼번에 결과를 받는 방법. 모든 작업의 결과를 리스트로 반환&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;asyncio.wait()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 코루틴을 실행하면서 개별적인 완료를 감지하는 방법&lt;/li&gt;
&lt;li&gt;완료된 작업과 실행 중인 작업을 구분 가능&lt;/li&gt;
&lt;li&gt;return_when 옵션을 사용하면, 일부 작업만 끝나도 반환 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;asyncio.Event()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 코루틴이 특정 이벤트가 발생할 때까지 대기하는 방법&lt;/li&gt;
&lt;li&gt;event.wait()을 사용하여 대기시키고, event.set()을 호출하여 코루틴을 실행시킴.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;asyncio.Condition()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 조건을 만족할 때까지 코루틴 실행을 대기하는 방법&lt;/li&gt;
&lt;li&gt;condition.notify_all()이 호출될 때까지 대기 후 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;asyncio.Semaphore(n)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시에 실행할 수 있는 코루틴 개수를 n개로 제한하는 방법(트래픽 제한이 필요한 경우 유용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이썬에서 코루틴은 큐(Queue), 락(Lock), 세마포어(Semaphore), 이벤트(Event), 조건 변수(Condition), 태스크 그룹(TaskGroup, Python 3.11+) 등을 사용하여 공유 자원을 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큐(Queue)는 여러 개의 코루틴이 데이터를 안전하게 주고받기 위해서 사용됩니다. 이름처럼 FIFO(First In, First Out) 방식으로 동작하며, 비동기적으로 데이터를 생산하고 소비하는(producer-consumer) 방식에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 큐 사용 예시를 살펴보면, 생산자는 데이터를 넣고 (queue.put()) 소비자는 큐에 데이터 있을 때까지 기다렸다가 데이터를 가져갑니다(queue.get()). 여러 개의 코루틴이 동시에 실행되어도 큐를 통해 안전하게 데이터를 전달할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import asyncio

queue = asyncio.Queue()

async def producer():
    for i in range(1, 6):
        await queue.put(i)  # 데이터를 큐에 넣음
        print(f&quot;생산: {i}&quot;)
        await asyncio.sleep(1)

async def consumer():
    while True:
        item = await queue.get()  # 큐에서 데이터를 꺼냄
        print(f&quot;소비: {item}&quot;)
        queue.task_done()  # 작업 완료 표시
        await asyncio.sleep(2)

async def main():
    asyncio.create_task(producer())  # 생산자 실행
    await consumer()  # 소비자 실행

asyncio.run(main())

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행결과:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;생산: 1
소비: 1
생산: 2
생산: 3
소비: 2
생산: 4
소비: 3
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;락(Lock)은 한 번에 오직 하나의 코루틴만 특정 코드 블록(또는 DB, 전역 변수 같은 공유 자원)을 실행하도록 보장합니다. 아래의 예시에서 async with lock: 은 Lock을 걸고(acquire()) 실행, 끝나면 자동으로 해제(release())합니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot;&gt;&lt;code&gt;import asyncio

lock = asyncio.Lock()

async def task(name):
    print(f&quot;{name} 실행 요청&quot;)
    async with lock:  # 락을 획득해야 실행 가능. 끝나면 자동으로 락 해제.
        print(f&quot;{name} 실행 중...&quot;)
        await asyncio.sleep(2)  # 임의의 작업 수행
        print(f&quot;{name} 완료!&quot;)

async def main():
    await asyncio.gather(task(&quot;A&quot;), task(&quot;B&quot;), task(&quot;C&quot;))

asyncio.run(main())
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행결과는 A, B, C 가 동시에 실행되는 것이 아니라 순서대로 하나씩 실행됩니다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;A 실행 요청
A 실행 중...
A 완료!
B 실행 요청
B 실행 중...
B 완료!
C 실행 요청
C 실행 중...
C 완료!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;asyncio.Queue와 asyncio.Lock을 적절히 조합하면, 데이터 공유와 동기화 문제를 동시에 해결할 수 있으므로 비동기 프로그램에서 안전하고 효율적인 데이터 처리가 가능합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코루틴을 사용하면 좋은 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 언제 코루틴을 사용하는 것이 좋을까요?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;I/O 작업이 많은 경우&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP API 호출, 웹 크롤링, 데이터베이스 조회, 파일 읽기/쓰기 작업 등&lt;/li&gt;
&lt;li&gt;비동기 방식으로 실행하면 대기 시간이 줄어들어 성능이 향상됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 비동기 처리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;asyncpg 라이브러리를 사용하면 다중 DB 요청 비동기 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대량의 네트워크 요청 처리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 웹 서버 개발 (FastAPI, Sanic, Quart 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메시지 큐 및 비동기 데이터 처리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kafka, RabbitMQ, Redis Pub/Sub 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 반대로 다음과 같은 경우에는 사용을 재고해 보는 것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 연산이 많은 경우 (ML 모델 학습, 이미지 처리 등)에는 멀티스레딩/멀티프로세싱 고려.&lt;/li&gt;
&lt;li&gt;비동기 지원이 없는 라이브러리와 함께 사용할 때&lt;/li&gt;
&lt;li&gt;단순한 코드 실행 (비동기 작업이 필요 없는 경우)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;Go 고루틴 (Goroutine)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go에서는 go 키워드를 사용하면 새로운 고루틴이 생성되며, 이를 통해 비동기 작업을 실행할 수 있습니다. 고루틴은 Go 런타임이 알아서 실행을 관리하며, OS 스레드와 독립적으로 실행됩니다. Go 스케줄러는 기본적으로 협력적(Cooperative) 방식으로 동작하지만, Go 1.14부터는 특정 지점에서 선점형(Preemptive) 스케줄링도 수행하여 긴 실행 시간을 가진 고루틴을 중단할 수 있습니다. 고루틴은 매우 가볍고, Go 런타임의 M:N 스케줄링을 통해 적은 수의 OS 스레드에서 수많은 고루틴을 효율적으로 실행할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 간단한 고루틴 예시는 다음과 같습니다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

func printNumber(n int) {
    for i := 1; i &amp;lt;= 5; i++ {
        fmt.Printf(&quot;고루틴 %d: %d\\n&quot;, n, i)
        time.Sleep(500 * time.Millisecond)
    }
}

func main() {
    go printNumber(1)
    go printNumber(2)
    fmt.Println(&quot;메인 함수 종료&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 코드를 실행하면 기대하던 결과가 아닌 오직 main() 함수의 마지막 라인만 실행된 것처럼 보입니다.&lt;/p&gt;
&lt;pre class=&quot;&quot;&gt;&lt;code&gt;메인 함수 종료
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 main() 함수가 끝날 때 실행 중인 고루틴들도 강제 종료되기 때문입니다. 이를 막으려면 sync.WaitGroup 또는 Channel을 사용해서 고루틴이 종료될 때까지 기다리도록 해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Channel은 고루틴이 완료될 때 신호를 보내고, main()에서 그 신호를 받을 수 있는 고루틴을 위한 통신 체계입니다. 채널을 통해 명시적으로 완료 신호를 전달해야 합니다. 데이터를 주고받거나, 특정 이벤트 감지를 감지하는 등 여러 개의 신호를 처리하며 고루틴과의 통신이 필요할 때 사용하면 좋습니다. 또한 고루틴끼리도 데이터를 안전하게 주고받을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시는 채널을 활용해 고루틴 실행이 끝날 때까지 기다린 프로그램입니다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;time&quot;
)

func printNumber(n int, doneChan chan bool) {
    for i := 1; i &amp;lt;= 5; i++ {
        fmt.Printf(&quot;고루틴 %d: %d\\n&quot;, n, i)
        time.Sleep(500 * time.Millisecond)
    }
    doneChan &amp;lt;- true // 채널에 데이터 전송
}

func main() {
    done := make(chan bool, 2) // 2개의 신호 저장 가능한 채널 생성

    go printNumber(1, done) // 고루틴에 채널 전달
    go printNumber(2, done)

    &amp;lt;-done // 채널에서 데이터 수신
    &amp;lt;-done

    fmt.Println(&quot;모든 작업 완료!&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과, 여러 개의 고루틴이 동시에 실행되었으므로 작업 순서가 일정하지 않은 것을 볼 수 있습니다. 각 고루틴이 1~5까지 출력한 후 채널에 불리언 데이터를 보내고( done &amp;lt;- true) 종료되었고, 메인 함수는 채널에서 2개의 신호를 받은(&amp;lt;-done) 후 종료되었습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;고루틴 1: 1
고루틴 2: 1
고루틴 1: 2
고루틴 2: 2
고루틴 1: 3
고루틴 2: 3
고루틴 1: 4
고루틴 2: 4
고루틴 1: 5
고루틴 2: 5
모든 작업 완료!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sync.WaitGroup은 고루틴이 실행될 때 Add(), 끝나면 Done()을 호출하여 자동으로 관리합니다. 단순히 모든 고루틴이 끝날 때까지 기다리는 경우나 신호 전달이 필요 없는 경우에 사용할 수 있습니다. 그러나 WaitGroup은 고루틴의 개수를 추적하므로, 사용하는 고루틴 개수를 정확히 알고 있어야 합니다. WaitGroup 은 아래와 같이 활용할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;go&quot;&gt;&lt;code&gt;package main

import (
    &quot;fmt&quot;
    &quot;sync&quot;
    &quot;time&quot;
)

func printNumber(n int, wg *sync.WaitGroup) {
    defer wg.Done() // defer는 바깥 함수가 실행완료된 후 실행됨.
    // 고루틴이 끝나면 WaitGroup 카운트 감소

    for i := 1; i &amp;lt;= 5; i++ {
        fmt.Printf(&quot;고루틴 %d: %d\\n&quot;, n, i)
        time.Sleep(500 * time.Millisecond)
    }
}

func main() {
    var wg sync.WaitGroup
    wg.Add(2) // 두 개의 고루틴을 기다림

    go printNumber(1, &amp;amp;wg)
    go printNumber(2, &amp;amp;wg)

    wg.Wait() // 모든 고루틴이 종료될 때까지 대기
    fmt.Println(&quot;모든 작업 완료!&quot;)
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고루틴 실행을 제어하는 다른 방법들에는 어떤 것이 있을까요?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;context 패키지 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고루틴을 실행 중간에 취소하거나, 타임아웃을 설정하는 데 유용 (API 요청 제한 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;sync.Once
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주어진 함수가 단 한 번만 실행되도록 보장. 여러 고루틴이 동시에 호출해도 한 번만 실행되고, 이후에는 아무 일도 일어나지 않음 (초기화 작업에 유용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;runtime.Goexit() 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 고루틴을 즉시 종료&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;sync.Cond (Condition Variables)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;특정 조건을 만족할 때까지 고루틴을 대기시키거나 깨우는 방법&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;sync/atomic 패키지 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원자적(atomic) 연산을 제공하여 고루틴 간의 데이터를 안전하게 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;time.Ticker와 time.After 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;일정 주기 실행 연산이나 시간 기반 제어가 필요할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;select 문 활용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개의 채널을 감시하면서 실행 흐름 제어&lt;/li&gt;
&lt;li&gt;default 문을 활용하면 채널이 비어 있어도 즉시 다른 작업 수행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Go에서는 뮤텍스(Mutex), WaitGroup, 조건 변수(sync.Cond), 채널(channel), 원자적 연산(sync/atomic) 등을 조합해서 고루틴의 공유 자원을 관리할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sync.Mutex (뮤텍스, Mutual Exclusion, 상호 배제)라는 여러 고루틴이 동시에 공유된 데이터를 읽고 쓰는 것을 방지하는 동기화 도구가 있습니다. 즉, 한 번에 하나의 고루틴만 특정 코드 블록을 실행하도록 보장합니다. mutex.Lock() 또는 mutex.Unlock()을 사용하여 race condition을 방지합니다. 하지만 채널로 뮤텍스를 대체할 수 있습니다. 데이터를 공유하는 것보다 전달(Pass)하는 것이 더 안전하기 때문에, 가능하면 channel을 우선적으로 고려하는 것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sync.Mutex &amp;rarr; 공유 데이터를 여러 고루틴이 동시에 수정해야 함 (한 번에 하나의 고루틴만 접근)&lt;/li&gt;
&lt;li&gt;sync.RWMutex &amp;rarr; 여러 고루틴이 읽기 가능하지만, 쓰기는 하나만 허용. 읽기 연산이 많고, 쓰기 연산이 적을 때.&lt;/li&gt;
&lt;li&gt;channel &amp;rarr; 데이터를 공유하지 않고 안전하게 전달하고 싶음 (경쟁 없이 안전한 통신). 채널을 잘못 사용하면 데드락(deadlock)이 발생할 수 있으니 주의하자.&lt;/li&gt;
&lt;li&gt;sync.WaitGroup &amp;rarr; 고루틴이 모두 끝날 때까지 main 함수가 종료되지 않도록 대기하는 역할. 단순히 여러 고루틴이 끝날 때까지 기다림. (데이터 보호 X)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고루틴을 사용하면 좋은 경우&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제 고루틴을 활용하면 좋을까요?&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;I/O 작업이 많은 경우&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네트워크 요청, 파일 처리, 데이터베이스 작업&lt;/li&gt;
&lt;li&gt;웹 서버에서 여러 요청을 동시에 처리할 때&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;고성능 웹 서버 및 API서버 개발&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Go 기반 HTTP 서버(net/http), gRPC 서버&lt;/li&gt;
&lt;li&gt;요청이 들어올 때마다 &lt;b&gt;고루틴을 자동으로 생성하여 요청을 비동기적으로 처리&lt;/b&gt;하므로, 많은 트래픽을 효율적으로 처리 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메시지 처리 시스템 (Kafka, RabbitMQ 등)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지 큐에서 데이터를 가져와 비동기적으로 여러 메시지를 동시에 처리&lt;/li&gt;
&lt;li&gt;IoT 장치에서 실시간 데이터 수집&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 이런 경우에는 고루틴을 사용하는 것을 다시 한번 고려해야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 바운드 작업
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 연산, 머신 러닝, 이미지 처리 등&lt;/li&gt;
&lt;li&gt;하나의 연산이 CPU 사용량을 100% 차지하는 경우, 단순한 고루틴 생성만으로는 성능 향상이 크지 않을 수 있음.&lt;/li&gt;
&lt;li&gt;단순히 고루틴을 생성하는 것만으로 병렬 실행이 보장되지는 않으며, runtime.GOMAXPROCS()로 CPU 개수를 조정할 필요가 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;고루틴 개수를 무작정 많이 생성하는 경우
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 사용량 증가 및 컨텍스트 스위칭 오버헤드가 발생하여 성능 저하&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공유 자원을 자주 수정하는 경우 (Race Condition 위험)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sync.Mutex 또는 sync.WaitGroup을 사용하여 동기화 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;요약&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 Python의 코루틴과 Go의 고루틴을 비교하며 비동기 프로그래밍의 개념과 활용법을 살펴보았습니다. Python의 코루틴은 async def와 await를 사용하며, 협력적 스케줄링 방식을 따릅니다. 이를 통해 HTTP 요청, 데이터베이스 쿼리, 파일 I/O와 같은 작업을 효율적으로 수행할 수 있습니다. asyncio.Queue와 asyncio.Lock을 활용하면 안전한 데이터 처리가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, Go의 고루틴은 go 키워드를 사용하여 실행되며, 선점형 스케줄링 방식으로 동작합니다. Go의 고루틴은 경량 쓰레드로, 수많은 작업을 동시에 효율적으로 실행할 수 있습니다. 고루틴 실행을 관리하기 위해 sync.WaitGroup, channel, context 등을 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Python의 코루틴은 I/O 중심의 비동기 처리에 강점을 가지며, Go의 고루틴은 다량의 네트워크 요청과 동시성 처리가 필요한 경우 적합합니다. 두 기술은 각각의 특성에 맞춰 적절히 활용할 때 강력한 성능을 발휘할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 이모저모</category>
      <category>async</category>
      <category>asyncio</category>
      <category>await</category>
      <category>coroutine</category>
      <category>goroutine</category>
      <category>lock</category>
      <category>고루틴</category>
      <category>동시성</category>
      <category>스케줄링</category>
      <category>코루틴</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/17</guid>
      <comments>https://juganote.tistory.com/entry/%EB%8F%99%EC%8B%9C%EC%84%B1-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D-Python-%EC%BD%94%EB%A3%A8%ED%8B%B4%EA%B3%BC-Go-%EA%B3%A0%EB%A3%A8%ED%8B%B4#entry17comment</comments>
      <pubDate>Sun, 2 Feb 2025 07:33:52 +0900</pubDate>
    </item>
    <item>
      <title>SQLAlchemy 2.0 - Major Migration Guide</title>
      <link>https://juganote.tistory.com/entry/SQLAlchemy-20-Major-Migration-Guide</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;SQLAlchemy란 무엇인가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLAlchemy는 Python에서 가장 널리 사용되는 데이터베이스 도구 중 하나로, SQL 데이터베이스를 효과적으로 다루기 위한 SQL 도구 및 Object-Relational Mapping(ORM) 라이브러리입니다. SQLAlchemy는 Python 개발자들에게 강력하고 유연한 데이터베이스 인터페이스를 제공합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SQLAlchemy 등장 배경&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;SQL의 복잡성 관리: 이전에는 데이터베이스와 상호작용하기 위해 직접 SQL 쿼리를 작성하는 것이 일반적이었으나, 복잡한 쿼리를 간편하게 사용하고 쉽게 유지보수하기 위해 SQLAlchemy가 등장했습니다.&lt;/li&gt;
&lt;li&gt;데이터베이스 데이터와 코드상의 객체 맵핑의 필요성: SQLAlchemy는 데이터베이스의 구조를 Python 객체로 추상화해, 데이터베이스의 테이블과 레코드를 코드로 쉽게 표현할 수 있으며, 객체 지향 프로그래밍 스타일로 데이터베이스를 다룰 수 있게 해 줍니다.&lt;/li&gt;
&lt;li&gt;데이터베이스 독립성 제공: SQLAlchemy는 다양한 데이터베이스(DBMS)에 대한 지원을 제공하며, 데이터베이스 간에 전환할 때 코드를 최소한으로 수정하도록 설계되었습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SQLAlchemy의 주요 특징과 장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Core: SQLAlchemy는 고급 SQL 표현식을 작성하고 실행할 수 있는 강력한 SQL Expression Language를 제공합니다.&lt;/li&gt;
&lt;li&gt;ORM(Object-Relational Mapper): 데이터베이스 테이블을 Python 클래스와 매핑하여 객체 지향적으로 데이터를 다룰 수 있습니다.&lt;/li&gt;
&lt;li&gt;데이터베이스 독립성: 다양한 데이터베이스 엔진(MySQL, PostgreSQL, SQLite 등)을 지원합니다.&lt;/li&gt;
&lt;li&gt;고성능: 효율적인 데이터 처리 및 쿼리 최적화를 제공합니다.&lt;/li&gt;
&lt;li&gt;유연성: SQLAlchemy는 SQL Expression Language와 ORM을 독립적으로 또는 함께 사용할 수 있도록 설계되었습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;SQLAlchemy 2.0&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLAlchemy 1.X버전에서 와 SQLAlchemy 2.0으로 매끄럽게 업데이트를 하는 방법에 대해서 알아봅시다. 2.0에서는 Core 및 ORM 구성 요소의 다양한 주요 사용 패턴이 크게 바뀌어서 버전 업데이트 시 코드 수정이 필수적입니다. 예를 들어 1.x 스타일에서&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;session.query(User).\\
  filter_by(name=&quot;some user&quot;).\\
  one()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 2.x 스타일로 변했습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;session.execute(
  select(User).
  filter_by(name=&quot;some user&quot;)
).scalar_one()&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;&lt;b&gt;2.0에서 변화된 것들&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.0에서의 새로운 변화는 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 ORM statement 패러다임&lt;/li&gt;
&lt;li&gt;Core 및 ORM의 SQL 캐싱&lt;/li&gt;
&lt;li&gt;새로운 Declarative 기능 및 ORM 통합&lt;/li&gt;
&lt;li&gt;새로운 Result 객체&lt;/li&gt;
&lt;li&gt;select() 및 case()에서 위치 인수 허용&lt;/li&gt;
&lt;li&gt;Core 및 ORM에 대한 asyncio 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 기능들은 2.0부터 더 이상 제공되지 않습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bound MetaData 및 연결 없는 실행(connectionless execution)&lt;/li&gt;
&lt;li&gt;Connection 레벨에서의 autocommit&lt;/li&gt;
&lt;li&gt;Session.autocommit 매개변수/모드&lt;/li&gt;
&lt;li&gt;select()의 List 및 Keyword 인자&lt;/li&gt;
&lt;li&gt;Python 2 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 변경 사항을 수용하기 위해서는 1.4에서 &amp;ldquo;future API&amp;rdquo;의 도움을 받아 매끄럽게 2.0으로 전환할 수 있습니다. 더 자세한 변화와 예시는 &lt;a href=&quot;https://docs.sqlalchemy.org/en/20/changelog/migration_20.html#migration-core-connection-transaction&quot;&gt;공식 문서&lt;/a&gt;에서 확인해 볼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;Migration Guide&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.0의 새로운 API 및 기능은 사실 1.4부터 포함되어 있으며 사용 가능합니다. 마이그레이션 단계를 간략하게 설명하자면, 모든 경고 플래그가 켜진 상태에서 1.4 버전이 잘 실행되고 2.0 지원 중단 경고가 발생하지 않으면 2.0과 대부분 상호 호환된다는 것이고, 이를 통해 2.0으로 부드러운 버전 업그레이드가 가능하도록 합니다. 마지막으로 2.x 릴리즈에 대해서 코드를 테스트해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전제 조건 1. 작동하는 1.3 버전&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 단계는 1.3 버전에서 SADeprecationWarning 클래스에서 발생하는 경고 없이 프로그램이 실행되거나 모든 테스트를 통과하는 것입니다. 1.4에는 이전 버전과 달라진 경고가 있으며, 특히 1.3에 도입된,&amp;nbsp;relationship.viewonly&amp;nbsp;와&amp;nbsp;relationship.sync_backref flag의 작동 방식이 일부 달라졌습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전제 조건 2. 작동하는 1.4 버전&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 단계는 SQLAlchemy 1.4 버전에서 프로그램이 잘 실행되는 것입니다. 대부분의 경우 1.4까지는 문제없이 실행될 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 매끄럽게 실행되지 않는다면 다음과 같은 사항을 확인해 보는 것이 좋습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URL 객체는 변경 불가능(immutable)&lt;/li&gt;
&lt;li&gt;SELECT 문은 더 이상 암시적으로 FROM 절로 간주되지 않음&lt;/li&gt;
&lt;li&gt;select().join() 및 outerjoin()은 하위 쿼리를 생성하는 대신 현재 쿼리에 JOIN 조건을 추가&lt;/li&gt;
&lt;li&gt;대다수의 Core 및 ORM statement 개체가 컴파일 단계에서 만들어지며 유효성 검사를 수행. 따라서 오류 메시지가 구성 시점이 아닌 컴파일/실행 시까지 표시되지 않을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1단계 - 최소 Python 3.7 이상&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2020년에 Python 2가 EOL(End of Life)을 맞이했습니다. SQLAlchemy 2.0을 사용하려면 애플리케이션이 최소한 Python 3.7에서 실행 가능해야 합니다. 1.4는 파이썬 3.6 이상을 지원합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2단계 - RemovedIn20Warnings 켜기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLAlchemy 1.4에는 레거시 패턴을 알려주는 deprecation warning 시스템이 도입되었습니다. 1.4에서 환경 변수 SQLALCHEMY_WARN_20을 true 또는 1로 세팅하여 사용 가능합니다. 실행 시 warning이 보이지 않는다면 혹시 경고를 억제하는 필터가 적용되어 있는지 확인합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3단계 - 모든 RemovedIn20Warnings 해결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 한 번에 모든 warning을 해결하려 하기보다 한 번에 한 종류씩 해결하는 것이 좋습니다. 특정 warning 하위 집합을 고른 후 그것만 빼고 나머지는 무시하도록 하는 필터를 설정해서, 한 번에 하나씩만 작업합니다. 코드를 수정한 후 특정 경고가 더 이상 발생하지 않으면 해당 필터를 제거하고, 다음 warning으로 넘어가 작업하고, 프로그램이 RemoveIn20Warning 없이 매끄럽게 실행될 때까지 반복합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4단계 - Engine 객체에서 future 플래그 사용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;future 플래그를 켜놓으면 해당 객체나 함수는 새로운 2.0 API를 완전히 지원하고, deprecated 된 기능들은 제외하여 실행합니다. 예를 들어 Engine 객체를 생성할 때 create_engine() 함수에 future=True 플래그를 전달하여 사용할 수 있습니다. Engine과 Connection 관련 RemovedIn20Warning 경고를 모두 해결한 후에 create_engine.future 플래그를 활성화할 수 있으며, 이때 오류는 발생하지 않아야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5단계 - Session에서 future 플래그 사용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Session 객체 역시 2.0 버전에서 업데이트된 트랜잭션/커넥션 레벨 API를 제공합니다. 1.4 버전에서는 Session.future 플래그를 Session 또는 sessionmaker에 사용하여 이 API를 활성화할 수 있습니다. 플래그가 켜지면 다음과 같은 상태가 됩니다.:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Session은 연결에 사용할 엔진을 확인할 때 더 이상 bound metadata를 지원하지 않습니다. 즉, Engine 객체를 생성자에게 전달해야 합니다(이때 Engine은 레거시나 2.x 스타일 객체 둘 다 가능).&lt;/li&gt;
&lt;li&gt;Session.begin.subtransactions 플래그는 더 이상 지원되지 않습니다.&lt;/li&gt;
&lt;li&gt;Session.commit() 메서드는 subtransaction을 조정하려 하지 않고 항상 데이터베이스에 COMMIT을 내보냅니다.&lt;/li&gt;
&lt;li&gt;Session.rollback() 메서드는 subtransaction을 그대로 유지하려고 하지 않고 항상 전체 트랜잭션 스택을 한 번에 롤백합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Session은 1.4에서 더욱 유연한 생성 패턴을 지원하며, Connection 객체에서 사용하는 패턴과 밀접하게 일치합니다. 주요 특징으로는 Session을 콘텍스트 관리자로 사용할 수 있다는 점이 있습니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from sqlalchemy.orm import Session

with Session(engine) as session:
    session.add(MyObject())
    session.commit()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 sessionmaker 객체는 세션을 생성하고 한 블록에서 트랜잭션을 시작/커밋하는 &amp;nbsp;sessionmaker.begin() 콘텍스트 매니저를 지원합니다:&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;from sqlalchemy.orm import sessionmaker

Session = sessionmaker(engine)

with Session.begin() as session:
    session.add(MyObject())&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLALCHEMY_WARN_20=1로 설정하고 모든 경고가 켜져 있을 때 모든 테스트를 통과하고 애플리케이션이 잘 실행된다면 완성입니다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6단계 - 명시적으로 형식화된 ORM 모델에 __allow_unmapped__=True 추가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLAlchemy 2.0에서는 ORM 모델의 &lt;a href=&quot;https://peps.python.org/pep-0484/&quot;&gt;PEP 484 type annotation&lt;/a&gt;(타입 힌트) 지원이 추가되었습니다. 이러한 annotation은 반드시 Mapped를 사용해야 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;class&amp;nbsp;sqlalchemy.orm.&lt;b&gt;Mapped&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매핑된 class에서 ORM 매핑된 attribute를 나타냅니다. Attribute가 올바르게 입력되도록 pylance 및 mypy와 같은 타입체커에 적절한 정보를 제공합니다. Mapped는 보통 mapped_class()나 relationship()등을 설정할 때 사용됩니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드에서는 Mapped를 사용하지 않은 클래스의 relationship() 필드의 타입 힌트가 List[&quot;Bar&quot;]일 때 에러를 발생시킵니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;Base = declarative_base()

class Foo(Base):
    __tablename__ = &quot;foo&quot;

    id: int = Column(Integer, primary_key=True)

    # This will raise Error!!
    bars: List[&quot;Bar&quot;] = relationship(&quot;Bar&quot;, back_populates=&quot;foo&quot;)

class Bar(Base):
    __tablename__ = &quot;bar&quot;

    id: int = Column(Integer, primary_key=True)
    foo_id = Column(ForeignKey(&quot;foo.id&quot;))

    # will raise
    foo: Foo = relationship(Foo, back_populates=&quot;bars&quot;, cascade=&quot;all&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mapped를 사용하지 않는 모든 type annotation이 오류 없이 전달되도록 하려면 __allow_unmapped__ 속성을 사용합니다. 이러한 경우 새로운 Declarative System에서 annotation을 완전히 무시하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시는 하위클래스에 __allow_unmapped__를 적용한 것입니다.&lt;/p&gt;
&lt;pre class=&quot;python&quot; data-ke-language=&quot;python&quot;&gt;&lt;code&gt;# qualify the base with __allow_unmapped__.  Can also be
# applied to classes directly if preferred
class Base:
    __allow_unmapped__ = True

Base = declarative_base(cls=Base)

# existing mapping proceeds, Declarative will ignore any annotations
# which don't include ``Mapped[]``
class Foo(Base):
    __tablename__ = &quot;foo&quot;

    id: int = Column(Integer, primary_key=True)

    bars: List[&quot;Bar&quot;] = relationship(&quot;Bar&quot;, back_populates=&quot;foo&quot;)

class Bar(Base):
    __tablename__ = &quot;bar&quot;

    id: int = Column(Integer, primary_key=True)
    foo_id = Column(ForeignKey(&quot;foo.id&quot;))

    foo: Foo = relationship(Foo, back_populates=&quot;bars&quot;, cascade=&quot;all&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7단계 - SQLAlchemy 2.0 릴리스에 대한 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLAlchemy 2.0에는 이전 버전과 호환될 수 있도록 추가된 API 변경 사항이 있지만 그럼에도 불구하고 특정 케이스에서 문제가 생길 수 있습니다. 따라서 마지막 단계는 가장 최신 버전의 SQLAlchemy 2.x에 대한 테스트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;마무리하며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 SQLAlchemy 1.x 버전에서 2.x 버전으로 업그레이드하는 방법을 살펴봤습니다. 이전 회사에서 1.4 버전으로 업그레이드하는 것도 꽤나 많은 작업이 필요했는데요, 앞으로 2.x 버전을 써보는 것이 기대가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 이모저모</category>
      <category>sqlalchemy</category>
      <category>sqlalchemy 2.x 마이그레이션</category>
      <category>sqlalchemy migration</category>
      <category>sqlalchemy2</category>
      <category>sqlalchemy2.0</category>
      <category>마이그레이션</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/16</guid>
      <comments>https://juganote.tistory.com/entry/SQLAlchemy-20-Major-Migration-Guide#entry16comment</comments>
      <pubDate>Sun, 19 Jan 2025 05:32:03 +0900</pubDate>
    </item>
    <item>
      <title>NGINX, Proxy, Load Balance</title>
      <link>https://juganote.tistory.com/entry/NGINX-Proxy-Load-Balance</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 NGINX가 무엇인지, 왜 만들어졌는지 다뤄보고 또 어떻게 사용되는지 실제 사례를 통해 알아보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초창기 웹이 간단하고 사용자가 적었을 시절에 웹의 간단한 사용 예시를 살펴보자면, 먼저 브라우저가 어떤 웹 서버에 웹 페이지를 요청합니다. 이 '웹 서버'는 서버 기계에 설치된 소프트웨어로, 웹 페이지를 조립한 후 브라우저로 전송하는 역할을 했습니다. 브라우저 요청에 응답할 수 있는 소프트웨어를 서버에 실행시키는 것입니다. 그 웹 서버 소프트웨어가 바로 NGINX였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 웹이 대중화되고 점점 발전하면서 한 웹사이트에 수천, 수백만 개의 요청이 몰리게 되었습니다. 하나의 서버가 이런 수백만 개의 요청을 처리하는 것은 불가능하기에, 여러 대의 서버를 추가해야 합니다. 하지만 서버가 여러 대인 경우에는 브라우저에서 들어오는 요청을 어떻게 각각의 서버로 분배할지 결정하는 단계가 필요해집니다. 이에 따라 로드 밸런싱(load balancing)이라는 개념이 등장하게 됩니다. 만약 10대의 NGINX 서버를 추가했다면, 역시 동일한 NGINX 웹 서버가 로드 밸런서로서 작동하며, 브라우저 요청을 10대의 서버 중 하나로 보냅니다(프록시 처리). 프록시(Proxy)란 누군가를 대신해서 무언가를 한다는 의미입니다. 즉, NGINX는 브라우저 요청을 웹 서버를 대신해 받아들이고 이를 각 서버에 분배하는 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런싱(load balancing) 방식은 설정된 알고리즘에 따라 다릅니다. 예를 들어, 가장 덜 바쁜 서버(least-connected)가 요청을 받거나, 또는 라운드 로빈(round robin)이라는 알고리즘을 사용하여 요청을 순차적으로 순환하며 균등하게 분배할 수 있습니다. 또는 IP 해시를 통해 다음 요청을 처리할 서버를 결정합니다. 이는 동일한 클라이언트의 요청이 항상 동일한 서버로 전달되도록 보장합니다. 이렇게 세 가지 방식이 NGINX가 지원하는 로드 밸런싱 메서드입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇듯 NGINX는 웹 서버 소프트웨어와 프록시 역할을 겸하고 있으며, 로드 밸런싱은 NGINX Proxy의 여러 기능 중 하나에 불과합니다. 또 다른 NGINX의 기능을 예시와 함께 살펴보도록 하겠습니다. 뉴욕타임스에서 새로운 기사가 발표되고 수백만 명의 사용자가 브라우저로 기사를 열었다고 생각해 봅시다. 각 요청이 웹 서버로 들어와서 이미지, 데이터베이스, 텍스트, 링크를 매번 새로 조합한 뒤 사용자에게 반환해야 한다면 매우 비효율적일 것입니다. 대신, 이 데이터를 한 번만 조합하고 이를 캐시(cache)로 저장해 뒀다가 요청이 올 때마다 동일한 파일을 반환하도록 하면 훨씬 더 효율적일 것입니다. 이 캐싱 기능이 NGINX의 또 다른 주요 기능입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 기능을 다른 예시와 살펴봅시다. 온라인 뱅킹 앱이나 소셜 네트워크 같은 시스템에 서버 100대가 있다고 할 때, 이 서버들은 해커들에게 아주 매력적인 표적이 될 것입니다. 만약 모든 서버가 공개적으로 접근 가능하다면, 해커들이 그중 하나의 약점을 찾아 시스템 전체를 침투할 가능성이 커집니다. 이렇게 모든 서버를 공개적으로 두는 대신, 단 하나의 프록시 서버만 공개적으로 접근 가능하도록 설정하면, 보안 공격 면적을 대폭 줄일 수 있습니다. 이 프록시 서버는 모든 요청을 받아 내부 웹 서버로 전달하며 방패 또는 보안 계층 역할을 합니다. 보안을 강화하기 위해 프록시 서버는 암호화된 통신(SSL)을 지원합니다. 예를 들어서 프런트엔드는 암호화된 메시지를 프록시에 보내고, 프록시는 이를 해독하거나 그대로 웹 서버로 전달할 수 있습니다. 이렇게 하면 통신이 외부에서 가로채여도 내용을 알 수 없게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대용량 콘텐츠 전송 시 NGINX는 압축 기능도 제공합니다. 예를 들어, Netflix 같은 플랫폼이 고화질 비디오를 수백만 사용자에게 전송할 때, NGINX는 파일을 압축해 대역폭을 절약하고 전송 속도를 높일 수 있습니다. 또한 콘텐츠를 한 번에 전송하는 대신 청크(chunk)로 나눠 전송할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 기능은 NGINX 설정 파일에서 구성할 수 있습니다. 설정 파일을 통해 NGINX가 웹 서버로 작동할지, 프록시 서버로 작동할지 등이나, 캐싱, SSL, 로드 밸런싱 옵션 등의 세부 설정을 정의할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NGINX는 설정이 간단하고 유연하며 빠른 성능 덕분에 매우 인기를 얻었으며, Kubernetes의 Ingress Controller로도 널리 사용됩니다. Ingress Controller는 클러스터 내부에서 프록시 및 로드 밸런서 역할을 하며, 외부에서 오는 트래픽을 적절히 분배합니다. 이를 통해 Kubernetes 환경에서도 NGINX는 중요한 역할을 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, NGINX는 웹 서버, 프록시 서버, 로드 밸런서, 보안 게이트웨이 등 다양한 용도로 활용되며, 간단한 설정으로 강력한 기능을 제공합니다. NGINX는 Apache 보다 더 가볍고 빠르며 정적 파일 처리와 컨테이너 환경에서 유리하다는 점입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프록시 vs 리버스 프록시 vs 로드 밸런서&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대형 웹사이트들이 수백만 명의 사용자를 동시에 처리하면서도 다운되지 않는 이유는 무엇일까요? 수백만명의 사용자에게 데이터를 안전하게 전송하며 적절한 서버로 안내하는 방법은 무엇일까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시(Proxy), 리버스 프록시(Reverse Proxy), 로드 밸런서(Load Balancer)를 포함한 세 가지 중요한 웹 컴포넌트를 간단하게 알아봅시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레스토랑에 저녁 식사를 예약해야 하는 상황을 가정해 봅시다. 만약 너무 바쁜 상황이라 레스토랑 직원과 직접 소통할 수 없습니다. 만약 개인 비서가 예약을 대신 처리한다면, 레스토랑 직원은 내가 아니라 비서와만 소통할 것입니다. 여기서 &amp;lsquo;나&amp;rsquo;는 인터넷을 탐색하는 데 사용하는 랩탑이라고 볼 수 있고 나의 비서는 프록시 서버라고 보면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 서버는 노트북과 연결된 개인 네트워크와 요청이 나가는 공용 인터넷 사이에서 중개 역할을 합니다. 프록시는 응답을 당신에게 전달하기 전에 트래픽을 필터링하고, 유해한 웹사이트나 스크립트를 차단하여 노트북을 보호합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업무 상황에서 프록시는 더 중요해질 수 있습니다. 예를 들어, 직원들이 인터넷에서 무엇을 할지 모르기 때문에, 관리자는 모든 직원의 인터넷 트래픽을 프록시를 통해 라우팅 하도록 설정할 수 있습니다. 이렇게 하면 직원이 악성 웹사이트를 방문해 바이러스에 감염되는 것을 방지할 수 있습니다. 프록시는 요청과 응답을 검사하고, 위험이 있는 콘텐츠를 차단하며, 내부 네트워크를 보호합니다. 또한 캐싱 기능도 있어 대역폭을 절약하고 불필요한 인터넷 트래픽을 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 레스토랑 비유로 돌아가서 나는 비서가 예약한 테이블을 찾기 위해 리셉션에서 체크인합니다. 리셉션 직원이 테이블로 안내하며, 이는 요청을 관리하고 올바르게 분배하는 리버스 프록시(Reverse Proxy)의 역할과 비슷합니다. 리버스 프록시는 서버 쪽에서 클라이언트 요청을 처리하며, 로드 밸런싱 기능을 수행합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리버스 프록시는 보안 역할도 수행합니다. 수백 개의 서버가 민감한 데이터에 접근할 수 있다면 이를 모두 인터넷에 노출하는 것은 위험합니다. 리버스 프록시는 요청을 스캔하고 SSL 암호화를 보장하며, 보안 위협을 차단하는 등 다수의 보안 기능을 제공합니다. 대표적인 리버스 프록시로는 Nginx가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로드 밸런서와 리버스 프록시는 서로 대체 관계가 아니라 상호 보완적인 관계입니다. 클라우드 로드 밸런서는 외부 트래픽을 내부 네트워크로 전달하는 반면, 리버스 프록시는 내부에서 더 정교한 트래픽 라우팅을 수행합니다. 리버스 프록시는 쿠키나 세션 데이터를 기반으로 더 세밀한 라우팅을 제공하며, 특정 사용자 요청을 동일한 서버로 보낼 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Node.js나 Java 애플리케이션이 자동으로 시작하는 경량 프록시도 존재합니다. 이는 대규모 생산 환경에서 사용하는 Nginx 같은 고성능 리버스 프록시와는 다른 목적을 가집니다. 그러나 이 두 기술은 조합해서 사용할 수 있습니다. 이렇게 프록시와 리버스 프록시, 로드 밸런서를 이해하면 인터넷 작동 방식을 더욱 명확히 알 수 있습니다.&lt;/p&gt;</description>
      <category>개발 이모저모</category>
      <category>load balance</category>
      <category>Load Balancing</category>
      <category>nginx</category>
      <category>proxy</category>
      <category>Reverse Proxy</category>
      <category>로드밸런서</category>
      <category>로드밸런싱</category>
      <category>리버스 프록시</category>
      <category>엔진엑스</category>
      <category>프록시</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/15</guid>
      <comments>https://juganote.tistory.com/entry/NGINX-Proxy-Load-Balance#entry15comment</comments>
      <pubDate>Sat, 4 Jan 2025 22:08:33 +0900</pubDate>
    </item>
    <item>
      <title>API 프로토콜: Rest API, GraphQL, 그리고 gRPC 그 외.</title>
      <link>https://juganote.tistory.com/entry/API-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-Rest-API-GraphQL-%EA%B7%B8%EB%A6%AC%EA%B3%A0-gRPC-%EA%B7%B8-%EC%99%B8</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 가장 널리 알려진 API 프로토콜을 살펴볼 예정입니다. 다양한 API 액세스 방식은 여러 가지 상황을 더 빠르고 효율적으로 처리하는데 도움이 될 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;API Protocol이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 프로토콜은 서로 다른 소프트웨어 애플리케이션 간에 데이터를 주고받기 위한 규칙이나 형식을 정의하는 기술입니다. API는 응용 프로그램 간에 상호작용을 가능하게 하는 인터페이스인데, 이 상호작용이 어떻게 이루어질지 정의하는 것이 바로 프로토콜입니다. API 프로토콜은 클라이언트와 서버 간의 데이터 전송 방식을 규정하며, 이를 통해 통신이 일관되게 이루어지도록 돕습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;REST&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 Representational State Transfer의 약자로, 2000년에 Roy Fielding에 의해 만들어졌으며 가장 널리 사용되고 있습니다. URL 구조와 HTTP(Hypertext Transfer Protocol, 하이퍼텍스트 전송 프로토콜)에 기반한 인터페이스로 구현된 아키텍처 스타일입니다. stateless(상태 비저장)하고, 캐싱이 가능하며 규칙(convention)에 기반한 클라이언트-서버 상호 작용을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 URL을 통해 어떤 리소스를 사용할지 정하고 HTTP 메서드를 사용해 어떤 작업을 수행할지 표현합니다. HTTP 메서드는 다음과 같습니다.:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET: 기존 리소스 또는 여러 리소스를 가져오기&lt;/li&gt;
&lt;li&gt;POST: 새 리소스를 생성&lt;/li&gt;
&lt;li&gt;PUT: 리소스를 업데이트하거나 존재하지 않는 경우 생성&lt;/li&gt;
&lt;li&gt;DELETE: 리소스 삭제&lt;/li&gt;
&lt;li&gt;PATCH: 기존 리소스를 부분적으로 업데이트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST의 가장 큰 장점은 기술 업계에서 가장 성숙한 API 아키텍처 스타일이라는 점입니다. REST 프로토콜은 HTTP를 지원하는 모든 프로그래밍 언어를 사용하여 구축할 수 있고 JSON 및 XML과 같은 다양한 데이터 형식을 지원하기 때문에 많은 개발자가 익숙하고 작업하기 쉽게 느껴집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 데이터를 나타내는 리소스와 URI(Uniform Resource Identifier)덕분에 변경이 용이합니다. 즉, 개발자는 기존 클라이언트 애플리케이션을 중단하지 않고도 API 구조를 변경할 수 있습니다. 이처럼 리소스가 고유 URL 뒤에 위치하기 때문에 API를 모니터링하고 속도를 제한하기가 쉽습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 REST는 HTTP를 활용하여 캐싱을 간소화합니다. HTTP 응답을 캐싱하면 클라이언트와 서버가 서로 지속적으로 상호 작용할 필요가 없습니다. 이렇게 HTTP의 캐싱 및 로드 밸런싱 기능을 덕에 확장이 가능하고 분산된 시스템을 구축하는 데에도 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면에 REST는 under-fetching과 over-fetching이 모두 발생하기 쉽습니다. 예를 들어 중첩된(nested) 엔티티를 가져오려면 여러 번 요청해야 할 수 있습니다. 또한 일반적으로 특정 엔티티의 일부 데이터만 가져올 수 없습니다. 클라이언트는 항상 엔드포인트가 자신의 configuration대로 반환하는 모든 데이터를 수신해야 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GraphQL&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL은 Facebook에서 개발한 API용 오픈 소스 쿼리 언어이며, 해당 쿼리를 수행하기 위한 프레임워크를 제공합니다. 데이터를 조작하기 위해 HTTP 메서드에 의존하지 않고 대부분 POST만 사용합니다. 그 대신 queries, mutations, 및 subscriptions을 사용하여 데이터를 처리합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Query는 서버에서 데이터를 요청하는 데 사용됩니다.&lt;/li&gt;
&lt;li&gt;Mutation은 서버의 데이터를 수정하는 데 사용됩니다.&lt;/li&gt;
&lt;li&gt;Subscription은 데이터가 변경될 때 실시간 업데이트를 받는 데 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL은 클라이언트가 한 번의 쿼리로 필요한 데이터를 정확하게 요청할 수 있는 프로토콜입니다. 따라서 데이터를 과도하게 가져오거나 적게 가져오는 일이 줄어듭니다. 이는 데이터 fetching이 한 번의 왕복으로 가능하게 하여 효율적이며, 네트워크를 통해 불필요한 데이터가 전송되지 않기 때문에 네트워크 오버헤드를 줄일 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL에서 API는 사용자가 요청할 수 있는 데이터 유형을 정의하는 스키마(Schema)에 의해 구조화됩니다. 이 스키마는 API를 통해 사용할 수 있는 데이터의 형식과 구조를 지정합니다. 이는 개발자를 위한 명확한 가이드 역할을 하며 데이터 접근의 일관성을 보장합니다. GraphQL을 사용하면 subscription이라는 기능을 통해 실시간 데이터 업데이트가 용이해집니다. subscription을 사용하면 말 그대로 서버가 관련 업데이트를 감지할 때 구독한 클라이언트에게 새 데이터를 선제적으로 푸시합니다. 이 메커니즘은 즉각적인 자동 업데이트를 가능하게 하여 클라이언트가 실시간으로 가장 최신의 정보를 받을 수 있도록 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유연하고 효율적인 데이터 검색 및 조작이 필요한 상황에서는 REST를 대체할 수 있는 훌륭한 대안입니다. 특히 복잡한 데이터 모델이 있는 애플리케이션이나 연결성이 제한된 모바일 애플리케이션에서 아주 적합한 선택입니다. GraphQL을 사용하면 이전 버전과 호환되는 API 스키마 변경이 가능하므로 기존 클라이언트를 중단하지 않고도 API를 쉽게 발전시킬 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 반대로 엄격한 데이터 유효성 검사가 필요한 애플리케이션, 다양한 클라이언트를 지원해야 하는 애플리케이션, 또는 소셜 미디어와 같은 사용자 대면 앱에는 적합하지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST와는 달리 쿼리가 다를 수 있기 때문에 GraphQL은 중간 프록시 캐싱을 중단하여 캐싱 구현을 더 어렵게 만듭니다. 또한 GraphQL 쿼리는 잠재적으로 크고 복잡한 서버 측 작업을 실행할 수 있으므로 서버에 과부하가 걸리지 않도록 쿼리의 복잡성을 제한하는 경우가 많습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;gRPC&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC는 Google이 개발한 고성능 오픈소스 RPC 프레임워크입니다. 이 프레임워크는 효율적이고 빠른 데이터 전송을 목표로 하며, 프로토콜 버퍼 (Protocol Buffers)는 인터페이스 정의 언어(IDL)를 사용하여 클라이언트와 서버 간 데이터 및 호출 계약을 명확히 정의합니다. 마이크로 아키텍처에서 각 서비스의 언어가 달라도 문제없는 통신을 가능하게 합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Remote Procedure Call 이란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원격 서버에 있는 함수나 프로시저를 호출할 수 있도록 설계된 통신 모델로, 네트워크를 통한 함수 호출을 마치 로컬 함수 호출처럼 간단하게 사용할 수 있게 합니다. 이러한 방식은 네트워크 통신을 직접 구현해야 하는 부담을 덜어주어, 분산 시스템이나 클라이언트-서버 구조에서 효율적으로 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC 프로토콜에서 클라이언트와 서버는 프로토콜 버퍼로 요청 및 응답 스키마를 정의하며, 이를 통해 상호작용이 일관되고 안정적으로 이루어집니다. 데이터는 바이너리 형식으로 직렬화되어 전송되므로, JSON 대비 페이로드 크기를 줄여 전송 속도와 효율성이 높습니다. gRPC는 HTTP/2를 사용해 효율적인 멀티플렉싱(단일 연결에서 여러 요청과 응답을 처리해 지연 시간을 줄이고 성능을 최적화) 기능을 제공하며 TLS/SSL를 통해 데이터 전송 보안을 강화합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프레임워크는 여러 유형의 클라이언트-서버 상호 작용을 지원합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;전통적인 요청-응답 상호 작용&lt;/li&gt;
&lt;li&gt;서버 스트리밍. 클라이언트의 한 개의 요청이 여러 개의 응답을 생성할 수 있습니다.&lt;/li&gt;
&lt;li&gt;클라이언트 스트리밍. 클라이언트의 여러 요청이 하나의 응답으로 처리될 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC의 가장 큰 장점은 컴팩트한 데이터 형식, 빠른 메시지 인코딩 및 디코딩, HTTP/2 사용으로 구현되는 높은 성능입니다. 또한 코드 생성 기능은 여러 프로그래밍 언어를 지원하며 상용구 코드를 작성하는 시간을 절약할 수 있도록 도와줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/2와 TLS/SSL을 요구함으로써 gRPC는 더 나은 보안과 스트리밍에 대한 기본 지원을 제공합니다. 인터페이스 계약의 언어에 구애받지 않는 정의는 서로 다른 프로그래밍 언어로 작성된 서비스 간의 통신을 가능하게 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 데이터 형식이 사람이 읽을 수 없기 때문에 페이로드를 분석하고 디버깅을 수행하려면 추가 도구가 필요합니다. 또한 HTTP/2는 최신 버전의 최신 브라우저에서 TLS를 통해서만 지원됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC 대량의 데이터를 조작하거나 다양한 클라이언트를 지원해야 하는 애플리케이션에는 최선의 선택이 아닐 수 있지만, 고성능과 낮은 오버헤드로 잘 알려져 있어 서비스 간 빠르고 효율적인 통신이 필요한 마이크로서비스 간 통신이나, 실시간 데이터 스트리밍(채팅, IoT) 또는 분산 시스템에서의 데이터 전송 애플리케이션에 적합합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;WebSocket&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹소켓은 전이중 통신(full-duplex)을 지원하여 클라이언트와 서버 간의 양방향 데이터 교환을 가능하게 하는 실시간 통신 프로토콜입니다. 영구적인 연결을 유지한다는 점에서 기존 HTTP의 요청-응답 모델과 다릅니다. 반복적으로 연결을 열고 닫을 때에 비해 오버헤드가 적습니다. 따라서 빈번한 데이터 업데이트가 필요한 시나리오에 더 효율적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 지속적인 연결을 설정하고 유지하므로 각 요청에 대해 반복적으로 연결을 열고 닫을 필요가 없으므로 지연 시간이 짧은(low latency) 통신을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청/응답 기반인 REST(상태 비저장형 단방향 대화)와 달리 웹소켓은 양방향의 지속적인 커뮤니케이션을 할 수 있으므로 채팅 애플리케이션이나 온라인 게임과 같이 실시간 업데이트가 필요한 애플리케이션에 이상적입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹소켓은 복잡한 데이터 조작이 필요한 애플리케이션이나 확장성이 중요한 애플리케이션에는 적합하지 않을 수 있습니다. 하지만 양방향 지속적 연결 덕분에 실시간 커뮤니케이션과 low latency가 중요한 상황의 경우 REST보다 효율적으로, 매우 요긴하게 쓰일 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SOAP&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOAP는 Simple Object Access Protocol의 약자로, 네트워크 상에서 애플리케이션 간 통신을 위한 표준 프로토콜입니다. 주로 웹 서비스의 데이터 교환을 위해 설계되었으며, XML 형식을 기반으로 데이터를 송수신합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOAP의 주요 특징은 높은 표준 준수와 보안성입니다. SOAP는 HTTP, SMTP 등 다양한 프로토콜 위에서 동작하며, 메시지 전송 중 데이터의 무결성을 보장하기 위해 강력한 보안 기능을 제공합니다. 이를 통해 트랜잭션 관리나 인증, 메시지 암호화 등 복잡한 작업을 안전하게 처리할 수 있습니다. 또한 SOAP는 상태를 유지해야 하는 애플리케이션에 적합하며, ACID 트랜잭션을 지원하는 환경에서도 안정적으로 작동합니다. XML 기반 구조 덕분에 확장성이 뛰어나며, 데이터의 명확한 정의와 구조를 유지할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SOAP는 주로 높은 보안 수준, 안정성, 표준화된 구조가 필요한 엔터프라이즈 애플리케이션에서 활용되며, 금융, 헬스케어, 정부 기관과 같은 산업 분야에서 자주 사용됩니다. 예를 들어, 보안이 중요한 금융 거래 시스템이나 전자 건강 기록(EHR)과 같은 애플리케이션에 적합합니다. 이러한 환경에서는 데이터 무결성과 보안이 핵심이며, SOAP의 표준화된 메시징 시스템과 높은 신뢰성이 큰 이점으로 작용합니다. 또한 다양한 시스템과의 상호 운용성이 요구되는 복잡한 통합 작업에서도 SOAP는 효과적인 선택이 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;어떤 것을 선택해야 할까?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 여러 가지 API 통신 프로토콜 중 하나를 고를 때 고려해야 할 몇 가지 기준을 살펴보겠습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Data Format&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 요청 및 응답 데이터 형식 측면에서 가장 유연한 접근 방식입니다. JSON 및 XML과 같은 하나 또는 여러 데이터 형식을 지원하도록 REST 서비스를 구현할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 GraphQL은 데이터를 요청할 때 사용해야 하는 쿼리 언어를 자체적으로 정의합니다. GraphQL 서비스는 JSON 형식으로 응답합니다. 응답을 다른 형식으로 변환할 수는 있지만 이는 흔하지 않으며 성능에 영향을 미칠 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC 프레임워크는 사용자 정의 바이너리 형식인 프로토콜 버퍼를 사용합니다. 이는 사람이 읽을 수 없지만 gRPC의 성능을 높이는 주요 이유 중 하나이기도 합니다. 여러 프로그래밍 언어에서 지원되지만 이 형식은 사용자 지정할 수 없습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Data Fetch&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL은 서버에서 데이터를 가져오는 데 가장 효율적인 API 접근 방식입니다. 클라이언트가 가져올 데이터를 선택할 수 있기 때문에 일반적으로 네트워크를 통해 불필요한 데이터가 전송되지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, REST와 gRPC는 이러한 고급 클라이언트 쿼리 기능을 지원하지 않습니다. 따라서 새로운 엔드포인트나 필터를 서버에 개발하고 배포하지 않으면 서버에서 불필요한 데이터가 반환될 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Browser Support&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST와 GraphQL API는 모든 최신 브라우저에서 지원됩니다. 일반적으로 JavaScript 클라이언트 코드가 브라우저에서 서버 API로 HTTP 요청을 보내는 데 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, gRPC는 HTTP/2를 지원하는 브라우저에서만 사용할 수 있습니다. 그러나 gRPC-Web라는 익스텐션을 사용하면, 이는 HTTP 1.1을 기반으로 하기 때문에 제한된 기능을 가지고 있지만 모든 브라우저에서 사용할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Code Generation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL은 추가 라이브러리가 필요하며, 이러한 라이브러리는 GraphQL 스키마 처리,  annotation 기반 프로그래밍, GraphQL 요청의 서버 처리 지원을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC 프레임워크도 추가 라이브러리를 필요로 하며, 필수적인 코드 생성 단계가 포함됩니다. 프로토콜 버퍼 컴파일러는 서버 및 클라이언트 보일러플레이트 코드를 생성하고, 이를 활용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 특정 프로그래밍 언어나 HTTP 라이브러리를 사용하여 구현할 수 있는 아키텍처 스타일로, 미리 정의된 스키마를 사용하지 않으며 코드 생성을 요구하지 않습니다. 다만, Swagger나 OpenAPI를 활용하면 스키마를 정의하고 원하는 경우 코드를 생성할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. Response Time&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC는 최적화된 이진 형식을 사용하여 REST와 GraphQL에 비해 응답 시간이 훨씬 빠릅니다. 그렇지만 다른 프로토콜도 로드 밸런싱을 사용하여 클라이언트 요청을 여러 서버에 고르게 분배할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC는 기본적으로 HTTP 2.0을 사용하기 때문에, REST와 GraphQL API에 비해 지연 시간이 더 낮습니다. HTTP 2.0을 통해 여러 클라이언트는 새로운 TCP 연결을 설정하지 않고 동시에 여러 요청을 보낼 수 있습니다. 대부분의 성능 테스트에 따르면, gRPC는 REST보다 약 5배에서 최대 10배까지 더 빠른 성능을 보여줍니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. Caching&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST에서 요청과 응답의 캐싱은 간단하고 성숙한 방식으로, HTTP 수준에서 데이터를 캐싱할 수 있게 해 줍니다. 각 GET 요청은 애플리케이션 자원을 노출하며, 이는 브라우저, 프록시 서버, 또는 CDN에 의해 쉽게 캐시 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, GraphQL은 기본적으로 POST 메서드를 사용하고, 각 쿼리가 다를 수 있기 때문에 캐싱 구현이 더 복잡합니다. 이는 특히 클라이언트와 서버가 지리적으로 멀리 떨어져 있을 때 더욱 그렇습니다. 이 문제를 해결할 수 있는 방법 중 하나는 GET을 사용하여 쿼리를 수행하고, 서버에 미리 계산되어 저장된 지속적인 쿼리를 사용하는 것입니다. 일부 GraphQL 미들웨어 서비스는 또한 캐싱 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 gRPC는 기본적으로 요청과 응답의 캐싱을 지원하지 않습니다. 그러나, 응답을 캐시 할 수 있는 맞춤형 미들웨어 계층을 구현하는 것은 가능합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. Intended Usage&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST는 도메인에 적합한 아키텍처 스타일로, 동작보다는 자원 집합으로 쉽게 설명할 수 있습니다. HTTP 메서드를 활용하면 이러한 자원에 대해 표준적인 CRUD 작업을 수행할 수 있습니다. HTTP의 의미론에 의존함으로써 호출자에게 직관적이며, 이는 공용 인터페이스에 적합합니다. 또한 REST는 좋은 캐싱 지원을 제공하여, 사용 패턴이 안정적이고 지리적으로 분산된 사용자들이 있는 API에 적합합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL은 여러 클라이언트가 서로 다른 데이터 세트를 요구하는 공용 API에 적합합니다. 따라서 GraphQL 클라이언트는 표준화된 쿼리 언어를 통해 원하는 정확한 데이터를 지정할 수 있습니다. 또한, 여러 출처에서 데이터를 집계하여 여러 클라이언트에 제공하는 API에 좋은 선택이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC 프레임워크는 마이크로서비스 간의 빈번한 상호작용이 필요한 내부 API를 개발할 때 적합합니다. 주로 IoT 장치와 같은 저수준 에이전트에서 데이터를 수집하는 데 사용됩니다. 그러나 제한된 브라우저 지원으로 인해 고객-facing 웹 애플리케이션에서 사용하기 어려운 점이 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Mix and Match&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 가지 API 프로토콜에는 각각 장단점이 있습니다. 그러나 모든 경우에 적합한 접근 방식은 없으며 사용 사례에 따라 어떤 접근 방식을 선택할지 결정해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번 한 가지를 선택할 필요는 없습니다. 또한 솔루션 아키텍처에서 다양한 스타일을 혼합하여 사용할 수도 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 아키텍처 다이어그램 예시에서 보듯이, 서로 다른 애플리케이션 계층에서 서로 다른 API 프로토콜을 적용할 수도 있습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2024-12-22 at 14.45.12.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;1386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ne7bm/btsLrdUmIFm/nqFcxxmD5yhpcGcaNT5N0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ne7bm/btsLrdUmIFm/nqFcxxmD5yhpcGcaNT5N0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ne7bm/btsLrdUmIFm/nqFcxxmD5yhpcGcaNT5N0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNe7bm%2FbtsLrdUmIFm%2FnqFcxxmD5yhpcGcaNT5N0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;639&quot; height=&quot;785&quot; data-filename=&quot;Screenshot 2024-12-22 at 14.45.12.png&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;1386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글에서는 웹 API 설계에 널리 사용되는 커뮤니케이션 스타일을 살펴보았습니다. REST, GraphQL, gRPC, WebSocket, SOAP의 사용 사례를 살펴보고 장점과 장단점을 살펴봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 어떤 API 프로토콜을 사용할지 결정하기 전에 고려해야 할 몇 가지 기준을 살펴보고 비교해 보았고, 애플리케이션 계층에 따라 서로 다른 접근 방식을 혼합하여 사용할 수 있는 방법을 살펴봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REST에서 요청과 응답의 캐싱은 간단하고 성숙한 방식으로, HTTP 수준에서 데이터를 캐싱할 수 있게 해 줍니다. 각 GET 요청은 애플리케이션 자원을 노출하며, 이는 브라우저, 프록시 서버, 또는 CDN에 의해 쉽게 캐시 될 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면, GraphQL은 기본적으로 POST 메서드를 사용하고, 각 쿼리가 다를 수 있기 때문에 캐싱 구현이 더 복잡합니다. 이는 특히 클라이언트와 서버가 지리적으로 멀리 떨어져 있을 때 더욱 그렇습니다. 이 문제를 해결할 수 있는 방법 중 하나는 GET을 사용하여 쿼리를 수행하고, 서버에 미리 계산되어 저장된 지속적인 쿼리를 사용하는 것입니다. 일부 GraphQL 미들웨어 서비스는 또한 캐싱 기능을 제공합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 gRPC는 기본적으로 요청과 응답의 캐싱을 지원하지 않습니다. 그러나, 응답을 캐시 할 수 있는 맞춤형 미들웨어 계층을 구현하는 것은 가능합니다.&lt;/p&gt;</description>
      <category>개발 이모저모</category>
      <category>API</category>
      <category>graphQL</category>
      <category>gRPC</category>
      <category>REST</category>
      <category>soap</category>
      <category>websocket</category>
      <category>웹소켓</category>
      <category>프로토콜</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/14</guid>
      <comments>https://juganote.tistory.com/entry/API-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C-Rest-API-GraphQL-%EA%B7%B8%EB%A6%AC%EA%B3%A0-gRPC-%EA%B7%B8-%EC%99%B8#entry14comment</comments>
      <pubDate>Sun, 22 Dec 2024 22:49:24 +0900</pubDate>
    </item>
    <item>
      <title>파이썬 비동기 웹 애플리케이션 기본 개념부터 FastAPI까지</title>
      <link>https://juganote.tistory.com/entry/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EB%B6%80%ED%84%B0-FastAPI%EA%B9%8C%EC%A7%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글은 사실 FastAPI를 언제 어떻게 쓰면 좋을까, 최고의 usecase에는 어떤 게 있을까 하는 고민으로부터 시작하게 되었습니다. 그런데 공부를 하다 보니 기본 개념을 한번 더 짚고 넘어가면 좋을 것 같다는 생각에 비동기 웹 애플리케이션 개념부터 WSGI, ASGI가 뭔지 FastAPI가 어떻게 등장하게 되었는지 등을 다뤄보려고 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;동기/비동기 웹 애플리케이션&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 동기 웹 애플리케이션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;동기 웹 애플리케이션에서는 클라이언트가 요청을 보내면 서버가 해당 요청을 처리할 때까지 클라이언트는 대기해야 합니다. 서버가 응답을 보내기 전까지, 다른 작업은 실행되지 않습니다. 동기 애플리케이션은 구현이 간단하지만, 서버가 오래 걸리는 작업을 처리할 때 클라이언트가 응답을 기다려야 한다는 단점이 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 비동기 웹 애플리케이션&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기 웹 애플리케이션은 클라이언트가 요청을 보내면 서버는 해당 요청을 처리하는 동안 클라이언트가 다른 작업을 계속할 수 있도록 합니다. 서버는 작업이 완료되면 클라이언트에게 응답을 보내, 필요한 부분만 업데이트합니다. 예를 들면 AJAX를 이용해 특정 데이터만 가져오거나, 채팅 애플리케이션에서 실시간 메시지를 주고받는 방식입니다. 비동기 애플리케이션은 서버의 응답을 기다리지 않고 동시에 여러 요청을 처리할 수 있어, 더 나은 사용자 경험을 제공합니다. 그러나 동기 방식에 비해 구현이 복잡하며, 데이터 동기화를 위한 처리가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;WSGI란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 웹 애플리케이션에서 메시지의 흐름은 다음과 같습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;napkin-selection (3).png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;244&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJcvuz/btsKB3ZS2bt/MgdN4Gwk26rt1hwUnx1Sv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJcvuz/btsKB3ZS2bt/MgdN4Gwk26rt1hwUnx1Sv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJcvuz/btsKB3ZS2bt/MgdN4Gwk26rt1hwUnx1Sv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJcvuz%2FbtsKB3ZS2bt%2FMgdN4Gwk26rt1hwUnx1Sv1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2132&quot; height=&quot;244&quot; data-filename=&quot;napkin-selection (3).png&quot; data-origin-width=&quot;2132&quot; data-origin-height=&quot;244&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 WSGI(Web Server Gateway Interface)는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Web Server와&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Python Web Application가 서로 통신할 수 있는 통로 역할을 합니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt; 즉, Python 웹 애플리케이션을 다양한 웹 서버와 호환될 수 있도록 하기 위해 만들어진 표준 인터페이스입니다. 쉽게 설명하자면 클라이언트가 보내는 Request Message가 Python이 이해할 수 있는 형태로 바꾸어주는 역할을 한다고 할 수 있습니다. &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WSGI 서버로는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Gunicorn, uWSGI, Waitress 등이 존재하며, WSGI 웹 프레임워크로는 Flast, Django, Pyramid 등이 있습니다. Flask의 경우 자체적으로 WSGI가 구현되어 있어서 `python run.py` &lt;/span&gt;&lt;span style=&quot;text-align: start;&quot;&gt;형태로 애플리케이션을 실행할 수 있습니다.&lt;/span&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 주요 역할과 특징&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;애플리케이션과 서버 간의 통신 표준 제공&lt;/b&gt;: WSGI는 애플리케이션과 서버가 데이터를 주고받는 방법을 정의하여 두 시스템 간의 호환성을 높입니다. 애플리케이션 개발자는 WSGI 표준을 따르기만 하면 특정 웹 서버에 종속되지 않고 다양한 서버에서 애플리케이션을 운영할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;애플리케이션의 유연성 향상&lt;/b&gt;: WSGI는 웹 서버와 애플리케이션의 결합을 느슨하게 만들어, 웹 애플리케이션을 쉽게 다른 환경이나 서버로 옮길 수 있도록 돕습니다. 예를 들어, Django나 Flask와 같은 WSGI 호환 웹 프레임워크로 작성된 애플리케이션은 WSGI를 지원하는 서버에서 문제없이 동작할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 장점과 한계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WSGI를 사용하면 웹 애플리케이션이 특정 웹 서버에 의존하지 않기 때문에, 다양한 WSGI 호환 서버에서 작동할 수 있어 서버 독립성을 가질 수 있습니다. 또한 WSGI 표준 덕분에 Python 웹 애플리케이션과 웹 서버의 호환성이 높아집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 WSGI는 동기식 요청 처리를 기반으로 설계되어, 고성능 비동기 애플리케이션에는 적합하지 않습니다. 따라서 WebSocket과 같은 실시간 양방향 통신을 지원하지 않습니다. 이러한 비동기 한계를 극복하기 위해 ASGI(Asynchronous Server Gateway Interface)라는 비동기 표준이 만들어졌으며, 이는 FastAPI와 같은 비동기 프레임워크에서 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ASGI란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASGI(Asynchronous Server Gateway Interface)는 Python에서 비동기 웹 애플리케이션과 웹 서버가 상호작용할 수 있도록 설계된 표준 인터페이스입니다. WSGI가 동기 방식의 웹 애플리케이션을 위한 표준이었다면, ASGI는 비동기 및 동기 애플리케이션 모두를 지원할 수 있도록 만들어졌습니다. 특히, 동시에 많은 요청을 효율적으로 처리해야 하는 현대적인 웹 애플리케이션을 위해 등장했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASGI 서버로는 Uvicorn, Daphne 등이 있으며, ASGI 웹 프레임워크로는 Starlette, FastAPI, aiohttp, Django Channels, Quart, Sanic, Starlite 등이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI는 실행 환경을 제공하지 않으므로, 이를 실행하려면 ASGI 서버가 필요합니다. Uvicorn은 ASGI 표준을 구현한 서버 중 하나로, FastAPI 애플리케이션을 실행하기 위해 흔히 사용됩니다. 예를 들어 `uvicorn app:app` 명령어로 FastAPI 애플리케이션을 실행할 수 있습니다. Uvicorn은 Python의 asyncio와 uvloop을 사용해 이벤트 루프 기반 비동기 처리를 제공합니다. 이 방식은 비동기 작업을 효율적으로 처리할 수 있게 하여 요청을 동시에 여러 개 처리하는 데 뛰어납니다. 네트워크 대기 시간과 I/O 작업이 많은 경우 성능이 크게 향상됩니다. 또한 HTTP/2 네트워크 프로토콜을 지원합니다. HTTP/2는 멀티플렉싱과 헤더 압축을 통해 대역폭 효율성을 높여 Uvicorn을 빠르고 유연하게 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Daphne는 Django Channels를 위해 설계된 ASGI 서버로, Django 애플리케이션에서 WebSocket을 지원합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 주요 역할과 특징&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;비동기 및 동기 요청 모두 지원&lt;/b&gt;: ASGI는 WSGI의 한계를 극복하고, 비동기 요청을 처리할 수 있도록 설계되었습니다. 이를 통해 Python 애플리케이션이 실시간 데이터 전송 및 높은 동시성 처리가 필요한 환경에서 효율적으로 작동할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;WebSocket과 같은 양방향 통신 지원&lt;/b&gt;: WSGI는 HTTP 통신만 지원하지만, ASGI는 WebSocket을 포함한 다양한 프로토콜을 지원합니다. 이를 통해 실시간 통신이 필요한 채팅, 알림, 실시간 대시보드와 같은 애플리케이션 개발이 가능해졌습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;범용성&lt;/b&gt;: ASGI는 비동기 및 동기 웹 애플리케이션 모두에서 작동할 수 있도록 설계되었기 때문에, 기존 WSGI 애플리케이션도 어느 정도 호환할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 장점과 한계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASGI는 비동기 처리가 가능하여 고성능의 동시성 처리 요구에 적합합니다. 또한 WebSocket을 통한 실시간 양방향 통신을 지원하므로, 실시간 애플리케이션에 유리합니다. 다양한 프로토콜을 처리할 수 있도록 설계되어 향후 확장이 용이합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 비동기 코드는 `async`, `await`등의 사용 등으로 동기 코드보다 이해하기 어렵고, 디버깅과 에러 처리가 더 복잡할 수 있습니다. 그리고 ASGI는 기존 WSGI와 완전히 호환되지 않으므로, 기존의 WSGI 애플리케이션을 ASGI로 마이그레이션 하는 과정을 거쳐야 합니다. 또한&amp;nbsp;비동기 애플리케이션에서는 데이터베이스 접근도 비동기적으로 처리할 수 있습니다. 예를 들어, SQLAlchemy의 비동기 버전인 Databases 라이브러리 또는 Tortoise ORM과 같은 비동기 지원 ORM을 사용할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;FastAPI란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FastAPI는 Python 기반의 비동기 웹 프레임워크로, 높은 성능과 간편한 사용성 덕분에 최근 인기를 끌고 있습니다. ASGI 표준을 따르며, 비동기 처리와 높은 성능이 요구되는 RESTful API나 실시간 통신을 위한 웹 애플리케이션 개발에 적합합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Starlette 기반&lt;/h4&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;Starlette 역시 Python기반 경량 ASGI 프레임워크로, 비동기 요청 처리를 지원하여 높은 동시성을 필요로 하는 웹 애플리케이션이나 API에 최적화되어 있어 Node.js나 Go와 같은 비동기 기반의 다른 언어 프레임워크와 비교할 수 있는 성능을 제공합니다.&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;또한 간결하고 유연한 코드 구조로 필요한 기능만을 추가해 코드를 간결하게 유지할 수 있으며, 다양한 기능을 쉽게 확장할 수 있습니다. 그 외에도 URL 경로 설정 및 미들웨어 구성을 간단하게 할 수 있도록 설계되어 있어, 요청과 응답을 효과적으로 관리할 수 있습니다. &lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;ASGI 애플리케이션 테스트도 HTTPX와 통합된 테스트 클라이언트로 쉽게 수행할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;FastAPI는 Starlette의 기능을 확장하여 사용자가 더욱 편리하게 REST API와 웹 애플리케이션을 만들 수 있도록 설계된 프레임워크입니다. Starlette은 라우팅, 미들웨어, 세션 관리와 같은 웹 애플리케이션의 기본 기능을 제공하고, FastAPI는 이에 더해 데이터 검증, 자동 문서화, 타입 힌트를 사용하는 추가 기능을 더했습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Pydantic과 SQLModel&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span data-token-index=&quot;0&quot;&gt;Pydantic&lt;/span&gt;은 Python 데이터 모델링 및 데이터 검증 라이브러리로, &lt;span data-token-index=&quot;2&quot;&gt;데이터 유효성 검사&lt;/span&gt;와 &lt;span data-token-index=&quot;4&quot;&gt;직렬화&lt;/span&gt;를 간편하게 수행할 수 있게 해 줍니다. &lt;span data-token-index=&quot;6&quot;&gt;Python의 타입 힌트&lt;/span&gt;를 사용하여 데이터 구조를 명확히 정의하고, 입력된 데이터가 사전에 정의된 형식에 부합하는지 자동으로 검사할 수 있습니다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;즉, 데이터베이스와의 직접적인 상호작용을 목적으로 하지 않으며, 데이터베이스와 상호작용하는 모델과 유효성을 검사하는 모델을 분리해 데이터의 무결성을 관리할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLModel은 Pydantic과 SQLAlchemy의 기능을 결합하여 데이터 유효성 검사와 동시에 SQLAlchemy ORM을 사용하여 데이터베이스와 직접 상호 작용할 수 있&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;게 해주는 Python 라이브러리입니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;SQLModel의 모델은 SQLAlchemy ORM 클래스처럼 사용할 수 있습니다. 따라서 데이터베이스 모델링과 데이터 유효성 검사를 동시에 수행하고 싶을 때&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;SQLModel을 사용하는 것이 좋습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2.1 SQLModel이 ORM인가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLModel은 자체 ORM이라기보다는 SQLAlchemy ORM을 확장하고 보완하는 역할을 합니다. SQLModel 클래스는 기본적으로 SQLAlchemy의 Declarative Base를 상속받아 SQL 테이블과 상호 작용할 수 있습니다. 따라서 SQLModel을 사용할 때는 SQLAlchemy ORM과 함께 사용한다고 생각하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2.2 Pydantic과 SQLModel 사용법&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SQLModel을 사용할 때 별도로 Pydantic을 사용할 필요는 없습니다. SQLModel이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Pydantic과 SQLAlchemy를 결합한 형태&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이기 때문에, SQLModel의 모델 클래스는 이미&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;Pydantic 모델&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;SQLAlchemy 모델로 동작합니다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;하지만,&lt;span&gt;&amp;nbsp;&lt;/span&gt;특정 상황에서는 Pydantic 모델을 추가로 사용하는 것이 유리할 때도 있습니다. 예를 들어:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;데이터베이스 스키마와 다른 API 요청/응답 스키마가 필요할 때&lt;/b&gt;: 데이터베이스 모델과 약간 다른 구조의 API 스키마가 필요할 때는 Pydantic을 별도로 사용할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;고급 데이터 유효성 검사를 수행할 때&lt;/b&gt;: SQLModel 모델이 제공하는 기본 유효성 검사 외에, Pydantic의 고급 유효성 검사 기능을 활용하고 싶을 때도 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이런 특수한 경우가 아니라면 SQLModel을 단독으로 사용하는 것으로 충분합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. FastAPI의 주요 장점&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 언급한 비동기 환경에서의 고성능 외에도 다음과 같은 장점들이 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;타입 힌트와 자동 검증&lt;/b&gt;: 요청과 응답의 데이터를 자동으로 검증하여 타입 안정성이 향상되며, 타입 에러를 줄일 수 있습니다. 오류 메시지를 자동으로 생성해 클라이언트에게 반환합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동 문서화&lt;/b&gt;: FastAPI는 OpenAPI와 JSON Schema를 자동으로 생성하여 API 문서를 자동으로 제공합니다. 개발자는 /docs 또는 /redoc 경로에서 Swagger UI 또는 Redoc을 통해 API 문서를 시각화할 수 있어, 빠르게 문서를 확인하고 테스트할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;간결하고 직관적인 코드&lt;/b&gt;: FastAPI는 코드가 간결하고 직관적이어서 학습 곡선이 낮습니다. RESTful API를 구축할 때, 엔드포인트 설정, 파라미터 검증, 응답 형식 정의 등이 빠르고 쉽게 이루어집니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비동기 및 동기 함수 모두 지원&lt;/b&gt;: FastAPI는 비동기 함수뿐만 아니라 동기 함수도 지원합니다. 이를 통해, 기존의 동기 코드를 쉽게 통합할 수 있으며, 필요에 따라 비동기로 전환할 수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;FastAPI를 선택하기 좋은 상황&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;고성능 API&lt;/b&gt;: FastAPI는 특히 RESTful API 또는 비동기 작업을 많이 필요로 하는 서비스에서 뛰어난 성능을 발휘하므로, 빠르고 효율적인 API가 필요한 서비스에 적합합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대규모 또는 실시간 데이터 통신&lt;/b&gt;: WebSocket과 같은 실시간 데이터 통신이나 많은 동시 요청 처리가 필요한 환경에서는 비동기 지원 덕분에 FastAPI가 유리합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;타입 안정성이 중요한 프로젝트&lt;/b&gt;: 타입 힌트를 통한 자동 검증 및 문서화가 필요하거나, 데이터 모델이 복잡하여 검증이 중요한 프로젝트에서 FastAPI는 Pydantic과의 통합을 통해 강력한 데이터 유효성 검사를 제공합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동화된 문서화와 빠른 개발 속도&lt;/b&gt;: 클라이언트와 협력하는 프로젝트나 빠른 프로토타입 개발이 필요한 경우, 자동화된 문서화는 API 설계 및 테스트 효율성을 높여줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발 이모저모</category>
      <category>ASGI</category>
      <category>fastapi</category>
      <category>pydantic</category>
      <category>sqlalchemy</category>
      <category>sqlmodel</category>
      <category>wsgi</category>
      <category>비동기</category>
      <author>발짜개</author>
      <guid isPermaLink="true">https://juganote.tistory.com/13</guid>
      <comments>https://juganote.tistory.com/entry/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EB%B9%84%EB%8F%99%EA%B8%B0-%EC%9B%B9-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B8%B0%EB%B3%B8-%EA%B0%9C%EB%85%90%EB%B6%80%ED%84%B0-FastAPI%EA%B9%8C%EC%A7%80#entry13comment</comments>
      <pubDate>Sat, 9 Nov 2024 07:39:38 +0900</pubDate>
    </item>
  </channel>
</rss>