<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>DEVLOG</title>
    <link>https://kmseop.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Thu, 9 Apr 2026 02:04:06 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>KMSEOP</managingEditor>
    <item>
      <title>Kubernetes 환경에서 gRPC 로드밸런싱</title>
      <link>https://kmseop.tistory.com/282</link>
      <description>&lt;p&gt;gRPC는 HTTP/2 기반으로 동작을 하고 HTTP/2는 하나의 TCP 연결에서 여러개의 스트림을 처리하도록 설계되어 클라이언트와 서버간의 연결이 오래 유지되는 Long lived connection 방식을 사용합니다.&lt;/p&gt;
&lt;p&gt;이러한 특성으로 인해 Kubernetes의 Connection Based 로드밸런싱이 gRPC에서는 정상적으로 동작하지 않게 되고 모든 요청이 동일한 파드에 고정되게 됩니다.&lt;/p&gt;
&lt;h1&gt;Connection based 로드밸런싱이란?&lt;/h1&gt;
&lt;p&gt;쿠버네티스의 기본 로드밸런싱 정책으로 L4 레이어에서 동작하며 TCP 연결이 생성될 때 로드밸런서가 특정 파드에 연결을 할당하고 연결이 종료될 때 까지 해당 파드에 트래픽을 라우팅하는 방식입니다.&lt;/p&gt;
&lt;h2&gt;동작 방식&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트가 Service의 IP(ClusterIP, NodePort, LoadBalancer 등)에 요청을 보냄.&lt;/li&gt;
&lt;li&gt;로드밸런서가 클라이언트의 요청을 받아 연결을 초기화하고, 연결된 서버(Pod)를 선택함.&lt;/li&gt;
&lt;li&gt;선택된 서버와 클라이언트 간 연결이 유지되는 동안, 해당 연결에서 발생하는 모든 트래픽은 동일한 서버로 전달됨.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Kubernetes의 Service 객체는 iptables 또는 IPVS를 통해 로드밸런싱을 구현하며, 이 과정에서 connection-based 로드밸런싱이 기본적으로 적용됩니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;iptables: DNAT(Destination NAT)를 통해 클라이언트의 요청을 특정 파드로 라우팅하며 각 연결은 고정된 Pod에 바인딩됩니다.&lt;/li&gt;
&lt;li&gt;IPVS: 좀 더 정교한 로드밸런싱 알고리즘을 제공하지만 여전히 connection-based 로드밸런싱 방식입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h1&gt;그러면 gRPC를 사용하면서 로드밸런싱을 하기 위해서 어떻게 해야하는가?&lt;/h1&gt;
&lt;h2&gt;1. Service mesh&lt;/h2&gt;
&lt;p&gt;Istio의 Envoy proxy의 기능을 활용하면 L7 레이어의 연결 관리 및 트래픽 라우팅을 설정할 수 있어 HTTP/2 및 gRPC의 Long lived connection을 호환가능하게 해줍니다.&lt;/p&gt;
&lt;h2&gt;Istio가 gRPC Long-Lived Connection 지원 가능한 이유&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Envoy는 Connection Pool을 사용하여 클라이언트와 서버 간의 연결을 관리하여 클라이언트와의 장기 연결을 유지하면서 파드들의 상태에 따라 트래픽을 동적으로 조정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;Istio는 Request-level 로드밸런싱을 지원하며, gRPC 요청을 개별적으로 파드들에 분배합니다.&lt;/li&gt;
&lt;li&gt;Istio는 VirtualService와 DestinationRule을 통해 연결 유지 시간, 재시도, 타임아웃, 로드밸런싱 정책 등을 세밀히 제어할 수 있습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;설정 예시&lt;/h2&gt;
&lt;p&gt;Destination rule&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: grpc-destination
spec:
  host: grpc-service.default.svc.cluster.local
  trafficPolicy:
    connectionPool:
      http:
        http2MaxRequests: 1000  # HTTP/2 연결 당 최대 요청 수
        maxRequestsPerConnection: 0  # 연결당 제한 없음
        idleTimeout: 1h  # 비활성 연결 유지 시간
    outlierDetection:
      consecutiveErrors: 5  # 오류 발생 시 연결 종료 기준
      interval: 10s  # 오류 탐지 간격
      baseEjectionTime: 1m  # 연결 제거 시간
    loadBalancer:
      simple: ROUND_ROBIN  # 요청을 라운드 로빈 방식으로 분배
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Virtual service&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-yaml&quot;&gt;apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
  name: grpc-virtual-service
