<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>yerim's log</title>
    <link>https://sunyerim.tistory.com/</link>
    <description>컴퓨터공학도 학생의 공부 기록 </description>
    <language>ko</language>
    <pubDate>Sun, 21 Jun 2026 22:42:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>SunYerim</managingEditor>
    <image>
      <title>yerim's log</title>
      <url>https://tistory1.daumcdn.net/tistory/5936823/attach/8f64f6e3ca9a47a39167aa17f05ae166</url>
      <link>https://sunyerim.tistory.com</link>
    </image>
    <item>
      <title>[Java] JDBC 인터페이스 분석: Statement vs PreparedStatement vs CallableStatement</title>
      <link>https://sunyerim.tistory.com/41</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;0. 들어가며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a style=&quot;background-color: #e6f5ff; color: #0070d1;&quot; href=&quot;https://sunyerim.tistory.com/40&quot;&gt;2026.01.04 - [개발관련] - [MySQL] 복합 인덱스 적용 및 성능테스트 트러블 슈팅&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 포스팅에서 MySQL 복합 인덱스 적용으로 조회 성능을 개선했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성능테스트를 진행하기 전, &lt;b data-index-in-node=&quot;47&quot; data-path-to-node=&quot;6&quot;&gt;다양한 최적화 방안&lt;/b&gt;을 고민했습니다. 그중 하나가 애플리케이션 레벨에서의 쿼리 전송 방식을 점검하는 것이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트는 &lt;b&gt;JPA(Hibernate)&lt;/b&gt;를 사용하고 있습니다. 이에 JPA가 내부적으로 어떤 방식을 사용하여 DB와 통신하는지, 그리고 그것이 대용량 트래픽 처리에 적합한 방식인지 검증하기 위해 JDBC 인터페이스(Statement, PreparedStatement, CallableStatement) 개념들을 다시 한번 정리해 보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1767598453014&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java.sql.Statement        (부모: 기본 쿼리 실행)
   └── java.sql.PreparedStatement  (자식: + 캐싱, 파라미터 바인딩)
           └── java.sql.CallableStatement  (손자: + 프로시저 호출, Output 파라미터)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Statement&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 JDBC 인터페이스로 SQL문을 단순 문자열 그대로 DB에 전송합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;특징: 주로 &lt;b&gt;정적 SQL&lt;/b&gt;을 만들 때 사용합니다. 여기서 정적 SQL이란 &lt;b&gt;매개변수가 없어 쿼리 문장 자체가 변하지 않는 상태&lt;/b&gt;를 의미합니다.&lt;/li&gt;
&lt;li&gt;동작 방식: &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;문자열로 완성된 쿼리를 전달하기 때문에, 값이 바뀔 때마다 쿼리 문장 전체를 새로 생성해야 합니다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;문제점
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;성능 저하&lt;/b&gt;: 값이 바뀔 때마다 DB는 매번 새로운 쿼리로 인식하여 파싱과 컴파일을 반복합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 취약&lt;/b&gt;: 문자열 연결 방식(+ 또는 String.format)을 사용하므로 SQL Injection 공격에 매우 취약합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;b&gt;*SQL Injection&lt;/b&gt;: 악의적인 사용자가 입력창에 SQL 구문을 주입하여, 데이터베이스를 조작하거나 비정상적으로 데이터를 유출시키는 공격 기법입니다.&lt;/p&gt;
&lt;pre id=&quot;code_1767598925174&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. 매개변수가 없는 순수 정적 쿼리
String sql = &quot;INSERT INTO tblAddress VALUES (seq.nextVal, 'Sopia', 21, 'f', '서울시', default)&quot;;

// 2. 값을 외부에서 받는 경우 (문자열 결합 방식 -&amp;gt; Injection 위험)
String name = &quot;Sopia&quot;; 
int age = 21;
String sql = String.format(&quot;INSERT INTO tblAddress VALUES (seqAddress.nextVal, '%s', %s, ...)&quot;, name, age);

Statement stmt = conn.createStatement();
stmt.executeUpdate(sql);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. PreparedStatement (JPA의 선택)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Statement를 상속받은 인터페이스로 동적 SQL을 처리하는 데 최적화되어 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;특징: 매개변수(?)가 포함된 동적 쿼리를 실행할 때 사용합니다. 쿼리의 뼈대는 고정하되 데이터만 실시간으로 갈아 끼우는 방식입니다.&lt;/li&gt;
&lt;li&gt;동작 방식: 쿼리를 미리 컴파일해두고, 실행 시에는 바인딩된 매개변수 값만 전달합니다.&lt;/li&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;성능 최적화: DB가 실행 계획을 재사용하므로 반복 요청 시 매우 빠릅니다.&lt;/li&gt;
&lt;li&gt;보안성: 매개변수로 전달된 값을 단순 텍스트로 처리하기 때문에 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;11,2,1,1,0&quot;&gt;SQL Injection을 원천 차단&lt;/b&gt;합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767599090080&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 매개변수(?)를 사용하는 동적 쿼리 틀 정의
String sql = &quot;INSERT INTO tblAddress VALUES (seqAddress.nextVal, ?, ?, ?, ?, default)&quot;;
PreparedStatement pstmt = conn.prepareStatement(sql);

// 실행 시점에 동적으로 값 바인딩 (안전함)
pstmt.setString(1, &quot;Sopia&quot;);
pstmt.setInt(2, 21);
pstmt.executeUpdate();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. CallableStatement&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 내부에 정의된 &lt;b&gt;스토어드 프로시저&lt;/b&gt;를 호출하기 위한 인터페이스입니다. PreparedStatement를 상속받아 파라미터 바인딩 기능을 그대로 사용하면서, 프로시저만의 Output 파라미터 처리 기능을 추가로 가집니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;특징: 복잡한 로직을 DB 서버 내부 함수(프로시저)로 정의해두고 Java에서는 호출만 합니다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;*스토어드 프로시저&lt;/b&gt;: 일련의 쿼리문들을 하나의 함수처럼 선언하여 데이터베이스에 저장해 둔 것입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;장점(네트워크 비용 감소): 여러 번의 쿼리 요청을 한 번의 호출로 처리하므로 네트워크 왕복 시간을 줄여 응답 속도를 단축할 수 있습니다.&lt;/li&gt;
&lt;li&gt;단점 및 고려 대상 제외 이유 (Trade-off)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;DB CPU 사용률 급증: 로직 수행 주체가 WAS에서 DB 서버로 넘어갑니다. WAS는 확장이 쉽지만 DB는 리소스 확장이 어렵고 비용이 비쌉니다. 연산 부하가 DB에 집중되면 CPU 사용률이 급증하여 전체 시스템의 병목이 될 위험이 있습니다.&lt;/li&gt;
&lt;li&gt;유지보수성 저하: 로직이 DB 내부에 숨어 있어 형상 관리와 디버깅이 까다롭습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767599373982&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DB에 저장된 프로시저 호출 (?는 파라미터)
String runProcedure = &quot;{call get_stock_history(?)}&quot;;
CallableStatement cstmt = conn.prepareCall(runProcedure);

cstmt.setString(1, &quot;005930&quot;);
cstmt.execute();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 요약 및 결론&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 95px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style13&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;구분&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;Statement&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;PreparedStatement&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;CallableStatement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;주요용도&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,1,0&quot;&gt;정적 SQL&lt;/b&gt; (단순 실행)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8,1,2,0&quot;&gt;동적 SQL&lt;/b&gt; (파라미터 중심)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;스토어드 프로시저 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;쿼리해석&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;실행 시마다 매번 파싱&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;최초 1회만 파싱 (캐싱)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;DB에 이미 저장됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;보안성&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;SQL Injection에 취약&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;매우 안전&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;안전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;리소스부하&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;DB 파싱 부하 발생&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;WAS/DB 리소스 균형&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 19px;&quot;&gt;DB CPU 부하 집중&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA(Hibernate)는 내부적으로 PreparedStatement를 기본 채택하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 성능 최적화는 단순히 실행 시간을 줄이는 것뿐만 아니라 시스템 전체 리소스(CPU, 메모리, 네트워크)의 균형을 맞춰 고려해야하는 과정임을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 프레임워크의 기능을 사용하는 것에 그치지 않고 동작 원리와 리소스 사용량 사이의 &lt;b data-index-in-node=&quot;55&quot; data-path-to-node=&quot;7&quot;&gt;Trade-off&lt;/b&gt;를 고민할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.datacadamia.com/lang/java/jdbc/callable_statement&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.datacadamia.com/lang/java/jdbc/callable_statement&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1767600227321&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;JDBC - Callable Statement (Stored Procedure)&quot; data-og-description=&quot;The CallableStatement objects interface adds methods to the statement interface for retrieving output parameter values returned from stored procedures. See java/sql/CallableStatementCallableStatement objects java/sql/CallableStatementCallableStatement obje&quot; data-og-host=&quot;www.datacadamia.com&quot; data-og-source-url=&quot;https://www.datacadamia.com/lang/java/jdbc/callable_statement&quot; data-og-url=&quot;https://www.datacadamia.com/lang/java/jdbc/callable_statement&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/RaVxF/dJMb8YXD8Cn/eth1uWyhdFKOweRmnaqvp0/img.jpg?width=933&amp;amp;height=933&amp;amp;face=0_0_933_933,https://scrap.kakaocdn.net/dn/7hMJE/dJMb8PGoOB4/yxMOV2oSB9kzrQ1UllztK1/img.jpg?width=933&amp;amp;height=400&amp;amp;face=0_0_933_400,https://scrap.kakaocdn.net/dn/d1PN6R/dJMb88eTftx/cfbDP1KWKpy3lUyTh2x7Z0/img.jpg?width=933&amp;amp;height=400&amp;amp;face=0_0_933_400&quot;&gt;&lt;a href=&quot;https://www.datacadamia.com/lang/java/jdbc/callable_statement&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.datacadamia.com/lang/java/jdbc/callable_statement&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/RaVxF/dJMb8YXD8Cn/eth1uWyhdFKOweRmnaqvp0/img.jpg?width=933&amp;amp;height=933&amp;amp;face=0_0_933_933,https://scrap.kakaocdn.net/dn/7hMJE/dJMb8PGoOB4/yxMOV2oSB9kzrQ1UllztK1/img.jpg?width=933&amp;amp;height=400&amp;amp;face=0_0_933_400,https://scrap.kakaocdn.net/dn/d1PN6R/dJMb88eTftx/cfbDP1KWKpy3lUyTh2x7Z0/img.jpg?width=933&amp;amp;height=400&amp;amp;face=0_0_933_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JDBC - Callable Statement (Stored Procedure)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The CallableStatement objects interface adds methods to the statement interface for retrieving output parameter values returned from stored procedures. See java/sql/CallableStatementCallableStatement objects java/sql/CallableStatementCallableStatement obje&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.datacadamia.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>개발관련</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/41</guid>
      <comments>https://sunyerim.tistory.com/41#entry41comment</comments>
      <pubDate>Mon, 5 Jan 2026 17:11:00 +0900</pubDate>
    </item>
    <item>
      <title>[MySQL] 복합 인덱스 적용 및 성능테스트 트러블 슈팅</title>
      <link>https://sunyerim.tistory.com/40</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;0. 들어가며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;종목별 과거 주가 조회 API의 응답 지연 문제가 있었고 &lt;b&gt;변동성이 낮은 데이터&lt;/b&gt;라는 특성을 고려해 &lt;b&gt;Redis 캐싱&lt;/b&gt;을 적용해 응답 속도를 약 30% 개선했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 외부 인프라에 의존하기에 앞서 &lt;b&gt;데이터베이스(RDBMS) 자체의 성능을 어느 수준까지 끌어올릴 수 있을지&lt;/b&gt;를 확인해보고 싶었습니다. 단순히 데이터를 메모리에 올려 반환하는 방식보다 병목의 원인이 되는 &lt;b&gt;쿼리 실행 계획을 분석하고 개선하는 것이 보다 기본적인 접근&lt;/b&gt;이라고 생각했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인별로 서버가 분리된 MSA 환경에서 주식 서비스를 독립된 환경으로 격리하여 테스트를 진행한 기록입니다. 병목을 재현한 뒤 추가적인 인프라 도입 없이 &lt;b&gt;인덱스 설계 변경만으로 조회 성능을 31초에서 0.009초까지 개선한 과정&lt;/b&gt;입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문제 상황: 500만 건 데이터의 정렬 지연&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 가장 빈번하게 조회하는 '종목별 과거 시세 조회' 기능에서 병목이 발견되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-1.&amp;nbsp; 대상 데이터&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 발생한 곳은 일별 주가 정보를 저장하는 테이블이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 초기의 누적 데이터는 50만 건 수준이었으나, &lt;b data-index-in-node=&quot;47&quot; data-path-to-node=&quot;15,2&quot;&gt;매일 전 종목의 시세가 쌓이는 구조&lt;/b&gt;입니다. 이에 현재 데이터 양에 안주하지 않고, 향후 데이터가 수백만 건 이상으로 폭증했을 때도 서비스가 안정적일지 검증하고 싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 테스트 환경에 &lt;b&gt;데이터를 약 10배 증폭시켜 부하 상황을 시뮬레이션&lt;/b&gt;했습니다. 특히 병목이 예상되는 특정 종목(삼성전자)에는 &lt;b&gt;약 100만 건의 데이터&lt;/b&gt;를 적재하여 추후 분봉/틱 단위 차트로 고도화될 경우까지 대비한 &lt;b&gt;선제적인 스트레스 테스트&lt;/b&gt;를 수행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-2. 병목 쿼리&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1767515612845&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM daily_stock_history
