<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>SY LOG</title><description>Developer</description><link>https://blog.csyhorizon.dev/</link><language>ko</language><item><title>[#1] 이펙티브 자바 (정적 팩토리 메서드)</title><link>https://blog.csyhorizon.dev/posts/2025/java/effective_java/1/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/java/effective_java/1/</guid><pubDate>Wed, 10 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;자바 개발자라면 한번쯤은 읽어봐야하는 책이라고 하는 &lt;code&gt;이펙티브 자바&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p&gt;책 구매는 8월 쯔음에 한거같은데, 프로젝트 1개가 어느 정도 완성된 거 같아 슬슬 하나씩 읽어보면서 코드 좀 갈아보려고요.&lt;/p&gt;
&lt;p&gt;이거 외에도 &lt;code&gt;토비의 스프링&lt;/code&gt;이나 &lt;code&gt;종만북&lt;/code&gt;, &lt;code&gt;노랭이&lt;/code&gt;도 일단 있긴한데 그닥 손이 안가는 거 보면 당장 필요한 시기는 아닌가봅니다.&lt;/p&gt;
&lt;p&gt;:::tip
미리 말하지만 부정안하고 제가 책 읽는 것을 진짜 싫어합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;제가 알고리즘 공부하려고 종만북 샀던 것도 1개 읽고 1개는 그대로 방치된 상태입니다.&lt;/li&gt;
&lt;li&gt;너무 방치하기엔 아까워서 개인 프로젝트 완수한 시점에 읽으면 좋아보이더라고요
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;정적 팩토리 메서드 (Static Factory Method)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;객체의 생성을 담당하는 클래스 메서드입니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;클래스의 인스턴스를 얻는 전통적 수단 public 생성자&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;또 알아야 할 게 있는데 그게 정적 팩터리 메서드&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&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;li&gt;정적 팩터리 메서드를 작성하는 시점에 반환 객체의 클래스가 존재하지 않아도 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상속하려면 public이나 protected 생성자가 필요해 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없음&lt;/li&gt;
&lt;li&gt;정적 팩터리 메서드는 프로그래머가 찾기 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;사실 뭔 소린지 모르겠습니다&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;책 잘 읽고 똑똑하면 서울대갔지.. 제 머리는 암기하는 머리가 아니기에 우선 키워드만 땄습니다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;머리가 좋지 않으니 하나하나 파서 해보죠&lt;/p&gt;
&lt;h2&gt;public 생성자&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;public&lt;/code&gt;은 알겠는데 왜 &lt;code&gt;public 생성자&lt;/code&gt;인지부터 생각해야합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Person {
    private String name;
    
    public Person() { }

    private Person(String name) {
        this.name = name;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;일단 &lt;code&gt;public 생성자&lt;/code&gt;는 클래스 바깥에서 객체를 만들 수 있는 공개되어 있는 생성자를 말합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Person p = new Person();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;우리는 내부에 Person이라는 것이 있기에 &lt;code&gt;생성자&lt;/code&gt;를 이해했습니다.&lt;/p&gt;
&lt;p&gt;그렇다면 public 생성자는?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;앞에 public이 붙으면 public 생성자가 됩니다.&lt;/li&gt;
&lt;li&gt;반대로 private이 붙으면 private 생성자가 되겠지요
&lt;ul&gt;
&lt;li&gt;이건 싱글톤에 사용하겠네요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정적 팩토리 메서드&lt;/h2&gt;
&lt;p&gt;그러면 또 익혀야 한다는 그 &lt;code&gt;정적 팩토리 메서드&lt;/code&gt;는 뭘까요?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Person {
    private String name;

    private Person(String name) {
        this.name = name;
    }

    public static Person from(String name) {
        return new Person(name);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;음.. 분명 저런 코드를 과거에 짜던 기억은 있지만 어째서 좋은건가요?&lt;/p&gt;
&lt;h2&gt;장점 분석&lt;/h2&gt;
&lt;p&gt;:::note
책의 긴 글 잘 안읽히니까 &lt;code&gt;핵심 키워드&lt;/code&gt;만 뽑아서 하나씩 해보겠습니다
:::&lt;/p&gt;
&lt;h3&gt;이름을 가질 수 있음&lt;/h3&gt;
&lt;p&gt;그러네요. 이름 가질 수 있네요&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// public 메서드
new Person(&quot;SY&quot;);

// 정적 팩토리 메서드
Person.from(&quot;SY&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;위 / 아래 쓰기엔 위가 편하겠지만, 의도 파악은 아래가 더 좋아 보입니다.&lt;/li&gt;
&lt;li&gt;예로 &lt;code&gt;Letter.to(&quot;SY&quot;)&lt;/code&gt; 이나 &lt;code&gt;Letter.from(&quot;YJ&quot;)&lt;/code&gt; 이런 느낌으로다가 의도에 맞게 만들 수 있겠네요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;호출될 때마다 인스턴스 새로 생성할 필요 없음&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Person p1 = new Person(&quot;SY&quot;);
Person p2 = new Person(&quot;SY&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;원래라면 제가 이런식으로 계속 만들텐데&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class Person {
    private static final Map&amp;lt;String, Person&amp;gt; CACHE = new HashMap&amp;lt;&amp;gt;();

    private String name;

    private Person(String name) {
        this.name = name;
    }

    public static Person of(String name) {
        if (CACHE.containsKey(name)) {
            return CACHE.get(name);
        }

        Person p = new Person(name);
        CACHE.put(name, p);
        return p;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러면 제 이름이 수 백명이라도 이미 있는 객체 그대로 쓰겠죠&lt;/p&gt;
&lt;p&gt;즉, 매번 new 할 필요 없으니까 이것도 이해했습니다.&lt;/p&gt;
&lt;h3&gt;반환 타입의 하위 타입 객체를 반환할 수 있는 능력이 있음&lt;/h3&gt;
&lt;p&gt;음 &lt;code&gt;인터페이스 뒤에 진짜 구현체를 숨긴다&lt;/code&gt; 그런 소리 같은데요?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public class GradeCalculator {
    public static List&amp;lt;Integer&amp;gt; getScores() {
        return new ArrayList&amp;lt;&amp;gt;();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예로 반환타입은 List인데 실제로는 하위 구현체인 ArrayList로 반환하는 것처럼요&lt;/p&gt;
&lt;p&gt;즉, 정적 팩토리 메서드에 적용하면, 굳이 API 사용하는 사람한테 클래스 알려줄 필요가 없다. 그렇 소리 같습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public interface Mobile {
    void call();
}

class Galaxy implements Mobile {}
class IPhone implements Mobile {}

public class MobileFactory {
    public static Mobile getPhone(String Type) {
        if (&quot;Apple&quot;.equals(type)) {
            return new IPhone();
        }
        return new Galaxy();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;음, 그러니까 Mobile만 알면되는데 그게 뭔 폰인지는 알 필요 없다는 그런 거네요.
구체 클래스 이름은 가리고, 알 필요도 없어지면 확실히 좋긴 하네요.&lt;/p&gt;
&lt;h3&gt;입력 매개변수에 따라 매번 다른 클래스 객체 반환할 수 있음&lt;/h3&gt;
&lt;p&gt;음.. 이건 아마 유연하게 할 수 있다는 그런걸까요?&lt;/p&gt;
&lt;p&gt;잘 모르는데 대충 &lt;code&gt;EnumSet&lt;/code&gt;이 대표라곤 하는데 코드 뜯으면서 확인해보겠습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EnumSet&amp;lt;Color&amp;gt; colors = EnumSet.of(Color.RED, Color.BLUE);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;일단 눈으로 보이는 건 EnumSet인데, 따로 public 생성자가 없네요?
정적 팩토리 메서드라는건 일단 바로 알긴했는데, 그래서 왜죠?&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public abstract class EnumSet&amp;lt;E extends Enum&amp;lt;E&amp;gt;&amp;gt; {
    
    public static &amp;lt;E extends Enum&amp;lt;E&amp;gt;&amp;gt; EnumSet&amp;lt;E&amp;gt; noneOf(Class&amp;lt;E&amp;gt; elementType) {
        Enum&amp;lt;?&amp;gt;[] universe = getUniverse(elementType);
        
        if (universe.length &amp;lt;= 64) {
            return new RegularEnumSet&amp;lt;&amp;gt;(elementType, universe);
        }
        
        return new JumboEnumSet&amp;lt;&amp;gt;(elementType, universe);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코드 중 일부 가져왔는데, 보시면 굉장히 재밌는 구조네요.&lt;/p&gt;
&lt;p&gt;Enum 개수가 여러개든 아니든 일단 받고, 64개 이하면 &lt;code&gt;RegularEnumSet&lt;/code&gt;을 리턴하고 그 이상은 &lt;code&gt;JumboEnumSet&lt;/code&gt;을 리턴합니다.&lt;/p&gt;
&lt;p&gt;또 들어가서 보면 &lt;code&gt;RegularEnumSet&lt;/code&gt;은 long 변수 하나로 뭐 &lt;code&gt;비트마스킹&lt;/code&gt; 써서 관리하고 있고, &lt;code&gt;JumboEnumSet&lt;/code&gt;은 long[] 배열 써서 관리하던데 그냥 알아서 최적화해준다는 게 엄청나네요.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;비트마스킹&lt;/code&gt;은 알고리즘 영역이고 쉽게 설명하면 이진수 표현으로 계산해서 효율 극대화한건데, 이건 웹 개발자가 현대에서 쓸 일이 거의 없기에 필요한 거 아니면 의식하지 않는 게 코딩을 재밌게 하려면 필요한 게 아니면 의식하지 않아도 될 거 같습니다.&lt;/p&gt;
&lt;h3&gt;정적 팩터리 메서드를 작성하는 시점에 반환 객체의 클래스가 존재하지 않아도 됨&lt;/h3&gt;
&lt;p&gt;마지막은 그냥 JDBC를 알려주는 거 같네요.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Connection conn = DriverManager.getConnection(&quot;jdbc:mysql://...&quot;);&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;우리가 MySQL을 쓸지 PostgreSQL을 쓸지 코드 짤 때는 모를 수 있잖아요?&lt;/p&gt;
&lt;p&gt;이건 드라이버 나중에 등록하면 알아서 &lt;code&gt;Connection&lt;/code&gt; 객체를 만들어 준다는 거 같습니다.&lt;/p&gt;
&lt;h2&gt;단점 분석&lt;/h2&gt;
&lt;p&gt;단점도 뜯어 봐야죠. 장점만 알아서 뭐합니까&lt;/p&gt;
&lt;h3&gt;상속하려면 public이나 protected 생성자가 필요해 정적 팩터리 메서드만 제공하면 하위 클래스를 만들 수 없음&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;public class Parent {
    private Parent() {}

    public static Parent getInstance() {
        return new Parent();
    }
}

public class Child extends Parent { 
    public Child() {
        super(); // Error
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이건 생성자를 막아버리니 다른 클래스가 상속받을 수 없다는 거네요.&lt;/p&gt;
&lt;ul&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;code&gt;정적 팩토리 메서드&lt;/code&gt;를 할 줄 안다. 가 될 거 같습니다.&lt;/p&gt;
&lt;h2&gt;Rule&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;from : 매개변수 하나 받아서 객체 생성할 때
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Data a = Data.from(instant);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;of : 매개변수 여러 개 받아서 객체 생성할 때
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Set&amp;lt;Rank&amp;gt; cards = EnumSet.of(JACK, QUEEN, KING);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;valueOf : &lt;code&gt;from&lt;/code&gt;과 &lt;code&gt;of&lt;/code&gt;의 더 자세한 버전
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;String s = String.valueOf(true);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;instance, getInstance : 인스턴스를 반환하지만, 매개변수와 같은 값인지는 보장 안함
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Calendar cal = Calendar.getInstance();&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;create, newInstance : &lt;code&gt;getInstance&lt;/code&gt;와 같지만, 매번 새로운 객체 생성 보장
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Object array = Array.newInstance(classObject, length);&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;결론은 내 프로젝트 안봐도 &lt;code&gt;너 public 생성자 써서 한거 다 알고있음&lt;/code&gt;이라고 묻는 거 같네요.&lt;/li&gt;
&lt;li&gt;머리에 넣을 게 아니라 빨리 니 코드 고치고 다음꺼 봐라라는 거 같습니다.&lt;/li&gt;
&lt;li&gt;첫장부터 이거 박아넣어서 제 코드를 뜯게 만드는 이 사람은 진짜 뭐하는 사람일까요?&lt;/li&gt;
&lt;li&gt;이거 재밌네요. Item 90개 검토하려면 최소 3개월 박겠는데 리펙토링, 성능 개선 생각하면 당분간은 자바만 생각해도 될 거 같습니다&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>게시글 조회 성능 2700% 개선 (2.3초 -&gt; 0.08초 단축)</title><link>https://blog.csyhorizon.dev/posts/2025/spring/refactor/2/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/spring/refactor/2/</guid><pubDate>Sun, 07 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::note
일본어 학습 커뮤니티 서비스를 개발하며 겪은 성능 병목 현상을 분석하고,&amp;lt;br/&amp;gt;
JPA Fetch Join, Redis Write-Back, DB Indexing을 통해 응답 속도를 최대 27배(2700%) 개선한 과정을 공유합니다.
:::&lt;/p&gt;
&lt;p&gt;&amp;lt;br/&amp;gt;&lt;/p&gt;
&lt;h1&gt;테스트 중 2.3초의 수준의 심각한 성능저하 식별&lt;/h1&gt;
&lt;p&gt;로컬 개발 환경에서는 빠르게 동작하였지만, DB 서버와의 지연을 고려하여 &amp;lt;bold&amp;gt;Azure 클라우드 DB 환경&amp;lt;/bold&amp;gt;에서 테스트를 진행하였을 때
심각한 성능 저하를 보였습니다.&lt;/p&gt;
&lt;p&gt;특히 &lt;code&gt;댓글이 많은 게시글 상세 조회&lt;/code&gt; API는 평균 응답 속도가 2.3초에 달하는 치명적인 수준을 확인했습니다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해 읽기와 쓰기 영역을 나누어 단계별 최적화를 진행했습니다.&lt;/p&gt;
&lt;h2&gt;읽기 최적화 (게시글 목록 조회 N+1 문제)&lt;/h2&gt;
&lt;h3&gt;문제 상황&lt;/h3&gt;
&lt;p&gt;:::warning
&lt;code&gt;Post&lt;/code&gt; 조회 시 작성자 정보(member_id)를 함께 보여줘야하는데, JPA 지연 로딩으로 인해 게시글 조회 후 각 게시글마다 작성자를 찾기 위한 추가 쿼리가 발생하는 N+1 문제 발생
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;member_id&quot;, nullable = false)
    private Member member;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;현상 : 게시글 20개 조회 시 총 21회의 쿼리 실행&lt;/li&gt;
&lt;li&gt;영향 : 트래픽 증가 시 DB 커넥션 풀 고갈 위험&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;해결&lt;/h3&gt;
&lt;p&gt;:::note
Spring Data JPA의 &lt;code&gt;@Query&lt;/code&gt;와 &lt;code&gt;JOIN FETCH&lt;/code&gt;를 사용하여, 연관된 엔티티를 한 번의 쿼리로 가져오도록 변경
:::&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 변경 전: N + 1 발생
Page&amp;lt;Post&amp;gt; findByPostStatusAndPostType(PostStatus postStatus, PostType postType, Pageable pageable);

// 변경 후: Fetch Join 적용
@Query(&quot;SELECT p FROM Post p &quot;
        + &quot;JOIN FETCH p.member m &quot;
        + &quot;LEFT JOIN FETCH p.category c &quot;
        + &quot;WHERE p.postStatus = :postStatus AND p.postType = :postType&quot;)
Page&amp;lt;Post&amp;gt; findPostsWithMember(@Param(&quot;postStatus&quot;) PostStatus postStatus, @Param(&quot;postType&quot;) PostType postType, Pageable pageable);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;결과&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Avg Latency&lt;/th&gt;
&lt;th&gt;Query Count&lt;/th&gt;
&lt;th&gt;개선율&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Before&lt;/td&gt;
&lt;td&gt;59.73ms&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;약 17% 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;After&lt;/td&gt;
&lt;td&gt;49.48ms&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;95% 감소&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;로컬 테스트이므로 속도 차이는 크지 않지만, 쿼리 횟수를 줄여 DB 리소스 효율을 극대화&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;쓰기 최적화 (조회수 동시성 및 Lock 문제 해결)&lt;/h2&gt;
&lt;h3&gt;문제 상황&lt;/h3&gt;
&lt;p&gt;:::warning
게시글을 클릭할 때마다 조회수를 업데이트하기위해 &lt;code&gt;UPDATE post SET view_count = view_count + 1&lt;/code&gt; 쿼리 실행&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이는 k6 부하 테스트 과정에서 &lt;code&gt;DB Lock&lt;/code&gt;에 의한 평균 응답이 1.35초까지 오름
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;해결&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;DB에 직접 쓰지 않고 Redis의 인메모리 연산을 활용한 Write-Back 패턴 도입&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;방식&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;조회 시 &lt;code&gt;INCR&lt;/code&gt; 명령어로 메모리 상에 카운트&lt;/li&gt;
&lt;li&gt;1분 단위로 스케줄러로 Redis 데이터를 DB에 일괄 반영&lt;/li&gt;
&lt;li&gt;사용자에게는 DB + Redis 값 합산으로 실시간성 보장&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;결과&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;개선 효과&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg Latency&lt;/td&gt;
&lt;td&gt;1.35s&lt;/td&gt;
&lt;td&gt;0.25s&lt;/td&gt;
&lt;td&gt;5.4배 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPS&lt;/td&gt;
&lt;td&gt;67/s&lt;/td&gt;
&lt;td&gt;232/s&lt;/td&gt;
&lt;td&gt;4.2배 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;Lock 대기 시간이 사라지며 처리량 상승&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;댓글 조회 최적화 (2700% 성능 개선)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;댓글 영역에도 동일하게 처음에는 N + 1 문제를 식별하고 해결을 준비하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note
댓글 최적화는 가장 큰 값 변화가 있었으며, 많은 배움을 얻은 부분입니다
:::&lt;/p&gt;
&lt;h3&gt;N + 1 해결 시작&lt;/h3&gt;
&lt;p&gt;해결 전에 성능 테스트부터 진행했습니다.&lt;/p&gt;
&lt;h4&gt;테스트 환경&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;사용자 1명의 댓글 100개&lt;/li&gt;
&lt;li&gt;DB : Azure / PostgreSQL&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;█ TOTAL RESULTS

    checks_total.......: 2800    91.30/s
    checks_succeeded...: 96.85%  2712 out of 2800
    checks_failed......: 3.14%   88 out of 2800

    ✓ status is 200
    ✗ time &amp;lt; 100ms
      ↳  93% — ✓ 1312 / ✗ 88

    HTTP
    http_req_duration..............: avg=84.84ms
      { expected_response:true }...: avg=84.84ms  p(95)=105.48ms
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;측정했을 때 N + 1 문제치곤 빠르다 생각해서 의심을 했었어야했습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    @Query(&quot;SELECT c FROM Comment c JOIN FETCH c.member WHERE c.post.id = :postId AND c.commentStatus = :status&quot;)
    Page&amp;lt;Comment&amp;gt; findByPost_IdAndCommentStatus(@Param(&quot;postId&quot;) Long postId, @Param(&quot;status&quot;) CommentStatus status, Pageable pageable);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처음에는 다른 것과 다름없이 N + 1 해결을 위해 &lt;code&gt;Fetch Join&lt;/code&gt;으로 해결을 시도했습니다.&lt;/p&gt;
&lt;p&gt;어느 정도 속도가 빨리지겠다 생각했지만 결과는 예상과 크게 달랐습니다.&lt;/p&gt;
&lt;h4&gt;테스트 결과&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;█ TOTAL RESULTS

    checks_total.......: 2800    90.58/s
    checks_succeeded...: 91.14%  2552 out of 2800
    checks_failed......: 8.85%   248 out of 2800

    ✓ status is 200
    ✗ time &amp;lt; 100ms
      ↳  82% — ✓ 1152 / ✗ 248

    HTTP
    http_req_duration..............: avg=97.61ms
      { expected_response:true }...: avg=97.61ms  max=582.1ms
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;N + 1 문제는 해결되었지만, 오히려 속도는 느려지고 에러율은 증가했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;어째서?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;원인은 성능 관련해서 찾아보니 PostgreSQL에 있었습니다&lt;/li&gt;
&lt;li&gt;우리가 MySQL을 사용할 때는 자동으로 인덱스를 진행해줘서 신경쓰지 않았지만, PostgreSQL는 오히려 인덱스가 성능을 저하할 수 있다 생각하나봅니다.&lt;/li&gt;
&lt;li&gt;따라서 인덱스 없이 발생한 &lt;code&gt;JOIN&lt;/code&gt;은 Full Table Scan으로 이어져 성능이 오히려 저하되었습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;(2차 개선 시도) 인덱스 튜닝&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;post_id&lt;/code&gt;와 &lt;code&gt;created_at&lt;/code&gt;을 묶은 복합 인덱스를 수동 생성하여 Full Scan을 막았습니다&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE INDEX idx_comment_post_created ON comment (post_id, created_at DESC);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;테스트 결과&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;█ TOTAL RESULTS

    checks_total.......: 2800    91.41/s
    checks_succeeded...: 97.21%  2722 out of 2800
    checks_failed......: 2.78%   78 out of 2800

    ✓ status is 200
    ✗ time &amp;lt; 100ms
      ↳  94% — ✓ 1322 / ✗ 78

    HTTP
    http_req_duration..............: avg=87.41ms
      { expected_response:true }...: avg=87.41ms  p(95)=102.73ms
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;이렇게 했을 때 개선이 분명 되긴했는데.. 뭔가 이상합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;기존&lt;/th&gt;
&lt;th&gt;1차 개선&lt;/th&gt;
&lt;th&gt;2차 개선&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;상태&lt;/td&gt;
&lt;td&gt;N + 1 발생&lt;/td&gt;
&lt;td&gt;Fetch Join 적용&lt;/td&gt;
&lt;td&gt;Fetch Join + Index&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg&lt;/td&gt;
&lt;td&gt;84.84ms&lt;/td&gt;
&lt;td&gt;97.61ms&lt;/td&gt;
&lt;td&gt;87.41ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fail Rate&lt;/td&gt;
&lt;td&gt;3.14%&lt;/td&gt;
&lt;td&gt;8.85%&lt;/td&gt;
&lt;td&gt;2.78%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P95&lt;/td&gt;
&lt;td&gt;105.48ms&lt;/td&gt;
&lt;td&gt;120.45ms&lt;/td&gt;
&lt;td&gt;102.73ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;개선이 되지 않았다기엔 개선이 되었지만, 드라마틱한 변화가 없었어요&lt;/li&gt;
&lt;li&gt;굳이? 싶은 행동을 했다 싶어서 어째서인가? 고민했습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;테스트 문제&lt;/h4&gt;
&lt;p&gt;사실 개선 과정은 나쁘지 않았다고 생각합니다. 다만 지식이 부족해서 예상 범주 밖이였던 거 같았습니다.&lt;/p&gt;
&lt;p&gt;생각하지 않았던 건 JPA의 &lt;code&gt;1차 캐시&lt;/code&gt; 였습니다.&lt;/p&gt;
&lt;h4&gt;이유는?&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;이 전에 제가 테스트 과정을 &lt;code&gt;사용자 1명의 댓글 100개&lt;/code&gt;이라 했었는데 저는 이게 N + 1로 터질 줄 알았습니다.&lt;/li&gt;
&lt;li&gt;다만, 작성자가 동일하면 Hibernate 1차 캐시로 N+1이 발생하지 않을줄 전혀 몰랐습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서 개선이 잘못된 건 아니지만 테스트가 잘못되었다 생각하며, 다시 한번 검증을 진행했습니다.&lt;/p&gt;
&lt;h3&gt;마지막 검증 테스트&lt;/h3&gt;
&lt;h4&gt;최적화 전&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/k6/k6-comment-before.png&quot; alt=&quot;before&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;최적화 후&lt;/h4&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/k6/k6-comment-after.png&quot; alt=&quot;after&quot; /&gt;&lt;/p&gt;
&lt;h4&gt;최종 결과&lt;/h4&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;최적화 전&lt;/th&gt;
&lt;th&gt;최적화 후&lt;/th&gt;
&lt;th&gt;개선율&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Avg&lt;/td&gt;
&lt;td&gt;2,310ms&lt;/td&gt;
&lt;td&gt;84.95ms&lt;/td&gt;
&lt;td&gt;2700% 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TPS&lt;/td&gt;
&lt;td&gt;14.2 / sec&lt;/td&gt;
&lt;td&gt;45.4 / sec&lt;/td&gt;
&lt;td&gt;3.2배 처리량 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;P95&lt;/td&gt;
&lt;td&gt;2,950ms&lt;/td&gt;
&lt;td&gt;115.5ms&lt;/td&gt;
&lt;td&gt;25배 개선&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;총 거리 요청 수&lt;/td&gt;
&lt;td&gt;476건&lt;/td&gt;
&lt;td&gt;1,400건&lt;/td&gt;
&lt;td&gt;3배 처리량 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;최종 테스트를 진행했을 때 약 2700%라는 앞도적 성능 개선이 있었습니다!&lt;/p&gt;
&lt;h2&gt;스스로 반성하기&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;최종 테스트를 통해 알다시피 N+1 문제에 의한 네트워크 비용은 굉장히 성능 저하가 발생한다는 것을 알았습니다.&lt;/li&gt;
&lt;li&gt;또한 JPA가 좋아도 DB의 특성이 다 다르므로 이에 잘 알 필요가 있을 거 같습니다.&lt;/li&gt;
&lt;li&gt;마지막으로 테스트는 단순 더미 데이터보단 확실하게 다양한 더미 데이터로 진행하여 병목을 확인해야할 필요가 있을 거 같습니다&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>SSE 구현 중 마주친 3가지 오류와 해결 과정</title><link>https://blog.csyhorizon.dev/posts/2025/spring/trouble/1/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/spring/trouble/1/</guid><pubDate>Tue, 02 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;실시간 알림 기능을 위해 SSE를 도입하던 중 예상치 못한 오류를 마주하였습니다.&lt;/p&gt;
&lt;p&gt;이것들은 코드 문제가 아닌 Spring Security와 비동기 통신의 특성을 이해해야 해결할 수 있는 문제였습니다.&lt;/p&gt;
&lt;h1&gt;Access Denied&lt;/h1&gt;
&lt;p&gt;우선 SSE 기능을 구현하고 테스트를 진행하는데 &lt;code&gt;/api/subscribe&lt;/code&gt;를 호출하자말자 심각한 오류가 발생했습니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- &amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;오류 코드&amp;lt;/summary&amp;gt;
&amp;lt;/details&amp;gt; --&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;org.springframework.security.authorization.AuthorizationDeniedException: Access Denied
jakarta.servlet.ServletException: Unable to handle the Spring Security Exception because the response is already committed.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로그로 일단 &lt;code&gt;권한 부족&lt;/code&gt;이랑 &lt;code&gt;Spring Security&lt;/code&gt; 부분에서 문제가 생겼다는 사실은 바로 알았습니다.&lt;/p&gt;
&lt;p&gt;그렇지만, 왜 터졌는지 찾아야합니다.&lt;/p&gt;
&lt;h2&gt;원인 분석&lt;/h2&gt;
&lt;p&gt;처음에는 로그인 된 사용자니까 정상적으로 되야하는 게 정상아닌가? 하고 생각했습니다.&lt;/p&gt;
&lt;p&gt;웹 페이지 새로고침을 반복하니, 처음에는 연결에 문제 없다가 다음 재연결 시 터지는 걸 확인했습니다.&lt;/p&gt;
&lt;h3&gt;어째서?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트가 처음 접근(&lt;code&gt;/api/subscribe&lt;/code&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&gt;비동기 문제?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;이 과정에서 발생할 만한 부분을 생각한다면 아마도 비동기 영역일 거 같습니다.
정확히 해당 구간과 로그만 뜨는걸 봐선 딱히 떠오르는 게 없었습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;그렇다면 비동기 통신은 토큰 검사를 하지 않도록 해야합니다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;사전에 이미 토큰 검사를 했으니 이후부터는 통과시켜줘야 이 문제가 해결될 거 같았습니다&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;        http
                .authorizeHttpRequests(auth -&amp;gt; auth
                        .dispatcherTypeMatchers(DispatcherType.ASYNC).permitAll()
                        .requestMatchers(
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;해결&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SecurityConfig에서 DispatcherType.ASYNC 요청은 보안을 생략하도록 설정했습니다&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.enterprise.spring.io/spring-security/reference/servlet/authorization/authorize-requests.html&quot;&gt;Spring 문서 참조&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;타임아웃(Timeout) 500 에러&lt;/h1&gt;
&lt;p&gt;상단 Access Denied 오류를 해결하니 다음 문제가 발생했습니다&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;org.springframework.web.context.request.async.AsyncRequestTimeoutException
...
Resolved [org.springframework.web.context.request.async.AsyncRequestTimeoutException]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;원인 분석&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;이번에는 TimeoutException이 뜨는 것을 보니 연결 끊어짐 상태에 대한 문제인 거 같습니다.&lt;/li&gt;
&lt;li&gt;해당 문제는 SSE 연결은 영구적이지 않지만, 서버 에러 500을 반환하고 있었습니다.&lt;/li&gt;
&lt;li&gt;이는 스트림이 열려있는 상태에서 에러 응답을 시도하니 문제가 발생한 거 같았습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// GlobalExceptionHandler.java

    @ExceptionHandler(AsyncRequestTimeoutException.class)
    public void handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e) {
        log.debug(&quot;SSE 연결 시간 만료&quot;);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;해결&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;타임아웃은 에러가 아닌 만료이므로, 핸들러에 로그만 남기고 아무 응답을 하지 않도록 처리했습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Connection Abort&lt;/h1&gt;
&lt;p&gt;이제는 사용자가 창을 닫을 때마다 서버 로그에 에러가 발생했습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;org.springframework.web.context.request.async.AsyncRequestNotUsableException
Caused by: java.io.IOException: 현재 연결은 사용자의 호스트 시스템의 소프트웨어의 의해 중단되었습니다
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;원인 분석&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;이번에는 명확하게 메시지가 뜨는 것을 보아, 클라이언트 이탈이 발생한 거 같습니다.&lt;/li&gt;
&lt;li&gt;SSE은 단방향이지만, 어쨌든 WebSocket 처럼 연결되어있을테니까요&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;@Override
public void onMessage(Message message, byte[] pattern) {
    // ... 
    SseEmitter emitter = sseEmitterRepository.get(userId);

    if (emitter != null) {
        try {
            emitter.send(SseEmitter.event()
                    .name(&quot;notification&quot;)
                    .data(notificationContent));
        } catch (IOException e) {
            sseEmitterRepository.deleteById(userId);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;바로 문제의 원인으로 갔지만 처음에는 이해할 수 없었습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예외 처리를 했었으니까요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;로그를 조금 더 자세히 볼 필요가 있습니다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;AsyncRequestNotUsableException&lt;/code&gt;는 IOException으로 처리가 되는 것인가?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;저는 이 에러를 모르니 ctrl + 클릭으로 봤더니 &lt;code&gt;RuntimeException&lt;/code&gt; 이였습니다&lt;/p&gt;
&lt;p&gt;해당 문제는 &lt;code&gt;IOException&lt;/code&gt;으로 잡을 수 없는 문제였습니다..&lt;/p&gt;
&lt;h2&gt;해결&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;@Component
public class NotificationRedisSubscriber implements MessageListener {
    // ... 
    @Override
    public void onMessage(Message message, byte[] pattern) {
        SseEmitter emitter = sseEmitterRepository.get(userId);

        if (emitter != null) {
            try {
                emitter.send(SseEmitter.event()
                        .name(&quot;notification&quot;)
                        .data(notificationContent));
            } catch (Exception e) {
                sseEmitterRepository.deleteById(userId);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;예외 범위를 Exception으로 확장했습니다.&lt;/li&gt;
&lt;li&gt;해당 상황에선 어떻게해도 기존 연결로 메시지를 전송할 수 없을테니까요&lt;/li&gt;
&lt;li&gt;기존 연결은 버리고 새 연결을 생성하는 편이 맞는 거 같습니다&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>클라우드 객체 스토리지에 관해</title><link>https://blog.csyhorizon.dev/posts/2025/devops/cloud_storage/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/devops/cloud_storage/</guid><pubDate>Thu, 27 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;개체 스토리지&lt;/h1&gt;
&lt;p&gt;이 전에 국비 교육을 들었을 때 실무자 평가 때,
&apos;클라우드 객체 스토리지&apos; 사용에 대해 질의를 받았던 적이 있습니다. 사실 생각하지도 않았습니다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 무단으로 데이터를 올리는 건 어떻게 처리하나?&lt;/li&gt;
&lt;li&gt;Nginx에서 송수신 크기는 알고 있나?&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;/p&gt;
&lt;p&gt;아무튼 이 부분에 대해 하나씩 풀어서 뜯어보겠습니다.&lt;/p&gt;
&lt;h2&gt;사용자가 무단으로 데이터를 올리는 건 어떻게 처리하나?&lt;/h2&gt;
&lt;p&gt;이건 &lt;code&gt;Presigned URL&lt;/code&gt; 방식을 여쩌보는 거 였습니다.&lt;/p&gt;
&lt;p&gt;S3를 처음 쓰는 입장에선 이 개념이 전혀 없었습니다.&lt;/p&gt;
&lt;p&gt;당시에는 처음 도입하는지라 그냥 &apos;클라이언트&apos; -&amp;gt; &apos;서버 전송&apos; -&amp;gt; &apos;S3에 업로드&apos;로 팀원끼리 생각하고 있었기 때문입니다.&lt;/p&gt;
&lt;p&gt;이러한 방식은 단순한 모든 파일을 public으로 규칙을 설정하는 데 다음과 같은 장단점이 있었습니다&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;/p&gt;
&lt;h3&gt;Presigned URL이란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;사용자가 S3에 대한 접근 권한을 받기 위해 인증 후 올릴 수 있는 &apos;미리 서명된 권한&apos;을 발급받는 구조입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;쉽게 말하면 &lt;code&gt;임시 권한&lt;/code&gt;을 발급 받는데 이는 일정 시간이 지나면 만료되는 URL입니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;파일에 접근 가능한 임시 URL 생성&lt;/li&gt;
&lt;li&gt;지정한 사람만 공유 가능&lt;/li&gt;
&lt;li&gt;URL의 만료기간 설정 가능&lt;/li&gt;
&lt;li&gt;POST, GET 등 메소드 설정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;진짜 좀 알려주면 좋을텐데 개발자들은 직접 생각하게 하는 귀찮음을 교육 과정에서도 듣고 있는다는 것이 기분이 별로 좋진 않네요.&lt;/p&gt;
&lt;p&gt;다른 대학의 친구들도 딱히 생각해본 적 없었나봅니다. 인프라가 할 일이라 그런가..?&lt;/p&gt;
&lt;h2&gt;Nginx에서 송수신 크기는 알고 있나?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;1MB&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이건 &lt;code&gt;client_max_body_size&lt;/code&gt;로 크기 조정할 수 있다는데, 클라이언트한테 &lt;code&gt;Presigned URL&lt;/code&gt; 던져주고 업로드 하는 형태로 교체했습니다.
굳이 크기 늘려서 서버 부하의 가능성을 줄 필요는 없더라고요.&lt;/p&gt;
&lt;p&gt;이것 생각하니 게시글 최대 수도 계산해서 50만자 정도로 제한해둬야할 거 같습니다.&lt;/p&gt;
&lt;p&gt;장점은 운영 서버 부하가 줄어든다는 점이고, 단점은 비동기 검증 로직을 추가해야한다는 점이네요.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;속도나 안정성 측면은 후자이니 때문에 코드 복잡성은 늘어나도 이후로는 public으로 박는 짓은 안하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;누구나 볼 수 있다면 문제인데 이는 어떻게 처리했나?&lt;/h2&gt;
&lt;p&gt;이것도 결국 &lt;code&gt;Presigned URL&lt;/code&gt;이 정답이더라고요.&lt;/p&gt;
&lt;p&gt;그냥 실무자가 S3 썻다길래 기대하면서 Presigned-URL 썻는 지 코드 보고, 안써서 실망해서 물어본 거 같습니다.&lt;/p&gt;
&lt;p&gt;:::tip
개체 스토리지, S3에 대해 물어본다면?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Presigned URL을 생각하자
:::&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;아, 저는 비용 절감상 R2 쓰고 있는데 동일하게 S3 기반이여서 Presigned URL은 구현할 수 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;혹여나 S3만 된다 생각한다면 그건 아니라고 말씀드립니다. GCP나 Azure은 잘 모르겠습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>AI로 퀴즈를 만드는 방식</title><link>https://blog.csyhorizon.dev/posts/2025/ai/story/1/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/ai/story/1/</guid><description>내가 AI 써서 일본어 퀴즈를 만드는 방식</description><pubDate>Tue, 25 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;AI로 퀴즈를?&lt;/h1&gt;
&lt;p&gt;이번에 웹 개발을 진행한 서비스 옵션 중에 &lt;a href=&quot;https://jpstudy.org/jlcb-test&quot;&gt;JLCB TEST&lt;/a&gt;라는 서비스가 있다.&lt;/p&gt;
&lt;p&gt;이건 DB에 CBT 형태로 대량의 퀴즈를 저장해서 사전에 설정한 문제 개수만큼 사용자에게 퀴즈를 제공하는 서비스이다.&lt;/p&gt;
&lt;p&gt;근데 일본어의 히라가나, 가타가나만 겨우 아는 수준인 내가 이러한 서비스를 만들 수 있을리가 없다..
심지어 검증도 생각하면 배보다 배꼽이 더 큰 서비스 개발이다.&lt;/p&gt;
&lt;p&gt;그렇다면 이러한 서비스는 못만드는 것일까? 이 부분에 대해 이번에 생각을 적어보았다.&lt;/p&gt;
&lt;h2&gt;사용자 중점 퀴즈&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;사용자가 퀴즈를 만들고 다른 사용자가 맞추는 형태의 서비스는 어떠한가?
결론적으로는 운영자 입장에서는 정말 좋고 행복한 일이 아닐 수 없다. 근데 사용자는 어디서..?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;음.. 초기 서비스에선 사실상 불가능한 거 같다&lt;/p&gt;
&lt;h2&gt;JLPT 공식 사이트에서 문제 &amp;amp; 외부 유출된 문제 종합해서 출제&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;외부에 있는 데이터를 싹 모아서 재가공하면 어떠한가?
일단 한자, 단어 같은 경우 해당 방식으로 초기 구성을 완료했다. 이후에 목표를 타깃으로 잡고 수집하면 문장이나 추가적인 단어도 학습할 수 있다.
하지만 퀴즈는 아니다. 어떻게 이게 가능하겠냔 말이다..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;AI 사용&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Rule을 설정하고, AI로 생성하는 건 어떠한가?
초기 구성에는 정말 편하긴 하지만 제대로 된 데이터인지 이걸 내가 증명해야할 문제가 있다.
근데 과연 잘못된 답이 나올까?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;새로운 문장이 나올지언정 이미 있는 문장 또는 문제를 AI가 오답을 낼 가능성은 현저히 적다.
잘못된 문제 또는 정답이라면 사용자가 피드백 하는 형태로 고칠 수도 있는 것이다.&lt;/p&gt;
&lt;p&gt;그러면 이걸 베이스로 진행해보자&lt;/p&gt;
&lt;h1&gt;문제 구성&lt;/h1&gt;
&lt;p&gt;문제는 &apos;청해&apos;나 &apos;긴문장 읽기&apos; 그런 것도 있어서 고려할 게 많다. N5, N4 등 등급도 존재하고, 문제 유형도 다양해서 여기부터 생각해야하는데,
우선 이 부분은 DB 테이블로 구성할 수 있도록 개발하였다.&lt;/p&gt;
&lt;p&gt;그래서 생각하지 않도록 했다. (사전에 구성하는 것보다 유동성 있게 추가하는 편이 좋아보였기에)&lt;/p&gt;
&lt;h2&gt;데이터 구성&lt;/h2&gt;
&lt;p&gt;사실 여기부터 생각하면 된다. AI가 대충 만들 수 있게 단순하되, 확실한 구조여야한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;questionText&lt;/th&gt;
&lt;th&gt;explanation&lt;/th&gt;
&lt;th&gt;choice1&lt;/th&gt;
&lt;th&gt;choice1Corrent&lt;/th&gt;
&lt;th&gt;choice2&lt;/th&gt;
&lt;th&gt;choice2Corrent&lt;/th&gt;
&lt;th&gt;...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;今日はとても（　　）です。プールに入りたいです。&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;暑い&lt;/td&gt;
&lt;td&gt;TRUE&lt;/td&gt;
&lt;td&gt;寒い&lt;/td&gt;
&lt;td&gt;FALSE&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;이건 &apos;contextual_vocabulary&apos; 라는 문제 중 하나를 가져왔다.
일단 questionText는 문제 자체가 뭘 하라는 지 알 수 있도록하고, 정답은 사지선다 형태로 만든다.
&apos;explanation&apos;은 AI가 왜 그런 형태로 만들었는지? 사용자가 납득할만한 답을 달라고 한다.&lt;/p&gt;
&lt;p&gt;테이블에는 sound나 일부 추가적인 테이블 값을 넣을 수도 있지만 당장은 초기 구성이니 이렇게 했다.&lt;/p&gt;
&lt;h1&gt;문제 생성&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;문제 생성은 최근에 AI를 쓸 때 많이 정의하는 &apos;규칙 파일&apos;을 작성했다.
생성된 문제는 특별히 가공없이 그대로 적용될 수 있도록 구성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip
문제에 중복이나, 문제 유형이 다르다는 것을 어떻게 검증할 수 있나?
:::&lt;/p&gt;
&lt;p&gt;이건 &lt;code&gt;생성하는 문제&lt;/code&gt;와 &lt;code&gt;문제 유형&lt;/code&gt;을 내가 규칙으로 설정할 수 있다는 점에서 큰 문제가 없어진다.
핵심은 이 시험은 CBT 방식을 메인 로직으로 삼았다는 점이다.&lt;/p&gt;
&lt;p&gt;나는 &lt;code&gt;Level: JLPT-N5 Category: Kanji Reading Data: 日, 月, 火, 水, 木, 金, 土&lt;/code&gt; 라는 값을 초기에 입력하게되는데,
그러면 핵심 문제 유형은 저 범위에서 크게 벗어나지 않게된다.&lt;/p&gt;
&lt;p&gt;DB 내 한자의 개수는 대략 3000개 정도이므로, 1회 생성 시 대략 200개의 문제가 생성된다.
따라서 이론상 600,000개의 문제를 구성할 수 있다.&lt;/p&gt;
&lt;p&gt;이 정도 수치라면 중복이 발생되어도 동일한 문제의 가능성은 현저히 낮아지게 된다. 특별히 문제가 있다면 단어 영역에서의 생성이 아니므로
단어는 대략 30000개 정도되는데 이 중에 매칭되지 않는 문제가 생길 수 있다는 점이다.&lt;/p&gt;
&lt;p&gt;우선 초기 기반은 마련했으니 AI 규칙 파일을 작성한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Role
You are a strictly defined &quot;JLPT Exam Data Generator&quot;. Your goal is to convert provided lists of Kanji or Vocabulary into JLPT N5 level quiz data in a specific CSV format.

# Input Format
I will provide a request in this format:
1. Level: (e.g., JLPT-N5)
2. Category: (e.g., Kanji Reading, Orthography, Contextual Vocabulary, Sentence Composition, etc.)
3. Data: (List of Kanji, Words, or Topic)

# Output Format (CSV)
You must output ONLY the CSV data block. Do not output code blocks or explanations unless asked.
Headers: `questionText,explanation,choice1,choice1Correct,choice2,choice2Correct,choice3,choice3Correct,choice4,choice4Correct`

# Critical Constraints (MUST FOLLOW)
1. **No ASCII Commas in Text:** You must strictly replace all commas (`,`) within the Japanese text with Japanese commas (`、`) to prevent CSV parsing errors. The only ASCII commas allowed are the field delimiters.
2. **No Quotes:** Do not wrap text fields in double quotes (`&quot;`). Keep the CSV raw.
3. **Language:** All content (Questions, Choices, Explanations) must be in Japanese suitable for the requested JLPT level.
4. **One Line Per Record:** No newline characters (`\n`) within a single row.
5. **Boolean:** Use `TRUE` for the correct answer and `FALSE` for distractors.

# Category Specific Rules
1. **Kanji Reading:** Question asks for reading of Kanji in brackets 【 】. Choices are Hiragana.
2. **Orthography:** Question asks for Kanji of Hiragana in brackets 【 】. Choices are Kanji (include similar-looking wrong Kanji).
3. **Contextual Vocabulary:** Fill in the blank ( ) based on context.
4. **Paraphrases:** Find the sentence with the same meaning.
5. **Grammar Form:** Particle or conjugation selection.
6. **Sentence Composition:** The &quot;Star Problem&quot; ( ★ ). `questionText` must look like `( ) ( ) ( ★ ) ( )`. The correct choice is the word that falls on the ★ position after rearranging.
7. **Reading (Short/Mid/Info):** Do NOT create a separate `passageText` column. Combine the passage and the question into the `questionText` column. (Format: Passage string + Japanese space + Question string).

# Example Interaction
User:
Level: JLPT-N5
Category: Kanji Reading
Data: 車, 犬

AI:
questionText,explanation,choice1,choice1Correct,choice2,choice2Correct,choice3,choice3Correct,choice4,choice4Correct
新しい【車】を買いました。,「車」の読み方は「くるま」です。,くるま,TRUE,くろま,FALSE,くうま,FALSE,ぐるま,FALSE
公園に【犬】がいます。,「犬」の読み方は「いぬ」です。,いぬ,TRUE,ねこ,FALSE,うぬ,FALSE,いの,FALSE
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;결과&lt;/h2&gt;
&lt;p&gt;학습을 AI로 한다는 점에서 이게 정확한 지 알 수는 없다. AI가 유추해서 만들었다면 유사한 정답에 가까운 값을 내겠지만, 오히려 틀리는 경우가 있을 수 있다.&lt;/p&gt;
&lt;p&gt;이러한 방식은 절대적으로 사용자가 문제들을 검증하거나 오류가 생길 수 있으므로 정기적으로 문제의 퀄리티를 조사할 필요는 있다.&lt;/p&gt;
</content:encoded></item><item><title>로그인 로직 리펙토링</title><link>https://blog.csyhorizon.dev/posts/2025/spring/refactor/1/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/spring/refactor/1/</guid><description>로그인 서비스 리펙토링 진행</description><pubDate>Sun, 23 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이번에 일본어 학습 사이트 만들면서 1달의 기간동안 대략 1만줄의 코드를 작성했습니다.
순수 백엔드만이며, 프론트, 관리자 페이지, 인프라 영역 코드까지 포함하면 더 길어갑니다&lt;/p&gt;
&lt;p&gt;제법 코드 구조를 생각하면서 작성했지만 막상 다시 읽어보면 코드가 500줄 넘어가고, 유지 보수 측면에서 조정해야할 필요를 느끼고 리펙토링 과정을 작성해보았습니다.&lt;/p&gt;
&lt;p&gt;프로젝트 전체 코드를 수정했지만, 공개는 Auth 부분만 진행합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
@Transactional
public class AuthService {

    private final MemberRepository memberRepository;
    private final PasswordEncoder passwordEncoder;
    private final JwtProvider jwtProvider;
    private final AuthenticationManager authenticationManager;
    private final UsernameValidator usernameValidator;

    private final RedisTemplate&amp;lt;String, String&amp;gt; authRedisTemplate;
    private final NotificationService notificationService;

    private final EmailService emailService;
    private final LoginHistoryRepository loginHistoryRepository;

    @Transactional
    public TokenResponse signUp(SignUpRequest requestDto, String ipAddress, String userAgent) {

        usernameValidator.validate(requestDto.getUsername());

        final String encryptedPassword = passwordEncoder.encode(requestDto.getPassword());
        final LocalMember newMember = new LocalMember(
                requestDto.getEmail(),
                requestDto.getUsername(),
                encryptedPassword,
                Role.USER
        );

        (...)

        return new TokenResponse(accessToken, refreshToken, userName, refreshTokenValidityMs);
    }

    public TokenResponse login(LoginRequest requestDto, String ipAddress, String userAgent) {

        String lockoutKey = LOGIN_FAIL_PREFIX + requestDto.getEmail();
        String currentFailCountStr = authRedisTemplate.opsForValue().get(lockoutKey);

        if (currentFailCountStr != null) {
            int failCount = Integer.parseInt(currentFailCountStr);
            if (failCount &amp;gt;= MAX_LOGIN_ATTEMPTS) {
                long expireTimeMinutes = authRedisTemplate.getExpire(lockoutKey, TimeUnit.MINUTES);
                long remainTime = expireTimeMinutes &amp;gt; 0 ? expireTimeMinutes + 1 : LOCKOUT_DURATION_MINUTES;

                throw new CustomException(ErrorCode.ACCOUNT_LOCKED, remainTime);
            }
        }

        (...)

        return new TokenResponse(accessToken, refreshToken, userName, refreshTokenValidityMs);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로그인 로직을 짜면 저처럼 복잡한 분도 있고, 애초에 깔끔하게 잘 작성한 분도 있으실텐데 단기간에 빠르게 작성하다보면 의식은 하고 있지만 이게 말처럼 잘 안되더라고요.&lt;/p&gt;
&lt;h1&gt;리펙토링 시작&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;우선은 뭐부터 해야할까? 생각해보면, 이름 규칙부터 설정해야겠더라고요&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;네이밍 컨벤션 시작&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;    @Transactional
    public TokenResponse signUp(SignUpRequest requestDto, String ipAddress, String userAgent) {
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사실 &apos;카멜 케이스&apos;나 폴더는 &apos;스네이크 케이스&apos; 그런 네이밍 적인 요소는 다 통일이 되어 있습니다.&lt;/p&gt;
&lt;p&gt;다만, dto의 경우 &lt;code&gt;SignUpRequestDto로&lt;/code&gt; 되어있는 파일도 있고, &lt;code&gt;SignResponse&lt;/code&gt;로 되어있는 파일도 있습니다. 파일에는 과연 &apos;dto&apos;를 붙이는 게 좋냐? 안붙이는 게 좋냐? 는 단순한 생각부터 시작합니다.&lt;/p&gt;
&lt;p&gt;사용할 때는 그래도 있는 편이 네이밍에서 헷갈림 없지 않냐?는 생각도 들지만, Request, Response가 있는 시점부터 굳이? 붙일 필요는 없더라고요.&lt;/p&gt;
&lt;p&gt;우테코 프리코스에 참여하다보니 폴더명에 이미 포함되어있다면, 이게 무엇인 지 인지할 수 있다면 굳이 필요없다. 라는 말을 듣고 dto를 전부 제거하는 것부터 시작했습니다.&lt;/p&gt;
&lt;p&gt;호출할 때도 &lt;code&gt;requestDto&lt;/code&gt;가 아닌 &lt;code&gt;request&lt;/code&gt;로 변환했습니다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;그러면 Request가 2개인 경우라면?
정말 그런 경우가 있을까 싶은데, 접속한 사용자의 PC정보가 IP, 언어 기반을 받을 때 우연찮게 2개가 겹치는 경우가 있긴 했었습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;    public ResponseEntity&amp;lt;AccessTokenResponse&amp;gt; signUp(
            @Valid @RequestBody SignUpRequest request,
            HttpServletResponse httpServletResponse,
            HttpServletRequest httpServletRequest
    ) {
        ...
        return authResponseHelper.createTokenResponse(token, response);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그런 경우에는 얘는 내가 만든 거니, &lt;code&gt;httpServletRequest&lt;/code&gt;처럼 이름 그대로 유추 가능하도록 이름을 붙이기로 했습니다.&lt;/p&gt;
&lt;h3&gt;컴포넌트 분리&lt;/h3&gt;
&lt;p&gt;&apos;&lt;code&gt;AuthService&lt;/code&gt;에는 너무 많은 역할을 부여했다`고 생각합니다.&lt;/p&gt;
&lt;p&gt;그런 생각이 든건 &apos;로그인 로직을 없애는 일&apos;은 없겠지만, &apos;수정하거나 추가&apos;는 있을 수 있는데, 이걸 그대로 쓰면 이후의 코드 구조는 난잡해질 거 같았습니다.&lt;/p&gt;
&lt;p&gt;당장 리펙토링 시작 시점에는 &lt;code&gt;OAuthService&lt;/code&gt;와 &lt;code&gt;AuthService&lt;/code&gt; 그리고 &lt;code&gt;AdminAuthService&lt;/code&gt; 정도로 분리되었고 따로 component는 없는 상태입니다. 그래서 분리가 중요한 시점이더라고요.&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;가볍게 생각하니 딱 3가지 정도로 떠오르네요.&lt;/p&gt;
&lt;p&gt;줄이면 &lt;code&gt;얘가 어떤걸 하는 놈인가?&lt;/code&gt;입니다.&lt;/p&gt;
&lt;p&gt;Email을 담당하는지? SSE를 보내는 지? OAuth에서만 쓰는지? 토큰을 발송하는 지? 뭐 그런 요소로 componet를 나눠보았습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;LoginHistoryRecorder&lt;/li&gt;
&lt;li&gt;LoginLockManager&lt;/li&gt;
&lt;li&gt;OAuth2Client&lt;/li&gt;
&lt;li&gt;OAuthMemberManager&lt;/li&gt;
&lt;li&gt;PasswordResetManager&lt;/li&gt;
&lt;li&gt;TokenManager&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;우선 이렇게 나눠봤는데, 분리하는 기준은 우테코에서 정말 좋아하는 &apos;15줄 이하, indent 2 이하, else 금지&apos; 정도만 지켜서 분리했는데 생각보다 나쁘지 않더라고요.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /**
     * 로그인 시도 전, 계정이 잠겨있는 지 확인
     * @param email Email
     */
    public void validateNotLocked(String email) {
        String key = getKey(email);
        String currentFailCountStr = authRedisTemplate.opsForValue().get(key);

        if (currentFailCountStr != null &amp;amp;&amp;amp; Integer.parseInt(currentFailCountStr) &amp;gt;= MAX_LOGIN_ATTEMPTS) {
            long expireTime = authRedisTemplate.getExpire(key, TimeUnit.MINUTES);
            long remainTime = expireTime &amp;gt; 0 ? expireTime + 1 : LOCKOUT_DURATION_MINUTES;

            throw new CustomException(ErrorCode.ACCOUNT_LOCKED, remainTime);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;예로 해당 코드는 로그인 계정이 잠겨있는 지 확인하는 코드입니다.
기존에는 Service에 각각 담겨서, 코드 중복이 3번이나 있었던 것을 공통 로직으로 줄여서 유지 비용을 줄였습니다.&lt;/p&gt;
&lt;p&gt;값들은 매번 코드에서 찾아서 넣기보다, 상수처리했습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;분리 결과&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;    public TokenResponse login(LoginRequest request, String ipAddress, String userAgent) {
        loginLockManager.validateNotLocked(request.getEmail());

        Member member = authenticateUser(request);

        loginLockManager.validateMemberStatus(member);

        loginLockManager.resetFailureCount(request.getEmail());
        loginHistoryRecorder.save(member.getId(), ipAddress, userAgent);

        return tokenManager.issueTokens(member);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기존 로그인 로직의 길이가 100줄에서 15줄 미만으로 줄었다는 점이 개발자 입장에선 정말 칭찬할만하더군요.&lt;/p&gt;
&lt;p&gt;뭐가 좋아졌냐? 묻는다면 제가 추후에 뭐 수정해야할 때 그냥 스트레스가 적어질 거 같더라고요.
이런 형태로 싹다 바꾸면 단위 테스트 작성하는 것도 복잡성이 줄어서 깔끔하게 되는 거 같습니다.&lt;/p&gt;
&lt;h3&gt;주석&lt;/h3&gt;
&lt;p&gt;분리를 하고보니 고칠 걸 바로 찾고 싶은데 과연 바로 찾아질까? 하는 의문이 생겼습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예로 당장은 괜찮지만 추후에 무언가 추가하면 &apos;여기&apos;도 진행해야한다.&lt;/li&gt;
&lt;li&gt;이 곳은 무언가 빠졌는데 당장 우선순위는 낮지만 꼭 해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이런 게 조금 있더라고요. 보통 바로 바로 처리할 수 없고 다른 로직이 개발되어야 할 수 있는 부분이 있어서 머리로만 기억하는 건 안그래도 머리 안좋은데 어떻게 합니까..&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /**
     * 시험 시작
     * TODO: 여긴 왜 DTO안쓰고 그냥 값들 가져오고 있지?
     */
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;인텔리제이는 TODO로 주석을 걸 수 있는데 이걸 모아서 볼 수 있습니다.
이거 말고 또 다른 방식도 있었는데 보통 TODO를 많이 쓴다고 하더라고요&lt;/p&gt;
&lt;p&gt;그래서 리펙토링 과정에서 &apos;추가&apos; 또는 &apos;수정&apos;이 필요한 건 주석으로 일단 표기하고 이후에 이어서 작업하고 있습니다.&lt;/p&gt;
&lt;h3&gt;Transactional은 만능이 아님&lt;/h3&gt;
&lt;p&gt;사실 처음에는 Service에 무작정 Transactional을 달았습니다. 근데 그게 만능은 아니라고하네요.&lt;/p&gt;
&lt;p&gt;무결성, 일관성을 보장해야한다면 롤백 과정때문에 필요하지만, 그렇지 않다면 오히려 DB 연결을 물고 있어서 성능 저하가 발생할 수 있다고합니다.&lt;/p&gt;
&lt;p&gt;사실 이건 테스트를 하고 싶어도 부하 테스트로는 N100으로는 CPU가 먼저 한계점에 도달해버리기에.. 차차 생각해보려고 하는데 일단 Auth 영역은 &apos;읽기 작업&apos;만 필요한 경우 Transactional(readOnly = true)로 조회용으로 선언하고 메모리를 절약하도록 했습니다.&lt;/p&gt;
&lt;h2&gt;완료&lt;/h2&gt;
&lt;p&gt;사실 이 정도면 리펙토링 적절하게 진행하지 않았나 싶습니다.&lt;/p&gt;
&lt;p&gt;성능 개선도 이후에 할 거 생각하면 할 게 많네요.
부지런히 해야할 거 같습니다&lt;/p&gt;
</content:encoded></item><item><title>아호-코라식</title><link>https://blog.csyhorizon.dev/posts/2025/algorithm/ahocorasick/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/algorithm/ahocorasick/</guid><description>악성 게시글 필터링을 위한 아호-코라식 이해</description><pubDate>Sun, 09 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;아호-코라식 (Aho-Corasick)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;사실 근시일 내로 도전할 일이 없을 줄 알았지만, 우테코에서 이러한 기회를 준 것에 대해 감사하게 도전&lt;/li&gt;
&lt;li&gt;아호-코라식은 우테코에서 &apos;고난도 문제&apos;를 제시하였기 때문에 이러한 주제를 진행함&lt;/li&gt;
&lt;li&gt;ACPC에서 뼈도 못추린 &apos;아호-코라식&apos;은 분명 넘어야 할 벽 중 하나&lt;/li&gt;
&lt;li&gt;이는 도전해야할 목록 중 하나에 있었고 이번 기회에 도전하게 되었음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;아호 코라식이란?&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;현재 광범위하게 알려진 거의 유일한 일대다 패터매칭 알고리즘&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;KMP의 확장 버전이며, &apos;트라이&apos;와 &apos;KMP&apos;의 선행 지식이 요구&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;선행 지식&lt;/h1&gt;
&lt;h2&gt;트라이 (Trie)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;트라이는 여러 개의 문자열을 효율적으로 저장하고 &apos;접두사&apos;를 빠르게 찾기 위한 자료구조&lt;/li&gt;
&lt;li&gt;예로 &apos;바보&apos;, &apos;바1보&apos;, &apos;바bo&apos;와 같이 &apos;공통된 접두사&apos;를 가진 단어가 많을 때 큰 효율을 가져옴&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;핵심 구조&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;트라이는 &apos;트라이 노드(TrieNode)&apos;와 &apos;루트 노드(root)&apos; 2가지가 핵심 요소
&lt;ul&gt;
&lt;li&gt;트라이 노드는 &apos;자식 노드&apos;의 맵(Map)을 가지고 있으며, 이는 경로(간선) 정보&lt;/li&gt;
&lt;li&gt;단어의 끝에는 &lt;code&gt;isEndOfWord&lt;/code&gt; 끝이라는 표시가 있음
&lt;ul&gt;
&lt;li&gt;예로 &apos;car&apos;, &apos;card&apos;를 저장하면, &apos;r&apos;과 &apos;d&apos;에 &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;이외 c와 a는 노드는 &lt;code&gt;false&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;루트 노드는 모든 검색과 삽입이 시작되는 &apos;출발점&apos;
&lt;ul&gt;
&lt;li&gt;해당 root 노드에는 아무 글자도 의미하지 않는 빈 노드를 가지고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동작 구조는 다음과 같음
&lt;ol&gt;
&lt;li&gt;&apos;car&apos; 삽입 시
&lt;ol&gt;
&lt;li&gt;root 에서 시작&lt;/li&gt;
&lt;li&gt;root에 &apos;c&apos;가 있는 지 확인 (없음)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;c 노드를 새로 만들고 &apos;c&apos;와 연결&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&apos;c&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;&apos;c&apos;에 &apos;a&apos;가 있는 지 확인 (없음)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a 노드를 새로 만들고 &apos;a&apos;와 연결&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&apos;a&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;&apos;a&apos;에 &apos;r&apos;가 있는 지 확인 (없음)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;r 노드를 새로 만들고 &apos;r&apos;와 연결&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&apos;r&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;&apos;car&apos; 단어 완료 &apos;r&apos;에 &lt;code&gt;inEndOfWord = true&lt;/code&gt;로 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&apos;cat&apos; 삽입 시
&lt;ol&gt;
&lt;li&gt;root 에서 시작&lt;/li&gt;
&lt;li&gt;root에 &apos;c&apos;가 있는 지 확인 (있음)&lt;/li&gt;
&lt;li&gt;&apos;c&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;&apos;c&apos;에 &apos;a&apos;가 있는 지 확인 (있음)&lt;/li&gt;
&lt;li&gt;&apos;a&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;&apos;a&apos;에 &apos;t&apos;가 있는 지 확인 (없음)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;t 노드를 새로 만들고 &apos;t&apos;와 연결&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&apos;t&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;&apos;cat&apos; 단어 완료 &apos;t&apos;에 &lt;code&gt;inEndOfWord = true&lt;/code&gt;로 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시간 복잡도&lt;/h3&gt;
&lt;h4&gt;&lt;code&gt;Trie&lt;/code&gt;의 모든 연산은 &lt;strong&gt;찾으려는 단어 길이&lt;/strong&gt;에만 의존&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;L = 찾으려는 단어의 길이&lt;/li&gt;
&lt;li&gt;N = &lt;code&gt;Trie&lt;/code&gt;에 이미 저장된 총 단어의 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;삽입&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- 시간 복잡도 : O(L)
- 단어의 길이 L 만큼만 노드를 따라가거나 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;검색&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- 시간 복잡도 : O(L)
- 단어의 길이 L 만큼만 노드를 따라가거나 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;접두사 검색&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- 시간 복잡도 : O(L)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;공간 복잡도&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;O(N X L_avg) (N: 총 단어 수, L_avg: 평균 단어 길이)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Trie&lt;/code&gt;에 생성되는 총 노드의 개수에 비례&lt;/li&gt;
&lt;li&gt;공동 접두사가 많으면 메모리가 절약되지만 그렇지 않다면 상당한 낭비가 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.HashMap;
import java.util.Map;

/**
 * 트라이의 개별 노드를 나타내는 클래스
 */
class TrieNode {

    /**
     * 자식 노드들을 저장하는 맵
     * key: 문자 (Character)
     * value: 해당 문자에 해당하는 자식 노드 (TrieNode)
     */
    private final Map&amp;lt;Character, TrieNode&amp;gt; children = new HashMap&amp;lt;&amp;gt;();

    /**
     * 이 노드에서 끝나는 단어가 있는지 여부를 표시하는 깃발
     */
    private boolean isEndOfWord;

    Map&amp;lt;Character, TrieNode&amp;gt; getChildren() {
        return children;
    }

    boolean isEndOfWord() {
        return isEndOfWord;
    }

    void setEndOfWord(boolean endOfWord) {
        isEndOfWord = endOfWord;
    }
}

/**
 * 트라이 (Trie) 자료구조 클래스
 */
public class Trie {

    private final TrieNode root;

    /**
     * Trie 생성자. 루트 노드를 초기화
     */
    public Trie() {
        root = new TrieNode();
    }

    /**
     * 트라이에 새로운 단어를 삽입
     * 시간 복잡도: O(L) - L은 단어의 길이
     *
     * @param word 삽입할 단어
     */
    public void insert(String word) {
        TrieNode current = root;

        for (char ch : word.toCharArray()) {
            current = current.getChildren()
                             .computeIfAbsent(ch, c -&amp;gt; new TrieNode());
        }
        
        current.setEndOfWord(true);
    }

    /**
     * 트라이에서 특정 단어가 &apos;완전히&apos; 일치하는지 검색
     * 시간 복잡도: O(L) - L은 단어의 길이
     *
     * @param word 검색할 단어
     * @return 단어가 존재하고, 해당 노드가 &apos;단어의 끝&apos;이면 true, 아니면 false
     */
    public boolean search(String word) {
        
        TrieNode node = findNode(word);
        
        return node != null &amp;amp;&amp;amp; node.isEndOfWord();
    }

    /**
     * 트라이에서 특정 &apos;접두사&apos;로 시작하는 단어가 있는지 검색
     * 시간 복잡도: O(L) - L은 접두사의 길이
     *
     * @param prefix 검색할 접두사
     * @return 접두사에 해당하는 노드 경로가 존재하면 true, 아니면 false
     */
    public boolean startsWith(String prefix) {
        return findNode(prefix) != null;
    }

    /**
     * 문자열을 따라 트라이를 탐색하여 마지막 노드를 찾는 내부 헬퍼 메서드
     *
     * @param str 검색할 문자열 (단어 또는 접두사)
     * @return 문자열의 마지막 문자에 해당하는 노드. 경로가 없으면 null 반환
     */
    private TrieNode findNode(String str) {
        TrieNode current = root;
        for (char ch : str.toCharArray()) {
            TrieNode node = current.getChildren().get(ch);
            
            if (node == null) {
                return null;
            }
            current = node;
        }
        return current;
    }

    public static void main(String[] args) {
        Trie trie = new Trie();

        // 1. 삽입
        trie.insert(&quot;apple&quot;);
        trie.insert(&quot;apply&quot;);
        trie.insert(&quot;banana&quot;);
        trie.insert(&quot;bat&quot;);

        // 2. 검색
        System.out.println(&quot;trie.search(\&quot;apple\&quot;): &quot; + trie.search(&quot;apple&quot;));   // true
        System.out.println(&quot;trie.search(\&quot;app\&quot;): &quot; + trie.search(&quot;app&quot;));     // false (&quot;app&quot; 자체는 삽입 안 함)
        System.out.println(&quot;trie.search(\&quot;apply\&quot;): &quot; + trie.search(&quot;apply&quot;));   // true
        System.out.println(&quot;trie.search(\&quot;batman\&quot;): &quot; + trie.search(&quot;batman&quot;)); // false

        // 3. 접두사 검색
        System.out.println(&quot;trie.startsWith(\&quot;app\&quot;): &quot; + trie.startsWith(&quot;app&quot;));   // true (&quot;apple&quot;, &quot;apply&quot;)
        System.out.println(&quot;trie.startsWith(\&quot;ban\&quot;): &quot; + trie.startsWith(&quot;ban&quot;));   // true (&quot;banana&quot;)
        System.out.println(&quot;trie.startsWith(\&quot;cat\&quot;): &quot; + trie.startsWith(&quot;cat&quot;));   // false
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;KMP&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;KMP는 하나의 긴 텍스트에서 &apos;하나의 특정 패턴&apos;을 매우 빠르게 찾는 알고리즘&lt;/li&gt;
&lt;li&gt;검색에 실패했을 때, 텍스트 포인터는 절대 뒤로 돌리지 않고 &apos;패턴 포인터&apos;만 점프시킴&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;핵심 구조&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;KMP&lt;/code&gt;의 핵심 요소는 &lt;code&gt;pi&lt;/code&gt;배열(실패 함수)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pi&lt;/code&gt; 배열은 &lt;strong&gt;패턴 문자열&lt;/strong&gt; 자신을 미리 분석하여 만드는 &apos;점프 맵(Map)&apos;&lt;/li&gt;
&lt;li&gt;이 배열은 검색 중 &lt;code&gt;i + 1&lt;/code&gt;에서 실패하면 다음엔 &lt;code&gt;pi[i]&lt;/code&gt;번째부터 이어서 비교를 진행함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;pi[i]&lt;/code&gt;는 패턴의 &apos;0&apos;부터 &apos;i&apos;번째까지의 부분 문자열에서 &apos;접두사와 접미사&apos;가 일치하는 최대 길이&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;예로 &apos;ABAB&apos;의 접두사 (&apos;A&apos;, &apos;AB&apos;, &apos;ABA&apos;)와 접미사 (&apos;B&apos;, &apos;AB&apos;, &apos;BAB&apos;)가 일치하는 최대 길이는 &apos;AB&apos;이므로, &lt;code&gt;pi[3] = 2&lt;/code&gt;가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;동작 구조는 다음과 같음&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;pi&lt;/code&gt; 배열 생성 (전처리)
&lt;ul&gt;
&lt;li&gt;패턴 &apos;ABABC&apos;의 &lt;code&gt;pi&lt;/code&gt;배열을 계산&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i=0&lt;/code&gt; &quot;A&quot;: 항상 0
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pi[0] = 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i=1&lt;/code&gt; &quot;AB&quot;: 접두사 &apos;A&apos;와, 접미사 &apos;B&apos; -&amp;gt; 일치 없음
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pi[1] = 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i=2&lt;/code&gt; &quot;ABA&quot;: 접두사(&apos;A&apos;, &apos;AB&apos;)와, 접미사(&apos;A&apos;, &apos;BA&apos;) -&amp;gt; &apos;A&apos; 일치
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pi[2] = 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i=3&lt;/code&gt; &quot;ABAB&quot;: 접두사(&apos;A&apos;,&apos;AB&apos;,&apos;ABA&apos;)와, 접미사(&apos;B&apos;,&apos;AB&apos;,&apos;BAB&apos;) -&amp;gt; &apos;AB&apos; 일치
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pi[3] = 2&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;i=4&lt;/code&gt; &quot;ABABC&quot;: 접두와와 접미사 일치 없음
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pi[4] = 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&quot;ABABC&quot;의 &lt;code&gt;pi&lt;/code&gt; 배열 = &lt;code&gt;[0, 0, 1, 2, 0]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;검색
&lt;ul&gt;
&lt;li&gt;텍스트: ABABDABABC...&lt;/li&gt;
&lt;li&gt;패턴: ABABC&lt;/li&gt;
&lt;li&gt;i(텍스트 포인터), j(패턴 포인터)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;i&lt;/th&gt;
&lt;th&gt;j&lt;/th&gt;
&lt;th&gt;일치 여부&lt;/th&gt;
&lt;th&gt;이동&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&apos;A&apos; 일치&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i++&lt;/code&gt;, &lt;code&gt;j++&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&apos;B&apos; 일치&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i++&lt;/code&gt;, &lt;code&gt;j++&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&apos;A&apos; 일치&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i++&lt;/code&gt;, &lt;code&gt;j++&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&apos;B&apos; 일치&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i++&lt;/code&gt;, &lt;code&gt;j++&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&apos;D&apos; != &apos;C&apos; 불일치&lt;/td&gt;
&lt;td&gt;i는 4 고정, j는 &lt;code&gt;pi[j-1]&lt;/code&gt;로 이동 / &lt;code&gt;j = pi[4-1] = pi[3] = 2&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&apos;D&apos;와 &apos;A&apos;의 비교 불일치&lt;/td&gt;
&lt;td&gt;j를 &lt;code&gt;pi[j-1]&lt;/code&gt;로 이동 / &lt;code&gt;j = pi[2-1] = pi[1] = 0&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;code&gt;D&lt;/code&gt;와 &lt;code&gt;A&lt;/code&gt;의 비교 불일치&lt;/td&gt;
&lt;td&gt;j가 0이므로 &lt;code&gt;i++&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&apos;A&apos; 일치&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i++&lt;/code&gt;, &lt;code&gt;j++&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시간 복잡도&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;N&lt;/code&gt; = 텍스트의 길이&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M&lt;/code&gt; = 패턴의 길이&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;배열 생성 (전처리)&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- 시간 복잡도 : 0(M)
- 패턴의 길이 &apos;M&apos; 만큼 1번 순회하여 &apos;pi&apos; 배열 생성
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;검색&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- 시간 복잡도 O(M)
- 텍스트의 길이 &apos;N&apos; 만큼만 1번 순회
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;총 시간 복잡도&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- O(N + M)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;공간 복잡도&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;O(M) (패턴의 길이)&lt;/li&gt;
&lt;li&gt;오직 &lt;code&gt;pi&lt;/code&gt; 배열을 저장할 공간이 필요&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KMP&lt;/code&gt;는 하나의 패턴을 찾는 데 엄청난 속도를 보여주지만, &lt;code&gt;K&lt;/code&gt;개의 패턴을 찾으려면 O(N + M) 연산을 &lt;code&gt;K&lt;/code&gt;번 반복해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.ArrayList;
import java.util.List;

public class KMP {

    /**
     * KMP의 핵심인 &apos;pi&apos; 배열 (실패 함수)을 생성
     * pi[i] = pattern[0...i]의 접두사==접미사 최대 길이
     *
     * @param pattern 검색할 패턴 문자열
     * @return 계산된 pi 배열
     */
    private int[] getPi(String pattern) {
        int m = pattern.length();
        int[] pi = new int[m];

        int j = 0; 
        
        for (int i = 1; i &amp;lt; m; i++) {
            while (j &amp;gt; 0 &amp;amp;&amp;amp; pattern.charAt(i) != pattern.charAt(j)) {
                j = pi[j - 1];
            }

            if (pattern.charAt(i) == pattern.charAt(j)) {
                pi[i] = ++j;
            }
        }
        return pi;
    }

    /**
     * KMP 알고리즘을 사용해 텍스트에서 패턴을 검색
     *
     * @param text    전체 텍스트
     * @param pattern 찾을 패턴
     * @return 패턴이 시작되는 모든 인덱스의 리스트
     */
    public List&amp;lt;Integer&amp;gt; search(String text, String pattern) {
        List&amp;lt;Integer&amp;gt; foundIndices = new ArrayList&amp;lt;&amp;gt;();
        int[] pi = getPi(pattern);

        int n = text.length();
        int m = pattern.length();
        
        int j = 0;

        for (int i = 0; i &amp;lt; n; i++) {
            while (j &amp;gt; 0 &amp;amp;&amp;amp; text.charAt(i) != pattern.charAt(j)) {
                j = pi[j - 1];
            }

            if (text.charAt(i) == pattern.charAt(j)) {
                if (j == m - 1) {
                    foundIndices.add(i - m + 1);
                    j = pi[j];
                } else {
                    j++;
                }
            }
        }
        return foundIndices;
    }

    public static void main(String[] args) {
        KMP kmp = new KMP();
        String text = &quot;ABABCABABCDA&quot;;
        String pattern = &quot;ABABC&quot;;

        List&amp;lt;Integer&amp;gt; result = kmp.search(text, pattern);
        System.out.println(&quot;패턴 발견 위치: &quot; + result); // [0, 5]
        
        text = &quot;AAAAABAAABA&quot;;
        pattern = &quot;AAAA&quot;;
        result = kmp.search(text, pattern);
        System.out.println(&quot;패턴 발견 위치: &quot; + result); // [0, 1, 7]
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;아호-코라식&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;여러 개의 패턴을 하나의 긴 텍스트에서 단 한번의 순회로 모두 찾아내는 알고리즘&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Trie&lt;/code&gt;는 여러 패턴을 저장&lt;/li&gt;
&lt;li&gt;&lt;code&gt;KMP&lt;/code&gt;는 일치하지 않을 때 점프하고 찾아가는 역활&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;핵심 구조&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;아호-코라식은 &lt;code&gt;Trie&lt;/code&gt; 구조를 그대로 사용하되, &lt;code&gt;TrieNode&lt;/code&gt;에 KMP의 점프 기능을 사용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TrieNode&lt;/code&gt;의 추가 요소
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Failure Link&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;KMP의 &lt;code&gt;pi&lt;/code&gt; 배열 역할&lt;/li&gt;
&lt;li&gt;현재 노드에서 다음 글자로 가는 길이 없을 때, 대신 탐색을 이어갈 &quot;가장 가능성이 높은 다른 노드&quot;&lt;/li&gt;
&lt;li&gt;&quot;가장 가능성이 높은 다른 노드&quot;는 &quot;현재까지 일치한 문자열의 가장 긴 접미사&quot; 이면서 &quot;다른 패턴의 접두사&quot;인 노드&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Output Link&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;현재 노드에서 실패 링크를 타고 갔을 때, 단어의 끝을 만난다면 그 노드를 가리킴&lt;/li&gt;
&lt;li&gt;예로 &lt;code&gt;she&lt;/code&gt;를 찾았을 때, &lt;code&gt;e&lt;/code&gt; 노드를 타고 가면 &lt;code&gt;he&lt;/code&gt;의 e노드 isEndOfWord를 만날 수 있으며, 이러면 숨겨진 일치를 빠르게 찾을 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;동작 구조&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;아호-코라식은 &lt;code&gt;전처리&lt;/code&gt;와 &lt;code&gt;검색&lt;/code&gt; 둘로 나눠서 진행해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;전처리&lt;/h4&gt;
&lt;p&gt;-&lt;code&gt;Trie&lt;/code&gt;를 만든 후, &lt;code&gt;BFS&lt;/code&gt;를 이용해 모든 노드의 &lt;code&gt;Failure Link&lt;/code&gt;를 설정&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;패턴 삽입 : 모든 금지어를 &lt;code&gt;Trie&lt;/code&gt;에 삽입&lt;/li&gt;
&lt;li&gt;실패 링크 구축 (BFS)
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;root&lt;/code&gt; 노드에 큐를 넣음
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;root&lt;/code&gt;의 실패 링크는 null 혹은 자기 자신&lt;/li&gt;
&lt;li&gt;BFS를 돌면서 큐에서 노드 &lt;code&gt;P&lt;/code&gt;를 꺼냄&lt;/li&gt;
&lt;li&gt;&lt;code&gt;P&lt;/code&gt;의 모든 자식 노드 &lt;code&gt;C&lt;/code&gt;의 경우
&lt;ul&gt;
&lt;li&gt;실패 링크를 가리키는 노드 &lt;code&gt;F&lt;/code&gt;로 점프&lt;/li&gt;
&lt;li&gt;&lt;code&gt;F&lt;/code&gt;에도 x의 자식이 있는가?
&lt;ul&gt;
&lt;li&gt;있는 경우 : &lt;code&gt;C&lt;/code&gt;의 실패 링크를 &lt;code&gt;F&lt;/code&gt;의 x 자식 노드로 설정&lt;/li&gt;
&lt;li&gt;없는 경우 : &lt;code&gt;F&lt;/code&gt;의 실패 링크를 타고 &lt;code&gt;F&apos;&lt;/code&gt;로 다시 점프를 2번 반복&lt;/li&gt;
&lt;li&gt;최종 실패 : &lt;code&gt;root&lt;/code&gt;까지 갔는데 x 자식이 없다면 &lt;code&gt;C&lt;/code&gt;의 실패 링크는 &lt;code&gt;root&lt;/code&gt;를 가리킴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C&lt;/code&gt;를 큐에 넣음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;검색&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;게시글 텍스트를 한번 탐색하면서 완성된 &apos;전처리&apos; 엔진을 사용
&lt;ul&gt;
&lt;li&gt;EX) 텍스트: &quot;나는 바보이고 멍청하다&quot;
&lt;ul&gt;
&lt;li&gt;currentNode = root에서 시작&lt;/li&gt;
&lt;li&gt;&apos;나&apos;: root에 &apos;나&apos; 자식이 X. currentNode = root 유지&lt;/li&gt;
&lt;li&gt;&apos;는&apos;: root에 &apos;는&apos; 자식이 X. currentNode = root 유지&lt;/li&gt;
&lt;li&gt;&apos;바&apos;: root에 &apos;바&apos; 자식이 O currentNode를 &apos;바&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;&apos;보&apos;: &apos;바&apos; 노드에 &apos;보&apos; 자식이 O currentNode를 &apos;보&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;(일치) &apos;보&apos; 노드는 isEndOfWord=true -&amp;gt; &quot;바보&quot; 찾음&lt;/li&gt;
&lt;li&gt;&apos;이&apos;: &apos;보&apos; 노드에 &apos;이&apos; 자식이 X&lt;/li&gt;
&lt;li&gt;(KMP 점프!) &apos;보&apos; 노드의 **&apos;실패 링크&apos;**를 따라 점프 (root)&lt;/li&gt;
&lt;li&gt;root에 &apos;이&apos; 자식이 X currentNode = root 유지&lt;/li&gt;
&lt;li&gt;&apos;멍&apos;: root에 &apos;멍&apos; 자식이 O currentNode를 &apos;멍&apos; 노드로 이동&lt;/li&gt;
&lt;li&gt;&apos;청&apos;: &apos;멍&apos; 노드에 &apos;청&apos; 자식이 O&lt;/li&gt;
&lt;li&gt;&apos;청&apos; 노드는 isEndOfWord=true&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;시간 복잡도&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;N : 텍스트의 길이&lt;/li&gt;
&lt;li&gt;L : 모든 패턴의 길이&lt;/li&gt;
&lt;li&gt;M : 텍스트에서 발견된 총 매칭 횟수&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;전처리&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- 시간 복잡도: O(L)
- `Trie` 생성(O(L)) + `Failure Link` 생성 (O(L))
- 서버 시작 시 최초 1회 수행
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;검색&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- 시간 복잡도: O(N + M)
- 텍스트 포인터는 KMP처럼 뒤로가지 않고 N번 전진함. 실패 링크를 따라 점프하는 횟수는 텍스트 전진 횟수를 넘을 수 없음
- M은 발견된 결과를 기록하는 시간
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;공간 복잡도&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;- O(L) : 모든 패턴의 총 길이
- `Trie`를 저장할 공간만 필요
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import java.util.*;

public class AhoCorasick {

    /**
     * 아호-코라식은 기본 트라이 노드에 &apos;실패 링크&apos;와 &apos;출력 셋&apos;을 추가
     */
    static class TrieNode {
        Map&amp;lt;Character, TrieNode&amp;gt; children = new HashMap&amp;lt;&amp;gt;();
        
        TrieNode failureLink = null;
        Set&amp;lt;String&amp;gt; output = new HashSet&amp;lt;&amp;gt;();

        public TrieNode getChild(char ch) {
            return children.get(ch);
        }
    }

    private final TrieNode root = new TrieNode();

    /**
     * 트라이에 패턴(금지어)을 삽입
     *
     * @param pattern 삽입할 패턴 문자열
     */
    public void insert(String pattern) {
        TrieNode current = root;
        for (char ch : pattern.toCharArray()) {
            current = current.children.computeIfAbsent(ch, c -&amp;gt; new TrieNode());
        }
        current.output.add(pattern);
    }

    /**
     * 실패 링크(Failure Links)를 구축
     * * 이 작업은 서버 시작 시 1회만 수행
     */
    public void buildFailureLinks() {
        Queue&amp;lt;TrieNode&amp;gt; queue = new LinkedList&amp;lt;&amp;gt;();

        for (TrieNode child : root.children.values()) {
            child.failureLink = root;
            queue.add(child);
        }

        while (!queue.isEmpty()) {
            TrieNode parent = queue.poll();

            for (Map.Entry&amp;lt;Character, TrieNode&amp;gt; entry : parent.children.entrySet()) {
                char ch = entry.getKey();
                TrieNode child = entry.getValue();

                queue.add(child);

                TrieNode fail = parent.failureLink; 

                while (fail != null &amp;amp;&amp;amp; !fail.children.containsKey(ch)) {
                    fail = fail.failureLink;
                }

                if (fail == null) {
                    child.failureLink = root;
                } else {
                    child.failureLink = fail.children.get(ch);
                }

                child.output.addAll(child.failureLink.output);
            }
        }
    }

    /**
     * 텍스트를 순회하며 모든 패턴을 검색
     *
     * @param text 검색 대상이 되는 전체 텍스트
     * @return 찾은 패턴과, 해당 패턴이 시작된 인덱스 목록
     */
    public Map&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; search(String text) {
        Map&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; results = new HashMap&amp;lt;&amp;gt;();
        TrieNode current = root;

        for (int i = 0; i &amp;lt; text.length(); i++) {
            char ch = text.charAt(i);

            while (current != null &amp;amp;&amp;amp; !current.children.containsKey(ch)) {
                current = current.failureLink;
            }

            if (current == null) {
                current = root;
                continue;
            }

            current = current.children.get(ch);

            if (!current.output.isEmpty()) {
                for (String pattern : current.output) {
                    results.computeIfAbsent(pattern, k -&amp;gt; new ArrayList&amp;lt;&amp;gt;())
                           .add(i - pattern.length() + 1);
                }
            }
        }
        return results;
    }


    public static void main(String[] args) {
        AhoCorasick ac = new AhoCorasick();
        
        String[] patterns = {&quot;he&quot;, &quot;she&quot;, &quot;his&quot;, &quot;hers&quot;};
        for (String p : patterns) {
            ac.insert(p);
        }

        ac.buildFailureLinks();

        String text = &quot;ushers&quot;;
        Map&amp;lt;String, List&amp;lt;Integer&amp;gt;&amp;gt; results = ac.search(text);

        results.forEach((pattern, indices) -&amp;gt; 
                System.out.println(&quot;패턴 \&quot;&quot; + pattern + &quot;\&quot; 발견 위치: &quot; + indices));
        
        /*
         출력 결과:
         패턴 &quot;she&quot; 발견 위치: [1]
         패턴 &quot;he&quot; 발견 위치: [2]
         패N &quot;hers&quot; 발견 위치: [2]
         */
    }
}
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[Ubuntu] git clone OAuth로 간편 인증</title><link>https://blog.csyhorizon.dev/posts/2025/ubuntu/githuboauth/draft/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/ubuntu/githuboauth/draft/</guid><pubDate>Thu, 23 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Github OAuth 인증으로 사용하기&lt;/h1&gt;
&lt;p&gt;윈도우에선 웹에 로그인하면 알아서 다 되고 좋은데 우분투는 토큰 발급한다던지,
2차 인증 걸어두면 그냥 비번은 아얘 먹히지 않더라고요.&lt;/p&gt;
&lt;p&gt;그렇다고 그냥 보관하면 평문 저장이고, 저장안하자니 매번 로그인해야하고..&lt;/p&gt;
&lt;p&gt;그래서 OAuth로 인증하는 법을 정리합니다.&lt;/p&gt;
&lt;h2&gt;1. GCM 설치&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/git-ecosystem/git-credential-manager/releases&quot;&gt;GCM Github&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;당시 최신 버전의 .deb를 설치해주세요.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. git config 설정&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;git config --global credential.helper manager
git config --global credential.credentialStore secretservice
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;그대로 넣어주시면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. OAuth 적용 확인&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;private으로 되어있는 레포지 clone 하면 웹 링크로 로그인할 수 있게 생깁니다.&lt;/li&gt;
&lt;li&gt;저는 UI가 좀 깨졌던데 정확한 이유는 모르겠는데 정상 동작해서 패스했습니다.&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[Ubuntu] VSCODE에서 한/영 클릭 시 File 포커스 맞춰지는 문제 해결</title><link>https://blog.csyhorizon.dev/posts/2025/ubuntu/vscodealtfile/draft/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/ubuntu/vscodealtfile/draft/</guid><pubDate>Wed, 22 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이 문제는 Ubuntu를 사용하다보니 겪었던 귀찮은 문제 중 하나였습니다.&lt;/p&gt;
&lt;p&gt;개발 중에는 한글을 칠 일이 많이 없다 생각했는데, 인터넷에 왔다갔다 한다던지..
언어를 막 바꾸다보면 생각보다 ESC누르고 귀찮았습니다&lt;/p&gt;
&lt;p&gt;VSCode에서 &lt;code&gt;settings.json&lt;/code&gt;의 파일 중에서&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;window.titleBarStyle&quot;: &quot;custom&quot;,
&quot;window.customMenuBarAltFocus&quot;: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;로 값을 바꿔주세요.&lt;/p&gt;
&lt;p&gt;근데 Alt 단축키를 주로 활용하는 분들이라면, 한/영 전환키를 Caps Lock으로 옮기는 방안을 생각해보는 게 좋을 거 같습니다.&lt;/p&gt;
</content:encoded></item><item><title>[Ubuntu] MX Master 3S 키 커스텀</title><link>https://blog.csyhorizon.dev/posts/2025/ubuntu/mxmaster3s/draft/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/ubuntu/mxmaster3s/draft/</guid><pubDate>Mon, 20 Oct 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;커스텀 이유&lt;/h1&gt;
&lt;p&gt;MX Master 3S 마우스를 우분투에서 블루투스로 연결하면 휠이 엄청 느리고, 창 전환도 안되고..
그렇다고 뭐 아무것도 안되서 정말 비싸고 아무것도 없는 마우스인게 짜증나더라고요.&lt;/p&gt;
&lt;p&gt;Logi Options?가 우분투 버전 있을 줄 알았는데 없어서 이것저것 찾아서 정리했습니다.&lt;/p&gt;
&lt;h2&gt;1. logiops 설치&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/PixlOne/logiops&quot;&gt;Github logiops&lt;/a&gt;에 가서 설치해주세요.&lt;/p&gt;
&lt;p&gt;설치 방법은 파일 받은 후에&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위에 명령어 입력하면 됩니다. 부가적인 안되는 이유는 검색해서 찾아주세요.&lt;/p&gt;
&lt;h2&gt;2. Option 값 변경&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;build 폴더 내에 /etc/logid.cfg를 열어줍니다.&lt;/li&gt;
&lt;li&gt;아마 초기엔 텅 비어있어서 그냥 만든다고 생각하면 됩니다.&lt;/li&gt;
&lt;li&gt;아래 값은 제 개인 커스텀 값 입니다. 잘 모르면 이거 쓰다가 조금씩 변경해도 될 거 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;devices: (
{
    name: &quot;MX Master 3S&quot;;
    smartshift:
    {
        on: true;
        threshold: 25;
        torque: 25;
    };
    hiresscroll:
    {
        hires: true;
        invert: false;
        target: false;
    }; 
    dpi: 700;
 
    buttons: (
        {
             cid: 0x0052;
             action =
             {
                 type: &quot;Gestures&quot;;
                 gestures: (
                     {
                         direction: &quot;Up&quot;;
                         mode: &quot;OnRelease&quot;;
                         action = 
                             {
                                 type: &quot;Keypress&quot;;
                                 keys: [&quot;KEY_LEFTCTRL&quot;, &quot;KEY_LEFTALT&quot;, &quot;KEY_T&quot;];
                             };
                     },
                     {
                         direction: &quot;Down&quot;;
                         mode: &quot;OnRelease&quot;;
                         action = 
                             {
                                 type: &quot;Keypress&quot;;
                                 keys: [&quot;KEY_LEFTCTRL&quot;, &quot;KEY_LEFTSHIFT&quot;, &quot;KEY_Q&quot;];
                             };
                     },
                     {
                         direction: &quot;Left&quot;;
                         mode: &quot;OnRelease&quot;;
                         action = 
                             {
                                 type: &quot;Keypress&quot;;
                                 keys: [&quot;KEY_LEFTCTRL&quot;, &quot;KEY_LEFTSHIFT&quot;, &quot;KEY_TAB&quot;];
                             };
                     },
                     {
                         direction: &quot;Right&quot;;
                         mode: &quot;OnRelease&quot;;
                         action = 
                             {
                                 type: &quot;Keypress&quot;;
                                 keys: [&quot;KEY_LEFTCTRL&quot;, &quot;KEY_TAB&quot;];
                             };
                     },
                     {
                         direction: &quot;None&quot;;
                         mode: &quot;NoPress&quot;;
                     }
         )
             };
        },
        {
             cid: 0x0053;
             action =
             {
                 type: &quot;Default&quot;;
             };
        },
        {
             cid: 0x0056;
             action =
             {
                 type: &quot;Default&quot;;
             };
        },
        {
             cid: 0x00c3;
             action =
             {
                 type: &quot;Gestures&quot;;
                 gestures: (
                     {
                         direction: &quot;Up&quot;;
                         mode: &quot;NoPress&quot;;
                     },
                     {
                         direction: &quot;Down&quot;;
                         mode: &quot;NoPress&quot;;
                     },
                     {
                         direction: &quot;Left&quot;;
                         mode: &quot;OnRelease&quot;;
                         action = 
                             {
                                 type: &quot;Keypress&quot;;
                                 keys: [&quot;KEY_LEFTCTRL&quot;, &quot;KEY_LEFTALT&quot;, &quot;KEY_LEFT&quot;];
                             };
                     },
                     {
                         direction: &quot;Right&quot;;
                         mode: &quot;OnRelease&quot;;
                         action = 
                             {
                                 type: &quot;Keypress&quot;;
                                 keys: [&quot;KEY_LEFTCTRL&quot;, &quot;KEY_LEFTALT&quot;, &quot;KEY_RIGHT&quot;];
                             };
                     },
                     {
                         direction: &quot;None&quot;;
                         mode: &quot;NoPress&quot;;
                     }
         )
             };
        },
        {
             cid: 0x00c4;
             action =
             {
                 type: &quot;Default&quot;;
             };
        }
    );
    thumbwheel: 
    {
        divert: true;
        invert: false;
    }
}
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. 커스텀 키 설명&lt;/h2&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;li&gt;스크롤 클릭 + 우 : 페이지 전환 (오른쪽)&lt;/li&gt;
&lt;li&gt;엄지 버튼 (좌, 우) : 화면 전환&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 아쉬운점&lt;/h2&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;
</content:encoded></item><item><title>넥토리얼 2025 DevOps 전형 탈락</title><link>https://blog.csyhorizon.dev/posts/2025/logs/nexon/tutorial-2025/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/logs/nexon/tutorial-2025/</guid><pubDate>Tue, 30 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;완벽하게 탈락했다&lt;/h1&gt;
&lt;p&gt;코테도 6개 중에 4개 풀었는데 넥슨은 이를 받아주진 않은 듯 하다.
한국 취업 수준이 생각보다 많이 높은건가 싶다.. 아무리 봐도 백준으로는 골, 플 난이도인데.........&lt;/p&gt;
&lt;p&gt;일부 커뮤니티에선 ChatGPT 돌려서 푼 사람은 면접까지 갔다는데 흠..
AI에 긍정적이지만 부정적인게 수준 파악에 이게 맞는지 궁금하긴하다&lt;/p&gt;
&lt;p&gt;하긴 이력서로 낼만한 것도 없는 주제에 넥슨에 지원한게 주제넘은 짓이긴 한듯하다.&lt;/p&gt;
&lt;h1&gt;목표 전환&lt;/h1&gt;
&lt;p&gt;취업을 목표로하지만 커리어를 쌓고 수준을 높이고 싶다.&lt;/p&gt;
&lt;p&gt;사실 아직 명확한 목표는 모르겠다. 넓지만 좁은 듯한 세상풀이라 해외 커리어를 쌓을까 싶기도하다&lt;/p&gt;
</content:encoded></item><item><title>if(kakao)25 DAY1 후기</title><link>https://blog.csyhorizon.dev/posts/2025/logs/if-kakao-2025/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/logs/if-kakao-2025/</guid><pubDate>Wed, 24 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;IF KAKAO 당첨!&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;이번에 카카오의 성과 컨퍼런스에 당첨되었습니다. 대학생이 확실히 다녀오는 난이도는 낮은 거 같아요.
카카오가 지속적으로 AI를 강조하길래 그쪽 방향으로 뭘 하고 있는 지 궁금하다고 적었는데 뽑아주신걸까요?
아무튼 좋은 경험이라 생각하고 당일치기를 계획했습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;새벽 3시 출발&lt;/h2&gt;
&lt;p&gt;아 생각보다 이 시간에 움직이는 거 빡세네요. 재정신이 아닌 거 같습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;지방인들은 전날에 다른 일정 잡고 올라가두는 편이 좋은 거 같습니다&lt;/li&gt;
&lt;li&gt;아니 다음 날도 잡으세요. 생각보다 당일치기는 힘듭니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;도착&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_view4.jpg&quot; alt=&quot;풍경&quot; /&gt;
&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_view3.jpg&quot; alt=&quot;풍경&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;도착하고보니.. 진짜 뭔가 산속 깊이 있는 거 같고 진짜 크네요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;키노트&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_players.jpg&quot; alt=&quot;키노트&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자리가 없었어요.. 사람이 진짜 많아서 이후에 스트리밍방? 그런 거 하나 구성해주셔서 스크린으로 보긴 했는데 현장감이 없었어요..&lt;/li&gt;
&lt;li&gt;다음엔 자리가 좀 있었으면 좋겠습니다. 위에서 볼때는 너무 힘들었어요&lt;/li&gt;
&lt;li&gt;키노트에선 카카오가 카카오톡을 어떻게 바꿀거다. AI 도입할거다. 카나나 만들었다. 그런 내용들인데,,&lt;/li&gt;
&lt;li&gt;아마 이 글을 쓰는 시점에선 다수가 알테니 생략하겠습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;프로그램 참여&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_view2.jpg&quot; alt=&quot;프로그램 1&quot; /&gt;
&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_test.jpg&quot; alt=&quot;프로그램 2&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이것저것 프로그램 참여하고 나중에 뽑기로 상품준다해서 몇개 해봤습니다. (이모티콘 1개월 2장......)&lt;/li&gt;
&lt;li&gt;위에껀 나를 위한 AI 적는건데, UI/UX를 AI가 대신 해줬으면 좋겠다고 적었습니다 ㅎㅎ&lt;/li&gt;
&lt;li&gt;밑에껀 실시간으로 카메라 띄우고 말하면 이에 설명해주는 느낌인데, 이게 서버 통신 없이 자체로 구현된거라면 상당히 대단하다 생각합니다. 정확히는 모르겠네요&lt;/li&gt;
&lt;li&gt;너무 피곤해도 사람이 정말 많은 거 빼곤 다 해본 거 같은데 막상 사진을 안찍었네요...&lt;/li&gt;
&lt;li&gt;AI 관련된 거랑 LLM 모델 써보는 거랑 키링 만드는 것도 있었습니다!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;풍경 구경&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_view.jpg&quot; alt=&quot;풍경&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;잠깐 쉬어야지 하면서 풍경을 보는데.. 진짜 좋긴하네요.......&lt;/li&gt;
&lt;li&gt;뭐랄까 현대화된 시골 보는 느낌?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;밥&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_food.jpg&quot; alt=&quot;밥&quot; /&gt;
&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_food2.jpg&quot; alt=&quot;밥2&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;밥 맛있어요. 배고프면 몰래 하나 더 먹을랬는데 생각보다 배 차더라고요..?&lt;/li&gt;
&lt;li&gt;그거랑 스벅 음료랑 스프를 계속 주셔서,, 계속 먹었습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;오후&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_session.jpg&quot; alt=&quot;오후&quot; /&gt;
오후엔 제가 시간이 없어서,, 다 경험하지 못하고 4시쯤 빠졌습니다! (기차 복귀하려면 어쩔 수 없었어요)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;코드로 해두면 편해요! 실수를 줄이는 개발 환경과 절차&lt;/li&gt;
&lt;li&gt;지금 이 순간, 재고는 줄고 있다: 실시간 UI 경험을 위한 SSE 여정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이렇게 두개 들었는데 신입 개발자 입장에선 내용이 정말 좋았습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;곧 공부할 부분이랑 이 전에 학습한 부분에서 약간 생각의 변화를 받을 수 있었던 거 같아요.&lt;/li&gt;
&lt;li&gt;굳이 적자면&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;1. 뭔가 DevOps를 공부하다보니 약간 고민한 내용들이 자주 들렸었습니다.
쿠버네티스의 Secret를 그냥 쓰면 사실 평문이라 보안 의미가 없는데, 그래서 저는 SealedSecret를 썻거든요.
여기선 그것도 좋지만, Sops가 좋다고 해서 찾아보니 온프레미스 개인이 도입하기엔 장비 비용이..
클라우드 서비스를 쓰던지 자체 KMS/HSM같은게 없으면 안되더라고요. 잠깐 고민했다가 내려놨습니다.

2. 이거 곧 만들어야하는데 듣다보니 원리만 이해하고 갑니다..!
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;기념품&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_item.jpg&quot; alt=&quot;기념품&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;뭐 많이 챙겨주세요. 감사합니다! 텀블러 컵은 진짜 예뻐서 바로 깠습니다&lt;/li&gt;
&lt;li&gt;굳이 나열하자면 뽑기 꽝 걸린 이모티콘 플러스 1개월 2장..&lt;/li&gt;
&lt;li&gt;마우스 패드, 텀블러, 복숭아 아이스티, 콘스프, 양말, 가방 받았습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;집으로&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_train.jpg&quot; alt=&quot;망함&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서울역 뛰어가서 기차 탔는데.. 잘못탔어요&lt;/li&gt;
&lt;li&gt;재정신 아닌가 싶은데 2시간 서울역 근처에 갖혔습니다
&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_coffee.jpg&quot; alt=&quot;커피&quot; /&gt;&lt;/li&gt;
&lt;li&gt;3시에 일어난 게 문제겠지.. 생각하면서 돌아온 블랙 글레이즈드 라떼 시켜서 롤체하다 집 갔습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;후기&lt;/h1&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/if_kakao/if25_update.png&quot; alt=&quot;업데이트&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;참여 후기&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;카카오 AI 컨퍼런스에 가서 듣다보니 카카오가 뭘 하려는 지 알았습니다. 그날 업데이트도 되었고요

다만, 사용자 측면을 고려하는 개발자가 만들어냈다기엔 억지스러운 부분이 있었네요.
카카오의 강점을 깍아내서라도 인스타의 방식으로 바꿀 필요가 있을 정도로 위기인가 싶었습니다.

왠지 싸워서 한쪽이 져서 만들어진 결과물같은데 일단 업데이트는 안하고 지켜볼 거 같습니다..
(괜히 했다가 라인이나 그런걸로 갈아탈지도...?)

AI 위기 탐지하는 모델도 사용해볼 수 있던데 &apos;자전거 타다가 바다에 빠지고 싶다&apos; 같은 이해할 수 없는 질문은 협박 80%로 잡히길래..
아직 카카오 AI가 많이 갈길이 먼거 같더라고요. 납득이 가능한 범위의 답을 낼줄 알았는데 아직 아닌가봅니다

뭐.. 괜찮은 경험이였습니다! 밥도 맛있고 엄청 크고, 사람들도 친철하고요.

의외로 이런 거 여성분들이 더 많이 오더라고요...?
참여부스 갔는데 저 혼자 남자라서 몹시 당황했습니다.

오후 발표도 개발자와 일반인 관점을 동시에 이야기하니 듣기도 좋고 재밌었습니다.
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;지방인으로써의 후기&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;사실.. 지방인 입장에선 당일치기로 다녀오는 건 다소 힘들었습니다.
전날 일정이 있어서 당일 치기로 갔다왔더니 재정신이 아니더라고요..

새벽 3시에 일어나서 준비하고 5시 KTX타고 광명역가서 광역버스랑 지하철타고 판교역으로 이동하는 동선은 사실 힘들거든요.
얼마나 정신 없었는지 kakao 적힌거 보고 탔는데 다른 회사가는 셔틀버스였고.. 서울역에서 기차타고 집가야하는데 ITX 잘못타서 서울역 근처에서 2시간 뻐팅기고..

한번쯤은 대학생 때 가고 싶어서 갔지만, 수도권 취업 전에는 아마 또 가냐 묻는다면 안갈거 같습니다..!

아 그래서 9시 20분 지나도 늦으시는 분들을 위해 추가적으로 판교역 가는 버스 운영해줘서 감사합니다..!
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>N100에 쿠버네티스 설치</title><link>https://blog.csyhorizon.dev/posts/2025/devops/n100_k3s/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/devops/n100_k3s/</guid><pubDate>Mon, 22 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;DevOps쪽에 관심은 많은데 취업은 어렵고.. 클라우드는 비싸고..
서비스도 운영하고 싶고, 부하 테스트도 하고 싶은데 어찌저찌 고민해본 결과
&lt;strong&gt;미니 PC를 구매하자!&lt;/strong&gt; 가 결론이 되어버렸습니다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;솔직히 클라우드 경험 중요하다 하지만 매달 20씩 박히던데.. 그럴빠엔 미니 PC 1대 사서 2달만 써도 본전..&lt;/li&gt;
&lt;li&gt;심지어 E코어긴하지만 4개나 박혀있어서 프리티어보단 좋습니다&lt;/li&gt;
&lt;li&gt;그리고 대규모 트래픽을 경험할 정도까지 가는건 디도스말곤 없으니 홈서버가 더 입문자에겐 정답..&lt;/li&gt;
&lt;li&gt;마지막으로 한달 내내 켜도 전기료는 몇 백원 수준입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서 알리를 통해 N100에 램 16GB, SSD 512GB 짜리를 10만원에 구매했습니다!&lt;/p&gt;
&lt;p&gt;:::tip
퀘이사존 같은 곳에서 알리 세일할 때 가격표 정리해서 누가 올려주니 그거보고 구매합시다
:::&lt;/p&gt;
&lt;p&gt;적당히 미니 서버도 얻었고 전기료도 얼마 안드니 이제 쿠버네티스 환경 셋팅이나 해보려고 합니다.&lt;/p&gt;
&lt;p&gt;:::warning&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;우분투 서버로 OS를 설치해둬야합니다.&lt;/li&gt;
&lt;li&gt;SSH 설정도 미리 해주세요&lt;/li&gt;
&lt;li&gt;포트포워딩도 다 알아서 하세요
:::&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h1&gt;쿠버네티스 설치&lt;/h1&gt;
&lt;h2&gt;1. 최신 상태로 업데이트&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update &amp;amp;&amp;amp; sudo apt upgrade -y
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;쿠버네티스를 설치하기 전 최신 상태로 업데이트합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 메모리 스왑 비활성화&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo swapoff -a
sudo sed -i &apos;/ swap / s/^\(.*\)$/#\1/g&apos; /etc/fstab
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;쿠버네티스는 메모리 스왑을 사용하지 않는데, 이건 kubelet이 이러한 상황을 처리하도록 만들어지지 않아서입니다.&lt;/li&gt;
&lt;li&gt;Swap 기능은 본래 가용 메모리보다 더 큰 메모리를 할당하는 장점이 있습니다.&lt;/li&gt;
&lt;li&gt;다만, 이러면 컨테이너 속도가 변칙적이고 고려사항도 많아지며, 이는 쿠버네티스가 지원하지 않으니 꺼버립시다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. K3S 설치&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;curl -sfL https://get.k3s.io | sh -s - --write-kubeconfig-mode 644
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;N100은 생각보다 크면서도 작은 서버라(클라우드보단 싸고 좋을 것).. K3S를 사용합니다. K8S는 생각보다 빡빡합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 워커 노드 합류 (단일 서버인 경우 패스)&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;sudo cat /var/lib/rancher/k3s/server/node-token

curl -sfL https://get.k3s.io | K3S_URL=https://&amp;lt;마스터_노드_IP&amp;gt;:6443 K3S_TOKEN=&amp;lt;복사한_토큰_값&amp;gt; sh -
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;이 부분은 저도 장비가 1대밖에 없어서 일단 패스합니다. (당근 아니면 알리에서 10미만으로 찾는중..)&lt;/li&gt;
&lt;li&gt;당장은 마스터와 워커가 같이 사용되는 형태지만 이 부분은 차차 장비가 생기면 바꿔보겠습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;Kubectl 설치&lt;/h1&gt;
&lt;p&gt;이제 로컬 환경에서 편하게 쿠버네티스를 사용하기 위해 kubectl 작업을 진행합시다.&lt;/p&gt;
&lt;h2&gt;1. (서버) kubeconfig 파일 복사&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;cat /etc/rancher/k3s/k3s.yaml
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;서버에서 설정된 파일을 가져온 후 이 값들을 복사합니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::warning
여기서 server: https://127.0.0.1:6443 로 되어있는 항목은 실제 서버 주소로 변경해야합니다!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https는 무조건 유지해야합니다&lt;/li&gt;
&lt;li&gt;내부인 경우 내부 서버 주소, 외부인 경우 외부 서버 주소로 변경합니다.
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. (로컬) kubectl 설치&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;winget install -e --id Kubernetes.kubectl
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;로컬 서버에 kubectl을 설치합니다. 맥북의 경우 명령어를 찾아서 바꿔주세요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. kube 디렉터리 생성&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;mkdir $HOME\.kube
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;해당 폴더 경로에 &lt;strong&gt;config&lt;/strong&gt; 파일 만들어서, 1번에서 복사한 값들을 넣어줍니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. 설치 확인&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;정상적으로 node가 뜬다면 성공입니다&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;사진도 좀 넣을걸 그랬나 싶긴한데 뭔가 찍기 귀찮아져서 안했습니다&lt;/p&gt;
&lt;h1&gt;추가 (쿠버네티스 업데이트)&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;주기적으로 쿠버네티스는 업데이트가 뜨는데 보안상 버전 이슈 아니면 업데이트 주기가 있으면 좋을 거 같습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;curl -LO &quot;https://dl.k8s.io/release/stable.txt&quot;
type stable.txt
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;curl -LO &quot;https://dl.k8s.io/release/v{최신 버전}/bin/windows/amd64/kubectl.exe&quot;
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>정규화 (Normalization)</title><link>https://blog.csyhorizon.dev/posts/2025/cs/normalization/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/normalization/</guid><pubDate>Wed, 10 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;정의&lt;/h1&gt;
&lt;p&gt;:::note
관계형 데이터베이스 설계에서 중복을 최소화하여 데이터의 구조를 효율적으로 만드는 과정
하나의 종속성이 하나의 릴레이션(테이블)에 표현되도록 분해하는 것
:::&lt;/p&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;이상 현상(Anomaly) 방지:&lt;/strong&gt; 데이터 삽입, 삭제, 갱신 시 논리적 오류 방지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터 일관성 및 무결성 유지:&lt;/strong&gt; 정확한 데이터 상태 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;이상 현상 (Anomaly)&lt;/h1&gt;
&lt;p&gt;정규화를 하지 않아 발생하는 데이터 불일치 문제&lt;/p&gt;
&lt;h2&gt;삽입 이상 (Insertion Anomaly)&lt;/h2&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;h2&gt;삭제 이상 (Deletion Anomaly)&lt;/h2&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;h2&gt;갱신 이상 (Update Anomaly)&lt;/h2&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;hr /&gt;
&lt;h1&gt;정규화 단계 (Normalization Steps)&lt;/h1&gt;
&lt;p&gt;보통 제3정규형(3NF)이나 BCNF까지만 진행해도 실무에서 충분함&lt;/p&gt;
&lt;h2&gt;제1정규형 (1NF)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;조건:&lt;/strong&gt; 테이블의 모든 속성 값은 **원자값(Atomic Value)**만 가져야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설명:&lt;/strong&gt; 하나의 칸(컬럼)에 여러 개의 값(예: &quot;취미: 축구, 농구&quot;)이 들어가면 안 됨. 각 로우와 컬럼의 교차점에는 하나의 값만 존재해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;제2정규형 (2NF)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;조건:&lt;/strong&gt; 1NF를 만족하고, 기본키가 아닌 모든 속성이 기본키에 &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;strong&gt;부분 함수 종속 제거&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;제3정규형 (3NF)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;조건:&lt;/strong&gt; 2NF를 만족하고, 기본키가 아닌 모든 속성이 기본키에 &lt;strong&gt;이행적 함수 종속&lt;/strong&gt;되지 않아야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설명:&lt;/strong&gt; A → B, B → C 관계가 있을 때(A → C), 이를 A → B 테이블과 B → C 테이블로 분리해야 함. 즉, 속성 간의 종속 관계를 제거하는 것&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;핵심:&lt;/strong&gt; &lt;strong&gt;이행 함수 종속 제거&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;BCNF (Boyce-Codd Normal Form)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;조건:&lt;/strong&gt; 3NF를 만족하고, 모든 결정자가 &lt;strong&gt;후보키&lt;/strong&gt;여야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;설명:&lt;/strong&gt; 강력한 제3정규형이라고도 불리며, 후보키가 아닌 속성이 기본키의 일부를 결정하는 상황을 제거함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;반정규화 (De-normalization)&lt;/h1&gt;
&lt;h2&gt;개념&lt;/h2&gt;
&lt;p&gt;정규화된 엔티티, 속성, 관계를 시스템의 성능 향상을 위해 의도적으로 중복, 통합, 분리하는 데이터 모델링 기법&lt;/p&gt;
&lt;h2&gt;사용하는 이유&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;정규화가 너무 과하게 진행되면 테이블이 많아져 &lt;strong&gt;JOIN 연산&lt;/strong&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;ul&gt;
&lt;li&gt;데이터 무결성이 깨질 위험이 있으므로 꼭 필요한 경우에만 신중하게 적용해야 함&lt;/li&gt;
&lt;li&gt;정규화를 먼저 완벽하게 수행한 후, 성능 이슈가 발생했을 때 최후의 수단으로 고려&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;요약&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;핵심 키워드&lt;/th&gt;
&lt;th&gt;제거 대상&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1NF&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;strong&gt;2NF&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;strong&gt;3NF&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;strong&gt;BCNF&lt;/strong&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;
</content:encoded></item><item><title>Redis와 Memcached의 차이</title><link>https://blog.csyhorizon.dev/posts/2025/cs/redis_memcahed/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/redis_memcahed/</guid><pubDate>Wed, 27 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Redis와 Memcached 비교&lt;/h1&gt;
&lt;p&gt;:::note
두 기술 모두 &lt;strong&gt;인메모리(In-memory) 키-값(Key-Value) 저장소&lt;/strong&gt;로서, 응답 속도가 매우 빠름
주로 데이터베이스의 부하를 줄이고 성능을 높이기 위한 &lt;strong&gt;캐시(Cache)&lt;/strong&gt; 용도로 사용됨
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;Memcached (멤캐시드)&lt;/h1&gt;
&lt;h2&gt;특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;멀티스레드 (Multi-thread):&lt;/strong&gt; 멀티스레드 아키텍처를 지원하여 멀티코어 CPU를 효율적으로 활용할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단순한 데이터 구조:&lt;/strong&gt; 오직 &lt;strong&gt;문자열(String)&lt;/strong&gt; 타입만 저장 가능함 (최대 1MB)&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;ul&gt;
&lt;li&gt;HTML 조각이나 단순한 객체 캐싱&lt;/li&gt;
&lt;li&gt;데이터 복구가 필요 없거나 재생성 비용이 낮은 세션 관리&lt;/li&gt;
&lt;li&gt;멀티코어 환경에서 수직적 확장(Scale-up)이 필요한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Redis (Remote Dictionary Server)&lt;/h1&gt;
&lt;h2&gt;특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;싱글스레드 (Single-thread):&lt;/strong&gt; 기본적으로 싱글스레드로 동작하여 원자성(Atomicity)을 보장함 (단, Redis 6.0부터 I/O 처리는 멀티스레드 지원)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다양한 자료구조:&lt;/strong&gt; String 외에도 List, Set, Hash, Sorted Set(ZSet), Bitmap, HyperLogLog 등 다양한 자료구조 지원&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;지속성 (Persistence):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;RDB (Snapshot):&lt;/strong&gt; 특정 시점의 데이터를 디스크에 저장&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AOF (Append Only File):&lt;/strong&gt; 모든 쓰기 연산을 로그로 저장하여 재시작 시 복구 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;고급 기능:&lt;/strong&gt; Pub/Sub(메시징), 트랜잭션, 루아 스크립트, Geo(지리 정보) 등 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;주요 용도&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;복잡한 캐싱 (예: 정렬된 랭킹 정보)&lt;/li&gt;
&lt;li&gt;메시지 브로커 (메시지 큐)&lt;/li&gt;
&lt;li&gt;실시간 채팅 및 알림 시스템 (Pub/Sub)&lt;/li&gt;
&lt;li&gt;세션 스토어 (서버가 죽어도 데이터 보존 필요 시)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;상세 비교표&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;비교 항목&lt;/th&gt;
&lt;th&gt;Memcached&lt;/th&gt;
&lt;th&gt;Redis&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&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;싱글스레드 (I/O는 멀티 지원)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;자료구조&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;문자열(String)&lt;/td&gt;
&lt;td&gt;String, List, Set, Hash, Sorted Set 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;데이터 저장(Persistence)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;지원 안 함 (휘발성)&lt;/td&gt;
&lt;td&gt;RDB, AOF 지원 (디스크 저장 가능)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;복제(Replication)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;지원 안 함 (클라이언트 측 분산)&lt;/td&gt;
&lt;td&gt;Master-Slave 복제 지원&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;Redis Cluster 자체 지원&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;strong&gt;메모리 관리&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slab Allocator (파편화 적음)&lt;/td&gt;
&lt;td&gt;Jemalloc (다양한 사이즈 처리)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h1&gt;결론: 언제 무엇을 쓸까?&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Memcached를 써야 할 때:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;데이터 구조가 단순(문자열)하고 용량이 작은 경우&lt;/li&gt;
&lt;li&gt;데이터가 날아가도 상관없는 순수 캐시 용도인 경우&lt;/li&gt;
&lt;li&gt;멀티코어 CPU를 최대한 활용해 트래픽을 처리해야 하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis를 써야 할 때:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;리스트, 셋, 랭킹(Sorted Set) 등 복잡한 데이터 처리가 필요한 경우&lt;/li&gt;
&lt;li&gt;서버가 재시작되어도 데이터가 보존되어야 하는 경우&lt;/li&gt;
&lt;li&gt;여러 서버 간의 데이터 복제 및 고가용성(HA) 구성이 필요한 경우&lt;/li&gt;
&lt;li&gt;메시지 큐나 Pub/Sub 기능이 필요한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>웹 통신 흐름 (google 접속 시 일어나는 일)</title><link>https://blog.csyhorizon.dev/posts/2025/cs/webgoogle/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/webgoogle/</guid><pubDate>Mon, 04 Aug 2025 00:00:00 GMT</pubDate><content:encoded>&lt;blockquote&gt;
&lt;p&gt;어째선지 가끔 물어봐서 정리하게 되는 것인데, 쉽게 풀어서 적어봄&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1&gt;1. 개요&lt;/h1&gt;
&lt;p&gt;브라우저 주소창에 &lt;code&gt;www.google.com&lt;/code&gt;을 입력하고 엔터를 치면, 사용자가 보는 화면이 뜰 때까지 네트워크 상에서는 많은 과정이 일어남
:::note
크게 &lt;strong&gt;IP 주소 획득(DNS)&lt;/strong&gt;, &lt;strong&gt;서버 연결(TCP/IP)&lt;/strong&gt;, &lt;strong&gt;데이터 요청 및 수신(HTTP)&lt;/strong&gt;, &lt;strong&gt;화면 렌더링&lt;/strong&gt; 단계로 나눌 수 있음
:::&lt;/p&gt;
&lt;h1&gt;2. 주요 단계별 상세 흐름&lt;/h1&gt;
&lt;h2&gt;IP 주소 찾기 (DNS Lookup)&lt;/h2&gt;
&lt;p&gt;브라우저는 사람이 이해하기 쉬운 도메인 이름(&lt;code&gt;google.com&lt;/code&gt;)을 컴퓨터가 이해하는 IP 주소(&lt;code&gt;142.250.xxx.xxx&lt;/code&gt;)로 변환해야 함&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;브라우저 캐시 확인:&lt;/strong&gt; 브라우저는 이전에 방문한 적이 있는지 자신의 캐시를 먼저 찾아봄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OS 캐시 확인:&lt;/strong&gt; 브라우저에 없으면 운영체제(OS)의 hosts 파일이나 캐시를 확인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Router 캐시 확인:&lt;/strong&gt; 공유기(Router)의 캐시 확인&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ISP(통신사) DNS 서버 요청:&lt;/strong&gt; 위 단계에 모두 없으면, 설정된 DNS 서버(보통 통신사 제공)에 요청을 보냄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;재귀적 쿼리 (Recursive Query):&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Root DNS 서버 → &lt;code&gt;.com&lt;/code&gt; 관리 서버 → &lt;code&gt;google.com&lt;/code&gt; 관리 서버 순으로 물어보며 최종 IP 주소를 획득&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;서버와 연결 수립 (TCP Connection)&lt;/h2&gt;
&lt;p&gt;IP 주소를 알았으니 구글 서버와 통신할 준비하는데, 이때 신뢰성 있는 전송을 위해 &lt;strong&gt;TCP/IP 프로토콜&lt;/strong&gt;을 사용&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;3-Way Handshake:&lt;/strong&gt; 클라이언트와 서버가 서로 연결을 맺기 위해 3단계 절차를 거침
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SYN:&lt;/strong&gt; 클라이언트 → 서버 (연결 요청)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SYN + ACK:&lt;/strong&gt; 서버 → 클라이언트 (요청 수락 및 연결 요청)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACK:&lt;/strong&gt; 클라이언트 → 서버 (수락 확인)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPS (TLS/SSL Handshake):&lt;/strong&gt; 보안 연결을 위해 암호화 키를 교환하는 과정이 추가됨 (데이터 암호화 준비)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;데이터 요청 및 응답 (HTTP Request/Response)&lt;/h2&gt;
&lt;p&gt;연결이 되면 실제 데이터를 달라고 요청&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Request (요청):&lt;/strong&gt; 브라우저가 구글 서버에 &lt;code&gt;GET / HTTP/1.1&lt;/code&gt; 같은 메시지를 보냄 (메인 페이지 줘)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Processing (처리):&lt;/strong&gt; 구글 서버(웹 서버 → WAS → DB)가 요청을 받아 필요한 데이터를 찾거나 가공함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Response (응답):&lt;/strong&gt; 서버가 찾은 HTML 문서를 HTTP 상태 코드(200 OK 등)와 함께 브라우저로 보냄&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;브라우저 렌더링 (Rendering)&lt;/h2&gt;
&lt;p&gt;서버로부터 받은 HTML 데이터를 사용자가 볼 수 있는 화면으로 그림&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;HTML 파싱:&lt;/strong&gt; HTML을 분석하여 &lt;strong&gt;DOM Tree&lt;/strong&gt; 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSS 파싱:&lt;/strong&gt; CSS를 분석하여 &lt;strong&gt;CSSOM Tree&lt;/strong&gt; 생성&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Render Tree 생성:&lt;/strong&gt; DOM과 CSSOM을 합쳐 실제 화면에 표시될 요소들로 트리를 만듦&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Layout (Reflow):&lt;/strong&gt; 각 요소가 화면 어디에 위치할지 크기와 좌표를 계산함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Paint:&lt;/strong&gt; 계산된 위치에 색을 칠하고 픽셀을 그림&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Composite:&lt;/strong&gt; 여러 레이어를 합성하여 최종 화면을 보여줌&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;3. 핵심 용어 정리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DNS (Domain Name System):&lt;/strong&gt; 도메인 이름을 IP 주소로 바꿔주는 전화번호부 같은 시스템&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP (Transmission Control Protocol):&lt;/strong&gt; 데이터가 누락 없이 안정적으로 전송되도록 돕는 프로토콜&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;3-Way Handshake:&lt;/strong&gt; TCP 연결을 맺기 위한 3단계 인사 과정&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP/HTTPS:&lt;/strong&gt; 웹에서 데이터를 주고받기 위한 규약 (S는 Secure, 보안 강화)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;렌더링(Rendering):&lt;/strong&gt; HTML/CSS 코드를 해석해서 눈에 보이는 화면으로 그려내는 과정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;보통 연계기 들어가기 좋은 질문이 &apos;구글 접속할 때 일어나는 일&apos; 입니다.&lt;/li&gt;
&lt;li&gt;이거 하나로 꼬리 질문 수십개 만들 수 있는데, 그냥 이 페이지에 적힌 글자나 단어 설명 못하는 건 다 공부하는 편이 좋습니다.&lt;/li&gt;
&lt;li&gt;쉽게 풀어쓰긴 했는데 서버 운영을 해보지 않았다면 조금 어렵다 느낄 수 있습니다&lt;/li&gt;
&lt;li&gt;OSI 7계층 관점은 저는 꼬리질문 빡세서 그냥 모른다고 합니다
:::&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>인덱스</title><link>https://blog.csyhorizon.dev/posts/2025/cs/index/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/index/</guid><pubDate>Tue, 29 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;데이터베이스 인덱스 (Database Index) 핵심 가이드&lt;/h1&gt;
&lt;h2&gt;1. 데이터베이스 인덱스 개요&lt;/h2&gt;
&lt;h3&gt;1.1 핵심 개념&lt;/h3&gt;
&lt;p&gt;인덱스(Index)는 데이터베이스 테이블의 검색 속도를 향상시키기 위한 자료구조임
책의 뒷부분에 있는 &apos;색인&apos;이나 도서관의 &apos;목차&apos;와 유사한 역할을 하며, 데이터를 정렬된 상태로 유지하여 원하는 정보를 빠르게 찾을 수 있도록 도움&lt;/p&gt;
&lt;h3&gt;1.2 인덱스 적용 전후 비교&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;인덱스가 없는 경우 (Full Table Scan)&lt;/strong&gt;
SQL 문을 실행했을 때, 데이터베이스는 조건에 맞는 데이터를 찾기 위해 테이블의 처음부터 끝까지 모든 행을 검사해야 함. 데이터 양이 많을수록 성능이 급격히 저하됨&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM users WHERE email = &apos;user@example.com&apos;;
-- 전체 테이블을 스캔하므로 속도가 느림

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;인덱스가 적용된 경우 (Index Scan)&lt;/strong&gt;
인덱스를 생성하면 정렬된 구조를 통해 해당 데이터를 빠르게 찾아낼 수 있어 검색 성능이 비약적으로 향상됨&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE INDEX idx_email ON users(email);
SELECT * FROM users WHERE email = &apos;user@example.com&apos;;
-- 인덱스를 사용하여 빠르게 검색

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;2. 주요 인덱스 자료구조&lt;/h2&gt;
&lt;h3&gt;2.1 B-Tree 인덱스 (Balanced Tree)&lt;/h3&gt;
&lt;p&gt;MySQL, PostgreSQL 등 대부분의 관계형 데이터베이스에서 기본적으로 사용하는 인덱스 구조
데이터가 항상 정렬된 상태를 유지하며, 트리(Tree) 구조를 통해 검색, 삽입, 삭제 연산에서 O(log N)의 시간 복잡도를 가짐&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; 검색, 삽입, 삭제 성능이 균일하며, 범위 검색(BETWEEN, ORDER BY 등)에 매우 효율적임&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점:&lt;/strong&gt; 데이터 삽입이나 삭제가 빈번하게 일어날 경우, 트리의 균형을 맞추기 위한 리밸런싱 비용이 발생하여 성능이 저하될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 Hash 인덱스&lt;/h3&gt;
&lt;p&gt;해시 함수(Hash Function)를 사용하여 키 값을 메모리 주소로 매핑하는 방식&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; 동등 비교(=) 연산 시 O(1)에 가까운 매우 빠른 검색 속도를 제공함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점:&lt;/strong&gt; 데이터가 정렬되어 있지 않으므로 부등호(&amp;lt;, &amp;gt;), 범위 검색(BETWEEN), 정렬(ORDER BY)에는 사용할 수 없음. 주로 Redis 같은 인메모리 데이터베이스나 특정 상황에서 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 Full-Text 인덱스 (전문 검색 인덱스)&lt;/h3&gt;
&lt;p&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; 일반적인 &lt;code&gt;LIKE &apos;%keyword%&apos;&lt;/code&gt; 패턴 검색보다 훨씬 빠른 속도를 제공하며, 자연어 처리에 유리함. &lt;code&gt;MATCH ... AGAINST&lt;/code&gt; 구문을 사용함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;단점:&lt;/strong&gt; 실시간 데이터 업데이트 처리가 까다로울 수 있으며, 한 글자 검색 등에는 별도의 설정이 필요할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 인덱스의 종류 및 활용&lt;/h2&gt;
&lt;h3&gt;3.1 Primary Key (기본 키) 인덱스&lt;/h3&gt;
&lt;p&gt;테이블 생성 시 Primary Key로 지정된 컬럼에 대해 자동으로 생성되는 인덱스. 데이터베이스에 따라 클러스터형 인덱스(Clustered Index)로 구성되어 데이터 자체가 정렬된 상태로 저장됨&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE users (
    id INT PRIMARY KEY,  -- 자동으로 인덱스 생성
    name VARCHAR(100),
    email VARCHAR(255)
);

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.2 Unique 인덱스&lt;/h3&gt;
&lt;p&gt;중복된 값을 허용하지 않는 인덱스. 특정 컬럼의 유일성을 보장해야 할 때 사용함 (단, NULL 값은 중복 허용 가능)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE UNIQUE INDEX idx_unique_email ON users(email);

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.3 Composite Index (복합 인덱스)&lt;/h3&gt;
&lt;p&gt;두 개 이상의 컬럼을 묶어서 하나의 인덱스로 만드는 것
&lt;strong&gt;중요:&lt;/strong&gt; 인덱스 생성 시 컬럼의 순서가 매우 중요함. 왼쪽 컬럼부터 순서대로 정렬되기 때문에, 선행 컬럼이 조건절에 없으면 인덱스가 제대로 활용되지 못할 수 있음&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE INDEX idx_name_email ON users(name, email);

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;사용 가능: &lt;code&gt;WHERE name = &apos;John&apos; AND email = &apos;john@example.com&apos;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;사용 불가: &lt;code&gt;WHERE email = &apos;john@example.com&apos;&lt;/code&gt; (name 조건 누락)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.4 Covering Index (커버링 인덱스)&lt;/h3&gt;
&lt;p&gt;쿼리에서 조회하려는 모든 컬럼이 인덱스에 포함되어 있어, 실제 데이터 테이블을 읽지 않고 인덱스만으로 쿼리를 완료할 수 있는 경우를 말함. 디스크 I/O를 획기적으로 줄일 수 있음&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- name 컬럼에 인덱스가 있는 경우
SELECT name FROM users WHERE name = &apos;Alice&apos;;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;4. 성능 최적화 전략 및 주의사항&lt;/h2&gt;
&lt;h3&gt;4.1 인덱스 사용 가이드라인&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;권장하는 경우&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WHERE&lt;/code&gt; 절에 자주 등장하는 컬럼&lt;/li&gt;
&lt;li&gt;&lt;code&gt;JOIN&lt;/code&gt; 연결 고리로 사용되는 컬럼&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ORDER BY&lt;/code&gt; 또는 &lt;code&gt;GROUP BY&lt;/code&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;데이터 변경(INSERT, UPDATE, DELETE)이 빈번한 컬럼 (인덱스 재정렬 비용 발생)&lt;/li&gt;
&lt;li&gt;카디널리티(Cardinality)가 낮은 컬럼 (예: 성별, 사용여부 등 중복도가 높은 데이터)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 실행 계획 (Explain) 확인&lt;/h3&gt;
&lt;p&gt;SQL 쿼리 앞에 &lt;code&gt;EXPLAIN&lt;/code&gt; 키워드를 붙여 인덱스가 정상적으로 사용되고 있는지 확인해야 함&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EXPLAIN SELECT * FROM users WHERE email = &apos;user@example.com&apos;;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;주요 결과 해석&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;type: ALL&lt;/code&gt;: 전체 테이블 스캔 (Full Table Scan). 성능 튜닝 필요&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type: INDEX&lt;/code&gt;: 인덱스 전체 스캔&lt;/li&gt;
&lt;li&gt;&lt;code&gt;type: REF&lt;/code&gt; 또는 &lt;code&gt;RANGE&lt;/code&gt;: 인덱스를 효율적으로 사용하여 특정 범위나 값을 조회 (권장)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;5. 트러블슈팅 및 실전 사례&lt;/h2&gt;
&lt;h3&gt;Case 1: 함수 사용으로 인한 인덱스 미작동&lt;/h3&gt;
&lt;p&gt;조건절의 컬럼을 함수로 가공하면 인덱스를 사용할 수 없음&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;문제 상황&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;-- email 컬럼을 가공하였으므로 인덱스를 타지 않음
SELECT * FROM users WHERE LOWER(email) = &apos;test@example.com&apos;;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;해결 방안&lt;/strong&gt;
컬럼 자체를 가공하지 않고 원본 데이터를 비교하거나, 미리 소문자로 변환하여 저장하는 방식을 사용해야 함&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM users WHERE email = &apos;test@example.com&apos;;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Case 2: OR 조건에서의 인덱스 효율 저하&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;OR&lt;/code&gt; 연산자는 연결된 조건 중 하나라도 인덱스가 없으면 전체 스캔이 발생할 수 있음&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;문제 상황&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM users WHERE name = &apos;Alice&apos; OR email = &apos;alice@example.com&apos;;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;해결 방안&lt;/strong&gt;
각각 인덱스가 걸린 쿼리를 &lt;code&gt;UNION&lt;/code&gt;으로 결합하여 처리하면 각 인덱스를 효율적으로 활용할 수 있음&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;SELECT * FROM users WHERE name = &apos;Alice&apos;
UNION
SELECT * FROM users WHERE email = &apos;alice@example.com&apos;;

&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>쿠키, 세션, JWT</title><link>https://blog.csyhorizon.dev/posts/2025/cs/cookie_section_jwt/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/cookie_section_jwt/</guid><pubDate>Tue, 22 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;인증과 상태 유지: Cookie, Session, JWT 핵심 정리&lt;/h1&gt;
&lt;h2&gt;1. 주제 소개&lt;/h2&gt;
&lt;h3&gt;1.1 웹 애플리케이션의 인증과 상태 유지&lt;/h3&gt;
&lt;p&gt;웹은 기본적으로 &lt;strong&gt;Stateless(무상태)&lt;/strong&gt; 프로토콜인 HTTP를 사용함. 따라서 사용자의 로그인 상태를 유지하거나 요청 간 정보를 공유하기 위해 별도의 기술이 필요&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;인증(Authentication):&lt;/strong&gt; 사용자가 누구인지 확인하는 절차&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;상태 유지:&lt;/strong&gt; 페이지를 이동해도 사용자의 로그인 상태가 끊기지 않도록 하는 기술&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 핵심 개념&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;쿠키(Cookie):&lt;/strong&gt; 클라이언트(브라우저) 측 저장 방식&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;세션(Session):&lt;/strong&gt; 서버 측 저장 방식&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWT(JSON Web Token):&lt;/strong&gt; 토큰 기반 인증 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 쿠키 (Cookie)&lt;/h2&gt;
&lt;h3&gt;2.1 개념&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트(브라우저)에 저장되는 작은 데이터 조각&lt;/li&gt;
&lt;li&gt;서버가 응답할 때 &lt;code&gt;Set-Cookie&lt;/code&gt; 헤더를 통해 설정함&lt;/li&gt;
&lt;li&gt;클라이언트는 이후 요청 시 자동으로 쿠키를 서버로 전송함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;키-값(key=value) 형태로 저장됨&lt;/li&gt;
&lt;li&gt;만료 시간 설정 가능 (&lt;code&gt;expires&lt;/code&gt; 또는 &lt;code&gt;max-age&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Secure:&lt;/strong&gt; HTTPS에서만 전송 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HttpOnly:&lt;/strong&gt; 자바스크립트에서 접근 불가 (보안 강화)&lt;/li&gt;
&lt;li&gt;도메인 및 경로 지정 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.3 예제 (JavaScript)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// 쿠키 설정
document.cookie = &quot;username=John; expires=Fri, 31 Dec 2024 23:59:59 GMT; path=/&quot;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;2.4 보안 문제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;XSS 공격:&lt;/strong&gt; 자바스크립트로 쿠키를 탈취할 수 있음 → &lt;strong&gt;HttpOnly&lt;/strong&gt; 설정으로 방지&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSRF 공격:&lt;/strong&gt; 인증된 쿠키를 악용해 원치 않는 요청을 보낼 수 있음 → &lt;strong&gt;CSRF 토큰&lt;/strong&gt; 사용 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 세션 (Session)&lt;/h2&gt;
&lt;h3&gt;3.1 개념&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;서버에서 사용자의 정보를 저장하고, 클라이언트에게는 &lt;strong&gt;세션 ID&lt;/strong&gt;만 부여하는 방식&lt;/li&gt;
&lt;li&gt;클라이언트는 요청 시 세션 ID를 보내고, 서버는 이를 통해 사용자 정보를 찾음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.2 특징&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터가 서버에 저장되므로 보안성이 높음&lt;/li&gt;
&lt;li&gt;일정 시간이 지나면 세션이 만료됨&lt;/li&gt;
&lt;li&gt;클라이언트는 의미 없는 세션 ID만 저장하므로 정보 유출 위험이 적음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 동작 방식&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;사용자가 로그인하면 서버에서 &lt;strong&gt;세션 ID 생성&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;클라이언트의 &lt;strong&gt;쿠키&lt;/strong&gt;에 세션 ID 저장&lt;/li&gt;
&lt;li&gt;이후 요청마다 클라이언트는 &lt;strong&gt;세션 ID를 전송&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;서버는 &lt;strong&gt;세션 저장소&lt;/strong&gt;에서 ID를 대조해 사용자 정보 조회&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;3.4 예제 (Express.js)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const session = require(&apos;express-session&apos;)

app.use(session({
    secret: &apos;my_secret_key&apos;,
    resave: false,
    saveUninitialized: true,
    cookie: { secure: false }
}))

app.get(&apos;/&apos;, (req, res) =&amp;gt; {
    req.session.username = &quot;John&quot;  // 세션에 데이터 저장
    res.send(&quot;Session Set!&quot;)
})

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;3.5 단점 및 보안 문제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;세션 탈취(Session Hijacking):&lt;/strong&gt; 세션 ID를 탈취당하면 해커가 사용자 행세를 할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서버 부담:&lt;/strong&gt; 동시 접속자가 많으면 서버 메모리 사용량이 급증함 (확장성 문제)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. JWT (JSON Web Token)&lt;/h2&gt;
&lt;h3&gt;4.1 개념&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;토큰 기반 인증 방식&lt;/li&gt;
&lt;li&gt;서버에 사용자 정보를 저장하지 않고, &lt;strong&gt;토큰 자체에 암호화된 정보(Payload)를 포함&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;클라이언트가 토큰을 저장하고 요청할 때마다 헤더에 포함시켜 보냄&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;4.2 구조 (3부분)&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;aaaaa.bbbbb.ccccc&lt;/code&gt; 형태&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;헤더(Header):&lt;/strong&gt; 토큰 타입과 암호화 알고리즘 (예: HS256)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;페이로드(Payload):&lt;/strong&gt; 실제 사용자 정보 (username, 만료 시간 등)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서명(Signature):&lt;/strong&gt; 위변조 방지를 위한 서명 값&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;4.3 예제 (Node.js + jsonwebtoken)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;const jwt = require(&apos;jsonwebtoken&apos;)

// JWT 생성 (발급)
const token = jwt.sign({ username: &quot;John&quot; }, &quot;my_secret_key&quot;, { expiresIn: &quot;1h&quot; })
console.log(token)

// JWT 검증
const decoded = jwt.verify(token, &quot;my_secret_key&quot;)
console.log(decoded)

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;4.4 장점과 단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;✅ 장점&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stateless:&lt;/strong&gt; 서버에 세션 정보를 저장하지 않아 서버 부담이 적음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;확장성:&lt;/strong&gt; 마이크로서비스(MSA) 환경이나 다중 서버 환경에 적합&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;❌ 단점&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;/ul&gt;
&lt;h2&gt;5. 세션 vs 쿠키 vs JWT 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;쿠키 (Cookie)&lt;/th&gt;
&lt;th&gt;세션 (Session)&lt;/th&gt;
&lt;th&gt;JWT (Token)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&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;서버 (메모리/DB)&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;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;td&gt;낮음 (Stateless)&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;td&gt;API 인증, 앱 인증, MSA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;6. 언제 무엇을 사용할까?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;📌 쿠키&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&quot;오늘 하루 보지 않기&quot; 같은 팝업 설정&lt;/li&gt;
&lt;li&gt;비로그인 상태의 장바구니 임시 저장&lt;/li&gt;
&lt;li&gt;단순한 사용자 편의 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;📌 세션&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;보안이 중요한 웹 애플리케이션의 로그인 관리&lt;/li&gt;
&lt;li&gt;사용자가 적거나 단일 서버 환경인 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;📌 JWT&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;모바일 앱&lt;/strong&gt; 또는 &lt;strong&gt;SPA(React, Vue)&lt;/strong&gt; 인증&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MSA(마이크로서비스)&lt;/strong&gt; 환경에서 서비스 간 인증&lt;/li&gt;
&lt;li&gt;소셜 로그인 (OAuth) 토큰 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;7. 보안 이슈 &amp;amp; 해결 방법&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;XSS (Cross-Site Scripting)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;쿠키/토큰을 자바스크립트로 탈취하는 공격&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결:&lt;/strong&gt; 쿠키 설정 시 &lt;code&gt;HttpOnly&lt;/code&gt; 옵션 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CSRF (Cross-Site Request Forgery)&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;사용자의 의지와 무관하게 공격자가 의도한 행위(수정, 삭제 등)를 요청하게 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결:&lt;/strong&gt; CSRF 토큰 사용, Referer 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JWT 탈취 위험&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;토큰이 탈취되면 만료될 때까지 막을 수 없음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결:&lt;/strong&gt; Access Token 만료 시간을 짧게(30분) 설정하고, Refresh Token을 함께 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;네트워크 도청&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;데이터가 전송 중에 가로채질 수 있음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;해결:&lt;/strong&gt; 모든 통신에 &lt;strong&gt;HTTPS&lt;/strong&gt; 필수 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;8. 실제 사례 분석&lt;/h2&gt;
&lt;ol&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;JWT 기반 API 인증:&lt;/strong&gt; 카카오/구글 로그인(OAuth 2.0), 넷플릭스 등 멀티 디바이스 환경 서비스&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>MSA</title><link>https://blog.csyhorizon.dev/posts/2025/cs/msa/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/msa/</guid><pubDate>Thu, 03 Jul 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. MSA 개요&lt;/h2&gt;
&lt;h3&gt;1.1 MSA란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;*MSA(Microservices Architecture)**는 하나의 애플리케이션을 여러 개의 독립적인 서비스로 나누어 개발하고 운영하는 소프트웨어 아키텍처 방식임&lt;/li&gt;
&lt;li&gt;각 서비스는 독립적으로 배포되고 실행되며, 특정 비즈니스 기능을 담당함&lt;/li&gt;
&lt;li&gt;즉, &lt;strong&gt;대규모 서비스/애플리케이션을 여러 개의 작은 규모의 서비스/애플리케이션으로 나누는 접근 방식&lt;/strong&gt;을 말함&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1.2 MSA와 모놀리식 아키텍처 비교&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;모놀리식 (Monolithic)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하나의 단위로 개발되는 일체식 애플리케이션&lt;/li&gt;
&lt;li&gt;단일 프로세스에서 실행됨&lt;/li&gt;
&lt;li&gt;스케일 아웃(Scale-out) 시 모놀리스 전체가 확장됨 (로드 밸런서를 두고 전체 덩어리를 복제)&lt;/li&gt;
&lt;li&gt;수정 시 시스템 전체를 재빌드/재배포해야 함&lt;/li&gt;
&lt;li&gt;데이터베이스가 통합되어 있어 탄력적 대응이 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;마이크로서비스 (Microservices)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버 측이 여러 개의 조각으로 구성되어 각 서비스가 별개의 인스턴스로 로딩됨&lt;/li&gt;
&lt;li&gt;각기 다른 저장소를 사용하여 업무 단위로 모듈 경계가 명확함&lt;/li&gt;
&lt;li&gt;API를 통해서만 느슨하게 연계됨 (Loose Coupling)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;폴리글랏(Polyglot):&lt;/strong&gt; 각 서비스를 구축하는 언어/DB를 자율적으로 선택 가능&lt;/li&gt;
&lt;li&gt;자동화된 환경에서 독립적으로 배포됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;🆚 비교표&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;모놀리식 아키텍처 (Monolithic)&lt;/th&gt;
&lt;th&gt;마이크로서비스 아키텍처 (MSA)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&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;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;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;strong&gt;장애 영향&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;전체 시스템에 영향 (SPOF 위험)&lt;/td&gt;
&lt;td&gt;특정 서비스만 장애 발생 가능 (격리)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;2. MSA의 장점과 단점&lt;/h2&gt;
&lt;h3&gt;2.1 장점&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; 각 서비스의 목적에 맞는 최적의 언어와 DB 선택 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2.2 단점&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; DB가 분산되어 있어 트랜잭션 관리가 어렵고 정합성 맞추기가 까다로움&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;네트워크 비용 증가:&lt;/strong&gt; 내부 호출(In-memory call)이 아닌 네트워크 통신(RPC, REST)이 많아져 지연 시간(Latency) 발생 가능&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;분산 트랜잭션 어려움:&lt;/strong&gt; 하나의 작업이 여러 서비스에 걸칠 경우 롤백 등의 처리가 복잡함 (SAGA 패턴 등 필요)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. MSA의 핵심 구성 요소&lt;/h2&gt;
&lt;h3&gt;3.1 API Gateway&lt;/h3&gt;
&lt;p&gt;클라이언트와 마이크로서비스 간의 &apos;문지기&apos; 역할을 하는 중간 계층&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; JWT, OAuth 등을 이용해 입구에서 보안 처리&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;3.2 서비스 디스커버리 (Service Discovery)&lt;/h3&gt;
&lt;p&gt;동적으로 생성/삭제되는 서비스들의 위치(IP, Port)를 등록하고 찾는 시스템&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;주요 기능:&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;Health Check:&lt;/strong&gt; 장애가 난 서비스는 목록에서 자동 제외&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;예시:&lt;/strong&gt; Eureka (Spring Cloud), Consul, Zookeeper&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.3 분산 데이터 관리&lt;/h3&gt;
&lt;p&gt;각 마이크로서비스는 &lt;strong&gt;Database per Service&lt;/strong&gt; 원칙에 따라 독립적인 DB를 가짐&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Eventual Consistency:&lt;/strong&gt; 실시간 일관성(ACID) 대신, 시간 차를 두고 최종적으로 데이터가 일치하도록 설계&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CQRS:&lt;/strong&gt; 명령(Write)과 조회(Read) 모델을 분리하여 성능 개선&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;이벤트 기반 아키텍처:&lt;/strong&gt; Kafka, RabbitMQ 등을 통해 데이터 변경 사실을 이벤트로 전파&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SAGA 패턴:&lt;/strong&gt; 분산 환경에서 트랜잭션을 안전하게 처리하기 위한 보상 트랜잭션 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3.4 로깅 및 모니터링&lt;/h3&gt;
&lt;p&gt;서비스가 분산되어 있어 장애 추적이 어렵기 때문에 중앙 집중화된 관리가 필수임&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;분산 로깅:&lt;/strong&gt; 여러 서비스의 로그를 한곳에 모아서 분석 (Trace ID 활용)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;메트릭 수집:&lt;/strong&gt; CPU, 메모리, 응답 시간, 에러율 등 수집&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;ul&gt;
&lt;li&gt;&lt;strong&gt;Logging:&lt;/strong&gt; ELK Stack (Elasticsearch, Logstash, Kibana)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring:&lt;/strong&gt; Prometheus, Grafana&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tracing:&lt;/strong&gt; Jaeger, Zipkin, OpenTelemetry&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;4. MSA 구축 시 고려 사항&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;서비스 경계 정의:&lt;/strong&gt; 도메인 주도 설계(DDD) 등을 기반으로 비즈니스 경계를 명확히 나누어야 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;데이터베이스 설계:&lt;/strong&gt; 단일 DB 공유를 피하고 서비스별 DB 설계를 원칙으로 함&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;보안 강화:&lt;/strong&gt; 서비스 간 내부 통신 암호화(mTLS), 인증/권한 관리 철저&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;테스트 전략:&lt;/strong&gt; 단위 테스트(Unit)뿐만 아니라 복잡한 통합 테스트(E2E) 시나리오 수립 필요&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>캐싱</title><link>https://blog.csyhorizon.dev/posts/2025/cs/caching/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/caching/</guid><pubDate>Fri, 20 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;캐싱(Caching)&lt;/h1&gt;
&lt;p&gt;:::tip
캐싱이란?
캐싱(Cache)이란 자주 사용하는 데이터를 빠르게 접근할 수 있도록 저장하는 임시 저장소를 의미
:::&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;/ul&gt;
&lt;h2&gt;캐싱이 필요한 이유&lt;/h2&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;h3&gt;예시&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;유튜브 동영상이 처음 재생될 때는 로딩 시간이 걸리지만, 한 번 본 영상은 다시 로드하지 않고 빠르게 실행됨 (브라우저 캐싱)&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;캐싱의 기본 원리&lt;/h1&gt;
&lt;h2&gt;주요 개념&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cache Hit (캐시 적중): 요청한 데이터가 캐시에 존재하여 빠르게 반환되는 경우&lt;/li&gt;
&lt;li&gt;Cache Miss (캐시 미스): 요청한 데이터가 캐시에 없어 원본 소스에서 가져와야 하는 경우 → 속도가 느려짐&lt;/li&gt;
&lt;li&gt;Cache Eviction (캐시 제거): 캐시 공간이 부족할 때 덜 중요한 데이터를 삭제하는 과정&lt;/li&gt;
&lt;li&gt;Cache Invalidation (캐시 무효화): 데이터 변경이 발생했을 때 캐시를 비우고 최신 데이터로 갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;캐싱의 종류&lt;/h1&gt;
&lt;h2&gt;하드웨어 기반 캐싱&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;CPU 캐시: CPU 내부에 L1, L2, L3 캐시를 두어 연산 속도를 높임&lt;/li&gt;
&lt;li&gt;디스크 캐시: HDD, SSD에서 자주 사용하는 데이터를 미리 저장하여 속도 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;소프트웨어 기반 캐싱&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;웹 캐시: CDN(Content Delivery Network), 브라우저 캐시, HTTP 캐싱을 통해 인터넷 속도 향상&lt;/li&gt;
&lt;li&gt;데이터베이스 캐시: MySQL Query Cache, Redis, Memcached 등의 도구를 활용하여 DB 부하 감소&lt;/li&gt;
&lt;li&gt;애플리케이션 캐시: 서버 사이드 캐싱, API 응답 캐싱을 통해 서버 성능 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;캐싱 전략&lt;/h1&gt;
&lt;h2&gt;캐시 관리 정책&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;LRU(Least Recently Used): 가장 오랫동안 사용되지 않은 데이터를 제거 (예: 최근 열어본 문서 먼저 보이도록 정리)&lt;/li&gt;
&lt;li&gt;LFU(Least Frequently Used): 가장 적게 사용된 데이터를 제거 (예: 자주 보는 파일을 저장하고 사용하지 않는 것은 삭제)&lt;/li&gt;
&lt;li&gt;FIFO(First-In First-Out): 먼저 들어온 데이터를 먼저 제거 (예: 마트의 재고 정리 방식과 유사)&lt;/li&gt;
&lt;li&gt;TTL(Time-To-Live): 특정 시간이 지나면 캐시 데이터를 자동으로 삭제 (예: 로그인 유지 시간이 만료되면 자동 로그아웃)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;캐싱 업데이트 방식&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Write-Through: 데이터를 캐시에 저장하면서 원본 데이터에도 즉시 반영 (안전하지만 속도가 느림)&lt;/li&gt;
&lt;li&gt;Write-Back: 변경 사항을 캐시에 먼저 반영하고 이후 원본 데이터에 저장 (속도는 빠르지만 데이터 손실 가능성 있음)&lt;/li&gt;
&lt;li&gt;Write-Around: 원본 데이터에만 저장하고 필요한 경우 캐시에 로드 (자주 변경되지 않는 데이터에 유용)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;캐시 계층화(Cache Layering)&lt;/h2&gt;
&lt;p&gt;여러 단계의 캐싱을 활용하여 최적의 성능 제공&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L1 캐시: 애플리케이션 내부 캐시 (메모리 기반)&lt;/li&gt;
&lt;li&gt;L2 캐시: Redis, Memcached 같은 네트워크 기반 캐시&lt;/li&gt;
&lt;li&gt;L3 캐시: 데이터베이스 쿼리 캐싱&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Redis(레디스)와 캐싱&lt;/h2&gt;
&lt;p&gt;:::tip
Redis란?
오픈 소스 기반의 메모리 데이터 저장소로 빠른 데이터 접근을 지원하는 NoSQL 캐시 데이터베이스
Key-Value 구조를 기반으로 작동하며, 다양한 데이터 구조(Hash, List, Set, Sorted Set 등) 지원
영속성(Persistence) 지원: 데이터를 디스크에 저장하여 시스템 재시작 후에도 유지 가능
:::&lt;/p&gt;
&lt;h2&gt;Redis의 주요 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;초고속 데이터 처리: 메모리 기반이므로 읽기/쓰기 속도가 매우 빠름&lt;/li&gt;
&lt;li&gt;다양한 데이터 구조 지원: 단순한 문자열 외에도 리스트, 해시, 집합, 정렬된 집합 등 지원&lt;/li&gt;
&lt;li&gt;TTL(Time-To-Live) 기능: 설정한 시간이 지나면 자동으로 캐시 데이터 삭제&lt;/li&gt;
&lt;li&gt;분산 처리 가능: 클러스터링을 통해 여러 서버에서 데이터 분산 저장 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Redis 활용 예제&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;세션 관리: 로그인 사용자 세션을 Redis에 저장하여 빠르게 인증&lt;/li&gt;
&lt;li&gt;실시간 데이터 처리: 실시간 순위, 대기열, 메시지 큐 등에서 활용&lt;/li&gt;
&lt;li&gt;데이터베이스 캐싱: 자주 조회하는 데이터를 캐싱하여 DB 부하 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;캐싱 실패와 해결 방법&lt;/h1&gt;
&lt;p&gt;캐시 스탬피드(Cache Stampede): 여러 요청이 동시에 원본 데이터를 가져오려고 할 때 발생&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해결 방법: Lazy Loading, Request Collapsing, Prewarming Cache&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cold Cache Problem: 새로운 서버가 가동될 때 캐시가 비어 있어 부하가 증가하는 문제&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해결 방법: 미리 캐싱된 데이터 로드, Warm-up 캐싱 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note
캐싱은 데이터를 미리 저장해 빠르게 제공하는 기술이다.
Redis는 빠르고 확장 가능한 캐싱 솔루션으로 활용 가능하다.
적절한 캐싱 전략을 통해 성능을 최적화해야 한다.
:::&lt;/p&gt;
</content:encoded></item><item><title>CORS</title><link>https://blog.csyhorizon.dev/posts/2025/cs/cors/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/cors/</guid><pubDate>Sat, 14 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;CORS (Cross-Origin Resource Sharing) 완전 정복&lt;/h1&gt;
&lt;h2&gt;1. CORS란?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;*CORS(Cross-Origin Resource Sharing)**는 직역하면 &apos;교차 출처 리소스 공유 정책&apos;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;웹 생태계에서는 보안을 위해 기본적으로 다른 출처의 리소스를 제한하는데, CORS는 특정 조건 하에 이를 허용해주는 정책을 말함. 여기서 핵심은 &lt;strong&gt;&apos;출처(Origin)&apos;&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Warning!
출처(Origin)인 Protocol + Hostname + Port를 비교하여 하나라도 다르면 브라우저는 이를 차단하고 아래와 같은 에러를 만남&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Access to fetch at ‘...’ from origin ‘...’ has been blocked by CORS policy...&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2. 출처 (Origin) 이해하기&lt;/h2&gt;
&lt;h3&gt;URL의 구성 요소&lt;/h3&gt;
&lt;p&gt;URL은 다음과 같이 구성됨.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Protocol (Scheme):&lt;/strong&gt; &lt;code&gt;http&lt;/code&gt;, &lt;code&gt;https&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host:&lt;/strong&gt; 사이트 도메인 (예: &lt;code&gt;www.domain.com&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port:&lt;/strong&gt; 포트 번호 (예: &lt;code&gt;3000&lt;/code&gt;, &lt;code&gt;8080&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Path:&lt;/strong&gt; 내부 경로&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query String:&lt;/strong&gt; 요청의 key-value&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fragment:&lt;/strong&gt; 해시 태그&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;동일 출처(Same-Origin)의 기준&lt;/h3&gt;
&lt;p&gt;브라우저는 &lt;strong&gt;Protocol, Host, Port&lt;/strong&gt; 이 3가지가 모두 일치해야 &apos;같은 출처&apos;로 인식함. 하나라도 다르면 **다른 출처(Cross-Origin)**로 간주하여 차단&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;비교 예시&lt;/strong&gt;
기준 URL: &lt;code&gt;https://www.myshop.com&lt;/code&gt; (기본 포트 443)&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;URL&lt;/th&gt;
&lt;th&gt;접근 가능 여부 (SOP 준수)&lt;/th&gt;
&lt;th&gt;이유&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;https://www.myshop.com/user&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;https://myshop.com/user&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;http://www.myshop.com/user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;프로토콜&lt;/strong&gt; 불일치 (https vs http)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;https://en.myshop.com/user&lt;/code&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;code&gt;https://www.myshop.com:8080&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;포트&lt;/strong&gt; 불일치 (443 vs 8080)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;참고: 출처 비교와 차단은 **&apos;브라우저&apos;**가 수행함. 서버는 정상적으로 200 OK를 응답하더라도, 브라우저가 출처를 비교해 다르면 데이터를 버리고 에러를 띄움 (Server-to-Server 통신에서는 발생하지 않음)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;3. 동일 출처 정책 (SOP: Same-Origin Policy)&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;SOP&lt;/strong&gt;는 &quot;동일한 출처에 대해서만 리소스를 공유할 수 있다&quot;는 보안 규칙
같은 출처의 리소스는 자유롭게 가져오지만, 다른 출처의 리소스(이미지, API 등)는 상호작용을 막는 것이 기본 원칙&lt;/p&gt;
&lt;h3&gt;SOP가 필요한 이유&lt;/h3&gt;
&lt;p&gt;출처가 다른 애플리케이션끼리 자유롭게 통신하면 보안에 매우 취약해짐&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CSRF (Cross-Site Request Forgery):&lt;/strong&gt; 사용자가 악성 사이트에 접속했을 때, 해커가 심어둔 코드가 사용자의 브라우저를 통해 로그인이 되어 있는 다른 사이트(은행, 포털 등)에 요청을 보내 개인정보를 탈취하거나 조작할 수 있음&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;XSS (Cross-Site Scripting):&lt;/strong&gt; 악성 스크립트 실행 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉, SOP는 악의적인 스크립트가 실행되지 않도록 브라우저 차원에서 사전에 방지하는 안전장치임. 하지만 현대 웹에서는 외부 리소스 사용이 필수적이므로, &lt;strong&gt;CORS 정책을 지킨 요청에 한해 예외적으로 허용&lt;/strong&gt;해주는 것&lt;/p&gt;
&lt;h2&gt;4. CORS 동작 원리 (기본 흐름)&lt;/h2&gt;
&lt;p&gt;기본적으로 클라이언트와 서버가 헤더를 주고받으며 확인&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;클라이언트:&lt;/strong&gt; 요청 헤더의 &lt;code&gt;Origin&lt;/code&gt; 필드에 현재 출처를 담아 보냄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서버:&lt;/strong&gt; 응답 헤더의 &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; 필드에 리소스 접근이 허용된 출처를 담아 보냄&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;브라우저:&lt;/strong&gt; 자신이 보낸 &lt;code&gt;Origin&lt;/code&gt;과 서버가 준 &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;을 비교. 유효하면 통과, 아니면 폐기(CORS 에러)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;5. CORS 작동 시나리오 3가지&lt;/h2&gt;
&lt;h3&gt;① 예비 요청 (Preflight Request)&lt;/h3&gt;
&lt;p&gt;가장 일반적인 시나리오. 브라우저가 본 요청을 보내기 전에 안전한지 확인하기 위해 &lt;strong&gt;&lt;code&gt;OPTIONS&lt;/code&gt; 메서드&lt;/strong&gt;로 미리 찔러보는 것&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;과정:&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;브라우저 → 서버: &lt;code&gt;OPTIONS&lt;/code&gt; 요청 (Origin, 사용할 메서드, 헤더 정보 포함)&lt;/li&gt;
&lt;li&gt;서버 → 브라우저: 허용 여부 응답 (&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; 등)&lt;/li&gt;
&lt;li&gt;브라우저: 안전하다 판단되면 실제 본 요청(Actual Request) 전송&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;캐싱:&lt;/strong&gt; 매번 예비 요청을 보내면 성능이 저하되므로 &lt;code&gt;Access-Control-Max-Age&lt;/code&gt; 헤더로 결과를 일정 시간 캐싱할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;② 단순 요청 (Simple Request)&lt;/h3&gt;
&lt;p&gt;예비 요청 없이 바로 본 요청을 보내는 방식. 하지만 조건이 매우 까다로워 잘 쓰이지 않음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;조건:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;메서드: &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt; 중 하나&lt;/li&gt;
&lt;li&gt;헤더: &lt;code&gt;Accept&lt;/code&gt;, &lt;code&gt;Content-Type&lt;/code&gt; 등 기본 헤더만 허용&lt;/li&gt;
&lt;li&gt;Content-Type: &lt;code&gt;application/x-www-form-urlencoded&lt;/code&gt;, &lt;code&gt;multipart/form-data&lt;/code&gt;, &lt;code&gt;text/plain&lt;/code&gt;만 가능. (&lt;strong&gt;&lt;code&gt;application/json&lt;/code&gt;은 불가능하므로 대부분 Preflight를 탐&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;③ 인증된 요청 (Credentialed Request)&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;ul&gt;
&lt;li&gt;&lt;code&gt;fetch&lt;/code&gt;, &lt;code&gt;axios&lt;/code&gt;, &lt;code&gt;jQuery&lt;/code&gt; 등에서 &lt;code&gt;credentials: &apos;include&apos;&lt;/code&gt; 또는 &lt;code&gt;withCredentials: true&lt;/code&gt; 설정 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;서버 설정 (중요):&lt;/strong&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;Access-Control-Allow-Credentials: true&lt;/code&gt; 로 설정해야 함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt;에 와일드카드()를 쓸 수 &lt;strong&gt;없음&lt;/strong&gt;. 반드시 구체적인 출처(예: &lt;code&gt;http://localhost:3000&lt;/code&gt;)를 명시해야 함&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;6. CORS 해결 방법 (솔루션)&lt;/h2&gt;
&lt;h3&gt;① 서버에서 헤더 설정 (백엔드)&lt;/h3&gt;
&lt;p&gt;가장 정석적인 방법. 백엔드에서 허용할 출처를 명시&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spring Framework (Global 설정)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping(&quot;/**&quot;)
            .allowedOrigins(&quot;http://localhost:8080&quot;) // 허용할 출처
            .allowedMethods(&quot;GET&quot;, &quot;POST&quot;) // 허용할 메서드
            .allowCredentials(true) // 인증 정보 허용
            .maxAge(3000); // Preflight 캐싱 시간
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Spring Framework (Controller 별 설정)&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@CrossOrigin(origins = &quot;*&quot;, methods = RequestMethod.GET)
@GetMapping(&quot;/url&quot;)
public ResponseEntity&amp;lt;?&amp;gt; findAll() { ... }

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Nginx 설정&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;location /api/ {
    # CORS 헤더 추가
    add_header &apos;Access-Control-Allow-Origin&apos; &apos;*&apos; always;
    add_header &apos;Access-Control-Allow-Methods&apos; &apos;GET, POST, OPTIONS&apos; always;
    add_header &apos;Access-Control-Allow-Credentials&apos; &apos;true&apos; always;

    # Preflight 요청 처리
    if ($request_method = OPTIONS) {
        return 204;
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;② 프록시(Proxy) 서버 이용&lt;/h3&gt;
&lt;p&gt;브라우저의 CORS 정책은 브라우저와 서버 간의 통신에서만 적용됨. 서버와 서버 간 통신에는 적용되지 않는 점을 이용&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Webpack Dev Server (React 개발 환경)&lt;/strong&gt;
개발 중에는 로컬 프록시를 띄워 CORS를 우회할 수 있음. 브라우저는 같은 출처인 로컬 프록시로 요청을 보내고, 프록시가 실제 백엔드로 요청을 전달하는 방식&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;package.json&lt;/code&gt; 설정 예시:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;proxy&quot;: &quot;https://myshop.com:8080&quot;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;주의: React의 Proxy 설정은 개발 환경(Development)에서만 동작함. 배포 시에는 백엔드나 Nginx 등에서 실제 CORS 설정을 해줘야 함&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded></item><item><title>Garbage Collection</title><link>https://blog.csyhorizon.dev/posts/2025/cs/garbage_collection/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/garbage_collection/</guid><pubDate>Sun, 01 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Garbage Collection 개념&lt;/h1&gt;
&lt;p&gt;:::note
**Garbage Collection(이하 GC)**란,&lt;/p&gt;
&lt;p&gt;동적으로 할당한 메모리 중 더이상 필요없는 객체를 모아 제거하는 메모리 관리 기법
:::&lt;/p&gt;
&lt;p&gt;이때 동적으로 할당된 메모리는 Heap 영역에 할당되므로, &lt;code&gt;GC는 Heap 영역을 직접적으로 관리한다&lt;/code&gt; 라고 이해하면 됨&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C 언어의 경우 malloc() free() 등을 통해 직접 메모리 관리를 해야함&lt;/li&gt;
&lt;li&gt;Java 개발자는 GC로 인해 메모리 관리에 신경쓰지 않고 개발에만 집중할 수 있음&lt;/li&gt;
&lt;li&gt;Kotlin을 포함하여 Python, Go, Javascript 등 많은 언어에서도 GC가 내장되어 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Heap 영역&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;GC의 성능 향상을 위해, Heap 영역은 Young Generation과 Old Generation으로 나눠짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Young Generation : 최근에 생성된 객체가 할당되는 영역&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Eden : new를 통해 새로 생성된 객체가 할당&lt;/li&gt;
&lt;li&gt;S0, S1(Survivior0, 1) : GC가 실행된 이후에도 살아남은 객체가 할당되는 영역&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Old Genaration : Young Generation의 객체가 특정 임계점에 도달하면 할당되는 영역&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;임계점 지표로 사용되는 값으로 Age가 대표적이며, 이는 Survivor 영역에서 객체가 살아남은 횟수를 의미&lt;/li&gt;
&lt;li&gt;설정값에 따라 달라지나, 일반적으로 Young Generation의 공간보다 2-3배 큰 공간을 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note
왜 이런 구조가 GC 성능 향상에 도움이 될까?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대부분의 객체는 금방 접근 불가능한 상태가 됨&lt;/li&gt;
&lt;li&gt;오래된 객체가 젊은 객체를 참조하는 경우는 드뭄
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;따라서, 크기가 작은 Young Generation을 대상으로 GC를 자주 수행하고, 크기가 큰 Old Generation을 대상으로는 GC를 비교적 적게 실행&lt;/p&gt;
&lt;h1&gt;Stop-The-World&lt;/h1&gt;
&lt;p&gt;Stop-The-Word(이하 STW)란, GC를 수행하기 위해 JVM이 프로그램 실행을 멈추는 현상을 의미&lt;/p&gt;
&lt;p&gt;즉, GC가 작동하는 동안 GC가 수행되는 스레드를 제외한 모든 스레드는 멈추게 됨&lt;/p&gt;
&lt;p&gt;따라서 GC가 너무 자주 실행되면 프로그램의 성능 하락으로 이어지며,
GC의 성능을 개선한다는 것은 곧 STW의 시간을 최소화하는 작업을 의미&lt;/p&gt;
&lt;h1&gt;Mark And Sweep&lt;/h1&gt;
&lt;p&gt;Mark And Sweep이란, GC의 대상 객체를 식별하고 제거하는 과정을 의미&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mark : 사용되고 있는 객체(reachable)를 찾아내 마킹하는 단계&lt;/li&gt;
&lt;li&gt;Sweep : 사용되지 않는 객체(unreachable)를 Heap에서 제거하는 단계&lt;/li&gt;
&lt;li&gt;Compaction : Sweep 후에 분산된 객체를 압축하는 단계&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note
GC에 따라 수행하지 않는 경우도 있다.
:::&lt;/p&gt;
&lt;h1&gt;Minor GC 과정&lt;/h1&gt;
&lt;p&gt;Minor GC란, Young Generaion에서 발생하는 GC를 의미한다.&lt;/p&gt;
&lt;p&gt;Old Generation에 비해 상대적으로 공간이 작아 GC 소요 시간 또한 적기 때문에 이러한 이름이 붙었다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;처음 생성된 객체는 Eden 영역에 할당&lt;/li&gt;
&lt;li&gt;Eden 영역이 가득 차면, Minor GC 실행&lt;/li&gt;
&lt;li&gt;Mark 동작을 통해 사용되는 객체를 탐색하고, Survivor 영역으로 이동&lt;/li&gt;
&lt;li&gt;Sweep 동작을 통해 Eden 영역의 사용되지 않는 객체 삭제&lt;/li&gt;
&lt;li&gt;살아남은 객체의 age 값 증가&lt;/li&gt;
&lt;li&gt;이후 Eden 영역이 가득 찰 때마다 Minor GC를 실행하고, 남은 Survivor 영역으로 이동&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Young Generaion에서는 이러한 과정을 반복
살펴본 바와 같이, Eden 영역이 가득 찰 때마다 GC가 실행된다는 특징&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Major GC 과정&lt;/h1&gt;
&lt;p&gt;Major GC란, Old Generation에서 발생하는 GC를 의미&lt;/p&gt;
&lt;p&gt;Young Generation의 객체가 특정 임계값에 도달하면 Old Generation으로 이동함. 이제 Major GC란?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Young Generation의 객체가 특정 임계값(Age = 8)에 도달&lt;/li&gt;
&lt;li&gt;임계값에 도달한 객체가 Old Generation으로 이동&lt;/li&gt;
&lt;li&gt;위 과정이 반복되어 Old Generation 공간이 가득 차면, Major GC 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Old Genaration에서는 Young Generation과 같이 영역이 나뉘지 않으므로,
별도의 공간 이동없이 Mark And Sweep 과정을 통해 사용되지 않는 객체가 모두 삭제&lt;/p&gt;
</content:encoded></item><item><title>ACPC 2025 참여 후기</title><link>https://blog.csyhorizon.dev/posts/2025/logs/acpc/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/logs/acpc/</guid><pubDate>Sun, 01 Jun 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;참여&lt;/h2&gt;
&lt;p&gt;AWS &amp;amp; Codetree가 알고리즘 대회하길래 참여했습니다&lt;/p&gt;
&lt;p&gt;예선은 무난하게 통과했는데 본선은 그렇지 못한 거 같습니다..&lt;/p&gt;
&lt;p&gt;암튼 예선 붙은겸 알고리즘 대회 안나가봐서 우리 대학의 유일한 참여자로 나가보게 되었습니다.
&lt;img src=&quot;https://logimage.csyhorizon.dev/acpc_centerfield.jpg&quot; alt=&quot;centerfield&quot; /&gt;
아침에 센텀필드로 갔습니다. 지방에서 저기 가려면 5시 기차 타고 가야합니다&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/acpc_aws.jpg&quot; alt=&quot;aws&quot; /&gt;
생각했던 와! 스러운 것보단 그냥 강의장 같은 기분이였던 AWS&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;아침 5시에 일어나서 갔더니 제정신은 아니었습니다&lt;/li&gt;
&lt;li&gt;처음 해보는 알고리즘 대회가 기대되기도 했습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;결과&lt;/h2&gt;
&lt;p&gt;7번인가 그거 하나만 풀고, 나머진 1번 말고는 도통 감을 못잡았습니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;알고보니까 골드 2인 제가 딱 수준 맞게 풀었더라고요.. (그 이상은 난이도가..!)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;./discord.png&quot; alt=&quot;discord&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;진짜 코테 뚫을 실력은 되는데... PS는 진짜 넘사 영역인 거 같습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://logimage.csyhorizon.dev/acpc_clothes.jpg&quot; alt=&quot;acpc_clothes&quot; /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;진짜 대학에서 교통, 식대비, 참여비 다 지원해줘서 갔는데 제 수준을 딱 깨닫고 왔습니다.&lt;/li&gt;
&lt;li&gt;세상엔 괴물이 많아요. 아호코라식? 그런 얘기 하던데 저 그거 처음 들어봤습니다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;문제&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.codetree.ai/ko/frequent-problems/acpc&quot;&gt;Codetree ACPC 문제&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;내년에도 하는진 모르겠지만 문제는 링크 들어가시면 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::important
알고리즘은 딱 플레까지만 찍고 취미가 아니라면 접는 게 좋은 거 같습니다
:::&lt;/p&gt;
</content:encoded></item><item><title>트랜잭션과 격리수준(고립수준)</title><link>https://blog.csyhorizon.dev/posts/2025/cs/transaction_flow/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/transaction_flow/</guid><pubDate>Wed, 14 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;트랜잭션과 격리 수준&lt;/h1&gt;
&lt;p&gt;:::note
트랜잭션이란?
트랜잭션(Transaction)이란 데이터베이스에서 하나의 논리적인 작업 단위를 의미함.
:::&lt;/p&gt;
&lt;h2&gt;트랜잭션의 ACID 특성&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Atomicity (원자성)
&apos;All or Nothing&apos; 원칙
하나라도 실패하면 전체 롤백(Rollback)&lt;/li&gt;
&lt;li&gt;Consistency (일관성)
트랜잭션 실행 전후 데이터가 일관성을 유지해야 함&lt;/li&gt;
&lt;li&gt;Isolation (격리성)
다른 트랜잭션의 변경이 보이지 않아야 함&lt;/li&gt;
&lt;li&gt;Durability (지속성)
커밋된 데이터는 영구적으로 저장됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;트랜잭션 격리 수준과 발생하는 문제&lt;/h2&gt;
&lt;p&gt;:::warning
트랜잭션 동시 실행 시 발생하는 문제
:::&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dirty Read – 커밋되지 않은 데이터를 읽는 문제&lt;/li&gt;
&lt;li&gt;Non-repeatable Read – 같은 데이터를 두 번 조회했을 때 값이 다름&lt;/li&gt;
&lt;li&gt;Phantom Read – 같은 조건으로 조회했을 때 결과 행 개수가 달라짐&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;격리 수준별 비교&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;격리 수준&lt;/th&gt;
&lt;th&gt;Dirty Read&lt;/th&gt;
&lt;th&gt;Non-repeatable Read&lt;/th&gt;
&lt;th&gt;Phantom Read&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;READ UNCOMMITTED&lt;/td&gt;
&lt;td&gt;❌ 발생&lt;/td&gt;
&lt;td&gt;❌ 발생&lt;/td&gt;
&lt;td&gt;❌ 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;READ COMMITTED&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;REPEATABLE READ (MySQL 기본)&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;SERIALIZABLE&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;h2&gt;Phantom Read가 발생하는 이유&lt;/h2&gt;
&lt;p&gt;MySQL의 REPEATABLE READ는 트랜잭션이 시작될 때 조회한 데이터를 유지하지만 새로운 행의 추가(INSERT)는 막지 않음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해결 방법: SELECT ... FOR UPDATE 사용 또는 SERIALIZABLE 적용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;MVCC (Multi-Version Concurrency Control)&lt;/h2&gt;
&lt;p&gt;:::note
💡 MVCC란?
각 트랜잭션이 시작될 때, 해당 시점의 데이터 스냅샷을 유지
다른 트랜잭션에서 변경이 일어나도 내 트랜잭션에서는 변경 전 데이터가 유지됨
:::&lt;/p&gt;
&lt;h3&gt;MVCC의 장점&lt;/h3&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;MVCC의 단점&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;데이터 버전이 증가하여 스토리지 사용량 증가&lt;/li&gt;
&lt;li&gt;Garbage Collection 필요&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;MySQL InnoDB는 MVCC를 사용하여 트랜잭션 성능을 최적화함&lt;/p&gt;
</content:encoded></item><item><title>로드 밸런싱</title><link>https://blog.csyhorizon.dev/posts/2025/cs/loadbalancing/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/loadbalancing/</guid><description>로드 밸런싱 (Load Balancing)</description><pubDate>Thu, 01 May 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;로드 밸런싱 (Load Balancing)&lt;/h1&gt;
&lt;p&gt;여러 서버나 컴퓨터 자원에 작업을 고르게 분산하여 처리하는 것을 말함&lt;/p&gt;
&lt;p&gt;서버의 규모가 커지고, 이용자 수가 늘어났을 때 대처할 수 있는 방법은 크게 두 가지가 있음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;기존 서버의 성능을 향상시키는 [Scale-Up 방식]&lt;/li&gt;
&lt;li&gt;서버를 여러 대 추가하여 시스템을 확장하는 [Scale-Out 방식]&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[Scale-Out 방식]은 여러 대의 서버로 트래픽을 균등하게 분산해주는 [로드 밸런싱]이 필요&lt;/p&gt;
&lt;h1&gt;로드 밸런서 (Load Balancer)&lt;/h1&gt;
&lt;p&gt;클라이언트와 서버 Pool 또는 네트워크 허브 사이에 위치해서 서버의 부하를 분산시키는 하드웨어나 소프트웨어를 의미&lt;/p&gt;
&lt;h2&gt;로드 밸런서의 종류&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;L4 Load Balancer (전송 계층)&lt;/strong&gt;&lt;br /&gt;
네트워크 계층(IP, IPX)이나 전송 계층(TCP, UDP)의 정보(IP 주소, 포트 번호, MAC 주소, 전송 프로토콜)를 바탕으로 요청(로드)을 분산
패킷 레벨에서만 요청을 분산하기 때문에 속도가 빠르고 효율이 높음
섬세한 라우팅이 불가능하지만, L7 로드 밸런서보다 가격이 저렴&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;L7 Load Balancer (응용 계층)&lt;/strong&gt;&lt;br /&gt;
L4 로드 밸런서의 기능을 포함
응용 계층(HTTP, FTP, SMTP)에서 요청을 분산하기 때문에 HTTP 헤더, 쿠키 등과 같은 사용자의 요청을 기준으로 특정 서버에 트래픽을 분산할 수 있음
섬세한 라우팅이 가능하고 비정상적인 트래픽을 사전에 필터링하여 서비스 안정성이 높음
패킷 내용을 복호화해야 하기에 비용이 더 높음&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;로드 밸런싱 알고리즘&lt;/h1&gt;
&lt;h2&gt;정적 로드 밸런싱 (Static load balancing)&lt;/h2&gt;
&lt;p&gt;고정된 규칙을 따르며, 현재 서버 상태와 무관하게 서버에 들어오는 부하를 분산&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;라운드 로빈 (Round Robin)&lt;/strong&gt;&lt;br /&gt;
클라이언트 요청을 여러 대 서버에 순차적으로 분배&lt;br /&gt;
서버 연결(세션)이 오래 지속되지 않거나, 여러 대 서버가 동일 스펙일 때 적합&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;가중 라운드 로빈 (Weighted Round Robin)&lt;/strong&gt;&lt;br /&gt;
서버 별로 가중치를 지정
가중치가 높은 서버는 더 많은 요청을 받음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IP 해시 (IP Hash)&lt;/strong&gt;&lt;br /&gt;
클라이언트 IP 주소를 특정 서버에 매핑하여 요청을 처리&lt;br /&gt;
사용자가 항상 동일한 서버에 연결됨을 보장&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;동적 로드 밸런싱 (Dynamic load balancing)&lt;/h2&gt;
&lt;p&gt;서버의 현재 상태(CPU, 메모리 등)를 조사한 뒤에 부하를 분산&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;최소 연결 방식 (Least Connection)&lt;/strong&gt;&lt;br /&gt;
가장 적은 연결 상태인 서버에 우선 트래픽을 배분
세션 길이가 길거나 요청 시간이 다를 때 적합&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;가중치 최소 연결 방식 (Weighted Least Connection)&lt;/strong&gt;&lt;br /&gt;
서버별 성능이나 처리 능력을 가중치로 반영&lt;br /&gt;
활성 연결 수를 가중치로 나눈 값이 가장 낮은 서버에 할당&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;최소 응답 시간 방식 (Least Response Time)&lt;/strong&gt;&lt;br /&gt;
현재 연결 상태와 응답 시간을 고려
가장 적은 연결과 짧은 응답 시간을 보이는 서버에 배분&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;리소스 기반 방식 (Resource Based)&lt;/strong&gt;&lt;br /&gt;
서버 부하(CPU, 메모리, 네트워크 등)를 분석해 트래픽을 분산&lt;br /&gt;
각 서버에 에이전트를 설치하여 사용량을 계산
처리에 필요한 리소스 양이 다를 때 적합&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>페이징과 세그멘테이션 기법</title><link>https://blog.csyhorizon.dev/posts/2025/cs/pagingsegment/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/pagingsegment/</guid><description>페이징과 세그멘테이션 기법 (Paging &amp; Segmentation)</description><pubDate>Sun, 27 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;메모리 관리 기법: 페이징과 세그멘테이션&lt;/h1&gt;
&lt;p&gt;:::tip
초기 컴퓨터는 연속 할당 방식을 사용
구현은 단순하지만 메모리 단편화와 동적 크기 변화에 대응하기 어려움
이후 페이징과 세그멘테이션 기법이 등장
MS-DOS나 임베디드에서는 아직 연속할당을 사용&lt;br /&gt;
현재는 대부분 페이징 기법을 기반으로 운영&lt;br /&gt;
:::&lt;/p&gt;
&lt;h2&gt;공통점&lt;/h2&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;li&gt;가상 메모리와 연동 가능하다는 점이 특징&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;세그멘테이션 기법&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;세그멘테이션은 논리적 단위인 세그먼트(코드, 데이터, 스택 등)로 나누어 메모리에 할당&lt;/strong&gt;&lt;br /&gt;
세그먼트 크기는 가변적이며, 외부 단편화가 발생할 수 있음
메모리 보호와 접근 권한 설정이 가능하며, 동적 크기 변경 시 압축이 필요할 수 있음&lt;/p&gt;
&lt;p&gt;:::tip&lt;br /&gt;
각 세그먼트는 연속된 메모리 공간을 할당받음
프로세스 전체가 연속된 공간에 있지 않아도 됨&lt;br /&gt;
:::&lt;/p&gt;
&lt;h3&gt;대표 기반 프로그램&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Intel 80286&lt;/li&gt;
&lt;li&gt;Multics&lt;/li&gt;
&lt;li&gt;IBM System/38&lt;/li&gt;
&lt;li&gt;HP 3000&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;페이징 기법&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;페이징은 메모리를 고정 크기 페이지로 분할하고, 각 페이지를 프레임에 할당&lt;/strong&gt;&lt;br /&gt;
페이지 크기는 일반적으로 4KB 또는 8KB
외부 단편화 문제는 해결되지만, 내부 단편화가 발생할 수 있음
주소 변환 과정에서 성능 오버헤드가 있으나 가상 메모리와 궁합이 좋음&lt;/p&gt;
&lt;h3&gt;대표 운영체제&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Windows NT 계열 (Windows 10, 11 등)&lt;/li&gt;
&lt;li&gt;Linux (Ubuntu, CentOS, Debian 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;페이징과 세그멘테이션 비교&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;특징&lt;/th&gt;
&lt;th&gt;페이징 (Paging)&lt;/th&gt;
&lt;th&gt;세그멘테이션 (Segmentation)&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;하드웨어에 의해 고정&lt;/td&gt;
&lt;td&gt;사용자/컴파일러가 결정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;속도&lt;/td&gt;
&lt;td&gt;상대적으로 빠름&lt;/td&gt;
&lt;td&gt;상대적으로 느림&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단편화 유형&lt;/td&gt;
&lt;td&gt;내부 단편화 가능&lt;/td&gt;
&lt;td&gt;외부 단편화 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;크기 제약&lt;/td&gt;
&lt;td&gt;페이지 크기=프레임 크기&lt;/td&gt;
&lt;td&gt;크기 제한 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;페이징 기법이 가상 메모리에 적합한 이유&lt;/h2&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;p&gt;&lt;strong&gt;가상 메모리는 실제 물리 메모리보다 큰 메모리를 사용하는 것처럼 보이게 함&lt;/strong&gt;&lt;br /&gt;
프로그램은 가상의 메모리 주소를 받아 사용하며, 이는 실제 주소가 아님
이로써 실제 물리 메모리 한계를 극복하고 큰 프로세스 실행이 가능&lt;/p&gt;
&lt;p&gt;:::tip
&lt;strong&gt;페이징과 세그멘테이션은 현대 운영체제의 핵심 메모리 관리 기법&lt;/strong&gt;
:::&lt;/p&gt;
</content:encoded></item><item><title>데드락</title><link>https://blog.csyhorizon.dev/posts/2025/cs/deadlock/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/deadlock/</guid><description>데드락 (DeadLock)</description><pubDate>Tue, 22 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;DeadLock&lt;/h1&gt;
&lt;p&gt;:::tip&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2개 이상의 프로세스가 다른 프로세스의 작업이 끝나기만을 기다리며 작업을 더이상 진행하지 못하는 상태&lt;/li&gt;
&lt;li&gt;다른 프로세스와 공유할 수 없는 자원을 사용할 때 발생
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Resource&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;하드웨어, 소프트웨어 등을 포함하는 개념&lt;/li&gt;
&lt;li&gt;프로세스가 자원을 사용하는 절자&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;DeadLock의 필요 조건(필요 조건)&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;아래 4 가지 조건이 모두 만족되어야 교착 상태&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mutual exclusion(상호 배제) : 자원의 특징&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;가지고 있는동안은 독점적으로만 쓸 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No preemption (비선점) : 자원의 특징&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;뻬앗기지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hold and wait(보유 대기) : 프로세스의 행위&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;보유를 하면서 추가적인 자원을 기다림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Circular wait(순환 대기) : 프로세스의 행위&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;자원을 기다리는 프로세스간에 사이클이 형성됨
자원의 특징은 바꿀 수 없기에, 프로세스의 행위를 예방하는 방식으로 교착 상태를 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Deadlock의 해결 방법&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;Deadlock은 빈번히 발생하지 않기 때문에 미연에 방지하기 위해 오버헤드를 감수하는 것이 현대의 시스템에서는 비효율적&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1️. 예방&lt;/h2&gt;
&lt;p&gt;교착 상태를 유발하는 네 가지 조건이 발생하지 않도록 무력화&lt;/p&gt;
&lt;h3&gt;상호 배제 예방&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자원의 특성은 바꿀 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;비선점 예방&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자원을 선점할 수 있도록(빼앗을 수 있도록) 함.&lt;/li&gt;
&lt;li&gt;마찬가지로 아무때나 뺏으면 문제가 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip
&lt;strong&gt;CPU, Memory는 왜 빼앗을 수 있을까?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;State를 쉽게 save하고 restore할 수 있는 자원에서 주로 사용 (바로 다음 지점에서 재시작 가능)
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;점유와 대기 예방&lt;/h3&gt;
&lt;p&gt;자원을 점유한 상태에서 다른 자원을 기다릴 수 없음 (전부 할당 or 아예 할당 x)&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;프로세스 시작 시 필요한 모든 자원을 전부 할당&lt;/li&gt;
&lt;li&gt;자원이 필요할 경우 보유 자원을 모두 놓고 다시 요청&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;하지만 자원의 활용성이 떨어지므로 좋은 방식은 X&lt;/p&gt;
&lt;h3&gt;원형 대기 예방 → 가장 쉬운 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;점유와 대기를 하는 프로세스들이 원형을 이루지 못하도록 막음&lt;/li&gt;
&lt;li&gt;모든 자원에 순서를 부여해 정해진 순서대로만 자원 할당&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 회피 (비효율적)&lt;/h2&gt;
&lt;h3&gt;자원 할당량을 조절하여 교착 상태 해결&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;자원 요청에 대한 부가적인 정보를 이용해 deadlock의 가능성이 없는 경우에만 자원 할당&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;교착 상태가 발생하는 범위 내에 있으면 프로세스 대기&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;시스템 state가 원래 state로 돌아올 수 있는 경우에만 자원 할당&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Available 범위 내의 Need인 프로세스의 요청을 받아들임&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;P0의 경우 Need(최대 요청)이 Available 내에선 만족할 수 없기 때문에 어떤 요청이 와도 자원을 내어주지 않음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;P1의 경우Available 내에 Need가 충족이 되기 때문에(충분히 커버) P1은 어떤 요청이 와도 자원을 내어줌&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3️. 검출과 회복&lt;/h2&gt;
&lt;p&gt;어떤 제약을 가하지 않고 자원 할당 그래프를 모니터링 하며 교착상태가 발생하는지 확인하고, 교착 상태가 발생하면 교착 상태 회복 단계 진행&lt;/p&gt;
&lt;h3&gt;검출&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Resource의 인스턴스가 1개인 경우 : 자원할당 그래프에서의 cycle로 검출&lt;/li&gt;
&lt;li&gt;Resource의 인스턴스가 여러개인 경우 : 테이블을 그려서 은행원 알고리즘으로 검출&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;회복&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;데드락에 연류된 프로세스들을 죽임&lt;/li&gt;
&lt;li&gt;데드락에 연류된 프로세스들을 하나씩 죽여서 자원을 뺏음&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;4️. 무시&lt;/h2&gt;
&lt;p&gt;Deadlock이 일어나지 않는다고 생각하고 아무런 조취도 취하지 않음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Deadlock이 매우 드물게 발생하므로 deadlock에 대한 조치가 더 큰 overhead일 수 있음&lt;/li&gt;
&lt;li&gt;운영체제가 관여하지 않으므로, 사람이 프로세스를 직접 죽임&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>SQL, NOSQL</title><link>https://blog.csyhorizon.dev/posts/2025/cs/sql_nosql/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/sql_nosql/</guid><pubDate>Sat, 19 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;1. DB, DBMS&lt;/h1&gt;
&lt;h2&gt;데이터베이스 (DB, Database)&lt;/h2&gt;
&lt;p&gt;전자적으로 저장되고 체계적인 데이터의 모음&lt;br /&gt;
여러 사람이 공유하여 사용할 목적으로 체계화해 통합, 관리하는 데이터의 집합&lt;/p&gt;
&lt;h2&gt;데이터베이스 관리 시스템 (DBMS, Database Management System)&lt;/h2&gt;
&lt;p&gt;데이터베이스 안에 데이터를 관리해주는 프로그램&lt;br /&gt;
다양한 데이터가 저장되어 있는 데이터베이스는 여러 명의 사용자나 응용 프로그램과 공유하고 동시에 접근을 가능하게 해줌&lt;br /&gt;
예) MySQL, Oracle, MongoDB&lt;/p&gt;
&lt;h2&gt;SQL : Structured Query Language&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;비절차적 언어로, 원하는 결과에 대한 내용만 명세하고 결과를 얻는 내부의 방식에 대한 내용은 없음&lt;/li&gt;
&lt;li&gt;RDBMS의 데이터를 관리하기 위해 설계된 특수 목적의 프로그래밍 언어&lt;/li&gt;
&lt;li&gt;RDBMS : 관계형 데이터베이스 관리 시스템&lt;/li&gt;
&lt;li&gt;데이터를 테이블 형태로 표현하고 데이터 간 관계를 쉽게 설정함&lt;/li&gt;
&lt;li&gt;RDBMS에서 사용되는 표준 질의언어&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;2. SQL (관계형 데이터베이스)&lt;/h1&gt;
&lt;p&gt;관계형 데이터베이스는 테이블, 행, 열의 정보를 구조화하는 방식&lt;br /&gt;
테이블 행에 해당하는 고유 식별자인 키를 통해 여러 테이블을 연결함&lt;/p&gt;
&lt;h2&gt;특징&lt;/h2&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;ACID 특성 보장 (원자성, 일관성, 고립성, 지속성)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;장점&lt;/h2&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;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;p&gt;MySQL, PostgreSQL, MariaDB, Microsoft SQL Server, Oracle Database&lt;/p&gt;
&lt;h1&gt;3. NoSQL (비관계형 데이터베이스)&lt;/h1&gt;
&lt;p&gt;Not only SQL 혹은 Non-Relational Operational Database&lt;br /&gt;
관계형 모델을 지양하고 대량의 분산된 비정형 데이터를 저장하고 조회함&lt;br /&gt;
스키마 없이 사용하거나 느슨한 스키마 제공&lt;/p&gt;
&lt;h2&gt;특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SQL과 달리 데이터 간 관계를 정의하지 않음&lt;/li&gt;
&lt;li&gt;Key-Value 형태로 저장되어 조인 연산이 불가능&lt;/li&gt;
&lt;li&gt;대용량 데이터 저장 가능&lt;/li&gt;
&lt;li&gt;다양한 데이터 모델 지원:
&lt;ul&gt;
&lt;li&gt;Key-Value (Redis, Oracle NoSQL DB, VoldeMarte)&lt;/li&gt;
&lt;li&gt;Document (MongoDB, CouchDB, Riak, Azure Cosmos DB)&lt;/li&gt;
&lt;li&gt;Wide-Column-Family (Cassandra, Hbase, GoogleBigTable, Vertica)&lt;/li&gt;
&lt;li&gt;Graph (Neo4j, Sones, AllegroGraph, OrientDB)&lt;/li&gt;
&lt;/ul&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;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;ul&gt;
&lt;li&gt;데이터 일관성은 SQL보다 떨어짐&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;h1&gt;4. SQL VS NoSQL 비교&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;비교 항목&lt;/th&gt;
&lt;th&gt;SQL (관계형 데이터베이스)&lt;/th&gt;
&lt;th&gt;NoSQL (비관계형 데이터베이스)&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;수직적 확장 (Scale-up)&lt;/td&gt;
&lt;td&gt;수평적 확장 (Scale-out)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;일관성&lt;/td&gt;
&lt;td&gt;강한 일관성 (ACID 준수)&lt;/td&gt;
&lt;td&gt;느슨한 일관성 (BASE 준수)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;쿼리 언어&lt;/td&gt;
&lt;td&gt;표준 SQL 사용&lt;/td&gt;
&lt;td&gt;데이터베이스별 다양한 쿼리어 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;트랜잭션&lt;/td&gt;
&lt;td&gt;완벽 지원&lt;/td&gt;
&lt;td&gt;제한적 지원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;관계&lt;/td&gt;
&lt;td&gt;테이블 간 관계 정의(조인 가능)&lt;/td&gt;
&lt;td&gt;관계 제한적 또는 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;성능&lt;/td&gt;
&lt;td&gt;복잡한 쿼리 적합&lt;/td&gt;
&lt;td&gt;단순 쿼리 높은 처리량&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 사례&lt;/td&gt;
&lt;td&gt;금융, 회계, ERP 등 정형 데이터&lt;/td&gt;
&lt;td&gt;빅데이터, 실시간 처리, 소셜 미디어 등&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;대표 제품&lt;/td&gt;
&lt;td&gt;MySQL, PostgreSQL, Oracle&lt;/td&gt;
&lt;td&gt;MongoDB, Redis&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;:::note&lt;br /&gt;
💡 결국 어느 DBMS를 선택하는 것이 맞는가?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SQL 선택 시&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;NoSQL 선택 시&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;li&gt;데이터 일관성보다 가용성이 중요한 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;결론&lt;/strong&gt;&lt;br /&gt;
두 데이터베이스는 상호 보완적이다.&lt;br /&gt;
프로젝트 특성에 따라 적절히 선택하며 때로는 두 가지를 함께 사용하기도 한다. (Polyglot Persistence)&lt;br /&gt;
:::&lt;/p&gt;
</content:encoded></item><item><title>CPU 스케줄링 알고리즘</title><link>https://blog.csyhorizon.dev/posts/2025/cs/cpu_schedule/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/cpu_schedule/</guid><pubDate>Sat, 12 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;스케줄링의 개요&lt;/h1&gt;
&lt;p&gt;CPU 스케줄러는 프로세스가 생성된 후 종료될 때까지 모든 상태 변화를 조정
여러 프로세스 상황을 고려하여 CPU와 시스템 자원을 어떻게 배정할지 결정&lt;/p&gt;
&lt;h3&gt;✔️ 규모별 스케줄링&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;고수준 스케줄링
시스템 내 전체 작업 수를 조절
작업은 1개 이상의 프로세스로 이루어진 가장 큰 단위&lt;br /&gt;
전체 시스템 부하를 고려해 작업 승인 또는 거부를 결정&lt;br /&gt;
멀티프로그래밍 정도, 즉 전체 프로세스 수가 결정&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;중간 수준 스케줄링&lt;br /&gt;
활성화된 프로세스 수를 조절해 시스템 과부하를 막음&lt;br /&gt;
일부 프로세스를 보류 상태로 옮겨 나머지 정상 작동을 지원&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;저수준 스케줄링&lt;br /&gt;
어떤 프로세스를 준비/실행/대기 상태로 보낼지 결정
오늘날 CPU 스케줄러는 대부분 중간과 저수준 스케줄링으로 이루어짐&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;✔️ 스케줄링의 목적&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;목적&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;공평성&lt;/td&gt;
&lt;td&gt;모든 프로세스가 자원을 공평히 배정받아야 함&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;tr&gt;
&lt;td&gt;무한 연기 방지&lt;/td&gt;
&lt;td&gt;작업이 무한히 연기되어선 안됨&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h1&gt;스케줄링 시 고려 사항&lt;/h1&gt;
&lt;h3&gt;✔️ 선점형 스케줄링, 비선점형 스케줄링&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;선점형 스케줄링
실행 중인 프로세스도 CPU를 빼앗을 수 있음
운영체제는 필요 시 실행 중인 프로세스를 중단시키고 새 작업 시작 가능&lt;br /&gt;
대표적으로 인터럽트 처리 방식이 있음
낭비가 있지만 대화형, 시분할 시스템에 적합&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;비선점형 스케줄링&lt;br /&gt;
프로세스가 CPU를 잡으면 종료 또는 대기 상태까지만 실행&lt;br /&gt;
문맥 교환 낭비는 없으나, 긴 CPU 사용으로 처리율 저하 가능&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;✔️ 프로세스 우선순위&lt;/h3&gt;
&lt;p&gt;우선순위가 높을수록 더 자주 실행
커널 프로세스와 일반 프로세스로 구분하며, 커널 프로세스가 우선순위가 높음&lt;br /&gt;
일반 프로세스는 사용자에 의해 우선순위 조절 가능
예: 비디오 플레이어가 문서 편집기보다 우선순위가 높음&lt;/p&gt;
&lt;h3&gt;✔️ CPU 집중 프로세스, 입출력 집중 프로세스&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CPU 집중 프로세스: CPU 연산 위주 작업&lt;/li&gt;
&lt;li&gt;입출력 집중 프로세스: 저장장치 입출력 위주 작업&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;입출력 집중 프로세스가 먼저 실행되는 것이 효율적&lt;br /&gt;
이런 현상을 사이클 훔치기&lt;/p&gt;
&lt;h3&gt;✔️ 전면 프로세스, 후면 프로세스&lt;/h3&gt;
&lt;p&gt;전면 프로세스는 사용자와 상호작용하며 입출력 중인 프로세스
후면 프로세스는 사용자와 상호작용 없고 백그라운드 작업&lt;br /&gt;
예: 브라우저는 전면, 문서 편집기는 후면 프로세스
전면 프로세스가 우선순위가 높음&lt;/p&gt;
&lt;h3&gt;정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;커널 프로세스 &amp;gt; 일반 프로세스&lt;/li&gt;
&lt;li&gt;전면 프로세스 &amp;gt; 후면 프로세스&lt;/li&gt;
&lt;li&gt;대화형 프로세스 &amp;gt; 일괄 처리 프로세스&lt;/li&gt;
&lt;li&gt;입출력 집중 프로세스 &amp;gt; CPU 집중 프로세스&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;다중 큐&lt;/h1&gt;
&lt;h3&gt;✔️ 준비 상태의 다중 큐&lt;/h3&gt;
&lt;p&gt;CPU 스케줄러는 PCB 우선순위 높은 프로세스를 먼저 CPU에 할당&lt;br /&gt;
운영체제는 우선순위별로 여러 큐를 만들고, 준비 상태 프로세스는 자신의 우선순위 큐 뒤에 삽입&lt;br /&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;여러 입출력장치 대기를 위해 대기 상태 PCB는 동일 입출력 큐에 모임&lt;br /&gt;
대기 상태에서는 여러 PCB를 꺼내 준비 상태로 이동
여러 입출력 완료 시 한꺼번에 인터럽트 처리 위해 인터럽트 벡터 활용&lt;br /&gt;
나중에 들어온 PCB가 먼저 준비 상태로 옮겨질 수 있음
이것은 입출력장치 속도를 고려한 작업 순서 조정&lt;/p&gt;
&lt;h1&gt;스케줄링 알고리즘&lt;/h1&gt;
&lt;p&gt;스케줄링 알고리즘은 비선점형과 선점형으로 나눔&lt;/p&gt;
&lt;h3&gt;평가 기준&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;li&gt;반환 시간: 생성 후 종료까지 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;평균 대기 시간을 가장 많이 평가&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;🔑 FCFS 스케줄링 (비선점형)&lt;/h3&gt;
&lt;p&gt;First Come First Serve로, 도착 순서대로 CPU 할당&lt;br /&gt;
비선점형이라 한 프로세스 완료 후 다음 실행&lt;br /&gt;
콘보이 효과로 긴 작업이 뒤 작업 지연&lt;/p&gt;
&lt;h3&gt;🔑 SJF 스케줄링 (비선점형)&lt;/h3&gt;
&lt;p&gt;Short Job First, 실행 시간 짧은 작업부터 CPU 할당&lt;br /&gt;
콘보이 효과 완화 가능하나 공평성 문제로 실무에 제한적&lt;br /&gt;
아사 현상이라 불리는 무한 연기 현상 발생 가능&lt;br /&gt;
에이징 기법으로 완화 가능하지만 잘 사용하지 않음&lt;/p&gt;
&lt;h3&gt;🔑 HRN 스케줄링 (비선점형)&lt;/h3&gt;
&lt;p&gt;High Response ratio Next&lt;br /&gt;
대기 시간과 CPU 사용 시간 고려한 우선순위 할당&lt;br /&gt;
아사 현상 완화 가능하나 공평성 문제 존재&lt;/p&gt;
&lt;h3&gt;🔑 라운드 로빈 스케줄링 (선점형)&lt;/h3&gt;
&lt;p&gt;Round Robin, 순환 순서로 타임 슬라이스 내 작업&lt;br /&gt;
타임 슬라이스 동안만 작업하고 완료 안 되면 큐 뒤로&lt;br /&gt;
FCFS 유사하나 선점형 문맥 교환 시간 고려 필요&lt;/p&gt;
&lt;h3&gt;🔑 SRT 우선 스케줄링 (선점형)&lt;/h3&gt;
&lt;p&gt;Shortest Remaining Time, 남은 작업 시간 가장 적은 프로세스 우선 CPU 할당&lt;br /&gt;
SJF와 라운드 로빈 혼합형&lt;br /&gt;
주기적 시간 계산과 아사 현상 문제로 사용 제한적&lt;/p&gt;
&lt;h3&gt;🔑 우선순위 스케줄링 (비선점형, 선점형)&lt;/h3&gt;
&lt;p&gt;우선순위 높은 프로세스에 CPU 할당&lt;br /&gt;
고정 및 변동 우선순위 가능&lt;br /&gt;
아사 현상과 오버헤드로 효율 저하 우려&lt;br /&gt;
우선순위는 시스템 효율성 아닌 프로세스 중요도로 결정됨&lt;/p&gt;
&lt;h3&gt;🔑 다단계 큐 스케줄링 (선점형)&lt;/h3&gt;
&lt;p&gt;우선순위별 여러 큐 사용&lt;br /&gt;
각 큐는 라운드 로빈 방식으로 처리&lt;br /&gt;
상위 큐 작업이 끝나야 하위 큐 작업 실행 가능&lt;br /&gt;
지연 문제 해결책이 다단계 피드백 큐 스케줄링&lt;/p&gt;
&lt;h3&gt;🔑 다단계 피드백 큐 스케줄링 (선점형)&lt;/h3&gt;
&lt;p&gt;CPU 사용 후 우선&lt;/p&gt;
</content:encoded></item><item><title>가상화</title><link>https://blog.csyhorizon.dev/posts/2025/cs/virtualization/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/virtualization/</guid><description>가상화 기술 (Virtualization)</description><pubDate>Mon, 07 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;가상화&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;가상화&lt;/code&gt;는 컴퓨터에서 컴퓨터 리소스의 추상화를 일컫는 광범위한 용어&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;물리적인 컴퓨터 리소스의 특징을 다른 시스템, 응용 프로그램, 최종 사용자들이 리소스와 상호 작용하는 방식으로부터 감추는 기술&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;가상화 대상 시스템&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;ABI 레벨 : Application 관점 가상화 (Ex. JVM)&lt;/li&gt;
&lt;li&gt;ISA 레벨 : 명령어 집합에 대한 가상화 (Ex. Vmware)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;하이퍼바이저&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;하이퍼바이저&lt;/code&gt;는 가상화 소프트웨어&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Type 1 Hypervisor&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;물리시스템 하드웨어에 직접 설치되어 실행되고 가상머신 관리&lt;/li&gt;
&lt;li&gt;베어 메탈 하이퍼바이저라고도 함&lt;/li&gt;
&lt;li&gt;EX. Xen, KVM, Microsoft Hyper-V, VMWare ESX/ESXi&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Type 2 Hypervisor&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;호스트머신 운영체제 위에서 애플리케이션처럼 설치되어 가상머신 관리&lt;/li&gt;
&lt;li&gt;Ex. VMWare Workstation, VirtualBox, QEMU, KVM&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;가상화 유형&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;전가상화 : 게스트 OS를 수정하지 않는 가상화&lt;/li&gt;
&lt;li&gt;반가상화 : 게스트 OS를 수정하여 하이퍼바이저와 통신할 수 있는 API를 추가한 가상화&lt;/li&gt;
&lt;li&gt;하이브리드 가상화 : I/O나 메모리 집약형 작업 등 제약이 있는 환경에 대해서 반가상화를 적용하여 성능을 개선하고자 제안&lt;/li&gt;
&lt;li&gt;운영체제 레벨 가상화 : 컨테이너화. 일반적인 가상화와 달리 물리시스템에 설치된 운영체제를 모든 컨테이너가 공유하는 시스템&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;가상화 기술&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;폰노이만 구조의 컴퓨터는 CPU, Memory, I/O 장치로 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;CPU 가상화&lt;/h2&gt;
&lt;p&gt;:::note
운영 체제는 CPU의 &lt;strong&gt;커널 모드&lt;/strong&gt;에서 실행되며, 일반 사용자 프로그램은 &lt;strong&gt;유저 모드&lt;/strong&gt;에서 실행된다.
:::&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;커널 모드는 모든 CPU 명령 실행 가능&lt;/li&gt;
&lt;li&gt;유저 모드는 일부 제한된 명령 실행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;소프트웨어 지원 전가상화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;가상머신을 실행하는 Guest OS는 원래 커널 모드에서 실행되어야하지만, 가상화 환경에선 이를 실행할 수 없음&lt;/li&gt;
&lt;li&gt;이에 유저 모드에서 Guest OS를 실행 후, 커널 명령이 실행될 때 예외를 발생시켜 하이퍼바이저가 이를 가로채서 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;단점
- 일부 명령어는 예외를 발생하지 않음
- 따라서 이러한 명령을 수정하는 &lt;strong&gt;바이너리 트랜슬레이션&lt;/strong&gt;이 필요한데, 이 과정에서 성능을 저하시킴&lt;/p&gt;
&lt;h3&gt;소프트웨어 지원 반가상화&lt;/h3&gt;
&lt;p&gt;하이퍼 콜은 가상 환경에서 Guest OS가 직접 하이퍼바이저와 통신하기 위한 특수한 호출&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Guest OS가 물리적 하드웨어를 직접 제어할 수 없기에 필요한 기능을 하이퍼바이저에게 요청&lt;/li&gt;
&lt;li&gt;이를 시스템콜과 유사한 방식으로 하이퍼바이저에게 직접 명령을 전달하므로 하이퍼 콜이다&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;장점은 트랩보다 성능은 향상되지만 Guest OS가 지원하도록 수정해야함&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이를 지원하는 방식이 반 가상화&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;하드웨어 지원 가상화&lt;/h3&gt;
&lt;p&gt;소프트웨어 가상화의 성능 문제 해결을 위해 Intel VT-x, AMD-V 같은 하드웨어 지원 가상화 지술 등장&lt;/p&gt;
&lt;p&gt;이 기술은 CPU 내부에 &lt;strong&gt;하이퍼바이저 전용 실행 모드&lt;/strong&gt;를 추가하여, Guest OS를 특권 모드에서 실행할 수 있도록 지원&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;하이퍼바이저가 직접 개입하지 않아도 CPU나 Guest OS 특권 명령을 처리할 수 있으므로 성능 향상&lt;/li&gt;
&lt;li&gt;트랩과 바이너리 트랜슬레이션이 불필요해지며, 가상 오버헤드 감소&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;메모리 가상화&lt;/h2&gt;
&lt;p&gt;가상화 이전에도 컴퓨터 시스템은 가상 메모리를 사용하고 있다. 따라서 주소 변환 과정은 아래와 같음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비가상화 환경(일반 환경)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;가상 주소 → 물리 주소&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;가상 주소(Virtual Address): 프로세스 또는 VM이 사용하는 주소&lt;/li&gt;
&lt;li&gt;물리 주소(Physical Address): 실제 RAM에서 사용하는 주소&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;운영 체제는 **페이지 테이블(Page Table)**을 사용해 가상 주소를 물리 주소로 매핑&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;가상화 환경(하이퍼바이저 개입)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;가상 주소 → 가상물리 주소 → 물리주소&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;가상화 환경에서는 가상머신 또한 하이퍼바이저 상에서 동작하는 프로세스이므로, 가상주소를 사용한다. 따라서 주소 변환에 한 단계가 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;섀도우 페이지 테이블 (Shadow Page Table) 방식 메모리 가상화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;하이퍼바이저가 게스트 OS의 페이지 테이블을 감시하고, 별도의 섀도우 페이지 테이블을 유지하며 주소 변환을 수행&lt;/li&gt;
&lt;li&gt;단점: 오버헤드가 크다 (페이지 테이블 변경 시 하이퍼바이저가 개입)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;하드웨어 지원 페이지 테이블 (EPT, NPT) 방식 메모리 가상화&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Intel VT-x: EPT (Extended Page Tables)&lt;/li&gt;
&lt;li&gt;AMD-V: NPT (Nested Page Tables)&lt;/li&gt;
&lt;li&gt;CPU가 2단계 주소 변환을 직접 처리하여 하이퍼바이저의 개입을 최소화 → 성능 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;메모리 가상화 방식&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;th&gt;장점&lt;/th&gt;
&lt;th&gt;단점&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;섀도우 페이지 테이블&lt;/td&gt;
&lt;td&gt;하이퍼바이저가 페이지 테이블을 관리&lt;/td&gt;
&lt;td&gt;모든 OS 지원 가능&lt;/td&gt;
&lt;td&gt;성능 저하&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EPT/NPT (하드웨어 지원)&lt;/td&gt;
&lt;td&gt;CPU가 직접 2단계 주소 변환&lt;/td&gt;
&lt;td&gt;성능 향상&lt;/td&gt;
&lt;td&gt;하드웨어 지원 필요&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;I/O 가상화&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;소프트웨어적 I/O 가상화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I/O 장치 에뮬레이션 : 하이퍼바이저가 가상 머신(VM)에 가짜(에뮬레이션된) I/O 장치를 제공하고, 실제 I/O 처리는 하이퍼바이저가 담당&lt;/li&gt;
&lt;li&gt;I/O 반가상화 : VM이 하이퍼바이저와 직접 통신하여 최적화된 I/O 드라이버를 사용. (예: VirtIO 방식)&lt;/li&gt;
&lt;li&gt;직접 접근 I/O : VM이 특정 물리적 I/O 장치를 직접 제어할 수 있도록 하이퍼바이저가 장치를 VM에 직접 할당 (장치 공유 불가, 한 VM이 독점)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;하드웨어 지원 I/O 가상화&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IOMMU (I/O memory management unit) : DMA(Direct Memory Access) 요청을 가상 주소에서 물리 주소로 변환하여 I/O 장치가 가상화 환경에서도 안전하게 메모리에 접근 가능하도록 지원&lt;/li&gt;
&lt;li&gt;SR-IOV (single root I/O virtualization) : 하나의 물리적 I/O 장치를 여러 개의 가상 기능(VF, Virtual Function)으로 나누어 여러 VM이 직접 I/O 장치를 공유할 수 있도록 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>가상 메모리</title><link>https://blog.csyhorizon.dev/posts/2025/cs/virtual_memory/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/virtual_memory/</guid><description>가상 메모리 (Virtual memory)</description><pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;가상 메모리란?&lt;/h1&gt;
&lt;p&gt;가상 메모리(Virtual memory)는 메모리 관리 기법 중 하나로 컴퓨터 시스템에 실제 이용 가능한 자원을 추상화하여 사용자들에게 매우 큰 메모리로 보이게 만드는 것
:::note
각 프로그램에 실제 메모리 주소가 아닌 가상의 메모리 주소를 주는 방식을 말합니다
:::&lt;/p&gt;
&lt;h2&gt;등장 배경&lt;/h2&gt;
&lt;p&gt;과거 컴퓨터는 제한된 물리 메모리(RAM)으로 큰 프로그램이나 다중 프로그래밍에 어려움을 겪었음&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;해결책으로 오버레이 기법을 사용했찌만, 복잡한 프로그래밍이 필요하여 좀 더 쉽게 메모리 관리하는 기법이 필요했음&lt;/li&gt;
&lt;li&gt;1956년 독일의 물리학자 프리츠 루돌프가 가상메모리라는 개념을 만듬&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip
물리 메모리와 논리 메모리를 분리하여 물리 메모리보다 큰 논리 메모리 공간을 제공하여 프로세스가 더 많은 메모리를 사용할 수 있도록 발명
:::&lt;/p&gt;
&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;/li&gt;
&lt;li&gt;프로그램 개발 및 실행 유연성 향상&lt;/li&gt;
&lt;li&gt;멀티태스킹 성능 향상 : 사용자에게 더욱 더 빠른 응답성 제공 및 짧은 시간 간격으로 프로세스들을 번갈아 가면서 실행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;구현 방법&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;가상 메모리는 운영체제와 하드웨의 협력으로 구현. MMU, 스왑공간, 요구 페이징을 이용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;메모리 관리 장치 (MMU)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;메모리 관리 장치는 CPU가 메모리에 접근하는 것을 관리하며, 페이지 테이블을 사용하여 가상 주소와 물리 주소간의 매핑을 관리하고, 페이지 폴트 발생 시 운영체제에 알리는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;스왑공간 (Swap Area)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;스왑 공간은 물리 메모리가 부족할 때, 운영체제가 디스크의 일부 공간을 마치 RAM처럼 사용하는 가상 메모리 영역을 뜻함&lt;/li&gt;
&lt;li&gt;실행중인 프로세스의 주소 공간을 일시적으로 메인 메모리에서 디스크에 내려 놓는 공간&lt;/li&gt;
&lt;li&gt;하드 디스크에서 사용되지만 휘발성으로 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;요구 페이지 (Demand Paging)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;프로그램 실행 시 필요한 페이지만 메모리에 로드하는 기법&lt;/li&gt;
&lt;li&gt;스왑된 페이지를 필요할 때 메모리에 적재하고 기본적인 페이징 기법에 따라 주소를 변환&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;무효 비트 (Invalid Bit)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;모든 페이지가 처음부터 메모리에 올라와 있지 않기에 페이지 테이블에 각 페이지 상태를 나타내는 비트가 필요&lt;/li&gt;
&lt;li&gt;이 때 무효 비트가 사용됨&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>프록시와 리버스 프록시</title><link>https://blog.csyhorizon.dev/posts/2025/cs/proxyreverse/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/proxyreverse/</guid><description>프록시와 리버스 프록시에 대해</description><pubDate>Sat, 22 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Proxy Server&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;서버와 클라이언트 사이에 중계기로서 대리로 통신을 수행하는 것을 &lt;code&gt;프록시&lt;/code&gt; 중계 기능을 하는 것을 &lt;code&gt;프록시 서버&lt;/code&gt;라고 부름&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Forward Proxy&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;포워드 프록시는 클라이언트 요청을 대신 보내고 응답을 전달하여 캐싱, IP 숨김, 보안 등의 역할을 수행하는 서버&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Forward Proxy 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;캐싱 : 클라이언트가 동일한 데이터를 반복 요청 시, 원본 서버로의 요청을 줄여 네트워크 트레픽과 서버 부하를 감소&lt;/li&gt;
&lt;li&gt;익명성
&lt;ul&gt;
&lt;li&gt;클라이언트의 IP 주소를 숨길 수 있어 익명성을 제공할 수 있음&lt;/li&gt;
&lt;li&gt;X-Forwarded-For 헤더를 추가해 클라이언트 IP 주소를 전달할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;보안 : 미리 정의된 규칙에 따라 특정 웹 사이트 또는 콘텐츠에 대한 액세스를 차단할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Reverse Proxy&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;리버스 프록시는 클라이언트의 요청을 대신 받아 원래 서버로 전달하고, 응답을 반환하여 보안 강화, 부하 분산, 캐싱 등의 역할을 수행하는 서버&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Reverse Proxy 특징&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;캐싱&lt;/li&gt;
&lt;li&gt;보안
&lt;ul&gt;
&lt;li&gt;서버의 IP 주소를 숨길 수 있어 DDOS 공격과 같은 표적 공격을 방어하는 데 유용&lt;/li&gt;
&lt;li&gt;하지만 CDN과 같은 리버스 프록시 서버가 공격의 타겟이 될 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부하 분산 (로그 밸런싱)
&lt;ul&gt;
&lt;li&gt;단일 서버에 과부하가 걸리는 것을 방지하기 위해 들어오는 트래픽을 여러 서버에 고르게 분산할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Forward Proxy &amp;amp; Reverse Proxy 비교&lt;/h1&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Forward Proxy&lt;/th&gt;
&lt;th&gt;Reverse Proxy&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;IP 숨김 역할&lt;/td&gt;
&lt;td&gt;클라이언트 IP를 숨김&lt;/td&gt;
&lt;td&gt;서버 IP를 숨김&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;보안 역할&lt;/td&gt;
&lt;td&gt;사용자가 특정 사이트에 접근하지 못하도록 차단&lt;/td&gt;
&lt;td&gt;서버를 직접 노출하지 않음 (DDOS 방어, 보안 강화)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
</content:encoded></item><item><title>프로세스와 스레드</title><link>https://blog.csyhorizon.dev/posts/2025/cs/processthread/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/processthread/</guid><description>프로세스와 스레드</description><pubDate>Fri, 14 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;프로세스의 개요&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;운영체제로부터 자원을 할당받은 작업의 단위&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;프로세스 제어 블록(PCB)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;프로세스란, 프로그램이 메모리에 올라와 실행 중인 상태
운영체제가 프로그램을 메모리에 가져올 때, 프로세스 제어 블록(PCB)라는 일종의 작업 지시서를 만듬&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;프로세스의 상태&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;현대 컴퓨터는 CPU가 여러 프로세스를 번갈아가며 실행하는 멀티 프로세싱 방식으로 동작
따라서 CPU를 얻어 실행 중인 프로세스가 다른 프로세스에게 넘겨주는 일이 자주 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이에 상태는 &lt;code&gt;생성&lt;/code&gt;, &lt;code&gt;준비&lt;/code&gt;, &lt;code&gt;실행&lt;/code&gt;, &lt;code&gt;대기&lt;/code&gt;, &lt;code&gt;완료&lt;/code&gt; 중 하나를 가지게 된다&lt;/p&gt;
&lt;h3&gt;생성 상태&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;프로그램이 메모리에 올라오고, 운영체제로부터 PCB를 할당받은 상태
생성된 프로세스는 준비 상태로 전환되어 CPU를 기다림&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;준비 상태&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;프로세스가 자신의 순서를 기다리는 상태&lt;/li&gt;
&lt;li&gt;준비 큐에서 기다리며 CPU 스케줄러에 의해 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;다수의 준비 큐가 운영되며, 스케줄러는 PCB에 기록된 우선순위를 기준으로 실행 상태로 옮길 프로세스를 선택&lt;/p&gt;
&lt;h3&gt;실행 상태&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;CPU를 할당받아 실행되는 상태이며, 주어진 시간 동안만 작업할 수 있음&lt;/li&gt;
&lt;li&gt;이를 &lt;code&gt;타임 슬라이스&lt;/code&gt;라고 함&lt;/li&gt;
&lt;li&gt;CPU 스케줄러가 프로세스를 실행 상태로 옮기는 과정을 &lt;code&gt;디스패치&lt;/code&gt;라고 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;실행 상태에 있는 프로세스가 입출력을 요청하면, 입출력 관리자에게 입출력을 요청하고 해당 프로세스를 대기 상태로 옮김&lt;/p&gt;
&lt;h3&gt;대기 상태&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;실행 상태의 프로세스가 입출력을 요청하면, 입출력이 완료될 때까지 기다리는 상태&lt;/li&gt;
&lt;li&gt;이 상태의 프로세스는 입출력장치별로 마련된 큐에서 대기&lt;/li&gt;
&lt;li&gt;입출력이 완료되면 인터럽트가 발생하고, PCB를 준비 상태로 이동&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;완료 상태&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;프로세스가 종료되는 상태, 코드와 데이터를 메모리에서 삭제하고 PCB를 폐기
만약 프로세스가 비정상적으로 종료된 경우, 복구를 위해 종료 직전의 메모리 상태를 저장장치로 옮기는 데 이를 &lt;code&gt;코어 덤프&lt;/code&gt;라고 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip
위 5가지의 활성 상태말고 추가로 &apos;휴식 상태&apos;와 &apos;보류 상태&apos;가 있다
:::&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;휴식 상태 : 프로세스가 작업을 일시적으로 쉬는 상태&lt;/li&gt;
&lt;li&gt;보류 상태 : 프로세스가 메모리에서 쫓겨나 스왑 영역에 보관되는 상태
&lt;ul&gt;
&lt;li&gt;메모리 공간 부족, 프로그램 오류, 악성 프로그램으로 의심되는 경우 보류 상태가 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;프로세스 제어 블록(PCB)&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;앞서 자주 언급한 PCB 블록은 도대체 무엇인가?
:::note
프로세스를 실행하는 데 필요한 주요 정보를 보관하는 자료구조
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;PCB 구성&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;명칭&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;포인터&lt;/td&gt;
&lt;td&gt;PCB를 연결하여 준비 상태나 대기 상태의 큐를 구현&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;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;tr&gt;
&lt;td&gt;계정 정보&lt;/td&gt;
&lt;td&gt;계정 정보, CPU 할당 시간, CPU 사용 시간 등 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;부모 프로세스 구분자, 자식 프로세스 구분자&lt;/td&gt;
&lt;td&gt;PPID, CPID를 통해 프로세스 부모-자식 관계를 저장&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;문맥 교환&lt;/h3&gt;
&lt;p&gt;:::note
CPU를 차지하던 프로세스가 나가고 새로운 프로세스를 받아들이는 작업
:::
실행 상태에서 나가는 PCB는 이전까지의 작업 내용을 저장하고, 실행 상태로 들어오는 PCB의 정보로 셋팅해야
이전의 작업에 이어 작업을 할 수 있게 되는데, 이렇게 교환하는 작업을 &lt;code&gt;문맥 교환&lt;/code&gt;이라고 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;문맥 교환의 타임 슬라이스는 되도록 작되, 문맥 교환의 시간보다 짧게 설정하여 성능 저하가 되는 것을 유의해야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;프로세스의 연산&lt;/h2&gt;
&lt;h3&gt;프로세스 구조&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;프로세스는 코드, 데이터, 스택, 힙 영역으로 구성&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;코드와 데이터 영역은 프로세스 실행 직전 위치와 크기가 결정되어 이를 정적 할당 영역&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;힙 영역과 스택 영역은 프로세스가 실행되는 동안 만들어지는 동적 할당 영역&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;코드 영역&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;프로그램의 본문이 기술된 곳으로 프로그래머가 작성한 프로그램이 탑재&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;데이터 영역&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;코드가 실행되면서 사용하는 변수나 파일 등의 데이터를 모아놓은 곳&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;스택 영역&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;함수 호출 후 되돌아올 위치를 저장하는 등 운영체제가 프로세스를 실행하기 위해 부수적으로 필요한 데이터를 모아두는 곳&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;힙 영역&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;동적으로 할당되는 변수 영역, malloc() 등으로 인해 프로그램 실행 도중 할당되는 데이터가 모임&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;fork(): 프로세스 복사&lt;/h3&gt;
&lt;p&gt;프로그램을 매번 저장장치에서 가져오면 시간 오래 걸린다.
이를 해결하기 위해 기존 메모리에 복사하여 자식 프로세스를 생성하는 &lt;code&gt;fork()&lt;/code&gt;를 통해 프로세스를 생성한다.&lt;/p&gt;
&lt;p&gt;예로 Edge에서 새로운 탭을 실행하면 Edge 창이 하나 더 생기는 데 이는 기존 프로세스를 복사하여 생성한 것이다.
여기서 핵심은 &lt;code&gt;기존 프로세스는 부모 프로세스가 되고, 새로운 프로세스는 자식 프로세스가 된다&lt;/code&gt;는 점이다.&lt;/p&gt;
&lt;p&gt;fork()는 아래의 이점이 있다&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로세스의 생성 속도가 빠름
&lt;ul&gt;
&lt;li&gt;저장장치가 아닌 기존 메모리에서 복사하므로 자식 프로세스 생성 속도가 빠름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;추가 작업 없이 자원을 상속할 수 있음
&lt;ul&gt;
&lt;li&gt;부모 프로세스가 사용한 모든 자원을 자식 프로세스에 상속할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;시스템 관리를 효율적으로 할 수 있음
&lt;ul&gt;
&lt;li&gt;부모와 자식이 PPID와 CPID로 연결되어 있어, 자식 프로세스 종료 시 부모 프로세스가 자식이 사용하는 자원을 정리할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;exec(): 프로세스 전환&lt;/h3&gt;
&lt;p&gt;Edge뿐만 아니라 카카오톡, PPT 등 다양한 프로세스를 실행하는데, 이를 위해선 기존 프로세스를 새로운 프로세스로 전환하는 과정이 필요
:::note
&lt;code&gt;fork()&lt;/code&gt;를 통해 새로운 프로세스를 복사했다면, &lt;code&gt;exec()&lt;/code&gt;를 통해 완전히 다른 프로세스로 전환
:::&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이는 프로세스의 구조를 재활용하기 위해 새로운 프로세스를 만들려면 PCB를 만든 후 메모리 자리를 확보해야 함.&lt;/li&gt;
&lt;li&gt;또한 프로세스 종료 후 사용한 메모리 청소를 위해 부모-자식 관계가 있어야 함&lt;/li&gt;
&lt;li&gt;다만, exec()를 통해 기존 PCB, 메모리 영역, 부모-자식 관계를 그대로 사용할 수 있어 운영체제 효율성이 향상됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;고아 프로세스와 좀비 프로세스&lt;/h3&gt;
&lt;p&gt;부모 프로세스는 자원 회수를 위해 자식 프로세스 종료까지 기다림&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;그러나 부모가 먼저 종료되거나, 자식이 비정상적으로 종료되어 부모에게 종료를 알리지 못하는 경우가 있음&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;고아 프로세스&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;부모 프로세스가 자식 프로세스보다 먼저 종료되는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;좀비 프로세스&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;자식 프로세스가 종료되었지만 부모 프로세스가 뒤처리를 하지 않은 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이러한 회수되지 못한 자원이 쌓이면 결국 운영 효율성이 떨어지며, 운영체제는 주기적으로 반환되지 못한 자원을 회수해야 함&lt;/p&gt;
&lt;h4&gt;exit()와 return() 시스템 호출&lt;/h4&gt;
&lt;p&gt;:::note
&lt;code&gt;main()&lt;/code&gt; 함수 마지막에 &lt;code&gt;exit()&lt;/code&gt; 또는 &lt;code&gt;return()&lt;/code&gt;을 사용하는 이유는 부모 프로세스에게 자식 프로세스가 끝났음을 알리기 위한 것임
:::
이를 통해 자원을 빠르게 회수할 수 있고, 전달 인자에 따라 정상 또는 비정상 종료 여부를 알 수 있음&lt;/p&gt;
&lt;h4&gt;wait() 시스템 호출&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;다만 종료를 알려도 부모가 먼저 종료되버리면 고아 프로세스가 발생함&lt;/li&gt;
&lt;li&gt;따라서 &lt;code&gt;wait&lt;/code&gt; 시스템을 호출하는 경우 자식 프로세스가 종료되는 것을 기다리다가, 자식이 종료되면 다음 명령을 실행함&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;스레드의 개요&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;CPU 스케줄러가 CPU에 전달하는 일 하나가 스레드&lt;/li&gt;
&lt;li&gt;즉, 운영체제 입장에서 작업 단위는 프로세스, CPU 입장에서 하는 일을 스레드이다&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;멀티스레드&lt;/h2&gt;
&lt;p&gt;멀티스레드는 작업을 하면서 값이 바뀌거나 새로 생기는 동적 할당 영역만 여러 개 생성 후, 정적 할당 영역을 공유하는 형태로 자원의 효율성을 향상 시키는 것&lt;/p&gt;
&lt;p&gt;즉, 비슷한 일 2개 하는 프로세스 대신, 코드와 데이터를 공유하면서 여러 개의 일을 하나의 프로세스 내에서 진행하는 것&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;/ul&gt;
&lt;p&gt;단점&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 스레드 자원을 공유하므로 한 스레드 문제가 발생 시 전체 프로세스에 영향이 발생&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>자바 컴파일 과정</title><link>https://blog.csyhorizon.dev/posts/2025/java/javacompile/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/java/javacompile/</guid><pubDate>Sun, 09 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;:::tip
자바는 Windows, MacOS에서 차이 없이 구동이 가능한데,
이러한 이유는 자바는 OS에 독립적인 특징을 가지기 때문입니다.
:::&lt;/p&gt;
&lt;h1&gt;JVM 구조&lt;/h1&gt;
&lt;h3&gt;Class Loader (클래스 로더)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;JVM내로 클래스를 로드하고, 링크를 통해 배치하는 작업을 수행하는 모듈&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Execution engine (실행 엔진)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;바이트 코드를 실행시키는 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::note
바이트코드는 JVM만이 이해할 수 있는 중간 언어이며, 각 OS에 맞는 JVM만 있으면 시스템에 상관없이 실행할 수 있음
:::&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;인터프리터: 바이트 코드를 한줄 씩 실행&lt;/li&gt;
&lt;li&gt;JIT 컴파일러: 인터프리터 효율을 높이기 위한 컴파일러
&lt;ul&gt;
&lt;li&gt;인터프리터가 반복되는 코드 발견 시 JIT 컴파일러가 반복되는 코드를 네이티브 코드로 변환&lt;/li&gt;
&lt;li&gt;이후 인터프리터는 네이티브 코드로 컴파일된 코드를 바로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Runtime Data Area&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;프로그램 실행 중 사용되는 다양한 영역&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;PC Register: 스레드가 시작될 때 생성되며 현재 수행 중인 JVM 명령 주소를 갖고 있음&lt;/li&gt;
&lt;li&gt;Stack Area: 지역 변수, 파라미터 등 생성되는 영역
&lt;ul&gt;
&lt;li&gt;실제 객체는 Heap에 할당되고 해당 레퍼런스만 Stack에 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Heap Area: 동적으로 생성된 오브젝트와 배열이 저장되는 곳으로 GC의 대상 영역&lt;/li&gt;
&lt;li&gt;Method Area: 클래스 멤버 변수, 메소드 정보, Type 정보, Constant Pool, static, final 변수 등 생성
&lt;ul&gt;
&lt;li&gt;상수 풀은 모든 Symbolic Reference를 포함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;JNI&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;자바 애플리케이션에서 C, C++, 어셈블리어로 작성된 함수를 사용할 수 있는 방법 제공&lt;/li&gt;
&lt;li&gt;Native 키워드를 사용하여 메서드를 호출&lt;/li&gt;
&lt;li&gt;대표적인 메서드는 Thread, currentThread&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Native Method Library&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;C, C++로 작성된 라이브러리&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;자바 코드 실행 과정&lt;/h1&gt;
&lt;p&gt;&amp;lt;img src=&quot;https://logimage.csyhorizon.dev/javacompile-1.png&quot;/&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;자바 실행 방식&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;자바 컴파일러(.javac)가 자바 소스코드(.java)를 읽어 자바 바이트코드(.class)로 변환&lt;/li&gt;
&lt;li&gt;Class Loader를 통해 class들이 JVM으로 로딩&lt;/li&gt;
&lt;li&gt;로딩된 class 파일들은 Execution engine을 통해 해석&lt;/li&gt;
&lt;li&gt;해석된 바이트코드는 Runtime Date Areas에 배치되어 수행이 이뤄짐&lt;/li&gt;
&lt;/ol&gt;
</content:encoded></item><item><title>5가지 객체 지향 원칙 (SOLID)</title><link>https://blog.csyhorizon.dev/posts/2025/cs/solid/</link><guid isPermaLink="true">https://blog.csyhorizon.dev/posts/2025/cs/solid/</guid><description>How to use this blog template.</description><pubDate>Mon, 03 Mar 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;SOLID 원칙이란?&lt;/h1&gt;
&lt;p&gt;:::tip
&apos;&lt;a href=&quot;https://en.wikipedia.org/wiki/Robert_C._Martin&quot;&gt;로버트 C.마틴&lt;/a&gt;&apos;이 2000년에 소개한 객체지향 설계 5가지 원칙을 &apos;마이클 페더스&apos;가 &lt;a href=&quot;https://ko.wikipedia.org/wiki/%EA%B8%B0%EC%96%B5%EC%88%A0&quot;&gt;기억술&lt;/a&gt;로 소개한 것이 SOLID
:::&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;객체지향 설계에서 지켜줘야 할 5개의 소프트웨어 개발 원칙을 말한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;✋ 5개의 개발 원칙&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;SRP (Single Responsibility Principle) : 단일 책임 원칙&lt;/li&gt;
&lt;li&gt;OCP (Open Closed Priciple) : 개방 폐쇄 원칙&lt;/li&gt;
&lt;li&gt;LSP (Listov Substitution Priciple) : 리스코프 치환 원칙&lt;/li&gt;
&lt;li&gt;ISP (Interface Segregation Principle) : 인터페이스 분리 원칙&lt;/li&gt;
&lt;li&gt;DIP (Dependency Inversion Principle) : 의존 역전 원칙&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;좋은 설계&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;좋은 설계 : 시스템에 새로운 요구사항이나 변경사항이 있을 때, 영향을 받는 범위가 적은 구조&lt;/p&gt;
&lt;/blockquote&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;hr /&gt;
&lt;h1&gt;SRP (단일 책임 원칙) : 객체는 한 가지 역할만 가져야 한다.&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;어떤 클래스를 변경 해야 하는 이유는 오직 하나뿐 이어야 한다&quot;&lt;/p&gt;
&lt;/blockquote&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;blockquote&gt;
&lt;p&gt;단일 책임 원칙을 준수한다면 한 책임의 변경에서 다른 책임의 변경의 연쇄작용에서 자유로울 수 있습니다. 즉, 코드의 의존성과 결합도를 줄입니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;SRP를 위반한 예제 코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;public class Sports {

	private String sport;

	public void setSport(String sport) {
		this.sport = sport;
	}

	public void itemReady() {
		if (sport == &quot;soccer&quot;) {
			System.out.println(&quot;축구공을 가져옵니다.&quot;);
		}

		else if (sport == &quot;basketball&quot;) {
			System.out.println(&quot;농구공을 가져옵니다.&quot;);
		}

		else if (sport == &quot;run&quot;) {
			throw new UnsupportedOperationException(&quot;달리기에는 아무것도 필요 없습니다.&quot;);
		}
	}

	public void play() {
		if (sport == &quot;soccer&quot;) {
        System.out.println(&quot;축구를 합니다.&quot;);
		}

		else if (sport == &quot;basketball&quot;) {
        System.out.println(&quot;농구를 합니다.&quot;);
		}

		else if (sport == &quot;run&quot;) {
        System.out.println(&quot;달리기를 시작합니다.&quot;);
		}
	}
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;특정 스포츠를 진행하기 전, 공을 준비하기 위한 소스코드이며, ready()를 통해 공을 가져올 수 있습니다.&lt;/li&gt;
&lt;li&gt;ready() 메서드는 sport가 soccer이면 축구공을, basketball이면 농구공을 가져온다고 출력합니다.&lt;/li&gt;
&lt;li&gt;하지만 스포츠에 대한 종류를 점점 늘리거나, 수정이 필요한 경우는 많은 것들을 의존하고 있는 ready()를 수정해야합니다.&lt;/li&gt;
&lt;li&gt;결국 두 기능은 분리되어 있지 않고 하나의 메서드가 두 기능을 모두 가지고 있어 “단일 책임 원칙”을 위반하고 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;interface ItemReady { void itemReady(); }
interface Play { void play(); }

abstract class Sport implements ItemReady, Play {}

class Soccer extends Sport {
    @Override
    public void itemReady() {
        System.out.println(&quot;축구공을 가져옵니다.&quot;);
    }

    @Override
    public void play() {
        System.out.println(&quot;축구를 합니다.&quot;);
    }
}

class Basketball extends Sport {
    @Override
    public void itemReady() {
        System.out.println(&quot;농구공을 가져옵니다.&quot;);
    }

    @Override
    public void play() {
        System.out.println(&quot;농구를 합니다.&quot;);
    }
}

class Run extends Sport {
		@Override
		public void itemReady() {
			throw new UnsupportedOperationException(&quot;달리기에는 아무것도 필요 없습니다.&quot;);
		}

    @Override
    public void play() {
        System.out.println(&quot;달리기를 시작합니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Ready와 Play는 각각의 인터페이스를 만들어 각각 다른 책임 영역을 생성&lt;/li&gt;
&lt;li&gt;Sport 라는 추상 클래스를 만들어, 자식 개체는 이를 상속받아 역할을 분리했습니다.&lt;/li&gt;
&lt;li&gt;이렇게 책임별로 클래스를 분리한다면 SRP를 따르게 됩니다.&lt;/li&gt;
&lt;li&gt;이러한 경우 변경이 발생하더라도 다른 관련 없는 동작에 영향을 미치지 않게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;OCP (개방 폐쇄 원칙) : 객체는 확장에 열려있고 변경에는 닫혀있어야 한다.&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;새로운 기능이 추가되더라도 기존 코드를 수정하지 않고 확장할 수 있어야 한다.&lt;/p&gt;
&lt;/blockquote&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;blockquote&gt;
&lt;p&gt;객체의 확장이 소스 코드의 변경을 초래하지 않아야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;OCP를 적용한다면 기존 코드를 쉽게 확장할 수 있으므로 유연성, 재사용성, 유지보수성이 좋아짐&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;이를 적용하려면?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;기존 코드 수정 없이 새로운 기능을 추가할 수 있어야한다.&lt;/li&gt;
&lt;li&gt;조건문(if-else, switch)을 추가하지 않고 다형성을 활용해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;OCP를 위반한 코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class NotificationService {
  sendNotification(type: string, message: string) {
    if (type === &quot;email&quot;) {
      console.log(&quot;Sending Email:&quot;, message);
    } else if (type === &quot;sms&quot;) {
      console.log(&quot;Sending SMS:&quot;, message);
    } else if (type === &quot;push&quot;) {
      console.log(&quot;Sending Push Notification:&quot;, message);
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;해당 코드는 알림을 전송하는 방식에 관한 코드&lt;/li&gt;
&lt;li&gt;만약, 새로운 기능을 추가하려면 (if-else)를 수정해야 하므로 OCP를 위반&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;interface Notifier {
  send(message: string): void;
}

class EmailNotifier implements Notifier {
  send(message: string) {
    console.log(&quot;Sending Email:&quot;, message);
  }
}

class SmsNotifier implements Notifier {
  send(message: string) {
    console.log(&quot;Sending SMS:&quot;, message);
  }
}

class PushNotifier implements Notifier {
  send(message: string) {
    console.log(&quot;Sending Push Notification:&quot;, message);
  }
}

class NotificationService {
  private notifier: Notifier;

  constructor(notifier: Notifier) {
    this.notifier = notifier;
  }

  sendNotification(message: string) {
    this.notifier.send(message);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;새로운 알림 유형(Notifier)이 추가될 때 NotificationService 코드를 변경할 필요가 없어짐&lt;/li&gt;
&lt;li&gt;이를 통해 확장성을 지키고, 재사용성, 유지보수성이 좋아짐&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;LSP (리스코프 치환 원칙) : 서브 타입은 자신의 기반 타입으로 교체할 수 있어야 한다.&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;라스코프 치환 원칙은 부모 객체와 자식 객체가 있을 때 부모 객체를 호출하는 동작에서 자식 객체가 부모 객체를 완전히 대체할 수 있어야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;상속이 일어나면, 하위 타입인 자식 객체는 상위 타입인 부모 객체의 특성을 가지며, 그 특성을 토대로 확장할 수 있음.&lt;/li&gt;
&lt;li&gt;리스코프 치환 원칙은 올바른 상속을 위해, 자식 객체의 확장이 부모 객체의 방향을 온전히 따르도록 권고하는 원칙.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;LSP를 위반한 코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;interface ItemReady { void itemReady(); }
interface Play { void play(); }

abstract class Sport implements ItemReady, Play {}

class Soccer extends Sport {
    @Override
    public void itemReady() {
        System.out.println(&quot;축구공을 가져옵니다.&quot;);
    }

    @Override
    public void play() {
        System.out.println(&quot;축구를 합니다.&quot;);
    }
}

class Basketball extends Sport {
    @Override
    public void itemReady() {
        System.out.println(&quot;농구공을 가져옵니다.&quot;);
    }

    @Override
    public void play() {
        System.out.println(&quot;농구를 합니다.&quot;);
    }
}

class Run extends Sport {
		@Override
		public void itemReady() {
			throw new UnsupportedOperationException(&quot;달리기에는 아무것도 필요 없습니다.&quot;);
		}

    @Override
    public void play() {
        System.out.println(&quot;달리기를 시작합니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;이는 LSP를 위반하고 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Run인 달리기 부분인데, 자신이 사용하지 않는 인터페이스에 의존하고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;이는 불필요한 메서드나 기능을 구현해야 하는 불필요한 인터페이스 의존성을 피해야 합니다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;throw new UnsupportedOperationException(&quot;달리기에는 아무것도 필요 없습니다.&quot;); 는 모든 스포츠에서 물품 준비 과정을 수행한다고 기대했지만, 이는 예외를 던지는 방식으로 구현되고 있습니다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;만약 실제 상황에서 예기치 못한 상황에 대한 예외가 발생하는 경우가 생기므로 이는 LSP 위반입니다.&lt;/p&gt;
&lt;p&gt;이를 해결하려면 스포츠는 물품이 필요한 스포츠와, 필요하지 않는 스포츠로 나눠야 합니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;interface ItemReady {
    void itemReady();
}
interface Play {
		void play();
}

// 물품이 필요한 스포츠 추상 클래스
abstract class SportWithItem implements ItemReady, Play{}

// 물품이 필요하지 않은 스포츠 추상 클래스
abstract class SportWithoutItem implements Play {}

class Soccer extends SportWithItem {
    @Override
    public void itemReady() {
        System.out.println(&quot;축구공을 가져옵니다.&quot;);
    }

    @Override
    void play() {
        System.out.println(&quot;축구를 합니다.&quot;);
    }
}

class Basketball extends SportWithItem {
    @Override
    public void itemReady() {
        System.out.println(&quot;농구공을 가져옵니다.&quot;);
    }

    @Override
    void play() {
        System.out.println(&quot;농구를 합니다.&quot;);
    }
}

class Run extends SportWithoutItem {
    @Override
    void play() {
        System.out.println(&quot;달리기를 시작합니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;물품을 준비해야 하는 스포츠들을 SportWithItem, 그렇지 않은 스포츠들을 SportWithoutItem으로 구현&lt;/li&gt;
&lt;li&gt;달리기(Run)은 SprotWithoutItem을 상속받아 play만 구현하면 됨&lt;/li&gt;
&lt;li&gt;준비 과정이 필요없는 달리기(run)의 경우 상속하지 않음 → 불필요한 의존성 제거&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;✨ 다만, 지나치게 세분화하는 건 오히려 가독성이 복잡해질 수 있음.
상황에 따라 필요한 기능만 인터페이스로 분리하여 구현하는 방법이 유연성, 확장성에 좋을 수 있음&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h1&gt;ISP (인터페이스 분리 원칙)&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;클라이언트는 자신이 사용하지 않는 메서드에 의존하지 않아야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;하나의 거대한 인터페이스보단 여러 개의 작은 인터페이스로 분리하는 원칙&lt;/p&gt;
&lt;p&gt;인터페이스는 자신의 클라이언트가 사용할 메서드만 가지고 있어야 한다
객체가 사용하지 않는 메서드를 의존해서는 안된다.&lt;/p&gt;
&lt;h2&gt;ISP를 위반한 코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;interface Worker {
    void work();
    void eat();
}

class Robot implements Worker {
    @Override
    public void work() {
        System.out.println(&quot;로봇이 작업을 합니다.&quot;);
    }

    @Override
    public void eat() {
        throw new UnsupportedOperationException(&quot;로봇은 식사를 하지 않습니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Worker에는 work(), eat() 메서드를 둘다 포함하고 있음.&lt;/li&gt;
&lt;li&gt;하지만, Robot은 식사를 하지 않으므로 오류를 던지고 있음&lt;/li&gt;
&lt;li&gt;따라서 자신이 사용하지 않는 메서드에 의존하지 않아야 한다는 규칙을 지키지 않고 있으며, 이는 ISP 위반으로 볼 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class Robot implements Workable {
    @Override
    public void work() {
        System.out.println(&quot;로봇이 작업을 합니다.&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;로봇은 자신에게 필요한 기능인 Workable만 가져오고 있음&lt;/li&gt;
&lt;li&gt;불필요한 메서드를 구현할 필요가 없어짐에 따라 ISP를 준수하고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h1&gt;DIP (의존 역전 원칙) : 추상화에 의존하며, 구체화에 의존하면 안된다.&lt;/h1&gt;
&lt;blockquote&gt;
&lt;p&gt;상위 모듈은 하위 모듈에 의존해서는 안된다&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;추상화는 세부 사항에 의존해서는 안된다&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;의존 관계는 변화하기 쉬운 것에 의존하기보다, 변화하지 않는 것에 의존해야 한다는 원칙&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;고수준 모듈은 저수준 모듈에 의존하면 안 되고, 둘 다 추상화에 의존해야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;추상화된 인터페이스는 구체적인 구현에 의존하지 않으며, 구체적인 클래스는 인터페이스에 구현해야 한다.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;DIP를 위반한 코드&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;class LightBulb {
    void turnOn() {
        System.out.println(&quot;전구가 켜졌습니다.&quot;);
    }

    void turnOff() {
        System.out.println(&quot;전구가 꺼졌습니다.&quot;);
    }
}

class Switch {
    private LightBulb bulb;

    public Switch(LightBulb bulb) {
        this.bulb = bulb;
    }

    public void operate() {
        bulb.turnOn();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;전구를 키고 끄는 기능을 구현하고 있음&lt;/li&gt;
&lt;li&gt;Switch 클래스는 LightBulb 라는 구체적인 클래스에 “직접 의존”하고 있음&lt;/li&gt;
&lt;li&gt;Switch는 고수준 모듈이며, LightBulb라는 저수준 모듈에 직접 의존하는 형태이며, 만약 LightBulb의 구현이 변경된다면 Switch 클래스도 수정해야 함&lt;/li&gt;
&lt;li&gt;이는 DIP를 위반하며 유연성이 떨어지고, 변경에 대한 의존성이 높고, 확장성이 낮아짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;interface Switchable {
    void turnOn();
    void turnOff();
}

class LightBulb implements Switchable {
    @Override
    public void turnOn() {
        System.out.println(&quot;전구가 켜졌습니다.&quot;);
    }

    @Override
    public void turnOff() {
        System.out.println(&quot;전구가 꺼졌습니다.&quot;);
    }
}

class Switch {
    private Switchable device;

    public Switch(Switchable device) {
        this.device = device;
    }

    public void operate() {
        device.turnOn();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;Switch는 Switchable이라는 인터페이스에 의존&lt;/li&gt;
&lt;li&gt;LightBulb와 같은 클래스는 Switchable 인터페이스를 구현하며, 이는 확장 시 다양한 디바이스가 스위치를 사용할 수 있게 됨&lt;/li&gt;
&lt;li&gt;Switch는 더 이상 구체적인 클래스에 의존하지 않으며, 추상화된 인터페이스에 의존&lt;/li&gt;
&lt;li&gt;다른 말로 고수준 모듈은 저수준 모듈에 의존하지 않고 추상화된 인터페이스에 의존하므로 DIP 준수&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;:::tip
SOLID를 준수한다면 분명 좋은 코드지만, 상황(lombok, 뭉치는 게 오히려 보기 편한 것 등)에 따라서는 유동적으로 판단할 필요가 있음
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h1&gt;마무리&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;5가지 객체 지향 설계 원칙인 SOLID의 핵심은 추상화와 다형성&lt;/li&gt;
&lt;li&gt;구체 클래스에 의존하지 않고 추상 클래스에 의존함으로써 유연하고 확장가능한 개발이 가능&lt;/li&gt;
&lt;li&gt;SOLID 원칙을 잘 준수하면 불필요한 변경을 최소화, 테스트와 디버깅이 쉬워짐&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>