<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Tom's Note</title>
    <link>https://kurukurucoding.tistory.com/</link>
    <description>공뷰를 합시다.</description>
    <language>ko</language>
    <pubDate>Mon, 29 Jun 2026 21:08:40 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>크크크크</managingEditor>
    <image>
      <title>Tom's Note</title>
      <url>https://tistory1.daumcdn.net/tistory/3156237/attach/68cabe5578be40698738979c47694eec</url>
      <link>https://kurukurucoding.tistory.com</link>
    </image>
    <item>
      <title>MySQL Connection.isValid() 최적화</title>
      <link>https://kurukurucoding.tistory.com/159</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;HikariCP에서 Connection.isValid() 이 실행되는 시점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;커넥션 풀에서 커넥션을 꺼낼 때&lt;/b&gt;&lt;br /&gt;정확히는 두 가지 시점이 있습니다:&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 60px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 16.4728%; height: 20px; text-align: center;&quot;&gt;시점&lt;/td&gt;
&lt;td style=&quot;width: 20.3101%; height: 20px; text-align: center;&quot;&gt;조건&lt;/td&gt;
&lt;td style=&quot;width: 63.217%; height: 20px; text-align: center;&quot;&gt;설명&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 16.4728%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;커넥션&lt;/span&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt; 획득 시&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.3101%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;항상&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 63.217%; height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getConnection()&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;호출 시 풀에서 꺼낸 커넥션이 유효한지 검증&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 16.4728%; height: 20px;&quot;&gt;&lt;span style=&quot;background-color: #efefef; color: #333333; text-align: start;&quot;&gt;유휴 커넥션 검사&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20.3101%; height: 20px;&quot;&gt;keepaliveTime&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;설정 시&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 63.217%; height: 20px;&quot;&gt;풀에 놀고 있는 커넥션을 주기적으로 검증 (기본값: 0 = 비활성)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 애플리케이션에서 DB 요청할 때마다 &lt;code&gt;SELECT 1 &amp;rarr; 실제 쿼리&lt;/code&gt; 이렇게 매번 2개의 쿼리가 나가고 있었던 상황.&lt;br /&gt;&lt;code&gt;COM_PING&lt;/code&gt;으로 전환하면 이 검증이 SQL 파서를 거치지 않는 프로토콜 명령으로 대체된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 HikariCP 설정 중 관련된 것들:&lt;/p&gt;
&lt;table style=&quot;height: 98px; width: 856px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 190px; text-align: center;&quot;&gt;설정&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 195px; text-align: center;&quot;&gt;기본값&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 471px; text-align: center;&quot;&gt;역할&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px; width: 190px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;connectionTestQuery&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; width: 195px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;없음 (JDBC4&amp;nbsp;&lt;/span&gt;isValid&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;사용)&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px; width: 471px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;커넥션 유효성 검증 쿼리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 190px;&quot;&gt;&lt;code&gt;validationTimeout&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 195px;&quot;&gt;&lt;code&gt;5000ms&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 471px;&quot;&gt;검증 타임아웃&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 190px;&quot;&gt;&lt;code&gt;keepaliveTime&lt;/code&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 195px;&quot;&gt;&lt;code&gt;0&lt;/code&gt; (비활성)&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 471px;&quot;&gt;유휴 커넥션 주기적 검증 간격&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;connectionTestQuery가 실행되는 시점&lt;/h2&gt;
&lt;table style=&quot;height: 80px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;상황&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;connectionTestQuery&lt;span style=&quot;text-align: center;&quot;&gt;&amp;nbsp;설정됨&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;connectionTestQuery&lt;span style=&quot;text-align: center;&quot;&gt;&amp;nbsp;미설정&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;getConnection()&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;호출 시&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SELECT 1&lt;span style=&quot;text-align: start;&quot;&gt;&amp;nbsp;실행&lt;/span&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;isValid() &amp;rarr; COM_PING&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유휴 커넥션 주기 검사 (&lt;code&gt;keepaliveTime &amp;gt; 0&lt;/code&gt;)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;SELECT 1&lt;/code&gt; 실행&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;isValid() &amp;rarr; COM_PING&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;유휴 커넥션 주기 검사 (&lt;code&gt;keepaliveTime = 0&lt;/code&gt;)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;검사 안 함&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;검사 안 함&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;keepaliveTime&lt;/code&gt;이 &lt;code&gt;0&lt;/code&gt;이든 아니든, &lt;code&gt;getConnection()&lt;/code&gt; 할 때마다 커넥션 유효성 검증은 항상 실행됩니다.&lt;br /&gt;&lt;code&gt;connectionTestQuery&lt;/code&gt;가 설정되어 있으면 매번 &lt;code&gt;SELECT 1&lt;/code&gt;이 나가고, 미설정이면 &lt;code&gt;COM_PING&lt;/code&gt;이 나가는 구조입니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;HikariCP 헬스체크 전환: SELECT 1 &amp;rarr; COM_PING&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 HikariCP의 &lt;code&gt;connectionTestQuery(&quot;SELECT 1&quot;)&lt;/code&gt;을 사용하여 커넥션 유효성을 검증하고 있었다.&lt;br /&gt;이 방식은 커넥션 풀에서 커넥션을 꺼낼 때마다 &lt;code&gt;SELECT 1&lt;/code&gt; 쿼리를 DB에 실행하므로, 불필요한 DB 조회 부하가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조사 결과, HikariCP는 JDBC4 드라이버 사용 시 &lt;code&gt;connectionTestQuery&lt;/code&gt;를 설정하지 않으면&lt;br /&gt;&lt;code&gt;Connection.isValid()&lt;/code&gt;를 호출하고, MySQL Connector/J는 이를 내부적으로 &lt;code&gt;COM_PING&lt;/code&gt; 프로토콜 명령으로 처리한다.&lt;br /&gt;&lt;code&gt;COM_PING&lt;/code&gt;은 SQL 쿼리가 아닌 MySQL 프로토콜 레벨의 경량 명령(1바이트)으로, &lt;code&gt;SELECT 1&lt;/code&gt; 대비 오버헤드가 적다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;변경 내용&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;변경 사항&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;connectionTestQuery(&quot;SELECT 1&quot;)&lt;/code&gt; 설정을 제거(주석 처리)하여&lt;br /&gt;HikariCP 기본 동작인 JDBC4 &lt;code&gt;Connection.isValid()&lt;/code&gt; &amp;rarr; &lt;code&gt;COM_PING&lt;/code&gt; 방식으로 전환했다.&lt;/p&gt;
&lt;pre class=&quot;awk&quot;&gt;&lt;code&gt;// Before
hikariConfig.setConnectionTestQuery(&quot;SELECT 1&quot;);

// After (제거)
// hikariConfig.setConnectionTestQuery(&quot;SELECT 1&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;동작 원리&lt;/h3&gt;
&lt;table style=&quot;height: 130px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;항목&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;SELECT 1 방식&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;COM_PING 방식&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;설정&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;connectionTestQuery(&quot;SELECT 1&quot;)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;설정 없음 (기본값)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;동작&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQL 쿼리 실행&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;MySQL 프로토콜 명령 (&lt;code&gt;0x0e&lt;/code&gt;)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;패킷 크기&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수십 바이트 (SQL 문자열)&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;5바이트 (헤더 4 + 명령 1)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 10px;&quot;&gt;
&lt;td style=&quot;height: 10px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;DB 파서&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 10px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;SQL 파서 경유&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 10px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;프로토콜 레벨 처리&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;HikariCP 조건&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;JDBC4 미지원 드라이버용&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;JDBC4 드라이버 사용 시 기본값&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;환경 정보&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HikariCP: Spring Boot 내장&lt;/li&gt;
&lt;li&gt;MySQL Connector/J: &lt;code&gt;8.0.33&lt;/code&gt; (JDBC4 지원)&lt;/li&gt;
&lt;li&gt;MySQL: &lt;code&gt;8.0.42&lt;/code&gt; (AWS RDS)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;검증&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1단계: profileSQL 로그를 통한 간접 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC URL에 &lt;code&gt;profileSQL=true&lt;/code&gt;를 추가하면 MySQL Connector/J가 실행하는 모든 SQL을 stderr로 출력한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;connectionTestQuery(&quot;SELECT 1&quot;)&lt;/code&gt; 설정 시 &amp;rarr; 로그에 &lt;code&gt;SELECT 1&lt;/code&gt; 출력됨&lt;/li&gt;
&lt;li&gt;&lt;code&gt;connectionTestQuery&lt;/code&gt; 미설정 시 &amp;rarr; 로그에 &lt;code&gt;SELECT 1&lt;/code&gt; 미출력 (SQL이 아닌 &lt;code&gt;COM_PING&lt;/code&gt; 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드:&lt;br /&gt;&lt;code&gt;connector/src/test/java/com/eximbay/datasource/HikariConnectionHealthCheckTest.java&lt;/code&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2단계: tcpdump + Wireshark를 통한 직접 검증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네트워크 패킷 캡처로 &lt;code&gt;COM_PING&lt;/code&gt; (&lt;code&gt;0x0e&lt;/code&gt;) 패킷이 실제로 전송되는 것을 확인했다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;tcpdump 캡처&lt;/h4&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;# 패킷 캡처 (en0 인터페이스, RDS 포트 지정)
sudo tcpdump -i en0 port 23306 -w /tmp/mysql_comping.pcap

# 별도 터미널에서 테스트 실행
./gradlew :connector:test --tests &quot;com.eximbay.datasource.HikariConnectionHealthCheckTest.withoutTestQuery_logDoesNotContainSelect1&quot;

# Ctrl+C로 캡처 중지&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;tcpdump 분석&lt;/h4&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;# 클라이언트&amp;rarr;서버 방향, length 5 패킷의 MySQL command byte 확인
tcpdump -r /tmp/mysql_comping.pcap -nn -X 'src {클라이언트IP} and dst port 23306' | grep -B2 -A4 &quot;length 5&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MySQL 프로토콜 패킷 구조 (5바이트):&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;01 00 00 00 0e
│        │  └─ command byte: 0x0e = COM_PING (14)
│        └──── sequence id: 0
└───────────── payload length: 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캡처 결과:&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;⚪ COM_QUIT (0x01): 커넥션 종료
  COM_PING (0x0e): isValid() &amp;rarr; COM_PING 호출 확인
  COM_PING (0x0e): isValid() &amp;rarr; COM_PING 호출 확인
⚪ COM_QUIT (0x01): 커넥션 종료&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Wireshark 확인&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;.pcap&lt;/code&gt; 파일을 Wireshark에서 열기&lt;/li&gt;
&lt;li&gt;비표준 포트이므로 &lt;code&gt;Analyze &amp;rarr; Decode As...&lt;/code&gt;에서 TCP port &lt;code&gt;23306&lt;/code&gt;을 &lt;code&gt;MySQL&lt;/code&gt;로 설정&lt;/li&gt;
&lt;li&gt;Display Filter에 &lt;code&gt;mysql.command == 14&lt;/code&gt; 입력&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COM_PING&lt;/code&gt; 패킷만 필터링되어 표시됨&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;mysql.command == 14&lt;/code&gt;는 Wireshark가 패킷 바이너리를 MySQL 프로토콜 규격으로 파싱한 내부 필드를 기준으로 필터링한다.&lt;br /&gt;화면에 보이는 텍스트 검색이 아니라, 디코딩된 프로토콜 필드 값으로 매칭한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 필터:&lt;/p&gt;
&lt;table style=&quot;height: 96px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;필터&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; text-align: center;&quot;&gt;&lt;span style=&quot;color: #ffffff;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;mysql.command == 14&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 18px;&quot;&gt;&lt;span style=&quot;color: #000000; text-align: start;&quot;&gt;COM_PING&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;mysql.command == 1&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;COM_QUIT&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;mysql.command == 3&lt;/code&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;code&gt;COM_QUERY&lt;/code&gt; (SELECT 등)&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;connectionTestQuery(&quot;SELECT 1&quot;)&lt;/code&gt; 제거만으로 HikariCP가 자동으로 &lt;code&gt;COM_PING&lt;/code&gt; 기반 헬스체크로 전환되며,&lt;br /&gt;&lt;code&gt;profileSQL&lt;/code&gt; 로그와 네트워크 패킷 캡처 양쪽에서 이를 검증 완료했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SELECT 1 검증 쿼리 확인 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;운영 중인 MySQL에서 &lt;code&gt;SELECT 1&lt;/code&gt; 헬스체크가 실제로 얼마나 실행되고 있는지 쿼리로 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Com_select 카운터 확인&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- SELECT 문 실행 횟수 (서버 시작 이후 누적)
SHOW GLOBAL STATUS LIKE 'Com_select';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;connectionTestQuery(&quot;SELECT 1&quot;)&lt;/code&gt; 제거 전후로 이 값의 증가 속도를 비교하면 효과를 확인할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;일정 시간 동안 SELECT 1 발생량 측정&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- 1) 현재 값 기록
SELECT VARIABLE_VALUE INTO @before
FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Com_select';

