<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>이름 없는 블로그</title>
    <link>https://ryuwon-it.tistory.com/</link>
    <description>여러 개발 정보 끄적이고 있습니닷..</description>
    <language>ko</language>
    <pubDate>Thu, 18 Jun 2026 19:00:57 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ryuwon</managingEditor>
    <item>
      <title>공짜로 k3s 홈랩 구축하기: 3노드 클러스터를 시작하며</title>
      <link>https://ryuwon-it.tistory.com/47</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존 배포 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스를 배포할 때마다 이 프로젝트는 내 AWS 계정에 있었는지, 팀원 계정에 있었는지, 프리티어 기간은 아직 남아 있는지, EC2 보안 그룹은 어디까지 열어뒀는지 같은 것들을 먼저 확인해야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정작 서비스 자체는 그렇게 크지 않았는데, 배포를 하려면 주변에 붙어 있는 설정들을 매번 다시 떠올려야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 그 방식이 크게 불편하지 않았다. AWS 프리티어 인스턴스 하나를 만들고 Docker Compose로 서비스를 올린 다음 nginx를 붙이면 웬만한 개인 프로젝트는 돌아갔고, 필요하면 RDS나 EC2를 하나 더 붙이면 됐으며, 팀 프로젝트에서도 빠르게 결과물을 보여줘야 할 때나 비용 측면에 있어 이 방식이 가장 현실적이었다.. &lt;s&gt;돈 없는 학생의 삶..&lt;/s&gt;&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;/p&gt;
&lt;pre class=&quot;livescript&quot;&gt;&lt;code&gt;프로젝트 서비스 배포
  -&amp;gt; 프리티어 계정 생성
  -&amp;gt; EC2 생성
  -&amp;gt; 각종 자원 생성(RDS, Elasticache 등)
  -&amp;gt; Docker / nginx / 인증서 설정
  -&amp;gt; 배포 스크립트 정리
  -&amp;gt; 다음 프로젝트에서 비슷한 과정 반복
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DevOps가 어렵다고 느낀 것도 처음부터 거창한 이유는 아니었다. 대규모 트래픽을 받아야 한다거나 복잡한 인프라를 설계해야 한다는 이야기보다, 서버를 만들고 패키지를 설치하고 Docker를 깔고 nginx를 설정하고 인증서를 붙인 뒤 보안 정책과 배포 스크립트와 로그 모니터링까지 다시 챙기는 과정이 프로젝트마다 반복되는 쪽에 가까웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올리려는 서비스는 작아도, 매번 새로 준비해야 하는 작업은 상당히 번거로웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Terraform으로 인프라를 코드화하면 이 불편함을 어느 정도 줄일 수 있겠다는 생각에 실제로 그 방향도 고민해봤지만, 서비스마다 필요한 자원이 제각각이라는 점이 계속 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 서비스는 API 서버와 DB 하나면 충분하고, 어떤 서비스는 DB 여러개가 필요하다. 어떤 서비스는 worker나 주기적으로 실행되는 job이 있어야 한다. 결국 Terraform을 쓰더라도 서비스마다 인스턴스를 어떻게 나눌지 다시 고민하게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이쯤 되니 서버 단위로 서비스를 쪼개는 것보다 하나의 클러스터 위에서 pod 단위로 관리하는 쪽이 더 낫겠다는 생각이 들었다. 서비스를 새로 올릴 때마다 인스턴스를 하나 더 만들고 그 서버의 생명주기를 따로 관리하는 방식이 아니라, 이미 준비된 클러스터 위에 필요한 리소스만 얹고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 서버 비용을 아끼려는 목적도 있었지만, 그보다 개인 프로젝트와 팀 프로젝트를 조금 더 운영답게 다뤄보고 싶다는 마음이 컸다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Docker Compose 쓰면 되는거 아니야?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트 몇 개를 올리는 정도라면 Docker Compose로도 충분하다. 서버 한 대에 compose 파일을 두고 docker compose up -d로 서비스를 띄운 뒤 nginx나 caddy로 외부 요청을 받아주면 되니까. 작은 서비스라면 이보다 단순한 방법을 찾기도 어렵고, 빠르게 결과물을 보여줘야 하는 상황에서는 여전히 가장 현실적인 선택지라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 하나일 때는 이 방식이 단순해서 좋았다. 하지만 여러 개인 프로젝트와 자동화 도구를 계속 올려두려 하니, 단일 서버에 compose 파일을 계속 쌓아두는 방식이 점점 답답하게 느껴졌다. 앱 서버, DB, Redis, worker, 배치 작업, 자동화 도구가 한 서버 안에 섞이기 시작하면 어디까지를 하나의 서비스로 보고 어떻게 나눠서 운영할지도 애매해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 단순히 컨테이너를 실행하는 것을 넘어, 여러 서비스를 한 환경 안에서 배포하고 관찰하고 관리하는 흐름을 만들어보고 싶었다. 무중단 배포를 조금 더 안정적으로 해보고 싶었고, 서비스별 리소스 배치나 네트워크 경계도 서버 단위가 아니라 워크로드 단위로 다뤄보고 싶었다. Argo CD 같은 GitOps 도구나 모니터링 도구도 실제 서비스가 올라간 환경에서 직접 붙여보고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 만들고 있는 코스피 공포 탐욕 지수 서비스나 n8n 같은 자동화 도구도 나중에는 이 환경 위에 올려보고 싶었다. 그러려면 단일 서버에 compose 파일을 쌓아두는 방식보다 잘 유지보수 할수있는 환경이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker Compose로도 어느 정도는 가능하지만, 서버 한 대에 모든 걸 몰아넣으면 그 서버가 죽을 때 전부 같이 죽는다. 개인 홈서버에서 SPOF를 완전히 없애겠다는 말은 조금 과하더라도, 적어도 서비스를 어떻게 나누고 배포하고 관찰할지 운영에 가까운 방식으로 연습해보고 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ECS 같은 선택지도 있었고 AWS에서 관리형으로 가져가면 편한 부분도 많지만, 공부와 개인 프로젝트를 위해 계속 켜둘 환경에 매달 비용이 들어가면 안됐다. 그래서 여러 클라우드를 서칭을 하다 OCI를 발견하게 되는데..!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OCI Always Free를 만나다&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;687&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kMOau/dJMcagMer44/0TEhUhGCSz1AecsLwWfiYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kMOau/dJMcagMer44/0TEhUhGCSz1AecsLwWfiYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kMOau/dJMcagMer44/0TEhUhGCSz1AecsLwWfiYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkMOau%2FdJMcagMer44%2F0TEhUhGCSz1AecsLwWfiYk%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;496&quot; height=&quot;284&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;687&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle Cloud Infrastructure(OCI). 4 OCPU에 24GB RAM을 무료로 쓸 수 있다는 조건은 AWS 프리티어 인스턴스 1 OCPU 1GB RAM을 쓰던 나에게는 정말 엄청났다.. 물론 무료라는 말만 보고 아무거나 만들 수는 없고, OCI도 부트 볼륨이나 LB, NAT Gateway 같은 리소스를 잘못 만들면 비용이 나올 수 있었지만 4코어 24GB를 무료로 쓸 수 있다면 뭐든 만들 수 있을거 같았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2403&quot; data-origin-height=&quot;1088&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/braZKs/dJMcajoxYVh/2MYaVlohy63TGN4iWNOsCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/braZKs/dJMcajoxYVh/2MYaVlohy63TGN4iWNOsCk/img.png&quot; data-alt=&quot;영롱한 OCI 프리티어...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/braZKs/dJMcajoxYVh/2MYaVlohy63TGN4iWNOsCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbraZKs%2FdJMcajoxYVh%2F2MYaVlohy63TGN4iWNOsCk%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;2403&quot; height=&quot;1088&quot; data-origin-width=&quot;2403&quot; data-origin-height=&quot;1088&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;영롱한 OCI 프리티어...&lt;/figcaption&gt;
&lt;/figure&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;처음부터 현업처럼 Kubernetes 운영을 하려는 건 아니었다. OKE를 쓰거나 관리형 DB를 붙이거나 LB를 바로 만드는 방향은 제외했다. 대신 4코어 24GB를 3대 VM으로 나누고 그 위에 k3s를 올린 뒤, 무료 한도 안에서 필요한 도구를 하나씩 붙여보기로 계획했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;K8s은 꽤 무겁기도 했고 지금 내가 감당할 수 있도록 규모를 천천히 키워가기로.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;k3s를 선택한 이유도 내가 하려는 규모에서는 일반 Kubernetes보다 부담이 적고, 단일 server와 여러 agent 구성으로 시작하기도 좋으며, Kubernetes API는 그대로 쓰기 때문에 Deployment와 Service, Ingress, CRD, Controller 같은 흐름을 익히기에도 충분하다 판단했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 컨테이너를 띄우는 경험보다 운영 도구들이 클러스터 안에서 리소스를 만들고 관리하는 과정을 직접 보고 싶었다. Argo CD가 Git 상태를 보고 클러스터를 맞추는 과정, cert-manager가 인증서를 발급하고 갱신하는 과정, 모니터링 도구가 노드와 pod 상태를 수집하는 과정을 내 서비스가 올라간 환경에서 겪어보고 싶었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;일단은 작게 나눠서 시작하자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 생각한 구성은 3노드다. OCI A1 무료 한도인 4 OCPU와 24GB RAM 안에서 역할을 나눠 아래처럼 계획했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;노드 자원 역할&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;ryuwon-core&lt;/td&gt;
&lt;td&gt;1 OCPU / 8GB&lt;/td&gt;
&lt;td&gt;k3s server, Argo CD, 운영 도구&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ryuwon-data&lt;/td&gt;
&lt;td&gt;1 OCPU / 6GB&lt;/td&gt;
&lt;td&gt;DB, Redis, PVC, 백업 대상 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ryuwon-app&lt;/td&gt;
&lt;td&gt;2 OCPU / 10GB&lt;/td&gt;
&lt;td&gt;public ingress, 웹앱, worker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 data 노드에 메모리를 더 주는 것도 고민했다. DB를 많이 올릴 거라면 그게 더 자연스러울 수 있지만, 지금은 DB보다 클러스터 운영 도구와 GitOps 흐름을 먼저 안정화하는 게 우선이라고 봐서 일단 core에 8GB를 줬다. 실제 메모리 사용량을 보고 나면 이 배치는 다시 바뀔 수 있고, 홈랩을 처음부터 정답처럼 설계하려고 하기보다 지금 이해할 수 있는 구조로 시작한 뒤 관찰하면서 바꾸는 쪽이 나에게는 더 맞다고 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1499&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNmVYT/dJMcacQyQnp/SNjOvVJtcgwGY0BOxnro8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNmVYT/dJMcacQyQnp/SNjOvVJtcgwGY0BOxnro8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNmVYT/dJMcacQyQnp/SNjOvVJtcgwGY0BOxnro8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNmVYT%2FdJMcacQyQnp%2FSNjOvVJtcgwGY0BOxnro8k%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;619&quot; height=&quot;113&quot; data-origin-width=&quot;1499&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크도 처음부터 크게 열지 않으려고 해서, 기본 방향은 ryuwon-app만 외부 진입점으로 두고 나머지 노드는 내부 관리용으로 두는 방식이다.&lt;/p&gt;
&lt;pre class=&quot;clean&quot;&gt;&lt;code&gt;Internet
  -&amp;gt; ryuwon-app
  -&amp;gt; Ingress / Gateway
  -&amp;gt; internal services
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷에서 들어오는 80/443 요청은 ryuwon-app의 Ingress나 Gateway를 지나 내부 서비스로 전달한다. ryuwon-core와 ryuwon-data는 외부 HTTP를 열지 않는 쪽으로 잡았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고했던 &lt;a href=&quot;https://github.com/pmh-only/lab&quot;&gt;PMH 홈랩&lt;/a&gt;처럼 세 노드를 모두 public edge로 쓰는 구성도 있지만, 현재는 내 IP에서만 접근하도록 제한한 다음 단순하게 시작하고 필요해지면 그때 확장하는 쪽이 낫다고 판단했다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&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;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;VCN, Subnet, NSG를 어떻게 잡았는지&lt;/li&gt;
&lt;li&gt;k3s server와 agent를 어떻게 붙였는지&lt;/li&gt;
&lt;li&gt;Argo CD로 DevOps&lt;/li&gt;
&lt;li&gt;Ingress와 TLS를 붙이는 과정&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zKvAq/dJMcag6vvdv/AzWFsTNyRtpEvsG0ey2pBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zKvAq/dJMcag6vvdv/AzWFsTNyRtpEvsG0ey2pBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zKvAq/dJMcag6vvdv/AzWFsTNyRtpEvsG0ey2pBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzKvAq%2FdJMcag6vvdv%2FAzWFsTNyRtpEvsG0ey2pBK%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;633&quot; height=&quot;153&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제는 계정별 EC2에 흩뿌려두는 방식에서 탈출하고 싶다.. 사실 얼마전에 프리티어 만료된지 모르고 그대로 두다가 금액이 꽤 나와서... 이번에는 흩어진 배포 환경을 정리하며 유지보수를 편하게 할 수 있는 환경을 만들어보려고 한다.&lt;/p&gt;</description>
      <category>Infra &amp;amp; DevOps</category>
      <category>K3S</category>
      <category>OCI</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/47</guid>
      <comments>https://ryuwon-it.tistory.com/47#entry47comment</comments>
      <pubDate>Fri, 8 May 2026 14:32:33 +0900</pubDate>
    </item>
    <item>
      <title>하루를 아끼는 체크리스트</title>
      <link>https://ryuwon-it.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 이런 경험이 한 번쯤 있을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명히 코드는 맞는 것 같고, GPT, 구글, 스택오버플로우도 뒤졌는데 해결이 안되는 경우. 그러다 몇 시간 뒤에 알고 보면 너무 단순한 이유였던 경우.&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;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;p data-ke-size=&quot;size16&quot;&gt;개발도 비슷한 것 같다. 단 한 줄의 점검이 몇 시간의 삽질을 막아줄 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;막막할 때 꺼내볼 체크리스트&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 운영체제를 재시작해봤는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각보다 재시작 한 번으로 해결되는 경우가 생각보다 많다. (서버는 예외입니다 허허)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트가 이미 사용 중이라는 오류, 환경변수를 분명히 설정했는데 인식이 안 되는 경우, 데몬이 떠 있어야 하는데 안 떠 있는 경우 등이 대표적이다. IDE나 터미널을 껐다 켜는 것만으로도 해결되는 경우도 있다. 원인을 찾기 전에 재시작부터 한 번 해보는 게 오히려 빠를 때가 있다.&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;2. 지금 보고 있는 코드와 실제 실행 중인 코드가 같은가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빌드를 안 했거나 배포가 안 됐거나 다른 서버를 보고 있는 경우.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 서버 환경에서는 코드를 수정하고 저장했더라도 빌드&amp;middot;배포 과정을 거치지 않으면 변경사항이 반영되지 않는다.&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;h3 data-ke-size=&quot;size23&quot;&gt;3. 환경 설정 파일에 한글이 들어가 있지는 않은가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 한글을 제대로 처리하지 못하는 프로그램들이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.yml이나 .env 같은 설정 파일에 한글 주석이나 경로가 포함되어 있으면, 일부 환경에서 파일 자체를 읽지 못하거나 파싱 오류가 발생할 수 있다. 특히 Windows 환경에서 개발하다가 Linux 서버에 올릴 때 이 문제가 불거지는 경우가 있다. 경로, 파일명, 환경변수 키 등 주요 네이밍에는 처음부터 한글을 쓰지 않는 게 나중에 편하다.&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;4. 대소문자 또는 공백 문자 문제는 아닌가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;userId와 userid는 다르고, &quot;test&quot;와 &quot;test &quot;도 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON 키나 API 파라미터를 다룰 때, 헤더 이름이나 쿼리 파라미터를 직접 입력할 때 이 실수가 자주 발생한다. 특히 공백 문자는 눈으로 보면 구분이 안 되기 때문에 더 찾기 어렵다. Postman이나 curl로 직접 요청을 보내보면서 값을 하나씩 확인하거나, 코드에서 값을 출력해 길이까지 같이 찍어보면 빠르게 잡을 수 있다. DB 컬럼명이나 테이블명도 대소문자를 구분하는 DB가 있으니 주의가 필요하다.&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;5. OS, 언어 버전, 라이브러리, 프레임워크 버전이 서로 맞는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 버전에는 특정 함수가 없거나, 동작이 다를 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Java 17에서 되는 문법이 Java 11에서는 안 된다거나, 라이브러리 메이저 버전이 올라가면서 기존 API가 deprecated되거나 제거되는 경우가 있다. 로컬에서는 잘 되는데 서버에서만 안 된다면 이 부분을 가장 먼저 의심해보는 편이다. java -version, node -v, pip show 같은 명령어로 환경을 빠르게 확인할 수 있다. 버전은 프로젝트 시작 시점에 팀원 간에 맞춰두고, .nvmrc나 .java-version 같은 파일로 명시해두면 나중에 훨씬 수월하다.&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;6. 네트워크 연결은 정상인가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 문제가 아니라 네트워크 문제인 경우, 코드를 아무리 봐도 원인을 찾기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방화벽 규칙, 보안 그룹 설정, VPN 연결 여부, 인바운드/아웃바운드 포트 허용 여부 등을 확인해볼 필요가 있다. AWS를 쓴다면 EC2 보안 그룹이나 RDS 인바운드 규칙이 막혀 있는 경우가 꽤 있다. ping, telnet, curl 명령어로 대상 호스트와 포트까지 연결이 되는지 먼저 확인해보는 습관이 도움이 된다. 연결 자체가 안 된다면 코드보다 인프라 설정을 먼저 살펴봐야 한다.&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;7. 캐릭터 인코딩 문제는 아닌가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한글이 깨지거나, 특수문자가 이상하게 표시된다면 인코딩 문제일 가능성이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능하면 모든 구간을 UTF-8로 통일해두는 걸 권장한다. WAS, DB 커넥션 설정, IDE 파일 인코딩, 편집기 설정까지 전부. MySQL이라면 character_set_server=utf8mb4로 설정하고, JDBC URL에도 characterEncoding=UTF-8을 명시하는 식이다. 한 군데라도 인코딩이 다르면 특정 환경에서만 깨지는 현상이 발생하고, 어디서 깨지는지 추적하는 게 꽤 번거로워진다. 프로젝트 초반에 한 번 쭉 맞춰두는 게 가장 좋다.&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;8. 해당 시스템의 로그를 확인했는가?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 작성한 코드만 실행되는 게 아니다. 프레임워크, 미들웨어, 인프라도 전부 로그를 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 메시지가 있다면 첫 줄만 보지 말고 스택 트레이스 전체를 읽어보는 게 좋다. 실제 원인은 중간이나 아래쪽에 있는 경우도 많다. Spring이라면 Caused by: 이하를 주의 깊게 보고, 서버 로그나 DB 슬로우 쿼리 로그, nginx 접근 로그 등 내가 직접 작성하지 않은 시스템의 로그도 같이 확인해보면 원인을 찾는 시간이 많이 줄어든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;</description>
      <category>Writing</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/46</guid>
      <comments>https://ryuwon-it.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 16 Apr 2026 09:29:22 +0900</pubDate>
    </item>
    <item>
      <title>단일 Repository에서 세 개의 서비스를 어떻게 관리할 것인가</title>
      <link>https://ryuwon-it.tistory.com/45</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;SSAFY 프로젝트에서 AI, Backend, Frontend를 한 repo에 담으며 고민한 것들&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSAFY 프로젝트를 시작하면서 우리 팀은 AI, Backend, Frontend 세 개의 서비스를 동시에 개발해야 했다. 원래라면 각각을 독립된 레포지토리로 관리하는 게 맞겠지만, SSAFY에서는 단일 레포지토리, 단일 인스턴스로 운영해야 한다는 제약이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 폴더만 나눠서 넣으면 되는 거 아닐까라고 생각했지만, 막상 고민을 시작하니 생각보다 복잡한 문제들이 보이기 시작했다. 로컬에서 개발할 때는 편해야 하고, 나중에 배포할 때도 간단해야 하고, CI/CD 붙일 때도 문제없어야 하고... 심지어 나중에 환경 분리(dev/prod)나 성능 개선도 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떻게 구조를 짜야 할까?에 대한 고민에 빠지게 되었다&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고민했던 방법들&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;A. 서비스별로 Docker Compose를 각각 만들기&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;ai/docker-compose.yml
backend/docker-compose.yml
frontend/docker-compose.yml
root-compose.yml (선택사항)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;/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;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;실제로 운영하거나 배포할 때 여러 개의 compose를 동시에 관리해야 해서 복잡하다&lt;/li&gt;
&lt;li&gt;network 설정이나 환경변수, 이미지 이름 같은 게 충돌날 수 있다&lt;/li&gt;
&lt;li&gt;CI/CD 파이프라인 짜기가 복잡하고, 롤백도 어렵다&lt;/li&gt;
&lt;li&gt;nginx 같은 proxy를 여러 곳에서 중복으로 설정해야 할 수 있다&lt;/li&gt;
&lt;/ul&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B. 폴더는 나누되 Docker Compose는 하나만 + Profile로 제어&lt;/h3&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;armasm&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;ai/
backend/
frontend/
docker-compose.yml (하나만)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영할 때는 하나의 orchestration으로 관리하고, 개발할 때는 profile로 필요한 서비스만 띄우자는 아이디어였다.&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;운영하고 배포하기가 정말 간단하다&lt;/li&gt;
&lt;li&gt;CI/CD 파이프라인을 하나만 만들면 된다&lt;/li&gt;
&lt;li&gt;나중에 스케일링할 때도 유리하다&lt;/li&gt;
&lt;li&gt;Compose에서 Kubernetes로 전환할 때도 자연스럽다&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;개발할 때 매번 --profile 옵션을 붙여야 해서 조금 불편하다&lt;/li&gt;
&lt;li&gt;개발 루프의 유연성이 조금 떨어진다&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C. 서비스별 Compose + Root Compose를 Makefile로 통제&lt;/h3&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;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;개발할 때는 make backend, make ai 이런 식으로 편하게 실행할 수 있다&lt;/li&gt;
&lt;li&gt;운영할 때는 단일 orchestrator를 유지할 수 있다&lt;/li&gt;
&lt;li&gt;여러 서비스를 한 번에 빌드하는 것도 쉽게 제어할 수 있다&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;Makefile을 관리하는 비용이 추가로 든다&lt;/li&gt;
&lt;li&gt;Makefile이 점점 복잡해지면 기술 부채가 될 수 있다&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;D. 아예 Repository를 여러 개로 분리&lt;/h3&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;/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;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;/li&gt;
&lt;li&gt;팀 간 컨텍스트 공유가 어렵다&lt;/li&gt;
&lt;li&gt;관리 비용이 너무 많이 든다&lt;/li&gt;
&lt;li&gt;그리고 애초에 SSAFY 제약사항이 단일 repo였다&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금도 많이 고민중이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마음은 C쪽으로 기울고 있어서, C로 선택할꺼같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그밖에도 밑에 사항들을 고려해야할 것 같다&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;앞으로 고려할 것들&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Staging 환경을 도입할 수도 있다&lt;/li&gt;
&lt;li&gt;Terraform 같은 걸로 인프라를 선언적으로 관리할지 고민 중이다&lt;/li&gt;
&lt;li&gt;Observability (로그, 메트릭, 트레이싱) 관련해서 추가 작업이 필요할 수 있다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약 이 부분이 들어간다면 observability/ 또는 monitoring/ 폴더를 따로 만들어서 관리할 생각이다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;생각보다 많은 컨테이너가 들어갈 예정이라 k3s 도입도 고려 중이다. 그렇게 되면 구조가 전반적으로 또 바뀔 수도...&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Project</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/45</guid>
      <comments>https://ryuwon-it.tistory.com/45#entry45comment</comments>
      <pubDate>Sun, 18 Jan 2026 23:54:58 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Profile을 활용한 환경 구축</title>
      <link>https://ryuwon-it.tistory.com/44</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;뒤죽박죽이 되어버린 개발 환경을 정리하며&lt;/blockquote&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;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 우리 서비스는 운영 환경에서 RDS, MongoDB Atlas, S3를 모두 사용하고 있다. 문제는 테스트 서버였다. 비용 문제로 EC2 한 대로 버티다 보니 docker-compose를 이용해 DB와 Redis 같은 것들을 함께 띄워야 했는데, 여기서부터 문제들이 하나둘씩 터지기 시작했다.&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;문제 1. 환경 설정이 뒤죽박죽&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기 .env 설정도 제대로 해두지 않은 상태에서 application-prod.yml에 모든 정보를 몰아넣은 구조였다. 이러다 보니 로컬에서 개발하다가 테스트 데이터를 넣는 과정이 귀찮고 시간이 걸리다 보니 그냥 운영 DB 붙어서 테스트를 하는 등 위험한 상황이 발생했다.&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;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 2. Docker Compose의 애매한 용도&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;compose 파일은 분명 존재했지만, 사실상 테스트 서버용으로만 쓰이고 있었다. 테스트 환경은 사실상 비용적 문제로 한개의 인스턴스로만 구성되어있고 별도로 rds, cache, mongoDB가 따로 없기에 docker compose로 세팅해둬었기에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬 환경에서의 테스트 환경은 각 개발자마다 달라서 docker-compose 설정도 따로 필요한 상황이었다. 일관된 개발 환경이 없다는 것은 팀 협업에 있어서 큰 걸림돌이었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;어떻게 해결할 것인가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: Spring 설정 레이어링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring의 프로파일 기능을 활용해 &lt;b&gt;&quot;환경 프로파일 + 기능 프로파일&quot;&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;local&lt;/b&gt;: 개발자 PC에서 실행하는 환경&lt;/li&gt;
&lt;li&gt;&lt;b&gt;staging&lt;/b&gt;: 테스트 서버(단일 인스턴스) 배포 환경 (기존 test 서버 역할)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;prod&lt;/b&gt;: 실제 운영 배포 환경&lt;/li&gt;
&lt;li&gt;&lt;b&gt;test&lt;/b&gt;: JUnit/CI 테스트 전용 (실제 서버 환경이 아님)&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;&lt;b&gt;observability&lt;/b&gt;: actuator, metrics, log 포맷 등 관측성 관련 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;debug&lt;/b&gt;: 로깅 상세화, SQL 로그/추적 등 개발 편의 기능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;swagger&lt;/b&gt;: API 문서 UI 등 개발/검증용 기능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;mock&lt;/b&gt;: 외부 연동을 대체하는 mock endpoint 등&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나눈 프로파일들은 Spring의 프로파일 그룹 기능을 통해 조합할 수 있다. 예를 들어 SPRING_PROFILES_ACTIVE=staging만 지정해도 내부적으로 staging, observability가 함께 활성화되도록 구성하는 것이다.&lt;/p&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;yaml&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;java&quot; style=&quot;color: #abb2bf; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring:
  profiles:
    group:
      local: local,debug,swagger
      staging: staging,observability
      prod: prod,observability&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: Docker Compose Overlay 방식 도입&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 환경에서 겹치는 설정이 많다는 점을 고려해 Overlay 방식으로 compose 파일을 구성하기로 했다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 구성:&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;css&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;docker-compose.yml              # 공통 설정
docker-compose.local.yml        # 로컬 개발용 추가 설정
docker-compose.staging.yml      # 테스트 서버용 추가 설정&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 Overlay를 사용하는가? 사실상 compose에서 겹치는 부분이 많이 존재하고, 거기에서 추가되는 것은 오버레이로 덮으면 되기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1768747571528&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# 로컬 환경
docker compose -f compose.yml -f compose.local.yml up -d --build

# staging 환경
docker compose -f compose.yml -f compose.staging.yml up -d&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서는 DB, MongoDB, Redis 등을 모두 컨테이너로 띄우고, staging에서는 운영과 가까운 환경을 재현할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&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;local&lt;/b&gt;: H2 또는 로컬 Docker 컨테이너 사용 (H2는 고려 중)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;staging&lt;/b&gt;: EC2 한 대에서 애플리케이션과 DB 컨테이너를 함께 실행. &lt;b&gt;운영 자원 직접 연결 금지..!&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;prod&lt;/b&gt;: RDS, MongoDB Atlas, S3 같은 관리형 서비스 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;고려했던 다른 방법들은?&lt;/h2&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;A. 환경별 Compose 파일 완전 분리&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 환경마다 완전히 독립된 compose 파일을 만드는 방법이다. 직관적이긴 하지만 중복과 누락이 쉽게 발생하고 유지보수가 어렵다는 단점이 있었다. 현재 우리 팀의 규모와 구조에서는 부적합하다고 판단했다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;B. Spring 설정을 하나의 파일에서 조건문으로 처리&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일 수는 줄어들지만 가독성이 떨어지고 운영 설정이 섞일 확률이 증가한다. 운영 안정성 측면에서 좋지 않은 선택이라 채택하지 않았다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;C. API 테스트 시 운영 자원 계속 공유&lt;/h3&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&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;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;변경할 시 장단점&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&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;/li&gt;
&lt;li&gt;테스트 서버가 EC2 한 대라도 compose로 전체 검증이 가능&lt;/li&gt;
&lt;li&gt;디버그, 스웨거 같은 기능을 환경별로 쉽게 제어&lt;/li&gt;
&lt;li&gt;나중에 CI를 붙일 때 충돌이 발생하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&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;/li&gt;
&lt;li&gt;테스트 서버 사양이 부족할 수 있다 (나중에 로깅 스택도 띄워야 함 흑흑)&lt;/li&gt;
&lt;li&gt;과거 502 에러 때처럼 또 걱정된다...&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최종 파일 구조&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;stylus&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;src/main/resources/
├── application.yml
├── application-local.yml
├── application-staging.yml
├── application-prod.yml
├── application-test.yml
├── application-observability.yml
└── application-swagger.yml

src/main/resources/logback/
├── logback-spring.xml
├── logback-local.xml
├── logback-staging.xml
└── logback-prod.xml

프로젝트 루트/
├── docker-compose.yml
├── docker-compose.local.yml
├── docker-compose.staging.yml
├── .env.local
└── .env.staging&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;&lt;hr data-ke-style=&quot;style1&quot; /&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;적용 순서&lt;/h2&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 파일 구조 잡기&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 설정을 분리해서 환경별 yml 파일을 만든다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 프로파일 그룹 설정&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;yaml&lt;/div&gt;
&lt;div&gt;
&lt;pre class=&quot;dts&quot; style=&quot;color: #abb2bf; text-align: left;&quot;&gt;&lt;code&gt;spring:
  profiles:
    group:
      local: local,debug,swagger
      staging: staging,observability
      prod: prod,observability&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 테스트 서버 전환&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기존 test 프로파일 &amp;rarr; staging으로 변경&lt;/li&gt;
&lt;li&gt;compose.staging.yml 기반으로 배포&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 안전장치&lt;/b&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;prod에서 환경변수를 넣지 않으면 앱이 시작되지 않게 설정&lt;/li&gt;
&lt;li&gt;staging에서 prod 엔드포인트 접근 차단 (보안그룹, DB 권한)&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;롤백 계획&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 생기면 SPRING_PROFILES_ACTIVE 환경변수와 compose 파일을 기존 방식으로 되돌리면 된다. 배포 파이프라인에서 기존 설정을 유지한 채로 신규 구성을 병행할 수 있게 해둘 예정이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아직 결정하지 못한 것들&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Grafana/Loki 모니터링 스택을 staging에 상시로 띄울지, 필요할 때만 띄울지&lt;/li&gt;
&lt;li&gt;Logback 수집도 local, test, prod에 따라 달라지는데 어떻게 분류해둘지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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://beoks.tistory.com/entry/Docker-Compose-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EC%84%A4%EC%A0%95-%EC%9D%BC%EB%8C%80%EA%B8%B0local-dev-prod&quot;&gt;Docker-Compose 개발 환경 설정 일대기(local, dev, prod)&lt;/a&gt;&lt;/p&gt;</description>
      <category>Framework &amp;amp; Library/Spring</category>
      <category>Spring Profile</category>
      <category>찐빵</category>
      <category>프로젝트</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/44</guid>
      <comments>https://ryuwon-it.tistory.com/44#entry44comment</comments>
      <pubDate>Sun, 18 Jan 2026 23:47:57 +0900</pubDate>
    </item>
    <item>
      <title>[KOSPI FGI] 앞으로의 기능과 발전 방향</title>
      <link>https://ryuwon-it.tistory.com/43</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재의 KOSPI 공포&amp;middot;탐욕 지수는 &amp;ldquo;지금 시장이 과열인지, 위축인지&amp;rdquo;를 한눈에 판단할 수 있는 1차 목표를 어느 정도 달성했다고 생각한다. 하지만 이 지표가 더 의미 있으려면, &lt;b&gt;지속적으로 검증되고 확장되는 구조&lt;/b&gt;를 가져야 한다고 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;307&quot; data-start=&quot;285&quot; data-ke-size=&quot;size23&quot;&gt;1. 서비스 배포 및 접근성 개선&lt;/h3&gt;
&lt;p data-end=&quot;433&quot; data-start=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;우선 가장 가까운 목표는 &lt;b&gt;정식 배포&lt;/b&gt;다..&lt;br /&gt;사실 개발자의 관점으로 안정적으로 배포하고 싶어서 좀 더 심열을 기울이고 있어 시간이 소모되지만.. 벌써 해당 키워드로 블로그에 유입될 정도로 사용자들이 어느정도 존재할꺼란거 보고 기대가 되서 빨리 배포하고 싶기도..&lt;/p&gt;
&lt;p data-end=&quot;433&quot; data-start=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;433&quot; data-start=&quot;309&quot; data-ke-size=&quot;size16&quot;&gt;무튼 지금까지는&amp;nbsp; 데이터를 검증하고 지표를 계산하는 데 초점을 맞췄다면, 앞으로는 일반 투자자도 부담 없이 접근할 수 있도록 안정적인 배포 환경을 구축할 예정이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;497&quot; data-start=&quot;435&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;456&quot; data-start=&quot;435&quot;&gt;OCI 에서 고자원 리소스 좀 들여서 생성 예정&lt;/li&gt;
&lt;li data-end=&quot;456&quot; data-start=&quot;435&quot;&gt;무중단 배포 및 CI/CD 적용&lt;/li&gt;
&lt;li data-end=&quot;456&quot; data-start=&quot;435&quot;&gt;로드밸런싱을 통한 장애 복구 안정성 확보&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;559&quot; data-start=&quot;499&quot; data-ke-size=&quot;size16&quot;&gt;공포탐욕 지수는 자주 봐야 의미가 있는 지표이기 때문에, 안정성도 무엇보다 중요하다고 생각한다.&lt;/p&gt;
&lt;p data-end=&quot;559&quot; data-start=&quot;499&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;591&quot; data-start=&quot;566&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;591&quot; data-start=&quot;566&quot; data-ke-size=&quot;size23&quot;&gt;2. KOSDAQ 공포&amp;middot;탐욕 지수 확장&lt;/h3&gt;
&lt;p data-end=&quot;697&quot; data-start=&quot;593&quot; data-ke-size=&quot;size16&quot;&gt;KOSPI만으로 국내 시장의 심리를 모두 설명하기에는 한계가 있다.&lt;br /&gt;특히 개인 투자자 비중이 높고 변동성이 큰 &lt;b&gt;KOSDAQ 시장&lt;/b&gt;까지 후후..&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;796&quot; data-start=&quot;713&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;736&quot; data-start=&quot;713&quot;&gt;KOSDAQ 전용 공포&amp;middot;탐욕 지수 산출&lt;/li&gt;
&lt;li data-end=&quot;762&quot; data-start=&quot;737&quot;&gt;KOSPI &amp;harr; KOSDAQ 심리 괴리 비교&lt;/li&gt;
&lt;li data-end=&quot;796&quot; data-start=&quot;763&quot;&gt;대형주 중심 시장과 성장주 중심 시장의 심리 차이 시각화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;898&quot; data-start=&quot;798&quot; data-ke-size=&quot;size16&quot;&gt;를 계획하고 있다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-end=&quot;929&quot; data-start=&quot;905&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-end=&quot;929&quot; data-start=&quot;905&quot; data-ke-size=&quot;size23&quot;&gt;3. 지표 계산식에 대한 학문적 검증&lt;/h3&gt;
&lt;p data-end=&quot;1056&quot; data-start=&quot;931&quot; data-ke-size=&quot;size16&quot;&gt;개인 프로젝트이긴 하지만, &lt;b&gt;지표의 신뢰성&lt;/b&gt;은 무엇보다 중요하다.&lt;br /&gt;그래서 공포탐욕 지수를 구성하는 각 지표의 정규화 방식, 가중치, 결합 로직이 과연 합리적인지에 대해 &lt;b&gt;경제학적 관점의 검증&lt;/b&gt;을 받아볼 계획이다...! CNN을 참고했다 하지만 내가 틀린 것일수도 있기에 ㅠㅠ&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1090&quot; data-start=&quot;1058&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1070&quot; data-start=&quot;1058&quot;&gt;모교 경제학 교수님&lt;/li&gt;
&lt;li data-end=&quot;1090&quot; data-start=&quot;1071&quot;&gt;타 대학 금융&amp;middot;경제 전공 교수진&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1188&quot; data-start=&quot;1092&quot; data-ke-size=&quot;size16&quot;&gt;에게 실제 계산식과 결과를 공유하고 이에 대한 피드백을 받을 예정이다.&lt;/p&gt;
&lt;p data-end=&quot;1243&quot; data-start=&quot;1190&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;1243&quot; data-start=&quot;1190&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1243&quot; data-start=&quot;1190&quot; data-ke-size=&quot;size16&quot;&gt;여러모로 내 스스로 도전하는 개인프로젝트기도 하고, 이런 서비스가 있으면 좋겠다라고 스스로 많이 생각했기에 신경쓰면서 서비스를 가꿔나가야겠다.&lt;/p&gt;</description>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/43</guid>
      <comments>https://ryuwon-it.tistory.com/43#entry43comment</comments>
      <pubDate>Sat, 13 Dec 2025 23:53:37 +0900</pubDate>
    </item>
    <item>
      <title>Response Body에 HTTP Status Code를 담아도 괜찮을까?</title>
      <link>https://ryuwon-it.tistory.com/42</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API를 설계하다 보면 아래와 같은 구조로 설계하는 경우가 많았다&lt;/p&gt;
&lt;pre id=&quot;code_1765635986583&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;code&quot;: &quot;AUTH_002&quot;,
  &quot;message&quot;: &quot;만료된 토큰입니다.&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문득 Response를 설계하다가 생각이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;이미 HTTP Status Code가 있는데, 굳이 body에 또 담아야 할까?&amp;rdquo;&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;452&quot; data-start=&quot;365&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;392&quot; data-start=&quot;365&quot;&gt;HTTP Status Code의 본래 역할&lt;/li&gt;
&lt;li data-end=&quot;431&quot; data-start=&quot;393&quot;&gt;Body에 status code를 중복해서 담는 패턴의 장단점&lt;/li&gt;
&lt;li data-end=&quot;452&quot; data-start=&quot;432&quot;&gt;프로젝트에서의 합리적인 선택 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;473&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;을 중심으로 이 질문을 정리해볼까 한다.&lt;/p&gt;
&lt;p data-end=&quot;473&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;473&quot; data-start=&quot;454&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;537&quot; data-start=&quot;499&quot; data-ke-size=&quot;size26&quot;&gt;1. HTTP Status Code는 왜 &amp;lsquo;고수준&amp;rsquo;이라고 할까?&lt;/h2&gt;
&lt;p data-end=&quot;595&quot; data-start=&quot;539&quot; data-ke-size=&quot;size16&quot;&gt;HTTP Status Code는 &lt;b&gt;의도적으로 고수준(high-level)&lt;/b&gt; 으로 설계된 표준이다.&lt;/p&gt;
&lt;p data-end=&quot;629&quot; data-start=&quot;597&quot; data-ke-size=&quot;size16&quot;&gt;HTTP Status Code가 표현하는 것은 딱 하나다&lt;/p&gt;
&lt;blockquote data-end=&quot;663&quot; data-start=&quot;631&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;663&quot; data-start=&quot;633&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;이 HTTP 요청 자체가 어떻게 처리되었는가&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;746&quot; data-start=&quot;665&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;691&quot; data-start=&quot;665&quot;&gt;요청이 정상적으로 처리되었는지 (2xx)&lt;/li&gt;
&lt;li data-end=&quot;718&quot; data-start=&quot;692&quot;&gt;클라이언트 요청이 잘못되었는지 (4xx)&lt;/li&gt;
&lt;li data-end=&quot;746&quot; data-start=&quot;719&quot;&gt;서버 내부에서 문제가 발생했는지 (5xx)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;803&quot; data-start=&quot;748&quot; data-ke-size=&quot;size16&quot;&gt;즉, HTTP Status Code는 &lt;b&gt;비즈니스 맥락이나 세부 오류 원인에는 관심이 없다&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;803&quot; data-start=&quot;748&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;/b&gt;어떻게보면 요청 자체가 어떻게 처리됬는지만 신경쓴다는 것이다.&lt;/p&gt;
&lt;p data-end=&quot;842&quot; data-start=&quot;805&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;842&quot; data-start=&quot;805&quot; data-ke-size=&quot;size16&quot;&gt;이러한 설계 덕분에 클라이언트는 다음을 빠르게 판단할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;903&quot; data-start=&quot;844&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;862&quot; data-start=&quot;844&quot;&gt;이 요청을 재시도해야 하는가?&lt;/li&gt;
&lt;li data-end=&quot;881&quot; data-start=&quot;863&quot;&gt;인증 로직으로 보내야 하는가?&lt;/li&gt;
&lt;li data-end=&quot;903&quot; data-start=&quot;882&quot;&gt;사용자에게 에러를 보여줘야 하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;974&quot; data-start=&quot;905&quot; data-ke-size=&quot;size16&quot;&gt;그래서 HTTP Status Code는&lt;br /&gt;&lt;b&gt;서버의 응답을 어떻게 핸들링할지 결정하기 위한 표준 신호&lt;/b&gt;&amp;nbsp;로 사용된다.&lt;/p&gt;
&lt;p data-end=&quot;974&quot; data-start=&quot;905&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;974&quot; data-start=&quot;905&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1030&quot; data-start=&quot;981&quot; data-ke-size=&quot;size26&quot;&gt;2. Response Body에 상태 코드를 담는 것은 RESTful 하지 못한가?&lt;/h2&gt;
&lt;p data-end=&quot;1075&quot; data-start=&quot;1032&quot; data-ke-size=&quot;size16&quot;&gt;REST에서 흔히 말하는 RESTful 하다 는 의미는 다음과 같다고 생각한다&lt;/p&gt;
&lt;blockquote data-end=&quot;1148&quot; data-start=&quot;1077&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1148&quot; data-start=&quot;1079&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;요청과 응답만 보아도 해당 endpoint의 의미를 이해할 수 있어야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1160&quot; data-start=&quot;1150&quot; data-ke-size=&quot;size16&quot;&gt;이 관점에서 보면,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1269&quot; data-start=&quot;1162&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1204&quot; data-start=&quot;1162&quot;&gt;HTTP Status Code는 이미 표준 방식으로 상태를 표현하고 있고&lt;/li&gt;
&lt;li data-end=&quot;1249&quot; data-start=&quot;1205&quot;&gt;body에 &lt;b&gt;동일한 상태 코드(200, 400 등)&lt;/b&gt; 를 다시 담는 것은&lt;/li&gt;
&lt;li data-end=&quot;1269&quot; data-start=&quot;1250&quot;&gt;의미적으로 &lt;b&gt;중복&lt;/b&gt;이 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;`HTTP/1.1 400 Bad Request &lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;status&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;400&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;message&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;Bad Request&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;1436&quot; data-start=&quot;1370&quot; data-ke-size=&quot;size16&quot;&gt;이 구조가 잘못된 것은 아니다.&lt;br /&gt;다만 이미 HTTP 레벨에서 충분히 전달된 정보를 다시 한 번 반복하는 셈이다.&lt;/p&gt;
&lt;p data-end=&quot;1457&quot; data-start=&quot;1438&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1457&quot; data-start=&quot;1438&quot; data-ke-size=&quot;size16&quot;&gt;그래서 많은 경우 이렇게 느껴진다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1475&quot; data-start=&quot;1459&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1475&quot; data-start=&quot;1461&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;굳이 또 담아야 할까?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1530&quot; data-start=&quot;1477&quot; data-ke-size=&quot;size16&quot;&gt;결론부터 말하면,&lt;br /&gt;&lt;b&gt;담는 것 자체가 문제는 아니지만, 목적 없이 담는다면 의미가 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1530&quot; data-start=&quot;1477&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-end=&quot;1530&quot; data-start=&quot;1477&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;1573&quot; data-start=&quot;1537&quot; data-ke-size=&quot;size26&quot;&gt;3. 그럼 Body에 상태 코드를 담는 것을 지양해야 할까?&lt;/h2&gt;
&lt;p data-end=&quot;1593&quot; data-start=&quot;1575&quot; data-ke-size=&quot;size16&quot;&gt;여기서 중요한 전제가 하나 있다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1641&quot; data-start=&quot;1595&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1641&quot; data-start=&quot;1597&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HTTP Status Code 하나만으로는 모든 에러를 설명할 수 없다.&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;1670&quot; data-start=&quot;1643&quot; data-ke-size=&quot;size16&quot;&gt;대규모 서비스를 운영할수록 이 한계는 명확해진다.&lt;/p&gt;
&lt;p data-end=&quot;1717&quot; data-start=&quot;1672&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 400 Bad Request 는 다음 상황을 모두 포함할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1771&quot; data-start=&quot;1719&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1731&quot; data-start=&quot;1719&quot;&gt;필수 파라미터 누락&lt;/li&gt;
&lt;li data-end=&quot;1742&quot; data-start=&quot;1732&quot;&gt;값의 형식 오류&lt;/li&gt;
&lt;li data-end=&quot;1750&quot; data-start=&quot;1743&quot;&gt;정책 위반&lt;/li&gt;
&lt;li data-end=&quot;1771&quot; data-start=&quot;1751&quot;&gt;현재 상태에서 수행 불가능한 요청&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;1839&quot; data-start=&quot;1773&quot; data-ke-size=&quot;size16&quot;&gt;HTTP Status Code는 이를 구분하지 않는다.&lt;br /&gt;왜냐하면? 애초에 &lt;b&gt;그 역할을 맡고 있지 않기 때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;1871&quot; data-start=&quot;1841&quot; data-ke-size=&quot;size16&quot;&gt;그래서 실제 서비스에서는 다음과 같은 방식이 등장한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1953&quot; data-start=&quot;1873&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1911&quot; data-start=&quot;1873&quot;&gt;HTTP Status Code &amp;rarr; &lt;b&gt;요청 처리 결과(고수준)&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;1953&quot; data-start=&quot;1912&quot;&gt;Response Body &amp;rarr; &lt;b&gt;비즈니스 맥락의 상세 정보(저수준)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;&lt;span&gt;`HTTP/1.1 400 Bad Request &lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;code&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;INVALID_EMAIL_FORMAT&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;,&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;message&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;&quot;이메일 형식이 올바르지 않습니다.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span&gt; &lt;/span&gt;&lt;span&gt;&lt;span&gt;}`&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p data-end=&quot;2091&quot; data-start=&quot;2078&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2091&quot; data-start=&quot;2078&quot; data-ke-size=&quot;size16&quot;&gt;그렇다면 핵심은..?&lt;/p&gt;
&lt;blockquote data-end=&quot;2172&quot; data-start=&quot;2093&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;2172&quot; data-start=&quot;2095&quot; data-ke-size=&quot;size16&quot;&gt;HTTP Status Code를 body에 그대로 복사하지 않는다&lt;br /&gt;서비스가 정의한 &lt;b&gt;자체 에러 코드&lt;/b&gt;를 body에 담는다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2210&quot; data-start=&quot;2179&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;2210&quot; data-start=&quot;2179&quot; data-ke-size=&quot;size26&quot;&gt;4. 그래서 어떤 선택이 RESTful한 설계일까?&lt;/h2&gt;
&lt;p data-end=&quot;2226&quot; data-start=&quot;2212&quot; data-ke-size=&quot;size16&quot;&gt;정리하면 기준은 명확하다.&lt;/p&gt;
&lt;p data-end=&quot;2226&quot; data-start=&quot;2212&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;비효율적인 경우&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2353&quot; data-start=&quot;2243&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2289&quot; data-start=&quot;2243&quot;&gt;HTTP Status Code와 &lt;b&gt;완전히 동일한 값&lt;/b&gt;을 body에 중복 전달&lt;/li&gt;
&lt;li data-end=&quot;2329&quot; data-start=&quot;2290&quot;&gt;body의 상태 코드가 HTTP Status Code의 의미를 침범&lt;/li&gt;
&lt;li data-end=&quot;2353&quot; data-start=&quot;2330&quot;&gt;코드에 대한 규칙이나 문서가 없는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2368&quot; data-start=&quot;2355&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-end=&quot;2368&quot; data-start=&quot;2355&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;권장되는 경우&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2473&quot; data-start=&quot;2369&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2399&quot; data-start=&quot;2369&quot;&gt;HTTP Status Code는 요청의 결과만 표현&lt;/li&gt;
&lt;li data-end=&quot;2445&quot; data-start=&quot;2400&quot;&gt;Response Body에는 &lt;b&gt;서비스에서 정의한 세분화된 에러 코드&lt;/b&gt; 전달&lt;/li&gt;
&lt;li data-end=&quot;2473&quot; data-start=&quot;2446&quot;&gt;API 문서에 해당 코드의 의미를 명확히 설명&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2492&quot; data-start=&quot;2475&quot; data-ke-size=&quot;size16&quot;&gt;이 방식은 다음 장점을 가진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2578&quot; data-start=&quot;2494&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2513&quot; data-start=&quot;2494&quot;&gt;HTTP 표준의 역할을 존중한다&lt;/li&gt;
&lt;li data-end=&quot;2552&quot; data-start=&quot;2514&quot;&gt;클라이언트는 고수준/저수준 에러를 모두 안정적으로 처리할 수 있다&lt;/li&gt;
&lt;li data-end=&quot;2578&quot; data-start=&quot;2553&quot;&gt;응답이 self-descriptive 하다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2582&quot; data-start=&quot;2580&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;HTTP Status Code는 그대로 사용하고, &lt;/b&gt;&lt;b&gt;부족한 표현력은 서비스 레벨의 에러 코드로 보완하는 설계가 어느정도 맞다고 생각이 든다..!&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2687&quot; data-start=&quot;2653&quot; data-ke-size=&quot;size16&quot;&gt;이것이 현실적인 의미에서 가장 RESTful한 선택에 가깝다.&lt;/p&gt;
&lt;p data-end=&quot;2687&quot; data-start=&quot;2653&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2700&quot; data-start=&quot;2694&quot; data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-end=&quot;2700&quot; data-start=&quot;2694&quot; data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-end=&quot;2762&quot; data-start=&quot;2702&quot; data-ke-size=&quot;size16&quot;&gt;Response Body에 상태 코드를 담는 것이 &lt;b&gt;무조건 나쁘거나 비RESTful한 것은 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;2779&quot; data-start=&quot;2764&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;2779&quot; data-start=&quot;2764&quot; data-ke-size=&quot;size16&quot;&gt;다만 중요한 것은 이것이다...!!!!!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2852&quot; data-start=&quot;2781&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2791&quot; data-start=&quot;2781&quot;&gt;왜 담는가?&lt;/li&gt;
&lt;li data-end=&quot;2822&quot; data-start=&quot;2792&quot;&gt;HTTP Status Code와 무엇이 다른가?&lt;/li&gt;
&lt;li data-end=&quot;2852&quot; data-start=&quot;2823&quot;&gt;클라이언트가 그 값을 통해 무엇을 판단하는가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-end=&quot;2900&quot; data-start=&quot;2854&quot; data-ke-size=&quot;size16&quot;&gt;이 질문에 명확한 답이 없다면 그 상태 코드는 &lt;b&gt;굳이 존재할 이유가 없다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-end=&quot;2900&quot; data-start=&quot;2854&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-is-only-node=&quot;&quot; data-is-last-node=&quot;&quot; data-end=&quot;2964&quot; data-start=&quot;2902&quot; data-ke-size=&quot;size16&quot;&gt;API 설계에서 중요한 것은 형식보다도&lt;br /&gt;&lt;b&gt;역할과 책임을 명확히 나누는 것&lt;/b&gt;이라는 점을 잊지 말아야 한다.&lt;/p&gt;</description>
      <category>Knowledge</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/42</guid>
      <comments>https://ryuwon-it.tistory.com/42#entry42comment</comments>
      <pubDate>Sat, 13 Dec 2025 23:39:05 +0900</pubDate>
    </item>
    <item>
      <title>[코드트리] 2명의 도둑</title>
      <link>https://ryuwon-it.tistory.com/41</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.codetree.ai/ko/external-connection/classes/158/lectures/1475/curated-cards/challenge-two-thieves/description&quot;&gt;Codetree | Learning to Code with Confidence&lt;/a&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;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-end=&quot;519&quot; data-start=&quot;484&quot; data-ke-size=&quot;size16&quot;&gt;이 문제는 &lt;b&gt;완전탐색 + 부분 조합 최적화&lt;/b&gt; 문제로 파악했다&lt;/p&gt;
&lt;p data-end=&quot;631&quot; data-start=&quot;521&quot; data-ke-size=&quot;size16&quot;&gt;내가 처음 접근했을 때는&lt;br /&gt;두 도둑이니까 DFS를 두 번 돌려야 하는건가&lt;br /&gt;각 도둑의 선택 구간에서 부분집합을 또 만들어야 하는지? 등&lt;br /&gt;이런 식으로 고민했는데, 구현 자체는 의외로 단순했다.&lt;/p&gt;
&lt;p data-end=&quot;647&quot; data-start=&quot;633&quot; data-ke-size=&quot;size16&quot;&gt;핵심 포인트는 두 가지다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;717&quot; data-start=&quot;649&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;679&quot; data-start=&quot;649&quot;&gt;&lt;b&gt;도둑 A가 고를 수 있는 모든 구간을 탐색&lt;/b&gt;&lt;/li&gt;
&lt;li data-end=&quot;717&quot; data-start=&quot;680&quot;&gt;&lt;b&gt;도둑 B는 A 구간을 침범하지 않는 곳에서 다시 고르기&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;734&quot; data-start=&quot;719&quot; data-ke-size=&quot;size16&quot;&gt;그리고 각각의 구간에서는&lt;/p&gt;
&lt;blockquote data-end=&quot;854&quot; data-start=&quot;735&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;854&quot; data-start=&quot;737&quot; data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;M개의 물건 중 일부를 골라 총 무게 &amp;le; C 를 만족하면서 가치(무게&amp;sup2;) 최대화&amp;rdquo;&lt;br /&gt;라는 배낭문제를 풀어야 하는데,&lt;br /&gt;M이 최대 5 수준이라서 굳이 DP를 쓰지 않아도 &amp;ldquo;정렬 후 그리디&amp;rdquo;로 해결 가능했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-end=&quot;942&quot; data-start=&quot;856&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;942&quot; data-start=&quot;856&quot; data-ke-size=&quot;size16&quot;&gt;사실 정석은 &lt;b&gt;부분집합 전부 탐색(2^M)&lt;/b&gt; 인데, 내가 짠 방식처럼&lt;br /&gt;&lt;b&gt;오름차순 정렬 &amp;rarr; 작은 것부터 담기&lt;/b&gt;&lt;br /&gt;해도 충분히 정답 처리가 된다.&lt;/p&gt;
&lt;p data-end=&quot;942&quot; data-start=&quot;856&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1060&quot; data-start=&quot;944&quot; data-ke-size=&quot;size16&quot;&gt;그리고 두 도둑이 한 행에서 겹치지 않도록 하기 위해&lt;br /&gt;같은 행일 경우 y + M &amp;le; next start 조건을 강제로 뛰어넘는 로직을 넣었다.&lt;br /&gt;이게 없으면 중복 탐색이 터져서 시간이 많이 낭비된다.&lt;/p&gt;
&lt;p data-end=&quot;1060&quot; data-start=&quot;944&quot; 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;h3 data-ke-size=&quot;size23&quot;&gt;풀이&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;import java.util.*;
import java.io.*;

public class Main {
    static int N, M, C, max = Integer.MIN_VALUE;
    static int[] arr =  new int[2];
    static int[][] map;
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());
        C = Integer.parseInt(st.nextToken());
        map = new int[N][N];

        for(int x = 0; x &amp;lt; N; x++){
            st = new StringTokenizer(br.readLine());
            for(int y = 0; y &amp;lt; N; y++)
                map[x][y] = Integer.parseInt(st.nextToken());
        }

        dfs(0,0,0);
        System.out.println(max);
    }
    // 두 도둑이 집는 함수
    public static void dfs(int nx, int ny, int n) {
        // 두 도둑이니까 dfs 2번일까?
        if(n == 2) {
            int sum = 0;
            for(int i = 0; i &amp;lt; 2; i++) sum += arr[i];
            if(max &amp;lt; sum) max = sum;
            return;
        }

        // 완전 탐색으로 지정. 단, 열은 N-M까지 이동
        for(int x = nx; x &amp;lt; N; x++){
            for(int y = 0; y &amp;lt;= N-M; y++){
                if(y == 0 &amp;amp;&amp;amp; x == nx &amp;amp;&amp;amp; ny + M &amp;lt;= N-M) y = ny + M; // 같은 행일때 중복 제거
                arr[n]= calc(x, y);
                dfs(x, y, n+1);
            }
        }
    }

    // 가치 계산 함수
    // 주어진 좌표에서 M행까지 담아야 하는데 크기 순으로 담는 것이 좋아보임.
    // 정렬을 하고 하나씩 탐색하며 크기가 초과하지 않을때까지 담기
    // 만약 c가 0이되면 바로 return
    public static int calc(int x, int y){
        int c = C,
            sum = 0;
        int[] temp = new int[M];

        // 값 복사
        for(int m = 0; m &amp;lt; M; m++)
            temp[m] = map[x][y+m];

        Arrays.sort(temp);
        for(int i = 0; i &amp;lt; M; i++){
            if(temp[i] &amp;lt;= c) {
                sum += (int)Math.pow(temp[i], 2);
                c -= temp[i];
            }
        }

        return sum;
    }
}

class Location {
    int x;
    int y;
    int cost;
    public Location(int x, int y, int cost) {
        this.x = x;
        this.y = y;
        this.cost = cost;
    }
}

// 가방에 최대한 가치가 높은 물건들을 담는다
// 물건의 가치는 무게^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;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제에서 중요한 부분은 &amp;ldquo;도둑 두 명의 선택 범위가 겹치지 않게 순회하는 방식&amp;rdquo;이었다.&lt;br /&gt;그걸 해결하니까 나머지는 그냥 브루트포스 + 간단한 최적화로 끝났다.&lt;/p&gt;
&lt;p data-end=&quot;3194&quot; data-start=&quot;3180&quot; data-ke-size=&quot;size16&quot;&gt;사실 더 깔끔한 방법은 &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;각 구간의 부분집합 가치(2^M)를 미리 계산해 DP 테이블처럼 저장해두는 방식&lt;/span&gt;인데 M이 작다 보니 정렬 후 greedy만으로도 충분했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Knowledge/알고리즘</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/41</guid>
      <comments>https://ryuwon-it.tistory.com/41#entry41comment</comments>
      <pubDate>Sun, 7 Dec 2025 23:50:31 +0900</pubDate>
    </item>
    <item>
      <title>[Server] 토큰 기반 인증 VS 세션 기반 인증</title>
      <link>https://ryuwon-it.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;로그인/인증을 구현하다 보면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;JWT 쓸까? 세션으로 쓸까?&amp;rdquo;&lt;/b&gt;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막상 구현하고 비교해보니 철학 자체가 다르다는 걸 깨달았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번 2편에서는&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 인증 상태를 기억하는가 안하는가.&lt;/p&gt;
&lt;/blockquote&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;rarr; 서버가 나의 로그인 상태를 기억하고 있음 (Stateful)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;토큰 기반(JWT) 인증&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 서버는 아무것도 기억하지 않음. 클라이언트가 인증 정보 들고 다님 (Stateless)&lt;/p&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-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAMIuA/dJMcagKMlMc/XlwFJkTArUOIk0S3soZsV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAMIuA/dJMcagKMlMc/XlwFJkTArUOIk0S3soZsV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAMIuA/dJMcagKMlMc/XlwFJkTArUOIk0S3soZsV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAMIuA%2FdJMcagKMlMc%2FXlwFJkTArUOIk0S3soZsV0%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;1024&quot; height=&quot;559&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;559&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 세션 기반 인증 흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션 기반 인증은 밑과 같이 흘러간다&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;로그인 성공
  &amp;darr;
서버: &quot;해당 유저 로그인했음&quot; 하고 세션에 저장
  &amp;darr;
클라이언트에 세션 ID 쿠키 발급 (JSESSIONID)
  &amp;darr;
이후 요청마다 세션 ID 자동 전송
  &amp;darr;
서버는 세션 저장소에서 유저 정보 꺼내서 인증
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버가 로그인 상태를 들고 있는 형태다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서&amp;hellip;&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;어..? 얘는 서버1에선 로그인인데 서버2에선 로그인이 아니네?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Redis 같은 곳에 세션을 &lt;b&gt;공유할 필요성이 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 토큰 기반(JWT) 인증 흐름&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는 이렇게 다르게 한다:&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;로그인 성공
  &amp;darr;
서버가 JWT 생성해서 클라이언트에게 줌
  &amp;darr;
서버는 아무것도 기억하지 않음
  &amp;darr;
클라이언트가 JWT를 들고 다님
  &amp;darr;
요청마다 JWT 검증해서 인증 처리
&lt;/code&gt;&lt;/pre&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 문제는&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰이 탈취되면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 서버는 알아낼 방법이 없다&amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 실제로는 Refresh Token 정책, 만료 전략 등을 꼭 신경 써야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 전략이 다양하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 장단점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;보안 제어가 직관적&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;세션 탈취? &amp;rarr; 서버에서 세션 삭제하면 끝&lt;/li&gt;
&lt;li&gt;Spring Security 기본 기능과 호환이 잘된다&lt;/li&gt;
&lt;li&gt;CSRF 같은 공격은 기본 설계에서 커버하기 쉽다&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;서버 확장 시 세션 공유 필요&lt;/li&gt;
&lt;li&gt;모바일/SPA와 연동할 때 구조가 조금 답답함&lt;/li&gt;
&lt;li&gt;API 서버 다중화하려면 고려할 게 많음&lt;/li&gt;
&lt;li&gt;확장에선 닫혀있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;토큰 기반 인증(JWT)&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;완전 Stateless &amp;rarr; &lt;b&gt;서버 여러 개 띄우기 최고&lt;/b&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;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;Refresh Token 탈취는 정말 크리티컬
&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;만료/재발급/블랙리스트 정책을 설계해야 한다&lt;/li&gt;
&lt;li&gt;보관 위치(LocalStorage vs Cookie) 논쟁이 끝이 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 결국 뭐가 더 좋을까..?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 보면 이런 질문을 한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;요즘은 JWT 쓰는 게 맞죠?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;세션 기반 인증은 구식 아닌가요?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 질문 자체가 잘못된 질문인거같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 처음에 앱 개발로 개발로 접하며 JWT로 자연스럽게 빠져들었었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무조건 JWT가 옳다. 세션이 옳다. 가 아니라 상황에 따라 다른 방식을 사용할 수도 있다..!&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;내부 어드민, 관리자 페이지&lt;/li&gt;
&lt;li&gt;사용자 수가 적고 안정성이 중요한 서비스&lt;/li&gt;
&lt;li&gt;서버 확장은 크게 고려하지 않을 때&lt;/li&gt;
&lt;li&gt;Spring Security 기본 기능을 그대로 쓰고 싶을 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JWT 기반 인증이 더 좋은 상황&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;모바일 앱 + API 서버 조합&lt;/li&gt;
&lt;li&gt;React/Vue SPA + 백엔드 분리 구조&lt;/li&gt;
&lt;li&gt;서버를 자주 확장하는 서비스&lt;/li&gt;
&lt;li&gt;마이크로서비스(MSA) 환경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 정리하자면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&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;b&gt;JWT&lt;/b&gt; = 확장성&amp;middot;유연성이 매우 뛰어남&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&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;서버가 인증 상태를 들고 있으면 &amp;rarr; 세션&lt;/li&gt;
&lt;li&gt;클라이언트가 인증 정보를 들고 다니면 &amp;rarr; JWT&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;확장성, 보안 전략, API 설계 방식까지 전부 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 편에서는 인증의 핵심 논쟁인&amp;hellip;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;JWT는 어디에 저장하는 게 안전할까?&amp;rdquo; LocalStorage? Cookie? Refresh Token 탈취 위험은?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&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;</description>
      <category>Knowledge/개발지식</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/40</guid>
      <comments>https://ryuwon-it.tistory.com/40#entry40comment</comments>
      <pubDate>Sun, 7 Dec 2025 23:45:01 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Security 겉핥기</title>
      <link>https://ryuwon-it.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 쉽지 않은 녀석이였따&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 처음 접했었을떄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;인증은 어디서 하고, 인가는 왜 또 따로 있을까&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;ldquo;필터는 왜 이렇게 많을까..?&amp;rdquo;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 최근 JWT 기반 인증 흐름을 리팩토링하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Security가 어려운 이유가 내가 구조 이해못해서라는 것을&lt;/b&gt; 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;내가 초보 개발자라..&lt;/s&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Spring Security가 어려운 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security는 &amp;ldquo;요청이 들어왔을 때 어떤 필터를 거쳐서 인증&amp;middot;인가를 수행할지&amp;rdquo;를 먼저 정의하는 프레임워크다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 사람이 Spring Security를 Controller 쪽에서 작동한다고 생각하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실은 그 이전, &lt;b&gt;DispatcherServlet보다 훨씬 앞단&lt;/b&gt;에서 모든 게 시작된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7ZFEp/dJMcaiu0S3Z/ELNk8KaVjI82wT7o2kKthK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7ZFEp/dJMcaiu0S3Z/ELNk8KaVjI82wT7o2kKthK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7ZFEp/dJMcaiu0S3Z/ELNk8KaVjI82wT7o2kKthK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7ZFEp%2FdJMcaiu0S3Z%2FELNk8KaVjI82wT7o2kKthK%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;1024&quot; height=&quot;768&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 우리가 작성한 Controller 코드가 실행되기도 전에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Security Filter Chain이 먼저 요청을 훑는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Security를 잘 모르면 다음과 같은 상황이 생긴다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내가 만든 Controller가 호출되지 않음&lt;/li&gt;
&lt;li&gt;JWT 필터가 동작 안 함&lt;/li&gt;
&lt;li&gt;SecurityConfig가 너무 거대해짐&lt;/li&gt;
&lt;li&gt;어디서 인증이 거부되는지 디버깅이 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Security 흐름을 먼저 이해해야만 요청과 응답의 흐름을 읽을 수 있따&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. DispatcherServlet 이전에 존재하는 &lt;b&gt;Security Filter Chain&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring MVC 전체 흐름을 단순화하면 대략 이런 구조다:&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;[요청] &amp;rarr; (서블릿 필터들) &amp;rarr; DispatcherServlet &amp;rarr; Controller &amp;rarr; Service &amp;rarr; Repository
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 Spring Security는 &lt;b&gt;&amp;lsquo;서블릿 필터들&amp;rsquo; 단계에 개입&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 요청이 들어오면 Security Filter Chain이 가장 먼저 요청을 가로챈다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 인증 검사, 로그인 체크, 권한 확인 같은 건 모두 여기서 이뤄진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security에서 자주 보는 필터들:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UsernamePasswordAuthenticationFilter&lt;/li&gt;
&lt;li&gt;JwtAuthenticationFilter (우리가 커스텀하는 경우)&lt;/li&gt;
&lt;li&gt;ExceptionTranslationFilter&lt;/li&gt;
&lt;li&gt;FilterSecurityInterceptor&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;p data-ke-size=&quot;size16&quot;&gt;예외 처리가 작동하지 않는 등 사고가 터진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. UsernamePasswordAuthenticationFilter의 역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 Security의 기본 로그인은 이 필터가 담당한다.&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;/login 요청이 들어오면&lt;/li&gt;
&lt;li&gt;아이디/비밀번호를 꺼내서 Authentication 객체 생성&lt;/li&gt;
&lt;li&gt;AuthenticationManager에게 전달&lt;/li&gt;
&lt;li&gt;Provider가 실제로 인증&lt;/li&gt;
&lt;li&gt;성공하면 SecurityContext에 인증 정보 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 우리가 &lt;b&gt;JWT 기반 로그인&lt;/b&gt;을 적용하면&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 필터 구조를 잘 모르면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;왜 JWT 인증이 안 되지?&amp;rdquo; 같은 상황이 생긴다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. AuthenticationManager / AuthenticationProvider 관계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Security 인증 흐름을 이해할 때 밑 내용을 참조하면 조금 더 편한데&lt;/p&gt;
&lt;pre class=&quot;excel&quot;&gt;&lt;code&gt;사용자 요청
   &amp;darr;
AuthenticationFilter
   &amp;darr;
AuthenticationManager
   &amp;darr;
AuthenticationProvider
   &amp;darr;
UserDetailsService (DB 조회)
   &amp;darr;
성공/실패 판단
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하면&amp;hellip;&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;Filter&lt;/b&gt;: 요청을 받아 매니저에게 넘김&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Manager&lt;/b&gt;: 여러 Provider 중 어떤 걸 사용할지 선택&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Provider&lt;/b&gt;: 실제 인증 (비밀번호 검증 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UserDetailsService&lt;/b&gt;: 유저 정보 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 방식에서도 흐름은 비슷한데, 인증 정보가 DB가 아니라 &lt;b&gt;토큰&lt;/b&gt;에서 유출된다는 점만 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Provider를 건드리기보다는 &lt;b&gt;Filter를 커스텀해서 JWT 해석/인증을 대신해주는 방식&lt;/b&gt;을 쓴다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. JWT와 Security가 만나면 왜 더 헷갈릴까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 자체는 단순한 문자열일 뿐인데, Security와 합치면 갑자기 난이도가 상승한다&amp;hellip;&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;1. &amp;ldquo;JWT를 언제 검증해야 하는지&amp;rdquo; 타이밍을 잡기 어렵다&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;Filter Chain 어디에 넣어야 하는지&lt;/li&gt;
&lt;li&gt;UsernamePasswordAuthenticationFilter 앞인지 뒤인지&lt;/li&gt;
&lt;li&gt;CORS 처리 전에 해야 하는지&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;사실 JWT의 경우에도 어떤 전략을 쓰는지에 따라 다 달라져서도 어느정도 영향을 미치는거 같다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;정답을 알려줘&lt;/s&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 인증 실패와 인가 실패가 다르다는 사실&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;인증 실패 &amp;rarr; AuthenticationEntryPoint&lt;/li&gt;
&lt;li&gt;인가 실패 &amp;rarr; AccessDeniedHandler&lt;/li&gt;
&lt;/ul&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;3. SecurityConfig 구조가 지나치게 방대해진다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jwt 필터, oauth 설정, csrf, cors, exception handler, path matcher&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 모든 걸 한 파일에 적으려니 복잡해질 수밖에 없다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 내가 이번에 공부하며 깨달았던 점.&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 필터 순서를 이해해야 문제 해결이 된다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 필터를&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UsernamePasswordAuthenticationFilter 앞에 둘지, 뒤에 둘지에 따라 동작이 완전히 달라진다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 인증은 &amp;ldquo;로그인 처리&amp;rdquo;가 아니라 &amp;ldquo;요청 검증&amp;rdquo;이기 때문에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 UsernamePasswordAuthenticationFilter &lt;b&gt;앞&lt;/b&gt;에 둔다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 인증/인가 예외 처리를 분리해야 한다&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;보안 관점에서는 완전히 구분하는 게 맞다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;토큰이 없거나 만료됨 &amp;rarr; 인증 에러&lt;/li&gt;
&lt;li&gt;권한이 부족함 &amp;rarr; 인가 에러&lt;/li&gt;
&lt;/ul&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;3. SecurityConfig는 &amp;ldquo;설정 집합&amp;rdquo;이지 &amp;ldquo;코드 집적 파일&amp;rdquo;이 아니다&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 Config에 너무 많은 로직이 들어가 있었는데&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;SecurityConfig&lt;/li&gt;
&lt;li&gt;JwtFilterConfig&lt;/li&gt;
&lt;li&gt;ExceptionHandlingConfig&lt;/li&gt;
&lt;li&gt;OAuth2Config&lt;/li&gt;
&lt;li&gt;CorsConfig&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Config를 쪼개니 변경 포인트도 줄고, 가독성도 올라갔다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Security가 어려운 이유는 결국 하나다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증 로직을 우리가 직접 구현하는 게 아니라, 필터 체인 속에 끼워 넣는 방식으로 만들기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Security는 단순히 코드만 보고 이해하려고 하면 절대 안 되고,&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;1460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEfheQ/dJMcac2DfQE/hfX87kTLqpWsaUaMUB73KK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEfheQ/dJMcac2DfQE/hfX87kTLqpWsaUaMUB73KK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEfheQ/dJMcac2DfQE/hfX87kTLqpWsaUaMUB73KK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEfheQ%2FdJMcac2DfQE%2FhfX87kTLqpWsaUaMUB73KK%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;1648&quot; height=&quot;1460&quot; data-origin-width=&quot;1648&quot; data-origin-height=&quot;1460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허헣 이젠 분석할 수 있겠서,,&lt;/p&gt;</description>
      <category>Framework &amp;amp; Library/Spring</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/39</guid>
      <comments>https://ryuwon-it.tistory.com/39#entry39comment</comments>
      <pubDate>Sat, 6 Dec 2025 23:55:01 +0900</pubDate>
    </item>
    <item>
      <title>[BOJ/JAVA] N과 M(9) 백트래킹</title>
      <link>https://ryuwon-it.tistory.com/38</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/15663&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/15663&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btPC6K/dJMcab3Jxyc/mKxU9XN3KeQ7PWB40DgJq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btPC6K/dJMcab3Jxyc/mKxU9XN3KeQ7PWB40DgJq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btPC6K/dJMcab3Jxyc/mKxU9XN3KeQ7PWB40DgJq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtPC6K%2FdJMcab3Jxyc%2FmKxU9XN3KeQ7PWB40DgJq1%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;751&quot; height=&quot;696&quot; data-origin-width=&quot;751&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&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;h3 data-ke-size=&quot;size23&quot;&gt;알고리즘 [접근 방법]&lt;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 접근 visited로 비내림차순을 막으면 되지 않을까? 라 생각했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N과 M(12)는 &lt;b&gt;중복 제거 + 비내림차순&lt;/b&gt;이 핵심이라&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초기에는 TreeMap과 visited 배열을 이용해 다음과 같이 생각했다.&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;첫 번째 원소가 같은 수열이 여러 번 나오지 않게 visited로 막자.&lt;/li&gt;
&lt;li&gt;visited를 적절히 조절하면 비내림차순도 만들 수 있지 않을까?&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는 완전히 실패였다 ㅠ&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visited로는 비내림차순을 보장할 수 없다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비내림차순은 &lt;b&gt;startIndex 기반 DFS&lt;/b&gt;가 해결해야 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visited로 강제로 막으려고 하다 보니&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;5851 2643
908 5851 2643

&lt;/code&gt;&lt;/pre&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;그리고 정상 조합까지 visited에 의해 막혀버리며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 visited가 다음 단계 DFS에서 필요한 선택까지 봉쇄해버렸다.&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;distinct()를 활용하여 중복제거를 하고, visited를 쓰지않고 단순 dfs로만 백트래킹을 하여 문제를 풀어냈다&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;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main {
    static int N, M;
    static int[] num, arr;
    static int[] cnt = new int[10001];
    static StringBuilder sb = new StringBuilder();
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());

        N = Integer.parseInt(st.nextToken());
        M = Integer.parseInt(st.nextToken());

        num = new int[N];
        st = new StringTokenizer(br.readLine());
        for(int i = 0; i &amp;lt; N; i++) {
            num[i] = Integer.parseInt(st.nextToken());
            cnt[num[i]] += 1;
        }
        num = Arrays.stream(num).distinct().toArray(); // 중복제거
        Arrays.sort(num); //정렬

        arr = new int[M];

        bfs(0);
        System.out.print(sb);
    }

    static void bfs(int m){
        if(m == M){
            for(int i = 0; i &amp;lt; M; i++) sb.append(arr[i]).append(&quot; &quot;);
            sb.append(&quot;\\n&quot;);
            return;
        }
        for(int i = 0; i &amp;lt; num.length; i++){
            if(cnt[num[i]] != 0) {
                arr[m] = num[i];
                cnt[num[i]]--;
                bfs(m + 1);
                cnt[num[i]]++;
            }
        }
    }
}
&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;/h3&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;107&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6efkr/dJMcajgliDr/Oxn9UJp64oFsQF3vXjFfIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6efkr/dJMcajgliDr/Oxn9UJp64oFsQF3vXjFfIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6efkr/dJMcajgliDr/Oxn9UJp64oFsQF3vXjFfIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6efkr%2FdJMcajgliDr%2FOxn9UJp64oFsQF3vXjFfIK%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;734&quot; height=&quot;107&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;107&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Knowledge/알고리즘</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/38</guid>
      <comments>https://ryuwon-it.tistory.com/38#entry38comment</comments>
      <pubDate>Sun, 30 Nov 2025 23:59:13 +0900</pubDate>
    </item>
    <item>
      <title>[Linux] 작업 예약 스케줄러(Cron)을 이용한 게임 서버 재부팅</title>
      <link>https://ryuwon-it.tistory.com/37</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;노션 정리를 하다가 22년도에 한창 '발헤임'이라는 게임이 유행했었을때&amp;nbsp;&lt;br /&gt;GCP를 활용하여 24시간 발헤임 서버를 구축하였었는데 해당 서버를 운영하며 있었던 이슈에 대해 정리해뒀던 기록을 발견하여 추억 회상 겸 올리게 되었습니다 ㅋㅋㅋ&lt;br /&gt;&lt;br /&gt;Ubuntu 20.04 LTS 기준으로 작성된 글입니다.&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;얘들하고 발헤임을 하며 접속 시간이 모두 다 다르다보니 24시간 항상 열려 있는 서버가 있으면 좋을꺼 같다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 대학생용 학습 계정이 있는 Azure로 서버를 구축을 할까 생각했다가 크레딧을 10만원밖에 주지 않아 나중에 진짜 돈이 나갈꺼 같았기에 .. 구성했던 VM를 삭제하고 GCP로 다시 세팅했는데&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;그래서 매일 주기적으로 새벽 4시에 VM 서버 재부팅을 진행하기로 마음먹었다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 재부팅을 자동화하기에 앞서 서버 시간대를 우리나라 시간대로 연동시켜 두어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;date 명령어를 통해 시간대를 확인한 후 현재 시간대와 맞지 않을 경우 zoneinfo에 있는 Seoul 시간대를 localtime 파일명으로 링킹 해주면 된다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;$ date
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;231&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uzaE0/dJMcagxcrfE/FPLlJRNXhvPY2UYFznshu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uzaE0/dJMcagxcrfE/FPLlJRNXhvPY2UYFznshu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uzaE0/dJMcagxcrfE/FPLlJRNXhvPY2UYFznshu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuzaE0%2FdJMcagxcrfE%2FFPLlJRNXhvPY2UYFznshu1%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;231&quot; height=&quot;34&quot; data-origin-width=&quot;231&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;$ sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/comqzc/dJMcaiIvNIG/flanxXHv1AUUyNMIGOpJn1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/comqzc/dJMcaiIvNIG/flanxXHv1AUUyNMIGOpJn1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/comqzc/dJMcaiIvNIG/flanxXHv1AUUyNMIGOpJn1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcomqzc%2FdJMcaiIvNIG%2FflanxXHv1AUUyNMIGOpJn1%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;618&quot; height=&quot;43&quot; data-origin-width=&quot;618&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 특정한 시간 마다 작업이나 스케줄링 역할을 해주는 명령어인 cron 을 활용하여&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새벽 4시마다 재부팅을 해주게 만들면 된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아 갑자기 생각난건데 6시간 주기로 자동백업도 진행해둬야겠다. 맵 파일 용량은 그다지 크지는 않지만 1주일 전 파일 또는 2주일 전 파일들은 자동 제거하게 되면 용량 걱정은 크게 안해도 될 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;# crontab 편집하기

$ crontab -e
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 열게되면 어떤 편집 창으로 파일을 열지 물어보는데 편한거 설정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 vi로 많이 파일들을 작업했기에 vi로 열었다&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;166&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3xNGA/dJMcaaRiae8/K54EPz5KS05wPtQI5fgD21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3xNGA/dJMcaaRiae8/K54EPz5KS05wPtQI5fgD21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3xNGA/dJMcaaRiae8/K54EPz5KS05wPtQI5fgD21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3xNGA%2FdJMcaaRiae8%2FK54EPz5KS05wPtQI5fgD21%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;446&quot; height=&quot;166&quot; data-origin-width=&quot;446&quot; data-origin-height=&quot;166&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 편집창에 원하는 세팅과 명령어를 적어주고 저장하면 끝&amp;hellip;!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(완전히 설정 하기 전에 5분 후 재부팅으로 등록 해둔 후 테스팅하고 반영하면 더욱 좋음!!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;crontab 주기는 별 위치에 따라 원하는 주기를 설정할 수 있는데 우리는 4시니까 밑과 같이 작성하면 된다&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;*　　　　　　*　　　　　　*　　　　　　*　　　　　　*
분(0-59)　　시간(0-23)　　일(1-31)　　월(1-12)　　　요일(0-7)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;00 04 * * * sudo /sbin/reboot
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;615&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnV0jo/dJMcahCSd1w/syp25N0Sjp9ffLXvurhXG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnV0jo/dJMcahCSd1w/syp25N0Sjp9ffLXvurhXG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnV0jo/dJMcahCSd1w/syp25N0Sjp9ffLXvurhXG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnV0jo%2FdJMcahCSd1w%2Fsyp25N0Sjp9ffLXvurhXG0%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;553&quot; height=&quot;615&quot; data-origin-width=&quot;553&quot; data-origin-height=&quot;615&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 저장하는 것 잊지말기!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 키보드에서 원하는 위치에 타자 포인트를 두고 i를 누르면 작성 모드.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ESC를 누르면 다시 명령 모드로 변경된다. 명령모드 상태에서 : 를 누르고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;wq를 써주고 엔터. 세세한 설명은 vi 편집기에 대해 검색해보면 된다)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;16&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blmSUy/dJMcabimDDY/bOtP1G5CJlj0Wk2oKoA051/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blmSUy/dJMcabimDDY/bOtP1G5CJlj0Wk2oKoA051/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blmSUy/dJMcabimDDY/bOtP1G5CJlj0Wk2oKoA051/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblmSUy%2FdJMcabimDDY%2FbOtP1G5CJlj0Wk2oKoA051%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;375&quot; height=&quot;16&quot; data-origin-width=&quot;375&quot; data-origin-height=&quot;16&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파일을 저장까지 했다면 해당 명령이 적용되도록 cron 서비스를 재부팅&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ sudo service cron restart
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 새벽 4시마다 자동으로 재부팅하며 나의 노고가 줄어든다 후후&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 좀 잔렉이 줄어들길 바라며&amp;hellip;&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;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cron에 대한 추가적인 상세 내용은 여기서 참조하면 좋을 것 같다..!&lt;/p&gt;
&lt;br /&gt;&lt;a href=&quot;https://inpa.tistory.com/entry/LINUX-%F0%9F%93%9A-%EC%9E%91%EC%97%85-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%9F%AC-%EB%AA%85%EB%A0%B9%EC%96%B4-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-at-crontab&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://inpa.tistory.com/entry/LINUX-%F0%9F%93%9A-%EC%9E%91%EC%97%85-%EC%8A%A4%EC%BC%80%EC%A4%84%EB%9F%AC-%EB%AA%85%EB%A0%B9%EC%96%B4-%F0%9F%92%AF-%EC%A0%95%EB%A6%AC-at-crontab&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;</description>
      <category>Knowledge/운영체제</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/37</guid>
      <comments>https://ryuwon-it.tistory.com/37#entry37comment</comments>
      <pubDate>Sun, 30 Nov 2025 23:51:21 +0900</pubDate>
    </item>
    <item>
      <title>[CS] System Call: 프로그래밍 언어로 어떻게 하드웨어를 조작할 수 있을까?</title>
      <link>https://ryuwon-it.tistory.com/36</link>
      <description>&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;p data-ke-size=&quot;size16&quot;&gt;이 명령들이 어떻게 CPU나 메모리, 디스크와 같은 하드웨어들을 직접 다룰 수 있으며, 제약은 없는걸까..&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 우리가 하드웨어를 다룰 수 있는 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 printf() 함수 하나로 화면에 글자를 띄우고, open() 함수 하나로 디스크의 파일을 조작할 수 있는 이유는 바로 &lt;b&gt;운영체제가 하드웨어 접근을 추상화&lt;/b&gt;해주기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 Java나 C 같은 언어로 코드를 짜면, 그 코드는 결국 OS가 이해할 수 있는 형태의 요청으로 변환되는데, 이 요청을 OS에게 전달하는 &lt;b&gt;공식적인 통로&lt;/b&gt;가 바로 &lt;b&gt;시스템 콜 인터페이스&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머 입장에선 복잡한 하드웨어의 작동 방식(예: 디스크 섹터 주소, 비디오 메모리 제어)을 몰라도, OS가 제공하는 &lt;b&gt;약속된 함수 이름&lt;/b&gt;만 가지고도 하드웨어를 다룰 수 있는 것이지만, 여기엔 명확한 &lt;b&gt;한계&lt;/b&gt;가 있다. 프로그램이 하드웨어를 '직접' 조작할 수 있는 게 아니라, 오직 OS에게 &lt;b&gt;대리인 역할&lt;/b&gt;을 부탁할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 문뜩 생각이 든다. 굳이 OS가 중간에 껴야할까?&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 왜 OS가 중간에 껴서 하드웨어를 다루는지&lt;/h2&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;h3 data-ke-size=&quot;size23&quot;&gt;시스템 보호의 필수 조건: 이중 모드 (Dual Mode)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터의 자원은 한정되어 있고, 여러 프로그램이 공유한다. 만약 프로그램이 실수든 버그든 악의적으로든 CPU나 메모리 같은 핵심 자원을 마음대로 조작할 수 있다면 한순간에 시스템 전체가 붕괴될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제를 막기 위해 CPU는 실행 모드를 두 가지로 나눠 권한을 분리한다.&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;사용자 모드 (User Mode):&lt;/b&gt; 일반 응용 프로그램이 실행되는 모드. &lt;b&gt;입출력 명령&lt;/b&gt;이나 &lt;b&gt;메모리 관리&lt;/b&gt; 같은 하드웨어 조작 명령어(&lt;b&gt;특권 명령어&lt;/b&gt;)의 실행이 금지된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커널 모드 (Kernel Mode):&lt;/b&gt; 운영체제의 핵심 영역인 &lt;b&gt;커널&lt;/b&gt;이 실행되는 모드. 모든 명령어를 실행할 수 있으며, 하드웨어를 직접 조작할 수 있는 &lt;b&gt;최고 권한 모드&lt;/b&gt;이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;응용 프로그램은 기본적으로 &lt;b&gt;사용자 모드&lt;/b&gt;로 실행되기 때문에, 하드웨어를 건드리고 싶으면 반드시 &lt;b&gt;커널 모드&lt;/b&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;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 시스템 콜(System Call) 작동 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;시스템 콜&lt;/b&gt;은 사용자 모드의 프로그램이 커널 모드에 있는 OS에게 서비스를 요청하는 공식적인 방법이다. 이는 일종의 &lt;b&gt;소프트웨어 인터럽트&lt;/b&gt;로 작동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;426&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/db7YmE/dJMcagKJqTM/33BqtEmyzBUwaATPy1z7fK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/db7YmE/dJMcagKJqTM/33BqtEmyzBUwaATPy1z7fK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/db7YmE/dJMcagKJqTM/33BqtEmyzBUwaATPy1z7fK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdb7YmE%2FdJMcagKJqTM%2F33BqtEmyzBUwaATPy1z7fK%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;700&quot; height=&quot;426&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;426&quot;/&gt;&lt;/span&gt;&lt;/figure&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;사용자 프로그램이 파일 열기(open()) 같은 하드웨어 관련 작업을 요청하면 다음과 같은 과정이 일어난다.&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;시스템 콜 호출&lt;/b&gt; 사용자 프로그램이 특정 OS 서비스를 요청한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Trap 발생&lt;/b&gt; CPU는 현재 실행을 멈추고 &lt;b&gt;Trap&lt;/b&gt;을 발생시킨다 (소프트웨어 인터럽트)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태 저장 및 모드 전&lt;/b&gt; CPU는 하던 작업의 상태(&lt;b&gt;PCB&lt;/b&gt;, &lt;b&gt;프로그램 카운터&lt;/b&gt;)를 저장하고, 실행 모드를 &lt;b&gt;사용자 모드 &amp;rarr; 커널 모드&lt;/b&gt;로 자동으로 전환한다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;인터럽트 서비스 루틴(ISR) 실행:&lt;/b&gt; 커널 영역에서 해당 시스템 콜 요청을 처리하는 코드를 실행하여 하드웨어 조작을 대리 수행한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;복귀&lt;/b&gt; 커널이 작업을 완료하면, 모드를 &lt;b&gt;커널 모드 &amp;rarr; 사용자 모드&lt;/b&gt;로 복귀하고, 저장했던 프로그램 상태를 복원하여 원래 프로그램으로 돌아간다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정을 통해 OS는 자원에 대한 통제권을 안전하게 유지하면서도, 프로그램의 요청을 완벽하게 처리할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 개발자가 직접 System Call을 부르진 않는다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 프로그래밍을 할 때 우리는 sys_write 같은 OS 종속적인 복잡한 함수 대신, printf()나 read() 같은 간단한 함수를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 사용하는 이 함수들은 OS가 제공하는 &lt;b&gt;라이브러리 함수&lt;/b&gt;이자 **API(Application Programming Interface)**이다. (이건 운영체제에 대한 API 입니닷)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**API**는 개발자와 시스템 콜 사이의 중간 계층 역할을 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;API는 복잡하고 OS마다 다른 시스템 콜을 개발자가 사용하기 쉬운 형태로 추상화한다&lt;/li&gt;
&lt;li&gt;예를 들어, C언어의 printf() 함수는 내부적으로 &lt;b&gt;write() 시스템 콜&lt;/b&gt;을 호출하여 커널 모드로 진입하게 한다&lt;/li&gt;
&lt;li&gt;&amp;rArr; printf()도 사실 write()로 구성되어있다..!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 API 덕분에 프로그래머는 OS 내부 구조나 시스템 콜의 세부 번호를 몰라도, 일관된 방식으로 OS 서비스를 요청할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 주요 시스템 콜 종류&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 콜은 OS가 제공하는 핵심 기능별로 분류되며, 우리가 코딩하는 모든 프로그램에서 간접적으로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C언어에서는 Unistd.h 라이브러리에 해당 시스템 콜이 포함되어있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카테고리 설명 주요 시스템 콜 (Unix/Linux 예시)&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;&lt;b&gt;프로세스 제어&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;프로세스의 생성, 실행, 종료 및 상태 관리&lt;/td&gt;
&lt;td&gt;fork(), exec(), exit(), wait()&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;td&gt;open(), read(), write(), close(), unlink()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;장치 관리&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;입출력 장치(Device)에 대한 접근 및 제어&lt;/td&gt;
&lt;td&gt;ioctl(), read(), write()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;정보 유지&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;시스템 시간, 프로세스 ID, OS 버전 등의 정보 얻기&lt;/td&gt;
&lt;td&gt;time(), getpid(), alarm()&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;통신&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;프로세스 간 통신(IPC) 및 네트워크 소켓 생성/데이터 전송&lt;/td&gt;
&lt;td&gt;pipe(), socket(), send(), recv()&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 마무리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 콜은 프로그램과 하드웨어 사이를 잇는 중요한 연결고리지만, 모드 전환(Context Switching)이 반복될 때마다 오버헤드가 발생한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;I/O 인터럽트와 시스템 콜의 관계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;커널 모드에 진입한 OS가 하드웨어에게 실제 일을 시키면, 하드웨어는 작업을 마친 후 CPU에게 &lt;b&gt;I/O 인터럽트&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;시스템 콜:&lt;/b&gt; 프로그램 &amp;rarr; OS (소프트웨어 요청)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;I/O 인터럽트:&lt;/b&gt; 하드웨어 &amp;rarr; CPU (하드웨어 응답)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 콜은 I/O 작업의 시작을 알리고, I/O 인터럽트는 그 작업의 완료를 알리는 중요한 쌍을 이룬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;성능 극대화 기술: DMA와 DirectX&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Context Switching의 비용을 줄여 I/O 성능을 높이려는 기술들이 발전해 왔다.&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;DMA (Direct Memory Access)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;CPU 개입 없이 주변 장치(디스크 등)가 메모리에 직접 데이터를 전송하도록 하여, CPU의 부하를 줄이고 불필요한 인터럽트 발생을 감소시킨다&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DirectX&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;게임처럼 고성능 그래픽 처리가 필요할 때, OS의 복잡하고 느린 커널 경로를 최소화하거나 우회하여 Context Switching 지연을 줄이고 처리 속도를 극대화하는 기술이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론적으로, 프로그래밍 언어로 작성된 모든 명령은 &lt;b&gt;시스템 콜&lt;/b&gt;을 거쳐야만 하드웨어에 닿을 수 있으며, 이 시스템 구조 자체가 프로그래밍 언어가 하드웨어를 동작시킬 수 있는 방식이댜.&lt;/p&gt;</description>
      <category>Knowledge/운영체제</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/36</guid>
      <comments>https://ryuwon-it.tistory.com/36#entry36comment</comments>
      <pubDate>Sat, 29 Nov 2025 23:50:31 +0900</pubDate>
    </item>
    <item>
      <title>[인증] 쿠키, 세션, 토큰에 대해</title>
      <link>https://ryuwon-it.tistory.com/35</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;1. 쿠키(Cookie)란?&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;h3 data-ke-size=&quot;size23&quot;&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; 사용자의 브라우저에 저장됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자동 전송:&lt;/b&gt; 같은 도메인으로 요청할 때 자동으로 포함됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;만료 시간:&lt;/b&gt; 설정한 기간이 지나면 자동으로 삭제됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;용량 제한:&lt;/b&gt; 도메인당 약 4KB로 제한됨&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;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/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;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;보안에 취약할 수 있음 (XSS 공격 위험)&lt;/li&gt;
&lt;li&gt;용량 제한이 있음&lt;/li&gt;
&lt;li&gt;매 요청마다 전송되어 네트워크 부담&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 세션(Session)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세션은 사용자 정보를 서버에 저장하는 방식이다. 사용자가 로그인하면 서버는 세션을 생성하고 고유한 세션 ID를 발급한다. 이 세션 ID는 쿠키를 통해 클라이언트로 전달되고, 클라이언트는 이후 요청 시 세션 ID를 보내서 자신을 식별한다.&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;/h3&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;li&gt;서버가 세션 생성하고 세션 저장소에 저장&lt;/li&gt;
&lt;li&gt;서버가 세션 ID를 쿠키로 클라이언트에 전달&lt;/li&gt;
&lt;li&gt;클라이언트가 이후 요청마다 세션 ID 쿠키 전송&lt;/li&gt;
&lt;li&gt;서버가 세션 ID로 세션 저장소에서 사용자 정보 조회&lt;/li&gt;
&lt;/ol&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;/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;/li&gt;
&lt;li&gt;클라이언트는 세션 ID만 저장하면 됨&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;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;/li&gt;
&lt;li&gt;서버 확장 시 세션 동기화 문제&lt;/li&gt;
&lt;li&gt;서버 부하가 증가할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 토큰(Token)이란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰은 사용자 인증 정보를 담은 암호화된 문자열이다. 주로 JWT(JSON Web Token)가 사용되는데, 서버가 토큰을 발급하면 클라이언트는 이걸 저장했다가 요청 시마다 헤더에 포함해서 보낸다.&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;JWT 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT는 점(.)으로 구분된 세 부분으로 이뤄진다:&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;Header:&lt;/b&gt; 토큰 타입과 해싱 알고리즘 정보&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Payload:&lt;/b&gt; 실제 전달할 데이터 (클레임)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Signature:&lt;/b&gt; 토큰 검증을 위한 서명&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsInVzZXJuYW1lIjoiam9obiIsImlhdCI6MTUxNjIzOTAyMn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;토큰의 동작 방식&lt;/h3&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;li&gt;서버가 JWT 생성 (사용자 정보 포함)&lt;/li&gt;
&lt;li&gt;서버가 토큰을 클라이언트에 전달&lt;/li&gt;
&lt;li&gt;클라이언트가 토큰 저장 (로컬 스토리지, 쿠키 등)&lt;/li&gt;
&lt;li&gt;클라이언트가 요청 시 Authorization 헤더에 토큰 포함&lt;/li&gt;
&lt;li&gt;서버가 토큰 검증 후 요청 처리&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;서버가 상태를 저장하지 않아 확장성이 좋음 (Stateless)&lt;/li&gt;
&lt;li&gt;여러 도메인에서 사용 가능 (CORS 문제 해결)&lt;/li&gt;
&lt;li&gt;모바일 앱에서 사용하기 좋음&lt;/li&gt;
&lt;li&gt;서버 부하가 적음&lt;/li&gt;
&lt;li&gt;토큰 자체에 정보가 담겨 있어 DB 조회 불필요&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;토큰 크기가 세션 ID보다 큼&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 세 가지 방식 비교&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특성 쿠키 세션 토큰&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;저장 위치&lt;/td&gt;
&lt;td&gt;클라이언트&lt;/td&gt;
&lt;td&gt;서버&lt;/td&gt;
&lt;td&gt;클라이언트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;보안성&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;서버 부하&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;확장성&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;용량&lt;/td&gt;
&lt;td&gt;~4KB&lt;/td&gt;
&lt;td&gt;제한 없음&lt;/td&gt;
&lt;td&gt;크기 유연&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;만료 관리&lt;/td&gt;
&lt;td&gt;자동&lt;/td&gt;
&lt;td&gt;서버 제어&lt;/td&gt;
&lt;td&gt;토큰 자체&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;상태 저장&lt;/td&gt;
&lt;td&gt;Stateless&lt;/td&gt;
&lt;td&gt;Stateful&lt;/td&gt;
&lt;td&gt;Stateless&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Access Token과 Refresh Token&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 서비스에선 보안과 사용자 경험을 동시에 고려해서 Access Token과 Refresh Token을 함께 쓰는 경우가 많다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;Access Token:&lt;/b&gt; 실제 API 요청에 사용, 수명이 짧음 (15분~1시간)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Refresh Token:&lt;/b&gt; Access Token 갱신에 사용, 수명이 김 (7일~30일)&lt;/li&gt;
&lt;/ul&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;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로그인 시 Access Token과 Refresh Token 발급&lt;/li&gt;
&lt;li&gt;API 요청 시 Access Token 사용&lt;/li&gt;
&lt;li&gt;Access Token 만료 시 Refresh Token으로 새 Access Token 발급&lt;/li&gt;
&lt;li&gt;Refresh Token도 만료되면 다시 로그인&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 보안 고려사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;HttpOnly 플래그:&lt;/b&gt; JavaScript로 쿠키 접근을 막아줌&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Secure 플래그:&lt;/b&gt; HTTPS에서만 전송되게 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;SameSite 속성:&lt;/b&gt; CSRF 공격을 방어함&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;/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;세션 ID 예측 불가능하게:&lt;/b&gt; 충분히 긴 랜덤 문자열 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;세션 고정 공격 방어:&lt;/b&gt; 로그인할 때 세션 ID를 새로 생성&lt;/li&gt;
&lt;li&gt;&lt;b&gt;세션 타임아웃:&lt;/b&gt; 적절한 만료 시간 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;안전한 세션 저장소:&lt;/b&gt; Redis 같은 보안이 강화된 저장소 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;HTTPS 사용 필수:&lt;/b&gt; 토큰 전송할 때 암호화가 필수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;짧은 만료 시간:&lt;/b&gt; Access Token은 가능한 짧게 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Refresh Token 보호:&lt;/b&gt; HttpOnly 쿠키에 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;민감 정보 제외:&lt;/b&gt; Payload에 비밀번호, 카드번호 같은 거 넣으면 안 됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;토큰 블랙리스트:&lt;/b&gt; 로그아웃할 때 토큰을 블랙리스트에 추가&lt;/li&gt;
&lt;li&gt;&lt;b&gt;강력한 비밀키:&lt;/b&gt; 충분히 복잡한 시크릿 키 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 어떤 방식을 선택해야 할까..?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/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;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RESTful API 서버&lt;/li&gt;
&lt;li&gt;마이크로서비스 아키텍처&lt;/li&gt;
&lt;li&gt;모바일 앱, SPA(Single Page Application)&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;/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;Access Token은 클라이언트 메모리에 저장 (보안)&lt;/li&gt;
&lt;li&gt;Refresh Token은 HttpOnly 쿠키에 저장 (XSS 방어)&lt;/li&gt;
&lt;li&gt;중요한 작업은 추가 인증 요구 (2FA 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 실전 팁과 베스트 프랙티스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;Local Storage:&lt;/b&gt; XSS에 취약함, 권장하지 않음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Session Storage:&lt;/b&gt; 탭 닫으면 삭제됨, 일부 시나리오에서 유용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리:&lt;/b&gt; 가장 안전하지만 새로고침하면 로그아웃됨&lt;/li&gt;
&lt;li&gt;&lt;b&gt;HttpOnly Cookie:&lt;/b&gt; Refresh Token 저장에 권장&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실무 권장 구조&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;로그인할 때 Access Token(짧은 수명)과 Refresh Token(긴 수명) 발급&lt;/li&gt;
&lt;li&gt;Access Token은 메모리나 상태 관리 라이브러리에 저장&lt;/li&gt;
&lt;li&gt;Refresh Token은 HttpOnly 쿠키에 저장&lt;/li&gt;
&lt;li&gt;Access Token 만료되면 Refresh Token으로 자동 갱신&lt;/li&gt;
&lt;li&gt;Refresh Token 만료되면 다시 로그인 요구&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;성능 최적화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세션 쓸 때는 Redis 같은 In-memory DB 활용&lt;/li&gt;
&lt;li&gt;토큰 페이로드 최소화&lt;/li&gt;
&lt;li&gt;불필요한 인증 요청 캐싱&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 결론&lt;/h2&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;p data-ke-size=&quot;size16&quot;&gt;어떤 방식을 선택하든 항상 보안을 최우선으로 고려하고, HTTPS 사용, 적절한 만료 시간 설정, XSS 및 CSRF 방어 같은 기본적인 보안 원칙을 지켜야 한다.&lt;/p&gt;</description>
      <category>Knowledge</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/35</guid>
      <comments>https://ryuwon-it.tistory.com/35#entry35comment</comments>
      <pubDate>Sun, 23 Nov 2025 23:59:06 +0900</pubDate>
    </item>
    <item>
      <title>[KOSPI FGI] 코스피 공포 탐욕 지수 (KOSPI Fear &amp;amp; Greed Index) 개발기</title>
      <link>https://ryuwon-it.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;평소 미국 주식 투자를 하는 나는 시장이 어느정도 과열된지 확인하기 위해 Put/Call 비율을 보거나 이를 포함한 CNN의 공포 탐욕 지수를 보곤 했다. 최근 국내 시장도 어느정도 투자 메리트가 생겨서 국내 시장도 어느정도 투자하고 있는데 국내 시장에도 ADR, VKOSPI 등은 존재하지만 아직 코스피 공포 탐욕 지수를 보여주는 서비스가 아직 없어 만들어두면 나부터 쓸꺼같아서 만들어보고자 했다. &lt;s&gt;수업 시간마다 이런 생각이 드는건..&lt;/s&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;CNN을 참조하여 심리를 측정하기 위해 7가지의 핵심 지표를 선정했다. 이 데이터들은 크게 주식 시장 내부의 움직임과 자금의 흐름 두 가지로 나뉜다.&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;Market Momentum &amp;amp; Strength&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시장이 실제로 얼마나 뜨거운지 확인한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;시장 모멘텀 (KOSPI Momentum)&lt;/b&gt; &lt;br /&gt;- 현재 KOSPI 지수가 지난 125일(약 6개월) 평균보다 높은지 낮은지를 본다&lt;br /&gt;- 평균보다 높다면 상승세(탐욕), 낮다면 하락세(공포)로 해석&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;주가 강도 (Stock Price Strength)&lt;/b&gt; &lt;br /&gt;- 52주 신고가(최근 1년 중 최고가)를 찍은 종목 수와 신저가(최저가)를 찍은 종목 수를 비교한다&lt;br /&gt;- 신고가 종목이 많다면 시장은 자신감에 차 있는 상태&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. &lt;b&gt;시장 폭 (Market Breadth)&lt;/b&gt; &lt;br /&gt;- 상승한 종목의 거래량과 하락한 종목의 거래량을 비교한다&lt;br /&gt;- 지수는 오르는데 상승 종목 거래량이 적다면? 일부 대형주만 오르는 가짜 상승일 수 있다. 시장 전체가 골고루 오르는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. &lt;b&gt;시장 변동성 (VKOSPI)&lt;/b&gt; &lt;br /&gt;- KOSPI 200 옵션 가격을 통해 산출된 변동성 지수다&lt;br /&gt;- 수치가 높을수록 시장 참여자들이 미래를 불안해하고 있다는 뜻&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. &lt;b&gt;풋/콜 비율 (Put/Call Ratio)&lt;/b&gt; &lt;br /&gt;- 하락에 베팅하는 옵션(Put)과 상승에 베팅하는 옵션(Call)의 거래 비율이다&lt;br /&gt;- 풋 옵션이 지나치게 많다면 시장은 공포에 질려 보험을 들고 있는 상태&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;자금의 흐름 (Safe Haven &amp;amp; Risk Appetite)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 안전 자산 수요 (Safe Haven Demand)&lt;br /&gt;- 최근 20일간 주식 수익률과 국채(안전 자산) 수익률을 비교한다&lt;br /&gt;- 주식보다 채권이 더 인기가 많다면, 투자자들은 겁을 먹고 안전한 곳으로 대피 중인 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 정크 본드 수요 (Junk Bond Demand)&lt;br /&gt;- 투자 적격 등급(AA-) 회사채와 투기 등급(BBB-) 회사채의 금리 차이(스프레드)를 본다&lt;br /&gt;- 시장이 좋을 땐 위험한(BBB-) 채권도 잘 팔려 금리 차이가 줄어든다. 반면 공포장에서는 위험한 채권을 아무도 안 사려 해서 금리 차이가 벌어질 수도 있다&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;가장 중요한 데이터는 밑 사이트와 API를 활용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터는 어디서 가져오나?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국은행 경제통계시스템 (ECOS API)&lt;/b&gt; &lt;br /&gt;- 활용 데이터 : 국채 금리, 회사채 금리&lt;br /&gt;- 금리 정보는 국가 공인 데이터가 가장 정확하다. 매일 발표되는 채권 수익률을 통해 자금의 거대한 흐름(Risk On/Off)을 읽어낼 수도 있고 돈 안내는게 컸다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;한국투자증권 (KIS OpenAPI)&lt;/b&gt; &lt;br /&gt;- 활용 데이터 : KOSPI 지수, 거래량, 등락 종목 수, VKOSPI 등&lt;br /&gt;- 실시간 시세 데이터에 강점이 있는 증권사 API를 통해, 장중 변화하는 시장 분위기를 빠르게 포착할 수 있었고 이것도 돈을 안내서..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 단순히 숫자를 보여주는 것을 넘어, 지금 시장이 이성적인가?라는 질문에 대한 답을 찾기 위한 과정이다. 투자자가 감정에 휘둘리지 않고 객관적인 판단을 내리는 데 이 서비스가 작은 나침반이 되기를 바란다. 현재 어느정도 개발은 완료되었지만 지표 그래프를 만드는데 있어 5년치의 데이터를 마련하는 과정에서 데이터를 구하고 있는 중이다..&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGqfjZ/dJMcacBw6CP/zgBHAyHn2nEABV7vkkiGMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGqfjZ/dJMcacBw6CP/zgBHAyHn2nEABV7vkkiGMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGqfjZ/dJMcacBw6CP/zgBHAyHn2nEABV7vkkiGMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGqfjZ%2FdJMcacBw6CP%2FzgBHAyHn2nEABV7vkkiGMk%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;784&quot; height=&quot;928&quot; data-origin-width=&quot;784&quot; data-origin-height=&quot;928&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>Project</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/34</guid>
      <comments>https://ryuwon-it.tistory.com/34#entry34comment</comments>
      <pubDate>Sun, 23 Nov 2025 23:56:17 +0900</pubDate>
    </item>
    <item>
      <title>[찐빵] Observability 구축기</title>
      <link>https://ryuwon-it.tistory.com/33</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Actuator + Micrometer + Logback + PLG(Loki/Promtail/Grafana)로 로그&amp;middot;메트릭 기반 모니터링 만들기&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;409&quot; data-start=&quot;262&quot; data-ke-size=&quot;size16&quot;&gt;서비스를 운영하다 보면 사용자의 동향을 파악하기 위해 GA를 달기도 하지만,&amp;nbsp;지금 서비스가 어떤 상태인지를 빠르게 파악하는 관측성이 정말 중요해지는데, 이번에 서비스에 &lt;b&gt;Observability(관측성)&lt;/b&gt; 기반을 처음부터 구축해보며, 메트릭과 로그를 통합적으로 수집하는 환경을 구성했다.&lt;/p&gt;
&lt;p data-end=&quot;429&quot; data-start=&quot;411&quot; data-ke-size=&quot;size16&quot;&gt;이번 작업의 핵심은 다음과 같다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;603&quot; data-start=&quot;431&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;461&quot; data-start=&quot;431&quot;&gt;Spring Actuator로 시스템 상태 노출&lt;/li&gt;
&lt;li data-end=&quot;507&quot; data-start=&quot;462&quot;&gt;Micrometer + Prometheus로 API 레이턴시, 처리량 측정&lt;/li&gt;
&lt;li data-end=&quot;549&quot; data-start=&quot;508&quot;&gt;Logback JSON + MDC TraceId로 구조화 로그 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 의존성 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 Gradle에 Spring Boot Actuator와 Micrometer, Logbak을 추가했다&lt;/p&gt;
&lt;pre id=&quot;code_1763304107772&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'io.micrometer:micrometer-registry-prometheus:1.13.11'
implementation 'net.logstash.logback:logstash-logback-encoder:7.4'&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;br /&gt;(단, 해당 엔드포인트는 누구나 접근 가능하면 안되기에 SecurityConfig에 path 설정할 예정)&lt;/p&gt;
&lt;pre id=&quot;code_1763304195614&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;management:
  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus&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;2. Micrometer + Prometheus Registry 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 공통 태그(custom tags)와 AOP 기반 @Timed 계측을 위해 MetricsConfig를 새로 만들었다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Bean
public MeterRegistryCustomizer&amp;lt;MeterRegistry&amp;gt; metricsCommonTags(Environment env) {
    return registry -&amp;gt; registry.config().commonTags(
        &quot;service&quot;, env.getProperty(&quot;spring.application.name&quot;, &quot;jjinbbang-be&quot;),
        &quot;environment&quot;, env.getProperty(&quot;spring.profiles.active&quot;, &quot;default&quot;)
    );
}

@Bean
public TimedAspect timedAspect(MeterRegistry meterRegistry) {
    return new TimedAspect(meterRegistry);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 아래처럼 컨트롤러 또는 서비스 메서드에 @Timed를 추가하면 자동으로 레이턴시/처리량이 측정된다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Timed(value = &quot;map.markers&quot;, description = &quot;지도 마커 조회&quot;)
public ResponseEntity&amp;lt;?&amp;gt; getMarkers(...) {
    ...
}

&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Logback JSON 설정 + MDC TraceId 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 Loki로 보내려면 &lt;b&gt;JSON 구조화 로그&lt;/b&gt;가 필수다. 구조화를 위해 Logback를 활용했는데 logback-spring은 다음과 같댜&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;&amp;lt;encoder class=&quot;net.logstash.logback.encoder.LoggingEventCompositeJsonEncoder&quot;&amp;gt;
    &amp;lt;providers&amp;gt;
        &amp;lt;timestamp /&amp;gt;
        &amp;lt;message /&amp;gt;
        &amp;lt;loggerName /&amp;gt;
        &amp;lt;threadName /&amp;gt;
        &amp;lt;mdc /&amp;gt;         &amp;lt;!-- traceId, spanId 등을 여기에 기록 --&amp;gt;
        &amp;lt;context /&amp;gt;
    &amp;lt;/providers&amp;gt;
&amp;lt;/encoder&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 모든 요청에 자동으로 traceId, spanId, userAgent 등을 넣기 위해 &lt;b&gt;MDC 기반 Trace Logging Filter&lt;/b&gt;도 만들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;요청 시작 &amp;rarr; traceId 생성 &amp;rarr; MDC에 저장 &amp;rarr; 로그 출력 &amp;rarr; 요청 종료 후 clear&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 구조 덕분에 Grafana Explore에서&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;{service=&quot;jjinbbang-be&quot;, trace_id=&quot;xxx&quot;}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처럼 &amp;ldquo;요청 단위&amp;rdquo;로 로그를 추적할 수 있게 되었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 로그 파일 정책 및 롤링 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 시 로그 파일은 무한정 커지기 때문에 롤링이 필수다.&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;rollingPolicy class=&quot;ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy&quot;&amp;gt;
    &amp;lt;fileNamePattern&amp;gt;logs/app.%d{yyyy-MM-dd}.%i.gz&amp;lt;/fileNamePattern&amp;gt;
    &amp;lt;maxFileSize&amp;gt;50MB&amp;lt;/maxFileSize&amp;gt;
    &amp;lt;maxHistory&amp;gt;30&amp;lt;/maxHistory&amp;gt;
&amp;lt;/rollingPolicy&amp;gt;

&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;50MB 단위로 압축하여 저장&lt;/li&gt;
&lt;li&gt;30일 보관&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 Promtail이 이 경로를 읽어서 Loki로 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=&amp;gt; 근데 함정은 그라파나 클라우드에서는 프리티어라 14일만 보관되긴 한다 허허헣 뭐.. 서버에라도 저장해두면 좋으니까..&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. application.yml에 메트릭&amp;middot;액추에이터 설정 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 동작을 위해 yml에 추가한 핵심 설정은 다음과 같다&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;management:
  metrics:
    tags:
      application: ${spring.application.name}
    distribution:
      percentiles-histogram:
        http.server.requests: true

  endpoint:
    health:
      show-details: when_authorized

  endpoints:
    web:
      exposure:
        include: health, info, metrics, prometheus

  prometheus:
    metrics:
      export:
        enabled: true

&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: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;histogram 활성화 &amp;rarr; p95, p99 계산 가능&lt;/li&gt;
&lt;li&gt;/actuator/prometheus 노출&lt;/li&gt;
&lt;li&gt;필요 최소한의 endpoint만 공개하기&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&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-origin-width=&quot;1537&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BjpFI/dJMcagw66hU/zaoHanPRqOy92QhlHEFnvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BjpFI/dJMcagw66hU/zaoHanPRqOy92QhlHEFnvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BjpFI/dJMcagw66hU/zaoHanPRqOy92QhlHEFnvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBjpFI%2FdJMcagw66hU%2FzaoHanPRqOy92QhlHEFnvk%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;1537&quot; height=&quot;289&quot; data-origin-width=&quot;1537&quot; data-origin-height=&quot;289&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;/p&gt;</description>
      <category>Project</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/33</guid>
      <comments>https://ryuwon-it.tistory.com/33#entry33comment</comments>
      <pubDate>Sun, 16 Nov 2025 23:49:14 +0900</pubDate>
    </item>
    <item>
      <title>[찐빵] 옵저버빌리티 설계</title>
      <link>https://ryuwon-it.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size14&quot;&gt;찐빵 서비스의 경우 &lt;span data-token-index=&quot;1&quot;&gt;MySQL&lt;/span&gt;, &lt;span data-token-index=&quot;3&quot;&gt;MongoDB&lt;/span&gt;, &lt;span data-token-index=&quot;5&quot;&gt;Redis&lt;/span&gt;, &lt;span data-token-index=&quot;7&quot;&gt;S3&lt;/span&gt;, &lt;span data-token-index=&quot;9&quot;&gt;Google Sheets&lt;/span&gt; 등 다양한 외부 리소스와 상호작용하고, &lt;span data-token-index=&quot;11&quot;&gt;건물&amp;middot;리뷰 조회/관리, 지도 검색, 사용자 인증&amp;middot;설정&lt;/span&gt; 등의 다양한 API들도 존재하기에 메트릭과 구조적인 로그 체계가 필수적이므로 옵저빌리티를 설계해보았습니다..!&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 수집할 핵심 메트릭 정의(API latency, error rate, RPS 등)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;범주 메트릭 태그/분류 설명 및 수집 이유&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;&lt;b&gt;HTTP 요청 지연&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;http.server.requests (Timer)&lt;/td&gt;
&lt;td&gt;uri, method, status, outcome, exception&lt;/td&gt;
&lt;td&gt;모든 REST 엔드포인트(건물 상세, 리뷰 CRUD, 지도 검색, 인증/사용자 API)의 응답 시간을 계층별로 가시화하여 병목 식별 (p50/p95/p99 추출)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;HTTP 오류율&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;http.server.requests (Counter)&lt;/td&gt;
&lt;td&gt;동일 + status 그룹 (2xx/4xx/5xx)&lt;/td&gt;
&lt;td&gt;status &amp;gt;= 400 비율을 계산해 API 안정성 모니터링. ControllerAdvice가 공통 예외를 처리하므로 특정 예외 유형을 태그로 전파하면 오류 유형별 추적이 가능할듯&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;요청 처리량(RPS)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;http.server.requests.count&lt;/td&gt;
&lt;td&gt;uri, method&lt;/td&gt;
&lt;td&gt;주요 API(지도 검색/리뷰/건물)의 초당 요청 수를 측정해 갑작스런 트래픽 급증을 감지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;DB 커넥션 풀 상태&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;hikaricp.connections.*&lt;/td&gt;
&lt;td&gt;pool&lt;/td&gt;
&lt;td&gt;MySQL 기반 리뷰/지도 조회가 집중되므로 Hikari 커넥션 사용량&amp;middot;대기시간을 관찰해 풀 사이즈를 조정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Mongo/Redis 연산률&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;mongodb.command.*, redis.commands&lt;/td&gt;
&lt;td&gt;collection, command, result&lt;/td&gt;
&lt;td&gt;리뷰 상세&amp;middot;키워드(Mongo), 메일/토큰 캐시(Redis)의 호출량&amp;middot;실패율 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;외부 연동 지연&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;사용자 정의 Timer (S3, Google Sheets, 메일, OAuth)&lt;/td&gt;
&lt;td&gt;service, operation, status&lt;/td&gt;
&lt;td&gt;S3 업로드/삭제, Google Sheets append, 메일 전송, OAuth 토큰 재발급 등 외부 I/O의 평균 지연과 실패율을 별도 측정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;스케줄러 성과&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Timer/Counter(user.scheduler.finalizeDeletionBatch)&lt;/td&gt;
&lt;td&gt;result&lt;/td&gt;
&lt;td&gt;탈퇴 유예 만료 사용자 정리 배치의 실행 주기&amp;middot;성공/실패 횟수를 수집&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 커스텀 메트릭 대상(중요 비즈니스 로직) 식별&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;리뷰 작성/수정/삭제 성공&amp;middot;실패 카운터 및 처리 시간&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리뷰 CRUD는 저장소(JPA, Mongo, S3)를 갱신하므로 타입(일반/기숙사/공인중개사)별로 Counter와 Timer를 두어 실패 원인을 분류&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;지도는 필터와 연산을 수행하므로 응답 시간과 반환 개수를 측정해 필터 조합에 따른 부하를 분석&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;기획용. 사용자들의 관심은 원룸과 같은 건물일지 기숙사인지 파악하기 위함&lt;/li&gt;
&lt;li&gt;상세 응답은 건물 타입별로 키워드 집계를 사용하므로 사용자 관심 분포를 파악 &amp;rArr; 차후 캐싱에도 도움될 듯..?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OAuth 로그인/회원가입/토큰 재발급 성공&amp;middot;예외 카운터&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메일 인증 코드 발송&amp;middot;검증 시도 및 실패 사유별 Counter&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;/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;A/B 테스트. 향후 탭 삭제를 해도 될까에 대해 여부 판단&lt;/li&gt;
&lt;li&gt;사용자 개인화 기능의 사용량을 측정해 UI 개선 우선순위를 판단&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Google Sheets 전송 성공률&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자 탈퇴 사유&amp;middot;문의 전송 시 Sheets API 오류를 추적&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;S3 presigned URL 발급/삭제 오류 카운터&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 업로드용 URL 발급과 삭제 오류를 추적해 CDN 연계 이상을 조기에 발견&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;UserScheduler에서 삭제된 계정 수를 Counter로 기록해 데이터 정리 현황을 모니터링&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. JSON 구조화 로그 필드 목록 정의(TraceID, URI, Status 등)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;traceId의 경우 MDC가 필요해서 향후 도입할 수도 있습니다. 감안해서 봐주셔욥&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드 내용 이유&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;timestamp, level, logger, thread&lt;/td&gt;
&lt;td&gt;기본 메타데이터&lt;/td&gt;
&lt;td&gt;시계열 정렬 및 로거 식별을 위해 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;traceId, spanId, requestId&lt;/td&gt;
&lt;td&gt;요청 상관관계&lt;/td&gt;
&lt;td&gt;분산 추적&amp;middot;멀티 서비스 연동 시 연관 로그 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;service, environment, version&lt;/td&gt;
&lt;td&gt;배포 메타&lt;/td&gt;
&lt;td&gt;다중 환경/버전 운영 시 필터링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;httpMethod, uri, query, status, latencyMs&lt;/td&gt;
&lt;td&gt;HTTP 컨텍스트&lt;/td&gt;
&lt;td&gt;주요 API 호출 정보를 구조화하여 SLA 위반 원인 파악&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;remoteIp, userAgent, referer&lt;/td&gt;
&lt;td&gt;클라이언트 정보&lt;/td&gt;
&lt;td&gt;비정상 패턴(봇/공격) 탐지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;userId, oauthProvider&lt;/td&gt;
&lt;td&gt;인증 컨텍스트&lt;/td&gt;
&lt;td&gt;@AuthenticationPrincipal Users를 통해 확보한 식별자 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;responseCode, errorCode, exception, stacktrace&lt;/td&gt;
&lt;td&gt;오류 정보&lt;/td&gt;
&lt;td&gt;ControllerAdvice에서 예외 유형과 메시지를 JSON 필드로 출력하여 분석 자동화&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;businessEvent, entityId, resultCount&lt;/td&gt;
&lt;td&gt;도메인 키&lt;/td&gt;
&lt;td&gt;예: review.create, map.search 등 비즈니스 이벤트 구분 및 결과 크기 기록&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Actuator 노출 endpoint 범위 확정&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는&amp;nbsp;&lt;b&gt;health&lt;/b&gt;, &lt;b&gt;info&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;Production&lt;/b&gt; [Release 브랜치]
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;health, info, metrics, prometheus&lt;/b&gt;&amp;nbsp;endPoint&lt;/li&gt;
&lt;li&gt;&lt;b&gt;/prometheus&lt;/b&gt;는 VPC 내부 혹은 Sidecar 프록시를 통해서만 접근; Spring Security 또는 API Gateway로 보호 (민감 정보가 많아 그라파나 클라우드로만 나갈 수 있도록)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;health&lt;/b&gt;는 liveness/readiness 분리(management.endpoint.health.probes.enabled=true) 후 외부 로드밸런서에는 간략 응답만 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Dev/Staging&lt;/b&gt; [Test 브랜치]
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;디버깅 목적의&amp;nbsp;&lt;b&gt;/loggers&lt;/b&gt;,&amp;nbsp;&lt;b&gt;/env&lt;/b&gt;,&amp;nbsp;&lt;b&gt;/configprops&lt;/b&gt; 등을 추가 노출하되, Basic Auth 또는 IP ACL로 제한&lt;/li&gt;
&lt;li&gt;&lt;b&gt;show-details=always&lt;/b&gt;로 health 세부 정보를 개발자에게 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. Micrometer 바인더 리스트 확정(JVM/Thread/GC/DB/Redis 등)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바인더 적용 이유&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;JvmMemoryMetrics, JvmGcMetrics, JvmThreadMetrics, ClassLoaderMetrics, ProcessorMetrics, UptimeMetrics&lt;/td&gt;
&lt;td&gt;Spring Boot JVM 애플리케이션 기본 리소스 감시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LogbackMetrics&lt;/td&gt;
&lt;td&gt;SLF4J/Logback 로거를 사용하므로 로그 이벤트를 카운트하여 경고/오류 폭주 감지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TomcatMetrics&lt;/td&gt;
&lt;td&gt;내장 컨테이너 스레드/커넥션 상태 파악&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HikariDataSourceMetrics, HibernateMetrics&lt;/td&gt;
&lt;td&gt;JPA &amp;amp; Hikari 기반 DB 접근의 커넥션 풀, 쿼리 캐시 상태 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MongoMetricsCommandListener&lt;/td&gt;
&lt;td&gt;리뷰 상세&amp;middot;키워드 컬렉션 MongoDB 사용에 대한 지표 수집&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LettuceMetrics (또는 RedisConnectionMetrics)&lt;/td&gt;
&lt;td&gt;RedisTemplate/StringRedisTemplate 기반 인증 코드/토큰 저장소 성능 모니터링&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ExecutorServiceMetrics / SchedulerMetrics&lt;/td&gt;
&lt;td&gt;@Scheduled 삭제 배치 등 스케줄러 스레드풀 상태 추적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용자 정의 MeterBinder (S3, Mail, Google API)&lt;/td&gt;
&lt;td&gt;외부 API 클라이언트(S3, Google Sheets, MailSendService) 호출의 지연/오류를 묶어 보고&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 메트릭&amp;middot;로그 정책 문서화(dev/prod)&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메트릭/로그 Naming 규칙, 태그 사용 가이드, PII(개인정보) 마스킹 규칙 문서화&lt;/li&gt;
&lt;li&gt;배포 시점마다 버전 태그(&lt;b&gt;buildVersion&lt;/b&gt;)를 메트릭과 로그에 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Development&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;샘플링 비율을 높여(예: 100%) 상세 로그 및 디버깅용 메트릭 수집&lt;/li&gt;
&lt;li&gt;Actuator에서 추가 엔드포인트(&lt;b&gt;loggers&lt;/b&gt;,&amp;nbsp;&lt;b&gt;env&lt;/b&gt;) 허용, 콘솔 Pretty JSON 로그 사용&lt;/li&gt;
&lt;li&gt;메트릭 저장은 경량 Prometheus/InfluxDB 등 로컬 인스턴스를 사용하고, 7일 보관&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Production&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;로그 샘플링/레벨 정책: INFO 기본, ERROR는 전량 수집, DEBUG는 비활성&lt;/li&gt;
&lt;li&gt;JSON 로그를 중앙 수집(ELK, Cloud Logging) 후 30일/90일 보관 정책&lt;/li&gt;
&lt;li&gt;메트릭은 Prometheus&amp;rarr;Grafana 대시보드, 혹은 Cloud Monitoring에 연동; 30~90일 유지&lt;/li&gt;
&lt;li&gt;PII가 포함될 수 있는 필드는 해시 처리 후 저장, 요청/응답 Body는 비활성(필요 시 화이트리스트만)&lt;/li&gt;
&lt;li&gt;비상 상황을 대비해 Rate Limit&amp;middot;Circuit Breaker 메트릭을 추가 문서화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 문서화에 대해서는 AI한테 맡겨봤는데 위에 적힌 내용은 30, 90일이지만, 그라파나 클라우드 Free Tier의 경우에는 14일 밖에 저장이 안된다는 슬픈 사실&amp;hellip;&lt;/p&gt;</description>
      <category>Project</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/32</guid>
      <comments>https://ryuwon-it.tistory.com/32#entry32comment</comments>
      <pubDate>Sun, 16 Nov 2025 23:17:01 +0900</pubDate>
    </item>
    <item>
      <title>[SWEA Extended #2] SWEA 분석해보기</title>
      <link>https://ryuwon-it.tistory.com/31</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;453&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2cR2E/dJMcaihoagV/Abp4A0ov1GOLKKrjkY2J0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2cR2E/dJMcaihoagV/Abp4A0ov1GOLKKrjkY2J0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2cR2E/dJMcaihoagV/Abp4A0ov1GOLKKrjkY2J0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2cR2E%2FdJMcaihoagV%2FAbp4A0ov1GOLKKrjkY2J0k%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;547&quot; height=&quot;453&quot; data-origin-width=&quot;547&quot; data-origin-height=&quot;453&quot;/&gt;&lt;/span&gt;&lt;/figure&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;우선적으로 SWEA를 사용해보고 로그인 세션 자체가 굉장히 짧다고 느꼈었고 별도의 클릭이나 페이지로의 리다이렉션이 없다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 번의 테스트를 거쳐본 결과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최대 30분 이상은&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 실험을 해본 결과&amp;nbsp;&lt;/p&gt;</description>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/31</guid>
      <comments>https://ryuwon-it.tistory.com/31#entry31comment</comments>
      <pubDate>Sat, 15 Nov 2025 23:57:10 +0900</pubDate>
    </item>
    <item>
      <title>[ SWEA Extended #2] SWEA 확장프로그램 제작기 인트로</title>
      <link>https://ryuwon-it.tistory.com/30</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SWEA에서 문제 풀다 보면 살짝 귀찮은 순간들이 있다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제 복붙 했을때 import를 빼먹고 제출하려고 한다거나, 보안이 중요하긴 하지만 IDE에서 문제풀고 있다가 세션이 만료되어 재로그인 해야한다거나, 예제 테스트 할때 복붙해서 계속 확인해야한다 등등&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SWEA Extended&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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;import 누락 확인&lt;/li&gt;
&lt;li&gt;복붙 시 자동 class 명 변경&lt;/li&gt;
&lt;li&gt;정답 필터(정답인 문제는 안보이게 처리가능)&lt;/li&gt;
&lt;li&gt;예제 복사, txt 전체 복사 및 확인 (예제 전체복사 버튼)&lt;/li&gt;
&lt;li&gt;로그인 세션 좀 더 유지(보안과 직결되기에 설정 후 사용 가능)&lt;/li&gt;
&lt;li&gt;컴파일 시 또는 RUN 시 예제 txt와 비교하여 맞는지 아닌지 판단해주는 기능&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;다크 모드
&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;swea 코드 제출 텍스트 영역에 색깔 추가(자바, C 등등)&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;해당 문제의 사용자가 사용한 언어 중 최단 시간이 걸린 문제 풀이자 2명, 최소 길이 코드 풀이자 2명 보여주기 (단, PASS 한 사람 중)&lt;/li&gt;
&lt;li&gt;몇퍼센트에서 틀렸는지 (해당 기능은 애초에 BOJ와 다르게 주고 받는 신호가 보이지가 않아서 좀 더 분석해봐야 할 것 같다..)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SWEA 사이트를 사용하면서 SWEA의 UI를 해치지 않는 선에서 사용자들에게 유용한 기능들을 제공하는게 목표로 열심히 만들어 봐야겠다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://startlink.blog/2015/12/21/boj%eb%8a%94-%ec%96%b4%eb%96%bb%ea%b2%8c-%eb%a7%8c%eb%93%a4%ec%96%b4%ec%a1%8c%ec%9d%84%ea%b9%8c/&quot;&gt;BOJ는 어떻게 만들어졌을까?&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://startlink.blog/2019/08/05/boj-10-years-2%eb%b6%80/&quot;&gt;BOJ 10 Years 2부&lt;/a&gt;&lt;/p&gt;</description>
      <category>Project</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/30</guid>
      <comments>https://ryuwon-it.tistory.com/30#entry30comment</comments>
      <pubDate>Sat, 15 Nov 2025 23:51:28 +0900</pubDate>
    </item>
    <item>
      <title>[자료구조] 정렬 #1</title>
      <link>https://ryuwon-it.tistory.com/29</link>
      <description>&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 버블 정렬 (Bubble Sort)&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;버블 정렬은 정렬 알고리즘 중 가장 단순한 알고리즘으로, 단순한 만큼 비효율적임.&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;O(n^2) 이며 공간복잡도는 하나의 배열만 사용하므로&amp;nbsp;O(n)을 가짐&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSW4tK/dJMcaboZsCC/tSFBaAbsPAdGCGHYlk2R70/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSW4tK/dJMcaboZsCC/tSFBaAbsPAdGCGHYlk2R70/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSW4tK/dJMcaboZsCC/tSFBaAbsPAdGCGHYlk2R70/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cSW4tK/dJMcaboZsCC/tSFBaAbsPAdGCGHYlk2R70/img.gif&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;480&quot; height=&quot;150&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 배열에 n개의 요소가 있을 경우 1번째 원소 vs 2번째 원소를 비교하고 2번째 원소 vs 3번째 원소를 비교하고, ... n-1번째 원소 vs n번째 요소를 비교하면 1회전 비교가 끝난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1회전이 끝나면 가장 큰 원소는 맨 뒤에 위치하게 되므로 2회전 비교에서는 제외된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마찬가지로 두 번째로 큰 원소는 가장 큰 원소 앞에 위치하게 되므로 3회전 비교에서는 제외된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 버블 정렬을 1회 수행할 때 마다 정렬해야 할 원소가 하나씩 줄어든다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void bubbleSort(int arr[], int n) {
	int temp;
	for (int i = 0; i &amp;lt; n; i++) { //회수
		for (int j = 0; j &amp;lt; n - 1 - i;j++) { 
			if (arr[j] &amp;gt; arr[j + 1]) { // 인접한 인덱스 비교
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 삽입 정렬 (Insert Sort)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정렬을 진행할 원소의 index보다 낮은 곳에 있는 원소들을 탐색하며 알맞은 위치에 삽입해주는 정렬 알고리즘&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 index는 비교할 원소가 없기 때문에 두 번째 index부터 시작.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;알고리즘이 동작하는 동안 계속해서 정렬이 진행되므로 반드시 맨 왼쪽 index까지 탐색하지 않아도 된다는 장점이 있다. 모두 정렬되어 있는 Optimal한 경우 모든 원소가 한 번씩만 비교되므로&amp;nbsp;O(n)의 시간 복잡도를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 최악의 경우 O(n^2)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;506&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Q25PH/dJMcaj8oMfD/3BYZBhhk4yb1ftadkSZHJk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Q25PH/dJMcaj8oMfD/3BYZBhhk4yb1ftadkSZHJk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Q25PH/dJMcaj8oMfD/3BYZBhhk4yb1ftadkSZHJk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/Q25PH/dJMcaj8oMfD/3BYZBhhk4yb1ftadkSZHJk/img.gif&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;772&quot; height=&quot;506&quot; data-origin-width=&quot;772&quot; data-origin-height=&quot;506&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입 정렬을 코드로 구현하면 아래와 같다. 첫 번째 for문은 정렬할 원소를 차례대로 선택하는 것이며, 두 번째 for문은 정렬할 원소보다 아래 인덱스에 있는 요소와 비교하기 위함이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void insertSort(int arr[], int n) {
	int key, i, j;
	for (i = 1; i &amp;lt; n; i++) {//index 1 부터 모두 확인
		key = arr[i];
		for (j = i - 1; j &amp;gt;= 0 &amp;amp;&amp;amp; key &amp;lt; arr[j]; j--) { // key의 값보다 크면 인덱스를 +1 이동
			arr[j + 1] = arr[j];
		}// 아니라면 그 위치가 key가 있어야할 위치
		arr[j + 1] = key; //외부에서 j를 정의했기 때문에 for문 밖에서도 사용가능
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 선택 정렬 (Selection Sort)&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-origin-width=&quot;946&quot; data-origin-height=&quot;262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dN5MhL/dJMcaacx2uh/SLoRC0umynCzxXiJ0OAiG0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dN5MhL/dJMcaacx2uh/SLoRC0umynCzxXiJ0OAiG0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dN5MhL/dJMcaacx2uh/SLoRC0umynCzxXiJ0OAiG0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dN5MhL/dJMcaacx2uh/SLoRC0umynCzxXiJ0OAiG0/img.gif&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;946&quot; height=&quot;262&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간복잡도 최선, 평균, 최악 모두&amp;nbsp;O(n^2)에 해당하는 비효율적인 알고리즘으로 정렬 여부와 상관없이 모든 경우의 수를 전부 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작방식 3단계로 구성&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;최소값을 맨 앞의 값과 바꾼다.&lt;/li&gt;
&lt;li&gt;바꿔준 맨 앞 값을 제외한 나머지 원소를 동일한 방법으로 바꿔준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void selectSort(int arr[], int n) {
	int indexMin, temp; // 최솟값 인덱스
	for (int i = 0; i &amp;lt; n - 1; i++) {
		indexMin = i;
		for (int j = i + 1; j &amp;lt; n; j++) {
			if (arr[indexMin] &amp;gt; arr[j]) {
				indexMin = j;
			}
		}
		temp = arr[i];
		arr[i] = arr[indexMin];
		arr[indexMin] = temp;
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 퀵 정렬 (Quick Sort)&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;퀵 정렬에는 피봇(Pivot)이라는 개념을 사용 (피봇은 한 마디로 정렬 될 기준 원소)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 배열의 첫 번째 값이나 중앙 값을 선택&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;O(nlogn)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LvNSN/dJMcabbslnP/yVMQwG4qTbnjvqY1VitRjk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LvNSN/dJMcabbslnP/yVMQwG4qTbnjvqY1VitRjk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LvNSN/dJMcabbslnP/yVMQwG4qTbnjvqY1VitRjk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/LvNSN/dJMcabbslnP/yVMQwG4qTbnjvqY1VitRjk/img.gif&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;800&quot; height=&quot;223&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가령 예를 들어 배열 [5, 6, 1, 4, 2, 3, 7]이 있고, 피봇을 임의로 4를 선택했다 가정한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 4를 기준으로 작은 것은 왼쪽으로 큰 것은 오른쪽으로 보내 [1, 2, 3] &amp;lt; 4 &amp;lt; [5, 6, 7]를 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 왼쪽에서부터 임의의 피봇 2를 설정하여 [1] &amp;lt; 2 &amp;lt; [3]을 생성하고 오른쪽에선 임의의 피봇 6를 설정하여 [5] &amp;lt; 6 &amp;lt; [7]로 나눈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 배열 길이가 1이 된다면 가장 정렬 완료된 것이므로 분할된 배열을 합쳐 줌으로써 정렬을 마침&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;void quickSort(int arr[], int L, int R) {
      int left = L, right = R;
      int pivot = arr[(L + R) / 2];    // pivot 설정 (가운데) 
      int temp;
      do
      {
        while (arr[left] &amp;lt; pivot) // left가 pivot보다 큰 값을 만나거나 pivot을 만날 때까지 
            left++;
        while (arr[right] &amp;gt; pivot) // right가 pivot보다 작은 값을 만나거나 pivot을 만날 때까지 
            right--;
        if (left&amp;lt;= right)    // left가 right보다 왼쪽에 있다면 교환 
        {
            temp = arr[left];
            arr[left] = arr[right];
            arr[right] = temp;
            /*left 오른쪽으로 한칸, right 왼쪽으로 한칸 이동*/
            left++;
            right--;
        }
      } while (left&amp;lt;= right);    // left가 right 보다 오른쪽에 있을 때까지 반복 
 
    /* recursion */
    if (L &amp;lt; right)
        quickSort(arr, L, right);    // 왼쪽 배열 재귀적으로 반복 
 
    if (left &amp;lt; R)
        quickSort(arr, left, R);    // 오른쪽 배열 재귀적으로 반복 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참조&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://roytravel.tistory.com/328&quot;&gt;[자료구조] 기본 정렬 알고리즘 총 정리&lt;/a&gt;&lt;/p&gt;</description>
      <category>Knowledge/자료구조</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/29</guid>
      <comments>https://ryuwon-it.tistory.com/29#entry29comment</comments>
      <pubDate>Sun, 9 Nov 2025 23:33:21 +0900</pubDate>
    </item>
    <item>
      <title>[CS 기초 시리즈 #2] DNS와 서버 그리고 클라우드</title>
      <link>https://ryuwon-it.tistory.com/28</link>
      <description>&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;&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;개발은 우리 눈에 보이는 영역을 개발하는 일과 눈에 보이지 않는 뒷단을 개발하는 일 크게 두 분야로로 나뉜다고 했죠.&lt;br /&gt;여기서 저희가 맡은 백엔드는 &lt;b&gt;프론트엔드에 있는 사용자들이 원하는 행동(이벤트)을 처리하는 역할 입니다&lt;/b&gt;&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;/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;그럼 API란 무엇이였나요? &lt;b&gt;API&lt;/b&gt;란? Application Programming Interface.&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;/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;실제 통신이 어떻게 이뤄지는지도 저희가 알아봤었죠.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전 세계의 컴퓨터가 인터넷이라는 네트워크에 묶여 IP 주소를 통해 서로를 식별하고 찾아갈 수 있다는 건 알게되었지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 구글을 접속할때 &amp;ldquo;216.58.200.14&amp;rdquo; 가 아닌 &lt;a href=&quot;http://www.google.com&quot;&gt;www.google.com&lt;/a&gt; 을 입력하였을 때, 접속이 되는 이유는 무엇일까요?&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;DNS&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유튜브(&lt;a href=&quot;http://www.youtube.com&quot;&gt;www.youtube.com&lt;/a&gt;)에 접속하여 영상을 본다고 가정 하였을 경우&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1GlzU/dJMcad79g3K/MkNjpIJNsq9XWVoknu3TD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1GlzU/dJMcad79g3K/MkNjpIJNsq9XWVoknu3TD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1GlzU/dJMcad79g3K/MkNjpIJNsq9XWVoknu3TD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1GlzU%2FdJMcad79g3K%2FMkNjpIJNsq9XWVoknu3TD0%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;792&quot; height=&quot;290&quot; data-origin-width=&quot;792&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;( 클라이언트는 유튜브를 볼 디바이스를 의미하고, 서버는 유튜브 내부의 영상이 저장되어있는 데이터센터)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크는 위에서 설명한대로 라우터를 활용한 광케이블 통신으로 구성되어있을것이고 서버의 IP주소를 통해 해당하는 서버가 있는 네트워크로 찾아가게 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기서 문제가 있습니다&lt;/b&gt;. 과연 모든 웹페이지의 IP주소를 어떻게 다 외울 수 있을까&amp;hellip;? IP주소는 단순한 숫자의 나열이므로 외우기가 쉽지 않음. 이 문제를 해결하기 위해 &amp;ldquo;&lt;b&gt;도메인&lt;/b&gt;&amp;rdquo;이라는 개념이 등장&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;br /&gt;&lt;b&gt;도메인이란?&lt;/b&gt; &lt;br /&gt;&lt;br /&gt;IP 주소는사람이 기억하기 어렵기 떄문에 각 IP에 기억이 쉽게끔 이름을 부여할 수 있게 했는데, 이것을 도메인이라 부름.&lt;br /&gt;&lt;br /&gt;ex) www.naver.com &lt;br /&gt;www.google.com&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bslmTp/dJMcake8h1l/HX8QHNMLy5KkdNyCzROg4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bslmTp/dJMcake8h1l/HX8QHNMLy5KkdNyCzROg4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bslmTp/dJMcake8h1l/HX8QHNMLy5KkdNyCzROg4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbslmTp%2FdJMcake8h1l%2FHX8QHNMLy5KkdNyCzROg4k%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;661&quot; height=&quot;443&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내 컴퓨터 &amp;rarr; 도메인 입력 &amp;rarr; DNS 서버 &amp;rarr; IP 획득 &amp;rarr; 라우터 &amp;rarr; ARP 프로토콜 &amp;rarr; MAC 주소 획득 &amp;rarr; 서버 &amp;rarr; HTTP 요청과 응답&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1762095419323&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;POST /payment-sync HTTP/1.1

Accept: application/json
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 83
Content-Type: application/json
Host: intropython.com
User-Agent: HTTPie/0.9.3

{
    &quot;imp_uid&quot;: &quot;imp_1234567890&quot;,
    &quot;merchant_uid&quot;: &quot;order_id_8237352&quot;,
    &quot;status&quot;: &quot;paid&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&lt;/b&gt;&amp;nbsp;브라우저는 DNS서버로 가서 도메인을 확인하고 웹사이트가 있는 서버의 IP주소를 찾음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&lt;/b&gt;&amp;nbsp;서버에게 웹사이트의 사본을 클라이언트에게 보내달라는 HTTP 요청 메시지를 서버로 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3.&lt;/b&gt;&amp;nbsp;서버는 클라이언트의 요청을 승인하고 웹 사이트의 정보를 패킷으로 묶어서 브라우저로 보냄&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4.&lt;/b&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;p data-ke-size=&quot;size16&quot;&gt;실제 네트워크는 IP를 통해 목적지까지 길을 찾아가지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사람들은 그 많은 사이트의 IP 주소를 기억할 수 없으니 좀 더 기억하기 쉬운 도메인을 통해&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희에게 사이트에 대한 정보를 보여주는 &amp;ldquo;서버&amp;rdquo;에 대해서 알아 보도록 하겠습니다.&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;/h2&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;p data-ke-size=&quot;size16&quot;&gt;서버(server)의 사전적 의미 자체는 &amp;lsquo;서비스 (service)를 제공하는 사람&amp;rsquo;&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;인터넷 네트워크 환경에서 다른 컴퓨터에게&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;다양한 기능, 데이터, 서비스를 제공하는 컴퓨터나 소프트웨어 전반&amp;rdquo;&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;lsquo;사람&amp;rsquo;에 국한되던 원래 의미에서&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lsquo;서버&amp;rsquo;는 누군가에게 서비스를 &amp;lsquo;제공&amp;rsquo;하는 &amp;lsquo;역할&amp;rsquo; 자체를 지칭하게 되버린거죠.&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;/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;b&gt;도메인 서버&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;&lt;b&gt;이메일 서버&lt;/b&gt; : 이메일을 관리하기 위한 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB 서버&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;&lt;b&gt;미디어 서버&lt;/b&gt; : 미디어를 제공 관리하기 위한 서버&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등등&amp;hellip;&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;수없이 많은 기능들을 제공하는 컴퓨터와 소프트웨어들이 존재합니다.&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;lsquo;하드웨어&amp;rsquo;만 있는 것이 아닙니다.&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;예를 들면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;홈페이지가 인터넷에서 구현되기 위해서는 여러 종류의 서버가 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에 가장 중요한 1가지가 웹 서버 (web server) 라고 할 수 있습니다.&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;/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;(각각의 목적에 맞춰 여러 대가 될 수도 있는)&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;rsquo;웹 서버 프로그램&amp;rsquo;이 필요합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버 프로그램에는 아파치 (Apache), 엔진엑스 (Nginx) 등 여러 가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 저희는 어떤 서버를 만들게 되는 걸까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WAS &lt;b&gt;웹 어플리케이션 서버(WAS, Web Application Server)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 조회나 로직 처리를 요구하는 동적 컨텐츠를 제공하기 위해 만들어진 Application Server&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ 웹 서버의 역할과 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버는 클라이언트가 웹 브라우저를 통해 요청한 정적 콘텐츠를 제공하는 역할을 합니다. 웹 서버는 주로 HTTP 프로토콜을 사용하여 작동하며, 클라이언트가 URL을 통해 요청한 웹 페이지를 찾아 전송해줍니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: 회사 홈페이지, 블로그, 뉴스 사이트 등&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;✅ WAS 서버의 역할과 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WAS 서버는 웹 애플리케이션을 실행하여 동적 콘텐츠를 생성하고, 웹 서버와 클라이언트 간의 데이터 처리를 담당하는 역할을 합니다. WAS 서버는 클라이언트의 요청에 따라 데이터베이스에서 정보를 가져오거나, 웹 애플리케이션을 실행하여 동적인 웹 페이지를 생성한 후 결과를 웹 서버에 전달합니다. 웹 서버는 이를 받아 클라이언트에게 전달합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: 온라인 쇼핑몰, 은행 인터넷 뱅킹, SNS 등&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 웹 서비스 환경에서는 웹 서버와 WAS 서버가 협업하여 작동합니다. 일반적으로 웹 서버는 정적 콘텐츠를 처리하고 WAS 서버는 동적 콘텐츠를 처리하는 역할을 맡아, 사용자에게 원활하고 다양한 웹 서비스를 제공하게 됩니다. 이를 통해 웹 사이트의 로딩 속도와 서비스 품질이 향상되며, 웹 애플리케이션의 성능이 개선됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희가 JAVA/SPRING을 통해 개발하게 될 API들도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WAS인 톰캣서버에서 돌아가게 됩니다.&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;/p&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;p data-ke-size=&quot;size16&quot;&gt;배포하는데 큰 어려움을 겪게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 나온 것이 클라우드 입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클라우드(AWS)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라우드(Cloud)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드(Cloud)란&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;h3 data-ke-size=&quot;size23&quot;&gt;클라우드 서비스(Cloud Service)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 서비스(Cloud Service)란&amp;nbsp;&lt;b&gt;'인터넷 서비스'&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷을 이용해 서비스를 제공하는 것을 '클라우드 서비스' 또는 'SaaS' 라고 부름&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라우드 컴퓨팅(Cloud Computing)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드 컴퓨팅(Cloud Computing)이란 내 컴퓨터의 서버, 네트워크 등을 사용하는 것이 아닌&amp;nbsp;**'컴퓨팅 리소스'**를 제공하는 회사를 통해 서버, 네트워크를 제공받아 사용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨팅 리소스를 제공하는 대표적인 클라우드 서비스 제공자에는 'Goggle Cloud', 'MS Azure', 'AWS'가 있다. 이런 종류의 클라우드 특성에 따라 IaaS, PaaS로 나눌 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;① &lt;b&gt;IaaS&lt;/b&gt;(=Infra as a Service, 인프라 서비스)&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;b&gt;PaaS&lt;/b&gt;(=Platform as a Service, 플랫폼 서비스)&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;인프라와 IT기술을 빌려주고, 다양한 지원 서비스도 함께 제공&lt;/li&gt;
&lt;li&gt;임대 서버가 이에 해당함, OS가 설치된 서버에 사용자가 애플리케이션 등을 설치해서 사용해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;③ &lt;b&gt;SaaS&lt;/b&gt;(=Software as a Service, 소프트웨어 서비스)&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;과거에 pc나 서버 등에 설치해서 이용해야 했던 소프트웨어를 클라우드를 통해 제공하는 서비스&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;④ &lt;b&gt;EaaS&lt;/b&gt;(=Everything as a Service)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp;Iaas, Paas, SaaS 세가지를 통칭하는 말&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;XaaS 라고도 함&lt;/li&gt;
&lt;li&gt;AWS는 EaaS라고 할 수 있음&lt;/li&gt;
&lt;/ul&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비용 절감&lt;/b&gt;, 즉&amp;nbsp;&lt;b&gt;경제성&lt;/b&gt;&amp;nbsp;측면에서 좋기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 서버 한대를 구축하려면 100만원 이상이 필요하다. 이후, 서버가 더 필요하다면 추가 구매 비용이 들 것이다. 하지만 클라우드를 사용한다면 초기 비용과 운영 비용에 드는 시간과 비용을 절감할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많이 들어보신 AWS의 EC2가 클라우드 컴퓨팅의 일종입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라우드를 통해 어떻게 서버를 구축하고 배포하는지는 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;서버를 구축해주기 때문에 인프라 운영이 쉽다.&lt;/li&gt;
&lt;li&gt;클라우드는 'AWS'과 같은 UI/UX를 통해서 편하게 서버 스펙을 바꿀 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;&amp;nbsp;&lt;/h1&gt;
&lt;h1&gt;끝마치며&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DNS, 서버, 클라우드 등에 대해서 알아보았습니다.&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;/p&gt;</description>
      <category>Knowledge</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/28</guid>
      <comments>https://ryuwon-it.tistory.com/28#entry28comment</comments>
      <pubDate>Sun, 2 Nov 2025 23:59:07 +0900</pubDate>
    </item>
    <item>
      <title>[CS 기초 시리즈 #1] 개발에 들어가기 앞서..</title>
      <link>https://ryuwon-it.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 매일 보고 사용하는 웹, 앱 서비스들은 &lt;span style=&quot;color: #387dc9;&quot; data-token-index=&quot;1&quot;&gt;우리 눈에 보이는 영역을 개발하는 일&lt;/span&gt;과 &lt;span style=&quot;color: #387dc9;&quot; data-token-index=&quot;3&quot;&gt;눈에 보이지 않는 뒷단을 개발하는 일&lt;/span&gt;로 나눌 수 있습니다.&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;span style=&quot;color: #387dc9;&quot; data-token-index=&quot;1&quot;&gt;프&lt;b&gt;론트엔드에 있는 사용자들이 원하는 행동(이벤트)을 처리하는 것이 백엔드&lt;/b&gt;&lt;/span&gt;라고 할 수 있습니다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKYYzH/dJMcabbqlit/co8QKl2GKrX0VwPnwlc691/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKYYzH/dJMcabbqlit/co8QKl2GKrX0VwPnwlc691/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKYYzH/dJMcabbqlit/co8QKl2GKrX0VwPnwlc691/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKYYzH%2FdJMcabbqlit%2Fco8QKl2GKrX0VwPnwlc691%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;760&quot; height=&quot;500&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&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;간단하게 레스토랑 예시를 들어서 설명하겠습니다.&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 data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;API &lt;/b&gt;란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Application Programming Interface.&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Athif/dJMcajmZx49/v4WWktSR3xmwELzeKwn2V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Athif/dJMcajmZx49/v4WWktSR3xmwELzeKwn2V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Athif/dJMcajmZx49/v4WWktSR3xmwELzeKwn2V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAthif%2FdJMcajmZx49%2Fv4WWktSR3xmwELzeKwn2V1%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;628&quot; data-origin-width=&quot;639&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API는 정보전달의 역할 외에도 여러 역할을 하는데.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;API는 서버와 데이터베이스에 대한 출입구 역할&lt;/li&gt;
&lt;li&gt;서비스(어플리케이션 등)과 기기가 원할하게 통신할 수 있게 하는 역할&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;p data-ke-size=&quot;size16&quot;&gt;API는 클라이언트와 서버 간의 정보전달 방법이기도 하지만, 앱과 앱, 프로그램과 프로그램 등에서의 정보전달도 모두 포함하는 인터페이스들을 한꺼번에 말하기도 합니다.&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;자 그러면 우리가 사용하는 인터넷에서는 프론트와 백엔드 영역이 존재하고, 그 사이 정보전달에서는 API라는 매개체가 요청과 응답을 중계해준다는 것을 알게되었습니다.&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;우리가 폰에서 쿠팡에 들어가서 보고싶은 상품을 클릭했었을때(프론트에서의 클릭이라는 행위) API가 해당 상품에 대한 요청을 백엔드로 전송하고, 백엔드에서는 해당 상품에 대한 정보를 다시 API에 응답을 보내게 되면 우리 핸드폰에 클릭했던 상품의 정보가 보이는거죠..!&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;그렇다면 어떻게 우리 집에서 쿠팡 앱에 접속했는데 저 멀리 있는 쿠팡 서버에 연결되서 데이터를 전달 받게 된 것일까요..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결은 그렇다 치고 컴퓨터는 단순히 0과 1을 계산하는 녀석으로 알고 있는데, 클릭 한번에 우리가 알아볼 수 있는 상품 데이터를 가져올 수 있던걸까요..?&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;ldquo;&lt;b&gt;네트워크&lt;/b&gt;&amp;rdquo; 에 대한 이해가 필요합니다.&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;/h3&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;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;집에서도 모든 업무를 처리하고, 재밌는 게임도 밖에 나가지 않고 친구와 각자 카카오톡이나 디스코드를 하면서 소통할 수 있도록 만들어준 것도 바로 인터넷. 즉, 네트워크입니다.&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;지금 보고있는 이 노션의 글 조차 네트워크가 존재하기에 볼 수 있는거죠.&lt;/p&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-origin-width=&quot;800&quot; data-origin-height=&quot;533&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cfXNA5/dJMcagw2dYr/BPkAGSf7RINDQ96EYks220/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cfXNA5/dJMcagw2dYr/BPkAGSf7RINDQ96EYks220/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cfXNA5/dJMcagw2dYr/BPkAGSf7RINDQ96EYks220/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcfXNA5%2FdJMcagw2dYr%2FBPkAGSf7RINDQ96EYks220%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;491&quot; height=&quot;327&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/figure&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;b&gt;네트워킹&lt;/b&gt;하여 형성된 망을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 또&amp;nbsp;&lt;b&gt;네트워킹&lt;/b&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;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;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;h4 data-ke-size=&quot;size20&quot;&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;/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;그렇다면 전 세계가 어떻게 연결되어 있는걸까요?&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;이 질문들을 쉽게 설명하기 위해서는 OSI 7계층에 대해서 알고 있어야합니다.&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;OSI 7계층&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OSI 7계층이라는 네트워크에서 통신이 일어나는 과정을 총 7가지 단계로 나눈 것을 말한겁니다.&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;통신이 일어나는 과정을 단계별로 한눈에 파악할 수 있기 때문입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;364&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BgKrc/dJMcagqgDYG/xAS8MnutOm1ttRAnZk25xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BgKrc/dJMcagqgDYG/xAS8MnutOm1ttRAnZk25xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BgKrc/dJMcagqgDYG/xAS8MnutOm1ttRAnZk25xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBgKrc%2FdJMcagqgDYG%2FxAS8MnutOm1ttRAnZk25xK%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;642&quot; height=&quot;324&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;364&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;size18&quot;&gt;&lt;b&gt;물리계층&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0과 1로 이루어진 데이터를 다른 컴퓨터로 전송하기 위해서는 &amp;ldquo;전선&amp;rdquo;을 이용하여 전송.&lt;/p&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-origin-width=&quot;1072&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckE59q/dJMcabibXmo/uUv0Y8XktzTJZmXqcW5lkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckE59q/dJMcabibXmo/uUv0Y8XktzTJZmXqcW5lkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckE59q/dJMcabibXmo/uUv0Y8XktzTJZmXqcW5lkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckE59q%2FdJMcabibXmo%2FuUv0Y8XktzTJZmXqcW5lkk%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;549&quot; height=&quot;210&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;0과 1의 나열을 아날로그 신호로 바꾸어 전선으로 흘려보냄. (encoding)&lt;/li&gt;
&lt;li&gt;아날로그 신호가 들어오면 0과 1의 나열을 해석(decoding)&lt;/li&gt;
&lt;li&gt;물리적으로 연결된 두 대의 컴퓨터가 0과 1의 나열을 주고받을 수 있게 해주는 모듈(module)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFN06j/dJMcahCH8dJ/XVYUYmovsQ8r7VBYWbzMm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFN06j/dJMcahCH8dJ/XVYUYmovsQ8r7VBYWbzMm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFN06j/dJMcahCH8dJ/XVYUYmovsQ8r7VBYWbzMm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFN06j%2FdJMcahCH8dJ%2FXVYUYmovsQ8r7VBYWbzMm1%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;555&quot; height=&quot;255&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&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;&lt;b&gt;데이터 링크&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cKOX7y/dJMcaiO9EO6/KX9kjc2kL2ZfVV63IcuJ11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cKOX7y/dJMcaiO9EO6/KX9kjc2kL2ZfVV63IcuJ11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cKOX7y/dJMcaiO9EO6/KX9kjc2kL2ZfVV63IcuJ11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcKOX7y%2FdJMcaiO9EO6%2FKX9kjc2kL2ZfVV63IcuJ11%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;484&quot; height=&quot;161&quot; data-origin-width=&quot;1186&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;&amp;ldquo;MAC 주소&amp;rdquo;&lt;/b&gt; 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MAC 주소를 활용하여 원하는 장치에 정보를 전달하는 장치를 &amp;ldquo;&lt;b&gt;스위치&lt;/b&gt;&amp;rdquo; 라고 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정보 단위는 &lt;span data-token-index=&quot;1&quot;&gt;프레임&lt;/span&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;스위치 한 대에 수십 수백대의 PC를 연결하기란 불가능.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 스위치와 스위치 간의 연결을 도와주는 장비가 필요해지고 네트워크가 방대해지면서 이 연결들을 &lt;b&gt;최적의 경로를 설계해주는 장비&lt;/b&gt;가 필요해짐.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;576&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0g5ys/dJMcah3Mtmi/ck5Tovs1iXaQASqK7FXjY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0g5ys/dJMcah3Mtmi/ck5Tovs1iXaQASqK7FXjY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0g5ys/dJMcah3Mtmi/ck5Tovs1iXaQASqK7FXjY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0g5ys%2FdJMcah3Mtmi%2Fck5Tovs1iXaQASqK7FXjY1%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;383&quot; height=&quot;368&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;576&quot;/&gt;&lt;/span&gt;&lt;/figure&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;각 노드에 IP 주소를 부여하고, 여러 개의 노드를 거칠때마다 최적의 경로를 설정해주는 장비인 &lt;span data-token-index=&quot;1&quot;&gt;&amp;ldquo;라우터&amp;rdquo;&lt;/span&gt;를 사용.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmg0k8/dJMcaiO9EPY/orX3H8u7fv3sJljk6yI5R1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmg0k8/dJMcaiO9EPY/orX3H8u7fv3sJljk6yI5R1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmg0k8/dJMcaiO9EPY/orX3H8u7fv3sJljk6yI5R1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdmg0k8%2FdJMcaiO9EPY%2ForX3H8u7fv3sJljk6yI5R1%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;700&quot; height=&quot;383&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;383&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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-origin-width=&quot;856&quot; data-origin-height=&quot;543&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qaoM8/dJMcaiIn1j6/pRtsjcoehNoalUxFHWcv2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qaoM8/dJMcaiIn1j6/pRtsjcoehNoalUxFHWcv2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qaoM8/dJMcaiIn1j6/pRtsjcoehNoalUxFHWcv2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqaoM8%2FdJMcaiIn1j6%2FpRtsjcoehNoalUxFHWcv2K%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;654&quot; height=&quot;415&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;543&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IP 주소를 이용해서 길을 찾고 (&lt;b&gt;Routing&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신 다음의 라우터에게 데이터를 넘겨주는 것(&lt;b&gt;forwarding&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 모든 인터넷 상의 컴퓨터가 서로 통신 가능&amp;hellip;!&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;h3 data-ke-size=&quot;size23&quot;&gt;전송 계층&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 받고자 하는 프로세스들은 통해 &lt;b&gt;포트 번호&lt;/b&gt;를 부여받고 이를 통해 원하는 정보를 주고 받음.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;  &lt;b&gt;포트란?&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;하나의 컴퓨터에서 동시에 실행되고 있는 프로세스들이 서로 겹치지 않게 가져야하는 정수값&lt;/blockquote&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;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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최종 목적지로서 HTTP, FTP, SMTP, POP3, IMAP, Telnet 등과 같은 프로토콜이 존재.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 통신 패킷들은 방금 나열한 프로토콜에 의해 모두 처리되며 우리가 사용하는 브라우저나, 메일 프로그램은 프로토콜을 보다 쉽게 사용하게 해주는 응용 프로그램.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한마디로 모든 통신의 양 끝단은 HTTP와 같은 프로토콜이지 응용프로그램이 아닙니다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 프론트, 백엔드에 대해 알아보았고, 네트워크에 대해 어느정도 조금이나마 알게되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에는 DNS, REST API, 인프라, 클라우드(AWS, WAS), GIT 등에 대해서 알아보도록 하겠습니다.&lt;/p&gt;</description>
      <category>Knowledge</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/27</guid>
      <comments>https://ryuwon-it.tistory.com/27#entry27comment</comments>
      <pubDate>Sun, 2 Nov 2025 23:53:30 +0900</pubDate>
    </item>
    <item>
      <title>[Java/알고리즘] 알고리즘을 들어가며..</title>
      <link>https://ryuwon-it.tistory.com/26</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&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;문제를 해결하기 위해 사용되는&lt;/b&gt;&amp;nbsp;&lt;b&gt;절차 또는 방법&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;프로그래밍에서 알고리즘은 효율적인 문제 해결의 &lt;b&gt;핵심 요소&lt;/b&gt;. 같은 결과를 내더라도 알고리즘에 따라 수행 시간과 메모리 사용량에 큰 차이가 발생한다.&lt;/li&gt;
&lt;li&gt;수학의 공식처럼, 특정 형태 또는 구조를 갖는 프로그래밍 문제에는&amp;nbsp;&lt;b&gt;공식화된 알고리즘&lt;/b&gt;이 존재함.&lt;/li&gt;
&lt;/ul&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; &amp;rarr; 따라서, 가장 적합한 알고리즘을 선택하려면 &lt;b&gt;평가 기준&lt;/b&gt;을 이해하고 있어야 한다&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;**알고리즘**
│
├─ **1. 정렬 (Sorting)**
│ ├─ 1) 단순 정렬( O(n&amp;sup2;)): 버블 / 선택 / 삽입
│ ├─ 2) 효율 정렬(O(n log n)): 퀵 / 병합 / 힙 / 트리
│ └─ 3) 특수 정렬: 계수 / 기수 / 버킷
│
├─ **2. 탐색 (Searching)**
│ ├─ 1) 기본 탐색
│ │ ├─ 선형 탐색
│ │ ├─ 이진 탐색
│ │ └─ 해시 탐색
│ ├─ 2) 상태공간&amp;middot;완전 탐색
│ │ ├─ 브루트 포스
│ │ ├─ 백트래킹
│ │ │ ├─ 순열/조합/부분집합 생성
│ │ │ ├─ 제약 충족(스도쿠, N-Queen 등)
│ │ │ └─ 비트마스크(부분집합 표현)
│ │ └─ 이분 탐색(Parametric Search)
│ ├─ 3) 비선형 구조 탐색
│ │ ├─ 트리 순회: 전위/중위/후위
│ │ └─ 그래프 탐색: DFS / BFS 
│ └─ 4) 자료구조 특화
│ └─ 이진 탐색 트리(BST): 삽입 / 삭제 / 탐색
│
├─ **3. 그래프 알고리즘 (Graph Algorithms)**
│ ├─ 1) 그래프 표현: 인접 리스트 / 인접 행렬
│ ├─ 2) 그래프 탐색: DFS / BFS &amp;larr; (탐색 2-3과 연결)
│ ├─ 3) 최단 경로: 다익스트라 / 벨만&amp;ndash;포드 / 플로이드&amp;ndash;워셜
│ ├─ 4) 서로소 집합 : 유니온 파인드 - Union by Rank, Union by Size
│ ├─ 5) 최소 신장 트리(MST): 크루스칼 / 프림
│ ├─ 6) 위상 정렬(Topological Sort)
│ ├─ 7) 강한 연결 요소 : SCC, Kosaraju/Tarjan
│ ├─ 8) 네트워크 플로우: Ford&amp;ndash;Fulkerson / Edmonds&amp;ndash;Karp
│ └─ 9) 최대 매칭: Hopcroft&amp;ndash;Karp
│ 
│
└─ **4. 문제해결 전략 (Problem-Solving Strategies)**
  ├─ 그리디(Greedy)
  ├─ 분할 정복(Divide &amp;amp; Conquer)
  ├─ 동적 계획법(DP)
  ├─ 투 포인터 &amp;amp; 슬라이딩 윈도우
  └─ 이분 탐색 응용(Parametric Search)[전략]
&lt;/code&gt;&lt;/pre&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;h2 data-ke-size=&quot;size26&quot;&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;시간 복잡도 (Time Complexity)&lt;/b&gt;
&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;li&gt;&lt;b&gt;Big-O 표기법&lt;/b&gt;으로 표현하며, 주로 **최악의 경우(worst-case)**를 기준으로 계산.&lt;/li&gt;
&lt;li&gt;예: O(1) &amp;lt; O(log n) &amp;lt; O(n) &amp;lt; O(n log n) &amp;lt; O(n&amp;sup2;) &amp;lt; O(2ⁿ) &amp;lt; O(n!)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;공간 복잡도 (Space Complexity)&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;b&gt;trade-off&lt;/b&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;협업 가능성과 유지보수를 고려한 코드 복잡도.&lt;/li&gt;
&lt;li&gt;너무 복잡한 알고리즘은 실무&amp;middot;테스트에서 오히려 불리할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코딩 테스트에서의 활용 팁&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시간 복잡도와 실행 시간 감 잡기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간 복잡도 입력 크기 n 대략적인 최대 n 한계 (1초 기준) 예시 알고리즘&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;&lt;b&gt;O(1)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;무관&lt;/td&gt;
&lt;td&gt;무제한&lt;/td&gt;
&lt;td&gt;해시 접근, 단일 연산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;O(log n)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;매우 큼&lt;/td&gt;
&lt;td&gt;10&amp;sup1;⁸ 이상 가능&lt;/td&gt;
&lt;td&gt;이진 탐색, 균형 트리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;O(n)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최대 10⁸&lt;/td&gt;
&lt;td&gt;1억&lt;/td&gt;
&lt;td&gt;단일 루프, 해시맵&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;O(n log n)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최대 10⁶&lt;/td&gt;
&lt;td&gt;100만&lt;/td&gt;
&lt;td&gt;병합 정렬, 퀵 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;O(n&amp;sup2;)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최대 10⁴&lt;/td&gt;
&lt;td&gt;1만&lt;/td&gt;
&lt;td&gt;완전탐색(이중 루프)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;O(n&amp;sup3;)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최대 500&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;td&gt;플로이드&amp;ndash;워셜&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;O(2ⁿ)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최대 20&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;부분집합, 백트래킹&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;O(n!)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;최대 10~11&lt;/td&gt;
&lt;td&gt;10~11&lt;/td&gt;
&lt;td&gt;순열&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1억 번 연산 &amp;asymp; 1초 (대략적인 기준)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;O(n&amp;sup2;)&lt;/b&gt;: n=10⁴ 이상이면 위험&lt;/li&gt;
&lt;li&gt;&lt;b&gt;O(n log n)&lt;/b&gt;: n=10⁶까지 안전&lt;/li&gt;
&lt;li&gt;&lt;b&gt;O(n)&lt;/b&gt;: n=10⁸까지 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 크기 100,000 이상&lt;/b&gt; &amp;rarr; O(n&amp;sup2;) 금지, O(n log n) 이하 알고리즘 필요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;데이터 크기 1,000 이하&lt;/b&gt; &amp;rarr; 완전탐색(O(n&amp;sup2;)~O(n⁴))도 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;순열/조합 문제&lt;/b&gt;는 보통 데이터 크기를 작게 줌 &amp;rarr; 백트래킹 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정렬 활용&lt;/b&gt;: sort 후 투 포인터, 이분 탐색 적용 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;해시맵 활용&lt;/b&gt;: O(1) 접근으로 시간 절약 가능 (메모리 여유 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시리즈에서는 각 알고리즘을 Java를 사용하여 구현하고, 설명하는 글을 작성할 예정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(아마 그때그때 공부하는 내용을 정리할 꺼라 포스팅 순서는 뒤죽박죽이겠지만 시리즈에는 예쁘게 정리해둘게요!)&lt;/p&gt;</description>
      <category>Knowledge/알고리즘</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/26</guid>
      <comments>https://ryuwon-it.tistory.com/26#entry26comment</comments>
      <pubDate>Sat, 1 Nov 2025 23:35:19 +0900</pubDate>
    </item>
    <item>
      <title>[CS] 동시성과 병렬성</title>
      <link>https://ryuwon-it.tistory.com/25</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;동시성과 병렬성에 관해서는 꽤 들어봤을텐데 CPU, 저장장치, 네트워크, 운영체제, 심지어 백엔드 등 다양한 영역에서 I/O가 있거나 연산이 필요한 모든 곳에 적용되는 개념이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 함께 알아보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동시성(Concurrency)이란?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdlkTu/dJMcacuC7QA/hnSX0uRPJOVEiosMO43Mqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdlkTu/dJMcacuC7QA/hnSX0uRPJOVEiosMO43Mqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdlkTu/dJMcacuC7QA/hnSX0uRPJOVEiosMO43Mqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdlkTu%2FdJMcacuC7QA%2FhnSX0uRPJOVEiosMO43Mqk%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;362&quot; height=&quot;234&quot; data-origin-width=&quot;362&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&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;CPU를 예로 들어보면, 단일 코어는 사실상 한 번에 하나의 작업만 처리할 수 있다. 그런데 작업 중 I/O(입출력) 대기 시간이 발생하면 CPU는 아무 일도 하지 않고 쉬게 된다. 이렇게 자원이 낭비되는 것을 방지하기 위해 CPU는 시간 분할을 통해 여러 작업(스레드)을 빠르게 번갈아 수행함으로써 마치 동시에 여러 작업이 처리되는 것처럼 보이게 만든다.&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;서버나 시스템의 성능은 단순히 CPU 속도만으로 결정되지 않는다.&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;b&gt;DB 조회&lt;/b&gt;, &lt;b&gt;파일 입출력&lt;/b&gt; 등 대기 시간이 긴 작업이 포함된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 동시성을 적용하면 CPU가 대기하지 않고 다른 작업을 처리할 수 있어, &lt;b&gt;처리량&lt;/b&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;유휴 시간 감소&lt;/b&gt; &amp;rarr; 자원 활용도 향상&lt;/li&gt;
&lt;li&gt;&lt;b&gt;처리량 증가&lt;/b&gt; &amp;rarr; 동일 자원으로 더 많은 요청 처리 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;응답 시간 단축&lt;/b&gt; &amp;rarr; 사용자 경험 개선 및 확장성 확보&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 웹 서버가 DB 응답을 기다리는 동안 다른 요청을 처리할 수 있다면, 같은 서버로도 훨씬 많은 동시 접속을 감당할 수 있다.&lt;/p&gt;
&lt;/blockquote&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;/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;컨텍스트 스위칭 기반 스레드 [운영체제]&lt;/b&gt;&lt;br /&gt;전통적인 방식, 운영체제가 스레드를 번갈아 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 루프 기반 (Non-blocking I/O) [런타임 환경]&lt;/b&gt;&lt;br /&gt;하나의 스레드로도 수천 개 요청 처리 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리액티브 스트림 기반&lt;/b&gt;&lt;br /&gt;파이프라인으로 데이터 흐름을 비동기 처리&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;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;b&gt;Deadlock&lt;/b&gt;, &lt;b&gt;Race Condition&lt;/b&gt;, 기아(&lt;b&gt;Starvation)&lt;/b&gt; 등 다양한 문제에 대해 신경 써야 한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;병렬성(Parallelism)이란?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;219&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3Ywdh/dJMcah3MaDZ/vQYt5CYPiLZnYooAVJKeoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3Ywdh/dJMcah3MaDZ/vQYt5CYPiLZnYooAVJKeoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3Ywdh/dJMcah3MaDZ/vQYt5CYPiLZnYooAVJKeoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3Ywdh%2FdJMcah3MaDZ%2FvQYt5CYPiLZnYooAVJKeoK%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;237&quot; height=&quot;219&quot; data-origin-width=&quot;237&quot; data-origin-height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;/figure&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;lsquo;동시 수행처럼 보이는 것'이 아니라, &lt;b&gt;진짜 동시에 실행된다는 점&lt;/b&gt;이다. 예를 들어 CPU에 코어가 4개 있다면, 4개의 작업을 같은 시점에 병렬로 실행할 수 있다.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 CPU 연산이 많은 작업(CPU-bound)일 경우 대기 시간이 아니라 계산 자체가 병목이 되기 때문에, 동시성만으로는 충분한 성능 향상을 기대하기 어렵다. 이때 병렬 처리를 활용하면 연산을 여러 코어에 분산해 &lt;b&gt;작업 완료 시간을 줄이고&lt;/b&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;계산 분할&lt;/b&gt; &amp;rarr; 작업을 나눠 여러 코어에서 동시에 수행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;처리 속도 향상&lt;/b&gt; &amp;rarr; 작업 완료 시간 단축&lt;/li&gt;
&lt;li&gt;&lt;b&gt;확장성 확보&lt;/b&gt; &amp;rarr; 하드웨어 스케일업으로 성능 향상 가능&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;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;br /&gt;여러 코어에 스레드를 분배해서 동시 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티프로세스 기반 기반 병렬처리&lt;/b&gt;&lt;br /&gt;프로세스 단위로 작업을 나눠서 실행&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GPU 병렬 처리&lt;/b&gt;&lt;br /&gt;요즘 핫한 CUDA처럼 연산 유닛을 병렬로 동작시켜 대규모 연산&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산 처리&lt;/b&gt;&lt;br /&gt;여러 대의 서버에 트래픽을 나눠서 처리 등&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;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; &amp;rarr; 락, 뮤텍스 등 동기화 처리가 필요&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경쟁 상태(Race Condition)&lt;/b&gt; &amp;rarr; 여러 스레드가 동시에 자원에 접근하면 문제 발생&lt;/li&gt;
&lt;li&gt;&lt;b&gt;오버헤드&lt;/b&gt; &amp;rarr; 스레드 생성, 작업 분할, 캐시 동기화 등으로 인해 오히려 느려짐.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;하드웨어 의존성&lt;/b&gt; &amp;rarr; 코어 수가 적거나 병렬화가 어려운 작업이라면 효과가 제한적&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 CPU 코어 수가 적은 환경에서는 병렬화의 이점이 거의 없거나, 관리 오버헤드로 인해 성능이 악화될 수도 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;270&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dXAXj0/dJMcaeFYUag/X1BJpsGabmGFkEfQ4id5qK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dXAXj0/dJMcaeFYUag/X1BJpsGabmGFkEfQ4id5qK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dXAXj0/dJMcaeFYUag/X1BJpsGabmGFkEfQ4id5qK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdXAXj0%2FdJMcaeFYUag%2FX1BJpsGabmGFkEfQ4id5qK%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;554&quot; height=&quot;270&quot; data-origin-width=&quot;554&quot; data-origin-height=&quot;270&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;기다리는 동안 다른 일을 처리&lt;/b&gt;해 자원을 효율적으로 쓰는 전략이다.&lt;/li&gt;
&lt;li&gt;병렬성은 &lt;b&gt;여러 일을 실제로 동시에 처리&lt;/b&gt;해 작업 속도 자체를 끌어올리는 전략이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현실의 시스템에서는 웹 서버는 동시성으로 I/O를 처리하고, 내부의 데이터 분석 엔진은 병렬성으로 연산 속도를 끌어올리는 식으로 이 둘을 &lt;b&gt;적절히 조합&lt;/b&gt;해 사용하는 경우가 많다.&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참조&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.maeil-mail.kr/question/120&quot;&gt;매일메일 - 동시성과 병렬성에 대해서 설명해주세요.&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.tutorialspoint.com/concurrency_in_python/concurrency_in_python_concurrency_vs_parallelism.htm&quot;&gt;Concurrency vs Parallelism&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://techdifferences.com/difference-between-concurrency-and-parallelism.html#google_vignette&quot;&gt;Difference Between Concurrency and Parallelism (with Comparison Chart) - Tech Differences&lt;/a&gt;&lt;/p&gt;</description>
      <category>Knowledge/개발지식</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/25</guid>
      <comments>https://ryuwon-it.tistory.com/25#entry25comment</comments>
      <pubDate>Sat, 1 Nov 2025 23:08:42 +0900</pubDate>
    </item>
    <item>
      <title>[단기 공기업 필기 준비] 데이터베이스</title>
      <link>https://ryuwon-it.tistory.com/24</link>
      <description>&lt;h1&gt;데이터베이스란&lt;/h1&gt;
&lt;p&gt;특정 조직 내에서 다수의 사용자들이 공유할 수 있도록 통합시키고 컴퓨터 저장 장치에 저장시킨 운영 데이터의 집합.&lt;/p&gt;
&lt;ul&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&gt;데이터베이스의 특성&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;실시간 접근성&lt;br&gt;데이터 베이스에 저장된 데이터는 실시간 접근이 보장됨&lt;/li&gt;
&lt;li&gt;계속적인 변화&lt;br&gt;데이터베이스에 저장된 데이터는 계속적으로 변화함&lt;/li&gt;
&lt;li&gt;동시공유&lt;br&gt;데이터베이스에 저장된 데이터는 여러 명의 사용자들이 동시에 공유할 수 있으며, 이와 같은 기능은 데이터베이스 관리시스템이 지원함&lt;/li&gt;
&lt;li&gt;내용에 의한 참조&lt;br&gt;데이터베이스에 저장된 데이터는 내용에 의한 참조를 할 수 있음. 즉, 데이터가 저장된 주소를 이용하여 원하는 데이터에 접근하는 것이 아니라, 저장된 데이터의 내용을 이용하여 원하는 데이터에 접근할 수 있음&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;DBMS의 장단점&lt;/h2&gt;
&lt;h3&gt;장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터 중복의 최소화&lt;br&gt;데이터를 중앙 집중식으로 관리하여 같은 데이터가 여러 곳에 저장되는 것을 방지하고, 저장 공간을 효율적으로 사용할 수 있음&lt;/li&gt;
&lt;li&gt;데이터의 공용&lt;br&gt;여러 사용자와 응용 프로그램이 동시에 데이터베이스에 접근하여 데이터를 공유할 수 있어 조직 내 협업이 용이함&lt;/li&gt;
&lt;li&gt;데이터 무결성 유지&lt;br&gt;제약 조건과 규칙을 설정하여 잘못된 데이터가 입력되는 것을 방지하고, 데이터의 정확성과 신뢰성을 보장함&lt;/li&gt;
&lt;li&gt;데이터의 일관성 유지&lt;br&gt;데이터 중복을 최소화함으로써 데이터 갱신 시 일관성을 유지할 수 있으며, 모순된 데이터가 발생하는 것을 방지함&lt;/li&gt;
&lt;li&gt;데이터 보안 보장&lt;br&gt;사용자별로 접근 권한을 설정하여 중요한 데이터를 보호하고, 무단 접근이나 데이터 유출을 방지할 수 있음&lt;/li&gt;
&lt;li&gt;응용 프로그램과 데이터의 독립성 유지&lt;br&gt;데이터 구조가 변경되어도 응용 프로그램을 수정할 필요가 없어 유지보수가 용이하고 시스템 유연성이 향상됨&lt;/li&gt;
&lt;li&gt;표준화의 달성&lt;br&gt;데이터 형식, 접근 방법, 인터페이스 등을 표준화하여 조직 내 데이터 관리의 일관성을 확보하고 효율성을 높임&lt;/li&gt;
&lt;li&gt;데이터 백업 및 회복 기능&lt;br&gt;자동 백업 기능과 장애 발생 시 회복 메커니즘을 제공하여 데이터 손실을 최소화하고 시스템의 안정성을 보장함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;운영비의 증대&lt;br&gt;DBMS 소프트웨어 구입, 하드웨어 업그레이드, 전문 인력 고용 등으로 인해 초기 투자 비용과 유지보수 비용이 증가함&lt;/li&gt;
&lt;li&gt;데이터베이스 설계의 어려움&lt;br&gt;효율적인 데이터베이스를 구축하기 위해서는 전문적인 지식과 경험이 필요하며, 설계 단계에서 많은 시간과 노력이 소요됨&lt;/li&gt;
&lt;li&gt;복잡한 예비와 회복&lt;br&gt;시스템 장애나 데이터 손실에 대비한 백업 및 복구 절차가 복잡하고, 대용량 데이터베이스의 경우 복구 시간이 오래 걸릴 수 있음&lt;/li&gt;
&lt;li&gt;시스템의 복잡성 증가&lt;br&gt;DBMS는 다양한 기능을 제공하지만 그만큼 시스템이 복잡해져 관리와 운영에 어려움이 생길 수 있음&lt;/li&gt;
&lt;li&gt;성능 저하 가능성&lt;br&gt;많은 사용자가 동시에 접근하거나 복잡한 쿼리를 실행할 경우 시스템 성능이 저하될 수 있으며, 적절한 튜닝이 필요함&lt;/li&gt;
&lt;li&gt;단일 장애점(Single Point of Failure)&lt;br&gt;중앙 집중식 구조로 인해 DBMS에 장애가 발생하면 전체 시스템이 마비될 수 있는 위험이 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;NoSQL의 장단점&lt;/h2&gt;
&lt;h3&gt;장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;유연한 데이터 모델&lt;br&gt;스키마가 고정되어 있지 않아 데이터 구조 변경이 용이함&lt;/li&gt;
&lt;li&gt;확장성&lt;br&gt;수평적 확장(Scale-out)이 쉬워 대용량 데이터 처리에 유리함&lt;/li&gt;
&lt;li&gt;높은 성능&lt;br&gt;특정 유형의 쿼리에 대해 빠른 읽기/쓰기 성능을 제공함&lt;/li&gt;
&lt;li&gt;비정형 데이터 처리&lt;br&gt;다양한 형태의 데이터를 저장하고 처리할 수 있음&lt;/li&gt;
&lt;li&gt;가용성&lt;br&gt;분산 시스템 구조로 인해 높은 가용성과 내결함성을 제공함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;제한적인 쿼리 기능&lt;br&gt;복잡한 조인이나 트랜잭션 처리가 어려움&lt;/li&gt;
&lt;li&gt;데이터 일관성 문제&lt;br&gt;강한 일관성 대신 최종 일관성을 보장하는 경우가 많음&lt;/li&gt;
&lt;li&gt;표준화 부족&lt;br&gt;NoSQL 데이터베이스마다 사용법이 다르고 통일된 표준이 없음&lt;/li&gt;
&lt;li&gt;성숙도&lt;br&gt;관계형 데이터베이스에 비해 상대적으로 생태계와 도구가 덜 발전됨&lt;/li&gt;
&lt;li&gt;관리의 복잡성&lt;br&gt;분산 시스템 관리와 운영에 전문 지식이 필요함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;데이터 독립성&lt;/h2&gt;
&lt;p&gt;데이터 독립성은 하위 단계의 데이터 구조가 변경되더라도 상위 단계에 영향을 미치지 않는 특성을 말한다. 데이터 독립성은 논리적 데이터 독립성과 물리적 데이터 독립성으로 구분된다.&lt;/p&gt;
&lt;h3&gt;논리적 데이터 독립성 (Logical Data Independence)&lt;/h3&gt;
&lt;p&gt;논리적 데이터 독립성은 개념 스키마가 변경되어도 외부 스키마에는 영향을 미치지 않도록 지원하는 것을 말함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;응용 프로그램과 데이터베이스를 독립시킴으로써 데이터의 논리적 구조를 변경시키더라도 응용 프로그램은 영향을 받지 않음&lt;/li&gt;
&lt;li&gt;테이블의 구조가 변경되거나 새로운 속성이 추가되어도 기존 응용 프로그램에 영향을 주지 않음&lt;/li&gt;
&lt;li&gt;사용자의 관점에서 본 데이터베이스의 논리적 구조가 변경되어도 응용 프로그램은 변경되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;물리적 데이터 독립성 (Physical Data Independence)&lt;/h3&gt;
&lt;p&gt;물리적 데이터 독립성은 내부 스키마가 변경되어도 개념 스키마에 영향을 미치지 않도록 지원하는 것을 말함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;저장 장치의 구조 변경, 인덱스의 추가 및 삭제, 파일 구조의 변경 등 물리적 저장 구조가 변경되어도 개념 스키마나 응용 프로그램에 영향을 주지 않음&lt;/li&gt;
&lt;li&gt;데이터베이스의 성능 향상을 위해 물리적 구조를 변경할 수 있음&lt;/li&gt;
&lt;li&gt;논리적 데이터 독립성보다 구현하기 쉬움&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;데이터베이스의 구조&lt;/h2&gt;
&lt;h3&gt;스키마 (Schema)&lt;/h3&gt;
&lt;p&gt;스키마는 데이터베이스의 구조와 제약 조건에 관한 전반적인 명세를 기술한 것으로, 데이터베이스를 구성하는 데이터 개체(Entity), 속성(Attribute), 관계(Relationship) 및 제약 조건 등을 정의함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;외부 스키마 (External Schema) = 뷰 = 서비스키마&lt;/strong&gt;&lt;br&gt;사용자나 응용 프로그래머가 각 개인의 입장에서 필요로 하는 데이터베이스의 논리적 구조를 정의한 것으로, 서브 스키마라고도 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개념 스키마 (Conceptual Schema)&lt;/strong&gt;&lt;br&gt;데이터베이스의 전체적인 논리적 구조로, 모든 응용 프로그램이나 사용자들이 필요로 하는 데이터를 종합한 조직 전체의 데이터베이스를 의미함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;내부 스키마 (Internal Schema)&lt;/strong&gt;&lt;br&gt;물리적 저장 장치의 입장에서 본 데이터베이스 구조로, 실제로 데이터베이스에 저장될 레코드의 물리적인 구조를 정의함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;데이터베이스의 3단계 구조&lt;/h3&gt;
&lt;p&gt;데이터베이스의 3단계 구조는 외부 스키마, 개념 스키마, 내부 스키마로 구분되며, 각 단계 간의 독립성을 유지하여 데이터베이스 관리의 효율성을 높임&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;외부 단계 (External Level)&lt;/strong&gt;&lt;br&gt;개별 사용자 관점의 데이터베이스로, 각 사용자가 보는 데이터베이스의 일부분을 나타냄. 여러 개의 외부 스키마가 존재할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개념 단계 (Conceptual Level)&lt;/strong&gt;&lt;br&gt;조직 전체의 관점에서 본 데이터베이스로, 데이터베이스에 저장되는 데이터와 그들 간의 관계를 표현함. 하나의 개념 스키마만 존재함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;내부 단계 (Internal Level)&lt;/strong&gt;&lt;br&gt;물리적 저장 장치의 관점에서 본 데이터베이스로, 데이터가 실제로 저장되는 방법을 정의함. 하나의 내부 스키마만 존재함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;a href=&quot;https://prinha.tistory.com/entry/DB-3%EB%8B%A8%EA%B3%84-%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%99%B8%EB%B6%80%EA%B0%9C%EB%85%90%EB%82%B4%EB%B6%80%EC%8A%A4%ED%82%A4%EB%A7%88&quot;&gt;[DB] 3단계 데이터베이스-외부/개념/내부스키마&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;데이터 사전 (Data Dictionary)&lt;/h3&gt;
&lt;p&gt;데이터 사전은 데이터베이스에 저장되어 있는 모든 데이터 객체들에 대한 정보를 유지·관리하는 시스템으로, 메타데이터(Metadata)를 저장하고 관리함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;시스템 카탈로그&lt;/strong&gt;&lt;br&gt;데이터 사전은 시스템 카탈로그라고도 하며, DBMS가 스스로 생성하고 유지함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저장 내용&lt;/strong&gt;&lt;br&gt;데이터베이스 객체(테이블, 뷰, 인덱스 등)의 정의, 데이터 타입, 제약 조건, 사용자 권한, 무결성 규칙 등의 정보를 포함함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;접근 방식&lt;/strong&gt;&lt;br&gt;일반 사용자는 읽기만 가능하며, DBMS만이 데이터 사전을 갱신할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;역할&lt;/strong&gt;&lt;br&gt;데이터베이스 설계 및 관리의 기초 자료로 활용되며, 데이터 무결성 유지와 보안 관리에 중요한 역할을 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;데이터 디렉토리 (Data Directory)&lt;/h3&gt;
&lt;p&gt;데이터 디렉토리는 데이터 사전에 수록된 데이터에 실제로 접근하는 데 필요한 위치 정보를 관리하는 시스템임&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;물리적 위치 정보&lt;/strong&gt;&lt;br&gt;데이터가 실제로 저장된 물리적 위치와 접근 경로에 대한 정보를 유지함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시스템 전용&lt;/strong&gt;&lt;br&gt;데이터 디렉토리는 사용자가 접근할 수 없으며, DBMS만이 사용하고 관리함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;성능 최적화&lt;/strong&gt;&lt;br&gt;효율적인 데이터 접근과 검색을 위해 데이터의 물리적 위치 정보를 관리함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 사전과의 관계&lt;/strong&gt;&lt;br&gt;데이터 사전이 논리적 정보를 담당한다면, 데이터 디렉토리는 물리적 정보를 담당하여 상호 보완적인 역할을 수행함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;데이터베이스 언어(DDL, DML, DCl)&lt;/h2&gt;
&lt;p&gt;데이터베이스 언어는 데이터베이스를 정의하고 조작하며 제어하기 위해 사용되는 언어로, DDL, DML, DCL로 구분됨.&lt;/p&gt;
&lt;h3&gt;DDL (Data Definition Language, 데이터 정의어)&lt;/h3&gt;
&lt;p&gt;DDL은 데이터베이스 구조를 정의하거나 변경, 삭제하는 데 사용되는 언어&lt;/p&gt;
&lt;p&gt;스키마, 테이블, 뷰, 인덱스 등의 데이터베이스 객체를 생성하고 관리함&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;명령어&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;기능&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;설명&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;CREATE&lt;/td&gt;
&lt;td&gt;생성&lt;/td&gt;
&lt;td&gt;새로운 데이터베이스 객체(테이블, 뷰, 인덱스 등)를 생성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ALTER&lt;/td&gt;
&lt;td&gt;변경&lt;/td&gt;
&lt;td&gt;기존 데이터베이스 객체의 구조를 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DROP&lt;/td&gt;
&lt;td&gt;삭제&lt;/td&gt;
&lt;td&gt;데이터베이스 객체를 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TRUNCATE&lt;/td&gt;
&lt;td&gt;내용 삭제&lt;/td&gt;
&lt;td&gt;테이블의 모든 데이터를 삭제하지만 구조는 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- &lt;strong&gt;특징&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DDL 명령어는 자동으로 커밋(Auto Commit)되므로 ROLLBACK이 불가능합니다.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- &lt;strong&gt;예시&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CREATE TABLE 학생 (학번 INT, 이름 VARCHAR(50), 학과 VARCHAR(50));&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;DML (Data Manipulation Language, 데이터 조작어)&lt;/h3&gt;
&lt;p&gt;DML은 데이터베이스 내의 데이터를 검색, 삽입, 수정, 삭제하는 데 사용되는 언어입니다. 사용자가 데이터베이스의 데이터를 실질적으로 처리하는 데 사용됩니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;명령어&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;기능&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;설명&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;SELECT&lt;/td&gt;
&lt;td&gt;검색&lt;/td&gt;
&lt;td&gt;데이터베이스에서 데이터를 검색하고 조회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;INSERT&lt;/td&gt;
&lt;td&gt;삽입&lt;/td&gt;
&lt;td&gt;테이블에 새로운 데이터를 삽입&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UPDATE&lt;/td&gt;
&lt;td&gt;수정&lt;/td&gt;
&lt;td&gt;테이블의 기존 데이터를 수정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;td&gt;삭제&lt;/td&gt;
&lt;td&gt;테이블에서 특정 데이터를 삭제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- &lt;strong&gt;특징&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DML 명령어는 트랜잭션을 발생시키므로 COMMIT이나 ROLLBACK을 통해 변경 사항을 확정하거나 취소할 수 있습니다.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- &lt;strong&gt;예시&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SELECT * FROM 학생 WHERE 학과 = &amp;#39;컴퓨터공학&amp;#39;;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;INSERT INTO 학생 VALUES (20250001, &amp;#39;김철수&amp;#39;, &amp;#39;컴퓨터공학&amp;#39;);&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;DCL (Data Control Language, 데이터 제어어)&lt;/h3&gt;
&lt;p&gt;DCL은 데이터베이스에 대한 접근 권한을 제어하고 트랜잭션을 관리하는 데 사용되는 언어입니다. 데이터의 보안과 무결성을 유지하는 역할을 합니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;명령어&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;기능&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;설명&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;GRANT&lt;/td&gt;
&lt;td&gt;권한 부여&lt;/td&gt;
&lt;td&gt;사용자에게 데이터베이스 객체에 대한 접근 권한을 부여&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;REVOKE&lt;/td&gt;
&lt;td&gt;권한 취소&lt;/td&gt;
&lt;td&gt;사용자에게 부여된 권한을 취소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;COMMIT&lt;/td&gt;
&lt;td&gt;트랜잭션 확정&lt;/td&gt;
&lt;td&gt;트랜잭션의 작업 결과를 데이터베이스에 영구적으로 반영&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROLLBACK&lt;/td&gt;
&lt;td&gt;트랜잭션 취소&lt;/td&gt;
&lt;td&gt;트랜잭션의 작업을 취소하고 이전 상태로 되돌림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SAVEPOINT&lt;/td&gt;
&lt;td&gt;저장점 설정&lt;/td&gt;
&lt;td&gt;트랜잭션 내에서 특정 지점을 표시하여 부분 롤백 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- &lt;strong&gt;특징&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DCL은 데이터베이스의 보안과 무결성을 관리하며, 다중 사용자 환경에서 중요한 역할을 합니다.&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- &lt;strong&gt;예시&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GRANT SELECT, INSERT ON 학생 TO 사용자1;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REVOKE INSERT ON 학생 FROM 사용자1;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COMMIT;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;데이터베이스 언어의 분류 비교&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;구분&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DDL&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DML&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;DCL&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;대상&lt;/td&gt;
&lt;td&gt;데이터베이스 구조&lt;/td&gt;
&lt;td&gt;데이터베이스 내의 데이터&lt;/td&gt;
&lt;td&gt;사용자 권한 및 트랜잭션&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주요 명령어&lt;/td&gt;
&lt;td&gt;CREATE, ALTER, DROP, TRUNCATE&lt;/td&gt;
&lt;td&gt;SELECT, INSERT, UPDATE, DELETE&lt;/td&gt;
&lt;td&gt;GRANT, REVOKE, COMMIT, ROLLBACK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;트랜잭션&lt;/td&gt;
&lt;td&gt;자동 커밋 (ROLLBACK 불가)&lt;/td&gt;
&lt;td&gt;수동 커밋 (ROLLBACK 가능)&lt;/td&gt;
&lt;td&gt;트랜잭션 제어&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;영향 범위&lt;/td&gt;
&lt;td&gt;스키마 및 객체 구조&lt;/td&gt;
&lt;td&gt;데이터 내용&lt;/td&gt;
&lt;td&gt;보안 및 무결성&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 주체&lt;/td&gt;
&lt;td&gt;데이터베이스 관리자(DBA)&lt;/td&gt;
&lt;td&gt;일반 사용자 및 응용 프로그램&lt;/td&gt;
&lt;td&gt;데이터베이스 관리자(DBA)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2&gt;RDBMS 기본 용어&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;릴레이션 (Relation):&lt;/strong&gt; 행과 열로 구성된 2차원 테이블 형태의 데이터 구조&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;속성 (Attribute):&lt;/strong&gt; 릴레이션의 열(Column)로, 개체의 특성을 나타냄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;튜플 (Tuple):&lt;/strong&gt; 릴레이션의 행(Row)로, 하나의 개체에 대한 정보를 표현&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;도메인 (Domain):&lt;/strong&gt; 하나의 속성이 가질 수 있는 모든 가능한 값들의 집합&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;차수 (Degree):&lt;/strong&gt; 릴레이션에 포함된 속성(열)의 개수&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;카디널리티 (Cardinality):&lt;/strong&gt; 릴레이션에 포함된 튜플(행)의 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;릴레이션의 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;튜플의 유일성:&lt;/strong&gt; 릴레이션 내의 모든 튜플은 유일해야 함. 즉, 중복된 튜플은 존재할 수 없음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;튜플의 무순서:&lt;/strong&gt; 튜플들 사이에는 순서가 없음. 어떤 순서로 저장되어 있든 같은 릴레이션으로 간주됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;속성의 무순서:&lt;/strong&gt; 속성들 사이에도 순서가 없음. 속성의 위치가 바뀌어도 같은 릴레이션임&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;속성의 원자성:&lt;/strong&gt; 각 속성의 값은 더 이상 분해할 수 없는 원자값만 가질 수 있음. 다중 값이나 복합 값을 가질 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;데이터베이스의 키&lt;/h2&gt;
&lt;h3&gt;슈퍼 키 (Super Key)&lt;/h3&gt;
&lt;p&gt;튜플을 유일하게 식별할 수 있는 속성 또는 속성들의 집합. 유일성은 만족하지만 최소성은 보장되지 않음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 학생 테이블에서 (학번), (학번, 이름), (학번, 주민등록번호) 모두 슈퍼 키가 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;후보 키 (Candidate Key)&lt;/h3&gt;
&lt;p&gt;튜플을 유일하게 식별할 수 있는 최소한의 속성 집합. &lt;strong&gt;유일성&lt;/strong&gt;과 &lt;strong&gt;최소성&lt;/strong&gt;을 모두 만족함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;특징:&lt;/strong&gt; 슈퍼 키 중에서 불필요한 속성을 제거한 키&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 학생 테이블에서 학번, 주민등록번호가 각각 후보 키가 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;기본 키 (Primary Key)&lt;/h3&gt;
&lt;p&gt;후보 키 중에서 선택된 주 키. 릴레이션에서 튜플을 유일하게 식별하기 위해 사용됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;특징:&lt;/strong&gt; NULL 값을 가질 수 없고, 중복된 값이 있어서는 안 됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;선정 기준:&lt;/strong&gt; 자주 사용되는 속성, 값이 변경될 가능성이 적은 속성, 간단한 속성을 선택&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 학생 테이블에서 학번을 기본 키로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;대체 키 (Alternate Key)&lt;/h3&gt;
&lt;p&gt;후보 키 중에서 기본 키로 선택되지 않은 나머지 키&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 학생 테이블에서 학번을 기본 키로 선택했다면, 주민등록번호는 대체 키가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;외래 키 (Foreign Key)&lt;/h3&gt;
&lt;p&gt;다른 릴레이션의 기본 키를 참조하는 속성 또는 속성들의 집합. 릴레이션 간의 관계를 표현하는 데 사용됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;특징:&lt;/strong&gt; 참조 무결성 제약 조건을 유지하기 위해 사용됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;특징:&lt;/strong&gt; NULL 값을 가질 수 있고, 중복된 값도 허용됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 수강 테이블의 학번 속성이 학생 테이블의 학번(기본 키)을 참조하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;복합 키 (Composite Key)&lt;/h3&gt;
&lt;p&gt;두 개 이상의 속성을 조합하여 만든 키. 단일 속성으로는 튜플을 유일하게 식별할 수 없을 때 사용됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 수강 테이블에서 (학번, 과목코드)를 조합하여 기본 키로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;
&lt;img src=&quot;https://www.notion.so/icons/fire_gray.svg&quot; alt=&quot;https://www.notion.so/icons/fire_gray.svg&quot; width=&quot;40px&quot; /&gt;

&lt;h3&gt;최소성 (Minimality)&lt;/h3&gt;
&lt;p&gt;최소성이란 키를 구성하는 속성들 중에서 &lt;strong&gt;어느 하나라도 제거하면 튜플을 유일하게 식별할 수 없게 되는 성질&lt;/strong&gt;을 의미합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;설명:&lt;/strong&gt; 키에 포함된 모든 속성이 식별에 꼭 필요하며, 불필요한 속성이 포함되어 있지 않아야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; (학번, 이름)으로 학생을 식별할 수 있지만, 학번만으로도 식별이 가능하다면 이름은 불필요한 속성이므로 최소성을 만족하지 못함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;후보 키와의 관계:&lt;/strong&gt; 후보 키는 유일성과 최소성을 모두 만족해야 하므로, 슈퍼 키 중에서 최소성을 만족하는 키만 후보 키가 될 수 있음&lt;/aside&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;무결성 제약&lt;/h2&gt;
&lt;h3&gt;개체 무결성 (Entity Integrity)&lt;/h3&gt;
&lt;p&gt;기본 키는 NULL 값을 가질 수 없으며, 릴레이션 내에서 중복될 수 없다는 제약 조건&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;목적:&lt;/strong&gt; 각 튜플을 유일하게 식별할 수 있도록 보장&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;규칙:&lt;/strong&gt; 기본 키를 구성하는 모든 속성은 NULL 값을 가질 수 없고, 릴레이션 내에서 유일한 값을 가져야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 학생 테이블에서 학번(기본 키)은 NULL이 될 수 없으며, 모든 학생은 서로 다른 학번을 가져야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;참조 무결성 (Referential Integrity)&lt;/h3&gt;
&lt;p&gt;외래 키 값은 참조하는 릴레이션의 기본 키 값과 일치하거나 NULL이어야 한다는 제약 조건&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;목적:&lt;/strong&gt; 릴레이션 간의 관계를 일관성 있게 유지하고, 존재하지 않는 데이터를 참조하는 것을 방지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;규칙:&lt;/strong&gt; 외래 키는 참조하는 릴레이션에 존재하는 값이거나 NULL이어야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 수강 테이블의 학번(외래 키)은 학생 테이블에 존재하는 학번이거나 NULL이어야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;참고:&lt;/strong&gt; 자세한 내용은 아래 참조 무결성 제약 조건 섹션 참조&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;

&lt;h3&gt;왜 외래 키 값이 NULL일 수 있는가?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;외래 키가 NULL을 허용하는 이유는 관계의 선택성(Optionality) 때문입니다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;관계의 종류&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;필수 관계 (Mandatory Relationship):&lt;/strong&gt; 자식 릴레이션의 모든 튜플이 반드시 부모 릴레이션의 튜플을 참조해야 하는 경우. 이때는 외래 키가 NULL을 허용하지 않음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;선택적 관계 (Optional Relationship):&lt;/strong&gt; 자식 릴레이션의 튜플이 부모 릴레이션의 튜플을 참조할 수도 있고, 참조하지 않을 수도 있는 경우. 이때는 외래 키가 NULL을 허용함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;실제 예시&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;직원 테이블:&lt;/strong&gt; 직원ID(기본 키), 이름, 부서ID(외래 키)&lt;/li&gt;
&lt;li&gt;신입 사원이 아직 부서 배치를 받지 못한 경우, 부서ID가 NULL일 수 있음&lt;/li&gt;
&lt;li&gt;이는 &amp;quot;모든 직원이 반드시 부서에 속해야 하는 것은 아니다&amp;quot;라는 업무 규칙을 반영함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;핵심 정리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;NULL 허용:&lt;/strong&gt; 관계가 선택적(Optional)인 경우 - 참조할 대상이 없어도 됨&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;NULL 불허:&lt;/strong&gt; 관계가 필수적(Mandatory)인 경우 - 반드시 참조할 대상이 있어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서 외래 키가 NULL을 허용하는 것은 &lt;strong&gt;현실 세계의 선택적 관계를 데이터베이스에서 표현하기 위한 것&lt;/strong&gt;이며, 설계 시 업무 규칙에 따라 NULL 허용 여부를 결정하게 됩니다.&lt;/p&gt;
&lt;/aside&gt;

&lt;h3&gt;도메인 무결성 (Domain Integrity)&lt;/h3&gt;
&lt;p&gt;각 속성 값은 해당 속성이 정의된 도메인에 속한 값이어야 한다는 제약 조건&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;목적:&lt;/strong&gt; 속성 값이 허용된 범위 내의 올바른 데이터 타입과 형식을 갖도록 보장&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;규칙:&lt;/strong&gt; 속성 값은 해당 속성의 도메인에서 정의한 데이터 타입, 길이, 형식, 범위 등을 만족해야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 나이 속성의 도메인이 0~150의 정수로 정의되어 있다면, -5나 200 같은 값은 입력할 수 없음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 성별 속성의 도메인이 {&amp;#39;남&amp;#39;, &amp;#39;여&amp;#39;}로 정의되어 있다면, 다른 값은 입력할 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;
&lt;img src=&quot;https://www.notion.so/icons/fire_gray.svg&quot; alt=&quot;https://www.notion.so/icons/fire_gray.svg&quot; width=&quot;40px&quot; /&gt;

&lt;h3&gt;&lt;strong&gt;참조 무결성 제약 조건&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;참조 무결성 제약 조건은 &lt;strong&gt;외래 키(Foreign Key)와 관련된 데이터 무결성 규칙&lt;/strong&gt;으로, 두 릴레이션 간의 관계를 일관성 있게 유지하기 위한 제약입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;핵심 개념&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;정의:&lt;/strong&gt; 외래 키 값은 참조하는 릴레이션의 기본 키 값과 일치하거나 NULL이어야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;목적:&lt;/strong&gt; 데이터베이스 내의 릴레이션들 간의 일관성을 보장하고, 존재하지 않는 데이터를 참조하는 것을 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;참조 무결성 규칙&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;삽입 규칙:&lt;/strong&gt; 자식 릴레이션에 튜플을 삽입할 때, 외래 키 값은 부모 릴레이션의 기본 키에 존재하는 값이거나 NULL이어야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;삭제 규칙:&lt;/strong&gt; 부모 릴레이션의 튜플을 삭제할 때, 다음 중 하나의 방법을 선택&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RESTRICTED (제한):&lt;/strong&gt; 자식 릴레이션에서 참조하고 있으면 삭제 불가&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CASCADE (연쇄):&lt;/strong&gt; 자식 릴레이션의 관련 튜플도 함께 삭제&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SET NULL:&lt;/strong&gt; 자식 릴레이션의 외래 키 값을 NULL로 설정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SET DEFAULT:&lt;/strong&gt; 자식 릴레이션의 외래 키 값을 기본값으로 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;수정 규칙:&lt;/strong&gt; 부모 릴레이션의 기본 키를 수정할 때, 삭제 규칙과 동일한 옵션 적용&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;예시&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;학생 테이블&lt;/strong&gt; (기본 키: 학번)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;수강 테이블&lt;/strong&gt; (기본 키: 수강번호, 외래 키: 학번)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;수강 테이블에 새로운 수강 정보를 입력할 때, 해당 학번이 학생 테이블에 존재해야 함&lt;/li&gt;
&lt;li&gt;학생 테이블에서 특정 학생을 삭제하려 할 때, 해당 학생의 수강 정보가 수강 테이블에 있다면 삭제 규칙에 따라 처리됨&lt;/aside&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SQL 뷰&lt;/h2&gt;
&lt;h3&gt;뷰의 개념&lt;/h3&gt;
&lt;p&gt;뷰(View)는 하나 이상의 기본 테이블로부터 유도된 가상 테이블(Virtual Table)로, 물리적으로 존재하지 않고 논리적으로만 존재하는 테이블이다. 뷰는 기본 테이블의 데이터를 기반으로 정의되며, 사용자에게는 실제 테이블처럼 보이지만 실제 데이터를 저장하지 않고 정의만 저장한다.&lt;/p&gt;
&lt;h3&gt;뷰의 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;가상 테이블:&lt;/strong&gt; 뷰는 물리적으로 데이터를 저장하지 않으며, 뷰에 대한 질의가 수행될 때 기본 테이블로부터 동적으로 데이터를 가져온다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;독립성:&lt;/strong&gt; 기본 테이블의 구조가 변경되어도 뷰를 이용하는 응용 프로그램은 변경할 필요가 없어 논리적 데이터 독립성을 제공한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보안성:&lt;/strong&gt; 사용자에게 기본 테이블의 특정 컬럼이나 행만 접근하도록 제한할 수 있어 데이터 보안을 강화할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;편리성:&lt;/strong&gt; 복잡한 질의를 뷰로 정의해두면 사용자는 간단한 질의만으로 원하는 결과를 얻을 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단순화:&lt;/strong&gt; 여러 테이블을 조인한 복잡한 쿼리를 뷰로 정의하여 데이터 접근을 단순화할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;뷰의 장점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;논리적 독립성 제공:&lt;/strong&gt; 응용 프로그램과 데이터베이스 간의 논리적 독립성을 제공하여 유지보수가 용이하다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 보안 강화:&lt;/strong&gt; 민감한 데이터를 숨기고 필요한 데이터만 노출할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;사용자 편의성:&lt;/strong&gt; 복잡한 쿼리를 단순화하여 사용자가 쉽게 데이터에 접근할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;동일 데이터의 다양한 관점 제공:&lt;/strong&gt; 같은 기본 테이블로부터 여러 개의 뷰를 생성하여 다양한 관점으로 데이터를 볼 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;뷰의 단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;성능 저하 가능성:&lt;/strong&gt; 뷰는 매번 기본 테이블로부터 데이터를 가져오므로 복잡한 뷰의 경우 성능이 저하될 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;갱신 제약:&lt;/strong&gt; 뷰를 통한 데이터 삽입, 수정, 삭제에는 제약이 있으며, 특정 조건을 만족하는 뷰만 갱신 가능하다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저장 공간:&lt;/strong&gt; 뷰의 정의는 시스템 카탈로그에 저장되므로 뷰가 많을 경우 저장 공간이 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;뷰의 생성과 삭제&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;생성:&lt;/strong&gt; CREATE VIEW 문을 사용하여 뷰를 생성한다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;CREATE VIEW 뷰이름 AS
SELECT 컬럼1, 컬럼2, ...
FROM 테이블명
WHERE 조건;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;삭제:&lt;/strong&gt; DROP VIEW 문을 사용하여 뷰를 삭제한다&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;DROP VIEW 뷰이름;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;뷰의 갱신 가능 조건&lt;/h3&gt;
&lt;p&gt;모든 뷰가 갱신 가능한 것은 아니며, 다음 조건을 만족하는 뷰만 갱신(INSERT, UPDATE, DELETE)이 가능하다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 기본 테이블로부터 유도된 뷰&lt;/li&gt;
&lt;li&gt;GROUP BY, HAVING, DISTINCT, 집계 함수를 사용하지 않은 뷰&lt;/li&gt;
&lt;li&gt;UNION, INTERSECT, MINUS 등의 집합 연산자를 사용하지 않은 뷰&lt;/li&gt;
&lt;li&gt;기본 테이블의 기본 키를 포함하는 뷰&lt;/li&gt;
&lt;li&gt;산술 연산자나 함수를 사용하여 계산된 컬럼이 없는 뷰&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;데이터베이스 설계&lt;/h2&gt;
&lt;h3&gt;데이터베이스 설계 단계&lt;/h3&gt;
&lt;p&gt;데이터베이스 설계는 사용자의 요구사항을 분석하여 효율적이고 안정적인 데이터베이스를 구축하기 위한 과정으로, 크게 5단계로 나뉜다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;요구사항 분석 (Requirements Analysis)&lt;/strong&gt;데이터베이스를 사용할 사용자들의 요구사항을 수집하고 분석하는 단계다. 어떤 데이터가 필요하고, 어떤 작업을 수행해야 하는지를 파악한다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;주요 활동:&lt;/strong&gt; 사용자 인터뷰, 기존 시스템 분석, 데이터 및 처리 요구사항 명세화&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과물:&lt;/strong&gt; 요구사항 명세서&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;개념적 설계 (Conceptual Design)&lt;/strong&gt;요구사항 분석 결과를 바탕으로 데이터베이스의 전체적인 구조를 추상적으로 표현하는 단계다. DBMS에 독립적인 개념적 스키마를 설계한다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;사용 도구:&lt;/strong&gt; ER 다이어그램 (Entity-Relationship Diagram)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;주요 요소:&lt;/strong&gt; 개체(Entity), 속성(Attribute), 관계(Relationship) 정의&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과물:&lt;/strong&gt; 개념적 스키마 (ER 다이어그램)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;논리적 설계 (Logical Design)&lt;/strong&gt;개념적 스키마를 특정 DBMS가 처리할 수 있는 논리적 스키마로 변환하는 단계다. 관계형 데이터베이스의 경우 테이블, 컬럼, 제약조건 등을 정의한다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;주요 활동:&lt;/strong&gt; ER 다이어그램을 릴레이션 스키마로 변환, 정규화 수행, 트랜잭션 인터페이스 설계&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과물:&lt;/strong&gt; 논리적 스키마 (릴레이션 스키마, 제약조건 명세)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;물리적 설계 (Physical Design)&lt;/strong&gt;논리적 스키마를 실제 저장장치에 저장할 수 있도록 물리적 구조를 설계하는 단계다. 성능을 고려하여 저장 구조, 접근 경로, 인덱스 등을 결정한다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;주요 활동:&lt;/strong&gt; 저장 레코드 형식 설계, 인덱스 설계, 접근 경로 설계, 저장 공간 할당&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고려사항:&lt;/strong&gt; 응답 시간, 저장 공간 효율, 트랜잭션 처리량&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과물:&lt;/strong&gt; 물리적 스키마&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;구현 (Implementation)&lt;/strong&gt;설계된 스키마를 실제 DBMS를 사용하여 데이터베이스로 구축하는 단계다. SQL의 DDL(Data Definition Language)을 사용하여 테이블, 인덱스, 제약조건 등을 생성한다.&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;주요 활동:&lt;/strong&gt; 테이블 생성, 인덱스 생성, 뷰 정의, 권한 설정, 테스트 데이터 입력&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;결과물:&lt;/strong&gt; 실제 운영 가능한 데이터베이스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;aside&gt;

&lt;p&gt;&lt;strong&gt;  설계 단계의 흐름&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;각 단계는 순차적으로 진행되며, 이전 단계의 결과물이 다음 단계의 입력이 된다. 상위 단계일수록 추상적이고 DBMS 독립적이며, 하위 단계로 갈수록 구체적이고 DBMS 종속적이다.&lt;/p&gt;
&lt;/aside&gt;

&lt;h2&gt;이상현상&lt;/h2&gt;
&lt;h3&gt;이상현상의 정의&lt;/h3&gt;
&lt;p&gt;이상현상(Anomaly)은 테이블이 제대로 정규화되지 않았을 때 발생하는 데이터의 중복성과 종속성 문제로 인한 불일치 현상이다. 잘못 설계된 테이블에서 데이터를 삽입, 삭제, 수정할 때 예상치 못한 문제가 발생할 수 있다.&lt;/p&gt;
&lt;h3&gt;이상현상의 종류&lt;/h3&gt;
&lt;h3&gt;1. 삽입 이상 (Insertion Anomaly)&lt;/h3&gt;
&lt;p&gt;새로운 데이터를 삽입할 때 불필요한 데이터까지 함께 삽입해야 하거나, 특정 데이터를 삽입할 수 없는 현상이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 학생 테이블에 (학번, 이름, 수강과목, 담당교수) 속성이 있을 때, 아직 수강 신청을 하지 않은 신입생의 정보를 입력하려면 수강과목과 담당교수에 NULL 값을 넣어야 한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문제점:&lt;/strong&gt; 원하는 정보만 독립적으로 삽입할 수 없고, 불필요한 NULL 값이나 더미 데이터가 발생한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2. 삭제 이상 (Deletion Anomaly)&lt;/h3&gt;
&lt;p&gt;특정 데이터를 삭제할 때 의도하지 않은 다른 데이터까지 함께 삭제되는 현상이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 학생이 수강 중인 과목을 취소하려고 해당 튜플을 삭제하면, 그 학생의 학번과 이름 등 기본 정보까지 함께 삭제될 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문제점:&lt;/strong&gt; 원하지 않는 정보까지 손실되어 데이터 무결성이 깨진다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. 갱신 이상 (Update Anomaly)&lt;/h3&gt;
&lt;p&gt;중복된 데이터 중 일부만 수정되어 데이터 불일치가 발생하는 현상이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; 한 교수가 여러 과목을 담당할 때, 교수의 연락처를 변경하려면 해당 교수가 담당하는 모든 튜플을 찾아서 수정해야 한다. 만약 일부만 수정하면 같은 교수의 연락처가 서로 다르게 저장된다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;문제점:&lt;/strong&gt; 데이터의 일관성이 깨지고, 모든 중복 데이터를 찾아 수정해야 하므로 비효율적이다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;이상현상의 해결 방법&lt;/h3&gt;
&lt;p&gt;이상현상은 테이블이 정규화되지 않았을 때 발생하므로, &lt;strong&gt;정규화(Normalization)&lt;/strong&gt;를 통해 테이블을 적절히 분해하고 함수적 종속성을 제거하면 해결할 수 있다. 정규화를 통해 데이터 중복을 최소화하고 각 테이블이 명확한 목적을 갖도록 설계해야 한다.&lt;/p&gt;
&lt;h2&gt;정규화&lt;/h2&gt;
&lt;h3&gt;정규화의 개념&lt;/h3&gt;
&lt;p&gt;정규화(Normalization)는 관계형 데이터베이스의 설계 과정에서 데이터의 중복을 최소화하고 데이터 무결성을 향상시키기 위해 테이블을 체계적으로 분해하는 과정이다. 이상현상을 제거하고 데이터베이스의 논리적 설계를 개선하는 것이 주요 목적이다.&lt;/p&gt;
&lt;h3&gt;정규화의 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;데이터 중복 최소화:&lt;/strong&gt; 같은 데이터를 여러 곳에 저장하지 않도록 테이블을 분해한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이상현상 방지:&lt;/strong&gt; 삽입, 삭제, 갱신 이상을 제거하여 데이터의 일관성을 유지한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;저장 공간 효율화:&lt;/strong&gt; 중복 데이터를 제거하여 저장 공간을 절약한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 무결성 향상:&lt;/strong&gt; 함수적 종속성을 명확히 하여 데이터의 정확성을 보장한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;유지보수 용이:&lt;/strong&gt; 데이터 구조가 체계적이어서 수정 및 관리가 쉽다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;정규화 과정&lt;/h3&gt;
&lt;p&gt;정규화는 단계적으로 진행되며, 각 단계를 정규형(Normal Form)이라고 한다. 일반적으로 제3정규형까지 수행하면 실무에서 충분하다.&lt;/p&gt;
&lt;h3&gt;제1정규형 (1NF, First Normal Form)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;조건:&lt;/strong&gt; 모든 속성의 도메인이 원자값(Atomic Value)만으로 구성되어야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;반복 그룹을 제거한다&lt;/li&gt;
&lt;li&gt;다중값 속성(Multi-valued Attribute)을 제거한다&lt;/li&gt;
&lt;li&gt;각 속성은 하나의 값만 가져야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;정규화 전 (1NF 위반):&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;이름&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;수강과목&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;td&gt;데이터베이스, 운영체제, 알고리즘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021002&lt;/td&gt;
&lt;td&gt;이영희&lt;/td&gt;
&lt;td&gt;자료구조, 네트워크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;정규화 후 (1NF 만족):&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;이름&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;수강과목&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;td&gt;운영체제&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;td&gt;알고리즘&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021002&lt;/td&gt;
&lt;td&gt;이영희&lt;/td&gt;
&lt;td&gt;자료구조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021002&lt;/td&gt;
&lt;td&gt;이영희&lt;/td&gt;
&lt;td&gt;네트워크&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;제2정규형 (2NF, Second Normal Form)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;조건:&lt;/strong&gt; 제1 정규형을 만족하고, 모든 비주요 속성이 기본 키에 완전 함수 종속되어야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;부분 함수 종속(Partial Functional Dependency)을 제거한다&lt;/li&gt;
&lt;li&gt;기본 키가 복합 키일 때, 기본 키의 일부분에만 종속되는 속성을 분리한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;정규화 전 (2NF 위반):&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;과목코드&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;학생이름&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;과목명&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;성적&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;CS101&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;CS102&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;td&gt;운영체제&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- 기본키: (학번, 과목코드)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- 학생이름은 학번에만 종속 → 부분 함수 종속&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- 과목명은 과목코드에만 종속 → 부분 함수 종속&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;정규화 후 (2NF 만족):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;학생 테이블:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;학생이름&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021002&lt;/td&gt;
&lt;td&gt;이영희&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;과목 테이블:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;과목코드&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;과목명&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;CS101&lt;/td&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CS102&lt;/td&gt;
&lt;td&gt;운영체제&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;수강 테이블:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;과목코드&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;성적&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;CS101&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;CS102&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;제3정규형 (3NF, Third Normal Form)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;조건:&lt;/strong&gt; 제2정규형을 만족하고, 모든 비주요 속성이 기본키에 이행적 함수 종속(Transitive Functional Dependency)이 되지 않아야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이행적 함수 종속을 제거한다&lt;/li&gt;
&lt;li&gt;A → B, B → C일 때 A → C가 성립하는 경우를 분리한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;정규화 전 (3NF 위반):&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;학생이름&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;학과코드&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;학과명&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;td&gt;CS&lt;/td&gt;
&lt;td&gt;컴퓨터공학과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021002&lt;/td&gt;
&lt;td&gt;이영희&lt;/td&gt;
&lt;td&gt;EE&lt;/td&gt;
&lt;td&gt;전자공학과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- 학번 → 학과코드 → 학과명 (이행적 함수 종속)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;정규화 후 (3NF 만족):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;학생 테이블:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;학생이름&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;학과코드&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;김철수&lt;/td&gt;
&lt;td&gt;CS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021002&lt;/td&gt;
&lt;td&gt;이영희&lt;/td&gt;
&lt;td&gt;EE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;학과 테이블:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학과코드&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;학과명&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;CS&lt;/td&gt;
&lt;td&gt;컴퓨터공학과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EE&lt;/td&gt;
&lt;td&gt;전자공학과&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;BCNF (Boyce-Codd Normal Form)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;조건:&lt;/strong&gt; 제3정규형을 만족하고, 모든 결정자(Determinant)가 후보키(Candidate Key)여야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3NF보다 강한 제약 조건을 가진다&lt;/li&gt;
&lt;li&gt;후보키가 아닌 속성이 다른 속성을 결정하는 경우를 제거한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;예시:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;정규화 전 (BCNF 위반):&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;과목명&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;교수&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;td&gt;김교수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021002&lt;/td&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;td&gt;김교수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- 후보키: (학번, 과목명)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- 교수 → 과목명 (교수가 결정자지만 후보키가 아님)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;정규화 후 (BCNF 만족):&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;수강 테이블:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;학번&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;교수&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;2021001&lt;/td&gt;
&lt;td&gt;김교수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2021002&lt;/td&gt;
&lt;td&gt;김교수&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;담당 테이블:&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;교수&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;과목명&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;김교수&lt;/td&gt;
&lt;td&gt;데이터베이스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;제4정규형 (4NF, Fourth Normal Form)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;조건:&lt;/strong&gt; BCNF를 만족하고, 다치 종속(Multi-valued Dependency)이 없어야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 속성이 다른 속성에 대해 다중값을 가지는 경우를 제거한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;제5정규형 (5NF, Fifth Normal Form)&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;조건:&lt;/strong&gt; 제4정규형을 만족하고, 조인 종속(Join Dependency)이 없어야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테이블을 분해했다가 다시 조인해도 정보 손실이 없어야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;

&lt;p&gt;&lt;strong&gt;  정규화의 장단점&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;장점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&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;strong&gt;단점:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;테이블 수가 증가하여 조인 연산이 많아짐&lt;/li&gt;
&lt;li&gt;조회 성능이 저하될 수 있음&lt;/li&gt;
&lt;li&gt;경우에 따라 반정규화(Denormalization)가 필요할 수 있음&lt;/aside&gt;

&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;트랜잭션&lt;/h2&gt;
&lt;h3&gt;트랜잭션의 개념&lt;/h3&gt;
&lt;p&gt;트랜잭션(Transaction)은 데이터베이스에서 수행되는 작업의 논리적 단위로, 분리될 수 없는 하나 이상의 데이터베이스 연산들의 집합을 의미한다. 예를 들어 은행 계좌 이체 시 출금과 입금은 반드시 함께 성공하거나 함께 실패해야 하는데, 이러한 일련의 연산들을 하나의 트랜잭션으로 묶어 처리한다.&lt;/p&gt;
&lt;h3&gt;트랜잭션의 특성 (ACID)&lt;/h3&gt;
&lt;p&gt;트랜잭션은 데이터베이스의 무결성을 보장하기 위해 ACID라는 4가지 특성을 만족해야 한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 원자성 (Atomicity)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션의 모든 연산이 완전히 수행되거나 전혀 수행되지 않아야 한다 (All or Nothing)&lt;/li&gt;
&lt;li&gt;트랜잭션 도중 오류 발생 시 모든 작업이 취소되고 원래 상태로 복구된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 일관성 (Consistency)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션 실행 전후에 데이터베이스가 일관된 상태를 유지해야 한다&lt;/li&gt;
&lt;li&gt;트랜잭션이 성공적으로 완료되면 언제나 일관성 있는 데이터베이스 상태로 변환된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 격리성 (Isolation)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;여러 트랜잭션이 동시에 실행될 때 각 트랜잭션은 다른 트랜잭션의 영향을 받지 않고 독립적으로 실행되는 것처럼 동작해야 한다&lt;/li&gt;
&lt;li&gt;한 트랜잭션의 중간 결과가 다른 트랜잭션에게 보이지 않아야 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 지속성 (Durability)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션이 성공적으로 완료(Commit)되면 그 결과는 영구적으로 데이터베이스에 반영되어야 한다&lt;/li&gt;
&lt;li&gt;시스템 장애가 발생해도 커밋된 트랜잭션의 결과는 보존된다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;트랜잭션의 주요 연산&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Commit&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션의 모든 연산이 성공적으로 완료되었음을 알리는 연산&lt;/li&gt;
&lt;li&gt;트랜잭션의 결과를 데이터베이스에 영구적으로 반영한다&lt;/li&gt;
&lt;li&gt;Commit 이후에는 트랜잭션을 되돌릴 수 없다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. Rollback&lt;/strong&gt;&lt;/p&gt;
&lt;ul&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;strong&gt;3. Savepoint&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션 내에서 특정 지점을 저장하는 연산&lt;/li&gt;
&lt;li&gt;전체 트랜잭션을 롤백하지 않고 특정 Savepoint까지만 롤백할 수 있다&lt;/li&gt;
&lt;li&gt;긴 트랜잭션에서 부분적인 롤백이 필요할 때 유용하다&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;

&lt;p&gt;&lt;strong&gt;  트랜잭션 예시&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;계좌 이체 시나리오:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-sql&quot;&gt;BEGIN TRANSACTION;  -- 트랜잭션 시작

-- A 계좌에서 10만원 출금
UPDATE 계좌 SET 잔액 = 잔액 - 100000 WHERE 계좌번호 = &amp;#39;A&amp;#39;;

-- B 계좌에 10만원 입금
UPDATE 계좌 SET 잔액 = 잔액 + 100000 WHERE 계좌번호 = &amp;#39;B&amp;#39;;

COMMIT;  -- 모든 연산 성공 시 커밋
-- 또는 오류 발생 시
ROLLBACK;  -- 모든 연산 취소&lt;/code&gt;&lt;/pre&gt;
&lt;/aside&gt;

&lt;h2&gt;동시성 제어(Concurrency Control)&lt;/h2&gt;
&lt;h3&gt;동시성 제어의 개념&lt;/h3&gt;
&lt;p&gt;동시성 제어(Concurrency Control)는 다수의 사용자가 동시에 데이터베이스에 접근하여 트랜잭션을 수행할 때, 각 트랜잭션이 정확하고 일관된 결과를 얻을 수 있도록 제어하는 기법이다. 여러 트랜잭션이 동시에 실행되면서도 데이터의 무결성과 일관성을 유지하는 것이 핵심 목표다.&lt;/p&gt;
&lt;h3&gt;동시성 제어의 목적&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;데이터 무결성 보장:&lt;/strong&gt; 동시에 실행되는 트랜잭션들이 서로 간섭하지 않도록 하여 데이터베이스의 정확성을 유지한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;일관성 유지:&lt;/strong&gt; 트랜잭션 실행 전후로 데이터베이스가 일관된 상태를 유지하도록 보장한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;시스템 성능 향상:&lt;/strong&gt; 여러 트랜잭션을 동시에 처리함으로써 시스템의 처리량과 응답 시간을 개선한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;격리성 보장:&lt;/strong&gt; 각 트랜잭션이 독립적으로 실행되는 것처럼 보이도록 하여 ACID 특성 중 격리성을 구현한다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;동시성 제어 문제점&lt;/h3&gt;
&lt;p&gt;동시성 제어가 제대로 이루어지지 않으면 다음과 같은 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 갱신 손실 (Lost Update)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;두 개 이상의 트랜잭션이 같은 데이터를 동시에 갱신할 때, 하나의 갱신 결과가 다른 트랜잭션의 갱신 결과로 덮어씌워지는 문제&lt;/li&gt;
&lt;li&gt;예: T1과 T2가 동시에 같은 계좌에서 출금하면 한 쪽의 출금 내역이 사라질 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 모순성 (Inconsistency)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 트랜잭션이 여러 개의 데이터를 갱신하는 도중에 다른 트랜잭션이 그 데이터들을 접근하여 일관성 없는 상태의 데이터를 읽는 문제&lt;/li&gt;
&lt;li&gt;예: 계좌 이체 중 출금은 완료되었지만 입금 전에 다른 트랜잭션이 잔액을 조회하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 연쇄 복귀 (Cascading Rollback)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 트랜잭션이 롤백될 때, 해당 트랜잭션이 갱신한 데이터를 읽은 다른 트랜잭션들도 함께 롤백되어야 하는 문제&lt;/li&gt;
&lt;li&gt;여러 트랜잭션이 연쇄적으로 롤백되면 시스템 성능이 크게 저하된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 비완료 의존성 (Uncommitted Dependency, Dirty Read)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 트랜잭션이 아직 커밋되지 않은 다른 트랜잭션의 갱신 결과를 읽는 문제&lt;/li&gt;
&lt;li&gt;만약 갱신한 트랜잭션이 롤백되면 잘못된 데이터를 읽은 것이 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;5. 반복 불가능 읽기 (Non-repeatable Read)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 트랜잭션이 같은 데이터를 두 번 읽을 때, 그 사이에 다른 트랜잭션이 데이터를 수정하여 두 번의 읽기 결과가 다른 문제&lt;/li&gt;
&lt;li&gt;트랜잭션 내에서 데이터의 일관성이 깨진다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;6. 유령 데이터 읽기 (Phantom Read)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;한 트랜잭션이 같은 조건으로 데이터를 두 번 조회할 때, 그 사이에 다른 트랜잭션이 데이터를 삽입하거나 삭제하여 조회 결과가 달라지는 문제&lt;/li&gt;
&lt;li&gt;반복 불가능 읽기와 유사하지만 행의 개수 자체가 변하는 차이가 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;동시성 제어 기법&lt;/h3&gt;
&lt;p&gt;동시성 제어를 위한 다양한 기법들이 존재하며, 각각의 기법은 서로 다른 방식으로 트랜잭션 간의 충돌을 방지하고 데이터 일관성을 유지한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. 로킹 (Locking)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;로킹은 가장 널리 사용되는 동시성 제어 기법으로, 트랜잭션이 데이터에 접근하기 전에 잠금(Lock)을 획득하여 다른 트랜잭션의 접근을 제어하는 방법이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;공유 락(Shared Lock, S-Lock):&lt;/strong&gt; 읽기 연산을 위한 잠금으로, 여러 트랜잭션이 동시에 획득할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배타 락(Exclusive Lock, X-Lock):&lt;/strong&gt; 쓰기 연산을 위한 잠금으로, 한 트랜잭션만 획득할 수 있다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;2단계 로킹 프로토콜(Two-Phase Locking, 2PL):&lt;/strong&gt; 확장 단계(Growing Phase)에서는 락만 획득하고, 축소 단계(Shrinking Phase)에서는 락만 해제하여 직렬성을 보장한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;2. 타임스탬프 순서 (Timestamp Ordering)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;각 트랜잭션에 고유한 타임스탬프를 부여하고, 타임스탬프 순서대로 트랜잭션을 실행하는 것처럼 동작하도록 제어하는 기법이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;트랜잭션이 시작될 때 시스템 시간이나 논리적 카운터를 기반으로 타임스탬프를 할당한다&lt;/li&gt;
&lt;li&gt;데이터 항목마다 읽기 타임스탬프(Read-Timestamp)와 쓰기 타임스탬프(Write-Timestamp)를 유지한다&lt;/li&gt;
&lt;li&gt;타임스탬프 순서를 위반하는 연산은 거부되고 해당 트랜잭션은 롤백된다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;3. 낙관적 검증 (Optimistic Concurrency Control)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;트랜잭션 실행 중에는 제약을 가하지 않고, 트랜잭션이 완료될 시점에 검증(Validation)을 수행하는 기법이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;읽기 단계(Read Phase):&lt;/strong&gt; 트랜잭션이 데이터를 읽고 로컬 복사본에서 작업한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검증 단계(Validation Phase):&lt;/strong&gt; 트랜잭션이 커밋을 요청할 때 충돌이 있는지 검사한다&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;쓰기 단계(Write Phase):&lt;/strong&gt; 검증이 성공하면 로컬 복사본의 변경 사항을 데이터베이스에 반영한다&lt;/li&gt;
&lt;li&gt;충돌이 적은 환경에서 효율적이지만, 충돌이 많으면 롤백이 빈번하게 발생한다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;4. 다중 버전 동시성 제어 (Multi-Version Concurrency Control, MVCC)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;데이터의 여러 버전을 유지하여 읽기와 쓰기 연산이 서로 블로킹되지 않도록 하는 기법이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;각 트랜잭션은 자신의 타임스탬프에 해당하는 데이터 버전을 읽는다&lt;/li&gt;
&lt;li&gt;쓰기 연산은 새로운 버전을 생성하며, 이전 버전은 유지된다&lt;/li&gt;
&lt;li&gt;읽기 연산이 쓰기 연산을 블로킹하지 않아 동시성이 향상된다&lt;/li&gt;
&lt;li&gt;PostgreSQL, MySQL(InnoDB), Oracle 등 많은 상용 DBMS에서 사용된다&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt;

&lt;p&gt;&lt;strong&gt;  동시성 제어 기법 비교&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;로킹:&lt;/strong&gt; 구현이 간단하지만 데드락(Deadlock) 발생 가능성이 있고, 대기 시간이 길어질 수 있다&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;타임스탬프:&lt;/strong&gt; 데드락이 발생하지 않지만, 롤백이 빈번할 수 있고 연쇄 롤백의 위험이 있다&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;낙관적 검증:&lt;/strong&gt; 충돌이 적을 때 성능이 좋지만, 충돌이 많으면 오히려 비효율적이다&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;MVCC:&lt;/strong&gt; 읽기 성능이 우수하지만 저장 공간이 추가로 필요하고 구현이 복잡하다&lt;/p&gt;
&lt;/aside&gt;

&lt;p&gt;&lt;a href=&quot;https://velog.io/@choidongkuen/%EB%8D%B0%EC%9D%B4%ED%84%B0%EB%B2%A0%EC%9D%B4%EC%8A%A4-%EB%8F%99%EC%8B%9C%EC%84%B1-%EC%A0%9C%EC%96%B4Concurrency-Controll&quot;&gt;[데이터베이스] 동시성 제어(Concurrency Controll)&lt;/a&gt;&lt;/p&gt;</description>
      <category>Knowledge/개발지식</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/24</guid>
      <comments>https://ryuwon-it.tistory.com/24#entry24comment</comments>
      <pubDate>Sun, 26 Oct 2025 23:19:40 +0900</pubDate>
    </item>
    <item>
      <title>[Java] DTO에 Record를 사용한다구??</title>
      <link>https://ryuwon-it.tistory.com/23</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;요새 프로젝트를 하다보면 DTO를 작성할때 클래스 기반이 아닌 Record로 코드를 작성하는 경우가 자주 보이고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 나도... 그렇다면 왜 Record로 작성하는 사람들이 점차 많아지고 있는걸까??&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h1&gt;Record를 DTO로 사용하는 이유&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클래스 기반 DTO를 작성하다 보면 늘 마주치는 게 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Getter, Setter, 생성자, toString, equals, hashCode&amp;hellip;&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;Java 16&lt;/b&gt;부터 정식으로 등장한 record는 이걸 아주 깔끔하게 해결해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이게 단순한 문법 편의성 수준이 아니라, 설계 관점에서도 꽤 괜찮은 선택지가 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DTO란 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO(Data Transfer Object)는 말 그대로 &lt;b&gt;데이터를 전달하기 위한 객체&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 &amp;rarr; 컨트롤러, 컨트롤러 &amp;rarr; 뷰, 또는 API 응답 등 계층 간 데이터를 깔끔하게 옮길 때 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 이런 식이다:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Getter
@Setter
@Builder
public class UserDto {
    private Long userId;
    private String nickname;
    private String email;
    private String profileImg;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지는 흔하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이게 쌓이다 보면 클래스마다 Getter, Setter, Builder, toString, equals, hashCode를 죄다 달고 있어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실상 로직은 1도 없는데 코드만 괜히 길어지는 셈이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Record가 등장한 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record는 불변(immutable)한 데이터를 간결하게 표현하기 위해 만들어졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드만 선언하면, 나머지는 &lt;b&gt;자동으로&lt;/b&gt; 해결된다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public record UserDto(Long userId, String nickname, String email, String profileImg) {}
&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: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 필드를 private final로 선언&lt;/li&gt;
&lt;li&gt;생성자&lt;/li&gt;
&lt;li&gt;Getter (필드명 그대로 메서드로 제공됨)&lt;/li&gt;
&lt;li&gt;equals, hashCode, toString&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;&amp;ldquo;데이터 전달만 하는 객체&amp;rdquo;라면 record가 DTO보다 압도적으로 간결하다.&lt;/b&gt;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 record를 DTO로 쓰는 게 좋은가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 불변성을 명시적으로 보장한다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO는 보통 Setter가 있기 때문에 객체 상태를 쉽게 바꿀 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 record는 필드가 전부 final이라 생성 이후 수정이 불가능하다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;UserDto dto = new UserDto(1L, &quot;junho&quot;, &quot;test@test.com&quot;, &quot;img.png&quot;);
// dto.nickname = &quot;changed&quot;; // ❌ 컴파일 에러

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 응답이나 서비스 간 데이터 전달에서 불변 객체는 &lt;b&gt;안전하고, 예측 가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티스레드 환경에서도 동기화 걱정을 덜 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 보일러플레이트 코드 제거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DTO 만들 때마다 Getter, Setter, toString, equals, hashCode, Builder 달고 있는 거 솔직히 귀찮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record는 그냥 필드만 적으면 끝이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public record PostDto(Long id, String title, String content) {}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸로도 equals, hashCode, toString이 전부 깔끔하게 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가독성도 좋아지고 코드 라인 수도 줄어든다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 의도가 명확하다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record를 보면 딱 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;아, 이건 데이터 전달용 객체구나.&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로직이 붙어있지 않고 필드만 존재하니까 역할이 명확해진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 DTO는 Lombok이나 메서드가 얹히기 시작하면 &lt;b&gt;&amp;ldquo;이게 진짜 DTO 맞나?&amp;rdquo;&lt;/b&gt; 싶을 때가 종종 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;커스터마이징은 불편하지 않을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record는 기본적으로 불변성을 지키는 설계라 &lt;b&gt;Setter나 상태 변경 로직은 추가할 수 없다.&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;로 어느 정도 확장이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 엔티티에서 DTO로 변환할 때 Builder 대신 이렇게 쓸 수 있다&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;public record UserDto(Long userId, String nickname, String email, String profileImg) {
    public static UserDto of(User user) {
        return new UserDto(
            user.getId(),
            user.getNickname(),
            user.getEmail(),
            user.getProfileUrl()
        );
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 DTO 생성 로직이 명확해지고, 필드가 바뀌어도 of()만 수정하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의미 전달도 깔끔하고 유지보수도 편하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무조건 record로 바꾸면 좋을까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 그렇진 않다..! 아래 케이스는 DTO가 더 낫다.&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;DTO 내부에서 커스텀 로직이나 검증이 필요한 경우&lt;/li&gt;
&lt;li&gt;Java 16 미만 버전을 사용 중일 때&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 경우는 여전히 클래스로 DTO를 작성하는 게 낫다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;record는 깔끔하지만 확장성은 상대적으로 제한적이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;table id=&quot;293a58d4-9be6-80f2-a23d-fea8ffa09b5d&quot; style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;클래스 기반 DTO&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;Record 기반 DTO&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;293a58d4-9be6-807b-be61-fa0196fcd838&quot;&gt;
&lt;td id=&quot;VIIP&quot;&gt;가변성&lt;/td&gt;
&lt;td id=&quot;@uIU&quot;&gt;가변 (Setter 제공 가능)&lt;/td&gt;
&lt;td id=&quot;B]zi&quot;&gt;불변 (final 필드)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;293a58d4-9be6-8074-af61-edcf9e94df17&quot;&gt;
&lt;td id=&quot;VIIP&quot;&gt;코드 길이&lt;/td&gt;
&lt;td id=&quot;@uIU&quot;&gt;길고 반복적&lt;/td&gt;
&lt;td id=&quot;B]zi&quot;&gt;짧고 간결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;293a58d4-9be6-80c7-ba36-eb1fe9b8342d&quot;&gt;
&lt;td id=&quot;VIIP&quot;&gt;커스터마이징&lt;/td&gt;
&lt;td id=&quot;@uIU&quot;&gt;용이&lt;/td&gt;
&lt;td id=&quot;B]zi&quot;&gt;제한적&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;293a58d4-9be6-8042-bb6d-cd1d7c582574&quot;&gt;
&lt;td id=&quot;VIIP&quot;&gt;불변성 보장&lt;/td&gt;
&lt;td id=&quot;@uIU&quot;&gt;직접 설계 필요&lt;/td&gt;
&lt;td id=&quot;B]zi&quot;&gt;기본적으로 보장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr id=&quot;293a58d4-9be6-80eb-bf67-dec730b7c616&quot;&gt;
&lt;td id=&quot;VIIP&quot;&gt;버전&lt;/td&gt;
&lt;td id=&quot;@uIU&quot;&gt;모든 버전 가능&lt;/td&gt;
&lt;td id=&quot;B]zi&quot;&gt;Java 16 이상 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;데이터 전달만 하는 객체라면 record로 바꾸는 게 훨씬 깔끔하고 안전하다.&lt;/li&gt;
&lt;li&gt;로직이 붙거나 필드가 많다면 여전히 DTO 클래스로 가는 게 낫다.&lt;/li&gt;
&lt;li&gt;그리고 정적 팩토리 메서드(of) 패턴과 함께 쓰면 유지보수성도 챙길 수 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Programming/Java</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/23</guid>
      <comments>https://ryuwon-it.tistory.com/23#entry23comment</comments>
      <pubDate>Sat, 25 Oct 2025 22:20:13 +0900</pubDate>
    </item>
    <item>
      <title>[CS] HTTPS에 대해 알아보기</title>
      <link>https://ryuwon-it.tistory.com/22</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다들 인터넷을 한 경험이 있다면 주소를 치게 되었을 때 http://, https://를 입력했던 경험이 있을 것이다.&lt;br /&gt;여기서 그럼 http와 https란 뭘까??&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP(Hypertext Transfer Protocol)는 웹에서 클라이언트와 서버 간 통신을 위한 통신 규약.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, HTTP는 암호화되지 않은 평문 데이터를 주고받기 때문에 해커가 MITM, 스니핑 공격을 할 경우 노출될 수 있다는 위험이 존재한다. 이를 해결하기 위해 &lt;b&gt;HTTPS&lt;/b&gt;가 등장..!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;603&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mdJIf/dJMb9P7tyJl/kSAI40pkhWDHriVXSQXlsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mdJIf/dJMb9P7tyJl/kSAI40pkhWDHriVXSQXlsk/img.png&quot; data-alt=&quot;HTTP를 사용할 경우 스니핑 했을때 이렇게 다 보인다..&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mdJIf/dJMb9P7tyJl/kSAI40pkhWDHriVXSQXlsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmdJIf%2FdJMb9P7tyJl%2FkSAI40pkhWDHriVXSQXlsk%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;846&quot; height=&quot;603&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;846&quot; data-origin-height=&quot;603&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HTTP를 사용할 경우 스니핑 했을때 이렇게 다 보인다..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS(Hypertext Transfer Protocol Secure)는 HTTP에 데이터 암호화를 추가하여, 암호화된 데이터를 전송하기 때문에 제 3자가 볼 수 없도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포트 번호도 서로 다르다. HTTP는 80, HTTPS는 443이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 어떤 원리로 HTTPS가 동작하는 걸까?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTPS의 동작원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTPS는 HTTP + SSL/TLS를 추가한 프로토콜이라 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;SSL&lt;/b&gt;은 인증과 데이터 무결성을 위해 데이터를 안전하게 전송하기 위해 만들어진 프로토콜이다. http 뿐만 아니라 ftp, smtp 등 다양한 프로토콜과 합쳐질 수 있기에 암호화가 필요한 프로토콜과 합쳐 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SSL&lt;/b&gt;에서 &lt;b&gt;*&lt;i&gt;보다 개선된 알고리즘을 이용하여 개발 된 것이 *&lt;/i&gt;TLS&lt;/b&gt;. &lt;b&gt;SSL&lt;/b&gt;의 후속 버전이 &lt;b&gt;TLS&lt;/b&gt;라고 보면 된다. 사실 그래서 대부분 TLS를 사용하고 있지만 SSL이라는 용어가 많이 쓰이는 편이다.&lt;br /&gt;&lt;br /&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;우선 인터넷을 사용한 통신에서 보안을 확보하려면 두 통신 당사자가 서로 신뢰할 수 있는 자임을 확인하고 서로 간의 통신이 도청되는 것을 방지해야한다. 따라서 서로 자신을 신뢰할 수 있음을 알리기 위해 전자 서명이 포함된 인증서를 사용하여, 도청을 방지하기 위해 공개키 방식으로 통신 내용을 암호화한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SSL/TLS Handshake&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP 연결을 할때도 3-way-handshake를 진행하지만 SSL/TLS도 동일하게 서로가 통신할 준비가 되어있는지 확인하는 과정인 handshake를 거친다. handshake는 클라이언트와 서버에서 지원하는 암호화 알고리즘, 키 교환 알고리즘에 따라 달라지지만 일반적으로 ECDHE 키 교환 알고리즘을 사용한다.&lt;/p&gt;
&lt;aside&gt;&lt;img src=&quot;/icons/fire_lightgray.svg&quot; alt=&quot;/icons/fire_lightgray.svg&quot; width=&quot;40px&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;RSA를 사용안하고 왜 ECDHE..?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TLS 버전 1.2 전까지는 RSA, ECDHE, DHE, PSK 등의 여러 방식들이 가능했었다.&lt;br /&gt;그래서 과거에는 많은 서버들이 RSA 방식을 많이 애용했었지만 RSA에는 PFS(Perfect Forward Secrecy) 기능이 존재하지 않아서 만약 서버의 개인 키가 털리게 될 경우, 이전 통신들도 복호화 할 수 있다는 문제가 발생.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기에 PFS가 기본적으로 제공되는 ECDHE 방식이 주류로 사용되고 있다.&lt;/p&gt;
&lt;/aside&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TCP handshake 과정을 통해 클라이언트와 서버 간의 통신 채널이 열린 이후 SSL/TLS handshak가 진행된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.svg&quot; data-origin-width=&quot;201&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WJovg/dJMb9XxAphs/ykgsmnUGHE7ZON8AGzgfKk/tfile.svg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WJovg/dJMb9XxAphs/ykgsmnUGHE7ZON8AGzgfKk/tfile.svg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WJovg/dJMb9XxAphs/ykgsmnUGHE7ZON8AGzgfKk/tfile.svg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWJovg%2FdJMb9XxAphs%2FykgsmnUGHE7ZON8AGzgfKk%2Ftfile.svg&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;474&quot; height=&quot;354&quot; data-filename=&quot;2.svg&quot; data-origin-width=&quot;201&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;TCP handshake를 통한 통신채널 생성.&lt;/li&gt;
&lt;li&gt;클라이언트 &amp;rarr; 서버 &lt;code&gt;ClientHello&lt;/code&gt;메시지 전송&lt;br /&gt;여기에는 클라이언트에서 사용 가능한 SSL/TLS 버전, 서버 도메인, 세션 식별자, 암호 설정 등의 정보가 포함된다.&lt;/li&gt;
&lt;li&gt;서버 &amp;rarr; 클라이언트 &lt;code&gt;ServerHello&lt;/code&gt; 메시지 전송&lt;br /&gt;여기에는 서버에서 사용하기로 선택한 SSL/TLS 버전, 세션 식별자, 암호 설정 등의 정보가 포함되어있다.&lt;/li&gt;
&lt;li&gt;서버 &amp;rarr; 클라이언트 &lt;code&gt;Certificate&lt;/code&gt; 메시지 전송&lt;br /&gt;여기에는 서버의 인증서가 들어간다. 이 인증서는 별도의 인증 기관(CA)에서 발급받은 것이며, 서버가 신뢰할 수 있는 자임을 인증한다.&lt;br /&gt;전송이 끝나면 &lt;code&gt;ServerHelloDone&lt;/code&gt; 메시지를 보내 끝났음을 알린다.&lt;/li&gt;
&lt;li&gt;클라이언트는 서버에서 받은 인증서를 검증&lt;br /&gt;인증서의 유효 기간이 만료되지 않았는지, 그 인증서가 해당 서버에게 발급된 인증서가 맞는지 등을 확인한다.&lt;/li&gt;
&lt;li&gt;클라이언트는 임의의 &lt;code&gt;pre-master secret&lt;/code&gt;을 생성한 뒤, 서버가 보낸 인증서에 포함된 공개 키를 사용해 암호화한다. 이렇게 암호화된 &lt;code&gt;pre-master secret&lt;/code&gt;을 &lt;code&gt;ClientKeyExchange&lt;/code&gt; 메시지에 포함시켜 서버에 전송한다.&lt;/li&gt;
&lt;li&gt;서버는 전송받은 정보를 복호화하여 &lt;code&gt;pre-master secret&lt;/code&gt;을 알아낸 뒤, 이 정보를 사용해 &lt;code&gt;master secret&lt;/code&gt;을 생성한다. 그 뒤 &lt;code&gt;master secret&lt;/code&gt;에서 세션 키를 생성해내며, 이 세션 키는 앞으로 서버와 클라이언트 간의 통신을 암호화하는 데 사용될 것이다. 물론 클라이언트 역시 자신이 만들어낸 &lt;code&gt;pre-master secret&lt;/code&gt;을 알고 있으므로, 같은 과정을 거쳐 세션 키를 스스로 만들 수 있다.&lt;/li&gt;
&lt;li&gt;이제 서버와 클라이언트는 각자 동일한 세션 키를 가지고 있으며, 이 키를 사용해 &lt;b&gt;대칭 키 암호&lt;/b&gt;를 사용하는 통신을 할 수 있다. 따라서 우선 서로에게 &lt;code&gt;ChangeCipherSpec&lt;/code&gt; 메시지를 보내 앞으로의 모든 통신 내용은 세션 키를 사용해 암호화해 보낼 것을 알려준 뒤, &lt;code&gt;Finished&lt;/code&gt; 메시지를 보내 각자의 핸드셰이킹 과정이 끝났음을 알린다.&lt;/li&gt;
&lt;li&gt;이제 서버와 클라이언트 간에 보안 통신이 구성된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 비대칭 키가 아닌 대칭 키 암호화를 통해 암호화하는 이유는 RSA 같은 비대칭 키 암호화는 하드웨어 자원을 많이 잡아먹고, 속도가 느리기 때문에 AES 알고리즘과 같은 대칭 키를 활용하여 암호화를 진행한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HTTPS를 적용하려면?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞에서 살펴봤듯이, HTTPS는 단순히 SSL/TLS만 붙이는 것이 아니라 &lt;b&gt;공인된 인증서(CA)&lt;/b&gt; 를 통해 신뢰할 수 있는 보안 채널을 구성해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러기 위해 SSL/TLS 인증서를 발급 받아야하는데 무료 CA들도 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;뭐니뭐니해도 편한건 AWS..&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS를 사용한다면 가장 쉬운 방법은 &lt;b&gt;ACM으로 인증서를 발급&lt;/b&gt;받고, 이를 &lt;b&gt;로드밸런서나 CloudFront에 연결&lt;/b&gt;하는 것입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ACM은 공인 CA 인증서를 &lt;b&gt;무료로 발급&lt;/b&gt;해주고 &lt;b&gt;자동 갱신&lt;/b&gt;도 지원한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증서 파일을 직접 서버에 설치할 필요가 없다..!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그치만 무료면 90일마다 갱신해줘야하는걸로..&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마무리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무튼 이렇게 HTTPS에 대해 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SSL/TLS가 전부인거같긴하지만 뭐.. 아직 덜 다뤘기때문에 TLS에 관련해서 차후 다뤄보도록 하겠다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@sangjinkang/38&quot;&gt;HTTPS를 위한 SSL/TLS 핸드 셰이크 작동원리&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://brunch.co.kr/@swimjiy/47&quot;&gt;그림으로 쉽게 보는 HTTPS, SSL, TLS&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://kanoos-stu.tistory.com/46&quot;&gt;SSL이란?, SSL과 TLS 정의 및 차이&lt;/a&gt;&lt;/p&gt;</description>
      <category>Knowledge/네트워크</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/22</guid>
      <comments>https://ryuwon-it.tistory.com/22#entry22comment</comments>
      <pubDate>Sat, 25 Oct 2025 22:09:01 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 리액티브에서의 Scheduler와 Context 그리고 Testing</title>
      <link>https://ryuwon-it.tistory.com/21</link>
      <description>&lt;h1&gt;Chapter 10. Scheduler&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10.1&amp;emsp;스레드(Thread)의 개념 이해&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;물리적인 스레드(Pysical Thread)&lt;/b&gt; : CPU 코어를 논리적으로 나눈 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;듀얼코어 4 스레드 (작업관리자 [성능]에서 확인 가능)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&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;논리적인 스레드(Logical Thread)&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;lt;aside&amp;gt;  &lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;병렬성(Parallelism) : 물리적인 스레드가 실제로 동시에 실행됨&lt;/li&gt;
&lt;li&gt;동시성(Concurrency) : 동시에 실행되는 것처럼 보이지만 실제로는 아님
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;무수히 많은 논리적인 스레드가 물리적인 스레드를 아주 빠른 속도로 번갈아 가면서 사용 &amp;lt;/aside&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10.2&amp;emsp;Scheduler란?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 스레드에서 무엇을 처리할지 제어(경쟁조건 등 고려)를 Scheduler가 개발자 대신 수행
&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;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;10.3&amp;emsp;Scheduler를 위한 전용 Operator&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;subscribeOn()
&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;원본 Publisher 동작 수행을 위한 스레드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;    public static void main(String[] args) throws InterruptedException {
        Flux.fromArray(new Integer[] {1, 3, 5, 7})
                .subscribeOn(Schedulers.boundedElastic())
                .doOnNext(data -&amp;gt; log.info(&quot;# doOnNext: {}&quot;, data))
                .doOnSubscribe(subscription -&amp;gt; log.info(&quot;# doOnSubscribe&quot;))
                .subscribe(data -&amp;gt; log.info(&quot;# onNext: {}&quot;, data));

        Thread.sleep(500L);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;    08:35:03.363 [main] INFO - # doOnSubscribe // 최초 실행 스레드 : main
    08:35:03.371 [boundedElastic-1] INFO - # doOnNext: 1 // 구독 발생 직후 스레드 : boundedElastic
    08:35:03.373 [boundedElastic-1] INFO - # onNext: 1
    08:35:03.373 [boundedElastic-1] INFO - # doOnNext: 3
    08:35:03.373 [boundedElastic-1] INFO - # onNext: 3
    08:35:03.373 [boundedElastic-1] INFO - # doOnNext: 5
    08:35:03.373 [boundedElastic-1] INFO - # onNext: 5
    08:35:03.373 [boundedElastic-1] INFO - # doOnNext: 7
    08:35:03.373 [boundedElastic-1] INFO - # onNext: 7
&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;publishOn()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Downstream으로 Signal을 전송할 때 실행되는 스레드 제어&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;    public static void main(String[] args) throws InterruptedException {
        Flux.fromArray(new Integer[] {1, 3, 5, 7})
                .doOnNext(data -&amp;gt; log.info(&quot;# doOnNext: {}&quot;, data))
                .doOnSubscribe(subscription -&amp;gt; log.info(&quot;# doOnSubscribe&quot;))
                .publishOn(Schedulers.parallel())
                .subscribe(data -&amp;gt; log.info(&quot;# onNext: {}&quot;, data));

        Thread.sleep(500L);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;    08:40:35.020 [main] INFO - # doOnSubscribe
    08:40:35.024 [main] INFO - # doOnNext: 1
    08:40:35.027 [main] INFO - # doOnNext: 3
    08:40:35.027 [parallel-1] INFO - # onNext: 1 // Downstream 실행 스레드 변경
    08:40:35.027 [main] INFO - # doOnNext: 5
    08:40:35.027 [main] INFO - # doOnNext: 7
    08:40:35.027 [parallel-1] INFO - # onNext: 3
    08:40:35.027 [parallel-1] INFO - # onNext: 5
    08:40:35.027 [parallel-1] INFO - # onNext: 7
&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;parallel()
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;병렬성을 가지는 물리적인 스레드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cf.&amp;nbsp;subscribeOn(),&amp;nbsp;publishOn()은 동시성을 가지는 논리적인 스레드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;라운드 로빈 방식으로 논리적인 코어(물리적인 스레드) 개수만큼의 스레드를 병렬로 실행&lt;/li&gt;
&lt;li&gt;Example10_3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;subscribeOn(),&amp;nbsp;publishOn()를 활용하여 Publisher에서 데이터를 emit하는 스레드와 emit된 데이터를 가공 처리하는 스레드를 적절하게 분리할 수 있음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Scheduler의 종류&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Schedulers.immediate() : 별도의 스레드를 추가적으로 생성하지 않고, 현재 스레드에서 작업 처리&lt;/li&gt;
&lt;li&gt;Schedulers.single() : 스레드 하나만 생성해서 Scheduler가 제거되기 전까지 재사용&lt;/li&gt;
&lt;li&gt;Schedulers.newSingle() : 매번 새로운 스레드 하나 생성&lt;/li&gt;
&lt;li&gt;Schedulers.boundedElastic() : ExecutorService 기반의 스레드 풀 생성한 후, 정해진 수만큼의 스레드를 사용하여 작업을 처리하고 작업 종료된 스레드는 반납하여 재사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본적으로 CPU 코어 수 x 10만큼의 스레드 생성&lt;/li&gt;
&lt;li&gt;최대 100,000개의 작업 큐에서 대기 가능&lt;/li&gt;
&lt;li&gt;Blocking I/O 작업을 효과적으로 처리하기 위한 방식&lt;/li&gt;
&lt;li&gt;다른 Blocking I/O 작업이 Non-Blocking 처리에 영향을 주지 않도록 전용 스레드 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Schedulers.parallel() : Non-Blocking I/O에 최적화되어 있는 Scheduler로서 CPU 코어 수만큼의 스레드 생성&lt;/li&gt;
&lt;li&gt;Schedulers.fromExecutorService() : 기존에 이미 사용하고 있는 ExecutorService가 있다면 이 ExecutorService로부터 Scheduler를 생성하는 방식&lt;/li&gt;
&lt;li&gt;Schedulers.newXXXX() : 스레드 이름, 생성 가능한 디폴트 스레드의 개수, 스레드의 유효 시간, 데몬 스레드로의 동작 여부 등 직접 지정해서 커스텀 스레드 풀 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Chapter 11. Context&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;11.1&amp;emsp;Context란?&lt;/h3&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;Reactor에서 Context의 정의
&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; : Downstream에서 Upstream 체인상의 각 Operator가 해당 Context 정보를 동일하게 이용할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Operator 같은 Reactor 구성요소 간에 전파되는 key/value 형태의 저장소&lt;/li&gt;
&lt;li&gt;Reactor의 Context는 Subscriber와 매핑됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cf. ThreadLocal의 경우 실행 스레드와 매핑됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉, 구독이 발생할 때마다 해당 구독과 연결된 하나의 Context가 생김&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;    public static void main(String[] args) throws InterruptedException {
        Mono
            .deferContextual(ctx -&amp;gt;
                Mono
                    .just(&quot;Hello&quot; + &quot; &quot; + ctx.get(&quot;firstName&quot;))
                    .doOnNext(data -&amp;gt; log.info(&quot;# just doOnNext : {}&quot;, data))
            )
            .subscribeOn(Schedulers.boundedElastic())
            .publishOn(Schedulers.parallel())
            .transformDeferredContextual(
                    (mono, ctx) -&amp;gt; mono.map(data -&amp;gt; data + &quot; &quot; + ctx.get(&quot;lastName&quot;))
            )
            .contextWrite(context -&amp;gt; context.put(&quot;lastName&quot;, &quot;Jobs&quot;))
            .contextWrite(context -&amp;gt; context.put(&quot;firstName&quot;, &quot;Steve&quot;))
            .subscribe(data -&amp;gt; log.info(&quot;# onNext: {}&quot;, data));

        Thread.sleep(100L);
    }
&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;contextWrite()&amp;nbsp;Operator로 Context에 데이터 쓰기 작업을 할 수 있다.&lt;/li&gt;
&lt;li&gt;Context.put()으로 Context에 데이터를 쓸 수 있다.&lt;/li&gt;
&lt;li&gt;deferContextual()&amp;nbsp;Operator로 Context에 데이터 읽기 작업을 할 수 있다.&lt;/li&gt;
&lt;li&gt;Context.get()으로 Context에서 데이터를 읽을 수 있다.&lt;/li&gt;
&lt;li&gt;transformDeferredContextual()&amp;nbsp;Operator로 Operator 중간에서 Context에 데이터 읽기 작업을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;11.2&amp;emsp;자주 사용되는 Context 관련 API&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context API (쓰기)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;put(key, value) : key/value 형태로 Context에 값을 쓴다.&lt;/li&gt;
&lt;li&gt;of(key1, value1, key2, value2, ...) : key/value 형태로 Context에 여러 개의 값을 쓴다.&lt;/li&gt;
&lt;li&gt;pulAll(ContextView) : 현재 Context와 파라미터로 입력된 ContextView를 merge한다.&lt;/li&gt;
&lt;li&gt;delete(key) : Context에서 key에 해당하는 value를 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ContextView API (읽기)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;get(key) : ContextView에서 key에 해당하는 value를 반환한다.&lt;/li&gt;
&lt;li&gt;getOrEmpty(key) : ContextView에서 key에 해당하는 value를 Optional로 래핑해서 반환한다.&lt;/li&gt;
&lt;li&gt;getOrDefault(key, default value) : ContextView에서 key에 해당하는 value를 가져온다. key에 해당하는 value가 없으면 default value를 가져온다.&lt;/li&gt;
&lt;li&gt;hasKey(key) : ContextView에서 특정 key가 존재하는지를 확인한다.&lt;/li&gt;
&lt;li&gt;isEmpty() : Context가 비어 있는지 확인한다.&lt;/li&gt;
&lt;li&gt;size() : Context 내에 있는 key/value의 개수를 반환한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;11.3&amp;emsp;Context의 특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Context는 구독이 발생할 때마다 하나의 Context가 해당 구독에 연결된다.&lt;/li&gt;
&lt;li&gt;Context는 Operator 체인의 아래에서 위로 전파된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 Operator에서 Context에 저장된 데이터를 읽어올 수 있으려면&amp;nbsp;contextWrite()을 Operator 체인의 맨 마지막에 둬야 한다.&lt;/li&gt;
&lt;li&gt;동일한 키에 대한 값을 중복해서 저장하면 Operator 체인에서 가장 위쪽에 위치한&amp;nbsp;contextWrite()이 저장한 값으로 덮어쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Inner Sequence 내부에서는 외부 Context에 저장된 데이터를 읽을 수 있다.&lt;/li&gt;
&lt;li&gt;Inner Sequence 외부에서는 Inner Sequence 내부 Context에 저장된 데이터를 읽을 수 없다.&lt;/li&gt;
&lt;li&gt;인증 정보 같은 직교성(독립성)을 가지는 정보를 전송하는 데 적합하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Chapter 12 Debugging&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;12.1&amp;emsp;Reactor에서의 디버깅 방법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기, 선언형 프로그래밍 방식의 Reactor는 디버깅이 쉽지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;12.1.1&amp;ensp;Debug Mode를 사용한 디버깅&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hooks.onOperatorDebug()를 통해 디버그 모드 활성화 (Example12_1)&lt;/li&gt;
&lt;li&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;li&gt;에러가 발생한 Assembly의 스택트레이스를 원본 스택트레이스 중간에 끼워 넣음&lt;/li&gt;
&lt;li&gt;운영 환경에서는&amp;nbsp;reactor.tools.agent.ReactorDebugAgent&amp;nbsp;이용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;IntelliJ에서는 [Enable Reactor Debug mode]에서&amp;nbsp;Hooks.onOperatorDebug()&amp;nbsp;또는&amp;nbsp;ReactorDebugAgent.init()&amp;nbsp;중 택1 하여 활성화 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;12.1.2&amp;ensp;checkpoint(&amp;thinsp;) Operator를 사용한 디버깅&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;checkpoint()&amp;nbsp;Operator를 사용하여 특정 Operator 체인 내의 스택트레이스를 캡처
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 Operator 체인에&amp;nbsp;checkpoint()를 추가한 후, 범위를 좁혀 가면서 단계적으로 에러 발생 지점을 찾는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Traceback 출력 (Example12_2)&lt;/li&gt;
&lt;li&gt;.checkpoint()&lt;/li&gt;
&lt;li&gt;Traceback 출력 없이 식별자를 포함한 Description 출력 (Example12_4)&lt;/li&gt;
&lt;li&gt;.checkpoint(&quot;Example12_4.zipWith.checkpoint&quot;)&lt;/li&gt;
&lt;li&gt;Traceback과 Description 모두 출력 (Example12_5)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;forceStackTrace=true로 지정&lt;/li&gt;
&lt;li&gt;.checkpoint(&quot;Example12_4.zipWith.checkpoint&quot;, true)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;12.1.3&amp;ensp;log( ) Operator를 사용한 디버깅&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;log()&amp;nbsp;Operator를 추가하여 Reactor Signal 출력 (Example12_7)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Chapter 13. Testing&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;StepVerifier - Operator 체인의 다양한 동작 방식 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Flux 또는 Mono를 Reactor Sequence로 정의한 후, 구독 시점에 해당 Operator 체인이 사니리오대로 동자갛는지 테스트&lt;/li&gt;
&lt;li&gt;Signal 이벤트 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;create() 통해 Sequence 생성&lt;/li&gt;
&lt;li&gt;expectXXXX()를 통해 예상되는 Signal 기대값 평가&lt;/li&gt;
&lt;li&gt;verifyXXXX()로 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TestPublisher - Signal을 발생시키며 원하는 상황을 미세하게 재연하며 테스트 진행&lt;/li&gt;
&lt;li&gt;PublisherProbe - Sequence의 실행 경로를 테스트&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Framework &amp;amp; Library/Spring</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/21</guid>
      <comments>https://ryuwon-it.tistory.com/21#entry21comment</comments>
      <pubDate>Mon, 20 Oct 2025 01:18:20 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Backpressure와 Sinks 이해하기</title>
      <link>https://ryuwon-it.tistory.com/20</link>
      <description>&lt;h1&gt;Chapter 8 Backpressure&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배압 또는 역압&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher가 끊임없이 emit하는 무수히 많은 데이터를 적절하게 제어하여 데이터 처리에 과부하가 걸리지 않도록 제어하는 것.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;322&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bakPkL/dJMb9MQpTwS/TY9BeayOoi5wIKz3upN95K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bakPkL/dJMb9MQpTwS/TY9BeayOoi5wIKz3upN95K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bakPkL/dJMb9MQpTwS/TY9BeayOoi5wIKz3upN95K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbakPkL%2FdJMb9MQpTwS%2FTY9BeayOoi5wIKz3upN95K%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;952&quot; height=&quot;322&quot; data-origin-width=&quot;952&quot; data-origin-height=&quot;322&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@zini9188/Spring-WebFlux-Project-Reactor&quot;&gt;https://velog.io/@zini9188/Spring-WebFlux-Project-Reactor&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Publisher가 빠른 속도로 emit 할 경우 Subscriber의 처리속도가 느려서 처리하기도 전에 emit 됨.&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;rArr; 이 문제를 해결하기 위한 수단이 Backpressure&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Reactor에서의 Backpressure 처리방식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 데이터 개수 제어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber가 적절히 처리할 수 있는 수준의 데이터 개수를 request()를 통해 Publisher에게 요청하는 것.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
public class Example8_1 {
    public static void main(String[] args) {
        Flux.range(1, 5)
            .doOnRequest(data -&amp;gt; log.info(&quot;# doOnRequest: {}&quot;, data))
            .subscribe(new BaseSubscriber&amp;lt;Integer&amp;gt;() {
                @Override
                // onSubscribe를 대신하여 구독 시점에 request()를 호출해 최초 데이터 요청 개수 제어
                protected void hookOnSubscribe(Subscription subscription) {
                    request(1);
                }

                @SneakyThrows
                @Override
                // onNext를 대신해 Publisher가 emit한 데이터를 전달받아 처리한 후 데이터 재요청할때
                protected void hookOnNext(Integer value) {
                    Thread.sleep(2000L);
                    log.info(&quot;# hookOnNext: {}&quot;, value);
                    request(1);
                }
            });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;c2. Backpressure 전략 사용&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;종류&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IGNORE 전략&lt;/td&gt;
&lt;td&gt;Backpressure를 적용하지 않는다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ERROR 전략&lt;/td&gt;
&lt;td&gt;Downstream으로 전달할 데이터가 버퍼에 가득 찰 경우, Exception을 발생시킨다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DROP 전략&lt;/td&gt;
&lt;td&gt;Downstream으로 전달할 데이터가 버퍼에 가득 찰 경우, 버퍼 밖에서 대기하는 먼저 emit된 데이터부터 Drop시킨다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LATEST 전략&lt;/td&gt;
&lt;td&gt;Downstream으로 전달할 데이터가 버퍼에 가득 찰 경우, 버퍼 밖에서 대기하는 가장 최근에(나중에) emit된 데이터부터 버퍼에 채운다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BUFFER 전략&lt;/td&gt;
&lt;td&gt;Downstream으로 전달할 데이터가 버퍼에 가득 찰 경우, 버퍼 안에 있는 데이터부터 Drop시킨다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- DROP_LATEST&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;- DROP_OLDEST&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;Chapter 9 - Sinks&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Processor 인터페이스의 기능을 개선한 것.&lt;/p&gt;
&lt;aside&gt; 
&lt;p data-ke-size=&quot;size16&quot;&gt;Processor란?&lt;br /&gt;Publisher와 Subscriber의 기능을 모두 지니기 있는 구성요소&lt;/p&gt;
&lt;/aside&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flux 또는 Mono가 onNext 같은 signal을 내부적으로 전송하는 방식이었는데, Sinks를 사용하면 프로그래밍 코드를 통해 명시적으로 Signal을 전송 가능&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Operator 사용방식 vs Sinks 사용방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 generate()나 create() Operator는 싱글스레드 기반에서 Signal을 전송하는데 사용하지만, Sinks는 멀티스레드 방식으로 Signal을 전송해도 스레드 안전성을 보장하기 때문에 예기치 않은 동작으로 이어지는 것을 방지&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RgXYe/dJMb9XEkXhS/pou6HmsK4s4IojG34YxfK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RgXYe/dJMb9XEkXhS/pou6HmsK4s4IojG34YxfK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RgXYe/dJMb9XEkXhS/pou6HmsK4s4IojG34YxfK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRgXYe%2FdJMb9XEkXhS%2Fpou6HmsK4s4IojG34YxfK0%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;824&quot; height=&quot;331&quot; data-origin-width=&quot;824&quot; data-origin-height=&quot;331&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 작업을 처리한 후, 그 결과 값을 반환하는 doTaks() 메서드가 싱글스레드가 아닌 여러 개의 스레드에서 각각의 &quot;전혀 다른 작업들&quot;을 처리한 후, 처리 결과를 반환하는 상황이라면? 현재는 한 개의 boundedElastic-1 스레드에서 doTask() 메서드가 실행되고 있는 것을 볼 수 있습니다. 이 같은 상황에서 적절히 사용할 수 있는 방식이 Sinks.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sinks는 프로그래밍 방식으로 Signal을 전송할 수 있으며, 멀티 스레드 환경에서 스레드 안정성을 보장받을 수 있는 장점이 있음.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Sinks 종류 및 특징&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sinks.One()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 건의 데이터를 emit&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 시 Sinks.One 객체로 데이터가 Emit 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 많은 수의 데이터를 emit 하더라도 처음 emit한 데이터만 정상적으로 emit. 나머진 다 Drop&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Sinks.Many()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 건의 데이터를 여러가지 방식으로 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 시 Sinks.Many가 아닌 ManySpec이라는 인터페이스 리턴&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Manyspec 인터페이스&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;unicast() - 단 하나의 Subscriber에게만 데이터를 emit&lt;/li&gt;
&lt;li&gt;multicast() - 하나 이상의 Subscriber에게 데이터를 emit&lt;/li&gt;
&lt;li&gt;replay() - emit된 데이터 중에서 특정 시점으로 되돌린 (replay) 데이터로부터 emit&lt;/li&gt;
&lt;/ul&gt;
&lt;aside&gt; 
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FAIL_FAST&lt;/b&gt;&lt;br /&gt;EmitFailureHandler 객체를 통해 emit 도중 에러가 발생했을 때 재시도를 하지 않고 즉시 실패 처리를 함.&lt;br /&gt;&amp;rArr; 빠르게 실패처리함으로써 교착 상태 등을 미연에 방지하여, 스레드 안전성 보장&lt;/p&gt;
&lt;/aside&gt;</description>
      <category>Framework &amp;amp; Library/Spring</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/20</guid>
      <comments>https://ryuwon-it.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 20 Oct 2025 01:17:16 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Reactive Streams 이해하기</title>
      <link>https://ryuwon-it.tistory.com/19</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해당 내용은 '스프링으로 시작하는 리액티브 프로그래밍' 책의 Chapter 2~7을 보고 학습한 내용을 정리한 글입니다.&lt;/p&gt;
&lt;h1&gt;Chapter 2. 리액티브 스트림즈&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;&amp;rArr; 데이터 스트림을 Non-Blocking이면서 비동기적인 방식으로 처리하기 위한 리액티브 라이브러리 표준&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 스레드에서 비동기적으로 상호작용하기에 Subscription.request를 통해 데이터 개수를 제어하여 통신하게 됌.&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Reactive streams&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Publisher&lt;/td&gt;
&lt;td&gt;데이터를 생성하고 통지(발행, 게시, 방출)하는 역할을 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subscriber&lt;/td&gt;
&lt;td&gt;구독한 Publlisher로부터 통지된 데이터를 받아서 처리하는 역할을 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subscrition&lt;/td&gt;
&lt;td&gt;Publisher에 요청할 데이터의 개수를 지정하고, 데이터의 구독을 취소하는 역할을 한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processor&lt;/td&gt;
&lt;td&gt;Publisher, Subscriber의 기능을 모두 가지고 있다. Subscriber로서 다른 Publisher를 구독할 수 있고, Publisher로서 다른 Subscriber가 구독할 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 Subscriber가 아닌 Publisher에 subscribe 메서드가 정의되어 있을까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;카프카는 메세지 브로커가 있고, 메세지 브로커 내의 특정 토픽으로 전송하고 Subscriber가 받기만 하는 느슨한 결합&lt;/li&gt;
&lt;li&gt;리액티브 스트림즈는 Subscriber가 구독한 것은 맞는데 Publisher가 subscribe 메서드의 파라미터인 Subscriber를 동록하는 형태로 구독이 이루어짐&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Publisher&lt;/b&gt; : subscribe()&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subscriber&lt;/b&gt; : onSubscribe(), onNext(), onError(), onComplete()&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subscription&lt;/b&gt; : request, cancel&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Processor&lt;/b&gt; : interface Processor&amp;lt;T, R&amp;gt; extends &lt;code&gt;Subscriber&amp;lt;T&amp;gt;&lt;/code&gt;, &lt;code&gt;Publisher&amp;lt;R&amp;gt;&lt;/code&gt; 빼곤 메서드 X&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현 규칙&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/reactive-streams/reactive-streams-jvm&quot;&gt;https://github.com/reactive-streams/reactive-streams-jvm&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Chapter 3. Blocking IO vs Non-Blocking IO&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;I/O 작업&lt;/b&gt; : 파일 읽고 기록하기, DB I/O, 네트워크 I/O, 예시는 네트워크 I/O&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Blocking IO&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&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;b&gt;컨텍스트 스위칭&lt;/b&gt; : 스레드가 여러 프로세스를 번갈아가며 실행할때 발생하는 비용&lt;br /&gt;프로세스 바꿔서 실행할 때 PCB(Process Control Block)(스레드는 TCB) 저장, 로드하는데 이 때 CPU가 대기한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;과다한 메모리 사용&lt;/b&gt; : 스레드가 늘어날수록 스택 영역의 메모리 사용량이 비례하게 늘어남&lt;br /&gt;JVM 디폴트 스택 사이즈는 1024KB (요청당 하나의 스레드 할당)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스레드 풀 응답 지연&lt;/b&gt; : 대량의 요청이 들어오면 스레드 풀에서 사용 가능한 유휴 스레드가 없어 응답 지연이 발생할 가능성 높아짐&lt;br /&gt;스레드 생성/수거에 비용 듬, 스레드풀 다 쓰면 응답 지연&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Non-Blocking IO&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IO 작업 안 기다리고 딴 일 한다 (&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;적은 수의 스레드만 사용해 스레드 전환 비용 낮음 &amp;rarr; 효율적인 CPU 사용&lt;/li&gt;
&lt;li&gt;CPU를 많이 사용하면 성능에 악영향, 요청에서 응답까지의 전체과정에서 Blocking IO 하나라도 있으면 이점이 없어짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spring WebFlux&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;spring framework 진영의 Non-Blocking I/O 구현체.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기 Non-Blocking I/O를 사용함으로써 적은 수의 쓰레드로 많은 수의 요청 처리&lt;/li&gt;
&lt;li&gt;Netty 같은 비동기 기반 서버 엔진 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Non-Blocking 통신이 적합한 시스템&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;학습 난이도 (특히, Reactive Streams에 대한 이해)&lt;/li&gt;
&lt;li&gt;개발인력, 유지보수 고려&lt;/li&gt;
&lt;li&gt;대용량 트래픽이 발생하는 시스템&lt;/li&gt;
&lt;li&gt;MSA 기반 시스템 (다른 서비스 호출이 많아 I/O구간 많으므로), 스트리밍 또는 실시간 시스템&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;Chapter 4. 리액티브 프로그래밍을 위한 사전 지식&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액티브 프로그래밍을 잘 사용하기 위해서는 기본적으로 함수형 프로그래밍 기법이 받쳐줘야 함.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;인터페이스에 단 하나의 추상 메서드만 정의되어 있음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;함수형 인터페이스를 구현한 클래스의 인스턴스를 람다 표현식으로 작성해서 전달함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) Comparator&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;메서드 레퍼런스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 간결하게 작성가능&lt;/p&gt;
&lt;pre class=&quot;autohotkey&quot;&gt;&lt;code&gt;(Car car) &amp;rarr; car.getCarName() = Car::getCarName&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;일반화된 람다 표현식을 통해서 이 함수형 인터페이스가 어떤 파라미터를 가지고, 어떤 값을 리턴하는지 설명해주는 역할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;람다 표현식의 파라미터 개수와 타입, 리턴 타입을 보고 어떤 함수형 인터페이스인지 알고 싶을 때 확인할때 유용함&lt;/p&gt;
&lt;h1&gt;Chapter 5. Reactor 개요&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactor는 리액티브 스트림즈 사양을 구현한 여러 구현체 중 하나.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring WebFlux의 핵심 역할&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;Reactive Streams&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Non-Blocking&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Java&amp;rsquo;s functional API&lt;/b&gt; - 함수형 프로그래밍&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Flux[N]&lt;/b&gt; - 0개부터 N개의 데이터를 Emit 가능한 Publisher&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Mono[0|1]&lt;/b&gt; - 데이터를 0개 or 1개만 Emit 가능한 Publisher&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Well-suited for microservices&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Backpressure-ready network&lt;/b&gt; - Publisher로부터 전달받은 데이터를 처리하는데 과부화 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Chapter 6. 마블 다이어그램&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reactor에서 지원하는 Operator를 이해하는데 중요한 역할.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구슬 도표&lt;/p&gt;
&lt;h1&gt;Chapter 7. Cold Sequence와 Hot Sequence&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Cold는 무언가를 새로 시작하고, Hot은 무언가를 새로 시작하지 않는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) HotSwap - 전원을 끄지않고 교체 가능&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Cold Sequence&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Subscriber가 구독할 때마다 데이터 흐름이 처음부터 다시 시작되는 Sequence&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독 시점이 달라도 구독할 때마다 Publisher가 데이터를 emit하는 과정을 처음부터 다시 시작하는 데이터 흐름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 구독할 때마다 Sequence 타임라인이 생김&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Hot Sequence&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구독이 발생한 시점 이전에 Publisher로부터 emit 된 데이터는 Subscriber가 전달받지 못하고 구독이 발생한 시점 이후에 emit된 데이터만 전달 받을 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 구독이 3번 이뤄질 경우 Cold Sequence는 3개의 타임라인이 생기겠지만, Hot Sequence는 한개의 타임라인만 가지고 있다는 것.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;share(), cache() 등의 Operator를 사용해서 Cold Sequence를 Hot Sequence로 변환할 수 있음&lt;/li&gt;
&lt;li&gt;Hot Sequence는 Suscriber의 최초 구독이 발생해야 Publisher가 데이터를 emit하는 Warm up과 Subsciber의 구독 여부와 상관없이 데이터를 emit하는 Hot으로 구분할 수 있음&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/19</guid>
      <comments>https://ryuwon-it.tistory.com/19#entry19comment</comments>
      <pubDate>Fri, 17 Oct 2025 11:07:30 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 리액티브 시스템? 리액티브 프로그래밍?</title>
      <link>https://ryuwon-it.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;해당 내용은 '스프링으로 시작하는 리액티브 프로그래밍' 책의 Chapter 1을 보고 학습한 내용을 정리한 글입니다.&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;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;우리가 비즈니스 로직을 작성하고, 서버를 운영하게 되었을때 기존 Spring MVC 모델을 사용한다고 가정하면 스레드 모델 자체가 요청당 하나의 스레드가 작업이 이뤄지게 되는데&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;블로킹(Blocking)과 논블로킹(Non-blocking)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;블로킹(Blocking)&lt;/b&gt;: 어떤 작업이 끝날 때까지 &lt;b&gt;다른 작업을 못하고 기다리는 상태&lt;/b&gt;. 예를 들어, 파일을 읽는 도중 CPU가 대기하면 다른 요청을 처리할 수 없음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;논블로킹(Non-blocking)&lt;/b&gt;: 작업이 완료되길 기다리지 않고 &lt;b&gt;다른 작업을 먼저 처리하는 방식&lt;/b&gt;. 결과가 준비되면 콜백이나 이벤트로 통보받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;쓰레드의 생성 비용은 비싸다 &amp;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;&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;이게 Spring WebFlux.&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;⚙️ Spring MVC와 Spring WebFlux의 차이점&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;Spring MVC&lt;/th&gt;
&lt;th&gt;Spring WebFlux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;처리 방식&lt;/td&gt;
&lt;td&gt;동기(블로킹)&lt;/td&gt;
&lt;td&gt;비동기(논블로킹)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스레드 모델&lt;/td&gt;
&lt;td&gt;요청당 하나의 스레드&lt;/td&gt;
&lt;td&gt;이벤트 루프 기반, 적은 수의 스레드로 다수 처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;기반 기술&lt;/td&gt;
&lt;td&gt;서블릿 API&lt;/td&gt;
&lt;td&gt;Netty, Undertow 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;적합한 상황&lt;/td&gt;
&lt;td&gt;일반적인 웹 애플리케이션&lt;/td&gt;
&lt;td&gt;높은 동시성, 실시간 데이터 처리 등&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&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;/h2&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;p data-ke-size=&quot;size16&quot;&gt;&amp;rArr; 클라이언트의 요청에 즉각적으로 응답함으로써 지연 시간을 최소화&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;331&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oQoiB/dJMb9PGm5sQ/KeKjbeKRphSI9IiMKIAchk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oQoiB/dJMb9PGm5sQ/KeKjbeKRphSI9IiMKIAchk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oQoiB/dJMb9PGm5sQ/KeKjbeKRphSI9IiMKIAchk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoQoiB%2FdJMb9PGm5sQ%2FKeKjbeKRphSI9IiMKIAchk%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;481&quot; height=&quot;331&quot; data-origin-width=&quot;481&quot; data-origin-height=&quot;331&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;code&gt;MEANS&lt;/code&gt; - 리액티브 시스템에서 주요 통신 수단은?&lt;/li&gt;
&lt;li&gt;&lt;i&gt;비동기 메시지 기반*&lt;/i&gt;의 통신&lt;/li&gt;
&lt;li&gt;&lt;code&gt;FORM&lt;/code&gt; - 메시지 기반 통신을 통해 어떤 형태를 지니는 시스템인지?&lt;br /&gt;작업량이 변하더라도 일정한 응답을 지닌 &lt;b&gt;탄력성&lt;/b&gt;,&lt;br /&gt;시스템 장애가 발생하더라도 응답을 지닌 &lt;b&gt;회복성&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;VALUE&lt;/code&gt; - 핵심가치는?&lt;/li&gt;
&lt;li&gt;&lt;i&gt;빠른 응답성을 바탕으로 유지보수와 확장이 용이한 시스템*&lt;/i&gt;&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&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;h3 data-ke-size=&quot;size23&quot;&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;aside&gt;&lt;img src=&quot;https://www.notion.so/icons/fire_gray.svg&quot; alt=&quot;https://www.notion.so/icons/fire_gray.svg&quot; width=&quot;40px&quot; /&gt;&lt;span style=&quot;color: #000000; font-size: 1.44em; letter-spacing: -1px;&quot;&gt;명령형 프로그래밍과 선언형 프로그래밍&lt;/span&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;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;여기 차가운 물 한잔 주세요&amp;rdquo;라고 종업원에게 부탁하는 것은 선언형.&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;405&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/o9bPt/dJMb9MCScyb/68xNrxNOOuHqdQLQqT1ge0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/o9bPt/dJMb9MCScyb/68xNrxNOOuHqdQLQqT1ge0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/o9bPt/dJMb9MCScyb/68xNrxNOOuHqdQLQqT1ge0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fo9bPt%2FdJMb9MCScyb%2F68xNrxNOOuHqdQLQqT1ge0%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;495&quot; height=&quot;405&quot; data-origin-width=&quot;495&quot; data-origin-height=&quot;405&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;382&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lfRV3/dJMb9LDXVNZ/JRDxfsQOQeRNpyVKckLpE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lfRV3/dJMb9LDXVNZ/JRDxfsQOQeRNpyVKckLpE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lfRV3/dJMb9LDXVNZ/JRDxfsQOQeRNpyVKckLpE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlfRV3%2FdJMb9LDXVNZ%2FJRDxfsQOQeRNpyVKckLpE0%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;494&quot; height=&quot;382&quot; data-origin-width=&quot;494&quot; data-origin-height=&quot;382&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드에서 선언형 코드를 보면 여러 가지 코드를 초기화, 할당, 조건, 반복 등으로 나누지 않고 &lt;code&gt;stream()&lt;/code&gt;, &lt;code&gt;filter()&lt;/code&gt;, &lt;code&gt;mapToInt()&lt;/code&gt;, &lt;code&gt;sum()&lt;/code&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;br /&gt;filter 메서드의 파라미터 부분이 함수형 프로그래밍 방식이라고도 볼 수 있음.&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;/aside&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;Publisher(생산자)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;입력으로 들어오는 데이터를 Subscriber에 제공하는 역할을 함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Subscriber(소비자)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Publisher로부터 전달받은 데이터를 사용하는 역할을 함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Data Source&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Publisher의 입력으로 전달되는 데이터를 의미함.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Operator&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;Publisher와 Subscriber 중간에서 데이터를 가공하는 역할을 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리액티브 프로그래밍은 &amp;ldquo;Operator로 시작하여 Operator로 끝난다&amp;rdquo; 는 말이 있을 정도로&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 필터링, 변환 등 수많은 Operator가 존재.&lt;/p&gt;</description>
      <category>Framework &amp;amp; Library/Spring</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/18</guid>
      <comments>https://ryuwon-it.tistory.com/18#entry18comment</comments>
      <pubDate>Fri, 17 Oct 2025 10:49:29 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 리액티브 프로그래밍을 들어가며</title>
      <link>https://ryuwon-it.tistory.com/17</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 HTTP, HTTPS 프로토콜을 통해 클라이언트와 서버 간 통신을 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 흔히 많이 사용하는 웹, 앱 등에서는 대부분 HTTP, HTTPS 프로토콜 기반으로 데이터를 전달하게 된다. 서버들이 어떤 방식을 통해 데이터를 전달해주는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버는 대표적으로 웹 서버와 웹 어플리케이션 서버가 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Web Server&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흔히 우리가 많이 보는 웹 화면을 표시해주는 서버이며, 정적 리소스를 제공해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 정적 리소스란 이미지, HTML, CSS, JavaScript 등 과 같은 정적인 파일을 제공하는 역할을 해주고, 흔히 우리가 쓰는 크롬 등 웹 브라우저에서는 해당 파일을 랜더링하여 우리가 볼 수 있는 화면으로 만들어 주는 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 서버에는 &lt;code&gt;Nginx&lt;/code&gt;, &lt;code&gt;Apache&lt;/code&gt; 등이 존재한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Web Application Server&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 어플리케이션 서버는 웹 서버의 역할도 진행할 수 있지만, 들어온 요청에 맞게 동적으로 만들어진 컨텐츠를 제공해 준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 동적은 서버 내 알고리즘, 비즈니스 로직, DB 조회 등 요청에 따라 컨텐츠를 제공하는 것에 있어 동적을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 어플리케이션은 축약하여 WAS라 불리며, &lt;code&gt;Tomcat&lt;/code&gt;, &lt;code&gt;Jetty&lt;/code&gt;, &lt;code&gt;JBoss&lt;/code&gt; 등이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;웹 어플리케이션 아키텍처
([https://medium.com/@chrisjune_13837/web-웹서버-앱서버-was-app이란-692909a0d363](https://medium.com/@chrisjune_13837/web-%EC%9B%B9%EC%84%9C%EB%B2%84-%EC%95%B1%EC%84%9C%EB%B2%84-was-app%EC%9D%B4%EB%9E%80-692909a0d363))&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹 어플리케이션 아키텍처&lt;br /&gt;(&lt;a href=&quot;https://medium.com/@chrisjune_13837/web-%EC%9B%B9%EC%84%9C%EB%B2%84-%EC%95%B1%EC%84%9C%EB%B2%84-was-app%EC%9D%B4%EB%9E%80-692909a0d363&quot;&gt;https://medium.com/@chrisjune_13837/web-웹서버-앱서버-was-app이란-692909a0d363&lt;/a&gt;)&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 한 가지 생각이 들 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 WAS가 웹 서버 역할을 할 수도 있다고 하였는데, 하나의 서버가 여러가지의 역할을 가질 순 없는걸까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그럼, WAS 하나로 모든 역할을 대신하면..?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WAS는 웹 서버의 기능도 수행할 수 있기에, 단일 서버로 구성해도 기술적으로 문제는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 여러 이유가 존재하는데.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 기능 분리로 인한 서버 부하 방지&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WAS는 DB 조회나 비즈니스 로직 처리 등 무거운 작업을 수행하기 때문에, &lt;b&gt;단순 정적 컨텐츠 요청은 Web Server가 처리하는 것이 훨씬 효율적&lt;/b&gt;이다.&lt;/li&gt;
&lt;li&gt;모든 요청을 WAS가 처리하게 되면 서버 부하가 커지고, &lt;b&gt;동적 컨텐츠 처리 속도 저하&lt;/b&gt;로 이어질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 보안 강화를 위한 물리적 분리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SSL 암복호화와 같은 보안 관련 처리를 Web Server에서 수행함으로써, WAS를 보안 위협으로부터 보호할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 로드 밸런싱을 통한 안정성 확보&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 대의 WAS를 Web Server를 통해 연결함으로써 &lt;b&gt;부하 분산(load balancing)&lt;/b&gt; 및 &lt;b&gt;장애 대응(fail over, fail back)&lt;/b&gt; 이 가능하다.&lt;/li&gt;
&lt;li&gt;특정 WAS에 장애가 발생하면, Web Server는 자동으로 다른 WAS로 요청을 분산시켜 &lt;b&gt;무중단 서비스를 실현&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 다양한 언어/플랫폼의 웹 애플리케이션 서비스 가능&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Web Server를 통해 &lt;b&gt;다른 언어들과 애플리케이션을 동시에 운영&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;li&gt;예를 들어, 정적 리소스를 제공하는 Web Server를 두고, &lt;b&gt;Node.js 기반의 API 서버&lt;/b&gt;는 RESTful API를 제공하며, &lt;b&gt;Flutter로 개발된 모바일 앱&lt;/b&gt;에서는 해당 API를 호출하여 동적으로 데이터를 주고받는 구조가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 지금처럼 WAS에 정적 리소스와 어플리케이션을 전부 담아서 기능을 하게되면 WAS가 너무 많은 역할을 담당하기 때문에 서버 과부하가 걸리고 만약 서버 오류가 났을 때 정적 리소스마저 전송을 못하기때문에 오류페이지도 띄우지 못하는 경우가 발생한다. 그리고 어플리케이션 로직은 가장 중요하고 가장 비싼 자원이기 때문에 정적 리소스가 이를 방해해서도 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 역할에 따라 기능을 분할하는 것이 가장 이상적인 웹 시스템이므로 다음과 같이 웹 서버가 정적 리소스를 제공하고 WAS가 어플리케이션 로직을 담당하는 이상적인 웹 시스템을 구성할 수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역할별로 분할했을 때 어플리케이션 로직을 방해하지도 않고, 어플리케이션 로직이 오류가 났더라고 하더라도 정적 리소스는 웹 서버를 통해 전송되기 때문에 오류페이지도 띄울 수 있다.&lt;br /&gt;그리고 중요한 것이 효율적으로 서버를 증설할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image%201.png&quot; alt=&quot;image.png&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;둘의 역할을 분할해두면 정적 리소스가 많이 사용되면 웹서버만, 어플리케이션 리소스가 많이 사용되면 WAS만 따로 증설을 하여서 좀 더 효율적인 서버 증설이 가능해지기에 &lt;b&gt;비용을 아낄 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;서블릿(Servlet)&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 WAS는 HTTP와 HTTPS 를 기반으로 동작한다고 했었다. 그럼 구체적으로 WAS가 어떤 식으로 돌아가는지 클라이언트가 Post를 전송해서 저장해야한다고 가정하고 직접 WAS 내부 동작을 보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsHJPe/btq128CZMiR/iCkgUGX7q2cQbFgA53kEG1/img.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Http 요청 하나를 위해서 WAS가 동작해야할 것들이 너무 많다. 비즈니스 로직이 의미가 있고 중요한데 나머지까지 짜기에는 시간이 오래 걸려보인다. 그래서 개발자가 비즈니스 로직만 짤 수 있도록 도움을 주는 기술이 Servlet이다.&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;흔히 알고있는 MVC 패턴에서 Controller에 해당한다.&lt;/li&gt;
&lt;li&gt;HttpServletRequest 객체를 이용하여 Http 요청 정보를 쉽게 알 수 있다.&lt;/li&gt;
&lt;li&gt;HttpServletResponse 객체를 이용하여 Http 응답 정보를 쉽게 보낼 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 비즈니스 로직을 제외한 일련이 과정들을 Servlet을 통해서 도움을 받아 개발자는 비즈니스 로직에만 집중할 수 있게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동작 구조를 알아보면,&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvYx7b/btq102wK4vE/EEHjvSiz0CV5kLp8RvqABK/img.png&quot; alt=&quot;&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;Http Request가 WAS로 들어온다.&lt;/li&gt;
&lt;li&gt;WAS가 Http Request를 기반으로 Request와 Response 객체를 생성하여 서블릿 객체(helloServlet)를 호출한다.&lt;/li&gt;
&lt;li&gt;이때 개발자는 Request 객체에서 Http Request의 요약된 요청 정보를 꺼내어 사용한다.&lt;/li&gt;
&lt;li&gt;그 후 결과물을 Response객체에 담는다.&lt;/li&gt;
&lt;li&gt;WAS는 Response 객체에 담긴 내용으로 클라이언트를 통해 Http Response를 전송한다.&lt;/li&gt;
&lt;li&gt;응답이 끝나면 Request와 Response 객체를 소멸시킨다.&lt;/li&gt;
&lt;/ol&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;지금까지 서블릿을 사용해서 Http 응답 정보를 처리하는 WAS들을 전부 서블릿 컨테이너라고 한다. 흔히 사용하는 Tomcat이 서블릿 컨테이너인 셈이다. 이런 서블릿 컨테이너들이 앞서 동작구조에서 보았다시피 서플릿 객체를 생성하고, 초기화, 호출, 종료하는 등 서블릿의 생명주기를 관리하는데 이때 중요한 사실이 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿 객체는 &lt;code&gt;싱글톤&lt;/code&gt;으로 관리한다.&lt;/p&gt;
&lt;/blockquote&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;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;/p&gt;
&lt;h1&gt;&lt;b&gt;동시 요청 - 멀티 쓰레드&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 쓰레드를 알기 앞서 쓰레드에 대해서 알아보자면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&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;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 메인 메소드를 실행하면 main이라는 이름의 쓰레드가 실행이 되고 이 쓰레드는 한번에 하나의 코드 라인만 수행할 수 있다. 하지만 단일쓰레드, 즉 쓰레드 하나로 시스템을 구성하면 다음과 같은 문제가 발생한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckJgl9/btq16zzPBnB/qNwif7NbIdleQtXDzIhKp1/img.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 요청1이 먼저와서 쓰레드가 sevlet 객체를 호출받아 처리 중인데 처리지연이 생겼다고 하자. 근데 알다시피 많은 서비스들은 동시접속자가 많아 지연되는 동안 요청2가 생기기마련이다. 따라서 요청2는 아무것도해보지도 못하고 대기를 하게 되는 상황이 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 우리는 요청마다 쓰레드를 새로 생성하여 다른 요청이 들어와도 그때그때 쓰레드를 부여하는 방식의 해결방법을 생각해낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;말한 것처럼 동시 요청이 들어와도 처리할 수 있고, CPU, 메모리가 허용되는 한에서는 계속해서 생성하여 처리할 수 있다. 하지만 단점이 있기마련이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쓰레드의 생성 비용은 비싸다 &amp;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;여기서 컨텍스트 스위칭 비용이란 CPU에서의 코어만큼 쓰레드가 돌아가는데 코어 한개를 두고 보았을 때 쓰레드가 두 개면 코어 하나가 둘을 순차적으로 수행을하고 이때 다음 쓰레드로 옮길때의 비용을 말한다.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;쓰레드 풀&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 우리는 쓰레드를 미리 만들어서 모아둔 쓰레드 풀을 이용한다. 이전에는 쓰레드를 요청때마다 생성하고, 사용한 후 쓰레드를 죽이는 방식을 사용했기 때문에 위와 같은 문제점들이 발생했었다. 따라서 쓰레드 풀이라는 곳에 쓰레드를 모아놓고 쓰레드 풀에서 쓰레드를 받아 사용하고 반환하는 방식으로 사용하게 된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EAHhm/btq16iSErfG/8CSNk09Bej4JviPLk9IbPk/img.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 쓰레드 풀을 사용하였을 때 장점은 쓰레드 생성에 제한을 걸어두었다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;200개의 쓰레드를 모두 사용한 시점이라면 새로 들어오는 요청일 지라도 대기를 시키거나, 연결자체를 끊어 연속되는 요청에 대해서 보완할 수 있는 것이다. 따라서 미리 쓰레드가 서버 실행과 함께 생성이 되기 때문에 새로 생성하거나 종료하는 비용이 절약되어 응답 시간이 빠르고, 기존 요청에 대해서 안전하게 처리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 주로 사용하는 Tomcat은 쓰레드 풀의 크기를 최대 200개로 default해두었고 물론 변경이 가능하다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;server.tomcat.max-threads = 0# number of threads in protocol handler&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 WAS에서의 주요 튜닝 포인트는 이 기본 default로 되어있는 최대 쓰레드 개수를 어떻게 조정하는 가이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드 수를 낮게 설정하면 동시 요청이 많을 때 서버 리소스는 여유롭지만 응답 지연이 발생하고, 너무 높으면 응답은 빠르지만 CPU, 메모리 초과로 서버가 다운될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐든 &quot;적절하게&quot; 어플리케이션 로직의 복잡도, CPU, 메모리, IO 리소스 상황에 맞추어서 실제 서비스와 유사하게 성능테스트를 진행하여 쓰레드 개수를 조정해야한다.(툴 : 아파치 ab, 제이미터, nGinder)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;WAS에서의 멀티 쓰레드 지원의 핵심은 결국 WAS가 알아서 멀티 쓰레드를 처리해주고, 개발자는 멀티 쓰레드 관련된 코드에 신경쓰지 않아도 된다는 것이다. 즉, 하나의 요청에 대한 쓰레드(싱글 쓰레드) 프로그래밍하듯이 편하게 소스 코드를 개발하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 싱글톤 객체(서블릿, 빈)은 주의해서 사용하자.&lt;/p&gt;
&lt;h1&gt;&lt;b&gt;HTML, HTTP API&lt;/b&gt;&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 정적 리소스로서 고정된 HTML 파일, CSS, Js, 이미지, 영상 등을 웹 브라우저를 통해 제공한다. 이때 WAS가 끼면?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PEkc8/btq14ZMzFsE/cPoAkAnZtqu4eh120j23aK/img.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 베이스를 사용하는 등 어플리케이션 로직으로 얻을 수 없이 정적인 화면만 제공하던 웹 브라우저에 WAS가 어플리케이션 로직을 통해 동적인 HTML 결과물을 보여줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 게임과 같은 사이트에서 많이 볼 수 있듯이 JSON의 형태로 API만을 제공해줄 수 있기도 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boelck/btq16AesPPq/ZRvlc7KgqADKQJcVBgL000/img.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 HTTP API를 이용하면 활용폭이 넓어진다. 가령 스마트폰 앱을 생각해보면 웹페이지를 굳이 만들지 않아도 스마트폰의 앱에서 데이터를 받아서 화면에 띄워주는 등과 같이 데이터만 주고받는 역할로 WAS가 사용될 수도 있고 WAS 서버끼리도 서로서로 통신시킬 수 있다. (결제 서버와 주문 서버를 따로 두는 것처럼) JSON이라는 통일된 데이터 포멧을 통해 서버와 클라이언트, 서버와 서버와의 통신을 진행할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://binux.tistory.com/32&quot;&gt;웹서버(Web Server) 와 웹 어플리케이션 서버 (WAS)&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://jinyoungchoi95.tistory.com/17&quot;&gt;[스프링 MVC 1편] 웹 애플리케이션의 이해&lt;/a&gt;&lt;/p&gt;</description>
      <category>Framework &amp;amp; Library/Spring</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/17</guid>
      <comments>https://ryuwon-it.tistory.com/17#entry17comment</comments>
      <pubDate>Sun, 12 Oct 2025 23:46:49 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Haversine 공식을 이용한 원하는 범위의 마커 출력하기</title>
      <link>https://ryuwon-it.tistory.com/16</link>
      <description>&lt;p&gt;대학생 주거 리뷰 플랫폼, 찐빵 서비스 운영 중 문제가 발생하였다.&lt;br&gt;원룸 리뷰를 작성할 때 건물 정보를 함께 입력하게 되는데, 원룸의 위치만으로 캠퍼스를 명확히 특정할 수 없는 경우가 많았다.&lt;/p&gt;
&lt;p&gt;예를 들어&lt;/p&gt;
&lt;ul&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;초기 설계 단계에서 검색 필터에서 캠퍼스를 선택할 경우 캠퍼스 ID를 이용하여 검색하는 로직으로 구현했기에, 이런 애매한 상황은 마커 조회 및 검색 로직에서 걸림돌이 되었다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;strong&gt;캠퍼스 위치(위도·경도)를 기준으로 반경 1~2km 내의 모든 마커를 불러오는 방식&lt;/strong&gt;으로 로직을 전환하기로 했다.&lt;/p&gt;
&lt;p&gt;그래서 현재 위치(위도, 경도) 기반으로 반경에 속하는 마커들을 어떻게 하면 불러올 수 있을까 해서 발견한 것이 &lt;strong&gt;Haversine 공식&lt;/strong&gt;이다.&lt;/p&gt;
&lt;h3&gt;Haversine 공식이란?&lt;/h3&gt;
&lt;p&gt;지구와 같은 구체의 두 지점 사이의 거리를 계산하기 위한 수학 공식이다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Illustration_of_great-circle_distance.svg/250px-Illustration_of_great-circle_distance.svg.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;attachment:d6668216-7ac1-40df-bdf6-4771e84f537f:image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;잘 와닿지 않겠지만 대표적인 예를 보자.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;attachment:2d0bad7f-1474-444a-925a-83fb6ce18e3f:image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;위 두 사진을 보았을 때 신기하게 밑의 경로가 곡선이지만 더 짧은 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;왜냐하면 밑의 곡선 경로가 돌아가는 것처럼 보이지만, 사실 3차원의 공간에서의 거리를 2차원 평면에서 거리로 투영했기 때문에 곡선으로 보이지만 &lt;strong&gt;실제 지구 곡면 위에선 직선&lt;/strong&gt;에 해당하는 경로이다.&lt;/p&gt;
&lt;p&gt;그래서 Haversine 공식을 사용하면 곡면상의 최단 거리를 수학적으로 계산을 하기에 GPS, 네비게이션, 지도 서비스 등 여러 거리 계산에 활용된다.&lt;/p&gt;
&lt;h3&gt;계산 공식 및 코드&lt;/h3&gt;
&lt;p&gt;계산 공식은 다음과 같다&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;attachment:ad3218ed-f2fd-47ef-9651-1c54c130eb3f:image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;중심각이 위와 같을때 (d는 두 지점 사이의 거리, r은 지구의 반지름), $θ$의 하버사인은 다음과 같이 계산 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;attachment:3123d9b5-4242-47b6-866f-91c9bffc7f3f:image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;attachment:e7d5aa37-188a-4775-bafd-e6dccf6d5016:image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;)&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;attachment:cc7280e7-4685-488f-b0e1-4084f7f30c1e:image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;위를 토대로 실제 거리 d는 다음과 같이 계산된다.&lt;/p&gt;
&lt;p&gt;$$&lt;br&gt;d=2R⋅arcsin(hav(θ))&lt;br&gt;$$&lt;/p&gt;
&lt;p&gt;즉, &lt;strong&gt;두 점의 위도·경도를 이용해 중심각을 구한 뒤&lt;/strong&gt;, 이를 지구 반지름에 곱하면 실제 거리를 구할 수 있다.&lt;/p&gt;
&lt;p&gt;위도·경도 값은 반드시 &lt;strong&gt;라디안(radian)&lt;/strong&gt; 단위로 변환 후 계산해야 한다.&lt;/p&gt;
&lt;p&gt;보통 DB나 API로 받는 좌표는 &lt;code&gt;도(degree)&lt;/code&gt; 단위이므로 변환 과정이 필수다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/icons/fire_gray.svg&quot; alt=&quot;/icons/fire_gray.svg&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주의사항&lt;/strong&gt;&lt;br&gt;위도·경도 값은 반드시 &lt;strong&gt;라디안(radian)&lt;/strong&gt; 단위로 변환 후 계산해야 한다.&lt;/p&gt;
&lt;p&gt;보통 DB나 API로 받는 좌표는 &lt;code&gt;도(degree)&lt;/code&gt; 단위이므로 변환 과정이 필수다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;private static final double EARTH_RADIUS_METERS = 6371000; // 지구 반지름 (m 단위)

private double calculateDistanceMeters(double lat1, double lng1, double lat2, double lng2) {
                // 1. 위도·경도 차이를 라디안으로 변환
        double dLat = Math.toRadians(lat2 - lat1);
        double dLng = Math.toRadians(lng2 - lng1);

                // 2. Haversine 공식 적용
        double a = Math.sin(dLat / 2) * Math.sin(dLat / 2)
                + Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2))
                * Math.sin(dLng / 2) * Math.sin(dLng / 2);

        // 3. 중심각(θ) 계산
        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

                // 4. 거리 반환 (단위: m)
        return EARTH_RADIUS_METERS * c;
}&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;위도, 경도 차이를 &lt;code&gt;Math.toRadians()&lt;/code&gt;를 이용하여 라디안으로 변환&lt;/li&gt;
&lt;li&gt;두 점 사이의 구면 삼각형에 Haversine 함수 적용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Math.atan2()&lt;/code&gt;로 중심각 구하기&lt;/li&gt;
&lt;li&gt;지구 반지름에 곱해 실제 거리를 계산&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;마무리&lt;/h3&gt;
&lt;p&gt;Haversine 공식에서는 지구가 완전한 구형이라고 가정을 했지만 실제 지구는 적도 쪽이 좀 더 길쭉한 타원형이기 때문에 아주 정확하다곤 할 수 없다. 그래서 매우 정확한 거리를 알아야한다면 타원체인 것을 고려한 Vincenty 공식을 사용하는게 좋다. 하지만, 계산량이 좀 더 많아지고 살짝 더 복잡하다..&lt;/p&gt;
&lt;p&gt;우리 서비스는 단순히 1~2km 반경의 마커들을 조회만 하면 되기에 계산량이 적고 좀 더 빠른 Haversine 공식을 채택하였지만 각 서비스에 맞춰서 원하는 공식을 적용하면 좋을 거 같다..!&lt;/p&gt;
&lt;h3&gt;참조&lt;/h3&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Haversine_formula&quot;&gt;Haversine formula&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://railly-linker.tistory.com/65&quot;&gt;[지리 정보] 지리적 좌표계, 위도/경도의 이해 및 Haversine 공식을 이용한 지도 좌표 거리 계산 Kotlin 함수 작성&lt;/a&gt;&lt;/p&gt;</description>
      <category>Framework &amp;amp; Library/Spring Boot</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/16</guid>
      <comments>https://ryuwon-it.tistory.com/16#entry16comment</comments>
      <pubDate>Sun, 12 Oct 2025 21:06:24 +0900</pubDate>
    </item>
    <item>
      <title>우당탕탕 서버 운영기 #1 - 로그 시스템..? (ELK와 PLG 스택)</title>
      <link>https://ryuwon-it.tistory.com/15</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;로그 시스템..?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쌓인 로그만 2gb에 달했지만 이 로그를 파싱하기에도, 관리하기에도 정말 힘들었다 .&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 grep으로 열심히 검색해서 뒤져봤어야 했고,&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;/p&gt;
&lt;p&gt;항목 효과 / 이득&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;중앙 집중화&lt;/td&gt;
&lt;td&gt;로그가 흩어지지 않고 한 곳에 모인다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시간&amp;middot;사용자 단위 검색&lt;/td&gt;
&lt;td&gt;특정 트랜잭션, 세션, 유저 ID 기준 탐색 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시각화 &amp;amp; 분석&lt;/td&gt;
&lt;td&gt;대시보드, 패턴 인지, 이상 탐지 등 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;장기 보관 &amp;amp; 보안&lt;/td&gt;
&lt;td&gt;보존 정책, 접근 제어, 감사 로그 활용 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;자동화 &amp;amp; 알림&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;ELK Stack 구성 요소&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;Elasticsearch&lt;/b&gt;&amp;ndash; 로그 데이터(JSON 문서)를 색인(Index)하고 검색 쿼리 처리&lt;/li&gt;
&lt;li&gt;&amp;ndash; 복제, 샤드, 인덱스 최적화 등 클러스터 운영 복잡도 존재&lt;/li&gt;
&lt;li&gt;&amp;ndash; Apache Lucene 기반의 분산 검색 엔진&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Logstash&lt;/b&gt;&amp;ndash; 플러그인 기반 확장 가능 (grok, mutate, geoip 등)&lt;/li&gt;
&lt;li&gt;&amp;ndash; 다양한 소스로부터 로그 수집 &amp;rarr; 파싱 &amp;rarr; 필터링 &amp;rarr; 출력&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Kibana&lt;/b&gt;&amp;ndash; 대시보드, 시계열 분석, 필터링, 지도 등 UI 기능&lt;/li&gt;
&lt;li&gt;&amp;ndash; Elasticsearch의 데이터를 시각화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Filebeat&lt;/b&gt; (종종 조합)&lt;/li&gt;
&lt;li&gt;&amp;ndash; 경량 로그 전송 에이전트 (Logstash 없이 Elasticsearch ingest pipeline 사용 가능)&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;애플리케이션 &amp;rarr; 로그 파일 (예: stdout, app.log)&lt;/li&gt;
&lt;li&gt;Filebeat / Logstash가 읽어서 전처리 (파싱, 필터링)&lt;/li&gt;
&lt;li&gt;Logstash &amp;rarr; Elasticsearch (혹은 Filebeat &amp;rarr; ingest pipeline)&lt;/li&gt;
&lt;li&gt;Elasticsearch가 문서를 색인&lt;/li&gt;
&lt;li&gt;Kibana로 쿼리 &amp;rarr; 시각화&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿼리 복잡성, 필터링, 대량 로그 처리 등에서 강점을 가짐&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;Elasticsearch는 문서 내 모든 필드와 텍스트를 색인하므로&lt;/li&gt;
&lt;li&gt;사용자 경험이 친숙하다&lt;/li&gt;
&lt;li&gt;Kibana의 UI는 강력하고 사용하기 직관적&lt;/li&gt;
&lt;li&gt;생태계가 풍부하다&lt;/li&gt;
&lt;li&gt;많은 플러그인, 알림, ML, 보안 확장 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;Elasticsearch 클러스터는 메모리, CPU, 디스크 I/O를 많이 요구&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;&amp;ldquo;&lt;a href=&quot;https://www.diva-portal.org/smash/record.jsf?pid=diva2%3A1771279&amp;amp;utm_source=chatgpt.com&quot;&gt;A comparative analysis of log management solutions&lt;/a&gt;&amp;rdquo; 에 따르면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PLG 스택이 CPU/메모리와 디스크 사용 면에서 더 효율적이지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ELK는 쿼리 응답 속도와 사용자 관점 사용성 면에서 우세하다는 결과가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 다른 비교 글에서는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Elasticsearch는 로그 콘텐츠 전체를 색인하는 반면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Loki는 메타데이터(라벨)만 색인한다는 설계 차이를 강조한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;PLG 스택 구성 요소&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;Promtail&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&amp;ndash; 로그 에이전트: 로그 파일을 tail 하며 메타데이터 라벨을 붙여 Loki로 전송&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Loki&lt;/b&gt;&amp;ndash; 내부적으로 메타데이터 색인 + 로그 청크 저장 구조&lt;/li&gt;
&lt;li&gt;&amp;ndash; 읽기/쓰기 경로 분리 구조&lt;/li&gt;
&lt;li&gt;&amp;ndash; 로그 저장 및 조회 시스템&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Grafana&lt;/b&gt;&amp;ndash; Loki를 데이터 소스로 활용&lt;/li&gt;
&lt;li&gt;&amp;ndash; 통합 시각화 플랫폼: 메트릭 + 로그 모두 표현 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Promtail이 로컬 로그 파일을 스캔 &amp;rarr; 라벨 부여&lt;/li&gt;
&lt;li&gt;Promtail &amp;rarr; Distributor(분배기) &amp;rarr; Ingester&lt;/li&gt;
&lt;li&gt;Ingester가 로그를 청크(chunk) 단위로 압축 저장&lt;/li&gt;
&lt;li&gt;색인된 메타데이터를 인덱스로 저장&lt;/li&gt;
&lt;li&gt;Querier가 인덱스 + 청크를 조합해 쿼리 응답&lt;/li&gt;
&lt;li&gt;Grafana 대시보드에서 로그 + 메트릭 동시 시각화&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;InfraCloud 블로그에 따르면, Loki는 로그 내용 전체를 색인하지 않고, 메타데이터만 색인하는 전략을 사용해서 &lt;b&gt;저장 비용 절감&lt;/b&gt;을 꾀한다는 설명이 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, Loki를 주로 사용하는 환경에서는 Prometheus와의 통합이 자연스럽고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메트릭 + 로그를 같은 Grafana 대시보드에서 표현하는 것이 강점으로 자주 언급된다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;리소스 효율성로그 저장 공간이 ELK 대비 5~10배 적다는 주장도 있음 &lt;a href=&quot;https://itnext.io/how-do-open-source-solutions-for-logs-work-elasticsearch-loki-and-victorialogs-9f7097ecbc2f?utm_source=chatgpt.com&quot;&gt;itnext.io+2signoz.io+2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;메타데이터만 색인 &amp;rarr; 색인 오버헤드 절감&lt;/li&gt;
&lt;li&gt;설치/운영 단순성&lt;/li&gt;
&lt;li&gt;Elasticsearch처럼 복잡한 샤드/Replica 전략 없이 시작 가능&lt;/li&gt;
&lt;li&gt;메트릭 + 로그 통합&lt;/li&gt;
&lt;li&gt;Grafana 하나로 메트릭과 로그를 동시에 시각화 가능&lt;/li&gt;
&lt;li&gt;비용 절감&lt;/li&gt;
&lt;li&gt;오브젝트 스토리지 (예: S3, MinIO)와 연결해 로그 청크 저장 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&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;/li&gt;
&lt;li&gt;문서 전체 텍스트 색인 기능이 없으므로, 조건부 full-text 검색엔 취약&lt;/li&gt;
&lt;li&gt;레이블(cardinality) 관리 부담&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;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 우리 서비스 / 인프라 환경을 아래 항목 기준으로 살펴야 한다:&lt;/p&gt;
&lt;p&gt;기준 고려점&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;로그량&lt;/td&gt;
&lt;td&gt;하루 생성되는 로그량 (MB/GB/TB 단위)&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;CPU / 메모리 / 디스크 여유 자원&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;미래 로그 증가 예측, 스케일 아웃 가능성&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;/tbody&gt;
&lt;/table&gt;</description>
      <category>Infra &amp;amp; DevOps/DevOps</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/15</guid>
      <comments>https://ryuwon-it.tistory.com/15#entry15comment</comments>
      <pubDate>Sun, 5 Oct 2025 23:31:27 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Spring Boot Actuator 살펴보기</title>
      <link>https://ryuwon-it.tistory.com/14</link>
      <description>&lt;p&gt;개인적으로 서비스를 개발하는 것도 중요하지만, 운영하는 것도 정말 중요하다고 생각한다. 왜냐하면 운영을 하게 됐을 때 어떤 상황들이 발생할지 모르기 때문에, 예기치 못한 오류나 성능 저하에 빠르게 대응할 수 있는 준비가 반드시 필요하다.&lt;/p&gt;
&lt;p&gt;그렇기에 서비스 운영이 있어 &lt;strong&gt;로깅&lt;/strong&gt;과 &lt;strong&gt;모니터링&lt;/strong&gt;을 통해 항상 문제 없는지 체크하고, 장애를 감지할 수 있는 체계를 갖추어야 한다.&lt;/p&gt;
&lt;p&gt;서비스에 문제가 없는지 확인하는 데에는 로그와 지표를 심어 감시하여 장애에 대응할 수 있다. &lt;/p&gt;
&lt;h3&gt;프로덕션 준비 기능이란?&lt;/h3&gt;
&lt;p&gt;운영 환경에서는 단순히 코드가 돌아간다는 사실만으로는 충분하지 않다. 서비스가 얼마나 안정적으로 동작하는지, 사용자가 체감하는 경험이 문제가 없는지까지 끊임없이 확인해야 한다. 이를 위해서는 눈에 보이지 않는 부분까지 세밀하게 점검할 수 있는 기준과 질문이 필요하다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;애플리케이션이나 DB가 잘 작동하고 있는지 확인할 수 있는가?&lt;/li&gt;
&lt;li&gt;각 서비스들은 얼마만큼의 서버 자원을 소모하고 있는가?&lt;/li&gt;
&lt;li&gt;만약 장애가 발생한다면 언제, 어디서 발생했는지 빠르게 확인 할 수 있는가?&lt;/li&gt;
&lt;li&gt;요청 단위 추적(trace)으로 장애 원인을 끝까지 따라갈 수 있는가?&lt;br&gt;…&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이런 질문들에 명확히 답할 수 없다면, 서비스는 언제든 예기치 못한 장애에 취약할 수밖에 없다.&lt;/p&gt;
&lt;p&gt;결국 애플리케이션이 잘 살아있는지, 로그가 잘 찍히고 있는지, DB와의 연결이 잘 되어 있는지, 자원을 얼마나 사용하고 있는지 등 운영 환경에서 서비스할 때 필요한 기능들을 ‘&lt;strong&gt;프로덕션 준비 기능(&lt;a href=&quot;https://docs.spring.io/spring-boot/reference/actuator/index.html&quot;&gt;Production-Ready Features&lt;/a&gt;)&lt;/strong&gt;’이라 한다.&lt;/p&gt;
&lt;p&gt;위와 같은 Spring Boot 애플리케이션을 운영함에 있어 프로덕션 준비 기능을 편하게 사용할 수 있는 것이 &lt;strong&gt;Spring Boot Actuator&lt;/strong&gt; 라이브러리이다.&lt;/p&gt;
&lt;h2&gt;Spring Boot Actuator 알아보기&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;액추에이터&lt;/strong&gt;란 애플리케이션 내부의 다양한 상태와 동작을 노출하여 운영자가 손쉽게 모니터링하고 관리할 수 있도록 도와주는 도구이다. 단순한 헬스체크 이상의 기능을 제공하며, 서비스가 프로덕션 환경에서 운영될 수 있도록 여러 정보들을 제공해주는 역할을 한다.&lt;/p&gt;
&lt;p&gt;액추에이터가 기본적으로 상당히 많은 모니터링 요소를 제공해주고 다음과 같은 기능들을 통해 프로덕션 준비를 손쉽고 편리하게 진행할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;애플리케이션 상태(Health) 및 정보(info) 조회&lt;/li&gt;
&lt;li&gt;JVM 및 시스템 자원(CPU, 메모리, GC) 등 모니터링 지표 확인 가능&lt;/li&gt;
&lt;li&gt;비즈니스 지표(HTTP 요청, DB 커넥션)까지 모니터링 가능&lt;/li&gt;
&lt;li&gt;내부 Bean, 매핑, 스레드 등 런타임 구조를 직접 노출해 심층적인 분석과 원인 진단을 지원&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;마이크로미터(Micrometer)&lt;/strong&gt;, &lt;strong&gt;프로메테우스(Prometheus)&lt;/strong&gt;, &lt;strong&gt;그라파나(Grafana)&lt;/strong&gt; 등 모니터링 시스템과의 손쉬운 연동&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;참조 - &lt;a href=&quot;https://eco-dev.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%95%A1%EC%B8%84%EC%97%90%EC%9D%B4%ED%84%B0actuator%EB%A1%9C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EB%8A%94-%ED%94%84%EB%A1%9C%EB%8D%95%EC%85%98-%EC%A4%80%EB%B9%84-%EA%B8%B0%EB%8A%A5&quot;&gt;https://eco-dev.tistory.com/entry/Spring-스프링-액추에이터actuator로-살펴보는-프로덕션-준비-기능&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;엔드포인트&lt;/h3&gt;
&lt;p&gt;액추에이터가 제공하는 다양한 기능들은 실제로는 각각 &lt;code&gt;엔드포인트(Endpoint)&lt;/code&gt;라는 단위로 구성되어 있다.&lt;/p&gt;
&lt;p&gt;엔드포인트는 애플리케이션 내부의 특정 정보를 외부에 노출해주는 창구 역할을 하며, 어떤 엔드포인트를 활성화하고 웹으로 열어둘지는 개발자가 직접 선택할 수 있다.&lt;/p&gt;
&lt;p&gt;따라서 운영 환경에서는 단순히 헬스 체크만 노출하거나, 특정 상황에서만 필요한 엔드포인트를 제한적으로 허용하는 방식으로 활용한다.&lt;/p&gt;
&lt;p&gt;다음은 엔드포인트들의 기본 활성화 상태와 웹 노출 Default 값에 대해 정리된 표를 살펴보고자 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enabled by default (기본 활성화)&lt;/strong&gt;: 엔드포인트 기능의 Bean이 기본적으로 Spring 컨테이너에 등록&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web Exposed (웹 노출)&lt;/strong&gt;: 실제로 &lt;code&gt;/actuator/{id}&lt;/code&gt; URL을 통해 접근할 수 있는지 가능 여부&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;ex)&lt;/code&gt; &lt;code&gt;*http://test.com/actuator/health*&lt;/code&gt; - 어플리케이션 상태 체크&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;ID&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;설명 (Description)&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;기본 활성화&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;Web 기본 노출&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;auditevents&lt;/td&gt;
&lt;td&gt;애플리케이션의 감사 이벤트 정보를 노출&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;beans&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;애플리케이션의 모든 Spring Bean 목록을 표시&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;caches&lt;/td&gt;
&lt;td&gt;사용 가능한 캐시를 노출&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;conditions&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;자동 구성에 대한 조건 평가 보고서를 보여줌&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;configprops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;@ConfigurationProperties&lt;/code&gt;의 목록을 표시&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;env&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;환경설정 정보 조회(&lt;code&gt;application.properties&lt;/code&gt;, &lt;code&gt;application.yml&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;flyway&lt;/td&gt;
&lt;td&gt;적용된 Flyway 데이터베이스 마이그레이션을 보여줌&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;health&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;애플리케이션 상태 정보를 보여줌&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;heapdump&lt;/td&gt;
&lt;td&gt;힙 덤프 파일을 반환&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;httptrace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;최근 HTTP request/response 표시 (기본값: 마지막 100개)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;info&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;애플리케이션 기본 정보를 표시&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;integrationgraph&lt;/td&gt;
&lt;td&gt;Spring Integration 그래프를 보여줌&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jolokia&lt;/td&gt;
&lt;td&gt;JMX bean을 HTTP를 통해 노출 (Jolokia가 클래스패스에 있을 때).&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;logfile&lt;/td&gt;
&lt;td&gt;&lt;code&gt;logging.file.name&lt;/code&gt; 또는 &lt;code&gt;logging.file.path&lt;/code&gt;가 설정된 경우 로그 파일 내용을 반환&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;loggers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;애플리케이션의 로거 구성을 보고 수정&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;liquibase&lt;/td&gt;
&lt;td&gt;적용된 Liquibase 데이터베이스 마이그레이션을 보여줌&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;metrics&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;현재 애플리케이션의 &amp;#39;메트릭&amp;#39; 정보를 보여줌&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;mappings&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;모든 &lt;code&gt;@RequestMapping&lt;/code&gt; 경로 목록을 표시&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;prometheus&lt;/td&gt;
&lt;td&gt;Prometheus 서버가 스크랩할 수 있는 형식으로 메트릭을 노출&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;scheduledtasks&lt;/td&gt;
&lt;td&gt;애플리케이션의 스케줄된 작업을 표시&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;sessions&lt;/td&gt;
&lt;td&gt;Spring Session에서 사용자 세션을 검색하고 삭제 가능&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;shutdown&lt;/td&gt;
&lt;td&gt;애플리케이션을 정상적으로 종료 가능&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;threaddump&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;스레드 덤프 조회&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/2.1.8.RELEASE/reference/html/production-ready-endpoints.html#production-ready-endpoints-enabling-endpoints&quot;&gt;53. Endpoints&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;보시는 바와 같이 URL을 통해 접근하는 경우 &lt;code&gt;health&lt;/code&gt;와 &lt;code&gt;info&lt;/code&gt;만 기본적으로 허용되어 있다.&lt;/p&gt;
&lt;p&gt;그래서 단순 health check만 필요한 상황이면 의존성만 추가하더라도 바로 사용 가능하다.&lt;/p&gt;
&lt;p&gt;만약 다른 엔드포인트(예: &lt;code&gt;beans&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt; 등)를 웹으로 노출하고 싶다면 &lt;code&gt;application.properties&lt;/code&gt; 또는 &lt;code&gt;application.yml&lt;/code&gt; 파일에 다음과 같이 &lt;code&gt;management.endpoints.web.exposure.include&lt;/code&gt; 설정을 추가해야 한다.&lt;/p&gt;
&lt;h3&gt;Actuator 사용해보기&lt;/h3&gt;
&lt;p&gt;액추에이터가 제공하는 기능들을 사용하기 위해선 의존성을 추가해줘야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;implementation &amp;#39;org.springframework.boot:spring-boot-starter-actuator&amp;#39;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;액추에이터가 제대로 동작하는지 보기 위해서는 &lt;code&gt;{서버 주소}/actuator&lt;/code&gt; 로 접속하면 확인 가능하다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;위와 같은 기능들이 달려 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;의존성만 달아두면 별다른 설정을 하지 않더라도, &lt;strong&gt;기본적인 헬스체크는 바로 사용가능&lt;/strong&gt;하다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;image%201.png&quot; alt=&quot;image.png&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/actuator/health&lt;/code&gt;에 접근하면 단순히 &amp;quot;정상/비정상&amp;quot; 여부만 반환하는 것이 아니라,&lt;/p&gt;
&lt;p&gt;애플리케이션의 현재 상태를 추상화한 &lt;code&gt;status&lt;/code&gt; 값을 함께 응답한다.&lt;/p&gt;
&lt;p&gt;이 값은 로드밸런서나 모니터링 시스템이 서버를 관리할 때 중요한 기준이 되며, 운영자가 서버 상태를 빠르게 이해하는 데에도 큰 도움을 주는데 이 상태는 &lt;strong&gt;총 4가지가 존재&lt;/strong&gt;한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;UP&lt;/code&gt;&lt;/strong&gt; : 시스템이 작동중.접근 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;DOWN&lt;/code&gt;&lt;/strong&gt; : 시스템이 작동중이지 않거나, 접근 불가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;UNKNWON&lt;/code&gt;&lt;/strong&gt; : 시스템의 상태가 분명하지 않음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;OUT_OF_SERVICE&lt;/code&gt;&lt;/strong&gt; : 시스템에 접근은 가능하지만, 현재 사용은 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 4가지 상태를 기반으로 로드밸런서는 정상적인 서버에만 트래픽을 분산하고, 운영자는 현재 애플리케이션이 서비스 가능한 상태인지 빠르게 판별할 수 있다.&lt;/p&gt;
&lt;p&gt;즉, 단순히 서버가 살아있다는 신호를 주는 것이 아니라, &lt;strong&gt;의존 리소스와 환경까지 종합적으로 평가한 결과&lt;/strong&gt;를 전달해주는 것이다. &lt;/p&gt;
&lt;p&gt;이 덕분에 서비스는 고가용성을 보장할 수 있고, 장애 발생 시에도 원인을 신속히 좁혀나갈 수 있다.&lt;/p&gt;
&lt;p&gt;다음은 &lt;strong&gt;액츄에이터 엔드포인트 추가&lt;/strong&gt;에 대해서 살펴보고자 한다.&lt;/p&gt;
&lt;p&gt;엔드포인트 노출 범위나 상세 정보 출력 여부는 &lt;code&gt;application.yml&lt;/code&gt; 또는 &lt;code&gt;application.properties&lt;/code&gt;에서 손쉽게 설정할 수 있다.&lt;/p&gt;
&lt;p&gt;사용하고 싶은 엔드포인트들의 id 값을 &lt;code&gt;management.endpoints.web.exposure.include&lt;/code&gt;에 담아주기만 하면 설정 완료이다. 그 밖에도 헬스 체크 결과에 세부 정보를 포함할지 여부도 &lt;code&gt;management.endpoint.health.show-details&lt;/code&gt; 옵션으로 제어할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;# application.properties 예시
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=when_authorized&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;# application.yml 예시
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics  # 노출할 엔드포인트 지정
  endpoint:
    health:
      show-details: when_authorized   # health 상세 정보 접근 제어&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같이 설정하면 운영 환경에서는 꼭 필요한 엔드포인트만 노출하고,&lt;/p&gt;
&lt;p&gt;민감한 세부 정보는 인증된 사용자에게만 확인할 수 있도록 제어할 수 있다.&lt;/p&gt;
&lt;p&gt;추가로, &lt;strong&gt;특정 엔드포인트 자체를 아예 활성화하거나 비활성화&lt;/strong&gt;할 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;exposure.include&lt;/code&gt;는 “웹으로 노출할지 말지”를 결정하는 반면,&lt;/p&gt;
&lt;p&gt;&lt;code&gt;management.endpoint.{id}.enabled&lt;/code&gt;는 엔드포인트 기능 자체를 등록할지 여부를 제어한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;# application.yml 예시
management:
  endpoint:
    shutdown:
      enabled: true     # shutdown 엔드포인트 활성화
    beans:
      enabled: false    # beans 엔드포인트 비활성화
    loggers:
      enabled: true     # loggers 엔드포인트 활성화&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;쉽게 말하자면&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;exposure/include&lt;/code&gt; - 보여줄지 말지&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enabled&lt;/code&gt; - 기능을 사용할지 말지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 구분할 수 있을 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;방식 비교&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;&lt;code&gt;exposure.include&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;endpoint.{id}.enabled&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;웹으로 노출할 엔드포인트 선택&lt;/td&gt;
&lt;td&gt;엔드포인트 자체를 켜거나 끔&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;범위&lt;/td&gt;
&lt;td&gt;노출 제어&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(내부 Bean은 살아있음)&lt;/td&gt;
&lt;td&gt;등록 자체를 차단&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(엔드포인트가 없음)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;예시&lt;/td&gt;
&lt;td&gt;&lt;code&gt;beans&lt;/code&gt;는 동작하지만 URL로 접근 불가&lt;/td&gt;
&lt;td&gt;&lt;code&gt;beans&lt;/code&gt; 엔드포인트 자체가 아예 사라짐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;보안 수준&lt;/td&gt;
&lt;td&gt;URL 차원에서 접근 막음&lt;/td&gt;
&lt;td&gt;기능 자체 제거&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;커스텀 엔드포인트&lt;/h3&gt;
&lt;p&gt;액추에이터에는 위와 같이 이미 다양한 엔드포인트들을 제공해주고 있지만, 실제 서비스를 운영하거나 실무환경에서는 프로젝트의 특성에 따라 직접 정의한 엔드포인트가 필요한 경우도 있다.&lt;/p&gt;
&lt;p&gt;예를 들어 &lt;/p&gt;
&lt;p&gt;그냥 &lt;code&gt;@RestController&lt;/code&gt;로 만들면 되는 거 아닌가?”라는 의문이 들 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 직접 만든 컨트롤러는 액추에이터 환경과 연동이 안된다.. &lt;/p&gt;
&lt;p&gt;예를 들어 Prometheus, Grafana 같은 모니터링 시스템은 액추에이터의 엔드포인트 인터페이스를 통해 데이터를 수집합니다. 따라서 커스텀 모니터링 지표를 제공하려면 &lt;strong&gt;액추에이터의 엔드포인트 구조를 따르는 것&lt;/strong&gt;이 훨씬 유리하다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/reference/actuator/endpoints.html#actuator.endpoints.implementing-custom&quot;&gt;Endpoints :: Spring Boot&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;커스텀 엔드포인트 만들기&lt;/h3&gt;
&lt;p&gt;Spring Boot Actuator는 이미 다양한 범용 엔드포인트를 제공하고 있다. &lt;code&gt;beans&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, &lt;code&gt;caches&lt;/code&gt;, &lt;code&gt;metrics&lt;/code&gt; 등 &amp;quot;이런 게 기본 제공되면 좋겠다&amp;quot; 싶은 기능들이 대부분 들어있다.&lt;/p&gt;
&lt;p&gt;하지만 실무에서는 사내 정책이나 프로젝트 특성에 따라 &lt;strong&gt;직접 정의한 전용 엔드포인트&lt;/strong&gt;가 필요한 경우도 있다. 예를 들어 서비스 전용(특정 로직) 상태 점검 API, 개별 라이브러리 버전 정보, 운영용 데이터 뽑아낼 때 등이 이에 해당한다.&lt;/p&gt;
&lt;p&gt;이럴 때는 액추에이터가 제공하는 확장 기능을 활용해 &lt;strong&gt;Custom Endpoint&lt;/strong&gt;를 만들 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;간단한 예제&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;아래 예시는 &lt;code&gt;myLibraryInfo&lt;/code&gt;라는 이름의 엔드포인트를 만들어, 애플리케이션에서 사용하는 라이브러리 이름과 버전 정보를 반환하도록 구현한 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Endpoint(id = &amp;quot;myLibraryInfo&amp;quot;)  // 엔드포인트 id 지정
public class MyLibraryInfoEndpoint {

    @ReadOperation   // GET 요청(Read)에 매핑
    public List&amp;lt;LibraryInfo&amp;gt; getLibraryInfos() {
        LibraryInfo lib1 = new LibraryInfo(&amp;quot;logback&amp;quot;, &amp;quot;1.0.0&amp;quot;);
        LibraryInfo lib2 = new LibraryInfo(&amp;quot;jackson&amp;quot;, &amp;quot;2.0.0&amp;quot;);

        return Arrays.asList(lib1, lib2);
    }
}

@Data
@AllArgsConstructor
class LibraryInfo {
    private String name;
    private String version;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 이 클래스를 빈으로 등록해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
public class MyLibraryInfoConfig {
    @Bean
    public MyLibraryInfoEndpoint myLibraryInfoEndpoint() {
        return new MyLibraryInfoEndpoint();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Spring Boot를 재실행하면 &lt;code&gt;/actuator/myLibraryInfo&lt;/code&gt;라는 URL이 추가되고, 우리가 정의한 데이터가 JSON 형태로 노출된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;지원되는 Operation 종류&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;엔드포인트는 단순 조회뿐만 아니라, 쓰기나 삭제 같은 명령형 동작도 지원한다. 이를 위해 액추에이터는 다음음과 같은 어노테이션을 제공한다. &lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;어노테이션&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;설명&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;매핑되는 HTTP Method&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@ReadOperation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데이터 조회&lt;/td&gt;
&lt;td&gt;GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@WriteOperation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데이터 생성/변경&lt;/td&gt;
&lt;td&gt;POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;@DeleteOperation&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;데이터 삭제&lt;/td&gt;
&lt;td&gt;DELETE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;예를 들어, 특정 로거 레벨을 변경하는 동작을 엔드포인트로 노출하고 싶다면 &lt;code&gt;@WriteOperation&lt;/code&gt;을 붙여 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;파라미터 처리&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Query String&lt;/strong&gt;: 단순 GET 파라미터는 메서드 인자에 그대로 선언하면 자동 매핑된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Body&lt;/strong&gt;: &lt;code&gt;@WriteOperation&lt;/code&gt;으로 매핑하면 POST Body도 받을 수 있다. (단, DTO 객체 바인딩은 지원되지 않고, 개별 필드 단위만 허용된다.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Path Parameter&lt;/strong&gt;: &lt;code&gt;@Selector&lt;/code&gt; 어노테이션을 이용하면 path 기반 파라미터도 받을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@ReadOperation
public String getPathInfo(@Selector String path) {
    return &amp;quot;입력받은 값: &amp;quot; + path;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/actuator/myLibraryInfo/test&lt;/code&gt;로 호출하면 &lt;code&gt;입력받은 값: test&lt;/code&gt;라는 결과를 반환한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;왜 직접 REST Controller가 아닌 Actuator를 써야 할까?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;quot;그냥 &lt;code&gt;@RestController&lt;/code&gt;로 만들면 되는 거 아닌가?&amp;quot;라는 의문이 들 수 있다.&lt;/p&gt;
&lt;p&gt;하지만 직접 만든 컨트롤러는 액추에이터의 환경과 연동되지 않는다. &lt;/p&gt;
&lt;p&gt;예를 들어 Prometheus, Grafana 같은 모니터링 시스템은 액추에이터의 엔드포인트 인터페이스를 통해 데이터를 수집한다. 따라서 커스텀 모니터링 지표를 제공하려면 &lt;strong&gt;액추에이터의 엔드포인트 구조를 따르는 것&lt;/strong&gt;이 훨씬 유리하다.&lt;/p&gt;
&lt;h2&gt;Actuator 안전하게 사용하기&lt;/h2&gt;
&lt;h3&gt;외부 노출 방지&lt;/h3&gt;
&lt;p&gt;액추에이터가 애플리케이션의 많은 정보들을 제공하는만큼 내부 정보가 외부에 과도하게 노출되며 보안이 취약해지는거 같다. 그래서 누구나 액추에이터를 접근할 수 있다면 문제가 발생할 수 있기에 반드시 접근 제어와 보안 설정이 필요하다고 생각한다. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;내부망으로만 접근 제한&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;액추에이터 엔드포인트는 외부에 직접 노출하지 말고, 내부망에서만 접근 가능하도록 설정하는 것이 좋다.&lt;/p&gt;
&lt;p&gt;AWS 환경에서는 내부 ALB나 VPC 내부에서만 접근하도록 보안 그룹을 설정하거나, 프라이빗 서브넷에 위치시키는 방법을 사용할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;보안 정책 설정&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;민감한 정보에 대한 접근을 제한하기 위해 특정 헤더나 IP 기반의 접근 제어를 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;또한 상세 정보 노출은 인증된 사용자에게만 제한하는 것이 좋다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;strong&gt;옵션&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;설명&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;보안 수준&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;권장 사용 환경&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;always&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;항상 상세 정보를 노출&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;td&gt;개발 및 테스트 환경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(디버깅 목적)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;never&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;상세 정보 없이 전반적인 상태만 노출&lt;/td&gt;
&lt;td&gt;보통&lt;/td&gt;
&lt;td&gt;모든 환경 &lt;strong&gt;(기본값)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;when_authorized&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;권한 있는 사용자에게만 상세 정보 노출&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(스프링 시큐리티 적용 시)&lt;/td&gt;
&lt;td&gt;프로덕션 환경&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;management:
  endpoint:
    health:
      show-details: when_authorized  # 인증된 사용자에게만 상세 정보 제공&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 인증이란 스프링 시큐리티가 인가 과정에서 검증하는, 사용자가 가지고 있는 특정 역할을 의미한다 JWT를 사용하든, 세션을 사용하든 관계없이 스프링 시큐리티가 인가 규칙에 따라 그 사용자가 상세 정보를 볼 수 있는 권한이 있는지 판단하기에 별도의 시큐리티 설정도 필요하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;스프링 시큐리티를 이용한 접근제어&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;스프링 시큐리티를 통해 액추에이터 엔드포인트에 대한 인증 및 권한 제어가 가능하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;@Configuration
public class ActuatorSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatcher(EndpointRequest.toAnyEndpoint())
            .authorizeRequests()
            .anyRequest().hasRole(&amp;quot;ACTUATOR_ADMIN&amp;quot;)
            .and()
            .httpBasic();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이렇게 설정하면 &amp;#39;ACTUATOR_ADMIN&amp;#39; 역할을 가진 사용자만 액추에이터에 접근할 수 있다.&lt;/p&gt;
&lt;p&gt;또는 security path에 대해 설정을 해두었다면, 특정 권한을 가진 유저들만 접근 할 수 있기에 훨씬 안전하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;security:
  path:
    # 모든 요청에 대해 인증을 요구하지 않는 경로 설정
    permit-all:
      ALL:
        - &amp;quot;/swagger-ui/**&amp;quot;
        - &amp;quot;/v3/api-docs/**&amp;quot;
        - &amp;quot;/api/v1/user/univ/campus&amp;quot;

    # 인증된 사용자만 접근 가능한 경로 설정
    authenticated:
      ALL:
        - &amp;quot;/api/v1/user/bookmark&amp;quot;
        - &amp;quot;/api/v1/user/recentReview&amp;quot;
        - &amp;quot;/api/v1/auth/tokenRefresh&amp;quot;
        - &amp;quot;/api/v1/auth/logout&amp;quot;
        - &amp;quot;/actuator/info&amp;quot; # 이렇게 인증된 사용자만 접근 가능하도록&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;필요한 엔드포인트만 활성화&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;모든 엔드포인트를 노출하지 말고, 꼭 필요한 것만 선택적으로 활성화하는 것이 안전하다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;management:
  endpoints:
    web:
      exposure:
        include: health,info,prometheus  # 필요한 엔드포인트만 활성화&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;엔드포인트 변경와 포트 변경 (비추)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;액추에이터는 yml 설정을 통해 다른 포트로 변경을 하거나 기본 &amp;#39;/actuator&amp;#39; 경로를 변경할 수도 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;management:
  server:
      port: 8081&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;management:
  endpoints:
    web:
      base-path: /management  # 기본 경로 변경&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;단, 이는 단순히 엔드포인트만 변경한 것이기에 nmap과 포트 스캔, 서비스 탐지로 충분히 다 뚫릴수도..&lt;/p&gt;
&lt;p&gt;실질적인 보안 수단으로는 사용 불가능하기에 다른 보안 조치와 함께 사용해야 한다.&lt;/p&gt;
&lt;h3&gt;헬스체크 주의사항&lt;/h3&gt;
&lt;p&gt;액추에이터의 &lt;code&gt;/health&lt;/code&gt;는 로드밸런서와 오토스케일러가 서버 상태를 판단하는 기준으로 자주 사용된다.&lt;/p&gt;
&lt;p&gt;하지만 동작 원리를 충분히 이해하지 못한 채 그대로 노출하거나 의존하면, 의도치 않게 장애가 확산되거나 원인 분석이 지연될 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;헬스체크 오용에 따른 혼란&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  최근 액추에이터를 설정했었을때, 도메인 관련 이슈가 있어서 LB에서의 설정이 제대로 안되어 서버는 살아있지만 헬스체크로는 DOWN으로 나온 상황이 있었다.&lt;/p&gt;
&lt;p&gt;  이처럼 문제가 발생했었을 때  LB 설정 문제인지, 서버 자체인지, DB인지 파악하는 데 시간이 오래 걸려 복구가 지연될 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;의존 리소스 문제 전파&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;HealthIndicator&lt;/code&gt;가 여러 개 등록되면, 하나라도 DOWN일 경우 전체 상태를 DOWN으로 반환한다.&lt;/p&gt;
&lt;p&gt;  예를 들어 서비스 DB는 정상인데 로그 DB만 일시적으로 끊겨도 서버가 503을 반환하며 로드밸런서에서 제외된다.&lt;/p&gt;
&lt;p&gt;  이는 실제 서비스는 가능한데, 헬스체크 때문에 오히려 장애가 발생하는 상황이다..&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;외부 서비스 장애 영향&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  Elasticsearch, Redis, 외부 API처럼 핵심 서비스가 아닌 의존 리소스가 죽었을 때도 액추에이터는 DOWN으로 판정할 수 있다.&lt;/p&gt;
&lt;p&gt;  API 서버 자체는 살아있음에도 LB가 트래픽을 끊어버려 불필요한 가용성 저하가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;  실제로 ES 장애 → API 서버 DOWN 판정 → 트래픽 차단 → 트러블슈팅 지연으로 이어진 경우가 있다.&lt;br&gt;  (feat. 밑 첨부된 토스 블로그 글)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;보안 이슈&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;  &lt;code&gt;management.endpoint.health.show-details: always&lt;/code&gt; 옵션을 켜두면 DB 연결 정보나 내부 의존성 상태가 노출될 수 있다.&lt;/p&gt;
&lt;p&gt;  로컬 테스트나 제한된 환경에서만 활용하고, 운영에서는 &lt;code&gt;when_authorized&lt;/code&gt; 수준으로 제한하는 것이 안전하다. &lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이를 해결 하기 위해 아래와 같은 대응들이 필요하다고 생각한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;꼭 필요한 &lt;code&gt;HealthIndicator&lt;/code&gt;만 활성화&lt;br&gt;(&lt;code&gt;management.health.db.enabled=false&lt;/code&gt; 등으로 불필요한 지표 비활성화)&lt;/li&gt;
&lt;li&gt;핵심 서비스 상태만 판단하는 &lt;strong&gt;커스텀 헬스체크 API&lt;/strong&gt; 구현 고려하기&lt;/li&gt;
&lt;li&gt;헬스체크의 DOWN 기준을 팀 내에서 명확히 합의하고 문서화하기&lt;/li&gt;
&lt;li&gt;상세 상태 노출은 위에 언급했듯 접근 제어와 보안 설정을 통해 내부망이나 인증된 사용자에게만 제한&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;헬스체크는 단순히 서버가 살아있는지 확인하는 수단이 아니라,&lt;/p&gt;
&lt;p&gt;운영 장애의 전파 범위와 대응 속도를 결정하는 중요한 요소니까&lt;/p&gt;
&lt;p&gt;무엇을 &lt;code&gt;UP&lt;/code&gt;/&lt;code&gt;DOWN&lt;/code&gt;으로 볼지를 반드시 팀 차원에서 합의하고,불필요한 의존성이 전체 서비스 가용성을 해치지 않도록 설계하는 것이 중요할 것 같다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://toss.tech/article/how-to-work-health-check-in-spring-boot-actuator&quot;&gt;Spring Boot Actuator의 헬스체크 살펴보기&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Actuator&lt;/strong&gt;는 단순한 개발 편의 기능을 넘어서, 운영 안정성과 서비스 관리의 편리함을 가져다주는 라이브러리다. 사실 단순한 헬스 체크만 간단히 사용할꺼라면 이만한 게 없는 것 같기도..&lt;/p&gt;
&lt;p&gt;덕분에 애플리케이션의 상태를 빠르게 점검하고, 장애 원인을 좁혀나가며, 모니터링 시스템과 연동해 더 체계적인 운영을 할 수 있다.&lt;/p&gt;
&lt;p&gt;다만, &lt;strong&gt;민감한 정보까지 노출될 수 있다는 점&lt;/strong&gt;에서 사용에는 반드시 신중해야 한다.&lt;/p&gt;
&lt;p&gt;노출할 엔드포인트와 차단할 엔드포인트를 명확히 구분하고, 접근 권한을 제한하며, 헬스체크 기준을 팀 차원에서 합의하는 것이 필요하다.&lt;/p&gt;
&lt;p&gt;좀 더 자세히 액추에이터에 대해 알고싶다면 밑 블로그의 액추에이터 시리즈 또는 참조 사이트들을 참조하면 더욱 좋다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://semtul79.tistory.com/11&quot;&gt;spring boot 3.x + actuator 파헤치기. 1. 프로젝트 생성&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;다음 글은 매트릭 수집을 위한 Micrometer에 대해 알아가보고자 한다.&lt;/p&gt;
&lt;p&gt;스프링 2.x.x 버전부터는 액추에이터에 &lt;a href=&quot;https://spring.io/blog/2018/03/16/micrometer-spring-boot-2-s-new-application-metrics-collector&quot;&gt;Micrometer가 내장&lt;/a&gt;되어 metrics을 표시할 때 도움을 주긴 하지만, 기본적인 기능들만 제공하기에 특정 비즈니스 로직을 추적하기 위한 커스텀 메트릭 등을 사용하고 싶을때는 따로 의존성을 추가해주는 편이 좋다고 한다.&lt;/p&gt;
&lt;p&gt;그래서 Micrometer를 이용하여 어떻게 매트릭을 수집하고 이를 액츄에이터로 어떻게 표현해줄 지에 대해 다뤄볼 생각이다.&lt;/p&gt;
&lt;h2&gt;참조&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/3.0.5/reference/html/actuator.html#actuator&quot;&gt;Production-ready Features&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hstory0208.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%97%91%EC%B8%84%EC%97%90%EC%9D%B4%ED%84%B0Actuator%EB%9E%80&quot;&gt;[Spring] 스프링부트 엑츄에이터(Actuator)란?&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://eco-dev.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%95%A1%EC%B8%84%EC%97%90%EC%9D%B4%ED%84%B0actuator%EB%A1%9C-%EC%82%B4%ED%8E%B4%EB%B3%B4%EB%8A%94-%ED%94%84%EB%A1%9C%EB%8D%95%EC%85%98-%EC%A4%80%EB%B9%84-%EA%B8%B0%EB%8A%A5&quot;&gt;[Spring] 스프링 액추에이터(actuator)로 살펴보는 프로덕션 준비 기능&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://joon2974.tistory.com/entry/Java-Spring-Boot-Actuator#google_vignette&quot;&gt;[Java] Spring Boot Actuator&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://hstory0208.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B6%80%ED%8A%B8-%EC%97%91%EC%B8%84%EC%97%90%EC%9D%B4%ED%84%B0Actuator%EB%9E%80&quot;&gt;[Spring] 스프링부트 엑츄에이터(Actuator)란?&lt;/a&gt;&lt;/p&gt;</description>
      <category>Framework &amp;amp; Library/Spring Boot</category>
      <author>ryuwon</author>
      <guid isPermaLink="true">https://ryuwon-it.tistory.com/14</guid>
      <comments>https://ryuwon-it.tistory.com/14#entry14comment</comments>
      <pubDate>Fri, 26 Sep 2025 15:07:04 +0900</pubDate>
    </item>
  </channel>
</rss>