spec:
  hosts:
  - grpc-service.default.svc.cluster.local
  http:
  - route:
    - destination:
        host: grpc-service.default.svc.cluster.local
        port:
          number: 50051
    timeout: 15s  # 요청 타임아웃
    retries:
      attempts: 3  # 최대 재시도 횟수
      perTryTimeout: 5s  # 각 시도당 타임아웃
      retryOn: 5xx,connect-failure,refused-stream  # 재시도 조건
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 클라이언트 사이드 로드밸런싱&lt;/h2&gt;
&lt;p&gt;gRPC는 클라이언트 사이드에서 로드밸런싱이 가능합니다.&lt;br&gt;클라이언트에서 Kubernetes API를 사용해 서비스의 엔드포인트(Endpoints)를 직접 조회하여 여러 Pod에 트래픽을 분산시킬 수 있고 channel을 만드는 과정에서 로드밸런싱 정책을 설정할 수 있습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;private val channel: ManagedChannel = ManagedChannelBuilder
    .forTarget(&amp;quot;dns:///example.example.svc.cluster.local&amp;quot;)
    .defaultLoadBalancingPolicy(&amp;quot;round_robin&amp;quot;)
    .usePlaintext()
    .build();&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 설정 방식은 현재 experimental 상태로 운영환경에서 적용하기에는 리스크가 존재합니다.&lt;br&gt;&lt;a href=&quot;https://github.com/grpc/grpc-java/issues/1771&quot;&gt;https://github.com/grpc/grpc-java/issues/1771&lt;/a&gt;&lt;/p&gt;</description>
      <category>Infra</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/282</guid>
      <comments>https://kmseop.tistory.com/282#entry282comment</comments>
      <pubDate>Sun, 17 Nov 2024 14:44:19 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] Optimistic, Pessimistic Lock</title>
      <link>https://kmseop.tistory.com/281</link>
      <description>&lt;h1&gt;낙관적 락(Optimistic Lock)&lt;/h1&gt;