-- 2) 일정 시간 대기 (예: 10초)
DO SLEEP(10);

-- 3) 차이 계산
SELECT VARIABLE_VALUE - @before AS select_count_in_10s
FROM performance_schema.global_status WHERE VARIABLE_NAME = 'Com_select';&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;General Log로 SELECT 1 직접 확인&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- General Log 활성화 (운영 환경에서는 짧은 시간만 사용)
SET GLOBAL general_log = 'ON';
SET GLOBAL log_output = 'TABLE';

-- 일정 시간 후 SELECT 1 쿼리 확인
SELECT event_time, command_type, argument
FROM mysql.general_log
WHERE argument LIKE 'SELECT 1%'
ORDER BY event_time DESC
LIMIT 20;

-- General Log 비활성화
SET GLOBAL general_log = 'OFF';&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;General Log는 모든 쿼리를 기록하므로 성능에 영향을 준다.&lt;br /&gt;운영 환경에서는 짧은 시간만 활성화하고 반드시 끌 것.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Performance Schema로 확인&lt;/h3&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;-- SELECT 1 쿼리의 실행 통계 확인
SELECT DIGEST_TEXT, COUNT_STAR, AVG_TIMER_WAIT/1000000000 AS avg_ms
FROM performance_schema.events_statements_summary_by_digest
WHERE DIGEST_TEXT LIKE 'SELECT ?%'
ORDER BY COUNT_STAR DESC
LIMIT 10;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SELECT ?&lt;/code&gt;는 MySQL이 &lt;code&gt;SELECT 1&lt;/code&gt;을 정규화한 형태다.&lt;br /&gt;&lt;code&gt;COUNT_STAR&lt;/code&gt;가 커넥션 풀 크기 &amp;times; 요청 수에 비례하면 헬스체크 쿼리일 가능성이 높다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HikariCP 공식 문서 &amp;mdash; JDBC4 드라이버 사용 시 &lt;code&gt;connectionTestQuery&lt;/code&gt; 설정하지 않을 것을 권장&lt;br /&gt;&lt;a href=&quot;https://github.com/brettwooldridge/HikariCP#frequently-used&quot;&gt;https://github.com/brettwooldridge/HikariCP#frequently-used&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>IT 지식</category>
      <category>HicariCP</category>
      <category>MySQL</category>
      <category>스프링부트</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/159</guid>
      <comments>https://kurukurucoding.tistory.com/159#entry159comment</comments>
      <pubDate>Mon, 30 Mar 2026 22:44:35 +0900</pubDate>
    </item>
    <item>
      <title>한글이 왜 깨질까? 캐릭터셋과 인코딩에 대해서 알아보자</title>
      <link>https://kurukurucoding.tistory.com/158</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. 깨진 한글과의 첫 만남&lt;/b&gt;&lt;/h2&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;span&gt;��&lt;/span&gt;처럼 깨져 보이는 경험을 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;ldquo;같은 요청인데 왜 영어는 괜찮고, 한글만 깨질까?&amp;rdquo;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 해답은 바로 &lt;b&gt;문자셋(Character Set)과 인코딩(Encoding)&lt;/b&gt;에 숨어 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 문자셋과 인코딩의 차이&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;문자셋(Character Set)&lt;/b&gt;&lt;/span&gt;: 문자에 번호를 붙인 목록 (예: &lt;span&gt;가&lt;/span&gt; = U+AC00)&lt;br /&gt;(사전 같은 개념이라고 생각해보자)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;인코딩(Encoding)&lt;/b&gt;&lt;/span&gt;: 그 번호를 실제 바이트로 바꾸는 규칙&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시 &amp;ndash; &lt;span&gt;&quot;가&quot;&lt;/span&gt; (U+AC00):&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UTF-8 &amp;rarr; &lt;span&gt;EA B0 80&lt;/span&gt; (3바이트)&lt;/li&gt;