WHERE stock_code = '005930'  -- 삼성전자 (종목코드)
ORDER BY daily_stock_history_date DESC
LIMIT 90;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1-3. 장애 현상&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부하 테스트(k6) 결과, 동시 접속자가 50명을 넘어서자 응답 속도가 기하급수적으로 느려지며 시스템 장애로 이어졌습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;p95 기준 응답 속도: 31.75s&lt;/li&gt;
&lt;li&gt;처리량(TPS) : 약 4.5 TPS
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;150명의 가상 유저(VUser)가 요청을 보내고 있음에도 서버는 초당 4건 정도밖에 처리하지 못했습니다. Filesort 작업이 DB 자원을 독점하면서 병목이 발생했기 때문입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;에러율: 1.13% (타임아웃 발생)&lt;/li&gt;
&lt;li&gt;로그: 쿼리 처리가 지연되면서 DB 커넥션 풀(HikariCP)이 고갈되어 &lt;b&gt;SQLTransientConnectionException&lt;/b&gt; 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1982&quot; data-origin-height=&quot;1242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uus8L/dJMcabv6BQZ/CgXYsUKr3b2rjoJ8qFx330/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uus8L/dJMcabv6BQZ/CgXYsUKr3b2rjoJ8qFx330/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uus8L/dJMcabv6BQZ/CgXYsUKr3b2rjoJ8qFx330/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fuus8L%2FdJMcabv6BQZ%2FCgXYsUKr3b2rjoJ8qFx330%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;503&quot; height=&quot;315&quot; data-origin-width=&quot;1982&quot; data-origin-height=&quot;1242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2864&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn89PG/dJMcadAPVOE/rt7JhRGyZkkp0hVkGtKO11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn89PG/dJMcadAPVOE/rt7JhRGyZkkp0hVkGtKO11/img.png&quot; data-alt=&quot;30초 대기 후 타임아웃 발생&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn89PG/dJMcadAPVOE/rt7JhRGyZkkp0hVkGtKO11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn89PG%2FdJMcadAPVOE%2Frt7JhRGyZkkp0hVkGtKO11%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;757&quot; height=&quot;59&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2864&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;30초 대기 후 타임아웃 발생&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 테스트 환경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정확한 원인 분석을 위해 MacBook Pro와 Mac Mini를 1:1로 직접 연결하여 하드웨어 자원을 완벽히 격리한 로컬 테스트 환경을 구축했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2-1. 물리적 구성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Target Server: MacBook Pro (M2 Chip)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;역할: 실제 서비스 트래픽 처리&lt;/li&gt;
&lt;li&gt;실행 프로세스: WAS, MySQL, Scouter Host / Java Agent&lt;/li&gt;
&lt;li&gt;요청 처리에만 자원을 집중하도록 구성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Load Generator &amp;amp; Monitor: Mac Mini
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;역할: 부하 생성 및 데이터 수집 / 관제&lt;/li&gt;
&lt;li&gt;실행 프로세스: k6 (부하 테스트 스크립트), Scouter Server(Collector), Scouter Client&lt;/li&gt;
&lt;li&gt;부하 발생과 로그 수집에 필요한 리소스가 Target Server의 CPU를 점유하지 않도록 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2-2. 네트워크 구성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;순수한 DB 쿼리 성능과 네트워크 I/O만을 측정하기 위하여 L2 스위치로 LAN을 구성했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2-3. 테스트 시나리오&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 사용자 유입 패턴을 고려하여 k6로 계단식 부하 시나리오를 설계했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;목표: 시스템이 버틸 수 있는 최대 처리량과 임계점 확인&lt;/li&gt;
&lt;li&gt;성능 기준: p95 응답 속도 500ms 이하, 에러율 1% 미만&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[테스트 단계 구성]&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Warm-up (10명)&lt;/li&gt;
&lt;li&gt;Load (50명): 평시 트래픽 상황 가정&lt;/li&gt;
&lt;li&gt;Stress (150명): 트래픽 폭증 상황 가정 &amp;rarr; 이 구간에서 DB CPU가 포화되고 병목이 발생하는지 관측&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767539711270&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;export const options = {
  // [Step Load 패턴 적용]
  stages: [
    { duration: '30s', target: 10 },  // 1. Warm-up: 10명으로 가볍게 시작
    { duration: '1m', target: 50 },   // 2. Load: 50명까지 증가 (평시 트래픽)
    { duration: '1m', target: 150 },  // 3. Stress: 150명으로 폭증 (병목 재현 구간)
    { duration: '30s', target: 0 },   // 4. Cool-down: 종료
  ],
  // [성능 목표 설정: SLA]
  thresholds: {
    // &quot;95%의 요청이 500ms 안에 들어와야 성공&quot; -&amp;gt; 인덱스 미적용 시 이 기준을 초과하여 Fail 발생
    http_req_duration: ['p(95)&amp;lt;500'], 
    // &quot;에러율은 1% 미만이어야 함&quot;
    http_req_failed: ['rate&amp;lt;0.01'],   
  },
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 원인 분석: 실행 계획 점검&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장애 재현 직후, 쿼리의 실행 계획을 확인해보았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2338&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d74J4c/dJMcadN9ZRG/5An6QspoErXtwHNrKMpSWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d74J4c/dJMcadN9ZRG/5An6QspoErXtwHNrKMpSWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d74J4c/dJMcadN9ZRG/5An6QspoErXtwHNrKMpSWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd74J4c%2FdJMcadN9ZRG%2F5An6QspoErXtwHNrKMpSWk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;606&quot; height=&quot;93&quot; data-origin-width=&quot;2338&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3-1. 대용량 데이터의 정렬 부하&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 계획의 Sort 단계에서 약 100만건의 데이터가 처리되고 있음이 확인되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;상황:&amp;nbsp;stock_code 인덱스를 통해 '삼성전자' 데이터를 찾았지만, 정렬 기준인 날짜(date)에 대한 인덱스는 없었습니다.&lt;/li&gt;
&lt;li&gt;동작: DB는 추출된 100만 건을 정렬하기 위해 메모리(Sort Buffer)를 할당했으나 용량을 초과하여 디스크에 임시 파일을 생성해 정렬하는 Filesort 작업을 수행했습니다.&lt;/li&gt;
&lt;li&gt;문제: 메모리 처리보다 느린 디스크 I/O가 발생하면서 쿼리 응답 시간이 30초 이상으로 치솟았고 과도한 CPU 자원을 소모했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3-2. 비효율적인 데이터 스캔&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 쿼리의 목적은 &lt;b data-index-in-node=&quot;13&quot; data-path-to-node=&quot;11&quot;&gt;최신 데이터 90건&lt;/b&gt;을 조회하는 것입니다. 하지만 실행 계획을 보면 1,010,000건(Rows)을 읽고 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,0,0&quot;&gt;Read Rows&lt;/b&gt;: 약 1,010,000건 (대상 종목 전체 스캔)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12,1,0&quot;&gt;Result Rows&lt;/b&gt;: 90건 (상위 90개 추출)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단 90개의 결과를 얻기 위해 100만 개를 읽고 정렬한 뒤, 나머지 99.9%는 버려버리는&lt;b data-index-in-node=&quot;52&quot; data-path-to-node=&quot;13&quot;&gt;&amp;nbsp;비효율적인 방식&lt;/b&gt;으로 동작하고 있었습니다. 이것이 CPU를 점유하고 DB 커넥션을 고갈시킨 원인이었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 해결 방법: 복합 인덱스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;WHERE 절의 조회 조건(stock_code)과 ORDER BY 절의 정렬 조건(daily_stock_history_date)을 결합한 복합 인덱스를 생성했습니다.&lt;/li&gt;
&lt;li&gt;원리: 인덱스는 데이터가 정렬된 상태로 저장됩니다. 따라서 복합 인덱스를 활용하면 DB가 런타임에 별도의 정렬을 수행할 필요 없이, 이미 정렬된 데이터를 뒤에서부터 순서대로 읽기만 하면(Backward Index Scan) 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1767536230466&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE INDEX idx_stock_history_code_date
ON daily_stock_history (stock_code, daily_stock_history_date);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4-1. 적용 시 고려사항 (Trade-off)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 인덱스 추가가 무조건적인 정답은 아닙니다. 인덱스를 적용할 때 두 가지 단점을 고려해야 했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;쓰기 성능 저하: 데이터가 INSERT, UPDATE, DELETE 될 때마다 인덱스도 재정렬해야 하므로 쓰기 작업 부하가 증가.&lt;/li&gt;
&lt;li&gt;저장 공간 증가: 인덱스 저장을 위한 별도의 디스크 공간을 차지.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IFogG/dJMcacu0Thq/PjZFMNqr2q0I9mTwZHdGMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IFogG/dJMcacu0Thq/PjZFMNqr2q0I9mTwZHdGMK/img.png&quot; data-alt=&quot;실제 테이블 용량 분석 결과&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IFogG/dJMcacu0Thq/PjZFMNqr2q0I9mTwZHdGMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIFogG%2FdJMcacu0Thq%2FPjZFMNqr2q0I9mTwZHdGMK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;659&quot; height=&quot;46&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 테이블 용량 분석 결과&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 용량 대비 &lt;b data-index-in-node=&quot;9&quot; data-path-to-node=&quot;14&quot;&gt;인덱스가 차지하는 비중은 약 12% 수준&lt;/b&gt;이었습니다. 주가 데이터는 장 마감 후 한 번 적재되면 수정이 거의 발생하지 않고 조회 트래픽이 압도적으로 높습니다. 따라서 &lt;b data-index-in-node=&quot;101&quot; data-path-to-node=&quot;14&quot;&gt;12%의 저장 공간을 투자하여 조회 속도를 3,500배 개선&lt;/b&gt;하는 것이 비용 대비 효과 측면에서 합리적이라고 생각했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스 적용 후, 동일한 격리 환경에서 성능을 측정한 결과입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5-1. 정량적 결과&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 단일 인덱스 환경과 복합 인덱스 적용 후의 성능을 비교했을 때, 약 3,500배의 성능 향상을 확인했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (4).png&quot; data-origin-width=&quot;2366&quot; data-origin-height=&quot;1318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfPoKm/dJMcagc7m05/23V8cU4OLA3j9McqP4ePI1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfPoKm/dJMcagc7m05/23V8cU4OLA3j9McqP4ePI1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfPoKm/dJMcagc7m05/23V8cU4OLA3j9McqP4ePI1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfPoKm%2FdJMcagc7m05%2F23V8cU4OLA3j9McqP4ePI1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;565&quot; height=&quot;315&quot; data-filename=&quot;image (4).png&quot; data-origin-width=&quot;2366&quot; data-origin-height=&quot;1318&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;응답 속도: 31.75s -&amp;gt; 0.009s (p95 기준)&lt;/li&gt;
&lt;li&gt;처리량(TPS): 4.5TPS -&amp;gt; 약 100TPS (20배 이상 증가)
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;기존에는 DB 병목으로 인해 요청을 처리하지 못하고 타임아웃이 발생했으나 개선 후에는 유입되는 트래픽을 지연 없이 안정적으로 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5-2. 실행 계획의 변화&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (5).png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;118&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w6Usa/dJMb99ZmBV8/Th4xJblDyKBkekIi7hsk3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w6Usa/dJMb99ZmBV8/Th4xJblDyKBkekIi7hsk3K/img.png&quot; data-alt=&quot;개선 전&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w6Usa/dJMb99ZmBV8/Th4xJblDyKBkekIi7hsk3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw6Usa%2FdJMb99ZmBV8%2FTh4xJblDyKBkekIi7hsk3K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;374&quot; height=&quot;55&quot; data-filename=&quot;image (5).png&quot; data-origin-width=&quot;802&quot; data-origin-height=&quot;118&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선 전&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;image (6).png&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bs7WXS/dJMcadN95GY/HxhxN1DKq8kSWwdXgrRqTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bs7WXS/dJMcadN95GY/HxhxN1DKq8kSWwdXgrRqTk/img.png&quot; data-alt=&quot;개선 후&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bs7WXS/dJMcadN95GY/HxhxN1DKq8kSWwdXgrRqTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbs7WXS%2FdJMcadN95GY%2FHxhxN1DKq8kSWwdXgrRqTk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;375&quot; height=&quot;55&quot; data-filename=&quot;image (6).png&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;개선 후&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;개선 전: 인덱스에 정렬 정보가 없어 DB가 별도로 정렬을 수행하며 부하 발생.&lt;/li&gt;
&lt;li&gt;개선 후: 인덱스가 이미 정렬되어 있어별도 정렬 없이 순서대로 읽기만 하여 부하 해소.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에 성능 테스트를 하면서 단순히 기능만 구현하는 게 다가 아니라는 걸 느꼈습니다. 특히 옵티마이저(Optimizer)의 동작 원리나 &lt;span data-token-index=&quot;1&quot;&gt;Statement, PreparedStatement, CallableStatement &lt;/span&gt;같은 기본 개념들도 다시 확실하게 정리해봐야겠다는 생각이 들었습니다.&lt;/p&gt;</description>
      <category>개발관련</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/40</guid>
      <comments>https://sunyerim.tistory.com/40#entry40comment</comments>
      <pubDate>Sun, 4 Jan 2026 23:59:43 +0900</pubDate>
    </item>
    <item>
      <title>WebSocket</title>
      <link>https://sunyerim.tistory.com/39</link>
      <description>&lt;h3&gt;0-1. 웹소켓이란?&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;WebSocket 프로토콜은 접속 확립에 HTTP를 사용하지만, 그 후의 통신은 WebSocket 독자의 프로토콜로 이루어진다.&lt;/li&gt;
&lt;li&gt;header가 상당히 작아 overhead가 적은 특징이 있다.&lt;/li&gt;
&lt;li&gt;실시간 양방향 데이터 통신이 필요한 경우, 많은 수의 동시 접속자를 수용해야 하는 경우, 브라우저에서 TCP 기반의 통신으로 확장해야 하는 경우 등.&lt;/li&gt;
&lt;li&gt;응용 계층(OSI 7계층) 수준의 프로토콜&lt;/li&gt;
&lt;li&gt;주로 사용되는 경우&lt;ul&gt;
&lt;li&gt;동시 접속자 수 많고 빠른 반응이 필요한 서비스&lt;/li&gt;
&lt;li&gt;실시간 채팅, 알림, 주식 시세 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;0-2. HTTP vs 웹 소켓 차이점?&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;결정적인 차이는 프로토콜에 있다. HTTP는 질문하면 대답하는 통신, WebSocket은 서로 실시간 대화하는 통신&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;HTTP&lt;/th&gt;
&lt;th&gt;WebSocket&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;통신 방식&lt;/td&gt;
&lt;td&gt;요청/응답 기반 (Request-Response)&lt;/td&gt;
&lt;td&gt;양방향 (Full-duplex)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;연결 유지&lt;/td&gt;
&lt;td&gt;매 요청마다 새 연결 (Stateless)&lt;/td&gt;
&lt;td&gt;한 번 연결 후 계속 유지 (Stateful)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프로토콜&lt;/td&gt;
&lt;td&gt;HTTP/1.1, HTTP/2&lt;/td&gt;
&lt;td&gt;WebSocket(ws://, wss://)&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;불가능 (단, Polling, SSE 등으로 우회)&lt;/td&gt;
&lt;td&gt;가능 (서버가 직접 Push 가능)&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;h3&gt;1. WebSocketHandler&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Spring Framework에서 웹소켓 통신을 처리하기 위한 기본 핸들러 클래스&lt;/li&gt;
&lt;li&gt;WebSocket 연결 수립, 메시지 송수신, 연결 종료 등 이벤트 기반 콜백 메서드를 제공&lt;/li&gt;
&lt;li&gt;주로 TextWebSocketHandler를 상속받아 사용&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;2. STOMP (Simple Text Oriented Messaging Protocol)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;WebSocket 위에서 동작하는 메시징 프로토콜&lt;ul&gt;
&lt;li&gt;WebSocket이 단순 통로라면, STOMP는 그 통로 안의 대화 규칙(프로토콜)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;텍스트 기반 프로토콜&lt;/li&gt;
&lt;li&gt;SEND, SUBSCRIBE, MESSAGE, CONNECT 등의 명령으로 메시지를 주고받음&lt;/li&gt;
&lt;li&gt;Pub/Sub (발행 - 구독) 구조를 지원하며, 클라이언트가 토픽을 구독하면 서버가 해당 주제로 브로드캐스트 가능&lt;/li&gt;
&lt;li&gt;Spring WebSocket + STOMP 구조&lt;ul&gt;
&lt;li&gt;/app : 클라이언트 → 서버 메시지 경로&lt;/li&gt;
&lt;li&gt;/topic : 서버 → 클라이언트 브로드캐스트&lt;/li&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;li&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;/li&gt;
&lt;li&gt;구조가 복잡하고 저지연 고빈도 스트림에는 부적합&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3. Socket.io&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;WebSocket을 우선 사용하지만, 브라우저가 지원하지 않거나 네트워크 제약이 있으면 fallback을 사용한다.&lt;/li&gt;
&lt;li&gt;즉, WebSocket 지원되면 WebSocket으로, 지원이 안 되면 HTTP Long polling으로 흉내낸다.&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;4. 장단점&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;WebSocketHandler&lt;/th&gt;
&lt;th&gt;STOMP&lt;/th&gt;
&lt;th&gt;Socket.io&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;WebSocket&lt;/td&gt;
&lt;td&gt;WebSocket + STOMP 프로토콜&lt;/td&gt;
&lt;td&gt;WebSocket + fallback(HTTP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;언어/환경&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java(Spring)&lt;/td&gt;
&lt;td&gt;Java(Spring)&lt;/td&gt;
&lt;td&gt;Node.js&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;Pub/Sub 구조&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;텍스트 / 바이너리&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;호환성·유연성&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h3&gt;5. 정리&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;WebSocketHandler → WebSocket을 직접 다루는 Spring 구현체&lt;/li&gt;
&lt;li&gt;STOMP → WebSocket 위에서 메시징 규칙을 추가한 프로토콜&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://socket.io/&quot;&gt;Socket.io&lt;/a&gt; → WebSocket을 확장한 Node.js 라이브러리 (이벤트 기반 + fallback 지원)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;결국 세 가지 모두 WebSocket을 기반으로 실시간 통신을 구현하는 방식&lt;/strong&gt;&lt;/p&gt;</description>
      <category>개발관련</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/39</guid>
      <comments>https://sunyerim.tistory.com/39#entry39comment</comments>
      <pubDate>Mon, 10 Nov 2025 13:24:48 +0900</pubDate>
    </item>
    <item>
      <title>[OS] JVM부터 동시성까지</title>
      <link>https://sunyerim.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;모종의 이유로 다시 내용을 훑고있는데, 모든 내용이 다 &amp;hellip; 유기적으로 연관이 되어있다는 걸 알고 흐름에 따라 정리해보기로 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JVM 메모리 구조 &amp;rarr; 멀티스레드와 공유 자원 &amp;rarr; 경쟁 상태 문제 발생 &amp;rarr; 동기화(뮤텍스, 세마포어)로 문제 해결 &amp;rarr; GC와 static의 관계&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JVM&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 바이트코드(.claass 파일)를 운영체제가 이해할 수 있는 언어로 번역하고 실행하는 소프트웨어입니다. 특정 운영체제에 종속되지 않고 자바 프로그램을 실행할 수 있게 해주는 가상 컴퓨터입니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;플랫폼 독립성, 메모리 관리, 런타임 환경 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;JVM 메모리 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 프로그램을 실행하면 하나의 프로세스가 생성되고, 그 안에 JVM이 동작합니다.&lt;br /&gt;JVM은 프로그램을 실행하는 데 필요한 메모리를 여러 영역으로 나누어 관리하는데, 크게 스레드가 공유하는 영역과 스레드마다 독립적인 영역으로 나뉩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 공유하는 영역
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM 내의 모든 스레드가 함께 사용합니다. 여러 스레드가 동시 접근이 가능하기 때문에 동기화 문제 (Race Condition)가 발생할 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메서드 영역&lt;/b&gt;: 클래스 파일의 바이트 코드, 정적(static) 변수, 상수, 필드 정보, 메서드 정보 등이 저장됩니다. JVM이 프로그램을 시작할 때 로드되며, 프로그램이 종료될 때까지 남아 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;힙 영역&lt;/b&gt;: new 연산자로 생성된 객체와 배열이 저장되는 공간입니다. 대부분의 프로그램 데이터가 이 곳에 저장되며, GC의 주요 관리 대상입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스레드마다 독립적인 영역
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 자신만의 고유한 공간을 가지며, 다른 스레드와 공유하지 않으므로 동기화 문제가 발생하지 않습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스택 영역&lt;/b&gt;: 메서드 호출 시 사용되는 지역 변수, 매개변수, 반호나 값 등이 임시로 저장됩니다. 메서드 호출이 끝나면 자동으로 소멸됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PC(Program Counter) 레지스터&lt;/b&gt;: 스레드가 실행할 다음 명령어의 주소를 저장합니다. 스레드마다 실행 순서가 다르므로 각각 독립적인 PC 레지스터를 가집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로세스와 스레드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM과 메모리 구조를 이해하려면? &amp;rarr; 둘의 관계를 명확히 알아야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;운영체제로부터 메모리, CPU 등 자원을 할당받아 실행되는 프로그램의 단위&lt;/li&gt;
&lt;li&gt;각 프로세스는 독립적인 메모리 공간을 가지며 하나의 JVM이 하나의 프로세스입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스레드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 프로세스 내에서 실행되는 작업의 흐름(실행 단위)&lt;/li&gt;
&lt;li&gt;한 프로세스는 여러 스레드를 가질 수 있으며, 이 스레드들은 프로세스의 메모리 공간을 공유합니다.&lt;/li&gt;
&lt;li&gt;자바의 멀티스레드 프로그래밍은 이 스레드들이 공유하는 힙과 메서드 영역의 데이터를 안전하게 다루는 것이 핵심입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;멀티스레드와 공유 자원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM 프로세스는 여러 개의 스레드를 생성하여 작업을 병렬로 처리할 수 있습니다. 각 스레드는 독립적인 실행 흐름을 가지지만, 힙과 메서드 영역을 공유합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 공유 자원에 여러 스레드가 동시에 접근하여 &lt;b&gt;값을 읽거나 수정할 때 문제가 발생&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(예를 들어 &amp;rarr; 여러 스레드가 동시에 static 변수의 값을 증가시키는 작업을 한다고 가정.)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 스레드는 변수의 현재 값을 읽고, 1을 더한 다음 다시 쓰는 과정을 거친다.&lt;/li&gt;
&lt;li&gt;만약 한 스레드가 값을 읽은 직후 다른 스레드가 먼저 값을 수정해 버리면? &amp;rarr; 첫 번째 스레드는 낡은 값으로 계산하게 되어 최종 결과가 예상과 달라진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 여러 스레드가 공유 자원에 동시에 접근하면서 발생하는 데이터 불일치 문제를 &lt;b&gt;경쟁 상태(Race Condition)&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;경쟁 상태 문제 발생&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방금 설명했듯, JVM의 힙과 메서드 영역은 모든 스레드가 공유하는 공간입니다.&lt;br /&gt;여러 스레드가 이러한 공유 자원에 동시에 접근하여 값을 읽거나 수정할 때 &lt;b&gt;경쟁 상태&lt;/b&gt;라는 문제가 발생합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경쟁 상태는 여러 스레드의 실행 순서가 예측 불가능하여, 그 결과가 달라지는 상황을 말합니다.&lt;br /&gt;이는 &lt;b&gt;데이터 불일치&lt;/b&gt;나 논리적 오류를 초래합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(간단한 예시 &amp;rarr; static 변수 counter를 두 스레드가 1씩 증가시키는 상황)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 A가 counter의 현재 값(0)을 읽습니다.&lt;/li&gt;
&lt;li&gt;동시에 스레드 B도 counter의 현재 값(0)을 읽습니다.&lt;/li&gt;
&lt;li&gt;스레드 A는 읽은 값에 1을 더해(0 + 1 = 1) counter에 다시 씁니다.&lt;/li&gt;
&lt;li&gt;스레드 B는 자신이 읽었던 값에 1을 더해 (0 + 1 = 1) counter에 다시 씁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과 적으로 counter의 최종 값은 2가 되어야 하지만, 잘못된 순서로 인해 1이 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이처럼 공유 자원에 접근하는 코드 영역을 &lt;b&gt;임계 구역(Critical Section)&lt;/b&gt;이라고 부르며, 이 구역에 대한 접근은 반드시 제어해야 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동기화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경쟁 상태 문제를 해결하기 위한 방법이 바로 &lt;b&gt;동기화&lt;/b&gt;입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동기화는 여러 스레드가 공유 자원(임계 구역)에 동시에 접근하는 것을 막고, 한 번에 한 스레드만 접근하도록 순서를 제어하는 것을 말합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 동기화를 구현하는 대표적인 도구에 &lt;b&gt;뮤텍스와 세마포어&lt;/b&gt;가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뮤텍스(Mutex)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상호 배제(Mutuql Exclusion)의 약자로 단 하나의 스레드만 임계 구역에 접근할 수 있도록 하는 &lt;b&gt;잠금(Lock)&lt;/b&gt;메커니즘입니다.&lt;/li&gt;
&lt;li&gt;잠금(Lock): 공유 자원을 사용하기 전에 먼저 잠금을 걸어 다른 스레드가 접근하지 못하도록 합니다.&lt;/li&gt;
&lt;li&gt;해제(Unlock): 작업을 마친 후에는 잠금을 풀어 다른 스레드가 사용할 수 있게 합니다.&lt;/li&gt;
&lt;li&gt;소유권: 잠금을 건 스레드만 잠금을 해제할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뮤텍스는 한 명의 사용자만 사용할 수 있는 화장실에 비유할 수 있습니다. 사용자가 화장실에 들어가면 문을 잠그고, 나오면서 문을 열어줍니다. 다른 사람은 문이 열릴 때까지 기다려야 합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세마포어(Semaphore)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공유 자원에 대한 접근을 제어하는 동구로 정수형 변수 하나와 P연산 V연산으로 구성됩니다. 뮤텍스와 달리 여러 개의 스레드가 동시에 접근할 수 있습니다.&lt;/li&gt;
&lt;li&gt;카운팅(Counting): 세마포어의 정수 값은 공유 자원에 접근 가능한 스레드 수를 의미합니다.&lt;/li&gt;
&lt;li&gt;P연산(Waiting): 자원을 사용하려는 스레드는 P연산을 수행하여 세마포어 값을 감소시킵니다. 만약 값이 음수가 되면 해당 스레드는 대기 상태로 전환됩니다.&lt;/li&gt;
&lt;li&gt;V연산(Signal): 자원 사용을 마친 스레드는 V연산을 수행하며 세마포어 값을 증가시킵니다. 대기 중인 스레드가 있다면 하나를 깨워 자원을 사용하게 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세마포어는 여러 개의 좌석이 있는 영화관에 비유할 수 있습니다. 좌석이 남아있다면(세마포어 값 &amp;gt; 0) 여러 명이 동시에 입장 가능하고, 좌석이 모두 차면(세마포어 값 &amp;lt;= 0) 다음 손님은 좌석이 생길 때까지 기다려야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뮤텍스는 세마포어의 특수한 형태로 볼 수 있습니다. 세마포어의 값이 1인 이진 세마포어(Binary Semaphore)는 뮤텍스와 동일한 역할을 합니다. 하지만 일반적으로는 단일 자원에는 뮤텍스, 여러 개의 자원에는 세마포어를 사용합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GC와 static 변수의 관계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;static 변수는 JVM의 메서드 영역에 할당됩니다. 이 영역은 프로그램이 시작될 때 메모리에 로드되어 프로그램이 종료될 때까지 메모리에 남아있습니다. 이러한 static 변수는 애플리케이션의 생명주기와 같기 때문에, GC의 관리 대상이 아닙니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 new키워드로 힙 영역에 생성된 객체는 해당 객체에 대한 유효한 참조가 사라지면 GC가 이를 감지하여 메모리를 회수합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 특성 때문에 static 변수를 남발하면 다음과 같은 문제가 발생할 수 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리 누수
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;static 변수가 힙 영역의 객체를 참조하고 있는 한, 해당 객체는 애플리케이션이 종료될 때까지 GC의 대상이 되지 않습니다. 만약 이 객체가 불필요한 데이터를 계속 축적하면 메모리가 점진적으로 부족해지는 &lt;b&gt;메모리 누수&lt;/b&gt;가 발생할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공유 자원 문제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;static 변수는 모든 스레드가 공유하는 자원으로, 멀티스레드 환경에서 동기화 처리를 제대로 하지 않으면 경쟁 상태에 빠질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 static 변수는 프로그램 전반에 걸쳐 공유되어야 하는 상수나 단일 객체(싱글턴)를 정의할 때만 신중하게 사용하는 것이 좋습니다.&lt;/p&gt;</description>
      <category>CS 및 면접복기</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/38</guid>
      <comments>https://sunyerim.tistory.com/38#entry38comment</comments>
      <pubDate>Fri, 12 Sep 2025 15:47:32 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Authentication &amp;amp; Authorization</title>
      <link>https://sunyerim.tistory.com/37</link>
      <description>&lt;p&gt;이전에 Spring Boot 프로젝트를 진행하면서는 HTTP 헤더의 Authorization 토큰을 직접 파싱해 사용자 정보를 가져오곤 했습니다.&lt;/p&gt;
&lt;p&gt;Spring Security를 사용한다면 컨트롤러 메서드에 Authentication 객체를 파라미터로 주입받아 사용할 수 있습니다.&lt;/p&gt;
&lt;h3&gt;1. Authorization헤더 vs. Authentication 파라미터&lt;/h3&gt;
&lt;p&gt;두 방식의 가장 큰 차이점은 &lt;strong&gt;인증 정보 처리의 책임이 어디에 있느냐&lt;/strong&gt;입니다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 직접 Authorization 헤더를 다루는 방식
@RestController
@RequestMapping(&amp;quot;/api/friends&amp;quot;)
public class FriendControllerV1 {
    // ... (의존성 주입)

    @GetMapping(&amp;quot;/search&amp;quot;)
    public ResponseEntity&amp;lt;List&amp;lt;UserSearchResponse&amp;gt;&amp;gt; searchUsers(
        @RequestParam String keyword,
        @RequestHeader(&amp;quot;Authorization&amp;quot;) String token) {

        // 1. 토큰 유효성 검증 로직 (만료, 변조 등)
        if (!isValidToken(token)) {
            return ResponseEntity.status(401).build();
        }

        // 2. 토큰에서 사용자 ID 추출
        Long currentUserId = extractUserIdFromToken(token);

        // 3. 비즈니스 로직 호출
        List&amp;lt;User&amp;gt; users = friendService.searchUsers(keyword, currentUserId);

        // ...
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;컨트롤러가 인증과 비즈니스 로직이라는 두 가지 역할을 동시에 수행하게됩니다. 토큰 처리 로직이 모든 컨트롤러 메서드에 반복되거나, 별도의 유틸리티 클래스로 분리해야 하는 번거로움이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Spring Security의 Authentication 파라미터를 사용하는 방식
@RestController
@RequestMapping(&amp;quot;/api/friends&amp;quot;)
public class FriendControllerV2 {

    // ... (의존성 주입)

    @GetMapping(&amp;quot;/search&amp;quot;)
    public ResponseEntity&amp;lt;List&amp;lt;UserSearchResponse&amp;gt;&amp;gt; searchUsers(
        @RequestParam String keyword,
        Authentication authentication) { // Spring이 인증된 사용자 정보를 자동으로 주입

        // 1. 인증된 사용자 ID를 바로 사용
        Long currentUserId = (Long) authentication.getPrincipal();

        // 2. 비즈니스 로직 호출 (깔끔해짐)
        List&amp;lt;User&amp;gt; users = friendService.searchUsers(keyword, currentUserId);

        // ...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;컨트롤러는 이미 인증이 완료되었다는 것을 신뢰하고 &lt;strong&gt;비즈니스 로직에만 집중&lt;/strong&gt;할 수 있습니다. 토큰 처리, 유효성 검증 같은 부가적인 관심사가 완전히 분리되어 코드가 간결해지고 유지보수성이 높아집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;결론적으로, Spring Security 환경에서는 &lt;code&gt;Authentication&lt;/code&gt; 파라미터를 사용하는 것이 &lt;strong&gt;객체지향 원칙과 관심사 분리&lt;/strong&gt; 측면에서 훨씬 더 좋은 설계라고 생각합니다.&lt;/p&gt;</description>
      <category>개발관련</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/37</guid>
      <comments>https://sunyerim.tistory.com/37#entry37comment</comments>
      <pubDate>Mon, 8 Sep 2025 15:11:18 +0900</pubDate>
    </item>
    <item>
      <title>20250829</title>
      <link>https://sunyerim.tistory.com/36</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DDD에서 aggregate란?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Aggregate는 도메인 주도 설계에서 핵심적인 개념 중 하나로, 연관된 도메인 객체들을 하나의 단위로 묶는 일관성 경계를 의미합니다.&lt;/li&gt;
&lt;li&gt;(예를 들어 &amp;lsquo;주문&amp;rsquo; 도메인을 모델링할 때 주문 자체와 그에 속한 주문 항목들을 함께 다루는 경우가 많습니다. 이럴 때 &amp;lsquo;주문&amp;rsquo;이 Aggregate Root가 되고, &amp;lsquo;주문 항목들&amp;rsquo;은 그 내부의 구성요소가 됩니다.&lt;/li&gt;
&lt;li&gt;중요한 점은 외부에서는 반드시 Aggregate Root를 통해서만 내부 객체에 접근하거나 조작해야 한다는 것입니다. 이렇게 함으로써 도메인의 불변 조건을 보장하고, 트랜잭션의 범위를 Aggregate 단위로 제한할 수 있어 시스템의 일관성을 유지할 수 있습니다.)&lt;/li&gt;
&lt;li&gt;즉, 복잡한 도메인 모델을 관리 가능하게 만들고, 도메인의 무결성을 보장하는 데 중요한 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;msa랑 모놀리식 설명
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모놀리식은 모든 기능이 하나의 통합된 애플리케이션으로 구성된 구조입니다.&lt;/li&gt;
&lt;li&gt;장점은 초기 개발과 배포가 단순하고 컴포넌트 간 호출이 내부 메서드 호출로 빠르다는 점입니다. 그러나 규모가 커질수록 코드가 복잡해지고, 일부 기능 수정이나 배포가 전체 시스템에 영향을 주는 문제가 있습니다.&lt;/li&gt;
&lt;li&gt;MSA는 시스템을 작고 독립적인 서비스들로 나누어 개발하는 방식입니다. 각 서비스는 독립적으로 배포되고 확장 가능하며, 자체 DB와 로직을 가집니다.&lt;/li&gt;
&lt;li&gt;장점은 서비스 단위의 빠른 배포, 장애 격리, 기술 스택의 다양성 등 여러 장점이 있지만, 반대로 서비스 간 통신 비용이 증가하고, 분산 환경에서 데이터 정합성이나 트랜잭션 관리가 어려워질 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;msa서비스 구분 어떤식으로 했나요?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서비스 구분 기준을 &amp;lsquo;도메인&amp;rsquo; 기반으로 했고, 구체적으로는 사용자에게 제공되는 핵심 기능 단위로 나눴습니다.&lt;/li&gt;
&lt;li&gt;구체적으로는 멀티 게임 보드, 싱글 게임 모드, 회원 관리, 랭킹 관리 등 사용자가 실제로 접하는 핵심 기능 단위로 서비스를 나눴습니다.&lt;/li&gt;
&lt;li&gt;장점은 각 기능의 요구사항이 명확하게 구분되기 때문에, 서비스 간 책임이 모호해지지 않고, 각각의 서비스가 독립적으로 개발 및 배포될 수 있다는 점입니다.&lt;/li&gt;
&lt;li&gt;각 서비스는 자체 DB를 가지며, 통신은 RESTful API 기반으로 이뤄졌습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 정합성 어떻게 관리했나요?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(MSA 도입 처음이었고, 일정상 기능 구현과 MVP 완성이 우선 과제였기에 체계적으로 대응하지는 못했다.)&lt;/li&gt;
&lt;li&gt;서비스 간 데이터 중복이나 비동기 처리 시점에서 발생할 수 있는 정합성 문제는 인지하고 있었으나, 기능 단위 완성과 배포에 집중했습니다.&lt;/li&gt;
&lt;li&gt;(이후에 이 부분 개선을 위해 CQRS 패턴과 이벤트 기반 아키텍처에 대해 찾아보았다.&lt;/li&gt;
&lt;li&gt;예를 들어, 사용자 점수 변경이나 랭킹 갱신의 경우 이벤트 발행과 소비를 통해 eventual consistency를 확보하는 방안을 고려해볼 수 있었음.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;왜 kafka를 선택했나요? rabbitmq나 redis pub/sub도 있는데
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Kafka를 선택한 이유는 사용자의 데이터 안정성 확보가 가장 중요한 고려사항이었기 때문입니다.&lt;/li&gt;
&lt;li&gt;RabbitMQ는 기본적으로 온메모리 방식으로 동작하기에 장애나 재시작 시 메시지 휘발 가능성이 존재합니다. (durable 옵션을 설정하면 디스크 기반으로도 사용가능하지만, 처리량과 지연 측면에서 불리한 부분도 있음.)&lt;/li&gt;
&lt;li&gt;Kafka는 기본적으로 디스크 기반 구조로 동작하며, 메시지를 일정 기간 동안 보존할 수 있고, 컨슈머 그룹 기반 메시지 처리 모델을 통해 대용량 처리나 재처리에도 유리합니다.&lt;/li&gt;
&lt;li&gt;(실제로 게임 결과 데이터를 안정적으로 수집하고, 이를 기반으로 랭킹 업데이트나 사용자 기록 저장을 하기 위해 Kafka 기반의 비동기 이벤트 처리 아키텍처로 전환하게 되었습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다형성이란?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체지향의 핵심 개념 중 하나로, 같은 메시지를 서로 다른 객체가 다르게 응답할 수 있는 특징을 말합니다.&lt;/li&gt;
&lt;li&gt;Animal이라는 부모 클래스에 speak() 라는 케서드가 있을 때, Dog, Cat 같은 자식 클래스들이 이를 각각 &amp;lsquo;멍멍&amp;rsquo;, &amp;lsquo;야옹&amp;rsquo;으로 구현하면, 동일한 speak() 호출이 객체 타입에 따라 다르게 동작합니다.&lt;/li&gt;
&lt;li&gt;코드의 확장성과 유연성이 높아져서, 새로운 동물 클래스가 추가되더라도 기존 코드를 수정하지 않고 사용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;응집도, 결합도 &amp;rarr; 스프링 개발시 어떤 상황에 적용되는 거 같은지 편하게 설명해보세요.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응집도는 하나의 클래스나 모듈이 얼마나 하나의 책임에 집중하고 있는지를 나타내는 지표&lt;/li&gt;
&lt;li&gt;결합도는 다른 클래스나 모듈에 얼마나 강하게 의존하는지를 나타내는 지표&lt;/li&gt;
&lt;li&gt;응집도는 기능 단위로 책임을 분리하고 각 모듈이 자신의 역할만 명확히 하도록 구성, 결합도는 di나 인터페이스 추상화를 통해 낮추는 방향으로 구조를 설계.&lt;/li&gt;
&lt;li&gt;유지보수성과 테스트 효율이 높아지고 기능 변경 시 영향 범위를 최소화할 수 있다는 장점이 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;nginx왜 썼나요? apache도 있는데?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(Nginx는 비동기 이벤트 구조를 가지고 있어, 동시에 많은 요청을 처리하는 데 Apache보다 효율적입니다. Apache는 쓰레드 기반이라 연결 수가 많아질 수록 리소스 사용이 급증하는 반면, Nginx는 가벼운 구조 덕분에 고성능, 고병렬 처리에 유리했습니다.&lt;/li&gt;
&lt;li&gt;트래픽 처리 효율이 중요했기 때문에, 보다 나은 성능과 낮은 리소스 소비, 그리고 설정의 유연성 측면에서 Nginx를 선택했습니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;nginx로 어떤 역할을 처리했나요?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순한 웹서버가 아닌 여러 기능을 통합 처리하는 게이트웨이 레벨을 역할로 활용&lt;/li&gt;
&lt;li&gt;리버스 프록시 &amp;rarr; 클라이언트 요청을 내부 API 서버로 전달&lt;/li&gt;
&lt;li&gt;서버 분산을 위해 로드밸런싱 기능도 적용&lt;/li&gt;
&lt;li&gt;정적 리소스는 Nginx가 직접 서빙하면서 캐싱 처리를 통해 응답 속도 개선&lt;/li&gt;
&lt;li&gt;로그인 이후 특정 API 접근 제어를 위해 Lua 스크립트로 인증, 인가 로직을 구현.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GC란?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JVM에서 더 이상 사용되지 않는 객체를 자동으로 탐지하고 메모리를 관리하는 것.&lt;/li&gt;
&lt;li&gt;Java는 명시적으로 메모리를 해제하지 않아도 되는데, 그 이유가 바로 GC 덕분입니다. GC는 힙 메모리 영역에서 참조되지 않는 객체들을 찾아 제거함으로써, 메모리 누수나 OutOfMemoryError를 방지하는 역할을 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;http vs https ?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP와 HTTPS의 가장 큰 차이는 보안 계층 (SSL/TLS)의 적용 여부입니다.&lt;/li&gt;
&lt;li&gt;HTTP는 데이터를 암호화하지 않고 평문으로 전송하기 때문에, 중간에 네트워크를 가로채면 요청 내용이 그대로 노출될 수 있습니다.&lt;/li&gt;
&lt;li&gt;반면 HTTPS는 TLS(Transport Layer Security)를 통해 클라이언트와 서버 간 데이터를 암호화하여 전송하기 때문에, 도청, 위조, 변조에 강합니다.&lt;/li&gt;
&lt;li&gt;또한 HTTPS는 서버의 디지털 인증서 (SSL 인증서)를 통해 사용자가 접속한 서버가 신뢰할 수 있는지 검증하는 과정도 포함되어 있어, 피싱 사이트 예방에도 효과적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;https://www.naver.com&lt;/code&gt;접속하면 일어나는 일
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;크게 네 단계로 나누어 설명&lt;/li&gt;
&lt;li&gt;DNS 조회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사용자가 &lt;code&gt;https://www.naver.com&lt;/code&gt;을 입력하면, 브라우저는 먼저 로컬 캐시를 확인하고, 없다면 순차적으로 Root &amp;rarr; TLD &amp;rarr; Authoritative DNS 서버를 통해 IP 주소를 얻습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TCP 연결 및 TLS/SSL Handshake
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;IP를 확보한 뒤 서버와 TCP 3-way Handshake로 연결을 맺고, 이어 TLS Handshake를 통해 서버 인증서 검증 및 세션 키 교환을 통해 보안 채널을 생성합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTP 요청 및 응답
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통신 채널이 만들어지면 브라우저는 서버에 HTTP GET 요청을 보냅니다.&lt;/li&gt;
&lt;li&gt;이 요청에는 브라우저 정보, 쿠키, 그리고 원하는 리소스 정보 등이 담겨 있습니다. 서버는 요청을 받아 처리한 후, 200 OK와 같은 상태 코드를 포함한 HTTP 응답으로 HTML, CSS, JavaScript 파일 등을 클라이언트에게 전송합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;브라우저 랜더링
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;받은 HTML은 DOM 트리로, CSS는 CSSOM 트리로 파싱되며, 이를 통해 렌더 트리를 만든 뒤 실제 픽셀을 화면에 그려서 화면에 그려집니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(OSI 7계층 위주의 설명으로도 정리해두기)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;gc안쓰는 프로그래밍 적용 산업은 왜 그런 언어를 굳이 사용하는지? 왜 그렇게 생각하는지? (방산이나 뭐 로켓 등 산업에서)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;(GC 없는 언어(C/C++)를 사용하는 산업은 실시간성, 예측 가능한 메모리 제어, 성능 보장이 중요한 분야입니다.&lt;/li&gt;
&lt;li&gt;예를 들어 방산, 항공, 로켓 산업처럼 한 치의 지연이나 오작동도 허용되지 않는 시스템에서는 GC에 의한 예기치 않은 성능 저하조차 위험 요소가 될 수 있습니다.&lt;/li&gt;
&lt;li&gt;그래서 수동으로 메모리를 제어할 수 있는 언어를 선택하는 것이고, 안정성과 신뢰성이 최우선인 분야 특성에 부합한다고 생각합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(etc)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WS, WAS
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Web Server, Web Application Server&lt;/li&gt;
&lt;li&gt;WS는 정적 리소스, 예를 들면 HTML, CSS, 이미지 등을 클라이언트에 전달하는 역할을 하고&lt;/li&gt;
&lt;li&gt;WAS는 동적인 처리가 필요한 요청, 예를 들어 DB 조회, 로직 실행과 같은 서버 사이드 처리를 담당합니다.&lt;/li&gt;
&lt;li&gt;보통 Nginx나 Apache 같은 WS가 클라이언트 요청을 먼저 받고, 내부적으로 WAS로 요청을 전달해 처리한 뒤 응답을 반환하는 구조로 함게 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체지향 5원칙 중 2개 설명
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SOLID&lt;/li&gt;
&lt;li&gt;단일 책임 원칙(SRP) : 하나의 클래스는 하나의 책임만 가져야한다는 원칙입니다. 즉, 클래스가 하나의 변경 이유만 가져야 한다는 뜻으로, 유지보수성과 응집도를 높이는 데 기여합니다.&lt;/li&gt;
&lt;li&gt;개방-폐쇄 원칙(OCP): 소프트웨어 구성 요소는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다는 원칙입니다. 기능을 추가할 땐 기존 코드를 수정하지 않고, 새로운 클래스를 추가하거나 기존 인터페이스를 구현해서 유연하게 확장할 수 있어야 한다는 의미입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;abstract vs interface
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공통적으로 추상화를 위해 사용된다.&lt;/li&gt;
&lt;li&gt;abstract 클래스는 공통된 상태와 기본 구현을 함께 제공할 수 있다. 일부 메서드는 구현하고 일부는 추상 메서드로 넘겨 하위 클래스가 구현하게 할 수 있다.&lt;/li&gt;
&lt;li&gt;interface는 행위 중심의 계약을 정의할 때 사용하며 원래는 메서드 선언만 가능했지만, (java 8 이후로는 default 메서드나 static 메서드도 정의할 수 있게 되었다.)&lt;/li&gt;
&lt;li&gt;가장 큰 차이는 상속 구조입니다. 자바는 클래스는 단일 상속만 가능하지만, 인터페이스는 다중 구현이 가능하기 때문에, 여러 역할을 조합해야 할 때는 인터페이스가 더 유리합니다.&lt;/li&gt;
&lt;li&gt;(인터페이스는 기능에 대한 &lt;b&gt;계약만 정의&lt;/b&gt;하고, 실제 구현은 전적으로 구현 클래스에서 맡는 구조입니다. 반면 추상 클래스는 &lt;b&gt;공통된 로직과 상태를 구현해두고&lt;/b&gt;, 하위 클래스는 필요한 부분만 오버라이딩해서 쓰는 방식입니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난주부터 갑자기 폭풍전야 같은 시간을 보내서 블로그 글 쓸 시간도 없었고 프로젝트 개발도 멈춰있었는데 ...&lt;br /&gt;이제 좀 숨 돌릴틈이 생겨서 바로 복기 !!!!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 회사가 도메인이나 성격이 너무 다르기도 했지만 .. 일단 !&lt;br /&gt;경험 기반으로 질문 들어왔던 회사 제외, 기술 질문 복기 기록만 올려봅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 언제 어디서 기회가 생길지 모른다 .. 부족한 건 인지하고 꾸준히 준비하자  &lt;/p&gt;</description>
      <category>CS 및 면접복기</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/36</guid>
      <comments>https://sunyerim.tistory.com/36#entry36comment</comments>
      <pubDate>Fri, 29 Aug 2025 16:02:11 +0900</pubDate>
    </item>
    <item>
      <title>20250820 - 디자인패턴(2)</title>
      <link>https://sunyerim.tistory.com/35</link>
      <description>&lt;h3&gt;이터레이터 패턴&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmveIh/btsPZZQ8u8J/EVQ6SdfofrdZrNapsbVLYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmveIh/btsPZZQ8u8J/EVQ6SdfofrdZrNapsbVLYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmveIh/btsPZZQ8u8J/EVQ6SdfofrdZrNapsbVLYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmveIh%2FbtsPZZQ8u8J%2FEVQ6SdfofrdZrNapsbVLYK%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&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;즉시 실행 함수를 통해 private, public 같은 접근 제어자를 만드는 패턴 (js)&lt;/li&gt;
&lt;li&gt;public: 클래스에 정의된 함수에서 접근 가능하며 자식 클래스와 외부 클래스에서 접근 가능한 범위&lt;/li&gt;
&lt;li&gt;protected: 클래스에서 정의된 함수에서 접근 가능, 자식 클래스에서 접근 가능하지만 외부 클래스에서 접근 불가능한 범위&lt;/li&gt;
&lt;li&gt;private: 클래스에 정의된 함수에서 접근 가능하지만 자식 클래스와 외부 클래스에서 접근 불가능한 범위&lt;/li&gt;
&lt;li&gt;즉시 실행 함수: 함수를 정의하자마자 바로 호출하는 함수. 초기화 코드, 라이브러리 내 전역 변수의 충돌 방지 등에 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;MVC 패턴&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RuwwR/btsP1u3Cx5f/QOMoKes8ffjfAFSt3zRLwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RuwwR/btsP1u3Cx5f/QOMoKes8ffjfAFSt3zRLwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RuwwR/btsP1u3Cx5f/QOMoKes8ffjfAFSt3zRLwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRuwwR%2FbtsP1u3Cx5f%2FQOMoKes8ffjfAFSt3zRLwk%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model, View, Controller&lt;/strong&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;ul&gt;
&lt;li&gt;애플리케이션의 데이터인 데이터베이스, 상수, 변수 등을 뜻한다.&lt;/li&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;li&gt;모델이 가지고 있는 정보를 따로 저장하지 않아야 하며 단순히 화면에 표시하는 정보만 가지고 있어야 한다.&lt;/li&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;li&gt;모델과 뷰의 생명주기도 관리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MVC 패턴을 이용한 대표적인 프레임워크 &lt;strong&gt;Spring&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;MVP 패턴&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chcSgI/btsP0RLMKcn/SEZRiPCFfByIYs07r6KYK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chcSgI/btsP0RLMKcn/SEZRiPCFfByIYs07r6KYK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chcSgI/btsP0RLMKcn/SEZRiPCFfByIYs07r6KYK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchcSgI%2FbtsP0RLMKcn%2FSEZRiPCFfByIYs07r6KYK1%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MVC 패턴으로부터 파생되었으며 C가 presenter로 교체된 패턴이다.&lt;/li&gt;
&lt;li&gt;뷰와 프레젠터는 일대일 관계이기 때문에 MVC 패턴보다 더 강한 결합을 지닌 디자인패턴이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;MVVM 패턴&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzcEQF/btsP1tRaU61/0GUKcTsmKyBRgzJKPkHlr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzcEQF/btsP1tRaU61/0GUKcTsmKyBRgzJKPkHlr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzcEQF/btsP1tRaU61/0GUKcTsmKyBRgzJKPkHlr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzcEQF%2FbtsP1tRaU61%2F0GUKcTsmKyBRgzJKPkHlr0%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MVC의 C에 해당하는 컨트롤러가 뷰모델로 바뀐 패턴&lt;/li&gt;
&lt;li&gt;뷰모델은 뷰를 더 추상화한 계층이며, MVC 패턴과는 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징이다.&lt;/li&gt;
&lt;li&gt;뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 코드 수정없이 재사용할 수 있고 단위 테스팅하기 쉽다는 장점이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;*출처: 면접을위한CS전공지식노트&lt;/p&gt;</description>
      <category>CS 및 면접복기</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/35</guid>
      <comments>https://sunyerim.tistory.com/35#entry35comment</comments>
      <pubDate>Wed, 20 Aug 2025 21:49:59 +0900</pubDate>
    </item>
    <item>
      <title>20250818 - 디자인 패턴</title>
      <link>https://sunyerim.tistory.com/34</link>
      <description>&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;li&gt;하나의 인스턴스를 만들어 놓고 해당 인스턴스를 다른 모듈들이 공유하며 사용하기 때문에 인스턴스를 생성할 때 드는 비용이 줄어드는 장점이 있지만, 의존성이 높아진다는 단점도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[단점]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TDD를 할 때 걸림돌이 된다.&lt;/li&gt;
&lt;li&gt;싱글톤 패턴은 미리 생성된 하나의 인스턴스를 기반으로 구현하는 패턴이므로 각 테스트마다 ‘독립적인’ 인스턴스를 만들기가 어렵다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[의존성 주입]&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLI2hQ/btsPUjbK4rc/wXg6ekUh8l2VkA9ZPYWKMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLI2hQ/btsPUjbK4rc/wXg6ekUh8l2VkA9ZPYWKMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLI2hQ/btsPUjbK4rc/wXg6ekUh8l2VkA9ZPYWKMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLI2hQ%2FbtsPUjbK4rc%2FwXg6ekUh8l2VkA9ZPYWKMK%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;싱글톤 패턴은 사용하기가 쉽지만 모듈 간의 결합을 강하게 만들 수 있다는 단점이 있는데, 이때 &lt;strong&gt;의존성 주입(DI)을&lt;/strong&gt; 통해 모듈 간의 결합을 조금 더 느슨하게 만들어 해결할 수 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;*의존성: 종속성이라고도 하며 A가 B에 의존성이 있다는 것을 B의 변경 사항에 대해 A 또한 변해야 된다는 것을 의미&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;li&gt;&lt;p&gt;모듈들이 더욱더 분리되므로 클래스 수가 늘어나 복잡성이 증가될 수 있으며 약간의 런타임 패널티가 생길수도 있다는 단점이 있다.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;“상위 모듈은 하위 모듈에서 어떠한 것도 가져오지 않아야 한다. 또한, 둘 다 추상화에 의존해야 하며, 추상화는 세부 사항에 의존하지 말아야 한다.”&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;팩토리 패턴&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dbiQS5/btsPUlgNxe1/vGtKxKu1pkuu81wpj5bD0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dbiQS5/btsPUlgNxe1/vGtKxKu1pkuu81wpj5bD0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dbiQS5/btsPUlgNxe1/vGtKxKu1pkuu81wpj5bD0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdbiQS5%2FbtsPUlgNxe1%2FvGtKxKu1pkuu81wpj5bD0k%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;객체를 사용하는 코드에서 객체 생성 부분을 떼어내 추상화한 패턴이자 상속 관계에 있는 두 클래스에서 상위 클래스가 중요한 뼈대를 결정하고, 하위 클래스에서 객체 생성에 관한 구체적인 내용을 결정하는 패턴.&lt;/li&gt;
&lt;li&gt;상위 클래스와 하위 클래스가 분리되기에 느슨한 결합을 가지며 상위 클래스에서는 인스턴스 생성 방식에 대해 전혀 알 필요가 없기에 더 많은 유연성을 갖게된다.&lt;/li&gt;
&lt;li&gt;ex. 라떼 레시피, 우유 레시피라는 구체적인 내용이 들어있는 하위 클래스가 컨베이어 벨트를 통해 전달, 상위 클래스인 바리스타 공장에서 이 레시피들을 토대로 우유 등을 생산하는 생산 공정을 생각.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;&lt;strong&gt;전략 패턴&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dxJAp5/btsPTPBYfCJ/iRKE8Ot5JwUFyk8lOY4oT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dxJAp5/btsPTPBYfCJ/iRKE8Ot5JwUFyk8lOY4oT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dxJAp5/btsPTPBYfCJ/iRKE8Ot5JwUFyk8lOY4oT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdxJAp5%2FbtsPTPBYfCJ%2FiRKE8Ot5JwUFyk8lOY4oT0%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;정책 패턴이라고도 하며, 객체의 행위를 바꾸고 싶은 경우 ‘직접’ 수정하지 않고 전략이라고 부르는 ‘캡슐화한 알고리즘’을 컨텍스트 안에서 바꿔주면서 상호 교체가 가능하게 만드는 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;옵저버 패턴&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/baEOyn/btsPUSSpJf1/P6CkPaCM9sz3g0bbgb6Tu1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/baEOyn/btsPUSSpJf1/P6CkPaCM9sz3g0bbgb6Tu1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/baEOyn/btsPUSSpJf1/P6CkPaCM9sz3g0bbgb6Tu1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbaEOyn%2FbtsPUSSpJf1%2FP6CkPaCM9sz3g0bbgb6Tu1%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIst4Y/btsPUXl7yX0/m47dnFmQrSYACf5iBwkv31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIst4Y/btsPUXl7yX0/m47dnFmQrSYACf5iBwkv31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIst4Y/btsPUXl7yX0/m47dnFmQrSYACf5iBwkv31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIst4Y%2FbtsPUXl7yX0%2Fm47dnFmQrSYACf5iBwkv31%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;주체가 어떤 객체의 상태 변화를 관찰하다가 상태 변화가 있을 때마다 메서드 등을 통해 옵저버 목록에 있는 옵저버들에게 변화를 알려주는 디자인 패턴&lt;/li&gt;
&lt;li&gt;주체란 객체의 상태 변화를 보고 있는 관찰자이며, 옵저버들이란 이 객체의 상태 변화에 따라 전달되는 메서드 등을 기반으로 ‘추가 변화 사항’이 생기는 객체들을 의미한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oXiBv/btsPUPBqiZ9/PgcmuzdaheQRe9yexmgiV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oXiBv/btsPUPBqiZ9/PgcmuzdaheQRe9yexmgiV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oXiBv/btsPUPBqiZ9/PgcmuzdaheQRe9yexmgiV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoXiBv%2FbtsPUPBqiZ9%2FPgcmuzdaheQRe9yexmgiV0%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;주로 이벤트 기반 시스템에 사용하며 MVC 패턴에도 사용된다.&lt;/li&gt;
&lt;li&gt;ex. 주체라고 볼 수 있는 모델에서 변경 사항이 생겨 update() 메서드로 옵저버인 뷰에 알려주고 이를 기반으로 컨트롤러 등이 작동&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;[자바: 상속과 구현]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;상속(extends): 자식 클래스가 부모 클래스의 메서드 등을 상속받아 사용하며 자식 클래스에서 추가 및 확장을 할 수 있음. 재사용성, 중복성의 최소화가 이뤄짐.&lt;/li&gt;
&lt;li&gt;구현(implements): 부모 인터페이스를 자식 클래스에서 재정의하여 구현하는 것을 말하며, 상속과는 달리 반드시 부모 클래스의 메서드를 재정의하여 구현해야함.&lt;/li&gt;
&lt;li&gt;상속과 구현의 차이: 상속은 일반클래스, abstract 클래스를 기반으로 구현하며, 구현은 인터페이스를 기반으로 구현한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;프록시 패턴&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/deU0f9/btsPURlH12W/CdcwHHkxoYkpgNY0anivd1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/deU0f9/btsPURlH12W/CdcwHHkxoYkpgNY0anivd1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/deU0f9/btsPURlH12W/CdcwHHkxoYkpgNY0anivd1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdeU0f9%2FbtsPURlH12W%2FCdcwHHkxoYkpgNY0anivd1%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;대상 객체에 접근하기 전 그 접근에 대한 흐름을 가로채 해당 접근을 필터링하거나 수정하는 등의 역할을 하는 계층이 있는 디자인 패턴&lt;/li&gt;
&lt;li&gt;객체의 속성, 변환 등을 보완하며 보안, 데이터 검증, 캐싱, 로깅에 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;프록시 서버&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버와 클라이언트 사이에서 클라이언트가 자신을 통해 다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나, 응용 프로그램&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;프록시 서버로 쓰이는 nginx&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; width=&quot;50%&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkzGsZ/btsPWrT6Sez/VbltFmcwtFm2wPjPJprkS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkzGsZ/btsPWrT6Sez/VbltFmcwtFm2wPjPJprkS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkzGsZ/btsPWrT6Sez/VbltFmcwtFm2wPjPJprkS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkzGsZ%2FbtsPWrT6Sez%2FVbltFmcwtFm2wPjPJprkS1%2Fimg.png&quot; width=&quot;50%&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비동기 이벤트 기반의 구조와 다수의 연결을 효과적으로 처리 가능한 웹 서버이며, 주로 Node.js 서버 앞단의 프록시 서버로 활용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Q. 싱글톤 패턴에 대해서 설명&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전역 변수를 사용하지 않고 객체를 하나만 생성하도록 하며, 생성된 개체를 어디에서든지 참조할 수 있도록 하는 패턴입니다.&lt;/li&gt;
&lt;li&gt;하나의 인스턴스만을 생성하며 getInstance메서드로 모든 클라이언트에게 동일한 인스턴스를 반환&lt;/li&gt;
&lt;li&gt;private 생성자를 가지는 특징을 가지며, 생성된 싱글톤 오브젝트는 저장할 수 있는 자신과 같은 타입의 static 필드를 정의한다.&lt;/li&gt;
&lt;li&gt;싱글톤은 stateless로 설계해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Q. 전략 패턴에 대해서 설명&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A&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;객체지향 설계의 SOLID 원칙 중 개방 폐쇄 원칙 (OCP)에 부합한 패턴이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;출처&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;면접을 위한 CS 전공지식 노트&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ksundong/backend-interview-question&quot;&gt;https://github.com/ksundong/backend-interview-question&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS 및 면접복기</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/34</guid>
      <comments>https://sunyerim.tistory.com/34#entry34comment</comments>
      <pubDate>Mon, 18 Aug 2025 14:53:19 +0900</pubDate>
    </item>
    <item>
      <title>Session, JWT, OAuth</title>
      <link>https://sunyerim.tistory.com/33</link>
      <description>&lt;p&gt;(20250812 cs정리 대신)&lt;/p&gt;
&lt;h3&gt;인증과 인가&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;인증(Authentication)&lt;/strong&gt;: ‘너는 누구인가?’를 확인하는 과정입니다. 사용자가 ‘자신이 누구인지 증명하는 것’ 입니다.&lt;ul&gt;
&lt;li&gt;비유: 회사의 출입문에 달린 지문 인식기나 카드 리더기에 본인의 지문을 찍거나 사원증을 태그하는 행위. 시스템은 ‘이 사람이 A 회사의 직원 김아무개가 맞다’는 것을 확인합니다.&lt;/li&gt;
&lt;li&gt;예시: 아이디와 비밀번호를 입력해 로그인하는 과정&lt;/li&gt;
&lt;li&gt;ErrorCode : 401&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;인가(Authorization)&lt;/strong&gt;: ‘너는 무엇을 할 수 있는가?’를 확인하는 과정입니다. 사용자가 ‘어떤 특정 자원에 접근할 수 있는 권한이 있는지’를 검증하는 것입니다.&lt;ul&gt;
&lt;li&gt;비유: 출입문  통과 후, 김아무개 사원이 자신의 팀 사무실에는 들어갈 수 있지만, 재무팀 사무실에는 들어갈 수 없도록 제한하는 것. 시스템은 ‘김아무개는 개발팀 사무실에 접근할 권한이 있다’는 것을 확인합니다.&lt;/li&gt;
&lt;li&gt;예시: 로그인한 사용자에게만 특정 페이지나 기능을 보여주는 것. 관리자 권한을 가진 사용자에게만 회원 정보 수정 기능을 제공하는 것&lt;/li&gt;
&lt;li&gt;ErrorCode : 403&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;인증이 먼저 이루어져야 인가를 할 수 있습니다. 시스템이 먼저 사용자가 누구인지(인증) 확인한 후에, 그 사용자가 어떤 권한을 가지고 있는지(인가)를 판단하게 됩니다.&lt;/p&gt;
&lt;h3&gt;세션 기반 인증&lt;/h3&gt;
&lt;p&gt;서버가 사용자의 상태를 기억하는 Stateful한 방식입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;동작방식&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트가 로그인 요청을 합니다.&lt;/li&gt;
&lt;li&gt;서버는 사용자의 정보를 검증하고 서버 메모리나 데이터베이스에 고유한 세션 ID를 생성해 저장합니다.&lt;/li&gt;
&lt;li&gt;서버는 생성된 세션 ID를 클라이언트에게 응답으로 보냅니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 이 세션 ID를 쿠키에 저장합니다.&lt;/li&gt;
&lt;li&gt;이후 클라이언트는 매 요청마다 쿠키에 담긴 세션 ID를 서버로 보냅니다.&lt;/li&gt;
&lt;li&gt;서버는 전달받은 세션 ID를 통해 세션 저장소에서 사용자 정보를 찾아 요청을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;보안성: 세션 ID만으로 사용자 정보를 알 수 없으며, 서버에서 세션 파기 시 강제로 로그아웃시킬 수 있어 안전합니다.&lt;/li&gt;
&lt;li&gt;간편한 관리: 서버에서 세션의 만료 시간을 설정하고 관리하기 용이합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;서버 부하: 사용자 수가 많아질수록 서버의 메모리나 DB에 저장해야 할 세션 정보가 늘어나 서버에 부하가 발생합니다.&lt;/li&gt;
&lt;li&gt;확장성 문제: 서버를 여러 대로 확장할 때 세션 정보를 모든 서버가 공유해야 하는 문제가 발생합니다. 이를 해결하기 위해 Redis같은 별도의 세션 저장소를 사용해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;JWT 기반 인증&lt;/h3&gt;
&lt;p&gt;서버가 사용자의 상태를 기억하지 않는 Stateless한 방식입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;동작 방식&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트가 로그인 요청을 합니다.&lt;/li&gt;
&lt;li&gt;서버는 사용자의 정보를 검증한 후, 사용자의 정보를 담은 JWT를 생성합니다.&lt;/li&gt;
&lt;li&gt;서버는 생성된 JWT를 클라이언트에게 응답으로 보냅니다.&lt;/li&gt;
&lt;li&gt;클라이언트는 JWT를 로컬 스토리지나 쿠키에 저장합니다.&lt;/li&gt;
&lt;li&gt;이후 클라이언트는 매 요청마다 &lt;code&gt;Authorization&lt;/code&gt;헤더에 JWT를 담아 서버로 보냅니다.&lt;/li&gt;
&lt;li&gt;서버는 토큰의 서명을 검증하여 유효성을 확인하고, 토큰 내의 정보를 사용해 요청을 처리합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;확장성: 서버가 상태를 저장하지 않으므로 여러 서버로 확장하기 용이합니다.&lt;/li&gt;
&lt;li&gt;CORS 문제 해결: JWT는 헤더에 포함되어 전달되므로 CORS 문제에 유연하게 대처할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;보안성: 토큰이 탈취될 경우, 토큰의 유효 기간이 끝날 때까지 악용될 수 있습니다. 이를 보완하기 위해 Refresh Token을 사용하기도 합니다.&lt;/li&gt;
&lt;li&gt;토큰의 크기: 페이로드에 많은 정보를 담을수록 토큰의 크기가 커져 네트워크 부하가 증가할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;OAuth&lt;/h3&gt;
&lt;p&gt;직접적인 비밀번호 공유 없이 제3자 애플리케이션에 사용자의 권한을 위임하는 인가 프로토콜입니다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;동작방식&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;클라이언트는 리소스 서버(ex. 구글)에 로그인을 요청하는 대신, 인가 서버에 인가 코드를 요청합니다.&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;strong&gt;장점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;보안성: 사용자가 서비스 제공자에게 직접 비밀번호를 알려주지 않아도 되므로, 개인정보 유출 위험을 크게 줄일 수 있습니다.&lt;/li&gt;
&lt;li&gt;편의성: 사용자는 아이디와 비밀번호를 여러 곳에 기억할 필요 없이 소셜 로그인 등으로 간편하게 서비스를 이용할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;단점&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;복잡한 흐름: 세션이나 JWT에 비해 동작 흐름이 복잡하고, 여러 주체가 등장합니다.&lt;/li&gt;
&lt;li&gt;목적의 차이: OAuth는 인증이 아닌 인가를 위한 프로토콜이라는 점을 이해해야 합니다. OAuth를 통해 발급받은 토큰으로 사용자 정보를 가져와서 로그인을 구현하는 것이 일반적입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;기타&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;SSO (Single Sign-On)&lt;ul&gt;
&lt;li&gt;한 번의 로그인으로 여러 개의 다른 애플리케이션이나 서비스에 접속할 수 있게 해주는 인증 방식입니다. 여러 개의 시스템을 운영하는 회사에서 직원들이 각 시스템마다 따로 로그인하는 불편함을 해소하기 위해 주로 사용됩니다.&lt;/li&gt;
&lt;li&gt;ex. 구글 워크스페이스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTP Basic Authentication&lt;ul&gt;
&lt;li&gt;클라이언트가 요청을 보낼 때, Authorization 헤더에 Basic 타입과 함께 아이디 : 비밀번호를 Base64로 인코딩한 값을 담아 보냅니다.&lt;/li&gt;
&lt;li&gt;보안에 매우 취약합니다. 암호화되지 않은 평문 데이터를 Base64로 인코딩만 한 것이라, 중간에 탈취될 경우 바로 노출됩니다. 따라서 HTTPS 환경이 아닌 곳에서는 절대 사용해서 안 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>개발관련</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/33</guid>
      <comments>https://sunyerim.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 12 Aug 2025 17:21:29 +0900</pubDate>
    </item>
    <item>
      <title>20250811 - Network</title>
      <link>https://sunyerim.tistory.com/32</link>
      <description>&lt;p&gt;(클라이언트는 서버에 요청을 전송할 수 있는지, 서버는 클라이언트에게 응답을 전송할 수 있는지 확인하는 과정입니다.)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TCP 3, 4 way handshake&lt;ul&gt;
&lt;li&gt;TCP 3-way handshake는 연결을 설정하는 과정으로, 클라이언트와 서버가 서로 송수신 가능 여부와 초기 시퀀스 번호를 확인하기 위해 3번의 패킷 교환을 합니다.&lt;ul&gt;
&lt;li&gt;클라이언트 → SYN&lt;/li&gt;
&lt;li&gt;서버 → SYN + ACK 응답&lt;/li&gt;
&lt;li&gt;클라이언트 → ACK 전송 → 연결이 성립&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;TCP 4-way handshake는 TCP 연결을 종료하는 과정으로 송신과 수신이 독립적으로 끊어지기 때문에 4번의 패킷 교환이 필요합니다.&lt;ul&gt;
&lt;li&gt;FIN → ACK → FIN → ACK&lt;/li&gt;
&lt;li&gt;마지막에 클라이언트는 TIME_WAIT 상태로 지연 패킷을 방지합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTP, HTTPS&lt;ul&gt;
&lt;li&gt;HTTP와 HTTPS의 차이는 보안 여부입니다.&lt;/li&gt;
&lt;li&gt;HTTP는 데이터를 평문으로 전송하기 때문에 중간에서 패킷을 가로채거나 수정할 수 있습니다.&lt;/li&gt;
&lt;li&gt;반면 HTTPS는 SSL/TLS를 사용해 데이터를 암호화하여 전송하므로, 기밀성과 무결성이 보장됩니다.&lt;/li&gt;
&lt;li&gt;(서버 인증서로 신뢰성을 확인한 뒤, 공개키 암호화로 안전하게 세션키를 교환하고, 이후 대칭키 암호화로 데이터를 주고받아 기밀성과 무결성을 보장합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SSL / TLS&lt;ul&gt;
&lt;li&gt;인터넷 통신을 암호화하고 인증하는 보안 프로토콜.&lt;/li&gt;
&lt;li&gt;TLS는 SSL의 개선·표준화 버전이며, 현재 대부분 HTTPS에서 TLS를 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;세션키&lt;ul&gt;
&lt;li&gt;세션키는 통신 세션동안 데이터 암호화에 사용되는 대칭키입니다.&lt;/li&gt;
&lt;li&gt;HTTPS에서는 공개키 암호화를 사용해 세션키를 안전하게 교환한 뒤, 세션 내 모든 데이터 전송에 세션키를 사용합니다.&lt;/li&gt;
&lt;li&gt;대칭키 암호화는 빠르고 효율적이기 때문에 대용량 데이터 전송에 적합합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTPS에 대해서 설명하고 SSL Handshake에 대해서 설명&lt;ul&gt;
&lt;li&gt;HTTPS는 HTTP에 보안 계층을 추가한 것입니다. HTTPS는 제 3자 인증, 공개키 암호화, 비밀키 암호화를 사용합니다.&lt;/li&gt;
&lt;li&gt;먼저 TCP 3-way handshake로 기본 연결을 맺은 뒤, SSL/TLS Handshake를 진행합니다. 클라이언트는 ‘Client Hello’를 보내고, 서버는 인증서와 암호화 방식 정보를 응답합니다.&lt;/li&gt;
&lt;li&gt;인증서는 신뢰할 수 있는 인증기관이 발급하며, 브라우저는 내장된 공개키로 이를 확인합니다. 이후 서버의 공개키를 이용해 세션키를 안전하게 교환하고, 이 세션키로 데이터를 암호화하며 통신합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;GET, POST&lt;ul&gt;
&lt;li&gt;GET은 서버에서 데이터를 조회할 때 사용하며, URL에 쿼리스트링으로 데이터를 전송하고 캐싱이 가능합니다. 멱등성을 보장해 같은 요청을 반복해도 결과가 같습니다.&lt;/li&gt;
&lt;li&gt;POST는 서버의 데이터를 생성하거나 변경할 때 사용하며, 요청 본문에 데이터를 담아 전송합니다. 기본적으로 캐싱하지 않으며, 멱등성이 없습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;HTTP 메서드와 이것이 하는 역할에 대해서 설명&lt;ul&gt;
&lt;li&gt;GET 요청은 서버에 존재하는 데이터를 요청하는 것입니다. CRUD 중 R입니다.&lt;/li&gt;
&lt;li&gt;POST 요청은 서버에 데이터를 생성하는 것을 요청합니다. CRUD 중 C입니다.&lt;/li&gt;
&lt;li&gt;PUT 요청은 서버에 존재하는 데이터를 수정하거나 존재하지 않으면 생성합니다. CRUD로 따지면 C, U입니다.&lt;/li&gt;
&lt;li&gt;DELETE 요청은 서버에 데이터를 제거할 것을 요청합니다. 존재하지 않아도 동일하게 동작합니다. CRUD로 따지면 D입니다.&lt;/li&gt;
&lt;li&gt;PATCH 요청은 서버에 존재하는 데이터를 일부 수정합니다. CRUD로 따지면 U입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Restful이란?&lt;ul&gt;
&lt;li&gt;REST를 준수해 설계된 API를 의미합니다.&lt;/li&gt;
&lt;li&gt;REST는 자원을 HTTP URI로 표현하고, HTTP 메서드를 통해 해당 자원에 대한 행위를 명확히 구분합니다.&lt;/li&gt;
&lt;li&gt;RESTful API는 무상태성, 일관된 자원 표현, 계층 구조 등의 특징을 가지며, 이를 지키려면 클라이언트와 서버가 독립적으로 확장 가능해집니다.&lt;/li&gt;
&lt;li&gt;단순히 JSON을 반환한다고 해서 RESTful이라고 부르진 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;CORS?&lt;ul&gt;
&lt;li&gt;브라우저에서 서로 다른 출처 간 자원 요청을 허용하는 메커니즘입니다.&lt;/li&gt;
&lt;li&gt;기본적으로 브라우저는 동일 출처 정책 때문에 다른 출처의 요청을 차단합니다.&lt;/li&gt;
&lt;li&gt;서버가 Access-Control-Allow-* 헤더로 허용 범위를 명시하면 요청이 가능합니다. 일부 요청은 본 요청 전에 OPTIONS 메서드로 preflight 요청을 보내 서버가 허용하는 메서드와 헤더를 확인합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS 및 면접복기</category>
      <author>SunYerim</author>
      <guid isPermaLink="true">https://sunyerim.tistory.com/32</guid>
      <comments>https://sunyerim.tistory.com/32#entry32comment</comments>
      <pubDate>Mon, 11 Aug 2025 16:03:30 +0900</pubDate>
    </item>
  </channel>
</rss>