&lt;p&gt;낙관적 락은 사전에 테이블 로우에 락을 거는 방식이 아닌 충돌이 발생했을 경우에 대비하는 방식입니다.&lt;br&gt;테이블에 특정 컬럼을 추가하여 조회 시점의 값과 저장하려는 시점의 값이 동일한지 확인하여 충돌을 방지하도록 동작합니다.&lt;br&gt;이는 충돌 발생 빈도수가 낮은 상황에 적합하며 지속적인 락으로 인한 성능 저하를 막을 수 있습니다.&lt;/p&gt;
&lt;p&gt;아래는 Spring boot, JPA에서 낙관적 락을 적용하는 예시입니다.&lt;/p&gt;
&lt;h3&gt;Entity&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Entity
class UserEntity(
    id: Long,
    name: String,
    version: Int,
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = id
        protected set

    var name: String = name
        protected set

    @Version
    var version: Int = version
        protected set

    fun updateName(name: String) {
        this.name = name
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Service&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Service
class UserService(
    private val userEntityRepository: UserEntityRepository
) {
    @Transactional
    fun update(id: Long, name: String) {
        val user = userEntityRepository.findById(id)
            .orElseThrow { RuntimeException(&amp;quot;User not found&amp;quot;) }
        user.updateName(name)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;테스트 케이스&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;class UserServiceTest @Autowired constructor(
    private val userService: UserService
) {
    @Test
    fun `10명 동시접근시 낙관락 테스트`() {
        val executorService = Executors.newFixedThreadPool(10)
        val latch = CountDownLatch(10)
        for (i in 1..10) {
            executorService.submit {
                val name = (1..100).random().toString()
                try {
                    userService.update(1, name)
                } catch (e: Exception) {
                    println(e)
                } finally {
                    latch.countDown()
                }
            }
        }

        latch.await()
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;위 테스트 케이스는 10명의 사용자가 동시에 접근하여 리소스를 수정하려는 상황을 연출했습니다.&lt;br&gt;테스트를 실행시 의도한대로 총 9번의 ObjectOptimisticLockingFailureException이 발생했습니다&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value ma
pping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]
org.springframework.orm.ObjectOptimisticLockingFailureException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect) : [com.side.jobiss.persistence.entity.UserEntity#1]&lt;/code&gt;&lt;/pre&gt;&lt;h1&gt;비관적 락(Pessimistic Lock)&lt;/h1&gt;
&lt;p&gt;비관적 락은 변경하려는 테이블 로우에 락을 걸어 다른 트랜잭션에서 접근을 하지 못하도록 막아 충돌을 방지하는 방식입니다.&lt;br&gt;낙관적 락에 비해 설정에 따라 데이터에 접근조차 하지 못하여 성능상에서는 좋지 않습니다.&lt;br&gt;JPA에서 제공하는 비관적 락은 PESSIMISTIC_READ, PESSIMISTIC_WRITE가 있습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;PESSIMISTIC_READ: 공유락으로 락이 걸린 데이터 로우를 읽은 수 있지만 쓰기 작업은 불가능합니다.&lt;/li&gt;
&lt;li&gt;PESSIMISTIC_READ: 배타락으로 락이 걸린 데이터에 읽기, 쓰기 작업이 모두 불가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;아래는 비관적 락의 예시 코드입니다.&lt;/p&gt;
&lt;h3&gt;Entity&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Entity
class UserEntity(
    id: Long,
    name: String,
) {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    var id: Long = id
        protected set

    var name: String = name
        protected set

    fun updateName(name: String) {
        this.name = name
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Repository&lt;/h3&gt;
&lt;p&gt;공유락을 적용&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;interface UserEntityRepository : JpaRepository&amp;lt;UserEntity, Long&amp;gt; {
    @Lock(LockModeType.PESSIMISTIC_READ)
    @QueryHints(QueryHint(name = &amp;quot;jakarta.persistence.lock.timeout&amp;quot;, value = &amp;quot;10000&amp;quot;))
    fun findWithPessimisticLockById(id: Long): Optional&amp;lt;UserEntity&amp;gt;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Service&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;@Service
class UserService(
    private val userEntityRepository: UserEntityRepository
) {
    @Transactional
    fun update(id: Long, name: String) {
        val user = userEntityRepository.findWithPessimisticLockById(id)
            .orElseThrow { RuntimeException(&amp;quot;User not found&amp;quot;) }
        user.updateName(name)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;테스트 케이스&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-kotlin&quot;&gt;class UserServiceTest @Autowired constructor(
    private val userService: UserService
) {
    @Test
    fun `10명 동시접근시 비관락 테스트`() {
        val executorService = Executors.newFixedThreadPool(10)
        val latch = CountDownLatch(10)
        for (i in 1..10) {
            executorService.submit {
                val name = (1..100).random().toString()
                try {
                    userService.update(1, name)
                } catch (e: Exception) {
                    println(&amp;quot;$e&amp;quot;)
                } finally {
                    latch.countDown()
                }
            }
        }

        latch.await()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;낙관적 락과 동일하게 10명의 사용자가 동시에 접근하는 상황을 가정하였습니다.&lt;br&gt;기대했던 대로 10건중 9건의 요청은 CannotAcquireLockException이 발생하였습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]
org.springframework.dao.CannotAcquireLockException: could not execute statement [Deadlock found when trying to get lock; try restarting transaction] [update user_entity set name=?,version=? where id=?]; SQL [update user_entity set name=?,version=? where id=?]&lt;/code&gt;&lt;/pre&gt;</description>
      <category>JAVA/SPRING</category>
      <category>jpa</category>
      <category>Lock</category>
      <category>Spring Boot</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/281</guid>
      <comments>https://kmseop.tistory.com/281#entry281comment</comments>
      <pubDate>Sat, 10 Aug 2024 16:06:39 +0900</pubDate>
    </item>
    <item>
      <title>[Istio] External Authorization</title>
      <link>https://kmseop.tistory.com/280</link>
      <description>&lt;h1&gt;Istio Serive Mesh&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Istio는 쿠버네티스 클러스터 내에서 서비스 간의 통신을 제어하고 관리하는 서비스 메쉬로 트래픽 관리, 보안, 정책 적용 및 모니터링을 제공하여 마이크로서비스의 복잡성을 관리하는 데 도움을 줍니다.&lt;/p&gt;
&lt;h1&gt;External Authorization&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;External Authorization은 Istio의 보안 기능중에 하나로 인증 서비스와 연동하여 요청에 대한 엑세스 제어를 할 수 있도록 중앙에서 관리 해줍니다.&lt;br /&gt;External Authorization은 Istio 데이터 플레인에서 트래픽을 처리하는 프록시인 Envoy Filter를 사용하여 구현됩니다. Envoy는 요청을 인증 서비스로 경유시키도록 하고 인증 서비스에 의해서 액세스 가능 여부를 결정하게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-08-04 오후 4.19.35.png&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;1016&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B0YA9/btsIVk1hR09/Biry6kr4Cqv5YM5uoox9U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B0YA9/btsIVk1hR09/Biry6kr4Cqv5YM5uoox9U1/img.png&quot; data-alt=&quot;https://istio.io/v1.10/blog/2021/better-external-authz/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B0YA9/btsIVk1hR09/Biry6kr4Cqv5YM5uoox9U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB0YA9%2FbtsIVk1hR09%2FBiry6kr4Cqv5YM5uoox9U1%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;1894&quot; height=&quot;1016&quot; data-filename=&quot;스크린샷 2024-08-04 오후 4.19.35.png&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;1016&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://istio.io/v1.10/blog/2021/better-external-authz/&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구성 요소&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Authorization Service&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인증, 인가를 수행하는 서버로 Istio와 통합되어 요청의 액세스 가능 여부를 결정하게 됩니다. Http 또는 gRPC를 통해 Envoy와 통신하게 됩니다.&lt;br /&gt;Envoy 문서에서 제공하는 &lt;a href=&quot;https://www.envoyproxy.io/docs/envoy/latest/api-v3/service/auth/v3/external_auth.proto#envoy-v3-api-msg-service-auth-v3-checkrequest&quot;&gt;Proto&lt;/a&gt;를 구현체로 구현하여 인증, 인가를 수행합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Envoy Filter&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Envoy Filter는 클러스터로 들어온 요청을 인증 서비스를 거쳐가도록 경유 시킵니다.&lt;br /&gt;Envoy Filter 설정 예시는 아래와 같습니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: networking.istio.io/v1alpha3
kind: EnvoyFilter
metadata:
  name: ext-authz
  namespace: istio-system
spec:
  workloadSelector:
    labels:
      istio: ingressgateway
  configPatches:
    - applyTo: HTTP_FILTER
      match:
        context: GATEWAY
        listener:
          portNumber: 80
          filterChain:
            filter:
              name: &quot;envoy.filters.network.http_connection_manager&quot;
              subFilter:
                name: &quot;envoy.filters.http.router&quot;
      patch:
        operation: INSERT_BEFORE
        value:
          name: envoy.filters.http.ext_authz
          typed_config:
            &quot;@type&quot;: type.googleapis.com/envoy.extensions.filters.http.ext_authz.v3.ExtAuthz
            http_service:
              server_uri:
                uri: &quot;http://auth-server.default.svc.cluster.local:8000&quot; # 인증 서비스 경로
                cluster: &quot;outbound|8000||auth-server.default.svc.cluster.local&quot;
                timeout: 5s
              authorization_request:
                allowed_headers:
                  patterns:
                    - exact: &quot;x-request-id&quot;
                    - prefix: &quot;x-envoy&quot;
                    - prefix: &quot;x-auth&quot;
                    - exact: &quot;authorization&quot;
              authorization_response:
                allowed_upstream_headers:
                  patterns:
                    - exact: &quot;x-auth&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Istio Policy&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Istio의 AuthorizationPolicy 리소스를 사용하여 특정 조건 하에서 요청이 외부 권한 부여 서비스로 전달되도록 정의합니다.&lt;br /&gt;아래 설정은 example.com 도메인으로 ingress에 요청이 들어올 경우 인증 서비스를 거치도록 하는 예시입니다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: ext-authz
  namespace: default
spec:
  selector:
    matchLabels:
      app: istio-ingressgateway
  action: CUSTOM
  provider:
    name: ext-authz
  rules:
  - to:
    - operation:
      - Hosts
        example.com&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Infra</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/280</guid>
      <comments>https://kmseop.tistory.com/280#entry280comment</comments>
      <pubDate>Sun, 4 Aug 2024 15:58:35 +0900</pubDate>
    </item>
    <item>
      <title>[Kubernetes] 쿠버네티스 클러스터 구조</title>
      <link>https://kmseop.tistory.com/278</link>
      <description>&lt;h1&gt;K8S 구조&lt;/h1&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;951&quot; data-origin-height=&quot;561&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UZssI/btsGefJt6cU/Ixy8S51K1YkKFYa06o7Ryk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UZssI/btsGefJt6cU/Ixy8S51K1YkKFYa06o7Ryk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UZssI/btsGefJt6cU/Ixy8S51K1YkKFYa06o7Ryk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUZssI%2FbtsGefJt6cU%2FIxy8S51K1YkKFYa06o7Ryk%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;951&quot; height=&quot;561&quot; data-origin-width=&quot;951&quot; data-origin-height=&quot;561&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컨트롤 플레인&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;etcd&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;쿠버네티스 구성요소들의 상태 값, 설정등을 key-value 형태로 저장하는 데이터베이스 역할을 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kube-apiserver&lt;/h3&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;Rest API 호출 또는 kubectl, kubeadm 과 같은 cli를 사용하여 api server에 접근할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kube-scheduler&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;각 노드의 CPU, 메모리 등의 자원 상태를 체크하여 신규 컨테이너 배치를 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kube-controller-manager&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;워커 노드&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kubelet&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;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;kube-proxy&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;/ul&gt;</description>
      <category>Infra</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/278</guid>
      <comments>https://kmseop.tistory.com/278#entry278comment</comments>
      <pubDate>Sun, 31 Mar 2024 17:32:52 +0900</pubDate>
    </item>
    <item>
      <title>HTTPS의 동작 원리</title>
      <link>https://kmseop.tistory.com/277</link>
      <description>&lt;p&gt;HTTPS란 HTTP에 암호 계층(SSL/TLS)을 추가한 개념의 프로토콜입니다. HTTPS는 데이터를 암호화하여 전송하기 위해 인증서를 통해 신뢰할 수 있는 서버인지 확인하고 서로가 함께 알 수 있는 암호화 키를 사용하여 데이터를 암호화합니다.&lt;/p&gt;
&lt;h1&gt;SSL/TLS Handshake&lt;/h1&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트에서 사용 가능한 암호화/해시 알고리즘과 랜덤한 값, SSL/TLS 버전 정보를 서버에 전달합니다.&lt;/li&gt;
&lt;li&gt;서버는 받은 암호화/해시 방식 중 우선순위가 높은 알고리즘과 랜덤한 값, 자신을 증명하기 위한 인증서를 클라이언트에 전달합니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 서버의 인증서가 올바른지 확인하기 위해 저장되어있는 CA 공개키로 인증서를 복호화합니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 자신이 만든 랜덤한 값과 서버에서 내려준 랜덤한 값을 사용하여 premaster secret을 만들고 이를 서버에 전송합니다.&lt;/li&gt;
&lt;li&gt;서버는 premaster secret을 복호화하고 이를 master secret으로 저장합니다. 생성된 master secret으로 클라이언트와의 연결에 고유한 값을 부여하기 위한 세션키를 생성합니다. 이때 세션키는 대칭키 암호화에 사용되는 키로 클라이언트, 서버간의 주고받는 데이터를 암/복호화합니다.&lt;/li&gt;
&lt;li&gt;SSL/TLS 핸드셰이크를 완료&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;대칭키 암호화&lt;/h1&gt;
&lt;p&gt;대칭키 암호화는 서버와 클라이언트가 동일한 키를 사용하는 암호화 방식입니다.&lt;br&gt;클라이언트는 암호화키를 사용하여 데이터를 암호화하고 암호화된 데이터와 키를 서버에 전송하고 서버는 암호화된 데이터를 전달 받은 키로 복호화합니다.&lt;/p&gt;
&lt;h1&gt;비대칭키 암호화&lt;/h1&gt;
&lt;p&gt;비대칭키 암호화는 암호화와 복호화에 다른 키를 적용하는 방식입니다.&lt;br&gt;서버는 Public, Private 키쌍을 만듭니다. Private키는 서버 본인이 저장하고 Public키를 통신하는 클라이언트에 제공합니다. 클라이언트는 Public 키를 사용하여 데이터를 암호화하고 이를 서버에 전송합니다. 서버는 암호화된 데이터를 Private 키로 복호화합니다.&lt;/p&gt;</description>
      <category>Network</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/277</guid>
      <comments>https://kmseop.tistory.com/277#entry277comment</comments>
      <pubDate>Sat, 27 Jan 2024 15:40:20 +0900</pubDate>
    </item>
    <item>
      <title>브라우저에 URL을 입력하면 어떤 동작을 하는가</title>
      <link>https://kmseop.tistory.com/276</link>
      <description>&lt;h1&gt;1. URL 파싱&lt;/h1&gt;
&lt;p&gt;URL을 입력받은 브라우저는 URL을 파싱하여 어떠한 프로토콜, 도메인, 포트로 구성된 구조인지 해석합니다.&lt;/p&gt;
&lt;h1&gt;2. DNS 기록 확인&lt;/h1&gt;
&lt;p&gt;브라우저는 연결할 서버를 파악하기 위해 도메인으로 IP 주소를 확인하는 과정을 거칩니다.&lt;br&gt;이때 IP 주소를 확인하기 위해 먼저 캐시된 DNS 기록을 확인하는데 순서대로 브라우저의 캐시, OS 캐시, 라우터 캐시, ISP 캐시에서 캐시된 DNS 기록을 확인합니다.&lt;br&gt;캐시된 DNS 기록이 없다면 ISP가 DNS 서버에 질의를 하여 IP 주소를 찾습니다. 이때 재귀적 질의를 통해 최상위 DNS 서버 까지 질의하게 될 수 있습니다.&lt;/p&gt;
&lt;h1&gt;3. 서버와의 TCP 연결&lt;/h1&gt;
&lt;p&gt;서버의 IP 주소를 확인 후 서버와 데이터 패킷 전송을 위해 TCP 연결을 하게 됩니다.&lt;br&gt;연결은 3-way handshake 과정을 통해 이루어지고 동작방식은 다음과 같습니다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;클라이언트는 서버에 SYN 패킷을 전송하여 연결이 가능한지 확인합니다.&lt;/li&gt;
&lt;li&gt;서버에서 연결을 할 수 있는 포트가 있다면 클라이언트의 SYN 패킷에 대한 응답으로 SYN/ACK 패킷을 전송합니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 서버로 SYN/ACK 패킷을 수신하고 ACK 패킷을 전송하여 연결을 완료합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;4. 서버에 HTTP 요청&lt;/h1&gt;
&lt;p&gt;TCP 연결이 완료되었다면 서버에 데이터를 전송을 하기 위해 HTTP 요청을 하게 되고 HTTP 요청에는 요청 라인, 헤더, 본문이 포함됩니다.&lt;/p&gt;
&lt;h1&gt;5. 서버의 응답&lt;/h1&gt;
&lt;p&gt;서버에서 받은 요청을 처리 후 클라이언트에 응답을 전송합니다.&lt;/p&gt;
&lt;h1&gt;6. 브라우저 렌더링&lt;/h1&gt;
&lt;p&gt;서버로부터 응답받은 컨텐츠를 통해 브라우저를 렌더링합니다.&lt;/p&gt;</description>
      <category>Network</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/276</guid>
      <comments>https://kmseop.tistory.com/276#entry276comment</comments>
      <pubDate>Sat, 27 Jan 2024 14:25:25 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 클러스터링(Clustering)</title>
      <link>https://kmseop.tistory.com/275</link>
      <description>&lt;h1&gt;클러스터링&lt;/h1&gt;
&lt;p&gt;클러스터링은 동일한 데이터베이스를 여러대의 서버가 관리하도록 수평적인 구조로 분산 환경을 구성하여 Failover 시스템을 구축할 수 있습니다.&lt;br&gt;데이터베이스 서버의 상태가 1개라도 Active 상태이면 장애 없이 서비스를 운영할 수 있습니다. 또한 여러대의 데이터베이스 서버에 로드 밸런싱을 통해 부하 분산도 가능합니다.&lt;/p&gt;
&lt;h2&gt;동작 방식&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Active Active&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;여러대의 데이터베이스 서버를 모두 Active 상태로 두는 방식으로 서버 하나에 장애가 발생하더라도 다른 서버가 역할을 바로 대행할 수 있어 서비스의 중단을 방지할 수 있습니다.&lt;/li&gt;
&lt;li&gt;하나의 데이터베이스를 공유하게 된다면 병목이 발생할 수 있습니다.&lt;/li&gt;
&lt;li&gt;활성 상태의 서버를 동시에 여러대를 운영한다면 비용이 증가하게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;ol start=&quot;2&quot;&gt;
&lt;li&gt;Active StandBy&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Active 상태인 서버와 Stand by 상태인 서버를 나누어 동작하는 방식으로 Stand by 서버는 대기 중에 있다가 Active 서버에 문제가 발생했을 경우 Active 상태로 전환하는 방식으로 동작합니다.&lt;/li&gt;
&lt;li&gt;Active Active 방식에 비해 비용적인 측면에서 이점이 있습니다.&lt;/li&gt;
&lt;li&gt;Stand by 에서 Active 상태로 전환되는 시간 동안은 정상적인 서비스 운영은 불가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwQApB/btsDLq6LqIP/Pxraz02aOltcWwJbk6Xj2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwQApB/btsDLq6LqIP/Pxraz02aOltcWwJbk6Xj2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwQApB/btsDLq6LqIP/Pxraz02aOltcWwJbk6Xj2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwQApB%2FbtsDLq6LqIP%2FPxraz02aOltcWwJbk6Xj2k%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DB</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/275</guid>
      <comments>https://kmseop.tistory.com/275#entry275comment</comments>
      <pubDate>Sun, 21 Jan 2024 16:23:31 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 동적 계획법 (Dynamic Programming)</title>
      <link>https://kmseop.tistory.com/274</link>
      <description>&lt;h1&gt;동적 계획법이란?&lt;/h1&gt;
&lt;p&gt;하나의 큰 문제를 여러개의 작은 단위의 문제로 나누고 작은 단위의 문제를 먼저 해결 후 결과를 저장한 뒤 더 큰 단위의 문제를 해결할 때 재활용하여 사용됩니다.&lt;/p&gt;
&lt;p&gt;동적 계획법은 재귀 함수와 동작 방식이 유사합니다. 하지만 재귀 함수는 작은 단위의 문제 해결이 여러번 반복 될 수 있는 구조입니다. 대표적인 예시인 피보나치 수열로 보면 점화식은 &lt;code&gt;f(n) = f(n - 1) + f(n-2)&lt;/code&gt; 이지만 f(n - 1), f(n-2)에서 각 함수를 한번씩 더 호출하면 동일한 값을 2번씩 구하게 되고 그로인해 피보나치 수를 구하기 위한 함수의 호출 횟수는 기하급수 적으로 증가하게 되고 시간 복잡도는 &lt;code&gt;O(n^2)&lt;/code&gt;가 됩니다.&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJkiKk/btsC51OhEuS/KUUkNCj3ysXbZrXkQEqh3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJkiKk/btsC51OhEuS/KUUkNCj3ysXbZrXkQEqh3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJkiKk/btsC51OhEuS/KUUkNCj3ysXbZrXkQEqh3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJkiKk%2FbtsC51OhEuS%2FKUUkNCj3ysXbZrXkQEqh3K%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;동적 계획법을 사용하게 된다면 작은 단위의 문제를 저장하게 되어 중복된 계산을 방지할 수 있고 시간복잡도는 &lt;code&gt;O(n)&lt;/code&gt;으로 해결할 수 있습니다.&lt;/p&gt;
&lt;h1&gt;조건&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;작은 문제에서 반복이 발생&lt;/li&gt;
&lt;li&gt;같은 문제는 항상 정답이 같음&lt;br&gt;위 두 조건이 만족된다면 메모이제이션을 통해 큰 문제를 해결 가능합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;구현 방법&lt;/h1&gt;
&lt;h3&gt;Top Down&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;  private static int[] memoization;

  public static int getFibonacci(int n) {
      memoization = new int[n];
      memoization[0] = 1;
      memoization[1] = 1;
      return dp(n);
  }

  private static int dp(int n) {
      if (memoization[n] != 0) {
          return memoization[n];
      }
      memoization[n] = dp(n - 1) + dp(n - 2);
      return memoization[n];
  }&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Bottom Up&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;    private static int[] memoization;

    public static int getFibonacci(int n) {
        memoization = new int[n];
        memoization[0] = 1;
        memoization[1] = 1;

        for (var i = 2; i &amp;lt; n; i++) {
            memoization[i] = memoization[i - 1] + memoization[i - 2];
        }

        return memoization[n - 1];
    }&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Algorithm</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/274</guid>
      <comments>https://kmseop.tistory.com/274#entry274comment</comments>
      <pubDate>Sat, 6 Jan 2024 12:06:25 +0900</pubDate>
    </item>
    <item>
      <title>[Algorithm] 이진 탐색(Binary search)</title>
      <link>https://kmseop.tistory.com/273</link>
      <description>&lt;h1&gt;이진 탐색이란?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;이진 탐색은 정렬된 리스트에서 검색 범위를 절반씩 줄여 나가며 검색 데이터를 탐색하는 알고리즘입니다.&lt;/li&gt;
&lt;li&gt;정렬된 리스트에서만 사용할 수 있다는 단점이 있지만 검색이 반복될 때마다 검색 범위가 절반으로 줄기 때문에 속도가 빠르다는 장점이 있습니다.&lt;/li&gt;
&lt;li&gt;찾으려는 데이터와 중간점 위치에 있는 데이터를 반복적으로 비교하여 원하는 데이터를 찾는 방식으로 동작합니다.&lt;ol&gt;
&lt;li&gt;배열의 중간 데이터를 비교&lt;/li&gt;
&lt;li&gt;중간 데이터와 검색 데이터를 비교&lt;ol&gt;
&lt;li&gt;중간 데이터가 검색 데이터와 같다면 종료&lt;/li&gt;
&lt;li&gt;중간 데이터보다 검색 데이터가 크다면 중간 데이터 기준 배열의 오른쪽 구간을 대상으로 탐색&lt;/li&gt;
&lt;li&gt;중간 데이터보다 검색 데이터가 작다면 중간 데이터 기준 배열의 왼쪽 구간을 대상으로 탐색&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;데이터를 찾을 때까지 2번 작업을 반복&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;예제&lt;/h3&gt;
&lt;p&gt;key = 23&lt;/p&gt;
&lt;p&gt;1. Low = 10, Mid = 21, High = 42&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;binary-search.drawio.png&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ibe2g/btsCP5PZ7oR/m1palAkaH5AQ8lq9LuzMNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ibe2g/btsCP5PZ7oR/m1palAkaH5AQ8lq9LuzMNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ibe2g/btsCP5PZ7oR/m1palAkaH5AQ8lq9LuzMNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIbe2g%2FbtsCP5PZ7oR%2Fm1palAkaH5AQ8lq9LuzMNK%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;421&quot; height=&quot;108&quot; data-filename=&quot;binary-search.drawio.png&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;2. Low = 23, Mid = 33, High = 42&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;binary-search.drawio (1).png&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mscut/btsCQ5oGN7O/QniZU4fl0YBLqM7xtpsN20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mscut/btsCQ5oGN7O/QniZU4fl0YBLqM7xtpsN20/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mscut/btsCQ5oGN7O/QniZU4fl0YBLqM7xtpsN20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMscut%2FbtsCQ5oGN7O%2FQniZU4fl0YBLqM7xtpsN20%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;421&quot; height=&quot;108&quot; data-filename=&quot;binary-search.drawio (1).png&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;3. Low = 23, Mid = 23, High = 23 (탐색 완료)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;binary-search.drawio (2).png&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0kZfT/btsCK8f0dRX/vVilgpO9GtEBDnEhuoIVOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0kZfT/btsCK8f0dRX/vVilgpO9GtEBDnEhuoIVOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0kZfT/btsCK8f0dRX/vVilgpO9GtEBDnEhuoIVOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0kZfT%2FbtsCK8f0dRX%2FvVilgpO9GtEBDnEhuoIVOK%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;421&quot; height=&quot;108&quot; data-filename=&quot;binary-search.drawio (2).png&quot; data-origin-width=&quot;421&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;시간 복잡도&lt;/h3&gt;
&lt;p&gt;이진 탐색을 반복할수록 남아있는 자료의 개수는 절반이 됩니다. (N/2, 1/2 * N/2, 1/2 * 1/2 * N/2 ...)&lt;br&gt;&lt;code&gt;K번의 탐색 = (1/2)^K * N&lt;/code&gt;이되고 &lt;code&gt;K = log2N&lt;/code&gt;입니다.&lt;br&gt;따라서 시간복잡도는 O(logN)입니다.&lt;/p&gt;
&lt;h3&gt;구현 (Java)&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;p&gt;반복문&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public static int binarySearch(int[] arr, int key) {
var low = 0;
var high = arr.length - 1;

while (low &amp;lt;= high) {
 var mid = (high + low) / 2;
 if (arr[mid] == key) {
   return mid;
 } else if (arr[mid] &amp;gt; key) {
   low = mid + 1;
 } else {
   high = mid - 1;
 }
}

return -1;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;재귀&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public static int binarySearch(int[] arr, int low, int high, int key) {
if (high &amp;lt; low) {
 return -1;
}

var mid = (high + low) / 2;
if (arr[mid] == key) {
 return mid;
} else if (arr[mid] &amp;gt; key) {
 low = mid + 1;
} else {
 high = mid - 1;
}

return binarySearch(arr, low, high, key);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Algorithm</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/273</guid>
      <comments>https://kmseop.tistory.com/273#entry273comment</comments>
      <pubDate>Thu, 28 Dec 2023 16:23:19 +0900</pubDate>
    </item>
    <item>
      <title>[자료구조] 힙(Heap)</title>
      <link>https://kmseop.tistory.com/272</link>
      <description>&lt;h1&gt;힙이란?&lt;/h1&gt;
&lt;p&gt;데이터에서 최대값과 최솟값을 빠르게 찾기 위해 완전 이진 트리로 구현된 자료구조입니다. 부모 노드가 가진 값이 자식 노드의 값보다 작은 힙을 Min Heap, 부모 노드가 가진 값이 자식 노드보다 큰 힙을 Max Heap 이라고 합니다. 이때 부모노드의 값은 자식 노드보다 무조건 작거나 커야합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lhLez/btsCALZWeFd/KO6SR4F2cOAx10tljswqv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lhLez/btsCALZWeFd/KO6SR4F2cOAx10tljswqv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lhLez/btsCALZWeFd/KO6SR4F2cOAx10tljswqv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlhLez%2FbtsCALZWeFd%2FKO6SR4F2cOAx10tljswqv0%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;371&quot; height=&quot;291&quot; data-origin-width=&quot;371&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;위 이미지는 Max Heap의 예시입니다. 루트 노드의 값이 가장 크고 리프 노드를 제외한 자식 노드들 또한 본인의 자식 노드 보다 큰 값을 가지고 있습니다.&lt;br&gt;이러한 특성을 활용하여 가장 크거나 가장 작은 값을 찾아내기에 적합한 자료구조라고 볼 수 있습니다.&lt;/p&gt;
&lt;h1&gt;힙의 동작 방식&lt;/h1&gt;
&lt;h2&gt;데이터 삽입&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;힙은 완전 이진 트리이기 때문에 최하단 노드부터 데이터가 채워집니다.&lt;/li&gt;
&lt;li&gt;추가된 노드와 부모 노드의 값을 비교 후 자식 노드가 크다면 서로의 위치를 바꿉니다.&lt;/li&gt;
&lt;li&gt;부모 노드가 더 클때까지 2번 작업을 반복합니다.&lt;br&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1431&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIwKw0/btsCzR0rBCu/86dOHE8F2P6SAIWQAbmubk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIwKw0/btsCzR0rBCu/86dOHE8F2P6SAIWQAbmubk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIwKw0/btsCzR0rBCu/86dOHE8F2P6SAIWQAbmubk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIwKw0%2FbtsCzR0rBCu%2F86dOHE8F2P6SAIWQAbmubk%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;1431&quot; height=&quot;291&quot; data-origin-width=&quot;1431&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;시간 복잡도&lt;/h3&gt;
&lt;p&gt;노드를 추가하는 작업의 최악의 상황은 추가된 노드를 루트 노드까지 비교하는 경우가 최악의 상황일 것 입니다. 이때는 트리의 높이만큼의 비교를 행하게 되고 그에 따라 시간복잡도는 &lt;code&gt;O(logN)&lt;/code&gt;입니다.&lt;/p&gt;
&lt;h2&gt;데이터 삭제&lt;/h2&gt;
&lt;p&gt;힙에서 데이터가 삭제는 루트(최대 또는 최소)노드를 삭제하는 케이스 밖에 없습니다. 이때 힙의 형태를 갖추도록 하는 과정은&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;최하위 마지막 자식 노드를 루트로 이동합니다.&lt;/li&gt;
&lt;li&gt;자식 노드와 값을 비교 후 자식 노드가 더 크다면 서로의 위치를 변경합니다.&lt;/li&gt;
&lt;li&gt;자식 노드의 값이 작을 때 까지 2번 작업을 반복합니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;100%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbBSB2/btsCELrRKrY/u68RrPLaOsGsPMihcBzPRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbBSB2/btsCELrRKrY/u68RrPLaOsGsPMihcBzPRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbBSB2/btsCELrRKrY/u68RrPLaOsGsPMihcBzPRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbBSB2%2FbtsCELrRKrY%2Fu68RrPLaOsGsPMihcBzPRK%2Fimg.png&quot; width=&quot;100%&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;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3&gt;시간 복잡도&lt;/h3&gt;
&lt;p&gt;위 이미지를 보먄 삭제 또한 삽입과 유사한 방식으로 동작하는 것을 확인할 수 있습니다. 삭제의 최악의 상황은 삽입과 마찬가지로 루트부터 최하단까지 값을 비교하는 케이스가 최악의 경우라고 볼 수 있습니다. 따라서 시간 복잡도는 &lt;code&gt;O(logN)&lt;/code&gt;이 됩니다.&lt;/p&gt;
&lt;h2&gt;활용&lt;/h2&gt;
&lt;p&gt;힙의 특성을 활용하여 우선순위 큐를 구현할 수 있습니다. 일반적인 큐는 FIFO로 먼저 들어온 데이터가 먼저 나간다는 컨셉을 갖고 있지만 우선순위 큐는 들어온 순서와는 관계없이 최대 또는 최소 값을 가진 데이터가 먼저 나가는 컨셉을 갖고 있습니다. 힙을 사용하여 우선순위 큐를 구현한다면 데이터 탐색의 시간 복잡도는 &lt;code&gt;O(1)&lt;/code&gt;로 가장 효율적일 것입니다.&lt;/p&gt;</description>
      <category>Data Structure</category>
      <author>KMSEOP</author>
      <guid isPermaLink="true">https://kmseop.tistory.com/272</guid>
      <comments>https://kmseop.tistory.com/272#entry272comment</comments>
      <pubDate>Wed, 27 Dec 2023 13:57:48 +0900</pubDate>
    </item>
  </channel>
</rss>