<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>To be data engineer</title>
    <link>https://yjjdevelop.tistory.com/</link>
    <description>컴퓨터 그래픽스, 유니티, c# 
게임 클라이언트 개발 
CS 공부 
github.com/YeongJUnJoH171 
</description>
    <language>ko</language>
    <pubDate>Sat, 24 Oct 2026 12:35:04 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>CBNA</managingEditor>
    <image>
      <title>To be data engineer</title>
      <url>https://tistory1.daumcdn.net/tistory/6920727/attach/8474c9a84be943a590b283a3a14b7e2d</url>
      <link>https://yjjdevelop.tistory.com</link>
    </image>
    <item>
      <title>Hash - 금융 보안에서의 중요성</title>
      <link>https://yjjdevelop.tistory.com/63</link>
      <description>&lt;h2 data-end=&quot;126&quot; data-start=&quot;121&quot; data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1022&quot; data-start=&quot;128&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;152&quot; data-start=&quot;128&quot;&gt;해시는 왜 금융 보안에서 중요한가?&lt;/li&gt;
&lt;li data-end=&quot;221&quot; data-start=&quot;153&quot;&gt;해시 함수 기본 개념 정리&lt;br /&gt;2-1. 단방향성&lt;br /&gt;2-2. 충돌 저항성&lt;br /&gt;2-3. 눈사태 효과&lt;/li&gt;
&lt;li data-end=&quot;339&quot; data-start=&quot;222&quot;&gt;비밀번호 저장에 쓰이는 해시 (고객 정보 보호)&lt;br /&gt;3-1. 평문 저장의 위험&lt;br /&gt;3-2. 솔트 + 해시 + 반복(PBKDF2)&lt;br /&gt;3-3. 파이썬 예제: 회원가입/로그인 비밀번호 검증&lt;/li&gt;
&lt;li data-end=&quot;461&quot; data-start=&quot;340&quot;&gt;거래 데이터 무결성 검증 (전문 위변조 방지)&lt;br /&gt;4-1. 전송 중 데이터가 바뀌지 않았는지 확인하기&lt;br /&gt;4-2. 해시값을 이용한 무결성 체크&lt;br /&gt;4-3. 파이썬 예제: 파일/전문 무결성 검증&lt;/li&gt;
&lt;li data-end=&quot;584&quot; data-start=&quot;462&quot;&gt;전자서명과 해시 (계약, 대출, 공인문서)&lt;br /&gt;5-1. 전자서명에서 해시가 하는 일&lt;br /&gt;5-2. 해시를 먼저 하고, 그다음에 서명하는 이유&lt;br /&gt;5-3. 파이썬 예제: &amp;ldquo;서명 전 단계&amp;rdquo; 해시 처리&lt;/li&gt;
&lt;li data-end=&quot;711&quot; data-start=&quot;585&quot;&gt;API 인증에서의 HMAC 해시 (오픈뱅킹, 내부 API)&lt;br /&gt;6-1. 단순 토큰보다 HMAC이 안전한 이유&lt;br /&gt;6-2. 요청 본문 + 시크릿키 조합&lt;br /&gt;6-3. 파이썬 예제: HMAC 기반 요청 서명&lt;/li&gt;
&lt;li data-end=&quot;833&quot; data-start=&quot;712&quot;&gt;블록체인과 해시 (거래 이력 위조를 어렵게 만드는 장치)&lt;br /&gt;7-1. 블록 헤더와 이전 해시 연결&lt;br /&gt;7-2. 하나만 바꿔도 줄줄이 전부 깨지는 구조&lt;br /&gt;7-3. 파이썬 예제: 장난감 블록체인&lt;/li&gt;
&lt;li data-end=&quot;984&quot; data-start=&quot;834&quot;&gt;금융권에서 해시를 안전하게 쓰기 위한 팁&lt;br /&gt;8-1. 빠른 해시(SHA-256)를 그대로 비밀번호에 쓰면 안 되는 이유&lt;br /&gt;8-2. 솔트, 키 스트레칭(PBKDF2, bcrypt, Argon2 등)&lt;br /&gt;8-3. 직접 암호 알고리즘 만들지 말 것&lt;/li&gt;
&lt;li data-end=&quot;1022&quot; data-start=&quot;985&quot;&gt;정리: 해시는 &amp;ldquo;숫자 바꾸는 함수&amp;rdquo;가 아니라 금융 보안의 기반&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-end=&quot;1108&quot; data-start=&quot;1083&quot; data-ke-size=&quot;size26&quot;&gt;1. 해시는 왜 금융 보안에서 중요한가?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1420&quot; data-start=&quot;1110&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1181&quot; data-start=&quot;1110&quot;&gt;인터넷 뱅킹, 카드 결제, 오픈뱅킹 API, 신용평가 시스템까지 대부분의 금융 서비스는 &amp;ldquo;데이터&amp;rdquo;와 &amp;ldquo;신원&amp;rdquo;을 다룹니다.&lt;/li&gt;
&lt;li data-end=&quot;1270&quot; data-start=&quot;1182&quot;&gt;이때 중요한 건 &lt;b&gt;(1) 정보가 중간에 바뀌지 않았는지&lt;/b&gt;, 그리고 &lt;b&gt;(2) 누군가의 비밀 정보(비밀번호, 키)가 그대로 노출되지 않는지&lt;/b&gt;입니다.&lt;/li&gt;
&lt;li data-end=&quot;1420&quot; data-start=&quot;1271&quot;&gt;해시(hash)는 이 두 가지를 모두 도와주는, 금융 보안의 핵심 도구 중 하나입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1420&quot; data-start=&quot;1324&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1354&quot; data-start=&quot;1324&quot;&gt;비밀번호를 &amp;ldquo;원래 값으로 되돌릴 수 없게&amp;rdquo; 저장&lt;/li&gt;
&lt;li data-end=&quot;1385&quot; data-start=&quot;1357&quot;&gt;전송된 메시지가 중간에 바뀌지 않았는지 검증&lt;/li&gt;
&lt;li data-end=&quot;1420&quot; data-start=&quot;1388&quot;&gt;전자서명, 블록체인, API 인증 등에서 핵심 역할&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-end=&quot;1425&quot; data-start=&quot;1422&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;1447&quot; data-start=&quot;1427&quot; data-ke-size=&quot;size26&quot;&gt;2. 해시 함수 기본 개념 정리&lt;/h2&gt;
&lt;h3 data-end=&quot;1462&quot; data-start=&quot;1449&quot; data-ke-size=&quot;size23&quot;&gt;2-1. 단방향성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1557&quot; data-start=&quot;1464&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1507&quot; data-start=&quot;1464&quot;&gt;입력 &amp;rarr; 해시값은 쉽지만, 해시값 &amp;rarr; 입력은 사실상 불가능해야 합니다.&lt;/li&gt;
&lt;li data-end=&quot;1557&quot; data-start=&quot;1508&quot;&gt;이 성질 덕분에 비밀번호를 &amp;ldquo;복호화 가능한 암호&amp;rdquo;가 아니라 &amp;ldquo;해시값&amp;rdquo;으로 저장합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1764670243810&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hashlib

password = &quot;MySecurePassword123!&quot;
hashed = hashlib.sha256(password.encode(&quot;utf-8&quot;)).hexdigest()

print(&quot;원본 비밀번호:&quot;, password)
print(&quot;SHA-256 해시:&quot;, hashed)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-end=&quot;1759&quot; data-start=&quot;1744&quot; data-ke-size=&quot;size23&quot;&gt;2-2. 충돌 저항성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1850&quot; data-start=&quot;1761&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1806&quot; data-start=&quot;1761&quot;&gt;서로 다른 두 입력이 &lt;b&gt;같은 해시값&lt;/b&gt;을 갖지 않도록 만드는 성질입니다.&lt;/li&gt;
&lt;li data-end=&quot;1850&quot; data-start=&quot;1807&quot;&gt;완전히 불가능한 것은 아니지만, 현실적으로 찾기 매우 어렵도록 설계됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;1885&quot; data-start=&quot;1852&quot; data-ke-size=&quot;size23&quot;&gt;2-3. 눈사태 효과(Avalanche Effect)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1922&quot; data-start=&quot;1887&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1922&quot; data-start=&quot;1887&quot;&gt;입력을 아주 조금만 바꿔도 해시값이 완전히 달라져야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1764670262612&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;m1 = hashlib.sha256(b&quot;transfer 10000 to A&quot;).hexdigest()
m2 = hashlib.sha256(b&quot;transfer 10001 to A&quot;).hexdigest()

print(&quot;10000원 전송:&quot;, m1)
print(&quot;10001원 전송:&quot;, m2)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;2103&quot; data-start=&quot;2100&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;2137&quot; data-start=&quot;2105&quot; data-ke-size=&quot;size26&quot;&gt;3. 비밀번호 저장에 쓰이는 해시 (고객 정보 보호)&lt;/h2&gt;
&lt;h3 data-end=&quot;2157&quot; data-start=&quot;2139&quot; data-ke-size=&quot;size23&quot;&gt;3-1. 평문 저장의 위험&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2281&quot; data-start=&quot;2159&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2243&quot; data-start=&quot;2159&quot;&gt;DB에 password = &quot;abcd1234&quot; 이런 식으로 저장돼 있다면, DB가 한 번 털리는 순간 &lt;b&gt;모든 계정이 즉시 위험&lt;/b&gt;해집니다.&lt;/li&gt;
&lt;li data-end=&quot;2281&quot; data-start=&quot;2244&quot;&gt;그래서 금융권에서는 비밀번호를 절대 평문으로 저장하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2312&quot; data-start=&quot;2283&quot; data-ke-size=&quot;size23&quot;&gt;3-2. 솔트 + 해시 + 반복(PBKDF2)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2527&quot; data-start=&quot;2314&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2392&quot; data-start=&quot;2314&quot;&gt;단순히 SHA-256(password)만 쓰면, 공격자가 미리 해시값을 만들어둔 &lt;b&gt;레인보우 테이블&lt;/b&gt;로 쉽게 역추적할 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;2527&quot; data-start=&quot;2393&quot;&gt;그래서 보통:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;2527&quot; data-start=&quot;2405&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;2435&quot; data-start=&quot;2405&quot;&gt;사용자마다 랜덤한 &lt;b&gt;솔트(salt)&lt;/b&gt; 생성&lt;/li&gt;
&lt;li data-end=&quot;2497&quot; data-start=&quot;2438&quot;&gt;PBKDF2, bcrypt, Argon2 같은 **느린 해시(KDF)**로 수천~수만 번 반복 -&amp;gt; 여기서 느린 해시는 일부러 계산 비용을 크게 만든 해시 함수를 말한다.&amp;nbsp;&lt;/li&gt;
&lt;li data-end=&quot;2527&quot; data-start=&quot;2500&quot;&gt;salt와 해시 결과를 같이 저장&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;2574&quot; data-start=&quot;2529&quot; data-ke-size=&quot;size23&quot;&gt;3-3. 파이썬 예제: 회원가입/로그인 비밀번호 검증 (PBKDF2 사용)&lt;/h3&gt;
&lt;pre id=&quot;code_1764670285445&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import os
import hashlib
import hmac

# 회원가입 시 비밀번호 해시 생성
def hash_password(password: str, iterations: int = 100_000) -&amp;gt; dict:
    salt = os.urandom(16)  # 16바이트 랜덤 솔트
    dk = hashlib.pbkdf2_hmac(
        &quot;sha256&quot;,
        password.encode(&quot;utf-8&quot;),
        salt,
        iterations
    )
    return {
        &quot;salt&quot;: salt.hex(),
        &quot;iterations&quot;: iterations,
        &quot;hash&quot;: dk.hex()
    }

# 로그인 시 비밀번호 검증
def verify_password(password: str, stored: dict) -&amp;gt; bool:
    salt = bytes.fromhex(stored[&quot;salt&quot;])
    iterations = stored[&quot;iterations&quot;]
    expected_hash = bytes.fromhex(stored[&quot;hash&quot;])

    dk = hashlib.pbkdf2_hmac(
        &quot;sha256&quot;,
        password.encode(&quot;utf-8&quot;),
        salt,
        iterations
    )
    # 타이밍 공격 방지를 위한 안전한 비교
    return hmac.compare_digest(dk, expected_hash)

# 사용 예시
if __name__ == &quot;__main__&quot;:
    # 회원가입 단계
    stored_info = hash_password(&quot;MySecurePassword123!&quot;)
    print(&quot;DB에 저장할 정보:&quot;, stored_info)

    # 로그인 단계
    print(&quot;맞는 비밀번호?&quot;, verify_password(&quot;MySecurePassword123!&quot;, stored_info))
    print(&quot;틀린 비밀번호?&quot;, verify_password(&quot;WrongPassword&quot;, stored_info))&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;3695&quot; data-start=&quot;3692&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;3728&quot; data-start=&quot;3697&quot; data-ke-size=&quot;size26&quot;&gt;4. 거래 데이터 무결성 검증 (전문 위변조 방지)&lt;/h2&gt;
&lt;h3 data-end=&quot;3762&quot; data-start=&quot;3730&quot; data-ke-size=&quot;size23&quot;&gt;4-1. 전송 중 데이터가 바뀌지 않았는지 확인하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3899&quot; data-start=&quot;3764&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3853&quot; data-start=&quot;3764&quot;&gt;예:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3853&quot; data-start=&quot;3773&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3815&quot; data-start=&quot;3773&quot;&gt;A &amp;rarr; B로 10,000원을 보내는 전문이 네트워크를 타고 가는 동안&lt;/li&gt;
&lt;li data-end=&quot;3853&quot; data-start=&quot;3818&quot;&gt;누군가가 중간에서 값을 100,000으로 바꾸려 한다면?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;3899&quot; data-start=&quot;3854&quot;&gt;서버 입장에선 &amp;ldquo;이 메시지가 원래 보낸 그대로인지&amp;rdquo; 확인할 방법이 필요합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;3925&quot; data-start=&quot;3901&quot; data-ke-size=&quot;size23&quot;&gt;4-2. 해시값을 이용한 무결성 체크&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4048&quot; data-start=&quot;3927&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3981&quot; data-start=&quot;3927&quot;&gt;메시지와 함께 &lt;b&gt;해시값&lt;/b&gt;을 같이 전송하고, 수신 측에서 다시 해시를 계산해 비교합니다.&lt;/li&gt;
&lt;li data-end=&quot;4048&quot; data-start=&quot;3982&quot;&gt;다만, 이 해시값 자체도 보호해줘야 하므로 실제 시스템에서는 &lt;b&gt;전자서명&lt;/b&gt;이나 &lt;b&gt;HMAC&lt;/b&gt;과 같이 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;4079&quot; data-start=&quot;4050&quot; data-ke-size=&quot;size23&quot;&gt;4-3. 파이썬 예제: 파일/전문 무결성 검증&lt;/h3&gt;
&lt;pre id=&quot;code_1764670316196&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hashlib

