<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Gy.log</title>
        <link>https://velog.io/</link>
        <description>한결같이</description>
        <lastBuildDate>Wed, 03 Dec 2025 16:09:45 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <copyright>Copyright (C) 2019. Gy.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/uni_gy" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[멀티레벨캐시 설계]]></title>
            <link>https://velog.io/@uni_gy/%EB%A9%80%ED%8B%B0%EB%A0%88%EB%B2%A8%EC%BA%90%EC%8B%9C-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@uni_gy/%EB%A9%80%ED%8B%B0%EB%A0%88%EB%B2%A8%EC%BA%90%EC%8B%9C-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Wed, 03 Dec 2025 16:09:45 GMT</pubDate>
            <description><![CDATA[<h1 id="주문-서비스-멀티레벨-캐시">주문 서비스 멀티레벨 캐시</h1>
<h2 id="캐싱-대상">캐싱 대상</h2>
<ul>
<li>읽기 비율이 압도적으로 높은 데이터</li>
</ul>
<p>사용자 프로필: userId, 이름, 소속, 슬랙ID, 전화번호, 등급</p>
<p>상품 정보: 상품Id, 허브Id, 상품명</p>
<h2 id="캐시-구조">캐시 구조</h2>
<p>1차 캐시 L1: Caffeine(in-memory)</p>
<p>2차 캐시 L2: Redis(공유 캐시)</p>
<h3 id="조회-흐름">조회 흐름</h3>
<p>사용자 프로필: L1→ L2 → UserService API</p>
<p>상품 정보: L1→ L2 → HubService API</p>
<h2 id="캐시-관리-전략">캐시 관리 전략</h2>
<h3 id="캐시-키-설계">캐시 키 설계</h3>
<ul>
<li><code>user:profile:{userId}</code><ul>
<li>userId, 이름, 소속, 슬랙ID, 전화번호, 등급</li>
</ul>
</li>
<li><code>product:{productId}</code><ul>
<li>상품Id, 허브Id, 상품명, 업체명, 가격(X)</li>
</ul>
</li>
</ul>
<h3 id="만료-전략">만료 전략</h3>
<p>정상 캐시</p>
<p>L1: TTL 5분, TTI 2분30초</p>
<p>L2: TTL 20분</p>
<p>Negative Cache </p>
<p>L1: TTL 15초, TTI  7초 (정상 캐시의 5%)</p>
<p>L2: TTL 1분 (정상 캐시의 5%)</p>
<h3 id="읽기-전략">읽기 전략</h3>
<ul>
<li><strong>Cache Aside</strong>: 요청 시 캐시 조회, 없으면 DB 조회 후 캐시 저장</li>
</ul>
<h3 id="쓰기-전략">쓰기 전략</h3>
<ul>
<li><strong>Write Around</strong>: 사용자/상품 데이터 변경은 데이터 소유 서비스에서 처리, Order는 캐시만 관리</li>
<li>캐시에 저장 시 L1+L2 모두 저장</li>
<li>사용자, 상품 데이터 변경 시 이벤트 발행 → Order 서비스에서 구독 L1,L2 모두 캐시 삭제</li>
</ul>
<h3 id="1차-설계">1차 설계</h3>
<ul>
<li>Redis pub/sub invalidation으로 L1 캐시 갱신(<a href="https://channel.io/ko/team/blog/articles/tech-distributed-cache-1-67a392c5">https://channel.io/ko/team/blog/articles/tech-distributed-cache-1-67a392c5</a>)
<img src="https://velog.velcdn.com/images/uni_gy/post/f7b7070b-2dfb-4918-b98b-654bea03e28e/image.png" alt=""><h3 id="2차-설계">2차 설계</h3>
</li>
</ul>
<p>Redis Pub/Sub은 메시지 유실 가능성이 있어 kafka로 변경
<img src="https://velog.velcdn.com/images/uni_gy/post/48b47122-cd5c-42f9-b815-d54ce035b5b1/image.png" alt=""></p>
<h2 id="장애실패-시나리오">장애/실패 시나리오</h2>
<h3 id="redis-장애-시">Redis 장애 시</h3>
<ul>
<li>L1 miss → L2(REDIS) 실패 → 사용자/상품 서비스 API 바로 호출</li>
<li>캐시 기능은 비활성에 가깝지만 기능은 정상 동작</li>
</ul>
<h3 id="userservice--hubservice-장애-시">UserService / HubService 장애 시</h3>
<ul>
<li>L1/L2 모두 miss → UserService/HubService 호출 실패</li>
<li><strong>404 NotFound:</strong> null 반환 + 네거티브 캐시 저장</li>
<li><strong>5xx / 타임아웃 / 네트워크 장애 등</strong>: ExternalApiException 예외 던짐</li>
</ul>
<h3 id="이벤트-누락-시">이벤트 누락 시</h3>
<ul>
<li>TTL로 만료 기간을 둬서 안전 장치</li>
</ul>
<h3 id="캐시-스탬피드cache-stampede-방지">캐시 스탬피드(Cache Stampede) 방지</h3>
<ul>
<li><strong>캐시 스탬피드</strong>: key(캐시 항목)가 만료되는 순간 다수의 요청이 동시에 캐시를 갱신하려고 하면서 발생하는 문제 (<a href="https://jhzlo.tistory.com/69">https://jhzlo.tistory.com/69</a>)</li>
<li>문제점: DB 부하가 급증하고 시스템 응답 시간이 느려지거나 장애가 발생</li>
<li>대응<ul>
<li>TTL에 랜덤 지터(±10~20%) 적용해서 동시 만료 방지</li>
<li>L2 캐시에서 PER(Probabilistic Early Refresh) 알고리즘 적용 : TTL 만료되기 전에 확률적으로 데이터를 갱신하는 기법<img src="https://velog.velcdn.com/images/uni_gy/post/4193e21a-854e-455e-ba68-98a3767d8c18/image.png" alt=""><h3 id="stale-cache">Stale Cache</h3>
</li>
</ul>
</li>
</ul>
<p>L1 miss → L2 캐시 조회 하는 상황에서 만약 L2 캐시의 TTL이 만료 직전인 stale 데이터라면?</p>
<pre><code>threshold 2분30초 ( L1 캐시의 TTL 시간의 50% )
L2의 데이터가 TTL &lt;= threshold 이면 DB 조회해서 새로 갱신</code></pre><h3 id="negative-caching">Negative Caching</h3>
<p>존재하지 않는 데이터(= null, empty)도 캐시에 넣어서, ‘없는 데이터’ 조회가 반복될 때 원본(DB, 외부 API)을 계속 호출하지 않도록 막는 기법</p>
<p>TTL 시간 정상 캐시의 10%</p>
<p>L1: 30초</p>
<p>L2: 2분</p>
<h2 id="모니터링-알람">모니터링/ 알람</h2>
<h3 id="모니터링-지표">모니터링 지표</h3>
<ul>
<li>L1,L2 캐시 히트율</li>
<li>Redis 지표: 메모리 사용량, 명령어 QPS</li>
<li>Redis pub/sub 이벤트 소비 상태 -&gt; kafka 이벤트 소비 상태</li>
</ul>
<h3 id="알람">알람</h3>
<ul>
<li>L1/L2 hit ratio가 특정 값 이하로 떨어질 때</li>
</ul>
<p>참고자료 : <a href="https://bcuts.tistory.com/185">https://bcuts.tistory.com/185</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Shedlock을 사용해서 Scheduler 중복 실행 방지하기]]></title>
            <link>https://velog.io/@uni_gy/Shedlock%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-Scheduler-%EC%A4%91%EB%B3%B5-%EC%8B%A4%ED%96%89-%EB%B0%A9%EC%A7%80%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@uni_gy/Shedlock%EC%9D%84-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%84%9C-Scheduler-%EC%A4%91%EB%B3%B5-%EC%8B%A4%ED%96%89-%EB%B0%A9%EC%A7%80%ED%95%98%EA%B8%B0</guid>
            <pubDate>Mon, 10 Nov 2025 06:47:23 GMT</pubDate>
            <description><![CDATA[<p>멀티 서버, 멀티 스레드 환경에서도 스케줄러를 하나만 실행하기 위해 Shedlock을 사용했습니다.</p>
<h2 id="필요-의존성">필요 의존성</h2>
<pre><code class="language-gradle">implementation &#39;net.javacrumbs.shedlock:shedlock-spring:6.10.0&#39;
implementation &#39;net.javacrumbs.shedlock:shedlock-provider-redis-spring:6.10.0&#39;</code></pre>
<h2 id="schedulingconfig">SchedulingConfig</h2>
<pre><code class="language-java">@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtLeastFor = &quot;10s&quot;, defaultLockAtMostFor = &quot;90s&quot;) //Lock 이 유지되는 최소,최대 시간
public class SchedulingConfig {

    @Value(&quot;${spring.profiles.active}&quot;)
    private String profile;

    @Value(&quot;${spring.application.name}&quot;)
    private String appName;

    @Bean
    public LockProvider lockProvider(RedisConnectionFactory redisConnectionFactory) {
        String prefix = appName + &quot;:&quot; + profile;
        return new RedisLockProvider(redisConnectionFactory, prefix);
    }
}</code></pre>
<p>defaultLockAtLeastFor : Lock을 유지하는 최소 시간 기본 설정
defaultLockAtMostFor : Lock을 유지하는 최대 시간 기본 설정
해당 코드는 RedisLockProvider를 사용하고 있습니다.</p>
<h2 id="scheduler">Scheduler</h2>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
@Slf4j
public class HubRouteInfoScheduler {

    private final HubRepository hubRepository;
    private final HubRouteInfoRepository routeInfoRepository;
    private final HubService hubService;
    private final HubRouteInfoRepository hubRouteInfoRepository;

    //기본 설정 2시간마다
    @Scheduled(
        fixedRateString = &quot;${scheduler.route-infos.fixed-rate}&quot;,
        initialDelayString = &quot;${scheduler.route-infos.initial-delay}&quot;
    )
    // 멀티 서버, 멀티 스레드 환경에서도 스케줄러를 하나만 실행 / lock 유지 최소, 최대 시간
    @SchedulerLock(
        name = &quot;generateMissingHubRouteInfos&quot;,
        lockAtLeastFor = &quot;${shedlock.route-infos.at-least}&quot;,
        lockAtMostFor  = &quot;${shedlock.route-infos.at-most}&quot;
    )
    public void generateMissingRoutes() {
        log.info(&quot;[{}] HubRouteInfoScheduler started at {}&quot;, LocalDateTime.now(), Thread.currentThread().getName());
        long start = System.currentTimeMillis();
        int createdCount = 0;

        List&lt;UUID&gt; hubIds = hubRepository.getActiveHubIds();
        if (hubIds.size() &lt; 2) return;

        Set&lt;RoutePairDto&gt; existing = new HashSet&lt;&gt;(routeInfoRepository.findExistingParisIn(hubIds));

        // 전체 순서쌍(출발!=도착) 생성 → 차집합으로 미존재 목록 계산
        List&lt;RoutePairDto&gt; missing = new ArrayList&lt;&gt;();
        for (UUID dep : hubIds) {
            for (UUID arr : hubIds) {
                if (!dep.equals(arr)) {
                    RoutePairDto pair = new RoutePairDto(dep, arr);
                    if (!existing.contains(pair)) {
                        missing.add(pair);
                    }
                }
            }
        }
        log.debug(&quot;missing size : {}&quot;, missing.size());

        Map&lt;UUID, Hub&gt; hubCache=hubService.getHubByIds(hubIds).stream()
            .collect(Collectors.toMap(Hub::getHubId, Function.identity()));

        for (RoutePairDto pair : missing) {
            try {
                Hub departureHub = hubCache.get(pair.departureId());
                Hub arrivalHub = hubCache.get(pair.arrivalId());

                Long durationMin = DistanceTimeUtil.estimateDurationMinutes(departureHub.getLatitude(),departureHub.getLongitude(),arrivalHub.getLatitude(),arrivalHub.getLongitude());
                Double distanceKm = DistanceTimeUtil.calculateDistanceKm(departureHub.getLatitude(),departureHub.getLongitude(),arrivalHub.getLatitude(),arrivalHub.getLongitude());

                HubRouteInfo routeInfo = HubRouteInfo.create(pair.departureId(),pair.arrivalId(),durationMin,distanceKm);
                hubRouteInfoRepository.save(routeInfo);
                createdCount++;
            } catch (BusinessException e) {
                log.warn(&quot;ErrorCode : {}, Message: {}&quot;,e.getErrorCode(),e.getMessage());
            } catch (DataIntegrityViolationException e) {
                log.warn(&quot;유니크 제약 조건 위반 departureId: {}, arrivalId: {}&quot;,pair.departureId(),pair.arrivalId());
            } catch (Exception e) {
                 log.warn(&quot;Route create failed: {} -&gt; {}&quot;, pair.departureId(), pair.arrivalId(), e);
            }
        }

        long elapsed = System.currentTimeMillis() - start;
        log.info(&quot;[HubRouteInfoScheduler] finished at {} — created {} new routes in {} ms&quot;,
            LocalDateTime.now(), createdCount, elapsed);
    }
}</code></pre>
<p>@SchedulerLock 어노테이션을 통해 shedlock을 적용할 수 있습니다.</p>
<h2 id="참고">참고</h2>
<ul>
<li><a href="https://github.com/lukas-krecan/ShedLock">https://github.com/lukas-krecan/ShedLock</a></li>
<li>Redis, JDBC, Mongo 등의 많은 Provider 를 제공하고 있다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[toString 순환참조 문제, Entity @Setter 위험성]]></title>
            <link>https://velog.io/@uni_gy/toString-%EC%88%9C%ED%99%98%EC%B0%B8%EC%A1%B0-%EB%AC%B8%EC%A0%9C-Entity-Setter-%EC%9C%84%ED%97%98%EC%84%B1</link>
            <guid>https://velog.io/@uni_gy/toString-%EC%88%9C%ED%99%98%EC%B0%B8%EC%A1%B0-%EB%AC%B8%EC%A0%9C-Entity-Setter-%EC%9C%84%ED%97%98%EC%84%B1</guid>
            <pubDate>Tue, 30 Sep 2025 12:02:05 GMT</pubDate>
            <description><![CDATA[<h3 id="tostring-순환-참조-문제">@toString 순환 참조 문제</h3>
<p>양방향 연관관계에서 toString()이 양쪽 필드 모두 출력하려 하면 무한 순환을 통해 StackOverflowError가 발생한다.</p>
<pre><code class="language-java">@Getter
@ToString(onlyExplicitlyIncluded = true)
@Entity
class Member {
  @Id Long id;
  @ToString.Include String name;

  @OneToMany(mappedBy = &quot;member&quot;)
  @ToString.Exclude               // 🔴 순환 방지
  private List&lt;Order&gt; orders = new ArrayList&lt;&gt;();
}</code></pre>
<h3 id="entity에-setter를-달면-안되는-이유">Entity에 @Setter를 달면 안되는 이유</h3>
<ol>
<li>양방향 연관관계 일관성이 깨진다.<ul>
<li>전용 메서드로 두 쪽을 함께 갱신해야 안전</li>
</ul>
</li>
<li>식별자/감사 필드 변경 가능성<ul>
<li>id(PK) 변경으로 DB 무결성이 깨질 수 있다.</li>
<li>createdAt, modifiedAt 등 감사 로그 왜곡</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[250924 분산추적]]></title>
            <link>https://velog.io/@uni_gy/250924-%EB%B6%84%EC%82%B0%EC%B6%94%EC%A0%81</link>
            <guid>https://velog.io/@uni_gy/250924-%EB%B6%84%EC%82%B0%EC%B6%94%EC%A0%81</guid>
            <pubDate>Wed, 24 Sep 2025 06:51:36 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-security-61-이후-변경점">Spring Security 6.1 이후 변경점</h3>
<pre><code>.authorizeRequests() → .authorizeHttpRequests() 
.antMatchers() → .requestMatchers()
.access(&quot;hasAnyRole(&#39;ROLE_A&#39;,&#39;ROLE_B&#39;)&quot;) → .hasAnyRole(&quot;A&quot;, &quot;B&quot;)</code></pre><p>메서드 체이닝이 사라지고 람다식을 통해 함수형으로 설정</p>
<h2 id="분산-추적">분산 추적</h2>
<p>Metric : 상태, 성능, 안정성을 나타내는 지표</p>
<h3 id="micrometer">Micrometer</h3>
<ul>
<li>Spring 기반 애플리케이션에서 메트릭을 수집하고 모니터링하기 위한 라이브러리</li>
<li>다양한 메트릭 수집, 유연한 연동(Prometheus, Grafana)<ol>
<li>수집된 메트릭을 보관할 DB ( = 프로메테우스 )</li>
<li>수집된 메트릭을 시각화 할 대시보드 ( = 그라파나 )</li>
</ol>
</li>
<li>추적 기능</li>
</ul>
<h3 id="zipkin">Zipkin</h3>
<ul>
<li>트레이스 데이터: 개별 요청(트랜잭션)의 흐름을 따라가는 데이터</li>
<li>트레이스 데이터를 수집하고 시각화하는 분산 추적 시스템</li>
<li>시각화, 검색 및 필터링 제공</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[250923 서킷브레이커, API gateway 필터링]]></title>
            <link>https://velog.io/@uni_gy/250923-%EC%84%9C%ED%82%B7%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4-API-gateway-%ED%95%84%ED%84%B0%EB%A7%81</link>
            <guid>https://velog.io/@uni_gy/250923-%EC%84%9C%ED%82%B7%EB%B8%8C%EB%A0%88%EC%9D%B4%EC%BB%A4-API-gateway-%ED%95%84%ED%84%B0%EB%A7%81</guid>
            <pubDate>Tue, 23 Sep 2025 08:49:22 GMT</pubDate>
            <description><![CDATA[<h3 id="서킷-브레이커">서킷 브레이커</h3>
<ul>
<li>클로즈드:<ul>
<li>기본 상태, 모든 요청 통과</li>
<li>실패율이 설정된 임계값을 초과하면 오픈 상태로 전환</li>
</ul>
</li>
<li>오픈:<ul>
<li>모든 요청을 즉시 실패로 처리 에러 응답 반환</li>
<li>설정된 대기 시간이 지난 후, 하프-오픈 상태로 전환</li>
</ul>
</li>
<li>하프-오픈:<ul>
<li>제한된 수의 요청을 허용하여 시스템이 정상 상태로 복구되었는지 확인</li>
<li>요청이 성공하면 클로즈드로 전환, 다시 실패하면 오픈 상태로 전환</li>
<li>예시: 하프-오픈 상태에서 3개의 요청을 허용하고, 모두 성공하면 클로즈드 상태로 전환. 만약 하나라도 실패하면 다시 오픈 상태 전환.</li>
</ul>
</li>
<li>Fallback: 호출 실패시 대체 로직</li>
</ul>
<h3 id="spring-cloud-gateway-필터링">Spring Cloud Gateway 필터링</h3>
<ul>
<li>Global Filter: 모든 요청에 대해 작동하는 필터</li>
<li>Gateway Filter: 특정 라우트에만 적용되는 필터</li>
</ul>
<p>필터 시점별 종류</p>
<ul>
<li><p>pre filter: 요청 처리되기 전에 실행한 다음 체인의 다음 필터로 요청을 전달.</p>
</li>
<li><p>post filter: Post 필터는 요청이 처리된 후, 응답이 반환되기 전에 실행됩니다. Post 필터에서는 체인의 다음 필터가 완료된 후에 실행되어야 하는 추가적인 작업을 수행해야 합니다.</p>
</li>
<li><p>게이트웨이 라우트에서 lb://product 같은 서비스 ID 기반 URI를 쓰면, 게이트웨이는 Eureka에 등록된 인스턴스 목록을 받아 라운드로빈(기본 전략) 으로 선택합니다. Feign이나 Ribbon을 “직접” 설정하지 않아도, Gateway 내부가 이미 “로드밸런싱 클라이언트”(Spring Cloud LoadBalancer)를 사용합니다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[250922 MSA, Spring Cloud]]></title>
            <link>https://velog.io/@uni_gy/250922-MSA-Spring-Cloud</link>
            <guid>https://velog.io/@uni_gy/250922-MSA-Spring-Cloud</guid>
            <pubDate>Mon, 22 Sep 2025 08:08:31 GMT</pubDate>
            <description><![CDATA[<h2 id="msa">MSA</h2>
<details><summary>주요 특징</summary>

<ul>
<li><p>독립적인 배포 가능성: 각 서비스는 독립적으로 배포할 수 있으며, 다른 서비스에 영향을 주지 않고 업데이트할 수 있다.</p>
</li>
<li><p>작은 팀 구성: 각 서비스는 작은 팀이 독립적으로 개발하고 관리할 수 있음</p>
</li>
<li><p>기술 스택의 다양성: 각 서비스는 적절한 기술 스택을 자유롭게 선택할 수 있음</p>
</details>
</li>
<li><p>장점: 확장성, 독립적 배포, 유연성</p>
</li>
<li><p>단점: 복잡성 증가(서비스 간 통신, 데이터 일관성 유지), 운영 비용 증가(모니터링, 로깅)</p>
</li>
</ul>
<h2 id="spring-cloud">Spring Cloud</h2>
<ol>
<li><p>서비스 등록 및 디스커버리(Eureka)</p>
<p>마이크로서비스 아키텍처에서 각 서비스의 위치를 동적으로 관리</p>
<ul>
<li>서비스 레지스트리: 모든 서비스 인스턴스 위치를 저장하는 중앙 저장소</li>
<li>헬스체크: 서비스 인스턴스의 상태를 주지적으로 확인하여 가용성을 보장</li>
</ul>
</li>
<li><p>로드 밸런싱(Ribbon)</p>
<p>서비스 인스턴스 간의 부하를 분산</p>
<ul>
<li>서버 리스트 제공자</li>
<li>로드 밸런싱 알고리즘: 라운드 로빈, 가중치 기반 등</li>
<li>Failover: 요청 실패 시 다른 인스턴스로 자동 전환</li>
</ul>
</li>
<li><p>서킷 브레이커(Hystrix, Resilience4j)</p>
<p> 서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지</p>
<p> Resilience4j 특징</p>
<ul>
<li>서킷 브레이커: 호출 실패를 감지하고 서킷을 열어 추가적인 호출을 차단하여 시스템의 부하를 줄임</li>
<li>Fallback: 호출 실패 시 대체 로직을 실행하여 시스템의 안정성을 유지</li>
<li>타임아웃 설정: 호출의 응답 시간을 설정하여 느린 서비스 호출에 대응할 수 있음</li>
<li>재시도: 재시도 기능을 지원하여 일시적인 네트워크 문제 등에 대응할 수 있음</li>
</ul>
</li>
<li><p>API 게이트웨이(Zuul, Cloud gateway)</p>
<ul>
<li>라우팅 및 필터링</li>
<li>보안</li>
<li>효율성</li>
</ul>
</li>
<li><p>Spring Cloud Config</p>
<p> 중앙 집중식 설정 관리를 제공</p>
<ul>
<li>Config 서버</li>
<li>Config 클라이언트</li>
<li>설정 갱신: 설정 변경 시 서비스 재시작 없이 실시간으로 반영 (@RefreshScope, Spring Cloud Bus)</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[250919 nftables 포트포워딩, Java 빌드 war에서 jar 변경]]></title>
            <link>https://velog.io/@uni_gy/250919-nftables-%ED%8F%AC%ED%8A%B8%ED%8F%AC%EC%9B%8C%EB%94%A9-Java-%EB%B9%8C%EB%93%9C-war%EC%97%90%EC%84%9C-jar-%EB%B3%80%EA%B2%BD</link>
            <guid>https://velog.io/@uni_gy/250919-nftables-%ED%8F%AC%ED%8A%B8%ED%8F%AC%EC%9B%8C%EB%94%A9-Java-%EB%B9%8C%EB%93%9C-war%EC%97%90%EC%84%9C-jar-%EB%B3%80%EA%B2%BD</guid>
            <pubDate>Fri, 19 Sep 2025 10:27:58 GMT</pubDate>
            <description><![CDATA[<h2 id="ubuntu-nftables로-포트포워딩-적용하기">Ubuntu nftables로 포트포워딩 적용하기</h2>
<ol>
<li><p>NAT 테이블/체인 생성</p>
<pre><code class="language-sh">sudo nft add table ip nat
sudo nft &#39;add chain ip nat prerouting { type nat hook prerouting priority 0; }&#39;
sudo nft &#39;add chain ip nat postrouting { type nat hook postrouting priority 100; }&#39;</code></pre>
</li>
<li><p>네트워크 인터페이스 확인</p>
<pre><code class="language-sh">ip link show</code></pre>
</li>
<li><p>포트포워딩 실행</p>
<pre><code class="language-sh">sudo nft add rule ip nat prerouting iif {2번의 네트워크 인터페이스이름:ens5} tcp dport 80 redirect to 8080</code></pre>
</li>
<li><p>설정 확인</p>
<pre><code class="language-sh">sudo nft list ruleset
// 아래처럼 나와야 합니다.
table ip nat {
     chain prerouting {
             type nat hook prerouting priority filter; policy accept;
             iif &quot;ens5&quot; tcp dport 80 redirect to :8080
     }</code></pre>
</li>
<li><p>인스턴스 재부팅 이후에도 반영되도록 설정</p>
<pre><code class="language-sh">sudo apt install nftables -y
sudo systemctl enable nftables
sudo sh -c &quot;nft list ruleset &gt; /etc/nftables.conf&quot;</code></pre>
</li>
</ol>
<h3 id="java-빌드-war에서-jar로-변경하기">Java 빌드 war에서 jar로 변경하기</h3>
<pre><code class="language-groovy">build.gradle
/* ⛔️ 제거: WAR 프로젝트 전용 설정
id &#39;war&#39;
providedRuntime &#39;org.springframework.boot:spring-boot-starter-tomcat&#39;
*/


bootJar { enabled = true }      // (기본값이지만 명시 OK)
jar { enabled = false }         // plain.jar 만들지 않으려면 유지</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[250918 영속성전이, orphanRemoval, Scheduler, Transactional]]></title>
            <link>https://velog.io/@uni_gy/250918-%EC%98%81%EC%86%8D%EC%84%B1%EC%A0%84%EC%9D%B4-orphanRemoval-Scheduler-Transactional</link>
            <guid>https://velog.io/@uni_gy/250918-%EC%98%81%EC%86%8D%EC%84%B1%EC%A0%84%EC%9D%B4-orphanRemoval-Scheduler-Transactional</guid>
            <pubDate>Thu, 18 Sep 2025 11:47:02 GMT</pubDate>
            <description><![CDATA[<h3 id="영속성-전이">영속성 전이</h3>
<ul>
<li>CascadeType.ALL : 모든 Cascade 옵션을 적용합니다.</li>
<li>CascadeType.PERSIST : 엔티티를 영속화할 때, 연관된 엔티티도 함께 영속화합니다.</li>
<li>CascadeType.REMOVE : 엔티티를 제거할 때, 연관된 엔티티도 함께 제거합니다.</li>
<li>CascadeType.MERGE : 엔티티 상태를 병합할 때, 연관된 엔티티도 함께 병합합니다.</li>
<li>CascadeType.REFRESH : 부모 엔티티를 Refresh하면, 연관된 엔티티도 함께 Refresh됩니다.</li>
<li>CascadeType.DETACH : 부모 엔티티를 Detach하면, 연관된 엔티티도 함께 Detach됩니다.</li>
</ul>
<h3 id="고아-entity-제거">고아 Entity 제거</h3>
<p>orphanRemoval = true</p>
<h3 id="scheduler">Scheduler</h3>
<p>// 초, 분, 시, 일, 월, 주 순서</p>
<pre><code>@Scheduled(cron = &quot;0 0 1 * * *&quot;)</code></pre><p>@EnableScheduling 달기</p>
<h3 id="transactional">@Transactional</h3>
<p>@Transactional이 없으면:</p>
<p>조회 자체는 되지만, 영속성 컨텍스트 범위가 보장되지 않아 Lazy Loading 시 문제가 생김.</p>
<p>사실상 &quot;트랜잭션 바깥&quot;에서 실행되는 것과 같음.</p>
<p>@Transactional(readOnly = true)가 있으면:</p>
<p>조회 트랜잭션을 열어서 Lazy 로딩도 안전하고, 변경 감지도 생략해 성능도 최적화됨.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[250917 validation, resttemplate, webclient, 영속성 컨텍스트, 지연로딩n+1]]></title>
            <link>https://velog.io/@uni_gy/250917-validation-resttemplate-webclient-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9n1</link>
            <guid>https://velog.io/@uni_gy/250917-validation-resttemplate-webclient-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9n1</guid>
            <pubDate>Wed, 17 Sep 2025 11:31:49 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-validation">spring validation</h3>
<p>@NotNotBlank, @Valid</p>
<p>validation 예외 발생하면 BindingResult 객체에 오류가 담겨져 들어온다.</p>
<pre><code class="language-java">List&lt;FieldError&gt; fieldErrors = bindingResult.getFieldErrors();
        if(fieldErrors.size() &gt; 0) {
            for (FieldError fieldError : bindingResult.getFieldErrors()) {
                log.error(fieldError.getField() + &quot; 필드 : &quot; + fieldError.getDefaultMessage());
            }
            return &quot;redirect:/api/user/signup&quot;;
        }</code></pre>
<h3 id="resttemplate">RestTemplate</h3>
<p>동기식 HTTP 클라이언트</p>
<h3 id="webclient">WebClient</h3>
<p>비동기+논블로킹(리액터 기반) HTTP 클라이언트</p>
<h3 id="영속성-컨텍스트">영속성 컨텍스트</h3>
<p>1차 캐시, 쓰기 지연 저장소, 변경 감지</p>
<h3 id="지연-로딩-n1-문제">지연 로딩 n+1 문제</h3>
<p>fetch join을 통해 방지</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[250916 쿠키,세션,필터]]></title>
            <link>https://velog.io/@uni_gy/250916-%EC%BF%A0%ED%82%A4%EC%84%B8%EC%85%98%ED%95%84%ED%84%B0</link>
            <guid>https://velog.io/@uni_gy/250916-%EC%BF%A0%ED%82%A4%EC%84%B8%EC%85%98%ED%95%84%ED%84%B0</guid>
            <pubDate>Tue, 16 Sep 2025 14:35:40 GMT</pubDate>
            <description><![CDATA[<h3 id="쿠키">쿠키</h3>
<p>클라이언트에 저장되는 key-value 형태의 데이터 파일. 유효 시간을 통해 브라우저 종료를 해도 인증 유지가 가능하다.</p>
<h3 id="세션">세션</h3>
<p>서버측에 저장하는 쿠키. 클라이언트가 요청을 보내면 Session ID를 발급.</p>
<h3 id="필터">필터</h3>
<p>웹 애플리케이션에서 관리되는 영역으로 Client로부터 오는 요청과 응답에 대해 최초/최종 단계의 위치, 요청과 응답의 정보를 변경하거나 부가적인 기능을 추가할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[LLM 간단 공부]]></title>
            <link>https://velog.io/@uni_gy/LLM-%EA%B0%84%EB%8B%A8-%EA%B3%B5%EB%B6%80</link>
            <guid>https://velog.io/@uni_gy/LLM-%EA%B0%84%EB%8B%A8-%EA%B3%B5%EB%B6%80</guid>
            <pubDate>Mon, 30 Jun 2025 16:54:32 GMT</pubDate>
            <description><![CDATA[<h1 id="llm">LLM</h1>
<ul>
<li>NLP 자연어 처리 기술의 한 분야이자 굉장히 큰 데이터 셋을 사용한다.</li>
<li>방대한 양의 데이터로부터 인간의 언어와 유사한 텍스트를 이해하고 만들어내는 기술</li>
<li>Transformer 구조 기반</li>
<li>Self-Attention: 문장 안의 단어들의 관계를 파악해서 중요한 단어에 집중하는 메커니즘</li>
<li>Word-Embedding: 단어를 벡터로 표현하는 기술, 비슷한 의미의 단어는 비슷한 벡터값을 가진다.</li>
</ul>
<h2 id="한계점">한계점</h2>
<ol>
<li>유지 비용이 많이 든다.</li>
<li>잘못되거나 나쁜 정보의 필터링 필요성</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[HTTP 공부 복습]]></title>
            <link>https://velog.io/@uni_gy/HTTP-%EA%B3%B5%EB%B6%80-%EB%B3%B5%EC%8A%B5</link>
            <guid>https://velog.io/@uni_gy/HTTP-%EA%B3%B5%EB%B6%80-%EB%B3%B5%EC%8A%B5</guid>
            <pubDate>Tue, 06 May 2025 07:02:12 GMT</pubDate>
            <description><![CDATA[<h1 id="http-10">HTTP 1.0</h1>
<ul>
<li>헤더 도입</li>
<li>응답에 상태코드 추가</li>
<li>Content-Type 도입 HTML 이외 문서 전송 가능<h3 id="한계">한계</h3>
</li>
<li>커넥션 하나당 요청 하나와 응답 하나만 처리 가능</li>
</ul>
<h1 id="http-11">HTTP 1.1</h1>
<ul>
<li>Keep-Alive: TCP연결을 끊지 않고 재사용</li>
<li>Pipelining: 앞 요청의 응답을 기다리지 않고 순차적으로 요청을 보내고 그 순서에 맞춰 응답을 받는 방식<h3 id="한계-1">한계</h3>
</li>
<li>Head of Line Blocking(HOL): 앞 요청의 응답이 너무 오래 걸리면 뒷 요청은 Blocking된다.</li>
<li>Header 중복: 연속된 요청의 헤더 많은 중복이 발생</li>
</ul>
<h1 id="http-20">HTTP 2.0</h1>
<ul>
<li>Binary Framing 계층<ol>
<li>Frame: HTTP 2.0에서 통신의 최소 단위, Header나 Data가 들어있다.</li>
<li>Message: 요청과 응답의 단위이고 다수의 Frame으로 이루어짐.</li>
<li>Stream: 연결된 Connection 내에서 양방향으로 Message를 주고받는 흐름. ID가 있음.</li>
</ol>
<ul>
<li>Message를 Frame 단위로 분할하며, Binary(이진)로 인코딩. 바이너리 형식 사용으로 파싱속도 및 전송 속도가 빠르고 오류 발생 가능성이 낮아졌다.</li>
<li>HTTP1.1은 텍스트 기반 프로토콜</li>
</ul>
</li>
<li>Multiplexing: 여러 개의 HTTP 요청과 응답을, 하나의 TCP 연결(Connection) 안에서 동시에 처리할 수 있도록 하는 기술. 응답이 요청의 순서에 상관없이 Stream으로 주고 받아 HOL 해결(병렬 처리).</li>
<li>Stream Prioritization: 리소스간 우선순위를 설정해 클라이언트가 먼저 필요한 리소스부터 보내준다.</li>
<li>Server Push: 서버는 클라이언트가 요청하지 않았지만 추가적으로 필요한 리소스를 보내줄 수 있다. 캐시에 저장.</li>
<li>Header Compression: HPACK 압축 알고리즘 사용. Header Table(Static, Dynamic)과 호프만 인코딩 기법을 사용해 압축하여 전송 효율 상승.</li>
</ul>
<h1 id="http-30">HTTP 3.0</h1>
<ul>
<li>QUIC: UDP 기반 전송 프로토콜</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQLD 오답노트]]></title>
            <link>https://velog.io/@uni_gy/SQLD-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8</link>
            <guid>https://velog.io/@uni_gy/SQLD-%EC%98%A4%EB%8B%B5%EB%85%B8%ED%8A%B8</guid>
            <pubDate>Tue, 04 Mar 2025 15:29:02 GMT</pubDate>
            <description><![CDATA[<h4 id="발생-시점-엔티티-유형">발생 시점 엔티티 유형</h4>
<p>기본 중심 행위</p>
<h4 id="형태별-엔티티-유형">형태별 엔티티 유형</h4>
<p>유형 개념 사건</p>
<h4 id="식별자의-대체-여부-분류">식별자의 대체 여부 분류</h4>
<p>본질 식별자 - 인조 식별자</p>
<h4 id="비식별자-관계">비식별자 관계</h4>
<p>부모 엔터티로부터 속성을 받았지만, 자식 엔터티의 주식별자로 사용하지 않고 일반적인 속성으로만 사용한다.</p>
<h4 id="데이터-모델링">데이터 모델링</h4>
<ul>
<li>데이터 모델링의 3 요소 Things, Attributes, Relationships</li>
<li>실제 데이터베이스 구축시 참고 물리적 데이터 모델링 </li>
<li>논리 모델링의 외래키는 물리 모델에서 선택 사항</li>
</ul>
<h4 id="데이터-모델링-관점">데이터 모델링 관점</h4>
<ul>
<li>데이터 관점: What, Data</li>
<li>프로세스 관점: How,Process</li>
<li>데이터와 프로세스의 상관 관점: interaction</li>
</ul>
<h4 id="erd-관계-표기법">ERD 관계 표기법</h4>
<p>관계명, 관계차수, 관계 선택사항</p>
<h4 id="서브쿼리-종류">서브쿼리 종류</h4>
<ul>
<li>Access Subquery: 쿼리의 변형이 없고 제공자의 역할을 하는 서브쿼리</li>
<li>Filter Subquery: 쿼리의 변형이 없고 확인자 역할을 하는 서브쿼리</li>
<li>Early Filter Subquery: 쿼리의 변형이 없고 서브쿼리가 먼저 실행하여 데이터를 걸러낸다.</li>
</ul>
<h4 id="natural-join">Natural join</h4>
<p>두 테이블 간에 동일한 칼럼 이름을 가지는 것을 모두 출력하는 조인
등가 조인 가능 비등가조인 불가능</p>
<h4 id="null-값-인덱스">Null 값 인덱스</h4>
<p>SQL Server는 null값을 인덱스 맨 앞에 저장
SQL Server는 인덱스 구성 칼럼이 모두 null인 레코드도 인덱스에 저장
Oracle은 null값을 인덱스 맨 뒤에 저장
Oracle은 인덱스 구성 칼럼이 모두 null인 레코드는 인덱스에 저장하지 않는다.</p>
<h4 id="슈퍼타입-서브타입-변환-방법">슈퍼타입 서브타입 변환 방법</h4>
<p>OneToOne Type , Plus Type, Single Type</p>
<h4 id="3층-스키마">3층 스키마</h4>
<p>외부(사용자뷰), 개념(통합 뷰), 내부(물리장치)</p>
<h4 id="데이터-모델링-관점-1">데이터 모델링 관점</h4>
<p>데이터관점 -구조분석
프로세스 관점- 업무시나리오분석
데이터와 프로세스의 상관 관점-CRUD메트릭스</p>
<h4 id="outer-join--데이터가-전부-포함되지-않는-쪽에-표시">outer join (+) 데이터가 전부 포함되지 않는 쪽에 표시</h4>
<h4 id="데이터-모델링-1">데이터 모델링</h4>
<p>개념적 데이터 모델링(높은 추상화 수준)
논리적 데이터 모델리(정규화, 재사용성)
물리적 데이터 모델링(반정규화, 성능/보안/저장)</p>
<h4 id="nullifab-ab-같으면-null반환">nullif(a,b) a,b 같으면 null반환</h4>
<h4 id="nvl2abc-a가-null이-아니면-b-null이면-c">NVL2(a,b,c) a가 null이 아니면 b null이면 c</h4>
<h4 id="그룹함수">그룹함수</h4>
<p>group by rollup(a,b) = grouping sets((a,b),(a),())
group by cube(a,b) = grouping sets((a),(b),(a,b),())</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 16953: A -> B]]></title>
            <link>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-16953-A-B</link>
            <guid>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-16953-A-B</guid>
            <pubDate>Thu, 11 Apr 2024 17:07:30 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/16953">문제</a></p>
<h1 id="풀이">풀이</h1>
<ul>
<li>DFS, BFS 두 방식 다 제출했는데 비슷한 속도로 통과했다.<h1 id="코드">코드</h1>
</li>
<li>BFS<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ul>
<p>public class Main {</p>
<pre><code>public static void main(String[] args) throws IOException {
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

    StringTokenizer st=new StringTokenizer(br.readLine());
    String a=st.nextToken();
    long b=Long.parseLong(st.nextToken());
    Queue&lt;Item&gt; q=new LinkedList&lt;&gt;();
    q.add(new Item(a,1));
    int ans=-1;
    while(!q.isEmpty()){
        Item item=q.poll();
        long n=Long.parseLong(item.num);
        if(n==b){
            ans=item.cnt;
            break;
        }
        if(2*n&lt;=b)q.add(new Item(String.valueOf(2*n),item.cnt+1));
        StringBuilder sb=new StringBuilder(item.num);
        sb.append(1);
        if(Long.parseLong(sb.toString())&lt;=b)q.add(new Item(sb.toString(),item.cnt+1));
    }
    System.out.println(ans);
}

static class Item{
    String num;
    int cnt;

    public Item(String num, int cnt) {
        this.num = num;
        this.cnt = cnt;
    }
}</code></pre><p>}</p>
<pre><code>- DFS
```java
import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

        StringTokenizer st=new StringTokenizer(br.readLine());
        String a=st.nextToken();
        long b=Long.parseLong(st.nextToken());
        Queue&lt;Item&gt; q=new LinkedList&lt;&gt;();
        q.add(new Item(a,1));
        int ans=-1;
        while(!q.isEmpty()){
            Item item=q.poll();
            long n=Long.parseLong(item.num);
            if(n==b){
                ans=item.cnt;
                break;
            }
            if(2*n&lt;=b)q.add(new Item(String.valueOf(2*n),item.cnt+1));
            StringBuilder sb=new StringBuilder(item.num);
            sb.append(1);
            if(Long.parseLong(sb.toString())&lt;=b)q.add(new Item(sb.toString(),item.cnt+1));
        }
        System.out.println(ans);
    }

    static class Item{
        String num;
        int cnt;

        public Item(String num, int cnt) {
            this.num = num;
            this.cnt = cnt;
        }
    }
}</code></pre><p>#dfs #bfs</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 2252: 줄 세우기]]></title>
            <link>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-2252-%EC%A4%84-%EC%84%B8%EC%9A%B0%EA%B8%B0</link>
            <guid>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-2252-%EC%A4%84-%EC%84%B8%EC%9A%B0%EA%B8%B0</guid>
            <pubDate>Thu, 11 Apr 2024 14:18:25 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/2252">문제</a></p>
<h1 id="풀이">풀이</h1>
<ol>
<li>pre 배열에는 본인보다 앞에서 서야 하는 학생의 수를 저장</li>
<li>v 배열은 학생이 줄에 섰는지 체크</li>
<li>post[i]에는 i번째 학생이 선행되어야 하는 학생들의 번호를 저장</li>
<li>pre[i]==0인 선행되어야 하는 학생 수가 0인 학생들을 반복적으로 확인해주며 모두 완료되면 종료<h1 id="코드">코드</h1>
<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ol>
<p>public class Main {</p>
<pre><code>public static void main(String[] args) throws IOException {
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

    StringTokenizer st=new StringTokenizer(br.readLine());
    int n=Integer.parseInt(st.nextToken());
    int m=Integer.parseInt(st.nextToken());

    int[] pre=new int[n+1];
    int[] v=new int[n+1];
    ArrayList&lt;Integer&gt;[] post=new ArrayList[n+1];
    for(int i=1;i&lt;n+1;i++)post[i]=new ArrayList&lt;&gt;();
    ArrayList&lt;Integer&gt; ans=new ArrayList&lt;&gt;();

    for(int i=0;i&lt;m;i++){
        st=new StringTokenizer(br.readLine());
        int a=Integer.parseInt(st.nextToken());
        int b=Integer.parseInt(st.nextToken());
        pre[b]++;
        post[a].add(b);
    }

    while(ans.size()!=n){
        for(int i=1;i&lt;n+1;i++){
            if(v[i]==0 &amp;&amp; pre[i]==0){
                v[i]=1;
                ans.add(i);
                for(int a:post[i])pre[a]--;
            }
        }
    }

    BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(System.out));
    for(int a:ans)bw.write(a+&quot; &quot;);
    bw.flush();
}</code></pre><p>}</p>
<p>```
#위상정렬</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 1922: 네트워크 연결]]></title>
            <link>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-1922-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0</link>
            <guid>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-1922-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EC%97%B0%EA%B2%B0</guid>
            <pubDate>Wed, 10 Apr 2024 20:07:20 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/1922">문제</a></p>
<h1 id="풀이">풀이</h1>
<ol>
<li>우선순위큐를 사용하여 크루스칼 알고리즘 구현</li>
<li>유니온파인드를 통해서 연결된 노드를 한 그룹으로 묶어줬다.<h1 id="코드">코드</h1>
<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ol>
<p>public class Main {</p>
<pre><code>static int[] group;
static int groupCnt;

public static void main(String[] args) throws IOException {
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

    int n=Integer.parseInt(br.readLine());
    int m=Integer.parseInt(br.readLine());
    group=new int[n+1];
    groupCnt=n;
    for(int i=1;i&lt;n+1;i++)group[i]=i;
    PriorityQueue&lt;Line&gt; pq=new PriorityQueue&lt;&gt;();
    for(int i=0;i&lt;m;i++){
        StringTokenizer st=new StringTokenizer(br.readLine());
        int a=Integer.parseInt(st.nextToken());
        int b=Integer.parseInt(st.nextToken());
        int c=Integer.parseInt(st.nextToken());
        pq.add(new Line(a,b,c));
    }

    int ans=0;
    while(!pq.isEmpty()){
        if(groupCnt==1)break;
        Line line=pq.poll();
        if(line.a==line.b)continue;
        boolean ret=union(line.a, line.b);
        if(ret)ans+=line.cost;
    }
    System.out.println(ans);
}

static int find(int x){
    if(x!=group[x]){
        group[x]=find(group[x]);
    }
    return group[x];
}

static boolean union(int a,int b){
    a=find(a);
    b=find(b);
    if(a==b)return false;
    if(a&lt;b)group[b]=a;
    else group[a]=b;
    groupCnt--;
    return true;
}

static class Line implements Comparable&lt;Line&gt;{
    int a,b,cost;

    public Line(int a, int b, int cost) {
        this.a = a;
        this.b = b;
        this.cost = cost;
    }

    public int compareTo(Line o){
        return this.cost-o.cost;
    }
}</code></pre><p>}</p>
<p>```
#MST #유니온파인드</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 12865: 평범한 배낭]]></title>
            <link>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-12865-%ED%8F%89%EB%B2%94%ED%95%9C-%EB%B0%B0%EB%82%AD</link>
            <guid>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-12865-%ED%8F%89%EB%B2%94%ED%95%9C-%EB%B0%B0%EB%82%AD</guid>
            <pubDate>Fri, 05 Apr 2024 00:18:02 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/12865">문제</a></p>
<h1 id="풀이">풀이</h1>
<ol>
<li>물건들을 무게순으로 오름차순 정렬</li>
<li>dp 2차원 배열의 행은 물건 열은 무게</li>
<li>dp 배열의 값은 해당 무게에서 가치의 최댓값이다.</li>
<li>j-물건의 무게가 0 이상과 0 미만일 경우 구분해서 구해준다.<h1 id="코드">코드</h1>
<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ol>
<p>public class Main {</p>
<pre><code>public static void main(String[] args) throws IOException {
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

    StringTokenizer st=new StringTokenizer(br.readLine());
    int n=Integer.parseInt(st.nextToken());
    int k=Integer.parseInt(st.nextToken());
    Goods[] goods=new Goods[n];
    for(int i=0;i&lt;n;i++){
        st=new StringTokenizer(br.readLine());
        int w=Integer.parseInt(st.nextToken());
        int v=Integer.parseInt(st.nextToken());
        goods[i]=new Goods(w,v);
    }
    Arrays.sort(goods);
    int[][] dp=new int[n+1][k+1];
    int ans=0;
    for(int i=1;i&lt;n+1;i++){
        Goods item=goods[i-1];
        for(int j=1;j&lt;k+1;j++){
            if(j-item.w&gt;=0){
                dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-item.w]+item.v);
            }
            else dp[i][j]=dp[i-1][j];
            ans=Math.max(ans,dp[i][j]);
        }
    }

    System.out.println(ans);
}

static class Goods implements Comparable&lt;Goods&gt;{
    int w;
    int v;

    public Goods(int w, int v) {
        this.w = w;
        this.v = v;
    }

    @Override
    public int compareTo(Goods o) {
        return this.w-o.w;
    }
}</code></pre><p>}</p>
<p>```
#dp</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 7795: 먹을 것인가 먹힐 것인가]]></title>
            <link>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-7795-%EB%A8%B9%EC%9D%84-%EA%B2%83%EC%9D%B8%EA%B0%80-%EB%A8%B9%ED%9E%90-%EA%B2%83%EC%9D%B8%EA%B0%80</link>
            <guid>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-7795-%EB%A8%B9%EC%9D%84-%EA%B2%83%EC%9D%B8%EA%B0%80-%EB%A8%B9%ED%9E%90-%EA%B2%83%EC%9D%B8%EA%B0%80</guid>
            <pubDate>Fri, 29 Mar 2024 10:46:27 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/7795">문제</a></p>
<h1 id="풀이">풀이</h1>
<ol>
<li>이분탐색<ul>
<li>B 배열만 정렬해주고 A 배열 각 원소마다 이분탐색 진행</li>
</ul>
</li>
<li>원포인터<ul>
<li>A,B 둘다 정렬 원포인터 사용</li>
<li>a[i] 원소보다 큰거나 같은 것 발견할때까지 포인터 이동, 멈추면 다음 원소로 포인터부터 시작해서 다시 진행</li>
</ul>
</li>
</ol>
<h1 id="코드">코드</h1>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws IOException {
//        System.out.println(new Solution().solution(elements));
        BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

        int t=Integer.parseInt(br.readLine());
        while(t--&gt;0){
            StringTokenizer st=new StringTokenizer(br.readLine());
            int n=Integer.parseInt(st.nextToken());
            int m=Integer.parseInt(st.nextToken());
            int[] a=new int[n];
            int[] b=new int[m];

            st=new StringTokenizer(br.readLine());
            for(int i=0;i&lt;n;i++)a[i]=Integer.parseInt(st.nextToken());
            st=new StringTokenizer(br.readLine());
            for(int i=0;i&lt;m;i++)b[i]=Integer.parseInt(st.nextToken());
            Arrays.sort(b);

            //이분탐색
            int ans=0;
            for(int num:a){
                int l=0;
                int r=m-1;
                int mid=(l+r)/2;
                while(l&lt;=r){
                    mid=(l+r)/2;
                    if(num&lt;=b[mid]){
                        r=mid-1;
                    }
                    else if(num&gt;b[mid]){
                        l=mid+1;
                    }
                }
                ans+=l;
            }

                // 원포인터
//            Arrays.sort(a);
//            int ans=0,pointer=0;
//            for(int num:a){
//                while( pointer&lt;m &amp;&amp; num&gt;b[pointer]){
//                    pointer++;
//                }
//                ans+=pointer;
//            }
            System.out.println(ans);
        }
    }
}</code></pre>
<p>#이분탐색</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 2531: 회전 초밥]]></title>
            <link>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-2531-%ED%9A%8C%EC%A0%84-%EC%B4%88%EB%B0%A5</link>
            <guid>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-2531-%ED%9A%8C%EC%A0%84-%EC%B4%88%EB%B0%A5</guid>
            <pubDate>Wed, 27 Mar 2024 09:39:15 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/2531">문제</a></p>
<h1 id="풀이">풀이</h1>
<ul>
<li>처음에 Set을 이용하여 구현했지만, 집합에 같은 번호 스시가 여러개 포함되어 있을 때 remove를 하면 한번에 삭제되는 문제가 있었다.</li>
<li>그래서 DAT로 변경하여 개수로 체크해줬다.</li>
<li>원형을 고려해서 인덱스를 % n 을 해줘서 인덱스 에러 방지<h1 id="코드">코드</h1>
<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ul>
<p>public class Main {</p>
<pre><code>public static void main(String[] args) throws IOException {
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

    StringTokenizer st=new StringTokenizer(br.readLine());
    int n=Integer.parseInt(st.nextToken());
    int d=Integer.parseInt(st.nextToken());
    int k=Integer.parseInt(st.nextToken());
    int c=Integer.parseInt(st.nextToken());

    int[] sushi=new int[n];
    for(int i=0;i&lt;n;i++)sushi[i]=Integer.parseInt(br.readLine());

    int[] dat=new int[d+1];
    int max=0;
    for(int i=0;i&lt;k;i++){
        if(dat[sushi[i]]==0)max++;
        dat[sushi[i]]++;
    }
    int cnt=max;
    for(int i=1;i&lt;=n;i++){
        dat[sushi[i-1]]--;
        if(dat[sushi[i-1]]==0)cnt--;
        if(dat[sushi[(i+k-1)%n]]==0)cnt++;
        dat[sushi[(i+k-1)%n]]++;
        if(dat[c]==0)max=Math.max(max,cnt+1);
        else max=Math.max(max,cnt);
    }
    System.out.println(max);
}</code></pre><p>}</p>
<p>```
#슬라이딩윈도우</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 2470: 두 용액]]></title>
            <link>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-2470-%EB%91%90-%EC%9A%A9%EC%95%A1</link>
            <guid>https://velog.io/@uni_gy/%EB%B0%B1%EC%A4%80-2470-%EB%91%90-%EC%9A%A9%EC%95%A1</guid>
            <pubDate>Tue, 26 Mar 2024 20:18:24 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/2470">문제</a></p>
<h1 id="풀이">풀이</h1>
<ol>
<li>숫자 정렬</li>
<li>l=0 r=n-1부터 투포인터 진행</li>
<li>두 용액 더했을 때 절댓값이 작으면 정답 갱신, 더한 값이 0보다 작으면 l++ 0보다 크면 r--<h1 id="코드">코드</h1>
<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ol>
<p>public class Main {</p>
<pre><code>public static void main(String[] args) throws IOException {
    BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
    int n=Integer.parseInt(br.readLine());
    int[] arr=new int[n];
    StringTokenizer st=new StringTokenizer(br.readLine());
    for(int i=0;i&lt;n;i++)arr[i]=Integer.parseInt(st.nextToken());
    Arrays.sort(arr);

    int l=0;
    int r=n-1;
    int sum=Math.abs(arr[0]+arr[n-1]);
    int ans1=arr[0];
    int ans2=arr[n-1];
    while(l&lt;r){
        int a=arr[l]+arr[r];
        if(Math.abs(a)&lt;sum){
            sum=Math.abs(a);
            ans1=arr[l];
            ans2=arr[r];
        }
        if(a&lt;0)l++;
        else r--;
    }
    System.out.println(ans1+&quot; &quot;+ans2);
}</code></pre><p>}</p>
<p>```
#투포인터</p>
]]></description>
        </item>
    </channel>
</rss>