&lt;li&gt;UTF-16LE &amp;rarr; &lt;span&gt;AC 00&lt;/span&gt; (2바이트)&lt;/li&gt;
&lt;li&gt;EUC-KR &amp;rarr; &lt;span&gt;B0 A1&lt;/span&gt; (2바이트)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;즉, &lt;/span&gt;&lt;b&gt;같은 문자라도 인코딩 방식에 따라 바이트가 달라진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영어는 ASCII 영역(0~127) 덕분에 어느 인코딩에서나 값이 같아 깨지지 않는 반면, 한글은 인코딩 차이로 쉽게 깨진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 실제 바이트 비교 &amp;ndash; &amp;ldquo;가나다&amp;rdquo;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 표는 &lt;span&gt;&quot;가나다&quot;&lt;/span&gt;라는 문자열을 다양한 인코딩으로 표현했을 때의 바이트 값이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 75.8139%; height: 74px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 11.1628%;&quot;&gt;&lt;b&gt;문자&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 18.2558%;&quot;&gt;&lt;b&gt;유니코드 코드 포인트&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 16.3954%;&quot;&gt;&lt;b&gt;UTF-8(hex)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 17.1974%;&quot;&gt;&lt;b&gt;UTF-16LE(hex)&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 17px; width: 20.1748%;&quot;&gt;&lt;b&gt;EUC-KR(hex)&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 11.1628%;&quot;&gt;&lt;span&gt;가&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 18.2558%;&quot;&gt;&lt;span&gt;U+AC00&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 16.3954%;&quot;&gt;&lt;span&gt;EA B0 80&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 17.1974%;&quot;&gt;&lt;span&gt;AC 00&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 20.1748%;&quot;&gt;&lt;span&gt;B0 A1&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 11.1628%;&quot;&gt;&lt;span&gt;나&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 18.2558%;&quot;&gt;&lt;span&gt;U+B098&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 16.3954%;&quot;&gt;&lt;span&gt;EB 82 98&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 17.1974%;&quot;&gt;&lt;span&gt;98 B0&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 20.1748%;&quot;&gt;&lt;span&gt;B3 AA&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;height: 19px; width: 11.1628%;&quot;&gt;&lt;span&gt;다&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 18.2558%;&quot;&gt;&lt;span&gt;U+B2E4&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 16.3954%;&quot;&gt;&lt;span&gt;EB 8B A4&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 17.1974%;&quot;&gt;&lt;span&gt;E4 B2&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 19px; width: 20.1748%;&quot;&gt;&lt;span&gt;B4 D9&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  같은 &quot;가나다&quot;라도 UTF-8은 9바이트, UTF-16LE는 6바이트, EUC-KR은 6바이트를 차지한다.&lt;br /&gt;이 차이 때문에 시스템마다 다르게 해석되면 &amp;ldquo;깨짐&amp;rdquo; 현상이 발생하는 거죠.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 문자셋의 역사적 배경&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;ASCII&lt;/b&gt;&lt;/span&gt;: 7비트, 영어/숫자/기호만 표현 가능&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;ISO-8859 계열&lt;/b&gt;&lt;/span&gt;: 유럽 언어 확장 (라틴-1, 키릴, 아랍 등)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;EUC-KR, CP949&lt;/b&gt;&lt;/span&gt;: 한국어 문자 집합 (완성형 한글 지원)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Shift_JIS&lt;/b&gt;&lt;/span&gt;: 일본어 문자 집합&lt;/li&gt;
&lt;li&gt;&lt;b&gt;GB2312, GBK, GB18030&lt;/b&gt;&lt;span&gt;: 중국어 문자 집합&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 결국 언어별로 각자 표준을 만들어 쓰다 보니, 다국어 환경에서 충돌이 생기고 &amp;ldquo;문자 깨짐&amp;rdquo;이 일상이 되고 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;5. 유니코드와 UTF 시리즈&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 등장한 게 &lt;b&gt;유니코드(Unicode)&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;유니코드: 전 세계 모든 문자를 하나의 코드 포인트 체계로 통합 (0x0000 ~ 0x10FFFF)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니코드를 바이트로 변환하는 인코딩 방식이 &lt;b&gt;UTF (Unicode Transformation Format)&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;UTF-8: 가변 길이 1~4바이트, ASCII 호환 &amp;rarr; 웹/네트워크 표준&lt;/li&gt;
&lt;li&gt;UTF-16: 대부분 2바이트, 동아시아 문자 효율 &amp;uarr; &amp;rarr; Windows, Java 내부 표현&lt;/li&gt;
&lt;li&gt;UTF-32: 고정 4바이트, 단순하지만 메모리 낭비 심함&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;  Unicode의 다른 이름들&lt;br /&gt;UCS (Universal Coded Character Set): ISO/IEC 10646 국제 표준에서 쓰는 이름. 사실상 Unicode와 동일한 개념. &lt;br /&gt;코드 페이지(Code Page): 유니코드 이전 시대, Windows/IBM에서 문자셋을 부르던 이름 (예: CP949, CP1252). &lt;br /&gt;BMP (Basic Multilingual Plane): 유니코드 첫 평면 (U+0000 ~ U+FFFF). 예전에는 &amp;ldquo;16비트면 충분&amp;rdquo;하다고 했던 배경.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;6. 실무에서 만난 문제와 해결&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;기존 서버&lt;/b&gt;&lt;/span&gt;: 인코딩을 느슨하게 처리 &amp;rarr; 어떤 charset이 와도 잘 받아줌&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;차세대 서버&lt;/b&gt;&lt;/span&gt;: UTF-8 전제 &amp;rarr; 다른 인코딩 오면 깨짐&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;라우트 서버&lt;/b&gt;&lt;/span&gt;: 요청을 한 번 디코딩 후 다시 UTF-8로 인코딩 &amp;rarr; 기존 서버가 받으면 이미 깨진 상태&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;&lt;span&gt;Content-Type&lt;/span&gt; 헤더에서 charset 확인&lt;/li&gt;
&lt;li&gt;UTF-8이면 차세대 서버로 전달 대상&lt;/li&gt;
&lt;li&gt;아니면 레거시 서버로 라우팅&lt;/li&gt;
&lt;li&gt;전달할 때는 반드시 &lt;span&gt;&lt;b&gt;raw bytes 그대로&lt;/b&gt;&lt;/span&gt; 넘겨야 깨지지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;7. 보안과 시스템 설계에서의 중요성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자셋과 인코딩은 단순히 &amp;ldquo;깨짐 방지&amp;rdquo; 이상의 의미가 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;보안&lt;/b&gt;&lt;/span&gt;: 인코딩 불일치를 이용한 공격 &amp;rarr; XSS, SQL Injection 우회 (&lt;span&gt;%uXXXX&lt;/span&gt; 등)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;시스템 설계&lt;/b&gt;&lt;/span&gt;: 글로벌 서비스는 기본적으로 &lt;span&gt;&lt;b&gt;UTF-8 통일&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;소프트웨어 공학&lt;/b&gt;&lt;/span&gt;: 국제화(i18n), 지역화(l10n)에서 필수 지식&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;CS 기본기&lt;/b&gt;&lt;/span&gt;: 데이터 표현(문자 &amp;rarr; 코드포인트 &amp;rarr; 바이트)의 이해&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 &amp;amp; 국제화 &amp;amp; 지역화에 대해서 더 정리하자면&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;( 보안 )&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;무슨 문제인가? 입력이 여러 번 디코딩/재인코딩되거나, 서버, 프록시, 앱이 서로 다른 문자셋을 가정하면 필터/검증을 우회할 수 있음.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;문자열 = 바이트의 해석 결과&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;네트워크/스토리지 레벨은 바이트. 사람이 읽는 문자열은 바이트를 어떤 인코딩으로 해석해서 얻은 결과.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;중간 컴포넌트가 임의로 디코딩/재인코딩하면 의미가 바뀜&lt;br /&gt;&lt;/b&gt;예) 프록시가 URL 디코딩을 한 번 하고, 웹 앱은 또 디코딩을 하면(또는 다른 charset으로 디코딩하면) 원래 숨겨진 문자가 드러남.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검증기는 보통 &amp;lsquo;지금 보이는 문자열&amp;rsquo;만 검사&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;검증 시점에선 무해해 보여도, 나중에 또 디코딩되면 악성 코드가 됨 &amp;rarr; 우회 성공.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시 A) &lt;/b&gt;&lt;b&gt;이중(중첩) 인코딩으로 XSS 우회&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자 전송(이중 인코딩):(이건 &quot;&amp;lt;&quot; &amp;rarr; &quot;&lt;span&gt;%3C&quot;&lt;/span&gt; &amp;rarr; &quot;&lt;span&gt;%253C&quot; &lt;/span&gt;처럼 한 번 더 인코딩한 것)&lt;/li&gt;
&lt;li&gt;%253Cscript%253Ealert(1)%253C/script%253E&lt;/li&gt;
&lt;li&gt;흐름:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프록시(또는 웹서버)가 URL 디코딩을 한 번만 수행 &amp;rarr; 결과: &lt;span&gt;%3Cscript%3Ealert(1)%3C/script%3E&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&amp;rarr; 이 문자열은 아직 &lt;span&gt;&amp;lt;&lt;/span&gt;로 변하지 않아서 필터(&amp;ldquo;금지 문자열 &lt;span&gt;&amp;lt;script&amp;gt;&lt;/span&gt; 포함 여부&amp;rdquo;)를 통과할 수 있음.&lt;/li&gt;
&lt;li&gt;앱 레이어에서 추가로 디코딩(또는 템플릿이 무심코 디코드) &amp;rarr; 결과: &lt;span&gt;&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&lt;/span&gt; &amp;rarr; 브라우저에서 실행 &amp;rarr; XSS 성공.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;한 번만 검사하면 안 된다. 여러 계층에서의 디코딩 타이밍을 고려해야 함.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시 B) &lt;/b&gt;&lt;b&gt;%uXXXX (옛 JS 이스케이프) 우회&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구형 코드/라이브러리에서 &lt;span&gt;unescape('%u003C')&lt;/span&gt; &amp;rarr; &lt;span&gt;&amp;lt;&lt;/span&gt; 로 해석되는 경우가 있음.&lt;/li&gt;
&lt;li&gt;공격자가 &lt;span&gt;%u003Cscript%u003E&lt;/span&gt;처럼 보내면 일부 필터에서 걸리지 않다가, 브라우저/JS에서 해석되면 XSS 발생.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예시 C) &lt;/b&gt;&lt;b&gt;인코딩 불일치로 인한 SQL 우회(개념적)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자가 의도적으로 바이트열을 구성해서,
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;방화벽/프록시가 &lt;span&gt;&lt;b&gt;UTF-8로 디코딩&lt;/b&gt;&lt;/span&gt;하면 안전한 문자로 보이고,&lt;/li&gt;
&lt;li&gt;백엔드가 &lt;span&gt;&lt;b&gt;CP949/EUC-KR&lt;/b&gt;&lt;/span&gt;로 디코딩하면 &lt;span&gt;'&lt;/span&gt;(따옴표)나 &lt;span&gt;;&lt;/span&gt; 같은 문자가 나와 SQL 쿼리를 끊는 경우.&lt;br /&gt;(구체적 바이트 값은 인코딩별 매핑에 따라 달라지므로, 원리는 &amp;ldquo;다른 인코딩에서는 다른 문자로 보일 수 있다&amp;rdquo;는 점.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;검증필터가 어떤 인코딩/타이밍에서 작동하는지 반드시 명확해야 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;b&gt;우선순위 &lt;b&gt;실무 체크리스트&lt;/b&gt; &lt;/b&gt;방어 방법&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;입력 정규화(입력 지점에서 canonicalize)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;수신한 &lt;b&gt;원본 바이트(raw bytes)&lt;/b&gt;를 한 번만 정해진 방식(예: UTF-8)으로 디코딩 &amp;rarr; 정규화(Unicode NFC) &amp;rarr; 그 결과로만 검증/필터 적용.&lt;/li&gt;
&lt;li&gt;즉, &amp;ldquo;정규화 &amp;rarr; 검증 &amp;rarr; 처리&amp;rdquo; 순서를 강제.&lt;/li&gt;
&lt;li&gt;(중요) 중간 컴포넌트가 임의로 디코딩하지 않게 아키텍처 설계.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;전 구간 인코딩 통일(또는 명시적 계약)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트 &amp;rarr; 프록시 &amp;rarr; 앱 &amp;rarr; DB &amp;rarr; 로그까지 &lt;span&gt;&lt;b&gt;UTF-8 강제&lt;/b&gt;&lt;/span&gt;(또는 명확히 문서화된 인코딩).&lt;/li&gt;
&lt;li&gt;Content-Type에 &lt;span&gt;charset=utf-8&lt;/span&gt;을 명시하고, 다른 charset은 거부하거나 엄격히 로깅/검사.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디코딩/디코드 연산을 최소화&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프록시나 게이트웨이가 쿼리스트링과 본문을 임의로 디코딩하지 않도록 설정.&lt;/li&gt;
&lt;li&gt;가능한 raw bytes를 그대로 전달하고, 애플리케이션 단에서 한 번만 처리.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;검증을 바이트 레벨이 아니라 &amp;lsquo;정규화된 유니코드 문자열&amp;rsquo; 레벨에서 수행&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;unicodedata.normalize('NFC', text)&lt;/span&gt; 같은 정규화를 통해 동등성 문제(NFKC/NFC)를 제거한 뒤 필터 적용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출력 인코딩(escaping) 철저&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTML context &amp;rarr; HTML escape (e.g., &lt;span&gt;&amp;amp;lt;&lt;/span&gt;, &lt;span&gt;&amp;amp;gt;&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;JS context &amp;rarr; JS escape&lt;/li&gt;
&lt;li&gt;SQL &amp;rarr; 파라미터화(PreparedStatement) 등.&lt;/li&gt;
&lt;li&gt;이건 입력 검증이 뚫려도 최종 공격 실행을 막는 마지막 방패.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;파라미터화된 쿼리 사용&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 연결로 쿼리를 만들지 마라. SQL Injection 근본 차단.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;의심스러운 인코딩/이상 패턴 차단 로깅&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;%uXXXX&lt;/span&gt;, 중첩 인코딩(&lt;span&gt;%25&lt;/span&gt; sequences), 이상한 대량 non-UTF-8 바이트 등은 탐지를 거부하거나 심층 검사.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;테스트 케이스로 &amp;lsquo;이중 디코딩&amp;rsquo; 시나리오 포함&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CI 유닛/통합 테스트에 double-encoding, unicode-escape, legacy-encoding 사례 포함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;입력이 여러 번 디코딩되거나(또는 서로 다른 컴포넌트가 서로 다른 인코딩 전제로 디코딩하면), &lt;br /&gt;한 컴포넌트에서는 &amp;ldquo;안전한 값&amp;rdquo;으로 보이지만 최종적으로는 &lt;br /&gt;악성 문자열(&amp;lt;script&amp;gt;, ' OR 1=1 -- 등)이 되어서 필터를 우회하여 침투할 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style3&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;( 국제화 &amp;amp; 지역화 )&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;i18n (internationalization)&lt;/b&gt;&lt;/span&gt;: 소프트웨어를 다국어 대응 가능하게 만드는 과정.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;i10n (localization)&lt;/b&gt;&lt;/span&gt;: 특정 언어/문화권에 맞게 현지화하는 과정.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;❓무엇을 조심할까 (i18n에서의 위험 요소들)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;문자 길이 문제&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예: &lt;span&gt;&quot;가&quot;&lt;/span&gt;는 UTF-8에서 3바이트, UTF-16에서 2바이트 &amp;rarr; &amp;ldquo;문자 수 제한&amp;rdquo;과 &amp;ldquo;바이트 크기 제한&amp;rdquo;이 달라질 수 있음.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;특히 DB 칼럼 정의(&lt;/span&gt;VARCHAR(10)&lt;span&gt; vs &lt;/span&gt;VARCHAR(30 BYTE)&lt;span&gt;)에서 혼동.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정렬(Collation)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&quot;가나다&quot;&lt;/span&gt;와 &lt;span&gt;&quot;가 나다&quot;&lt;/span&gt;를 사전순으로 정렬할 때, 한국어 사전 규칙과 단순 유니코드 코드포인트 정렬이 달라질 수 있음.&lt;/li&gt;
&lt;li&gt;MySQL, PostgreSQL도 &lt;span&gt;collation&lt;/span&gt;을 설정하지 않으면 기대와 다른 정렬 결과가 나옴.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대소문자 규칙&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영어: &lt;span&gt;User&lt;/span&gt; == &lt;span&gt;user&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;터키어: &lt;/span&gt;&quot;I&quot;.lower() &amp;rarr; &quot;ı&quot;&lt;span&gt; (점 없는 i)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;단순 &lt;span&gt;toLowerCase()&lt;/span&gt;로는 언어별 규칙을 반영 못함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;날짜/숫자 포맷&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;한국: &lt;/span&gt;2025-09-28&lt;span&gt;, 미국: &lt;/span&gt;09/28/2025&lt;span&gt;, 독일: &lt;/span&gt;28.09.2025&lt;/li&gt;
&lt;li&gt;통화: &lt;span&gt;1,234.56&lt;/span&gt; (미국) vs &lt;span&gt;1.234,56&lt;/span&gt; (독일)&lt;/li&gt;
&lt;li&gt;지역화를 고려하지 않으면 잘못된 값 파싱 가능.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;방향성 (RTL, Right-to-Left)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아랍어, 히브리어 등은 오른쪽&amp;rarr;왼쪽으로 작성.&lt;/li&gt;
&lt;li&gt;UI 레이아웃, 입력 필드, 문자열 붙임 위치(예: 따옴표&amp;middot;괄호)도 영향을 받음.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;‼️ 실무 원칙&lt;/b&gt;&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;저장/전송은 UTF-8 통일&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DB, API, 메시지 큐, 로그 &amp;rarr; 전부 UTF-8로 강제.&lt;/li&gt;
&lt;li&gt;이유: 호환성, ASCII fallback, 네트워크 효율.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입력은: 바이트 &amp;rarr; 정규화(NFC) &amp;rarr; 유효성 검사 &amp;rarr; 저장&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&amp;eacute;&lt;/span&gt; &amp;rarr; 단일 문자(U+00E9) / &lt;span&gt;e&lt;/span&gt;+&lt;span&gt;́&lt;/span&gt; 조합(U+0065 + U+0301) &amp;rarr; 시각적 동일&lt;/li&gt;
&lt;li&gt;정규화를 안 하면 &amp;ldquo;동일하게 보이는 문자열이 DB에서는 다르다&amp;rdquo;는 문제 발생.&lt;/li&gt;
&lt;li&gt;저장은 &lt;span&gt;NFC&lt;/span&gt;, 보안 검사 시는 &lt;span&gt;NFKC + casefold&lt;/span&gt; 활용.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문자 길이 vs 바이트 길이 분리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI 제한(예: 닉네임 10글자): &lt;span&gt;&lt;b&gt;문자 단위&lt;/b&gt;&lt;/span&gt;(grapheme cluster)&lt;/li&gt;
&lt;li&gt;DB 저장/네트워크 패킷 제한: &lt;span&gt;&lt;b&gt;바이트 단위&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;이모지 하나 = 2~4 code units&amp;rdquo; 문제를 꼭 고려해야 함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;로케일 기반 처리&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;문자열 비교, 정렬은 반드시 &lt;span&gt;collation&lt;/span&gt;을 지정해야 함.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;자바: &lt;/span&gt;Collator&lt;span&gt;, Python: &lt;/span&gt;locale.strxfrm&lt;span&gt;, DB: &lt;/span&gt;COLLATE&lt;span&gt; 절.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이모지&amp;middot;보조 평면 지원&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이모지는 UTF-8에서 4바이트, UTF-16에서 surrogate pair(2 code units).&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Java &lt;/span&gt;String.length()&lt;span&gt;는 code unit 수 &amp;rarr; &lt;/span&gt;&quot; &quot;.length() == 2&lt;span&gt; (혼동 주의).&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;UI는 grapheme cluster 단위로 길이 계산해야 올바름. (Python &lt;span&gt;regex&lt;/span&gt;\PLG, ICU 라이브러리 사용)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RTL 언어 테스트&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UI에서 텍스트 정렬&amp;middot;플로우가 깨지지 않게 해야 함.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;HTML: &lt;/span&gt;&amp;lt;bdo dir=&quot;rtl&quot;&amp;gt;&lt;span&gt;, CSS: &lt;/span&gt;direction: rtl&lt;/li&gt;
&lt;li&gt;숫자&amp;middot;기호&amp;middot;이모지 섞이면 혼란 생기므로 반드시 QA 필요.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;✅ 배포 전 체크리스트&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Content-Type: text/html; charset=UTF-8&lt;span&gt; 헤더 강제.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;DB 문자셋 &lt;span&gt;utf8mb4&lt;/span&gt;(MySQL) / &lt;span&gt;UTF8&lt;/span&gt;(Postgres) 확인.&lt;/li&gt;
&lt;li&gt;로그/백업도 UTF-8로 통일 (혼합 인코딩 방지).&lt;/li&gt;
&lt;li&gt;외부 연동(API, 메일, SFTP 등) 시 인코딩 계약서/명세 확인.&lt;/li&gt;
&lt;li&gt;이모지/보조 평면 저장 가능한지 확인 (MySQL의 &lt;span&gt;utf8&lt;/span&gt; vs &lt;span&gt;utf8mb4&lt;/span&gt; 차이).&lt;/li&gt;
&lt;li&gt;UI/UX 테스트에 RTL 언어 포함(아랍어/히브리어 샘플 텍스트).&lt;/li&gt;
&lt;li&gt;다국어 정렬, 검색 동작 확인 (예: &amp;ldquo;김영희&amp;rdquo; vs &amp;ldquo;김 영희&amp;rdquo;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;인코딩이 틀어지면 보안 필터가 무력화될 수 있고, 국제화, 지역화는 단순 인코딩 통일을 넘어 문자 단위의 처리, 정렬, 표시까지 고려해야 안전하고 올바르게 동작한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #000000;&quot;&gt;결국, 우리가 맞닥뜨린 한글 깨짐은 단순한 버그가 아니라,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR'; color: #000000;&quot;&gt;컴퓨터가 문자를 이해하는 방식 자체를 탐험하는 여정이었던 셈이다.&lt;br /&gt;한글이 꺠지는 순간, 나는 비로소 유니코드와 UTF-8의 위대함을 깨달았다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>IT 지식</category>
      <category>인코딩</category>
      <category>캐릭터셋</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/158</guid>
      <comments>https://kurukurucoding.tistory.com/158#entry158comment</comments>
      <pubDate>Sun, 28 Sep 2025 01:29:11 +0900</pubDate>
    </item>
    <item>
      <title>macOS에서 계정명(short name) 변경하기 - Sequoia 15.1 경험 정리</title>
      <link>https://kurukurucoding.tistory.com/157</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. macOS 계정 구조 이해하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;macOS에는 계정과 관련된 세 가지 이름이 있다:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Full name (전체 이름)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;로그인 화면이나 Finder에 표시되는 이름. 단순히 표시용이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Short name (계정명, RecordName)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템이 계정을 식별하는 핵심 값. UID와 연결되어 파일 권한까지 좌우한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Home directory (NFSHomeDirectory)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;/Users/shortname&lt;/span&gt; 형태로, 실제 데이터와 설정이 저장되는 경로.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote style=&quot;color: #0e0e0e;&quot; data-ke-style=&quot;style1&quot;&gt;파일 권한은 UID(숫자)에 묶여 있기 때문에 UID를 건드리지 않는 한 short name을 바꿔도 권한은 깨지지 않는다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. 예전(macOS Catalina ~ Big Sur) 방식&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 계정명 변경이 번거로웠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;1. 변경 하기 전 상태를 확인&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1758434341454&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dscl . -read /Users/newname&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;&lt;span&gt;dscl&lt;/span&gt; 명령어로 short name(RecordName) 수정&lt;/p&gt;
&lt;pre id=&quot;code_1758434200703&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo dscl . -change /Users/oldname RecordName oldname newname&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;span&gt;3. dscl&lt;/span&gt;로 홈 디렉토리 경로(NFSHomeDirectory)도 수정&lt;/p&gt;
&lt;pre id=&quot;code_1758434228010&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo dscl . -change /Users/newname NFSHomeDirectory /Users/oldname /Users/newname&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;4. Finder/터미널에서 실제 &lt;span&gt;/Users/oldname&lt;/span&gt; 폴더명을 &lt;span&gt;/Users/newname&lt;/span&gt; 으로 변경&lt;/p&gt;
&lt;pre id=&quot;code_1758434242111&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mv /Users/oldname /Users/newname&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;span&gt;5. chown&lt;/span&gt; 으로 권한 보정&lt;/p&gt;
&lt;pre id=&quot;code_1758434259256&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo chown -R newname:staff /Users/newname&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;즉, short name과 홈 디렉토리, 실제 폴더 세 군데를 모두 맞춰줘야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 예전에는 계정명과 디렉토리 명을 수동으로 변경한 적도 있다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;dscl은 macOS전용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;풀네임은 &lt;/span&gt;&lt;b&gt;Directory Service command line utility&lt;/b&gt;&lt;span&gt; 라고 해서, macOS가 내부적으로 계정을 관리하는 &lt;/span&gt;&lt;b&gt;DirectoryService / opendirectoryd&lt;/b&gt;&lt;span&gt; DB에 접근하는 도구&lt;/span&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;b&gt;특징&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;macOS에만 기본 탑재돼 있음 (&lt;span&gt;/usr/bin/dscl&lt;/span&gt;)&lt;/li&gt;
&lt;li&gt;전통적인 UNIX/Linux 계열은 &lt;span&gt;/etc/passwd&lt;/span&gt;, &lt;span&gt;/etc/shadow&lt;/span&gt;, &lt;span&gt;/etc/group&lt;/span&gt; 같은 텍스트 파일을 직접 수정하지만, &lt;br /&gt;macOS는 이걸 다 opendirectoryd가 관리 &amp;rarr; &lt;span&gt;dscl&lt;/span&gt;로 접근해야 함&lt;/li&gt;
&lt;li&gt;예전 macOS 서버 시절(Open Directory 기반 LDAP)에서도 동일하게 쓰였고, 지금은 로컬 사용자/그룹 관리에도 그대로 사용됨&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Linux랑 비교&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Linux&lt;/b&gt;&lt;/span&gt;&lt;span&gt; &amp;rarr; &lt;/span&gt;useradd&lt;span&gt;, &lt;/span&gt;usermod&lt;span&gt;, &lt;/span&gt;passwd&lt;span&gt;, &lt;/span&gt;vipw&lt;span&gt; 같은 명령어로 &lt;/span&gt;/etc/passwd&lt;span&gt; 수정&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;macOS&lt;/b&gt;&lt;/span&gt;&lt;span&gt; &amp;rarr; &lt;/span&gt;dscl&lt;span&gt; (ex: &lt;/span&gt;dscl . -read /Users/username&lt;span&gt;)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Sequoia(15.x) 이후 달라진 점&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sequoia에서는 계정 관리가 단순화되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;RecordName&lt;/span&gt;만 바꿔도 자동으로:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NFSHomeDirectory 값이 &lt;span&gt;/Users/newname&lt;/span&gt; 으로 변경&lt;/li&gt;
&lt;li&gt;&lt;span&gt;실제 &lt;/span&gt;/Users/oldname&lt;span&gt; 폴더명이 &lt;/span&gt;/Users/newname&lt;span&gt; 으로 rename&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;UID는 그대로 유지되어 권한도 자동으로 &lt;span&gt;newname&lt;/span&gt;으로 보정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 예전에는 3~4단계가 필요했지만, &lt;span&gt;&lt;b&gt;Sequoia에서는 단 한 줄&lt;/b&gt;&lt;/span&gt;로 끝나는 경우가 많다:&lt;/p&gt;
&lt;pre id=&quot;code_1758433964048&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo dscl . -change /Users/oldname RecordName oldname newname&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;재부팅 후 확인:&lt;/p&gt;
&lt;pre id=&quot;code_1758433975439&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;id -un       # newname
echo $HOME   # /Users/newname&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 주의할 점&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;버전에 따라 동작 차이&lt;/b&gt;&lt;/span&gt;: Ventura나 Sonoma에서는 자동 보정이 일부만 적용되기도 한다. (홈 디렉토리 경로는 남아 있는 경우가 있음)&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;Keychain&lt;/b&gt;&lt;/span&gt;: short name 변경 시 꼬일 수 있다. 앱 로그인 문제 발생 시, &lt;span&gt;키체인 접근&lt;/span&gt;에서 로그인 키체인을 재설정하면 해결된다.&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;보안 앱&lt;/b&gt;&lt;/span&gt;: 안랩, 백신, Docker 등은 Library/Keychain 데이터를 강하게 묶어두는 경우가 있어 정상 동작을 반드시 확인해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Sequoia 15.1에서 계정명 변경을 직접 해본 결과,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거에는 몇 단계 수작업이 필요했지만, 이제는 &lt;span&gt;dscl&lt;/span&gt; 명령 한 줄이면 short name, 홈 디렉토리, 폴더명, 권한까지 자동으로 맞춰졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 버전별 차이를 고려해 &amp;ldquo;정석 절차&amp;rdquo;를 알고 있는 게 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;맥 계정 관리 = UID는 건드리지 말고, short name과 홈 디렉토리만 맞추자!&lt;/b&gt;&lt;/p&gt;</description>
      <category>IT 지식</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/157</guid>
      <comments>https://kurukurucoding.tistory.com/157#entry157comment</comments>
      <pubDate>Sun, 21 Sep 2025 15:02:31 +0900</pubDate>
    </item>
    <item>
      <title>리눅스는 어떤 CPU 스케쥴링 알고리즘을 사용할까?</title>
      <link>https://kurukurucoding.tistory.com/156</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 왜 이걸 알아야 할까?&lt;/h2&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;top, htop, pidstat, pref 등 시스템 모니터링 도구에서 나오는 load average, context switch, CPU 사용률은 결국 스케줄링 알고리즘의 결정 결과값이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;멀티스레딩, 병렬처리, 비동기 서버 개발 시 스케줄링 정책의 특성을 모르면 CPU 낭비, starvation, 성능 저하 발생이 이어진다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  즉, CPU 알고리즘은 성능 디버깅, 병렬 시스템 설계, 실시간 서비스 운영을 위해 꼭 알아야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 어떤 알고리즘이 있고, 리눅스에는 어떤걸 쓸까?&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이해의 출발을 위해 고전 알고리즘을 알아보자&lt;/h3&gt;
&lt;table style=&quot;width: 867px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 390px;&quot;&gt;&lt;b&gt;알고리즘&lt;/b&gt;&lt;/th&gt;
&lt;th style=&quot;width: 477px;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 390px; text-align: center;&quot;&gt;FCFS (First-Come First-Served)&lt;/td&gt;
&lt;td style=&quot;width: 477px;&quot;&gt;단순하지만 비효율적, Convoy 효과 발생&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 390px; text-align: center;&quot;&gt;SJF (Shortest Job First)&lt;/td&gt;
&lt;td style=&quot;width: 477px;&quot;&gt;이론상 최적이지만, 실행 시간 예측 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 390px; text-align: center;&quot;&gt;Round Robin&lt;/td&gt;
&lt;td style=&quot;width: 477px;&quot;&gt;시분할 시스템의 기본, 일정 시간마다 태스크 전환&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 390px; text-align: center;&quot;&gt;Priority Scheduling&lt;/td&gt;
&lt;td style=&quot;width: 477px;&quot;&gt;우선순위에 따라 실행, starvation 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그래서 리눅스의 알고리즘은?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스 우분투에서는 기본적으로 CFS(Completely Fair Scheduler)를 사용한다. 이는 Round Robin + Weight 기반 Priority Scheduling을 조합한 현대적 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음&amp;hellip; 말이 어렵고 잘 와닿지 않는다&amp;hellip; 완전히 공정한 스케줄러라는거 같다. 도대체 뭐가 공정하고 어떻게 스케줄링하는지 알아보자&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. CFS(Completely Fair Scheduler)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스 커널 2.6.23부터 기본 스케줄러로 채택된 CPU 스케줄러이며 Round Robin처럼 번갈아 주지만, 사용한 시간(vruntime)을 고려해서 덜 쓴 놈에게 먼저 기회를 주는 구조&lt;br /&gt;즉, 공정하게 CPU를 나눠 쓰도록 만드는 알고리즘이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 만들어 졌을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 스케줄러들은 문제점이 많았다고 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Round Robin: 모든 프로세스에 똑같은 시간 주는데, 작업량 차이가 커도 무시해버림&lt;/li&gt;
&lt;li&gt;Priority Scheduler: 높은 우선순위가 낮은 우선순위를 굶기기도 함(starvation)&lt;/li&gt;
&lt;li&gt;O(1) Scheduler: 매우 빠르지만 비선형적인 스케줄러 동작과 CPU 사용률 편향 문제&lt;br /&gt;그래서 등장한게 CFS라고 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공정하게 동작하는 과정&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;가상 실행 시간 vruntime을 통해 파악하고 균형을 맞춥니다.&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 태스크는 CPU를 사용한 시간이 저장됩니다. &amp;rarr; vruntime&lt;/li&gt;
&lt;li&gt;이 값이 작은 프로세스일 수록 CPU를 적게 썻다는 뜻입니다. &amp;rarr; 우선 실행 대상&lt;/li&gt;
&lt;li&gt;즉, CPU를 적게 쓴 프로레스를 먼저 실행 시켜서 균형을 맞추는 겁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &amp;ldquo;CPU 사용량을 추적하고, 가장 적게 사용한 놈부터 돌려준다&amp;rdquo;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;태스크 관리는 어떻게 할까?&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모든 태스크는 vruntime 기준으로 Red-Black Tree에 정렬됩니다.&lt;/li&gt;
&lt;li&gt;루트 노드 = 가장 vruntime이 작은 태스크 &amp;rarr; 이걸 먼저 실행&lt;/li&gt;
&lt;li&gt;실행되면 vruntime이 늘어나고, 다시 트리 재정렬 되며 위 과정을 반복합니다.&lt;br /&gt; &lt;br /&gt;키워드: Red-Black Tree&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Time Slice가 없습니다??!&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 Round Robin은 &amp;ldquo;10ms씩 돌아가자&amp;rdquo; 식이었는데, CFS는 그런 고정 time slice 개념이 없습니다.&lt;br /&gt;대신:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sched_latency_ns: 전체 프로세스가 한 바퀴 도는데 걸리는 총 시간&lt;/li&gt;
&lt;li&gt;min_granularity_ns: 한 태스크가 최소로 보장받는 실행 시간&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CFS는 이 값을 기준으로 동적으로 태스크를 교체합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;조정 가능한 CFS 파라미터&lt;/h3&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;b&gt;파일&lt;/b&gt;&lt;/th&gt;
&lt;th&gt;&lt;b&gt;의미&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/proc/sys/kernel/sched_latency_ns&lt;/td&gt;
&lt;td&gt;전체 태스크가 한 번씩 실행되는 주기 (기본: 6ms)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/proc/sys/kernel/sched_min_granularity_ns&lt;/td&gt;
&lt;td&gt;한 태스크가 최소 실행할 시간 (기본: 750&amp;micro;s)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;nice 값&lt;/td&gt;
&lt;td&gt;우선순위 가중치 (vruntime 계산 시 반영됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예시: CFS 동작 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 3개: A, B, C&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A: CPU-bound(연산 많은)&lt;/li&gt;
&lt;li&gt;B: I/O-bound(대기 많음)&lt;/li&gt;
&lt;li&gt;C: 중간 정도&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CFS는 A가 CPU를 오래 쓰면 vruntime이 빨리 증가하고&lt;br /&gt;&amp;rarr; B, C는 상대적으로 vruntime이 작아져 우선 배정됨&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실전 명령어&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;ps -eo pid,ni,psr,cmd --sort=ni  # nice 우선순위 확인&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. CPU, I/O bound에 적합한 스케줄러 무엇인가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 고민을 해볼 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CFS는 둘중에 어디에 적합하다고 생각하는지 고민해보자&lt;br /&gt;vrutime 이 적은 값이고 자주 태스크가 스위칭 되면 vruntime이 작아질것이다.&lt;br /&gt;.&lt;br /&gt;.&lt;br /&gt;.&lt;br /&gt;.&lt;br /&gt;.&lt;br /&gt;그렇다면 I/O bound이다, 비동기나 외부 자원 대기, API 요청 등 Waiting 상태가 많은 작업에 적합하다.&lt;br /&gt;즉, 짧고 자주 실행되는 작업이 유리하다.&lt;br /&gt;그래서 CFS는 I/O-bound 태스크를 더 자주, 더 빨리 실행시키는 구조로 되어있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어떻게 판단할 수 있을까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리눅스 기준 명령어&lt;/p&gt;
&lt;table style=&quot;width: 862px;&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style=&quot;width: 163px;&quot;&gt;&lt;b&gt;명령어&lt;/b&gt;&lt;/th&gt;
&lt;th style=&quot;width: 699px;&quot;&gt;&lt;b&gt;설명&lt;/b&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 163px; text-align: center;&quot;&gt;top, htop&lt;/td&gt;
&lt;td style=&quot;width: 699px;&quot;&gt;CPU 사용률, 상태 (R: Running / S: Sleeping 등)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 163px; text-align: center;&quot;&gt;pidstat -p 1&lt;/td&gt;
&lt;td style=&quot;width: 699px;&quot;&gt;특정 프로세스의 사용자/시스템 CPU 사용률&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 163px; text-align: center;&quot;&gt;iostat -xz 1&lt;/td&gt;
&lt;td style=&quot;width: 699px;&quot;&gt;디스크 I/O 대기율 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 163px; text-align: center;&quot;&gt;vmstat 1&lt;/td&gt;
&lt;td style=&quot;width: 699px;&quot;&gt;us, sy, wa &amp;rarr; CPU, 시스템, 대기 비율 분석&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  &quot;wa&quot;(IO Wait)가 높으면 I/O-bound 프로세스가 많은 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  us(User CPU)가 높으면 CPU-bound 작업이 많다는 뜻&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반대로 CPU-bound는?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Real-Time Priority 기반 스케줄링으로 실시간 우선순위 기반인 스케줄러를 이용하는 것이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가적으로 공부해볼것들&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Convoy 효과&lt;/li&gt;
&lt;li&gt;Red-Black Tree&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CS/운영체제</category>
      <category>CFS</category>
      <category>스케줄러</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/156</guid>
      <comments>https://kurukurucoding.tistory.com/156#entry156comment</comments>
      <pubDate>Fri, 22 Aug 2025 15:03:00 +0900</pubDate>
    </item>
    <item>
      <title>[암호화] 웹 보안은 SSL!</title>
      <link>https://kurukurucoding.tistory.com/155</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. SSL(Secure Socket Layer)이란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터넷 상에서 데이터 통신을 암호화하기 위한 보안 프로토콜입니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 현재는 SSL이 진화한 TLS(Transport Layer Security)가 사용되고 있지만, 실무에서는 여전히 SSL이란 표현을 널리 사용합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. SSL의 목적, 암호화!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 3자가 내용을 볼 수 없도록 암호화하는 &lt;code&gt;기밀성&lt;/code&gt;, 전송 중 데이터가 변조되지 않았는지 확인하는 &lt;code&gt;무결성&lt;/code&gt;, 서버 또는 클라이언트의 신원을 확인하는 &lt;code&gt;인증&lt;/code&gt;이 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. SSL 동작 방식 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2종류의 키를 사용 비대칭키와 대칭키를 혼합하여 사용합니다.&lt;/p&gt;
&lt;table style=&quot;height: 80px; width: 861px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; text-align: center; width: 152px;&quot;&gt;&lt;b&gt;암호 방식&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center; width: 349px;&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px; text-align: center; width: 360px;&quot;&gt;&lt;b&gt;사용 이유&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 152px;&quot;&gt;비대칭키 암호화&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 349px;&quot;&gt;느림, 공개키로 암호화 &amp;rarr; 비밀키로 복호화&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 360px;&quot;&gt;대칭키 전달 시 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px; width: 152px;&quot;&gt;대칭키 암호화&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 349px;&quot;&gt;빠름, 같은 키로 암복호화&lt;/td&gt;
&lt;td style=&quot;height: 20px; width: 360px;&quot;&gt;이후 데이터 통신에 사용&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;blockquote data-ke-style=&quot;style2&quot;&gt;즉, 비대칭키는 클라이언트의 대칭키를 가져오기 위한 것이고 대칭키는 전달할 데이터를 암호화 하기 위한 것입니다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 일반적인 SSL 핸드쉐이크 과정( HTTPS 시 사용)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2종류의 키를 사용해서 대칭키를 서버로 전달하는 과정이 SSL 핸드쉐이크 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rhWYa/btsPZsLL1Rh/AIIFnIiDZq46XVavkfcyrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rhWYa/btsPZsLL1Rh/AIIFnIiDZq46XVavkfcyrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rhWYa/btsPZsLL1Rh/AIIFnIiDZq46XVavkfcyrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrhWYa%2FbtsPZsLL1Rh%2FAIIFnIiDZq46XVavkfcyrK%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;809&quot; height=&quot;467&quot; data-origin-width=&quot;809&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 이 과정을 SSL Handshake라고 하며, 이 후에는 빠른 대칭키 기반 암호화로 HTTPS 통신이 진행됩니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 클라이언트가 웹페이지를 읽을 수 있는 이유는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트가 서버로부터 받는 HTML, CSS, JS 등의 응답은 대칭키로 암호화되어 전송됩니다. 클라리언트는 자신이 생성한 대칭키를 서버의 공개키로 암호화해서 전달했기 때문에 클라이언트 역시 대칭키를 알고 있습니다. 따라서 같은 대칭키로 응답을 복호화해서 웹페이지를 표시할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클라이언트의 대칭키는 어떻게?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 브라우저를 통해 따로 키를 만들지 않았는데 어떻게 대칭키를 만들어서 서버에 전달할 수 있지?라고 생각을 하셨을 겁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저는 내부에서 자동으로 대칭키를 생성하고 기능(SSL/TLS 스택을 내장)을 탑재하고 있습니다. 그래서 자동으로 이루어지는 보안 동작으로 사용자는 개입하지 않으면서 https를 사용할 수 있게 됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rdquo;브라우저가 안전한 난수 생성기(CSPRNG)를 통해 대칭키를 생성합니다.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;내장된 SSL 스택의 기능&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공개키 인증서 검증&lt;/li&gt;
&lt;li&gt;대칭키 생성&lt;/li&gt;
&lt;li&gt;키 교환 및 암호화&lt;/li&gt;
&lt;li&gt;세션 암호화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은&amp;nbsp;웹들은&amp;nbsp;HTTPS를&amp;nbsp;사용되어지면&amp;nbsp;우리도&amp;nbsp;모르게&amp;nbsp;자동으로&amp;nbsp;SSL&amp;nbsp;Handshake를&amp;nbsp;통해&amp;nbsp;보안을&amp;nbsp;유지하고&amp;nbsp;있다는&amp;nbsp;것입니다.&lt;br /&gt;SSL&amp;nbsp;Handshake는&amp;nbsp;다양한&amp;nbsp;암호화&amp;nbsp;알고리즘으로&amp;nbsp;다를&amp;nbsp;수&amp;nbsp;있지만&amp;nbsp;여기서는&amp;nbsp;흔히&amp;nbsp;사용되는&amp;nbsp;방식으로&amp;nbsp;설명하였습니다.&lt;br /&gt;더&amp;nbsp;깊이&amp;nbsp;있는&amp;nbsp;인증&amp;nbsp;과정을&amp;nbsp;공부하시려면&amp;nbsp;DH/ECDHE나&amp;nbsp;Perfect&amp;nbsp;Forward&amp;nbsp;Secrecy(PFS)를&amp;nbsp;참고하시면&amp;nbsp;좋을거&amp;nbsp;같습니다.&lt;/p&gt;</description>
      <category>보안</category>
      <category>https</category>
      <category>ssl</category>
      <category>TLS</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/155</guid>
      <comments>https://kurukurucoding.tistory.com/155#entry155comment</comments>
      <pubDate>Tue, 19 Aug 2025 23:25:29 +0900</pubDate>
    </item>
    <item>
      <title>[암호화] 대칭키란?</title>
      <link>https://kurukurucoding.tistory.com/154</link>
      <description>&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 대칭키란?&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;같은 키로 암호화와 복호화를 수행하는 방식입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;속도는 빠르고&lt;/b&gt; 구현이 간단하지만, &lt;br /&gt;&lt;b&gt;키가 노출될 경우&lt;/b&gt; 모든 통신이 해독 가능하다는 보안상 리스크가 있습니다.&lt;/span&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 대칭키를 이용하는 방법&lt;/h2&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;방식 1: RSA 기반(TLS 1.2 이하)&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 클라이언트는 서버 인증서를 받고 &amp;rarr; 공개키를 추출&lt;br /&gt;2. 브라우저가 임의로 세션키(대칭키)를 생성&lt;br /&gt;3. 이 세션키를 서버의 공개키로 암호화해서 전송&lt;br /&gt;4. 서버는 자신의 비밀키로 복호화 &amp;rarr; 세션키 확보&lt;br /&gt;&amp;nbsp;5. 이후 통신은 이 세션키(대칭키)로 암호화 진행&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이 방식은 RSA를 사용해 &lt;b&gt;클라이언트가 대칭키를 생성하고 서버의 공개키로 암호화하여 전송&lt;/b&gt;하는 구조이며, &lt;b&gt;서버는 비밀키로 복호화하여 대칭키를 획득&lt;/b&gt;합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;방식 2: DH/ECDHE 기반(TLS 1.2 ~ 1.3)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 성능과 보안상의 이유로 Diffie-Hellman 키 교환 방식이 더 많이 사용됩니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. 클라이언트와 서버는 서로 공개키 정보를 교환&lt;br /&gt;2. 서로의 정보를 조합하여 동일한 대칭키를 계산&lt;br /&gt;&amp;nbsp; &amp;nbsp; - 직접 키를 전달하지 않고도, 수학적 방식으로 같은 키를 공유&lt;br /&gt;3. 이후 해당 키를 통해 암호화 통신&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  이 방식은 키를 직접 전달하지 않고, &lt;b&gt;공개값과 개인키를 조합해 서로 같은 대칭키를 계산&lt;/b&gt;합니다.&lt;br /&gt;  이 과정을 통해 &lt;b&gt;중간자 공격(MITM)에 강하고, Perfect Forward Secrecy(PFS)&lt;/b&gt;를 보장합니다.&lt;br /&gt;  특히 TLS 1.3에서는 RSA 기반 방식이 제거되고, &lt;b&gt;ECDHE 방식만을 사용&lt;/b&gt;하도록 명시되어 있습니다.&lt;/p&gt;</description>
      <category>보안</category>
      <category>대칭키</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/154</guid>
      <comments>https://kurukurucoding.tistory.com/154#entry154comment</comments>
      <pubDate>Tue, 19 Aug 2025 23:18:49 +0900</pubDate>
    </item>
    <item>
      <title>UUID(Universally Unique Identifier) 버전과 사용법</title>
      <link>https://kurukurucoding.tistory.com/153</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;전 세계적으로 유일한 식별자를 생성하기 위한 표준 형식입니다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;업무에 적용해보자면, &amp;ldquo;다른 시스템과 충돌 없이 고유한 ID를 만들 수 있는 규격&amp;rdquo;입니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UUID는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분산환경에 걸쳐 ID 충돌 없이 식별자를 생성하기 위해 나타났습니다. (멀티 스레드, 멀티 프로세스 등등)&lt;br /&gt;특징으로는 126비트(16바이트)의 길이로 표기되며 32개의 16진수와 하이픈(-)의 조합 표현됩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예) 12341234-1234-1234-1234-123412341234&lt;br /&gt;특성상 전역적으로도 고유성을 띔으로 중앙 서버 없이도 가기 시스템이 독립적으로 ID 생성이 가능하게 되었습니다.&lt;br /&gt;대표적으로 DB PK, 분산 시스템 메시지 ID, 세션 토큰, 객체 식별자 등 다양하게 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UUID 버전이 있는데&amp;hellip;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;버전별 생성 방식이 다름으로 레거시를 조심합시다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;RFC 4122 표준에서 정의된 대표 버전과 최근 채택된 v6, v7을 다음과 같이 정리했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table style=&quot;height: 506px; width: 856px;&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 78px;&quot;&gt;
&lt;td style=&quot;width: 62px; height: 78px; text-align: center;&quot;&gt;&lt;b&gt;버전&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 406px; height: 78px; text-align: center;&quot;&gt;&lt;b&gt;생성 방식&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 388px; height: 78px; text-align: center;&quot;&gt;&lt;b&gt;특징과 사용 용도&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 62px; height: 18px;&quot;&gt;&lt;b&gt;&lt;b&gt;v1&lt;/b&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 406px; height: 18px;&quot;&gt;시간(60비트)&amp;nbsp;+&amp;nbsp;MAC&amp;nbsp;주소(48비트)&amp;nbsp;+&amp;nbsp;시퀀스&lt;/td&gt;
&lt;td style=&quot;width: 388px; height: 18px;&quot;&gt;시간&amp;nbsp;순&amp;nbsp;정렬&amp;nbsp;가능,&amp;nbsp;하드웨어&amp;nbsp;주소&amp;nbsp;노출&amp;nbsp;위험&amp;nbsp;있음&lt;br /&gt;로그&amp;middot;이벤트&amp;nbsp;타임라인,&amp;nbsp;순차&amp;nbsp;PK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 78px;&quot;&gt;
&lt;td style=&quot;width: 62px; height: 78px;&quot;&gt;&lt;b&gt;v2&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 406px; height: 78px;&quot;&gt;v1 + POSIX UID/GID&lt;/td&gt;
&lt;td style=&quot;width: 388px; height: 78px;&quot;&gt;거의 사용되지 않음, DCE Security 전용&lt;br /&gt;&lt;br /&gt;오래된 DCE 시스템&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 58px;&quot;&gt;
&lt;td style=&quot;width: 62px; height: 58px;&quot;&gt;&lt;b&gt;v3&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 406px; height: 58px;&quot;&gt;네임스페이스 + MD5 해시&lt;/td&gt;
&lt;td style=&quot;width: 388px; height: 58px;&quot;&gt;같은 입력 &amp;rarr; 같은 UUID, 재현 가능&lt;br /&gt;&lt;br /&gt;URL, 도메인 기반 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 58px;&quot;&gt;
&lt;td style=&quot;width: 62px; height: 58px;&quot;&gt;&lt;b&gt;v4&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 406px; height: 58px;&quot;&gt;난수 기반 (122비트 랜덤)&lt;/td&gt;
&lt;td style=&quot;width: 388px; height: 58px;&quot;&gt;단순, 충돌 확률 매우 낮음&lt;br /&gt;&lt;br /&gt;범용 ID, 세션 토큰&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 58px;&quot;&gt;
&lt;td style=&quot;width: 62px; height: 58px;&quot;&gt;&lt;b&gt;v5&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 406px; height: 58px;&quot;&gt;네임스페이스 + SHA-1 해시&lt;/td&gt;
&lt;td style=&quot;width: 388px; height: 58px;&quot;&gt;v3과 동일하지만 SHA-1 사용&lt;br /&gt;&lt;br /&gt;URL, 도메인 기반 ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 40px;&quot;&gt;
&lt;td style=&quot;width: 62px; height: 40px;&quot;&gt;&lt;b&gt;v6&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 406px; height: 40px;&quot;&gt;v1 변형 (시간 필드를 비트 순서 변경해 정렬 친화성&amp;uarr;)&lt;/td&gt;
&lt;td style=&quot;width: 388px; height: 40px;&quot;&gt;DB 인덱스&amp;middot;정렬 최적화, MAC 주소 포함 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 98px;&quot;&gt;
&lt;td style=&quot;width: 62px; height: 98px;&quot;&gt;&lt;b&gt;v7&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 406px; height: 98px;&quot;&gt;Unix Epoch 타임스탬프(밀리초) + 랜덤 비트&lt;/td&gt;
&lt;td style=&quot;width: 388px; height: 98px;&quot;&gt;가독성&amp;middot;정렬성&amp;uarr;, MAC 주소 사용 안 함, 충돌 방지&lt;br /&gt;&lt;br /&gt;로그, 타임라인, PK 생성 (v4 대체 후보)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UUID의 역사에 대해 조사한 내용&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;1980년대 초반&lt;/b&gt;&lt;br /&gt;UUID 개념은 &lt;b&gt;Apollo Computer&lt;/b&gt;의 네트워크 OS에서 처음 사용됨. 당시에는 &amp;ldquo;NCS Identifier&amp;rdquo;라는 이름.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;1988년&lt;/b&gt;&lt;br /&gt;&lt;b&gt;DCE(Distributed Computing Environment)&lt;/b&gt; 표준에 채택. 이 시기부터 &amp;ldquo;UUID&amp;rdquo;라는 용어 사용.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;1997년&lt;/b&gt;&lt;br /&gt;UUID 규격이 &lt;b&gt;ISO/IEC 11578&lt;/b&gt; 표준으로 지정됨. v1 형식(시간+MAC) 기반.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;2005년&lt;/b&gt;&lt;br /&gt;&lt;b&gt;RFC 4122&lt;/b&gt;가 발표되며 현재 우리가 사용하는 UUID 형식(버전 1~5) 정의.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최근&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;UUIDv4&lt;/b&gt; (랜덤 기반) 사용이 가장 보편적.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;UUIDv7&lt;/b&gt; 같은 시간 기반 + 랜덤 혼합 규격이 IETF에서 논의 중(정렬성 + 난수 안정성 확보 목적).&lt;/li&gt;
&lt;li&gt;대규모 분산 시스템에서는 &lt;b&gt;ULID&lt;/b&gt;, &lt;b&gt;KSUID&lt;/b&gt; 등 더 정렬 친화적인 대안도 등장.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;많이 사용되는 UUID의 Bit 구성을 보면&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;562&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/96PBk/btsPVBRlyeO/tvTxCJgUSIKPq5MqDqUBck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/96PBk/btsPVBRlyeO/tvTxCJgUSIKPq5MqDqUBck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/96PBk/btsPVBRlyeO/tvTxCJgUSIKPq5MqDqUBck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F96PBk%2FbtsPVBRlyeO%2FtvTxCJgUSIKPq5MqDqUBck%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;890&quot; height=&quot;562&quot; data-origin-width=&quot;890&quot; data-origin-height=&quot;562&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노랑 &amp;rarr; 시간 정보(타임스탭프 기반)&lt;/li&gt;
&lt;li&gt;보라 &amp;rarr; 버전 비트(UUID 버전 식별)&lt;/li&gt;
&lt;li&gt;파랑 &amp;rarr; variant나 Clock Sequence&lt;/li&gt;
&lt;li&gt;연두 &amp;rarr; MAC 주소나 랜덤 비트&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;MSB는 주로 시간, 버전&lt;/li&gt;
&lt;li&gt;LSB는 랜덤, MAC&lt;br /&gt;쪽에 위치한다는 걸 직관적으로 확인할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그래서 어느 버전을 사용할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;v1&lt;/code&gt;을 보면 시간순으로 정렬 가능하지만, 비트 순서가 DB 인덱스에 친화적이지 않으며 MAC 주소 노출 문제가 있습니다.&lt;br /&gt;&lt;code&gt;V4&lt;/code&gt;는 순서성이 전혀 없어 인덱스 성능이 떨어집니다.&lt;br /&gt;&lt;code&gt;v6&lt;/code&gt;은 &lt;code&gt;v1&lt;/code&gt;의 시간 기반 구조를 재배열하여 정렬성과 성능 최적화를 하였습니다.&lt;br /&gt;&lt;code&gt;v7&lt;/code&gt;은 &lt;code&gt;v4&lt;/code&gt; + 시간 순서성을 결합해 MAC 주소 없이 안전하게 정렬 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 UUID는 &lt;code&gt;v4&lt;/code&gt;를 사용하고 있습니다. 앱 내부적으로 사용하고 시간과 무관한 값이면 &lt;code&gt;UUID v4&lt;/code&gt;를 사용해도 됩니다.&lt;br /&gt;다만, 시간과 정렬 최적화가 필요하다면 &lt;code&gt;UUID v7&lt;/code&gt;을 사용하는 것이 바람직하다고 할 수 있습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;v6, v7의 시간 기준은 다르다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;v7&lt;/code&gt;을 현재 시스템 구조에 맞게 생성된 ID이며 유닉스기반 시간으로 작성하고 로그, 메트릭 등 다양한 시스템 통합에 장점이 있습니다.&lt;br /&gt;&lt;code&gt;v6&lt;/code&gt;과의 차이는 시간 기준과 타 시스템 연계가 다릅니다.&lt;br /&gt;&lt;code&gt;v6&lt;/code&gt; 시간은 그레고리안 에폭(1582-10-15) 기준이며 최신 시스템과의 시간 연동을 하려면 추가 변환이 필요합니다.&lt;br /&gt;그렇다면 &lt;code&gt;v7&lt;/code&gt;은 유닉스 에폭(1970-01-01) 기준입니다. 레거시에 따라 적용하되 신규 시스템은 v7을 권장합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;UUID v7에서 값 뽑는 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UUID v7는 시간순 정렬 가능하고 랜덤성이 보장됩니다.&lt;br /&gt;랜덤 부분에서 하위 일부 비트만 사용해서 짧은 값 생성도 용이합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;UUID를 어떻게 사용해야할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대게 실무에서는 UUID를 ID로 저장하는 경우 BINARY(16)로 DB에 저장합니다.&lt;br /&gt;UUID v7 자체적으로 고유성과 시간정렬이 가능하기 때문입니다.&lt;br /&gt;또한, 사람이 보기 좋은 짧은 값으로도 전환하여 저장하기도 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용방법 예시&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;규칙 및 조건&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UUID ID 생성하고 저장합니다.&lt;/li&gt;
&lt;li&gt;위 기반으로 사람이 읽기 좋은 값으로 0-9, A-Z(36자리)의 조합으로 6자리로 표현한다고 해봅시다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예) yyyyMMdd-ABCDEF&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UUID 비트 일부를 잘라서 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;UUID의 동시대 기준 많이 변화되는 LSB를 잘라서 인코딩용으로 사용합니다.&lt;/li&gt;
&lt;li&gt;참고) MSB XOR LSB로 사용하면 추가적으로 랜덤성을 극대화 시킬 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;36^6 = 약 21억 &amp;rarr; 하루 수천만 건 이상 주문 처리에도 안전합니다.&lt;/li&gt;
&lt;li&gt;6자리 미만이 나올 경우 맨 앞자리에 &amp;ldquo;0&amp;rdquo;을 붙여 줍니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;구현을 해보겠습니다.&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;불변성으로 값 객체용인 record를 활용했습니다.&lt;/li&gt;
&lt;li&gt;UUID v7을 사용하기 위해 외부 라이브러리를 사용했습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;java 내부 기능으로 UUID v4를 사용합니다.&lt;/li&gt;
&lt;li&gt;com.github.f4b6a3:uuid-creator 라이브러리를 추가합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;import com.github.f4b6a3.uuid.UuidCreator;  
import java.time.*;  
import java.time.format.DateTimeFormatter;  
import java.util.UUID;    

public record OrderId(  
        UUID uuid  
) {  
    private static final DateTimeFormatter FORMATTER = DateTimeFormatter.BASIC_ISO_DATE;  
    private static final char[] BASE36 = &quot;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;.toCharArray();  

    public String createNo() {  
        // UUID의 적용된 시간을 가져옵니다.
        long epochMillis = (uuid.getMostSignificantBits() &amp;gt;&amp;gt;&amp;gt; 16) &amp;amp; 0x0000_FFFF_FFFF_FFFFL;  
        String ymd = Instant.ofEpochMilli(epochMillis)
                    .atZone(ZoneId.systemDefault())
                    .toLocalDate()
                    .format(FORMATTER);
        // 랜덤성을 향상 시키기 위해 MSB xor LSB 이후 하위 32bit를 가져옵니다.
        long randomBits = (uuid.getLeastSignificantBits() ^ uuid.getMostSignificantBits()) 
                        &amp;amp; ((1L &amp;lt;&amp;lt; 32) - 1);  
        // 최대 자리 수를 맞추기 위해 remainder를 이용하여 값 도출합니다.(랜덤성 데이터라 무관)
        long value = randomBits % (long) Math.pow(36, 6);  
        // 나름? 인코딩된 값을 가져옵니다.
        String enc = toBase(value);
        // 5자리 이하는 맨 앞자리에 &quot;0&quot;을 붙여줍니다.  
        if (enc.length() &amp;lt; 6) enc = &quot;0&quot;.repeat(6 - enc.length()) + enc;  
        // yyyyMMdd-ABCDEF 형태로 반환합니다.
        return ymd + &quot;-&quot; + enc;  
    }  

    private String toBase(long value) {  
        if( value == 0 ) return &quot;0&quot;;  
        StringBuilder sb = new StringBuilder();  
        long v = value;  
        int length = BASE36.length;  
        while( v &amp;gt; 0) {  
            sb.append(BASE36[(int) (v % length)]);  
            v /= length;  
        }  
        return sb.reverse().toString();  

    }


    public static OrderId of(UUID value) {  
        Assert.notNull(value, &quot;uuid must not be null&quot;);  
        return new OrderId(value);  
    }  

    public static OrderId create() {  
        return of(UuidCreator.getTimeOrderedEpoch());  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;UUID의 적용된 시간을 가져옵니다.&lt;/li&gt;
&lt;li&gt;랜덤성을 향상 시키기 위해 MSB xor LSB 이후 하위 32bit를 가져옵니다.&lt;/li&gt;
&lt;li&gt;최대 자리 수를 맞추기 위해 remainder를 이용하여 값 도출합니다.(랜덤성 데이터라 무관)&lt;/li&gt;
&lt;li&gt;toBase를 통해 나름? 인코딩된 값을 가져옵니다.&lt;/li&gt;
&lt;li&gt;5자리 이하는 맨 앞자리에 &quot;0&quot;을 붙여줍니다.&lt;/li&gt;
&lt;li&gt;yyyyMMdd-ABCDEF 형태로 반환합니다.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>보안</category>
      <category>java</category>
      <category>uuid</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/153</guid>
      <comments>https://kurukurucoding.tistory.com/153#entry153comment</comments>
      <pubDate>Sun, 10 Aug 2025 17:53:34 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] DFS(Depth-First Search, 깊이 우선 탐색) 문제 풀이 가이드</title>
      <link>https://kurukurucoding.tistory.com/152</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DFS(Depth-First Search,깊이 우선 탐색)&lt;/b&gt; 는 그래프나 트리와 같은 자료구조에서 탐색을 진행할 떄 사용하는 대표적인 알고리즘입니다.&lt;br /&gt;특정 노드를 시작점으로 잡고 최대한 &lt;b&gt;깊은 경로&lt;/b&gt;까지 탐색하다가, 더 이상 탐색할 곳이 없으면 &lt;b&gt;백트래킹&lt;/b&gt;을 통해 돌아오며 탐색하지 않은 경로를 다시 탐색합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;즉, 깊게 먼저 들어가며 탐색하는 방식&lt;/b&gt;입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DFS의 핵심 원리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 노드를 선택해 방문한 후, 인접한 노드를 따라 갈 수 있는 곳까지 깊이 들어갑니다.&lt;br /&gt;더 이상 이동할 곳이 없으면 이전 노드로 돌아와서 다시 탐색을 시작합니다.&lt;br /&gt;방문한 곳을 기록해, 이미 방문한 노드는 재탐색하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &amp;ldquo;한쪽 방향 끝까지 먼저 파고든다&amp;rdquo;라는 생각하면 직관적으로 이해할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DFS 탐색 순서 예시&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;      1 (root node)
    /   \
   2     5
  / \   / \
 3   4 6   7&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;탐색 순서: 1 &amp;rarr; 2 &amp;rarr; 3 &amp;rarr; 4 &amp;rarr; 5 &amp;rarr; 6 &amp;rarr; 7&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DFS의 시간 복잡도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 &lt;b&gt;O(N)&lt;/b&gt; ~ &lt;b&gt;O(N&amp;sup2;)&lt;/b&gt; 까지 다양합니다. 다만, 각 문제의 요구조건에 따라 가지치기(pruning) 기법으로 활용하여 탐색 횟수를 크게 줄일 수 있습니다.&lt;br /&gt;즉, 조건에 부합하지 않은 탐색 경로를 미리 차단(return)하여 불필요한 탐색을 방지할 수 있습니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java 코드로 DFS 알고리즘 가이드 보기&lt;/h2&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// 그래프를 인접리스트로 표현 (예시)
List&amp;lt;Integer&amp;gt;[] graph = new ArrayList[5];
for (int i = 0; i &amp;lt; graph.length; i++) {
    graph[i] = new ArrayList&amp;lt;&amp;gt;();
}
// 간선 연결 예시 (0&amp;rarr;1, 0&amp;rarr;2, 1&amp;rarr;3, 2&amp;rarr;4)
graph[0].add(1);
graph[0].add(2);
graph[1].add(3);
graph[2].add(4);

boolean[] visited = new boolean[5];  // 방문 여부 체크

dfs(0, graph, visited, 0);  // 0번 노드에서 DFS 탐색 시작, 초기 깊이는 0

// DFS 재귀 메서드 구현
public void dfs(int node, List&amp;lt;Integer&amp;gt;[] graph, boolean[] visited, int depth) {
    // 특정 조건에 따라 탐색을 중단할 수 있음 (가지치기)
    // 예시: 깊이가 3 이상이면 탐색 중지
    // if (depth &amp;gt;= 3) return;

    visited[node] = true; // 현재 노드를 방문 처리, 다음 깊이를 위해
    System.out.println(&quot;방문 노드: &quot; + node + &quot;, 현재 깊이: &quot; + depth);

    // 현재 노드와 연결된 모든 인접 노드를 탐색
    for (int next : graph[node]) {
        if (!visited[next]) { // 방문하지 않은 노드만 탐색
            dfs(next, graph, visited, depth + 1);
        }
    }

    // 다른 경로 탐색을 위한 백트래킹 처리
    visited[node] = false;
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 흐름 간략 정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현내 노드 방문 처리(visited[node] = true)&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DFS가 유용한 문제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그래프 탐색 및 경로 탐색 관련 문제
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;길찾기: 특정 노드에서 도달 가능한 모든 노드 찾기&lt;/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;미로 찾기, 조합(Combination), 순열(Permutation) 등&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;전위(Pre-order)&lt;/li&gt;
&lt;li&gt;정위(Post-order)&lt;/li&gt;
&lt;li&gt;후위(In-order)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;연결 요소(Connected Component) 개수 찾기 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;요약&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DFS는&amp;nbsp;그래프나&amp;nbsp;트리처럼&amp;nbsp;특정&amp;nbsp;노드부터&amp;nbsp;최대한&amp;nbsp;깊이&amp;nbsp;들어가면서&amp;nbsp;탐색하는&amp;nbsp;알고리즘이며&amp;nbsp;가능한&amp;nbsp;깊게&amp;nbsp;탐색을&amp;nbsp;진행하고,&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;탐색할&amp;nbsp;수&amp;nbsp;없을&amp;nbsp;때&amp;nbsp;이전으로&amp;nbsp;되돌아가&amp;nbsp;탐색하지&amp;nbsp;않은&amp;nbsp;노드를&amp;nbsp;다시&amp;nbsp;방문하는&amp;nbsp;방식입니다.&lt;br /&gt;&lt;br /&gt;DFS는&amp;nbsp;탐색&amp;nbsp;대상의&amp;nbsp;전체&amp;nbsp;구조를&amp;nbsp;파악하거나&amp;nbsp;완전&amp;nbsp;탐색할&amp;nbsp;때&amp;nbsp;유용합니다.&lt;br /&gt;&lt;br /&gt;반면,&amp;nbsp;최단거리와&amp;nbsp;같이&amp;nbsp;최적의&amp;nbsp;경로를&amp;nbsp;찾는&amp;nbsp;문제에서는&amp;nbsp;일반적으로&amp;nbsp;BFS가&amp;nbsp;더&amp;nbsp;효율적이며,&amp;nbsp;그래프가&amp;nbsp;매우&amp;nbsp;깊고&amp;nbsp;넖을&amp;nbsp;때&amp;nbsp;DFS는&amp;nbsp;스택&amp;nbsp;오버플로우가&amp;nbsp;발생할&amp;nbsp;가능성이&amp;nbsp;있습니다.&lt;/p&gt;</description>
      <category>CS/알고리즘 &amp;amp; 자료구조</category>
      <category>BFS</category>
      <category>dfs</category>
      <category>알고리즘</category>
      <category>풀이</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/152</guid>
      <comments>https://kurukurucoding.tistory.com/152#entry152comment</comments>
      <pubDate>Mon, 14 Jul 2025 13:53:31 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 슬라이딩 윈도우 풀이 가이드</title>
      <link>https://kurukurucoding.tistory.com/151</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개념&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;창문을 한칸 씩 이동해서 문제가 요구하는 조건을 체크하는 것입니다.&lt;br /&gt;가이드 순서는 아래와 같습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;윈도우에 담을 기존 원소(데이터)를 확인합니다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;윈도우의 left, right 포인터를 0부터 시작합니다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;left는 윈도우의 왼쪽 끝, right는 왼도우의 오른쪽 끝을 나타냅니다.&lt;/li&gt;
&lt;li&gt;조건에 따라 시작 지점은 변경합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;right를 하나씩 증가시키면서 배열을 순회합니다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;right가 증가할 때마다 기존 데이터를 기반으로 새로운 원소(데이터)를 반영합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;윈도우에 추가된 원소를 문제의 조건에 맞춰 처리합니다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;left ~ right를 순회해서 처리합니다.&lt;/li&gt;
&lt;li&gt;예 빈도수 계산, 함계 계산, 최댓값/최솟값 업데이트 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;문제의 조건을 벗어나면(right 로직 마지막) 조건을 만족할 때까지 left 포인터를 오른쪽으로 이동시킵니다.&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 과정에서 윈도우에서 원소가 갱신됩니다.&lt;/li&gt;
&lt;li&gt;윈도우의 유효셩을 체크합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;매번 윈도우를 업데이트할 때마다 결과를 저장하거나 갱신합니다.&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java 코드로 확인해보자&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 내의 합 문제&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int[] arr = {1, 3, 2, 5, 1, 1, 2, 3};
int targetSum = 8;

// 슬라이딩 윈도우의 두 포인터 left, right 선언
int left = 0, windowSum = 0;

// right를 기준으로 배열 전체를 순회
for (int right = 0; right &amp;lt; arr.length; right++) {

    // right가 가리키는 원소를 윈도우에 포함
    windowSum += arr[right];

    // 윈도우 내 합이 조건(targetSum)을 초과하면, 초과하지 않을 때까지 left를 오른쪽으로 이동
    while (windowSum &amp;gt; targetSum) {
        windowSum -= arr[left]; // left 원소를 윈도우에서 제거
        left++;                 // left 포인터 이동
    }

    // 조건을 만족하면 현재 윈도우 범위를 출력
    if (windowSum == targetSum) {
        System.out.println(&quot;조건 만족 윈도우: [&quot; + left + &quot;, &quot; + right + &quot;]&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;코드 흐름을 간단하게 정리하면&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;right로 원소 추가 &amp;rarr; 윈도우 합 증가하고&lt;br /&gt;조건을 만족하지 않으면 left로 원소 제거 &amp;rarr; 윈도우 합 감소&lt;br /&gt;조건 만족 시 현재 윈도우 범위 출력&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;슬라이딩 윈도우가 유용한 문제 유형은?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;연속된 구간의 합&lt;/b&gt; 또는 &lt;b&gt;특정 조건을 만족하는 부분 배열&lt;/b&gt; 찾기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최대 길이 부분 배열, 최소 길이 부분 배열&lt;/b&gt; 찾는 문제&lt;/li&gt;
&lt;li&gt;문자열 또는 배열에서 &lt;b&gt;중복 제거 후 연속 구간 찾기&lt;/b&gt; 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 포인터로 윈도우 범위를 유지하고 right 순회에서 원소 추가하고, left로 조건을 유지하는 것입니다.&lt;/p&gt;</description>
      <category>CS/알고리즘 &amp;amp; 자료구조</category>
      <category>슬라이딩윈도우</category>
      <category>알고리즘</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/151</guid>
      <comments>https://kurukurucoding.tistory.com/151#entry151comment</comments>
      <pubDate>Mon, 14 Jul 2025 11:34:12 +0900</pubDate>
    </item>
    <item>
      <title>단방향 암호화(해시 알고리즘)</title>
      <link>https://kurukurucoding.tistory.com/150</link>
      <description>&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단방향은 왜 해싱을 사용할까? 해싱은 복호화가 불가능한 방식이 때문에 사용하는 것입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style7&quot; /&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;단방향 해싱의 특징&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값을 일정한 길이의 공전된 값으로 변환하여 DB컬럼 길이를 일정하게 유지가 가능합니다.&lt;br /&gt;복호화가 불가능하므로 원본으로 돌릴 수 없기에 데이터 탈취해도 무의미 합니다.&lt;br /&gt;항상 같은 입력값은 항상 같은 출력값을 나옵니다. 그래서 이러한 입력에 대한 출력 테이블 있으면 해킹이 될거 같은데라고 생각할 수 있습니다. 그래서 레인보우 테이블이란 데이터 집합으로 보안에 문제가 발생한 적이 있었죠&amp;hellip; 이후에는 salt 라는 기법으로 이 방법을 해결합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해시로 저장해야 할까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비밀번호 등 복호화하지 않아야 할 데이터입니다. 즉, 누가 탈취해도 알 수 없기에 굉장히 안정적입니다. 예로 앱의 패스워드가 해싱을 적용한다면 앱의 관계자는 패스워드 복호화 할 수 없기 때문에 사용자 측면에서 안정성 &amp;amp; 신뢰성이 생깁니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 가능한 해시 알고리즘&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 145px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;&lt;b&gt;알고리즘&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;&lt;b&gt;복호화 가능 여부&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;&lt;b&gt;비밀번호에 적합 여부&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 20px;&quot;&gt;&lt;b&gt;비고&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;MD5&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;❌ 단방향&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;❌ 위험 (빠름, 충돌 다발)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;SHA-256&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;❌ 단방향&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;⚠️ 보완 필요 (솔트 없음)&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;단독 사용 금지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;&lt;b&gt;bcrypt&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;❌ 단방향&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;✅ 권장&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;솔트 + 느린 계산 = 안전&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;scrypt&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;❌ 단방향&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;✅ 권장&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;메모리 의존성 &amp;uarr;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;Argon2&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;❌ 단방향&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;✅ 최신 권장&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 25px;&quot;&gt;2015년 비밀번호 해시 대회 수상&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;(참고) Spring Security 에서는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 bcrypt를 사용합니다. 스프링에서는 bcrypt 암호화를 하게 되면 {bcrypt} 라는 접두어로 암호화된 값 앞에 알고리즘 식별용으로 접두어를 적용합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;DelegatingPasswordEncoder&lt;/b&gt;를 통해 암호흘 생성할 떄만 {암호화 알고리즘} 접두어 추가됩니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;BCryptPasswordEncoder&lt;/b&gt; 직접 사용하면 {암호화 알고리즘}은 붙지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
String encoded = encoder.encode(&quot;password&quot;);
System.out.println(encoded); // {bcrypt}로 시작하는 문자열 출력&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 110px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style14&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;width: 26.1628%; height: 20px;&quot;&gt;&lt;b&gt;항목&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 73.8372%; height: 20px;&quot;&gt;&lt;b&gt;내용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.1628%; height: 18px;&quot;&gt;단방향 암호화 = 해싱?&lt;/td&gt;
&lt;td style=&quot;width: 73.8372%; height: 18px;&quot;&gt;✅ 맞음. 복호화 불가&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.1628%; height: 18px;&quot;&gt;해시만으로 충분한가?&lt;/td&gt;
&lt;td style=&quot;width: 73.8372%; height: 18px;&quot;&gt;❌ Salt 필수 (충돌, 무차별 대입 방지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.1628%; height: 18px;&quot;&gt;비밀번호 해싱 추천 알고리즘&lt;/td&gt;
&lt;td style=&quot;width: 73.8372%; height: 18px;&quot;&gt;✅ bcrypt, ✅ Argon2, ✅ PBKDF2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.1628%; height: 18px;&quot;&gt;{bcrypt} 의미&lt;/td&gt;
&lt;td style=&quot;width: 73.8372%; height: 18px;&quot;&gt;Spring Security의 알고리즘 식별자 prefix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 26.1628%; height: 18px;&quot;&gt;직접 {bcrypt} 붙이나?&lt;/td&gt;
&lt;td style=&quot;width: 73.8372%; height: 18px;&quot;&gt;❌ 아님. PasswordEncoder가 자동으로 붙임&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;</description>
      <category>보안</category>
      <category>springsecurity</category>
      <category>단반향</category>
      <category>해시</category>
      <author>크크크크</author>
      <guid isPermaLink="true">https://kurukurucoding.tistory.com/150</guid>
      <comments>https://kurukurucoding.tistory.com/150#entry150comment</comments>
      <pubDate>Sat, 12 Jul 2025 14:14:06 +0900</pubDate>
    </item>
  </channel>
</rss>