def calc_sha256(data: bytes) -&amp;gt; str:
    return hashlib.sha256(data).hexdigest()

# 송신자
message = b&quot;transfer 10000 KRW from A to B&quot;
msg_hash = calc_sha256(message)
print(&quot;전송할 데이터:&quot;, message)
print(&quot;전송할 해시 :&quot;, msg_hash)

# 수신자
received_message = message  # 정상 케이스라 가정
received_hash = msg_hash

verify_hash = calc_sha256(received_message)
if verify_hash == received_hash:
    print(&quot;무결성 OK: 데이터가 변조되지 않았습니다.&quot;)
else:
    print(&quot;무결성 FAIL: 데이터가 중간에 바뀌었습니다.&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;4600&quot; data-start=&quot;4571&quot; data-ke-size=&quot;size26&quot;&gt;5. 전자서명과 해시 (계약, 대출, 공인문서)&lt;/h2&gt;
&lt;h3 data-end=&quot;4626&quot; data-start=&quot;4602&quot; data-ke-size=&quot;size23&quot;&gt;5-1. 전자서명에서 해시가 하는 일&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4749&quot; data-start=&quot;4628&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4695&quot; data-start=&quot;4628&quot;&gt;전자서명은 보통:
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;4695&quot; data-start=&quot;4642&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;4660&quot; data-start=&quot;4642&quot;&gt;문서 내용을 해시로 압축&lt;/li&gt;
&lt;li data-end=&quot;4695&quot; data-start=&quot;4663&quot;&gt;그 해시값에 개인키(private key)로 서명&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li data-end=&quot;4749&quot; data-start=&quot;4696&quot;&gt;이렇게 하면 문서가 &lt;b&gt;조금이라도 바뀌면 해시가 완전히 달라져서&lt;/b&gt;, 서명이 무효가 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;4783&quot; data-start=&quot;4751&quot; data-ke-size=&quot;size23&quot;&gt;5-2. 해시를 먼저 하고, 그다음에 서명하는 이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4898&quot; data-start=&quot;4785&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4850&quot; data-start=&quot;4785&quot;&gt;문서가 몇 MB, 몇십 MB가 되어도, 해시값은 항상 고정 길이(예: 256비트)라서 서명 연산이 빨라집니다.&lt;/li&gt;
&lt;li data-end=&quot;4898&quot; data-start=&quot;4851&quot;&gt;동시에, 해시의 &lt;b&gt;눈사태 효과&lt;/b&gt; 덕분에 위변조를 아주 잘 탐지할 수 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;4932&quot; data-start=&quot;4900&quot; data-ke-size=&quot;size23&quot;&gt;5-3. 파이썬 예제: &amp;ldquo;서명 전 단계&amp;rdquo; 해시 처리&lt;/h3&gt;
&lt;blockquote data-end=&quot;5021&quot; data-start=&quot;4934&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;5021&quot; data-start=&quot;4936&quot; data-ke-size=&quot;size16&quot;&gt;실제 전자서명은 RSA, ECDSA 같은 비대칭키 알고리즘이 필요하지만, 여기서는 &amp;ldquo;서명에 들어갈 해시를 만드는 단계&amp;rdquo;만 단순화해서 보여줄 수 있습니다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1764670340345&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hashlib

document = &quot;&quot;&quot;
대출 계약서
- 고객명: 홍길동
- 금액: 50,000,000 KRW
- 기간: 3년
&quot;&quot;&quot;

# 전자서명 전에 문서 내용을 해시로 압축
doc_hash = hashlib.sha256(document.encode(&quot;utf-8&quot;)).hexdigest()
print(&quot;문서 SHA-256 해시:&quot;, doc_hash)

# 실제 시스템에서는 이 해시(doc_hash)를 개인키로 서명해서 전자서명을 생성
# signature = sign_with_private_key(doc_hash)
# 검증 시에는 공개키로 서명을 검증하고, 문서를 다시 해싱해 해시가 같은지 비교&lt;/code&gt;&lt;/pre&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;hr data-end=&quot;5387&quot; data-start=&quot;5384&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;5427&quot; data-start=&quot;5389&quot; data-ke-size=&quot;size26&quot;&gt;6. API 인증에서의 HMAC 해시 (오픈뱅킹, 내부 API)&lt;/h2&gt;
&lt;h3 data-end=&quot;5458&quot; data-start=&quot;5429&quot; data-ke-size=&quot;size23&quot;&gt;6-1. 단순 토큰보다 HMAC이 안전한 이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5644&quot; data-start=&quot;5460&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5510&quot; data-start=&quot;5460&quot;&gt;그냥 Authorization: Bearer &amp;lt;token&amp;gt; 형태만 쓰는 것보다,&lt;/li&gt;
&lt;li data-end=&quot;5644&quot; data-start=&quot;5511&quot;&gt;&lt;b&gt;요청 내용 + 시크릿 키&lt;/b&gt;를 같이 해시한 &lt;b&gt;HMAC 서명&lt;/b&gt;을 쓰면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5644&quot; data-start=&quot;5558&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5597&quot; data-start=&quot;5558&quot;&gt;중간자 공격자가 요청을 수정해도 서명이 틀어져서 바로 탐지 가능&lt;/li&gt;
&lt;li data-end=&quot;5644&quot; data-start=&quot;5600&quot;&gt;서버와 클라이언트만 아는 키로 검증하므로, 외부에서 위조하기 매우 어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;5670&quot; data-start=&quot;5646&quot; data-ke-size=&quot;size23&quot;&gt;6-2. 요청 본문 + 시크릿키 조합&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5767&quot; data-start=&quot;5672&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5767&quot; data-start=&quot;5672&quot;&gt;보통 다음 정보들을 붙여서 HMAC 해시:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;5767&quot; data-start=&quot;5700&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;5724&quot; data-start=&quot;5700&quot;&gt;HTTP 메서드 (GET/POST&amp;hellip;)&lt;/li&gt;
&lt;li data-end=&quot;5737&quot; data-start=&quot;5727&quot;&gt;요청 URL&lt;/li&gt;
&lt;li data-end=&quot;5749&quot; data-start=&quot;5740&quot;&gt;타임스탬프&lt;/li&gt;
&lt;li data-end=&quot;5767&quot; data-start=&quot;5752&quot;&gt;요청 본문(body)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;5799&quot; data-start=&quot;5769&quot; data-ke-size=&quot;size23&quot;&gt;6-3. 파이썬 예제: HMAC 기반 요청 서명&lt;/h3&gt;
&lt;pre id=&quot;code_1764670364496&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hmac
import hashlib
import time

API_SECRET = b&quot;my-very-secret-key&quot;

def sign_request(method: str, path: str, body: str, timestamp: str) -&amp;gt; str:
    message = f&quot;{method}\n{path}\n{timestamp}\n{body}&quot;.encode(&quot;utf-8&quot;)
    signature = hmac.new(API_SECRET, message, hashlib.sha256).hexdigest()
    return signature

# 클라이언트에서 요청 보낼 때
method = &quot;POST&quot;
path = &quot;/api/v1/transfer&quot;
body = '{&quot;from&quot;:&quot;A&quot;,&quot;to&quot;:&quot;B&quot;,&quot;amount&quot;:10000}'
timestamp = str(int(time.time()))

signature = sign_request(method, path, body, timestamp)
print(&quot;요청 시 포함할 헤더 예시:&quot;)
print(&quot;X-Timestamp:&quot;, timestamp)
print(&quot;X-Signature:&quot;, signature)

# 서버에서 검증할 때도 동일한 방식으로 sign_request를 호출해서 값 비교&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;6474&quot; data-start=&quot;6471&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;6513&quot; data-start=&quot;6476&quot; data-ke-size=&quot;size26&quot;&gt;7. 블록체인과 해시 (거래 이력 위조를 어렵게 만드는 장치)&lt;/h2&gt;
&lt;h3 data-end=&quot;6539&quot; data-start=&quot;6515&quot; data-ke-size=&quot;size23&quot;&gt;7-1. 블록 헤더와 이전 해시 연결&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6685&quot; data-start=&quot;6541&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6616&quot; data-start=&quot;6541&quot;&gt;블록체인의 블록은 보통:
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6616&quot; data-start=&quot;6559&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6576&quot; data-start=&quot;6559&quot;&gt;현재 블록의 거래 데이터&lt;/li&gt;
&lt;li data-end=&quot;6593&quot; data-start=&quot;6579&quot;&gt;이전 블록의 해시값&lt;/li&gt;
&lt;li data-end=&quot;6616&quot; data-start=&quot;6596&quot;&gt;기타 메타데이터를 포함합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-end=&quot;6685&quot; data-start=&quot;6617&quot;&gt;이 구조 때문에, 과거의 거래를 조금만 바꿔도 &lt;b&gt;그 이후 모든 블록의 해시가 달라져서&lt;/b&gt; 전체 체인이 깨지게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;6717&quot; data-start=&quot;6687&quot; data-ke-size=&quot;size23&quot;&gt;7-2. 하나만 바꿔도 줄줄이 전부 깨지는 구조&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;6782&quot; data-start=&quot;6719&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;6782&quot; data-start=&quot;6719&quot;&gt;길게 연결된 도미노처럼, 앞쪽 블록을 건드리면 뒤쪽 블록까지 모두 영향이 가기 때문에 조작이 매우 어렵습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;6809&quot; data-start=&quot;6784&quot; data-ke-size=&quot;size23&quot;&gt;7-3. 파이썬 예제: 장난감 블록체인&lt;/h3&gt;
&lt;pre id=&quot;code_1764670395462&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import hashlib
from dataclasses import dataclass

def sha256_hex(data: str) -&amp;gt; str:
    return hashlib.sha256(data.encode(&quot;utf-8&quot;)).hexdigest()

@dataclass
class Block:
    index: int
    prev_hash: str
    data: str

    @property
    def hash(self) -&amp;gt; str:
        content = f&quot;{self.index}{self.prev_hash}{self.data}&quot;
        return sha256_hex(content)

# 체인 생성
genesis = Block(index=0, prev_hash=&quot;0&quot; * 64, data=&quot;Genesis Block&quot;)
block1 = Block(index=1, prev_hash=genesis.hash, data=&quot;A -&amp;gt; B : 10000&quot;)
block2 = Block(index=2, prev_hash=block1.hash, data=&quot;B -&amp;gt; C : 5000&quot;)

chain = [genesis, block1, block2]

print(&quot;=== 정상 체인 ===&quot;)
for b in chain:
    print(b.index, b.data, &quot;hash:&quot;, b.hash, &quot;prev_hash:&quot;, b.prev_hash)

# 중간 블록 데이터 조작
print(&quot;\n=== block1 데이터 조작 후 ===&quot;)
block1.data = &quot;A -&amp;gt; B : 1000000&quot;  # 10000 -&amp;gt; 1000000 으로 바꾸기
for b in chain:
    print(b.index, b.data, &quot;hash:&quot;, b.hash, &quot;prev_hash:&quot;, b.prev_hash)

# block2.prev_hash 는 여전히 '예전 block1.hash' 를 가리키기 때문에, 실제 시스템이라면 검증 실패&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-end=&quot;7815&quot; data-start=&quot;7812&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-end=&quot;7845&quot; data-start=&quot;7817&quot; data-ke-size=&quot;size26&quot;&gt;8. 금융권에서 해시를 안전하게 쓰기 위한다면?&lt;/h2&gt;
&lt;h3 data-end=&quot;7892&quot; data-start=&quot;7847&quot; data-ke-size=&quot;size23&quot;&gt;8-1. 빠른 해시(SHA-256)를 그대로 비밀번호에 쓰면 안 되는 이유&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;7984&quot; data-start=&quot;7894&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;7925&quot; data-start=&quot;7894&quot;&gt;SHA-256은 &lt;b&gt;빠르게&lt;/b&gt; 설계된 해시입니다.&lt;/li&gt;
&lt;li data-end=&quot;7984&quot; data-start=&quot;7926&quot;&gt;공격자도 GPU/ASIC으로 초당 수십억 번 시도할 수 있어서, 비밀번호 해싱에는 적합하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;8031&quot; data-start=&quot;7986&quot; data-ke-size=&quot;size23&quot;&gt;8-2. 솔트, 키 스트레칭(PBKDF2, bcrypt, Argon2 등)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8151&quot; data-start=&quot;8033&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8053&quot; data-start=&quot;8033&quot;&gt;각 사용자마다 랜덤 솔트 사용&lt;/li&gt;
&lt;li data-end=&quot;8108&quot; data-start=&quot;8054&quot;&gt;수천~수만 번 반복해서 계산 시간을 늘리기 (PBKDF2, bcrypt, Argon2 등)&lt;/li&gt;
&lt;li data-end=&quot;8151&quot; data-start=&quot;8109&quot;&gt;이렇게 해야 공격자가 비밀번호 하나를 깨는 데도 큰 비용이 들게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-end=&quot;8180&quot; data-start=&quot;8153&quot; data-ke-size=&quot;size23&quot;&gt;8-3. 직접 암호 알고리즘 만들지 말 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;8254&quot; data-start=&quot;8182&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;8215&quot; data-start=&quot;8182&quot;&gt;검증되지 않은 &amp;ldquo;자작 암호&amp;rdquo;는 거의 항상 취약합니다.&lt;/li&gt;
&lt;li data-end=&quot;8254&quot; data-start=&quot;8216&quot;&gt;표준화된 알고리즘과 검증된 라이브러리를 사용하는 것이 기본입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/63</guid>
      <comments>https://yjjdevelop.tistory.com/63#entry63comment</comments>
      <pubDate>Tue, 2 Dec 2025 19:28:17 +0900</pubDate>
    </item>
    <item>
      <title>Apache Spark</title>
      <link>https://yjjdevelop.tistory.com/61</link>
      <description>&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/apache-spark/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/what-is/apache-spark/&lt;/a&gt; 포스트를 참고해서 작성했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 왜 사용하는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Apache Spark 는 빅데이터를 빠르게 처리하기 위해 사용하는 분산 처리 엔진이다. 등장 배경을 이해하기 위해선 Hadoop MapReduce를 알아야 한다(이하 HM). HM은 병렬 분산 알고리즘을 사용해서 빅 데이터 세트를 처리하기 위한 프로그래밍 모델인데, 매번 디스크 I/O가 일어나기 때문에 속도가 느리다는 단점이 있었다. Spark는 인 메모리 처리를 수행하고, 작업의 단계 수를 줄이고, 여러 병렬 작업에서 데이터를 재사용하여 MapReduce의 한계를 해결하기 위해 만들어졌다.&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;- Spark는 데이터를 메모리에 올려놓고(Cache) 여러 연산에서 재사용할 수 있도록 하는 구조를 사용한다. -&amp;gt; 여러번 반복해서 계산하는 작업에 유리하다. 이를테면 머신러닝, 실시간 분석 같은 작업이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Spark는 하나의 프레임워크 안에서 배치 처리, SQL, 스트리밍 처리, 머신러닝, 그래프 분석까지 지원한다. 하나의 기술 스택 위에서 다양한 작업을 할 수 있다는 것이 장점이다 .&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;- Java, Scala, Python, R 등과 호환 가능한 범용성 높은 API가 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 메모리 기반 동작 - 빠르다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 한 프레임워크 내에서 다양한 분석이 가능 -&amp;gt; 아키텍처 단순화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. API가 잘 되어있어 개발 생산성이 높다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 어떻게 동작하는가&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Apache Spark는 &lt;b&gt;클러스터 환경에서 분산 계산을 수행하는 엔진&lt;/b&gt;으로 동작한다. 사용자는 로컬 코드에서 Spark API를 이용하여 연산을 정의하고, Spark는 이 연산을 클러스터 전체에 배포해서 병렬로 실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 단계로 동작을 이해할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1. Application 정의&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 개발자는 Python, Scala, Java, R 중 하나의 언어로 Spark 애플리케이션을 작성한다. 이 애플리케이션은 데이터를 어떻게 불러오고, 어떤 변환을 적용하고, 어떤 결과를 낼지에 대한 논리적인 파이프라인을 포함한다.&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;2-2 드라이버 프로세스&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 애플리케이션을 실행하면 드라이버 프로그램이 올라가고, 드라이버는 전체 작업의 계획을 세운다. 드라이버는 사용자가 정의한 연산들을 모아서 &lt;b&gt;DAG(Directed Acyclic Graph)&lt;/b&gt; 형태의 논리적 실행 계획을 만든다. &lt;b&gt;이 과정에서 연산 간 의존 관계와 병렬 실행 가능한 부분이 분석된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-3 클러스터 매니저와 실행기&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Spark는 YARN, Mesos, Kubernetes, 또는 자체 스탠드얼론 클러스터 매니저 위에서 실행될 수 있다. 클러스터 매니저는 노드들에 리소스를 할당하고, Spark는 각 노드에서 &lt;b&gt;Executor 프로세스&lt;/b&gt;를 띄워서 실제 연산을 수행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-4 RDD / DataFrame / Dataset과 Lazy Evaluation&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Spark는 내부적으로 &lt;b&gt;RDD(Resilient Distributed Dataset)&lt;/b&gt; 또는 그 위에 추상화된 &lt;b&gt;DataFrame&lt;/b&gt;과 같은 구조를 사용해서 데이터를 표현한다. 사용자가 map, filter, groupBy 같은 변환 연산을 호출해도, Spark는 즉시 실행하지 않고 &lt;b&gt;게으른 평가(lazy evaluation)&lt;/b&gt; 방식으로 연산 그래프만 쌓아 둔다. 최종적으로 count, show, write 같은 액션 연산이 호출될 때 비로소 전체 DAG를 최적화하여 실제 작업을 실행한다.&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;2-5 단계(Stage)와 작업(Task) 분할&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Spark는 전체 DAG를 &lt;b&gt;Stage&lt;/b&gt;와 &lt;b&gt;Task&lt;/b&gt; 단위로 쪼갠다. 셔플(shuffle)이 필요 없는 연속된 변환들은 하나의 Stage로 묶이고, 셔플 경계에서 Stage가 끊어진다. 각 Stage는 여러 개의 Task로 나뉘어 파티션 단위로 클러스터의 Executor에 병렬로 배포된다.&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;2-6 메모리 기반 처리와 캐시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Spark는 데이터를 메모리에 유지할 수 있기 때문에, 같은 데이터를 여러 번 사용하는 알고리즘에서 큰 이점을 가진다. 개발자는 cache() 또는 persist()를 호출해서 중간 결과를 메모리에 저장하고, 이후 연산에서 재사용할 수 있다. AWS 문서에서도 Spark가 인메모리 처리를 통해 MapReduce의 다단계 디스크 I/O 문제를 해결했다고 설명한다.&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;요약 : Spark는 사용자가 정의한 연산을 DAG로 분석하고, 이를 Stage와 Task로 나누어 Executor에 병렬로 배포하며, 메모리 기반 처리와 캐시를 활용해 빠르게 계산을 수행하는 방식으로 동작한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 장점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1 속도 : 인메모리 캐시, 최적화된 쿼리 실행 엔진으로 인해 낮은 지연 시간을 가진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2 개발자 친화적 : 고수준 API를 다양한 언어 환경에서 제공한다. -&amp;gt; 분산 처리를 직접 구현할 필요 없이 알아서 다 해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-3 다양한 워크로드 : Batch 처리, 실시간 스트리밍 분석, 머신러닝, 그래프 처리까지 모두 지원한다. 별도의 시스템을 여러 개 운영하는 부담을 줄일 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-4 연동성 : DFS, Amazon S3, Amazon Redshift, Cassandra, MongoDB 등 다양한 외부 스토리지 시스템과 연동하여 분석을 수행할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 세부 아키텍처 - Spark Core, Spark SQL, Spark Streaming, MLlib, GraphX&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-1 Spark Core : Spark 플랫폼의 기초이다. 메모리 관리, 장애 복구, 스케줄링, 작업 배포 및 모니터링, storage 시스템과의 상호작용을 담당한다.&amp;nbsp;&lt;b&gt;개발자는 고수준 API를 통해 Spark Core와 상호작용한다. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-2 Spark SQL :&amp;nbsp; Spark SQL은 분산 SQL 쿼리 엔진으로, 구조화된 데이터에 대한 SQL 기반 질의를 수행할 수 있게 해준다. 이 모듈은 비용 기반 최적화, 컬럼 지향 스토리지, 코드 생성 등의 기술을 활용해서 &lt;b&gt;매우 빠른 대화형 쿼리를 제공&lt;/b&gt;하며, 표준 SQL과 HiveQL을 사용할 수 있게 한다. 다양한 데이터 소스(HDFS, JSON, Parquet, Hive, 외부 DB 등)와 연동된다.&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;4-3 Spark Streaming : Spark Streaming은 &lt;b&gt;실시간 데이터 스트림을 처리하기 위한 모듈&lt;/b&gt;이다. 전통적인 Spark Streaming은 &lt;b&gt;입력 스트림을 작은 배치 단위(micro-batch)로 나누어 처리하는 방식으로 동작&lt;/b&gt;하며, 동일한 API로 배치 처리 코드와 스트리밍 코드를 함께 재사용할 수 있게 해준다. 이는 Twitter, Kafka, Flume, HDFS 등 다양한 소스에서 데이터를 받아 실시간 분석을 수행할 수 있도록 한다.&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;4-4 MLlib : MLlib는 Spark 위에서 구동되는 머신러닝 라이브러리이다. 이 라이브러리는 분류, 회귀, 클러스터링, 협업 필터링, 패턴 마이닝 등 다양한 알고리즘을 포함하며, 대규모 데이터셋에 대한 머신러닝을 효율적으로 수행하도록 설계되어 있다. MLlib는 R이나 Python 등으로 학습한 모델을 저장하고, Java나 Scala 기반 파이프라인에서 이를 로드하여 사용할 수 있는 구조를 제공한다.&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;4-5 GraphX : GraphX는 대규모 그래프 데이터를 처리하기 위한 모듈이다. 이 모듈은 ETL, 탐색적 분석, 반복적 그래프 계산을 지원하며, 그래프 알고리즘(페이지랭크, 연결 요소 탐색 등)을 대규모 분산 환경에서 실행할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이러한 모듈들이 모두 Spark Core 위에 올라가 있기 때문에, 하나의 클러스터와 하나의 API 세트로 다양한 분석 요구 사항을 충족할 수 있다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. Python, PySpark 예시&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1764137664680&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql import SparkSession
from pyspark.sql.functions import col, avg

# 1. SparkSession 생성
spark = SparkSession.builder \
    .appName(&quot;SimpleSparkExample&quot;) \
    .master(&quot;local[*]&quot;) \
    .getOrCreate()

# 2. 예제용 데이터 생성 (파이썬 리스트 &amp;rarr; DataFrame)
data = [
    (&quot;user1&quot;, &quot;A&quot;, 100),
    (&quot;user2&quot;, &quot;A&quot;, 200),
    (&quot;user3&quot;, &quot;B&quot;, 300),
    (&quot;user4&quot;, &quot;B&quot;, 150),
    (&quot;user5&quot;, &quot;C&quot;, 400),
]

columns = [&quot;user_id&quot;, &quot;group&quot;, &quot;amount&quot;]

df = spark.createDataFrame(data, schema=columns)

# 3. DataFrame 내용 확인
print(&quot;=== 원본 데이터 ===&quot;)
df.show()

# 4. 그룹별 평균 amount 계산
grouped_df = df.groupBy(&quot;group&quot;).agg(
    avg(col(&quot;amount&quot;)).alias(&quot;avg_amount&quot;)
)

print(&quot;=== 그룹별 평균 amount ===&quot;)
grouped_df.show()

# 5. 결과를 파일로 저장 (예: Parquet)
# 실제 환경에서는 HDFS, S3 등으로 저장 가능
grouped_df.write.mode(&quot;overwrite&quot;).parquet(&quot;./output/group_avg_amount&quot;)

# 6. SparkSession 종료
spark.stop()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 더 쉬운 예시&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot; 신용카드 거래 데이터를 하루 단위로 분석하는 배치 작업 &quot;&amp;nbsp;&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;p data-end=&quot;319&quot; data-start=&quot;277&quot; data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 데이터가 S3, HDFS, DW 등 저장소에 쌓인다:&lt;/p&gt;
&lt;p data-end=&quot;319&quot; data-start=&quot;277&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;transaction_id card_id amount merchant timestamp&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;T10001&lt;/td&gt;
&lt;td&gt;C123&lt;/td&gt;
&lt;td&gt;35000&lt;/td&gt;
&lt;td&gt;CU편의점&lt;/td&gt;
&lt;td&gt;2025-11-20 10:01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;T10002&lt;/td&gt;
&lt;td&gt;C555&lt;/td&gt;
&lt;td&gt;120000&lt;/td&gt;
&lt;td&gt;배달의민족&lt;/td&gt;
&lt;td&gt;2025-11-20 10:02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;td&gt;&amp;hellip;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark는 S3, HDFS, DW, MySQL 등 어디서든 데이터를 읽어올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764138116662&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql import SparkSession

spark = SparkSession.builder.appName(&quot;DailyBatchJob&quot;).getOrCreate()

# HDFS 또는 S3에 저장된 거래 로그 읽기
df = spark.read.parquet(&quot;s3://my-data/card_transactions/2025-11-20/&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 정재하는 것도 가능하다 !&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764138182122&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;df_clean = df.filter(df.amount &amp;gt; 0).dropna()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객별 일일 총 결제 금액 집계를 한다고 가정하자.&amp;nbsp; 다음과 같은 코드를 실행할 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764138221790&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;daily_sum = (
    df_clean.groupBy(&quot;card_id&quot;)
            .sum(&quot;amount&quot;)
            .withColumnRenamed(&quot;sum(amount)&quot;, &quot;daily_total&quot;)
)&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;이런 작업은 Shuffle을 발생시키는 대표적인 작업이다. Spark에서는 이를 내부적으로 어떻게 처리하냐면,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 각 노드에 분산된 데이터를 key 기준으로 정렬하고 병합한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 하나의 노드에서 카드별 합계를 계산한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 결과를 다시 분산 저장한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이상 거래 집계를 하고자 한다면,&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764139035396&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fraud_df = df_clean.filter(df_clean.amount &amp;gt; 1_000_000)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 방식으로 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가맹점별 매출 집계를 하고자 한다면&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764139152944&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;merchant_stats = (
    df_clean.groupBy(&quot;merchant&quot;)
            .agg(
                F.count(&quot;*&quot;).alias(&quot;count&quot;),
                F.sum(&quot;amount&quot;).alias(&quot;total_sales&quot;),
                F.avg(&quot;amount&quot;).alias(&quot;avg_sales&quot;)
            )
)&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;이런 식으로 할 수 있다. SQL을 사용하는 것과 별 다를 바 없다. 내부 처리가 엄청나게 효율적일 뿐이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과를 Warehouse 또는 S3에 저장하기 위해서 다음과 같은 코드를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764139221663&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;daily_sum.write.mode(&quot;overwrite&quot;).parquet(&quot;s3://reports/daily_sum/2025-11-20/&quot;)
merchant_stats.write.mode(&quot;overwrite&quot;).parquet(&quot;s3://reports/merchant_stats/2025-11-20/&quot;)&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;Airflow / Cron으로 배치 자동 실행 파이프라인을 만들 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. 클러스터와 노드&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Spark 에서 노드는 크게 세 종류가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7-1 Driver Node : 메인 컨트롤 노드이다. Job을 여러 Task로 나누고, Worker Node에게 일을 분배한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7-2 Worker Node : 실제로 데이터를 계산, 가공하는 노드이다. CPU + Memory를 사용해 Task를 실행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7-3 Cluster Manager : 클러스터 전체의 자원을 관리하고, Driver/Worker의 실행을 조율한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1354&quot; data-start=&quot;1332&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 1 - Driver 시작&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;1354&quot; data-start=&quot;1332&quot; data-ke-size=&quot;size16&quot;&gt;- 개발자가 &amp;ldquo;spark-submit&amp;rdquo;으로 Spark 프로그램 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Driver 노드가 실행됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1442&quot; data-start=&quot;1413&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 2 - 클러스터 매니저에게 자원 요청&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Driver는 Worker 노드 몇 개가 필요한지 요청&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) Worker 4개 할당받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1526&quot; data-start=&quot;1500&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 3 - Job을 Task로 쪼갬&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 10GB 데이터를 256MB씩 쪼개면 약 40개 블록&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Driver는 각 블록을 하나의 Task로 분리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Task 40개 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1634&quot; data-start=&quot;1607&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 4 - Worker에게 작업 분배&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Worker Node 1: Task 1~10&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Worker Node 2: Task 11~20&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Worker Node 3: Task 21~30&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Worker Node 4: Task 31~40&lt;/p&gt;
&lt;p data-end=&quot;1765&quot; data-start=&quot;1755&quot; data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 병렬 처리 시작&lt;/p&gt;
&lt;p data-end=&quot;1765&quot; data-start=&quot;1755&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1797&quot; data-start=&quot;1767&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 5 - Worker가 데이터 연산 실행&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 필터, 그룹바이, 조인 등 DataFrame 연산 수행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- CPU 코어를 모두 사용하며 parallel execution&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-end=&quot;1889&quot; data-start=&quot;1871&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Step 6 - 결과 수집&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Worker &amp;rarr; Driver로 결과 전송&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Driver가 최종 결과를 파일로 저장하거나 반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;여기서 클러스터는 여러 대의 컴퓨터를 묶어서 하나처럼 쓰는 시스템인 것이다. 그리고 노드는 클러스터를 구성하는 개별 컴퓨터이다. 노드는 물리적 서버, 가상 머신 등이 될 수 있다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8. 쿠버네티스와 Spark&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 쿠버네티스는 어떻게 클러스터가 되는가 : 쿠버네티스는 여러 서버를 묶어서 하나의 거대한 컴퓨팅 시스템처럼 작동한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spark에서는 다음과 같이 클러스터 종류를 자체적으로 선택할 수 있는데,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1. Standalone Cluster (Spark 자체 클러스터)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;2. YARN Cluster (하둡 생태계)&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;3. Kubernetes Cluster&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스도 그 중 하나이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쿠버네티스는 컨테이너 기반이기 때문에 클러스터 환경을 구성하기 쉽다는 장점이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764140273477&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pyspark.sql import SparkSession
from pyspark.sql.functions import col

spark = SparkSession.builder.appName(&quot;K8sSparkExample&quot;).getOrCreate()

# 예: S3 또는 HDFS에 저장된 거래 로그
df = spark.read.parquet(&quot;s3://bucket/transactions/2024/&quot;)

# 예: 2024년 1월 거래 합계 계산
result = df.groupBy(&quot;customer_id&quot;).sum(&quot;amount&quot;)

# 다시 S3에 저장
result.write.mode(&quot;overwrite&quot;).parquet(&quot;s3://bucket/processed/2024/&quot;)

spark.stop()


// 클러스터 실행 명령 

spark-submit \
  --master k8s://https://k8s-mycluster:6443 \
  --deploy-mode cluster \
  --name spark-k8s-job \
  --conf spark.executor.instances=4 \
  --conf spark.kubernetes.container.image=my-spark:latest \
  local:///app/job.py&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <category>CS</category>
      <category>kubernetes</category>
      <category>spark</category>
      <category>데이터엔지니어</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/61</guid>
      <comments>https://yjjdevelop.tistory.com/61#entry61comment</comments>
      <pubDate>Wed, 26 Nov 2025 16:01:28 +0900</pubDate>
    </item>
    <item>
      <title>Apache Kafka</title>
      <link>https://yjjdevelop.tistory.com/60</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/what-is/apache-kafka/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://aws.amazon.com/ko/what-is/apache-kafka/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글을 참고했다. 이 포스트는 내가 공부하려고 쓴 글이니 되도록 링크에 들어가서 하나하나 탐독하기를 권한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 왜 사용하는가?&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 두 가지로 생각할 수 있다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-1 : 실시간 스트리밍 데이터 파이프라인, 실시간 스트리밍 애플리케이션 구축&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 사용자 활동 데이터를 받아 유저들이 웹사이트를 어떻게 사용하는지 실시간으로 추척하는 데이터 파이프라인을 만들 수 있음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 단순하게 데이터를 수집 및 저장하는 것이 아니라, 애플리케이션에 read 서비스를 제공해 실시간으로 저장되는 데이터를 읽을 수 있게 함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-2 : 1-1 에서 본 특성으로 인해 두 애플리케이션 간의 통신을 처리하고 중재하는 '메세지 브로커 솔루션'으로 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 작동 원리 - 대기열 모델과 게시-구독 모델을 모두 사용한다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1 대기열 메세징 모델&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TNQOI/dJMcaiBHVCM/PlrJPi9BjSZzTpiXXKCDL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TNQOI/dJMcaiBHVCM/PlrJPi9BjSZzTpiXXKCDL0/img.png&quot; data-alt=&quot;사진 출처 : AWS&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TNQOI/dJMcaiBHVCM/PlrJPi9BjSZzTpiXXKCDL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTNQOI%2FdJMcaiBHVCM%2FPlrJPi9BjSZzTpiXXKCDL0%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;1692&quot; height=&quot;486&quot; data-origin-width=&quot;1692&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사진 출처 : AWS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 처리를 여러 소비자 인스턴스에 분산할 수 있다 -&amp;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;p data-ke-size=&quot;size16&quot;&gt;이게 뭔 말이냐 하면, 메세지가 한 줄로 쌓여 있을 때, 소비자가 여러 명이어도 각 메세지는 단 한명만 가져가게 된다. 이러면 병렬 처리가 가능하기 때문에 속도는 빠르지만, 여러 시스템이 동시에 동일한 메세지를 처리하고 싶어도 불가능 하다는 한계가 있다는 뜻이다!&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-2 게시-구독 메세징 모델&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1iB9J/dJMcachex2D/towRYskYTGNcvpFqbaW9G0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1iB9J/dJMcachex2D/towRYskYTGNcvpFqbaW9G0/img.png&quot; data-alt=&quot;사진 출처 : AWS&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1iB9J/dJMcachex2D/towRYskYTGNcvpFqbaW9G0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1iB9J%2FdJMcachex2D%2FtowRYskYTGNcvpFqbaW9G0%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;1246&quot; height=&quot;356&quot; data-origin-width=&quot;1246&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;사진 출처 : AWS&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하나의 메세지를 구독자 모두에게 그대로 복사해서 보내준다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 작업자에게 작업을 분산시킬 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-3 그런데 카프카는&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파티셔닝된 로그 모델을 사용해서 두 개의 솔루션을 통합한다. 핵심은 결국 &quot;Partition&quot;이라고 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS는 &quot;Kafka는 파티셔닝된 로그 모델을 사용하여 이러한 두 개의 솔루션을 통합합니다. 로그는 순서가 지정된 레코드 시퀀스이며 이러한 로그는 여러 구독자에 해당하는 세그먼트 또는 파티션으로 나뉩니다. 즉, 동일한 주제에 여러 구독자가 있을 수 있고 각 구독자에게 파티션을 할당하여 확장성을 높일 수 있습니다. 마지막으로 Kafka의 모델은 재생 기능을 제공하므로 데이터 스트림을 읽는 여러 독립 애플리케이션이 자체 속도에 따라 독립적으로 작동할 수 있습니다.&quot; 라고 말한다. 말이 좀 어려우니 풀어서 이해해보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka의 Topic(주제)은 여러 개의 파티션으로 나누어져 있다. 이때 파티션은 &quot;작업 라인&quot; 같은 개념이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파티션은 다음과 같은 특성을 가진다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 순서가 보장된 로그 파일이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 여러 파티션이 있으면 병렬 처리가 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하나의 파티션은 한 Consumer Group에서 단 한 소비자만 읽을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 서로 다른 Consumer Group은 각자 Topic을 처음부터 읽을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 방법을 통해 대기열 모델의 장점인 작업 분산 및 병렬 처리, 구독-게시 모델의 장점인 같은 메세지를 여러 시스템이 동시에 읽는 것을 모두 실현할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 또한 Kafka는 메세지를 소비 후 삭제하지 않는다. (Replay 기능)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 어떤 소비자는 느리게 읽어도 되며, 어떤 소비자는 빠르게 읽어도 되며, 처음부터 다시 읽어도 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 장점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파티셔닝된 로그 모델을 여러 서버에 분산할 수 있으므로 단일 서버에 담을 수 있는 수준 이상으로 데이터를 확장할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터 스트림을 분리하므로 지연 시간이 짧아 속도가 빠르다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데이터는 모두 디스크에 기록되기 때문에 데이터의 내결함성, 내구성을 높일 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 세부 아키텍쳐&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kafka에는 네 가지 API가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-1 : Producer API - 데이터를 넣는 담당이다. Kafka Topic에 메세지를 보내는 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 카드 승인 이벤트, 대출 신청 이벤트&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-2 : Consumer API - 데이터를 꺼내서 처리하는 담당이다. Kafka Topic의 레코드를 읽어서 처리하는 API이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 실시간 사기 감지 탐지 프로그램, ETL배치 작업 등이 소비자가 된다고 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-3 : Streams API - Kafka 안에서 연산하는 애플리케이션이다. Kafka에서 데이터를 읽고 -&amp;gt; 처리하고 -&amp;gt; 다시 Kafka에 쓴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) 카드 거래 5분 합계 계산, 모델 결과 스코어링, 실시간 이상 탐지 등&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-4 : Connect API - 외부 시스템과 Kafka를 자동 연결한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ex) PostgrSQL -&amp;gt; Kafka / Kafka -&amp;gt; HANA IQ 등&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 정리 및 궁금증&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Topic : Kafka에서 Topic 이란 메세지를 구분하는 논리적 단위이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 하나의 Topic을 여러 Consumer Group이 독립적으로 읽을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Topic A가 있고, Partition A1, A2가 있다고 하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Consumer Group B,C가 있다고 하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Group B는 A1/A2를 나눠서 읽고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Group C도 A1/A2를 나눠서 읽는다. (병렬)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 B,C는 독립적이다. (구독-게시)&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;B,C가 동시에 A1을 읽는 것이 어떻게 가능한가? -&amp;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;만약 Kafka에 새로운 데이터가 저장될 때 읽기 중인 Consumer들은 문제가 생기지 않는가?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 데이터는 항상 partition의 뒤쪽에 append된다. 또한 각 Consumer group은 각각 자신들이 읽은 offset을 기억한다. 때문에 읽기와 쓰기가 충돌할 일이 없다. 참고로 offset은 Kafka, Consumer 둘 다 관리한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 코드 예시 (Python)&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1764125596376&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install kafka-python // 카프카 설치 

1. 메세지 전송 -&amp;gt; Kafka로 메세지를 전송한다 

from kafka import KafkaProducer
import json
import time

producer = KafkaProducer(
    bootstrap_servers='localhost:9092',
    value_serializer=lambda v: json.dumps(v).encode('utf-8')
)

topic_name = 'my_topic'

for i in range(10):
    data = {&quot;id&quot;: i, &quot;msg&quot;: f&quot;hello-{i}&quot;}
    producer.send(topic_name, data)
    print(f&quot;Sent: {data}&quot;)
    time.sleep(1)

producer.flush()


2. Consumer 예제 : group A 

from kafka import KafkaConsumer
import json

consumer = KafkaConsumer(
    'my_topic',
    bootstrap_servers='localhost:9092',
    group_id='groupA',        # &amp;lt;-- Consumer Group A
    auto_offset_reset='earliest',
    enable_auto_commit=True,
    value_deserializer=lambda v: json.loads(v.decode('utf-8'))
)

print(&quot;Group A consumer started&quot;)

for message in consumer:
    print(f&quot;[Group A] Partition: {message.partition}, Offset: {message.offset}, Value: {message.value}&quot;)
   
   
3. Consumer 예제  : group B 

from kafka import KafkaConsumer
import json

consumer = KafkaConsumer(
    'my_topic',
    bootstrap_servers='localhost:9092',
    group_id='groupB',        # &amp;lt;-- Consumer Group B
    auto_offset_reset='earliest',
    enable_auto_commit=True,
    value_deserializer=lambda v: json.loads(v.decode('utf-8'))
)

print(&quot;Group B consumer started&quot;)

for message in consumer:
    print(f&quot;[Group B] Partition: {message.partition}, Offset: {message.offset}, Value: {message.value}&quot;)


4. offset 재조정 

auto_offset_reset='earliest'

5. 파티션 분배 확인하기 

# 터미널 1
python consumer_groupA_1.py

# 터미널 2
python consumer_groupA_2.py


6. 코드 실행 순서 

# Kafka broker 실행 

bin/zookeeper-server-start.sh ...
bin/kafka-server-start.sh ...

# Topic 생성 

bin/kafka-topics.sh --create --topic my_topic --bootstrap-server localhost:9092 --partitions 2 --replication-factor 1

# producer 실행
# Consumer 실행&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <category>Kafka</category>
      <category>데이터엔지니어</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/60</guid>
      <comments>https://yjjdevelop.tistory.com/60#entry60comment</comments>
      <pubDate>Wed, 26 Nov 2025 11:54:16 +0900</pubDate>
    </item>
    <item>
      <title>면접을 위한 JAVA 정리</title>
      <link>https://yjjdevelop.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;제가 다시 보려고 만든 건데, C#과 비슷한 부분이 많아서 생략된 부분이 많습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. C와의 차이점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JVM 위에서 실행&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;- 절차지향 vs 객체지향&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;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 접근 제어자&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- public : 모든 곳에서 접근 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- protected : 같은 패키지,&amp;nbsp; 상속 관계에서 접근'&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- default : 같은 패키지에서 접속 가능&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- private : 클래스 내부에서 접근 가능&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. OOP 객체 지향 프로그래밍&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 추상화 : 핵심만 명시하고 구체적인 부분은 내부에서 구현함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 캡슐화 : 변수 보호, getter, setter&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;p data-ke-size=&quot;size16&quot;&gt;# 오버라이딩 : 부모 클래스에서 상속받은 메소드를 자식 클래스에서 재정의&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 오버로딩 : 함수명을 리턴값에 따라 여러번 사용 가능&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. JVM과 메모리 구조&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- .java -&amp;gt; 컴파일 -&amp;gt; .class ( Bytecode 생성)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- JVM에 클래스를 로딩하고 -&amp;gt; 검증하고 -&amp;gt; 실행 엔진이 이를 해석, JIT 컴파일러가 최적화&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. JVM 메모리 영역&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Method Area : 클래스 정보를 담고 있음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Heap : 객체 저장 ( GC 포획 대상 )&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Stack : 메서드 호출, 지역 변수&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- PC Register : 실행 위치&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. GC&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 참조가 끊킨 객체를 Heap 에서 자동으로 회수&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. List, Set, Map&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- List : 순서 o, 중복 x&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Set : 순서 o, 중복 x&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Map : key - value 저장&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763360140274&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
list.add(&quot;A&quot;);
list.add(&quot;B&quot;);

Set&amp;lt;Integer&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
set.add(1);
set.add(1); // 무시됨

Map&amp;lt;String, Integer&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
map.put(&quot;철수&quot;, 20);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#HashMap: 비동기, 빠름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#Hashtable: 동기화 O, 하지만 전체 락 &amp;rarr; 느림&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;#ConcurrentHashMap: Segment 기반 락 &amp;rarr; 안전 + 빠름&lt;br /&gt;&amp;rarr; 금융권에서 다중 스레드를 안전하게 처리하기 때문에 ConcurrentHashMap을 가장 많이 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8. Thread, Runnable&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- thread 는 실행 단위, runnable은 실행할 작업을 정의&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763360168943&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class MyThread extends Thread {
    public void run() {
        System.out.println(&quot;스레드 실행&quot;);
    }
}

new MyThread().start();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;9. 동기화와 데드락&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 동기화 : 여러 스레드가 동시에 공유 자원에 접근하지 못하도록 락을 걸어 데이터 일관성 보장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 데드락 : 서로가 서로의 락을 기다리며 무한 대기가 걸린 상태&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;10. 예외 처리&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763360112071&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try {
    int x = 10 / 0;
} catch(Exception e) {
    System.out.println(&quot;에러!&quot;);
} finally {
    System.out.println(&quot;무조건 실행&quot;);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;11. Stream -&amp;gt; 반복문 대체&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763360194849&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;list.stream()
    .filter(x -&amp;gt; x.startsWith(&quot;A&quot;))
    .forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;12. 의존성 주입 - 외부에서 instance를 만들어서 주입시키는 것임.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763360483379&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class PasswordEncoder {
    String encode(String raw) { ... }
}

class UserService {
    private PasswordEncoder encoder = new PasswordEncoder();

    void register(String id, String pw) {
        String encoded = encoder.encode(pw);
        // 저장 ...
    }
}

// 문제점 
// Tight coupling이라 테스트가 어렵다 

// 의존성 주입 

class UserService {
    private final PasswordEncoder encoder;

    public UserService(PasswordEncoder encoder) {
        this.encoder = encoder;  // 외부에서 만든 객체를 주입
    }

    void register(String id, String pw) {
        String encoded = encoder.encode(pw);
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>CS</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/58</guid>
      <comments>https://yjjdevelop.tistory.com/58#entry58comment</comments>
      <pubDate>Mon, 17 Nov 2025 15:18:55 +0900</pubDate>
    </item>
    <item>
      <title>면접을 위한 C 정리</title>
      <link>https://yjjdevelop.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 컴파일 과정&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전처리 - #include, #define 같은 지시문 처리 -&amp;gt; 하나의 소스 코드로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴파일 - 어셈블리 코드로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어셈블 - 기계어를 담은 obj 파일로 변환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크 - 여러 오브젝트 파일과 라이브러리가 결합 -&amp;gt; 실행 가능한 바이너리 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 메모리 구성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;텍스트 - 코드 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 - 초기값이 있는 전역/정적 변수 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BSS -초기값이 없는 전역/정적 변수 저장 -&amp;gt; 프로그램 실행 시 0으로 초기화&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힙 - malloc과 같은 함수를 통해 할당하는 동적 메모리 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택 - 함수 호출 시 지역 변수, 매개변수, 리턴 주소 등 저장&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 선언과 정의&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선언 - 컴파일러에게 이름과 타입 정보를 알려주는 행위 / 정의 - 실제로 메모리 공간을 할당&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763356247628&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;extern int g_count; // 선언 -&amp;gt; g_count라는 정수형 전역 변수가 어딘가에 선언됨
int g_count = 0; // 정의 -&amp;gt; 실제 메모리 할당&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 변수의 Scope, Storage Duration&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지역 변수는 기본적으로 해당 블록이 끝나면 메모리가 해제됨.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전역 변수는 프로그램 시작부터 종료까지 저장 기간이 유지됨.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5.&amp;nbsp; Static 키워드의 전역/지역 에서의 동작&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전역 : static 선언 시 해당 변수나 함수의 링크 범위가 그 소스 파일 내부로 제한됨 -&amp;gt; 다른 파일에서 extern으로 접근 불가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 지역 : 변수의 스코프를 블록 내부로 제한시키는 것은 여전하지만, 저장 기간이 프로그램 종료 시까지로 늘어남.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. extern&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 다른 파일의 정의된 전역 변수나 함수를 참조할 때 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. 포인터 - 메모리 영역의 주소를 저장하는 변수&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763356524602&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
 
int main(void) {
 
    int num;    // 일반 변수 선언
    int * pnum;    // 포인터 선언
    
    pnum = &amp;amp;num;    // 포인터에 num 주소 대입
    *pnum = 10;    // 포인터로 변수 num에 10 
 
    printf(&quot;변수명으로 num 값 출력 : %d\n&quot;, num);
    printf(&quot;포인터로 num 값 출력 : %d\n&quot;, *pnum);
 
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;8. 배열과 포인터의 차이점&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열 : 같은 타입의 원소들이 연속된 메모리 공간에 저장된 집합&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 포인터 : 주소를 담을 수 있는 단일 변수&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 함수 인자로 배열을 전달할 때, 배열 이름은 첫 번째 원소를 가리키는 포인터다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 배열은 컴파일 타임에 크기가 정해지고, 가리키는 주소를 바꿀 수 없다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;9. 이중 포인터와 포인터 연산&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 포인터 연산 : 포인터에 정수를 더하거나 빼면 메모리 주소가 자료형의 크기만큼 증가&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이중 포인터 : 포인터를 가리키는 포인터&lt;/p&gt;
&lt;pre id=&quot;code_1763356984538&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
 
int main(void) {
    double num = 3.14;
    double* ptr = &amp;amp;num; // 포인터 변수 ptr로 num을 가리킴
    double** dptr = &amp;amp;ptr; // 이중 포인터 변수 dptr로 포인터 변수 ptr을 가리킴
 
    double* ptr2;
 
    printf(&quot;%9p %9p \n&quot;, ptr, *dptr); // ptr과 *dptr은 같으므로 동일한 출력결과를 보인다.
    printf(&quot;%9g %9g \n&quot;, num, **dptr); // **dpt과 num은 같으므로 동일한 출력결과를 보인다.
    ptr2 = *dptr; // ptr2 = ptr과 같은 의미
    *ptr2 = 10.99;
    printf(&quot;%9g %9g \n&quot;, num, **dptr); // num과 **dptr이 같으므로 동일한 출력결과를 보인다.
    return 0;
}&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;*ptr은 num의 값 / ptr은 num 변수의 주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*dptr = ptr = num 변수의 주소&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;**dptr = *ptr = num의 값 = 10&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ptr2 = *dptr &amp;lt;- ptr2가 가리키는 주소에 *dptr = ptr = num 변수의 주소를 넣음&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;*ptr2는 해당 주소가 가리키는 값 -&amp;gt; 10.99로 변경&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 num 값이 10.99로 바뀜&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;10.&amp;nbsp; void*&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 타입이 정해지지 않은 일반 포인터 -&amp;gt; 모든 포인터는 void*로 암시적 변환이 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- malloc은 어떤 타입의 메모리든 할당할 수 있음 -&amp;gt; 반환 타입이 void*이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 캐스팅 주의해야 함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;11. malloc, calloc, realloc, free&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- malloc : 지정한 바이트 수만큼 힙에서 메모리를 할당, 초기화 x&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- calloc : 원소 개수와 원소 크기를 인자로 받아 메모리를 할당, 0으로 초기화&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- realloc : 기존에 할당된 메모리 블록의 크기를 변경할 때 사용 -&amp;gt; 필요할 경우 새로운 영역으로 복사한 뒤 기존 영역을 해제 (주소가 바뀐다는 뜻임)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- free : alloc 함수로 할당한 메모리를 명시적으로 해제. 매 우 중 요&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;12. 메모리 누수와 dangling pointer, double free&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 메모리 누수 : 동적 메모리 해제 안해서 발생하는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- dangling pointer : 이미 free된 메모리를 가리키고 있는 포인터를 말함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- double free : 같은 메모리 블록을 두 번 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;13. Struct / Union&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Struct : 여러 멤버가 각자 별도의 메모리 공간을 차지함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Union : 여러 멤버가 동일한 메모리 공간을 공유함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 구조체(struct)의 크기는 멤버들의 크기 합보다 클 수 있다. Cpu가 정렬된 메모리 접근을 선호하기 때문에 padding 바이트 삽입하기 때문&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;14. Typedef&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 타입 이름 지정하는 것&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 금융권에서 특히 자주 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;15. const&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763357819107&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const int a = 10; // a 값 변경 불가 
const int *p // p가 가리키는 대상의 값은 변경할 수 없지만 p가 가리키는 대상의 주소는 변경할 수 있음
int * const p // p는 다른 주소를 가리킬 수 없다. 가리키는 값은 변경 가능&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;16. volatile&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 예기치 않게 변경될 수 있는 변수에 사용 -&amp;gt; 항상 메모리에서 실제 값을 다시 읽도록 보장함&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;17. 함수 포인터와 콜백&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763357922908&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int (*op)(int, int); // 두 개의 int 인자를 받아 int를 반환하는 함수를 가리킴&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콜백 함수는 뭔가 나중에 처리하기 위해 사용자로부터 전달받는 함수를 뜻하는데, 이때 함수 포인터가 사용됨&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;18. Call by value, Call by reference&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763358037195&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void swap(int *a, int *b) // 실제 변수의 값을 교환 
void swap(int a, int b) // 인자의 값을 복사해서 함수 내부로 전달&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;19. 문자열 표기 방식&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 마지막에 Null 문자가 들어있는 char 배열로 표현됨&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763358198258&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;:&quot;ABC&quot; -&amp;gt; {'A','B','C','\0'}
strlen, strcpy, strncpy, strcmp, strcat 등을 사용 
금융권에선 strncpy, snprintf처럼 복사 길이를 제한할 수 있는 함수를 선호&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;20. printf, sprintf, snprintf&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- printf : 포맷 문자열에 따라 내용을 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- sprintf : 결과를 버퍼에 저장, 버퍼 크기 전달하지 않음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- snprintf : 버퍼 포인터와 함께 버퍼의 최대 크기를 인자로 받음 -&amp;gt; 지정한 크기를 넘지 않게 출력&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;21. #include &amp;lt;&amp;gt; , #include ...&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전자 : 표준 라이브러리 헤더&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 후자 : 사용자 정의 헤더&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;22. #define&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전처리 단계에서 문자열 치환을 수행하는 함수&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763358437731&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#define SQUARE(x) ((x)*(x))
SQUARE(3)가 ((3)*(3))으로 치환&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 주의사항 : 타입 체크 이루어지지 않고, 디버깅 시에도 스택에 남지 않음 -&amp;gt; 금융에서는 잘 안쓰인다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;23. 헤더가드&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 헤더 파일 중복 포함 방지&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;24. UB - Undefined Behavior&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 배열 밖 메모리 접근, 초기화되지 않은 변수 사용, 0으로 나누기, singed 정수 오버플로우&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;25. 정수 오버플로우와 부동소숫점&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- signed 정수 오버플로우는 Undefined Behavior이며, 컴파일러가 예측 불가능한 최적화를 할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 부동 소수점은 0.1과 같은 수를 제대로 표기하지 못함&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;26. 빅 엔디언, 리틀 엔디언&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 엔디언 : 멀티바이트 데이터를 메모리에 저장하는 바이트 순서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 리틀 : 하위 바이트를 먼저 저장&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 빅 : 상위 바이트를 먼저 저장&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;# 단위가 바이트 = 8 bit 임에 주의하자&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 주고 받을 때 엔디언 처리 주의해야 함 htonl, htons, ntohl, ntohs 등으로 처리 (리틀 엔디언 사용 시)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;27. File I/O&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- fopen, fclose, fread, fwrite, fprintf, fscanf 등의 함수 사용&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;내부적으로, fopen을 호출하면, File 구조체와 함께 버퍼 메모리를 생성&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763359130470&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;쓰기&amp;gt;

FILE* fp = fopen(&quot;log.txt&quot;, &quot;w&quot;);
fprintf(fp, &quot;Hello\n&quot;);

#이 때 &amp;ldquo;Hello\n&amp;rdquo;이 바로 파일에 저장되는 게 아니라:
#&quot;Hello\n&quot; &amp;rarr; FILE 구조체 내부의 buffer에 저장
#버퍼가 꽉 차거나
#줄바꿈(\n 으로 line buffered일 때),
#fflush() 또는 fclose() 호출 시
#한 번에 디스크로 기록됨 -&amp;gt; 쓰기 버퍼링의 예시  

&amp;lt;읽기&amp;gt;

fread(buf, 1, 10, fp);

#실제로는 이렇게 동작함:
#OS로부터 파일을 큰 덩어리(예: 4KB)로 읽어서 버퍼에 저장
#요청한 10바이트만 딱 잘라서 buf에 줌
#다음 fread 요청 시, 버퍼에 남아 있는 것 먼저 사용
#버퍼가 다 소진되면 다음 덩어리를 읽음
#즉, 매 fread 때마다 디스크를 다시 읽는 게 아님.
#속도가 엄청 빨라짐.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;28. 스레드 세이프&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 내부에서 전역 상태를 사용하거나 정적 버퍼를 공유하면 여러 스레드가 동시에 호출됐을 때 데이터가 섞임&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;29. C 디버그 방법&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- valgrind, gdb 등 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/57</guid>
      <comments>https://yjjdevelop.tistory.com/57#entry57comment</comments>
      <pubDate>Mon, 17 Nov 2025 14:59:59 +0900</pubDate>
    </item>
    <item>
      <title>면접을 위한 DB 정리</title>
      <link>https://yjjdevelop.tistory.com/56</link>
      <description>&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;이번에 나이스 평가정보 면접을 보며 데이터베이스 공부를 다시 할 기회가 있었다.&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;(공부한 내용 위주로 면접이 흘러간 것은 아니었다 ㅜㅜ)&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;공부한 것이 아까워 주요 내용을 블로그에 남기려고 한다! 키워드 위주로~&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. SQL&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;1-1 행 삽입&lt;/p&gt;
&lt;pre id=&quot;code_1763040933200&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO employees (emp_id, name, dept, salary)
VALUES (1, 'Kim', 'IT', 5000);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;text-align: left;&quot; data-ke-size=&quot;size16&quot;&gt;다른 행에서 SELECT 해서 INSERT 하는 경우&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763040985028&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;INSERT INTO high_salary (emp_id, name, salary)
SELECT emp_id, name, salary
FROM employees
WHERE salary &amp;gt;= 5000;&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-2 테이블 구조 수정&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763043940311&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. 컬럼 추가 (ADD)
ALTER TABLE employees
ADD birth_date DATE;


-- 2. 컬럼 타입 변경 (MODIFY TYPE)
ALTER TABLE employees
MODIFY salary NUMBER(10, 2);


-- 3. 컬럼 이름 변경 (RENAME COLUMN)
ALTER TABLE employees
RENAME COLUMN name TO full_name;


-- 4. 컬럼 삭제 (DROP COLUMN)
ALTER TABLE employees
DROP COLUMN birth_date;


-- 5. DEFAULT 값 설정/변경
ALTER TABLE employees
MODIFY dept DEFAULT 'IT';


-- 6. NOT NULL 제약 추가/제거
ALTER TABLE employees
MODIFY salary NOT NULL;

ALTER TABLE employees
MODIFY salary NULL;   -- NOT NULL 제거


-- 7. 테이블 이름 변경
ALTER TABLE employees
RENAME TO workers;


-- 8. 제약조건 추가
ALTER TABLE employees
ADD CONSTRAINT pk_emp PRIMARY KEY (emp_id);

ALTER TABLE employees
ADD CONSTRAINT uq_phone UNIQUE (phone);

ALTER TABLE orders
ADD CONSTRAINT fk_emp
  FOREIGN KEY (emp_id) REFERENCES employees(emp_id);


-- 9. 제약조건 삭제
ALTER TABLE orders
DROP CONSTRAINT fk_emp;&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;1-3 테이블 업데이트&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763044094442&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. 가장 기본적이고 가장 중요한 UPDATE 패턴
UPDATE employees
SET salary = salary + 500
WHERE emp_id = 1001;


-- 2. 조건을 통해 여러 행을 선택적으로 업데이트하는 형태
UPDATE employees
SET dept = 'HR'
WHERE dept = 'IT';&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;1-4 튜플 삭제&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763044193885&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DELETE FROM employees
WHERE emp_id = 1001;&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;1-5 행 삽입 시 중복이 발생했을 때&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763044232615&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. (Oracle) 중복 발생 시 INSERT 대신 MERGE 사용
MERGE INTO employees e
USING (SELECT 1001 AS emp_id, 'Kim' AS name FROM dual) src
ON (e.emp_id = src.emp_id)
WHEN NOT MATCHED THEN
    INSERT (emp_id, name)
    VALUES (src.emp_id, src.name);
-- 중복(emp_id 동일)일 경우 INSERT 실행 X


-- 2. (PostgreSQL) 중복이면 INSERT 건너뛰기
INSERT INTO employees (emp_id, name)
VALUES (1001, 'Kim')
ON CONFLICT (emp_id) DO NOTHING;

-- 3. PostgreSQL의 UPSERT: 중복 발생 시 UPDATE 실행
INSERT INTO employees (emp_id, name, salary)
VALUES (1001, 'Kim', 5000)
ON CONFLICT (emp_id) 
DO UPDATE SET
    name = EXCLUDED.name,
    salary = EXCLUDED.salary;


-- 4. 특정 컬럼만 업데이트하고 싶을 때
INSERT INTO employees (emp_id, salary)
VALUES (2001, 4500)
ON CONFLICT (emp_id)
DO UPDATE SET
    salary = EXCLUDED.salary;&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;1-6 Groupby, Having&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763044335617&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. GROUP BY로 부서별 평균 급여 구하기
SELECT dept,
       AVG(salary) AS avg_salary
FROM employees
GROUP BY dept;


-- 2. HAVING으로 집계 조건 필터링 (평균 급여가 5000 이상인 부서만)
SELECT dept,
       AVG(salary) AS avg_salary
FROM employees
GROUP BY dept
HAVING AVG(salary) &amp;gt;= 5000;&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;1-7 Join&lt;/p&gt;
&lt;pre id=&quot;code_1763044493682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. INNER JOIN (양쪽 테이블에서 매칭되는 행만)
SELECT e.emp_id, e.name, d.dept_name
FROM employees e
INNER JOIN departments d
    ON e.dept_id = d.dept_id;


-- 2. LEFT JOIN (왼쪽은 모두, 오른쪽은 매칭될 때만)
SELECT e.emp_id, e.name, d.dept_name
FROM employees e
LEFT JOIN departments d
    ON e.dept_id = d.dept_id;


-- 3. RIGHT JOIN (오른쪽은 모두, 왼쪽은 매칭될 때만)
SELECT e.emp_id, e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d
    ON e.dept_id = d.dept_id;


-- 4. FULL OUTER JOIN (양쪽 모두, 매칭되면 합치고 없으면 NULL)
SELECT e.emp_id, e.name, d.dept_name
FROM employees e
FULL OUTER JOIN departments d
    ON e.dept_id = d.dept_id;


-- 5. CROSS JOIN (모든 조합 생성 = 곱집합)
SELECT e.emp_id, d.dept_name
FROM employees e
CROSS JOIN departments d;


-- 6. SELF JOIN (자기 자신과 조인)
SELECT e1.emp_id, e1.name, e2.name AS manager_name
FROM employees e1
LEFT JOIN employees e2
    ON e1.manager_id = e2.emp_id;&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;참고로 Full Outer Join == Left Join + Union + Right Join 이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1-8 단일행 서브쿼리와 다중행 서브쿼리 + 상관 서브쿼리&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763044596671&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. 단일행 서브쿼리 (결과가 1개 행/1개 값)
-- 예: 가장 높은 급여를 받는 직원 조회
SELECT *
FROM employees
WHERE salary = (
    SELECT MAX(salary)
    FROM employees
);


-- 2. 다중행 서브쿼리 (결과가 여러 행)
-- 예: 급여 상위 3명(emp_id)을 가진 직원만 조회
SELECT *
FROM employees
WHERE emp_id IN (
    SELECT emp_id
    FROM employees
    ORDER BY salary DESC
    FETCH FIRST 3 ROWS ONLY
);

-- 상관 서브쿼리 (Correlation Subquery)
-- 서브쿼리가 바깥 쿼리의 각 행을 참조하면서 반복 실행됨

-- 예: 각 직원의 부서 평균 급여보다 높은 직원만 조회
SELECT e.emp_id, e.name, e.salary, e.dept
FROM employees e
WHERE e.salary &amp;gt; (
    SELECT AVG(salary)
    FROM employees
    WHERE dept = e.dept   -- 바깥 쿼리의 값(dept)을 참조
);&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;1-9 집합 연산&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763044681248&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. UNION (중복 제거 후 합집합)
SELECT emp_id FROM employees_2023
UNION
SELECT emp_id FROM employees_2024;


-- 2. UNION ALL (중복 포함한 합집합)
SELECT emp_id FROM employees_2023
UNION ALL
SELECT emp_id FROM employees_2024;


-- 3. INTERSECT (교집합)
SELECT emp_id FROM employees_2023
INTERSECT
SELECT emp_id FROM employees_2024;


-- 4. MINUS (Oracle) / EXCEPT (PostgreSQL)
-- Oracle: 2023에는 있지만 2024에는 없는 직원
SELECT emp_id FROM employees_2023
MINUS
SELECT emp_id FROM employees_2024;&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;1-10 Coalesce&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763045811237&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- COALESCE: NULL을 다른 값으로 대체하는 함수
-- 여러 값 중 NULL이 아닌 첫 번째 값을 반환

-- 1. 기본 예시: NULL이면 'N/A'로 대체
SELECT COALESCE(phone, 'N/A') AS phone_display
FROM employees;


-- 2. 여러 후보 중 첫 번째 NULL이 아닌 값 선택
-- nickname -&amp;gt; name -&amp;gt; 'Unknown' 순으로 선택
SELECT COALESCE(nickname, name, 'Unknown') AS display_name
FROM users;&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;1-11 윈도우 함수&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763045875269&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. ROW_NUMBER: 각 행에 고유한 순번 부여
SELECT emp_id, name, salary,
       ROW_NUMBER() OVER (ORDER BY salary DESC) AS rn
FROM employees;


-- 2. RANK: 동일 값은 동일 순위, 다음 순위는 건너뜀(1,1,3...)
SELECT emp_id, name, salary,
       RANK() OVER (ORDER BY salary DESC) AS rk
FROM employees;


-- 3. DENSE_RANK: 동일 값은 동일 순위, 번호 건너뛰지 않음(1,1,2...)
SELECT emp_id, name, salary,
       DENSE_RANK() OVER (ORDER BY salary DESC) AS dr
FROM employees;


-- 4. SUM OVER: 누적합 (선형 집계)
SELECT emp_id, salary,
       SUM(salary) OVER (ORDER BY emp_id) AS running_total
FROM employees;


-- 5. PARTITION BY: 그룹별 윈도우 연산
SELECT dept, emp_id, salary,
       AVG(salary) OVER (PARTITION BY dept) AS dept_avg
FROM employees;&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;1-12 누계합, 이동평균&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763045906753&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. 누계합(Running Total, Cumulative Sum)
SELECT emp_id,
       salary,
       SUM(salary) OVER (ORDER BY emp_id) AS cumulative_salary
FROM employees;


-- 2. 이동평균(Moving Average, Rolling Average)
-- 최근 3개 행 기준 이동평균 예시
SELECT emp_id,
       salary,
       AVG(salary) OVER (
           ORDER BY emp_id
           ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
       ) AS moving_avg_3
FROM employees;&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;1-13 그룹 별 값이 차지하는 비율&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763045948172&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 파티션(그룹)별 비율 계산
-- 각 부서(dept) 안에서 직원의 salary가 차지하는 비율

SELECT dept,
       emp_id,
       salary,
       salary / SUM(salary) OVER (PARTITION BY dept) AS salary_ratio
FROM employees;&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;1-14 LAG, LEAD&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763045988062&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 1. LAG: 이전 행의 값을 가져오기
SELECT emp_id,
       salary,
       LAG(salary, 1) OVER (ORDER BY emp_id) AS prev_salary
FROM employees;


-- 2. LEAD: 다음 행의 값을 가져오기
SELECT emp_id,
       salary,
       LEAD(salary, 1) OVER (ORDER BY emp_id) AS next_salary
FROM employees;&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;1-15 문자, 시간, CASE&lt;/p&gt;
&lt;pre id=&quot;code_1763046051145&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 문자열 길이
SELECT LENGTH(name) AS len
FROM employees;

-- 문자열 일부 추출
SELECT SUBSTR(name, 1, 3) AS first_three
FROM employees;

-- 대문자/소문자 변환
SELECT UPPER(name), LOWER(name)
FROM employees;

-- 문자열 결합
SELECT CONCAT(first_name, ' ', last_name) AS full_name
FROM employees;

-- 공백 제거
SELECT TRIM('   hello   ') AS trimmed;


-- 오늘 날짜 (Oracle: SYSDATE, PostgreSQL/MySQL: CURRENT_DATE)
SELECT SYSDATE AS today
FROM dual;

-- 날짜 + 7일
SELECT SYSDATE + 7 AS next_week
FROM dual;

-- 두 날짜 차이(일 단위)
SELECT end_date - start_date AS days_diff
FROM projects;

-- 날짜의 특정 부분 추출
SELECT EXTRACT(YEAR FROM hire_date) AS hire_year,
       EXTRACT(MONTH FROM hire_date) AS hire_month
FROM employees;



-- 조건에 따라 값 분류
SELECT emp_id,
       salary,
       CASE 
           WHEN salary &amp;gt;= 6000 THEN 'High'
           WHEN salary &amp;gt;= 4000 THEN 'Medium'
           ELSE 'Low'
       END AS salary_level
FROM employees;

-- NULL 처리에도 사용 가능
SELECT emp_id,
       CASE 
           WHEN phone IS NULL THEN 'No Phone'
           ELSE phone
       END AS phone_display
FROM employees;&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;1-16 Grouping과 Rolling&lt;/p&gt;
&lt;pre id=&quot;code_1763046248948&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 부서별 급여 합 + 전체 총합
SELECT dept,
       job,
       SUM(salary) AS total_salary,
       GROUPING(dept) AS grp_dept,
       GROUPING(job) AS grp_job
FROM employees
GROUP BY ROLLUP (dept, job);

-- GROUPING(dept) = 1 &amp;rarr; dept가 총합(=NULL로 표시되는 집계 행)
-- GROUPING(job)  = 1 &amp;rarr; job이 총합 행

-- dept &amp;rarr; job 순서로 단계적 부분합 생성
SELECT dept,
       job,
       SUM(salary) AS total_salary
FROM employees
GROUP BY ROLLUP (dept, job);

-- 결과 예시 형태(개념)
-- dept   | job      | total_salary
-- ---------------------------------
-- IT     | dev      | 10000
-- IT     | tester   | 8000
-- IT     | NULL     | 18000   &amp;larr; IT 부서 subtotal
-- HR     | analyst  | 7000
-- HR     | NULL     | 7000    &amp;larr; HR 부서 subtotal
-- NULL   | NULL     | 25000   &amp;larr; 전체 total&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;1-17 CTE와 재귀 CTE (조직도)&lt;/p&gt;
&lt;pre id=&quot;code_1763046306310&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- CTE 
WITH high_salary AS (
    SELECT emp_id, name, salary
    FROM employees
    WHERE salary &amp;gt;= 5000
)
SELECT *
FROM high_salary;


-- Recursive CTE
WITH RECURSIVE org AS (
    -- Anchor: 최고 관리자(루트)
    SELECT emp_id, name, manager_id, 1 AS level
    FROM employees
    WHERE manager_id IS NULL

    UNION ALL

    -- Recursive: 하위 직원 연결
    SELECT e.emp_id, e.name, e.manager_id, o.level + 1
    FROM employees e
    JOIN org o ON e.manager_id = o.emp_id
)
SELECT *
FROM org
ORDER BY level, emp_id;&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;1-18 시쿼스, 트리거, 자동 적용 트리거, 프로시저&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763046407317&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 시퀀스 생성
CREATE SEQUENCE seq_emp
START WITH 1
INCREMENT BY 1;

-- 시퀀스 사용 예 (INSERT)
INSERT INTO employees (emp_id, name)
VALUES (seq_emp.NEXTVAL, 'Kim');


--트리거

-- 예: INSERT 시 생성일자(created_at) 자동 기록
CREATE OR REPLACE TRIGGER trg_set_created_at
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    :NEW.created_at := SYSDATE;
END;


-- 예: 시퀀스 자동 적용 트리거 (emp_id 자동 세팅)
CREATE OR REPLACE TRIGGER trg_emp_id
BEFORE INSERT ON employees
FOR EACH ROW
BEGIN
    :NEW.emp_id := seq_emp.NEXTVAL;
END;


--프로시저 

-- 직원 급여 인상 프로시저 예시
CREATE OR REPLACE PROCEDURE raise_salary (
    p_emp_id IN employees.emp_id%TYPE,
    p_amount IN NUMBER
)
AS
BEGIN
    UPDATE employees
    SET salary = salary + p_amount
    WHERE emp_id = p_emp_id;
END;


-- 프로시저 호출
BEGIN
    raise_salary(1001, 500);
END;&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;1-19 권한&lt;/p&gt;
&lt;pre id=&quot;code_1763046482878&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- SELECT 권한 부여
GRANT SELECT ON employees TO user1;

-- INSERT, UPDATE 권한 부여
GRANT INSERT, UPDATE ON employees TO user1;

-- 모든 권한 부여 (주의!)
GRANT ALL PRIVILEGES ON employees TO user1;

-- 권한 회수 
REVOKE SELECT ON employees FROM user1;
REVOKE ALL PRIVILEGES ON employees FROM user1;

-- Role
-- 롤 생성
CREATE ROLE read_only;

-- 롤에 권한 부여
GRANT SELECT ON employees TO read_only;

-- 롤을 사용자에게 부여
GRANT read_only TO user1;

-- 사용자 생성
CREATE USER user1 IDENTIFIED BY password123;

-- 패스워드 변경
ALTER USER user1 IDENTIFIED BY new_password;

-- 필요 권한만 부여 (예: SELECT만 필요하면 SELECT만)
GRANT SELECT ON sensitive_table TO analyst_user;

-- 쓰기가 필요 없는데 INSERT/UPDATE를 주지 않음
REVOKE INSERT, UPDATE ON sensitive_table FROM analyst_user;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. Indexing&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-1 B+Tree&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;특징&amp;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;- 리프 노드들이 Linked List로 연결 -&amp;gt; 범위 검색 빠름&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트리 Depth가 낮음 -&amp;gt; 균일한 검색&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;장점&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 범위 검색 가능, Order By, Group By 성능 좋음, 균형 트리 구조라서 성능 안정적&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;단점&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 특정 경우 Hash보다 느릴 수 있다, 랜덤 I/O 발생, 너무 많은 인덱스가 있으면 DML 성능 저하&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-2 Hash&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;특징 == 장점&amp;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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;단점&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 범위 조건 불가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 정렬, Order By 불가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 해시 충돌 가능성&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-3 Bitmap&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;특징&amp;gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Oracle에서 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 값에 대해 Bitmap 형태로 존재 유무를 기록한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 중복 값이 매우 많은 컬럼 (성별, 지역)에 적합&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DW에서 자주 사용&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-4 Full-Text 인덱스&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;- &quot;문장 안에서 특정 단어 포함 여부&quot; &amp;lt;- 이런 검색에 유리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2-5 Spatial 인덱스&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;- R-Tree 구조&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. KEY&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-1. Primary Key - 각 행을 식별하는 대표 키 / 중복x Null x&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-2. Candidate Key - Primary Key가 될 수 있는 키 / 중복x Null x&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-3. Unique Key - 중복x Null o 인 키 ex) 전화번호, 닉네임 등 -&amp;gt; 한 테이블에 여러 개 있을 수 있다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-4. Alternate Key - Primary Key가 될 수 있는 키 중 Primary Key가 아닌 것&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-5 Foreign Key - 다른 테이블의 Primary Key 또는 Unique Key를 참조하는 키&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-6 Super Key - 유일성을 만족하는 모든 속성의 집합&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3-7 Composite Key - 둘 이상의 컬럼을 묶어 Primary Key 또는 Unique Key를 구성&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 모델링&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-1 개념적 모델링 : ER Diagram, 요구사항 분석&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-2 논리적 모델링 : 정규화, 키 지정&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4-3 물리적 모델링 : Indexing, Partitioning, View, Cache Table 등&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. ER 다이어그램 그리기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-1 Entity : 저장할 대상 (테이블) - 네모&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-2 Attribute : Entity의 속성 - 타원&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5-3 Relatioship : 1대1 1대n n대m 등 표현 - 마름모&amp;nbsp;&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;359&quot; data-origin-height=&quot;250&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjF4U1/dJMcacnVlrT/OWfEz8tVGPYKKMxLZztDGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjF4U1/dJMcacnVlrT/OWfEz8tVGPYKKMxLZztDGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjF4U1/dJMcacnVlrT/OWfEz8tVGPYKKMxLZztDGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjF4U1%2FdJMcacnVlrT%2FOWfEz8tVGPYKKMxLZztDGk%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;359&quot; height=&quot;250&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;250&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 다대다 관계 (ex: 학생 : 교과목) 는 제1, 3 정규화 법칙을 위반한다. 학생과 교과목 사이에 Enrollment 테이블을 만드는 것처럼 중간 테이블을 만들어서 기본 키를 Composite Key로 둔다(학생 id, 교과목 id)&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 테이블에서 null 값이 많은 속성은 분리하는 것이 좋다&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;6. 정규화 - 데이터 중복 제거, 이상 현상 방지&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상 현상에는 삽입 이상, 갱신 이상, 삭제 이상 등이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-1 1NF - 하나의 컬럼에는 원자값만 들어가야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-2 2NF - 복합키가 기본키일 때 복합키의 일부 속성에만 종속된 속성은 제거해야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-3 3NF - 이행 함수 종속 제거 A-B B-C 형태의 연쇄적 종속을 제거한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6-4 BCNF - 모든 결정자는 후보키여야 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;7. 무결성&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7-1 개체 무결성 : 기본키는 Not Null&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7-2 참조 무결성 : 외래키는 기본키 참조&lt;br /&gt;7-3 도메인 무결성 : 속성 값은 정의된 자료형, 범위 내에서 지정&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/56</guid>
      <comments>https://yjjdevelop.tistory.com/56#entry56comment</comments>
      <pubDate>Fri, 14 Nov 2025 01:16:12 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 서버 증설 횟수 + sortedDictionary 사용</title>
      <link>https://yjjdevelop.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;문제 링크 : &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/389479?language=csharp&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/389479?language=csharp&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754714615251&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/389479?language=csharp&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ii4LR/hyZuJq07WF/vAzPHSHtUwb1gJTVGkSn6K/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/rNZcN/hyZuGui9M1/hHU6N5KuX9ewSGthEb6LL1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/389479?language=csharp&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/389479?language=csharp&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ii4LR/hyZuJq07WF/vAzPHSHtUwb1gJTVGkSn6K/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/rNZcN/hyZuGui9M1/hHU6N5KuX9ewSGthEb6LL1/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;풀이: Priority Queue는 C# 6.0 버전 이상부터 사용할 수 있어서 프로그래머스 환경에서는 사용하기 힘들다. 때문에 SortedDictionary를 사용해서 해결함.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케쥴링 문제는 공통적으로 두 가지 이슈를 함께 생각하면 좋은 거 같다. 1. 언제 만료되는가 2. 만료됐을 때의 상태는 어떠한가. -&amp;gt; 이 두가지를 적절하게 자료 구조에 녹여낼 수 있다면 문제가 쉽게 풀리는 경우가 많다. 자세한 설명은 주석을 보면 이해가 될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로, SortedDictionary, 또는 그냥 Dictionary에 들어 있는 유틸 함수들을 잘 익혀 놓으면 코딩 테스트에서 유용하게 쓸 수 있을 것이다. ( First(), Last(), Add, Remove, TryGetValue, ContainsKey, ContainsValue 등등) -&amp;gt; using System.Linq도 잊지 말자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754714697946&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Linq; 
using System.Collections; 
using System.Collections.Generic;  

public class Solution {
    public int solution(int[] players, int m, int k) {
        // k는 서버 지속시간, 서버 개수 = pla
        //key, value 
        var endMap = new SortedDictionary&amp;lt;int, long&amp;gt;(); // key : endTime   Value : 그 시각 만료 서버 대수
        long running = 0; // 현재 운영 중인 &quot;증설 서버&quot; 수
        long answer  = 0; // 총 증설 횟수
        
        for(int t = 0; t&amp;lt; players.Length; t++){
            
            //t는 현재 시각 -&amp;gt; 만료된 서버를 제거한다. 
            while(endMap.Count &amp;gt; 0 &amp;amp;&amp;amp; endMap.Keys.First() &amp;lt;= t)
            {
                running = running - endMap.Values.First(); 
                endMap.Remove(endMap.Keys.First());
            }
            
            long needCount = players[t] / m; // 타임 t에 필요한 서버 개수 
            
            if(running &amp;lt; needCount) // 만약 운용중인 서버가 필요 서버보다 작으면; 
            {
                long add = needCount - running; 
                answer += add; //필요한 수 만큼 서버 증설 
                running += add; 
                
                int endTime = t+k; // 증설한 서버가 끝나는 시간 
                long prev = 0;
                endMap.TryGetValue(endTime, out prev);
                
                if(endMap.ContainsKey(endTime))
                {
                    endMap[endTime] += prev + add;
                }
                else
                {
                    endMap.Add(endTime, add);
                }
            }
        }
        return (int)answer;
    }
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩테스트</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/54</guid>
      <comments>https://yjjdevelop.tistory.com/54#entry54comment</comments>
      <pubDate>Sat, 9 Aug 2025 13:48:58 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 징검다리 + 이분 탐색 정리</title>
      <link>https://yjjdevelop.tistory.com/53</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문제 링크 : &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/43236&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/43236&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754569739189&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/43236&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b9MeBC/hyZuGOcQBK/VVf9CLYJhOvySk1fyk64Fk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/8RdxM/hyZuIL02pA/7GqXI3cKpynSExzMHMxHzK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/43236&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/43236&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b9MeBC/hyZuGOcQBK/VVf9CLYJhOvySk1fyk64Fk/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/8RdxM/hyZuIL02pA/7GqXI3cKpynSExzMHMxHzK/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 풀이&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;딱히 풀이가 복잡하진 않은데, 이 문제를 보고 이분 탐색 개념을 떠올리는 것이 어렵다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754569802105&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;

public class Solution {
    public int solution(int distance, int[] rocks, int n) {
          
        Array.Sort(rocks);
        List&amp;lt;int&amp;gt; rocksList = rocks.ToList();
        rocksList.Add(distance);
        int left = 1; // 거리의 최소값 
        int right = distance; //거리의 최대값 
        
        while(left &amp;lt;= right)
        {
            int median = (left + right) / 2; // 최소 거리
            
            int deleteRock = 0;
            int prevRock = 0;    
            
            foreach(var rock in rocksList)
            {
                if((rock - prevRock) &amp;lt; median) // 임의로 정한 최소거리보다 작다는 것은 결국 빠진 돌
                {
                    deleteRock ++; 
                }
                else
                {
                    prevRock = rock;   
                }
            }
            
            if(deleteRock &amp;gt; n) // median이 너무 큼
            {
                right = median - 1;
            }
            else
            {
                left = median + 1;   
            }
        }
        
        Console.WriteLine(left);
        return left - 1;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 세 종류의 이분 탐색에 대한 정리. -&amp;gt; 징검다리 문제에서 return left -1을 한 이유다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-end=&quot;500&quot; data-start=&quot;157&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody data-end=&quot;500&quot; data-start=&quot;249&quot;&gt;
&lt;tr data-end=&quot;332&quot; data-start=&quot;249&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;277&quot; data-start=&quot;249&quot;&gt;&lt;b&gt;1. 최대값 찾기 (최대 조건 만족값)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;299&quot; data-start=&quot;277&quot; data-col-size=&quot;sm&quot;&gt;조건을 만족하는 &lt;b&gt;가장 큰 값&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;318&quot; data-start=&quot;299&quot; data-col-size=&quot;sm&quot;&gt;left = mid + 1&lt;/td&gt;
&lt;td data-end=&quot;332&quot; data-start=&quot;318&quot; data-col-size=&quot;sm&quot;&gt;left - 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;414&quot; data-start=&quot;333&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;361&quot; data-start=&quot;333&quot;&gt;&lt;b&gt;2. 최소값 찾기 (최소 조건 만족값)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;384&quot; data-start=&quot;361&quot; data-col-size=&quot;sm&quot;&gt;조건을 만족하는 &lt;b&gt;가장 작은 값&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;404&quot; data-start=&quot;384&quot; data-col-size=&quot;sm&quot;&gt;right = mid - 1&lt;/td&gt;
&lt;td data-end=&quot;414&quot; data-start=&quot;404&quot; data-col-size=&quot;sm&quot;&gt;left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr data-end=&quot;500&quot; data-start=&quot;415&quot;&gt;
&lt;td data-col-size=&quot;sm&quot; data-end=&quot;447&quot; data-start=&quot;415&quot;&gt;&lt;b&gt;3. 특정 값 정확히 찾기 (정확한 값 탐색)&lt;/b&gt;&lt;/td&gt;
&lt;td data-end=&quot;467&quot; data-start=&quot;447&quot; data-col-size=&quot;sm&quot;&gt;정확한 값이 배열에 있는지 여부&lt;/td&gt;
&lt;td data-end=&quot;482&quot; data-start=&quot;467&quot; data-col-size=&quot;sm&quot;&gt;return mid&lt;/td&gt;
&lt;td data-end=&quot;500&quot; data-start=&quot;482&quot; data-col-size=&quot;sm&quot;&gt;못 찾으면 예외 or -1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경우 1.&lt;/p&gt;
&lt;pre id=&quot;code_1754569863798&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int left = 최소값;
int right = 최대값;

while (left &amp;lt;= right)
{
    int mid = (left + right) / 2;

    if (조건을 만족함)
        left = mid + 1;   // 더 큰 값도 시도
    else
        right = mid - 1;  // mid가 너무 큼
}
return left - 1; // 마지막으로 만족한 값&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;경우 2.&lt;/p&gt;
&lt;pre id=&quot;code_1754569874539&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int left = 최소값;
int right = 최대값;

while (left &amp;lt;= right)
{
    int mid = (left + right) / 2;

    if (조건을 만족함)
        right = mid - 1;  // 더 작은 값도 시도
    else
        left = mid + 1;   // mid가 너무 작음
}
return left; // 최초로 조건을 만족하는 값&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;경우 3.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754569902334&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int left = 0, right = arr.Length - 1;

while (left &amp;lt;= right)
{
    int mid = (left + right) / 2;

    if (arr[mid] == target)
        return mid;
    else if (arr[mid] &amp;lt; target)
        left = mid + 1;
    else
        right = mid - 1;
}
return -1; // 못 찾음&lt;/code&gt;&lt;/pre&gt;</description>
      <category>코딩테스트</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/53</guid>
      <comments>https://yjjdevelop.tistory.com/53#entry53comment</comments>
      <pubDate>Thu, 7 Aug 2025 21:32:31 +0900</pubDate>
    </item>
    <item>
      <title>Singleton 패턴</title>
      <link>https://yjjdevelop.tistory.com/51</link>
      <description>&lt;h2 data-pm-slice=&quot;1 2 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt; Singleton 패턴이란?&lt;/span&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&quot;하나의 클래스에 대해 오직 하나의 인스턴스만 존재하도록 보장하고, 그 인스턴스에 접근할 수 있는 전역적인 접근점을 제공하는 패턴&quot;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Singleton 패턴 구현 방법&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;C# 구현 예시 - &lt;span&gt;Lazy Initialization&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754533671692&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // private 생성자
    }

    public static Singleton Instance {
        get {
            if (instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드의 문제점은 단일 스레드에선 잘 동작하지만 멀티 스레드 환경에서는 잘 동작하지 않을 수 있음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개선 코드&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754534079421&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Singleton
{
    private static readonly Lazy&amp;lt;Singleton&amp;gt; lazyInstance =
        new Lazy&amp;lt;Singleton&amp;gt;(() =&amp;gt; new Singleton());

    private Singleton() {}

    public static Singleton Instance =&amp;gt; lazyInstance.Value;
}&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;Lazy를 사용하면 해결할 수 있다. 내부적으로 lock을 구현해주기 때문에 멀티스레드 환경에서 안전함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt; Singleton 패턴의 장점&lt;/span&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span&gt;인스턴스가 하나만 존재하므로 메모리 낭비를 방지&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;전역 접근이 가능하여 상태 공유가 쉬움&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;객체 생성을 제어할 수 있어 리소스 사용을 관리하기 용이&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;DB 생성과 같은 부분에 많이 쓰이게 됨 &lt;/span&gt;&lt;/p&gt;
&lt;h2 data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size26&quot;&gt;&lt;span&gt;Singleton 패턴의 단점과 극복 방법&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;TDD(Test Driven Development)를 할 때 걸림돌이 된다. 단위 테스트를 할 때 각 테스트마다 독립적인 객체를 만들기 어렵기 때문이다. 이는 의존성 주입(DI)를 통해 어느정도 해결할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;예시 코드와 함께 살펴보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1754534599711&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Service
{
    public void DoWork()
    {
        var logger = Logger.Instance; // 직접 Singleton에 의존
        logger.Log(&quot;작업 시작&quot;);
    }
}&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;이 코드의 문제점은 Logger가 Singleton에 직접 접근하기 때문에 가짜 객체(mock)를 사용할 수 없다는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 인터페이스로 구현해보자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754534720127&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface ILogger
{
    void Log(string message);
}

public class Logger : ILogger
{
    private static readonly Logger instance = new Logger();

    private Logger() {}

    public static Logger Instance =&amp;gt; instance;

    public void Log(string message)
    {
        Console.WriteLine($&quot;[Log] {message}&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면 실제로 로그를 찍는 클래스는 다음과 같이 구현돤다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754534779328&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Service
{
    private readonly ILogger _logger;

    public Service(ILogger logger) // 생성자 주입
    {
        _logger = logger;
    }

    public void DoWork()
    {
        _logger.Log(&quot;작업 시작&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단위 테스트는 다음과 같이 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754534853603&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class FakeLogger : ILogger
{
    public List&amp;lt;string&amp;gt; Logs = new List&amp;lt;string&amp;gt;();

    public void Log(string message)
    {
        Logs.Add(message); // 실제 로그 대신 저장
    }
}

// 테스트 코드
public void Test_Service_LogsWorkStart()
{
    var fakeLogger = new FakeLogger();
    var service = new Service(fakeLogger);

    service.DoWork();

    Debug.Assert(fakeLogger.Logs.Contains(&quot;작업 시작&quot;));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CS</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/51</guid>
      <comments>https://yjjdevelop.tistory.com/51#entry51comment</comments>
      <pubDate>Thu, 7 Aug 2025 11:55:36 +0900</pubDate>
    </item>
    <item>
      <title>[프로그래머스] 에어컨 , C#</title>
      <link>https://yjjdevelop.tistory.com/50</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;원래도 코딩테스트 준비는 많이 하고 있는데, 블로그에 정리를 시작해보려 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 문제 링크 : &lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/214289&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/214289&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1754529609467&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로그래머스&quot; data-og-description=&quot;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/214289&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/JTEWH/hyZuvZ546j/hqg4cMjZl7oWYjmoFBkZ80/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bqTcp3/hyZuIE8Qhh/JNNhLrxKfNrLxGMi3BAe20/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/214289&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/214289&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/JTEWH/hyZuvZ546j/hqg4cMjZl7oWYjmoFBkZ80/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960,https://scrap.kakaocdn.net/dn/bqTcp3/hyZuIE8Qhh/JNNhLrxKfNrLxGMi3BAe20/img.png?width=1920&amp;amp;height=960&amp;amp;face=0_0_1920_960');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SW개발자를 위한 평가, 교육의 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 문제 조건 정리&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 차내에 승객이 탑승 중일때 온도를 t1~t2 사이로 유지 (희망온도)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 실외온도와 실내온도 존재. 처음에는 실외온도와 실내온도가 같음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 에어컨을 켰을 때 실내온도와 희망온도가 다르면 1분 뒤 실내온도가 희망온도와 같아지는 방향으로 1도 상승 또는 하락.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 에어컨을 껐을 때 실내온도가 실외온도와 같아지는 방향으로 매분 1도 상승 또는 하락&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 희망온도와 실내온도가 다르면 매 분 전력을&amp;nbsp; a만큼 소비. 같다면 b만큼 소비. 에어컨이 꺼져 있다면 전력을 소비하지 않음.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 문제 해결 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 DP 문제이다. i 분에 실내온도가 j도일때 최소 소비 전력을 2차원 배열에 저장한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 조건을 잘 살펴봐야 하는데, 제한 사항을 보면 온도의 범위가 -10~40이다. 이는 for문 사용에 있어 약간 불편함을 초래한다. 때문에 관련된 변수에 Offset을 더해 해결한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 유틸 함수를 몇 개 만들고, dp를 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수 설정 부분&lt;/p&gt;
&lt;pre id=&quot;code_1754530365431&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;const int OFFSET = 10;
const int MAX_TEMP = 50; 
const int INF = 10000000;&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;DP 테이블 초기화 부분&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754530447736&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DP 테이블 초기화 -&amp;gt; i분에 실내온도가 j도일때 최소 소비 전력
int[,] dp = new int[n, MAX_TEMP + 1];
for (int i = 0; i &amp;lt; n; i++)
     for (int t = 0; t &amp;lt;= MAX_TEMP; t++)
         dp[i, t] = INF;
        
dp[0, temperature] = 0; // 0분에는 실내온도가 실외온도와 같다.&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;필요한 유틸 함수 만들기 (MoveTowardOutside, IsVaild)&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754530938439&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 실외 온도로 점차 이동 (OFF 상태)
    private int MoveTowardOutside(int current, int outside)
    {
        if (current == outside) return current;
        return current &amp;lt; outside ? current + 1 : current - 1;
    }

    // 온도가 유효한지 체크 (승객 탑승 시 t1 ~ t2 범위 내)
    private bool IsValid(int temp, int onboard, int t1, int t2)
    {
        if (onboard == 1)
            return temp &amp;gt;= t1 &amp;amp;&amp;amp; temp &amp;lt;= t2;
        return true; // 승객이 탑승하고 있지 않으면 꼭 온도가 범위 내일 필요가 없다
    }&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;DP 진행&lt;/p&gt;
&lt;pre id=&quot;code_1754530552743&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// DP 진행-&amp;gt; i분에 실내온도가 j도일때 최소 소비 전력
        for (int i = 0; i &amp;lt; n - 1; i++)
        {
            for (int temp = 0; temp &amp;lt;= MAX_TEMP; temp++)
            {
                if (dp[i, temp] == INF) continue; // 해당 상태 불가능 
                // 0분에서 온도는 무조건 실외온도(temperature)과 같다. 그리고 이는 0으로 초기화됨.

                // 현재 minute + 1 시점에 가능한 온도 후보
                //1) 에어컨 OFF -&amp;gt; 다음 시간에서의 온도는 실외온도를 향해서 간다 
                int nextTemp = MoveTowardOutside(temp, temperature);
   				
                //만약 다음 시간에서의 온도가 제한 조건을 만족하면 dp[i + 1, nextTemp], dp[i, temp]를 비교해서 작은 값을 저장
                if (IsValid(nextTemp, onboard[i + 1], t1, t2))
                    dp[i + 1, nextTemp] = Math.Min(dp[i + 1, nextTemp], dp[i, temp]);

                // 2) 에어컨 ON - 온도 유지 (b)
                if (IsValid(temp, onboard[i + 1], t1, t2))
                    dp[i + 1, temp] = Math.Min(dp[i + 1, temp], dp[i, temp] + b);

                // 3) 에어컨 ON - 온도 -1 (a) 
                if (temp &amp;gt; 0)
                {
                    int down = temp - 1;
                    if (IsValid(down, onboard[i + 1], t1, t2))
                        dp[i + 1, down] = Math.Min(dp[i + 1, down], dp[i, temp] + a);
                }

                // 4) 에어컨 ON - 온도 +1 (a)
                if (temp &amp;lt; MAX_TEMP)
                {
                    int up = temp + 1;
                    if (IsValid(up, onboard[i + 1], t1, t2))
                        dp[i + 1, up] = Math.Min(dp[i + 1, up], dp[i, temp] + a);
                }
            }
        }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정답 리턴&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1754531840328&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; int answer = INF;
 for (int t = 0; t &amp;lt;= MAX_TEMP; t++)
    answer = Math.Min(answer, dp[n - 1, t]);
 return answer;&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. 교훈&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DP를 작성하는 것 자체는 크게 어렵지 않은 문제같은데 제한 조건을 보고 적절하게 Offset을 조절하거나 유틸 함수를 만들어서 사용하는 것이 까다로운 문제였다.&amp;nbsp;&lt;/p&gt;</description>
      <category>코딩테스트</category>
      <category>C#</category>
      <category>dp</category>
      <category>다이나믹프로그래밍</category>
      <category>에어컨</category>
      <category>취준</category>
      <category>코딩테스트</category>
      <category>프로그래머스</category>
      <author>CBNA</author>
      <guid isPermaLink="true">https://yjjdevelop.tistory.com/50</guid>
      <comments>https://yjjdevelop.tistory.com/50#entry50comment</comments>
      <pubDate>Thu, 7 Aug 2025 10:59:04 +0900</pubDate>
    </item>
  </channel>
</rss>