곱게 간 기출
[STL] 벡터(Vector)
벡터는 동적 배열(dynamic array)로 구현된다. 원소를 연속된 메모리에 저장하고, 내부적으로
- 데이터 포인터
- 현재 원소 수(size)
- 할당된 용량(capacity)
의 세 가지를 유지한다. 연속 저장 덕분에 인덱스 접근은 O(1)이다. 끝에 원소를 추가하다 size == capacity가 되면, 보통 2배 크기의 새 메모리를 할당 → 기존 원소를 복사 → 이전 메모리를 해제한다. 이 재할당은 O(n)이지만 드물게 일어나므로, 끝에 추가(push_back)하는 비용은 평균적으로(amortized) O(1)이다.
노트
핵심은 size와 capacity의 분리다.
size= 지금 담긴 원소 수 (사용자가 보는 크기)capacity= 실제로 확보해 둔 공간 (미리 넉넉히 잡아 둠)- 항상
size ≤ capacity. 이 여유 공간 덕분에 매번 추가할 때마다 재할당하지 않아도 된다.
왜 “2배”인가 (제일 중요한 꼬리질문):
공간이 꽉 찰 때마다 일정하게 2배씩 늘리면, n개를 넣는 동안 발생하는 총 복사 비용이
n + n/2 + n/4 + … ≈ 2n → 즉 총 O(n), 하나당 나눠보면 O(1) 평균(amortized)이 된다.
반대로 “한 칸씩” 또는 “고정 개수씩” 늘리면 총 복사 비용이 1+2+3+…+n ≈ n²/2 → 하나당 O(n)으로 폭망한다. 그래서 기하급수적(곱셈) 증가가 핵심.
| 연산 | 복잡도 | 이유 |
|---|---|---|
인덱스 접근 v[i] | O(1) | 연속 메모리, base + i×원소크기 |
| 끝에 추가/제거 | amortized O(1) | 대부분 O(1), 가끔 재할당 O(n) |
| 중간/앞 삽입·삭제 | 뒤 원소들을 전부 밀어야 함 | |
| 탐색(정렬 안 됨) | O(n) | 전체 순회 |
꼬리질문
- 왜 2배로 늘리나요, 하나씩 늘리면요? → 위의 amortized 논리.
- 중간에 삽입하면 복잡도는? → , 뒤 원소를 밀어야 하므로.
- 재할당이 일어나면 기존 원소를 가리키던 포인터/참조는요? → 메모리 블록이 통째로 옮겨가므로 기존 포인터·참조·반복자(iterator)는 전부 무효화(invalidation)된다.
[SW 개발] 데이터베이스 스키마
데이터베이스 스키마는 데이터베이스의 구조를 정의한 설계도다. 구체적으로
- 어떤 테이블이 있고
- 각 테이블에 어떤 컬럼(이름 + 데이터 타입)이 있으며
- 테이블 간 관계와
- 제약조건(기본키·외래키·NOT NULL 등)이 어떻게 되는지를 정의한다.
즉 실제 데이터 값 자체가 아니라 데이터가 담기는 틀(구조)이며, 한 번 설계되면 자주 바뀌지 않는다.
노트
-
스키마 vs 인스턴스:
- 스키마: 구조/정의 = “설계도”. 정적, 잘 안 바뀜. (내포, intension)
- 인스턴스: 특정 시점에 실제 담긴 데이터. 동적, 계속 바뀜. (외연, extension)
-
제약조건:
- 기본키(PK): 각 행을 유일하게 식별. 자동으로
NOT NULL + UNIQUE. → 개체 무결성 - 외래키(FK): 다른 테이블의 PK를 참조. → 참조 무결성. 존재하지 않는 값을 참조하지 못하게 막음. (예: 주문 테이블의
회원ID는 반드시 회원 테이블에 실재해야 함) - NOT NULL / UNIQUE / CHECK / DEFAULT: 널 금지 / 중복 금지 / 조건 검사 / 기본값.
- 기본키(PK): 각 행을 유일하게 식별. 자동으로
[자료구조/알고리즘] 데이터사전을 만드는 데 필요한 자료구조
데이터 사전의 정체
‘데이터 사진’ = Dictionary 이거 말하는 것 같다.
찾아보면 개념이 3가지 정도 나옴.
맥락 부르는 이름 저장하는 것 추상 자료형(ADT) Dictionary / Map key → value 매핑 데이터베이스 시스템 카탈로그 메타데이터(테이블·컬럼·제약조건·사용자…) 컴파일러 심볼 테이블 식별자(변수·함수명) → 타입·스코프·주소 근데 “만드는 데 필요한 자료구조” = “구현하는 자료구조”를 묻는 것으로 해석하면 답이 하나, ADT로 수렴된다.
데이터 사전은 이름(키) → 속성(값)을 저장하고 이름으로 빠르게 조회하는 저장소다. 따라서 구현의 핵심 자료구조는 해시 테이블(hash table)이다. 키를 해시 함수로 변환해 평균 시간에 삽입·검색하기 때문이다. 데이터 사전은 ‘조회’가 압도적으로 빈번하므로 빠른 키 기반 검색이 필수이고, 그래서 해시 테이블이 가장 적합하다. (정렬·범위 조회가 필요하면 균형 이진 탐색 트리를 쓰기도 한다.)
[SW 개발] 디버깅 방법
| 단계 | 핵심 | 여기서 쓰는 기법 |
|---|---|---|
| ① 재현(Reproduce) | 안정적 재현 조건 확보 (못 재현하면 못 고침) | 최소 재현 예제(MRE), 결정적 재현 |
| ② 격리(Isolate) | 문제 범위를 반씩 좁힘 | 분할 정복 / 이분 탐색, git bisect, 주석 처리 |
| ③ 가설·검증(Verify) | 무작정 고치지 말고 가설→실험 | 중단점, 로그, 어써션, 과학적 디버깅 |
| ④ 수정(Fix) | 증상 아닌 원인 제거 | — |
| ⑤ 회귀 방지(Regress) | 재현 테스트를 남겨 재발 차단 | 회귀 테스트 등 |
디버깅 기법
A. 관찰 (실행 들여다보기)
- 디버거 / 중단점 — step in/over/out, 변수 watch, 호출 스택. 심화: 조건부 중단점, logpoint(안 멈추고 로그만).
- 로그 / printf 디버깅 — 흐름·상태 추적, 로그 레벨 구분. → 디버거 못 붙이는 프로덕션·동시성·타이밍 버그에 진가.
- 어써션 — 불변식(invariant)을 코드에 심어 가정 깨지면 즉시 터뜨림(fail-fast).
- 스택 트레이스 / 예외 분석 — 가장 기초·공짜 단서. “어디서·왜 터졌나” 먼저 읽기.
B. 격리 (범위 좁히기)
- 분할 정복 / 이분 탐색 — 코드·입력·커밋을 반씩.
git bisect가 커밋 버전. - 최소 재현 예제(MRE) — 최소 코드로 축소하는 과정에서 원인이 드러남.
C. 추론 (머리로 잡기)
- 과학적 디버깅 — 관찰→가설→예측→실험→검증.
- 러버덕 디버깅 — 남/고무오리에게 한 줄씩 설명하다 논리 허점 발견.
D. 도구 (특수 상황 — 말하면 차별화)
- 정적 분석 — 실행 없이 결함 탐지: 린터·타입체커·컴파일러 경고(Roslyn, ESLint/TS).
- 동적 분석 — 프로파일러(성능은 측정), 메모리 검사(Valgrind/ASan), race detector.
- 코어·크래시 덤프 사후 분석(post-mortem) — 프로덕션 크래시 규명.
- 네트워크 관찰 — 웹이면 DevTools Network 탭, 프록시(Fiddler/Charles), Wireshark.
[네트워크] Presentation Layer (OSI 표현 계층)
표현 계층(Presentation Layer)은 OSI 6계층으로, 데이터의 ‘표현 형식’을 책임지는 통역사 역할이다. 송신 측이 보낸 데이터를 수신 측 애플리케이션이 이해할 수 있는 형태로 맞춰준다.
핵심 기능:
- 번역/인코딩(문자 코드·데이터 포맷 변환, 예: ASCII↔Unicode)
- 암호화/복호화(기밀성, 예: SSL/TLS)
- 압축/압축해제(전송량 감소)
하위 계층이 ‘어떻게 전송하느냐’를 맡는다면, 표현 계층은 ‘데이터를 어떤 형식으로 표현하느냐’를 담당한다.
| 기능 | 하는 일 | 예시 |
|---|---|---|
| 번역/인코딩 | 문자셋·데이터 표현 방식 변환 | ASCII, Unicode/UTF-8, EBCDIC, JPEG·MPEG |
| 암호화/복호화 | 기밀성 보장 | SSL/TLS |
| 압축/압축해제 | 비트 수 감소로 효율↑ | 데이터·미디어 압축 |
심화: 실무 TCP/IP 모델엔 표현 계층이 따로 없음 — 이 기능들이 애플리케이션 레벨에 흡수됨. OSI는 개념을 나눈 참조 모델이라는 점을 덧붙이면 성숙한 답.
[네트워크] 서브넷(Subnet)
서브넷(subnet)은 하나의 큰 IP 네트워크를 여러 개의 작은 논리적 네트워크로 나눈 것이고, 그 과정을 서브네팅(subnetting)이라 한다.
IP 주소는 네트워크 부분 + 호스트 부분으로 이루어지며, 그 경계를 서브넷 마스크가 결정한다.
서브네팅은 호스트 부분의 비트 일부를 네트워크 쪽으로 빌려와 더 작은 네트워크로 쪼개는 것이다. 목적은 IP 주소 낭비 방지, 브로드캐스트 도메인 축소로 성능 향상, 네트워크 분리로 보안·관리 용이 등이 있다.
메모
IP = 네트워크부 + 호스트부, 경계는 마스크가 결정:
192 . 168 . 1 . 0 ← 192.168.1.0/24
[ 네트워크 부분 24비트 ][호스트 8비트]
11111111.11111111.11111111.00000000 = 255.255.255.0 (1=네트워크, 0=호스트)
- CIDR
/24= 앞 24비트가 네트워크. - 핵심 주소 3종: 네트워크 주소(호스트 비트 all
0) / 브로드캐스트 주소(호스트 비트 all1) / 사용 가능 호스트 = 그 사이 =2^(호스트비트) − 2.
[네트워크] TCP/UDP
TCP는 연결 지향(connection-oriented) 프로토콜로, 전송 전 3-way handshake로 연결을 수립한다. 신뢰성이 핵심 — 시퀀스 번호로 순서 보장, ACK 미수신 시 재전송, 흐름·혼잡 제어로 유실 없는 전달 보장. 대신 오버헤드가 커 느리다.
UDP는 비연결(connectionless), handshake 없이 바로 전송. 순서·재전송·ACK가 없는 best-effort라 신뢰성은 낮지만 빠르고 가볍다(헤더 8바이트).
핵심 차이: TCP=정확성/복구성(신뢰성) 우선(재전송으로 복구), UDP=속도/실시간성 우선(손실 감수). 트레이드오프.
예시: TCP → 웹(HTTP/HTTPS)·이메일(SMTP)·FTP·SSH / UDP → DNS·실시간 스트리밍·VoIP·게임.
| 항목 | TCP | UDP |
|---|---|---|
| 연결 | 연결 지향(3-way handshake) | 비연결 |
| 신뢰성/복구 | 보장(ACK·재전송·순서) | 미보장(best-effort) |
| 순서 보장 | O | X |
| 흐름·혼잡 제어 | O | X |
| 속도 | 느림 | 빠름 |
| 헤더 크기 | 20바이트~ | 8바이트 |
| 전송 단위 | 바이트 스트림 | 데이터그램(메시지) |
| 통신 방식 | 1:1 | 1:1, 1:N(브로드/멀티캐스트) |
| 통신 방식 | 1:1 | 1:1, 1:N(브로드/멀티캐스트) |
[네트워크] Slow Client + Timeout 없을때 서버 리소스 영향
타임아웃이 없으면 느린 클라이언트가 연결을 붙잡은 채 놓지 않아, 그 연결에 묶인 서버 리소스가 반환되지 않는다.
구체적으로:
- 소켓/파일 디스크립터
- 워커 스레드
- 메모리 버퍼
가 계속 점유되고, 누적되면 리소스가 고갈된다.
결국 서버는 CPU가 놀아도 새 연결을 수락하지 못하는(accept 실패) 상태가 되어, 정상 클라이언트까지 서비스를 못 받는 서비스 거부(DoS)에 빠진다. 이를 소량 트래픽으로 악용한 것이 Slowloris.
메모
| 리소스 | 무슨 일 | 고갈되면 |
|---|---|---|
| 소켓 / FD | 연결마다 FD 1개 점유, 반환 안 됨 | FD 한도 초과 → accept() 실패(Too many open files), 신규 연결 거부 |
| 워커 스레드 | (thread-per-conn) 각 연결이 스레드 블로킹 점유 | 스레드 풀 소진 → 신규 요청 대기/거부 |
| 메모리 | 소켓 버퍼 + 스레드 스택(~1MB) + 세션/TLS 상태 | 누적 → 메모리 증가 → OOM |
| accept 큐(backlog) | 워커가 안 비어 대기 연결 적체 | 큐 초과 → SYN 드롭 → 연결 거부 |
통찰: 병목이 CPU가 아니라 FD·스레드·메모리라 CPU 지표는 멀쩡한데 서버가 마비됨.
- 스레딩 모델별 취약도 (심화)
- thread-per-connection(블로킹, Apache prefork) → 가장 취약. 느린 클라 1 = 스레드 1 블로킹.
- 이벤트 기반/비동기(Nginx·Node·Netty) → 스레드 고갈엔 강하나 FD·메모리는 여전히 소모 → async는 완화지 해결이 아님.
- 해결 — 타임아웃 (+ 보조 방어)
- 연결/핸드셰이크 · 읽기(read) ←Slowloris 차단 · 쓰기(write)/최소 전송률 ←slow-read 차단 · 요청 전체 타임아웃
- 보조: IP당 연결 수 제한, rate limiting, 역방향 프록시(요청 다 받고 백엔드 전달)
[AI] LoRA
LoRA(Low-Rank Adaptation)는 거대 모델 전체를 다시 학습하는 대신, 사전학습된 가중치 W는 얼려두고(frozen) 그 위에 변화량 ΔW만 두 저랭크 행렬의 곱으로 근사해 학습하는 파인튜닝 경량화 기법이다. 직관적으로는 원본 모델 위에 작은 ‘패치(diff)‘만 학습해 얹는 것이고, 그 diff를 두 저랭크 행렬로 압축 저장한다.
기출에서 정의는 문제에 주어졌고, 하위 문항이 공식 쓰기 / 장점·한계 2개 이상이었다.
공식
기존 가중치 W는 고정하고, 변화량 ΔW를 두 개의 작은 행렬의 곱으로 표현한다:
W: 사전학습된 원본 가중치 행렬. 학습 시 고정.ΔW: 모델이 학습하는 가중치 업데이트.A: 차원을 축소하는 행렬(랭크r).B: 원래 차원으로 복원하는 행렬.
실기 답안은 여기까지로 충분.
심화 (이해용):
- 크기:
W가d×k면B는d×r,A는r×k, 랭크r ≪ min(d,k). 그래서 학습 파라미터가d·k→r·(d+k)(예 d=k=4096, r=8 → 256배↓). - 실제 계산: 학습 중엔
W와BA를 합치지 않고 두 경로를 따로 계산해 더한다. 입력x에 대한 출력은 . 앞항은 고정이고 뒷항만 학습된다. - 초기화:
B=0,A=랜덤→ 시작 시ΔW=0(원본과 동일 출발, 안정). r: 표현력↔효율 손잡이.α: 스케일링 상수(업데이트에α/r을 곱함,r을 바꿔도 학습 세기 유지).- 추론:
W_final = W + BA로 merge → 추가 지연 0(킬러 포인트). 보통 어텐션 Q·V(또는 QKVO)에 적용.
장점 / 한계·사이드이펙트
장점
- 메모리·저장 효율: 학습 파라미터가 전체의 0.1~1% 수준 → 옵티마이저 상태 급감, 체크포인트 GB→MB.
- 추론 지연 0: merge 시
W와 한 몸(adapter와 차별점). - 모듈성: 원본 1개 + 작은 LoRA 여러 개, 태스크마다 패치만 교체.
- 재앙적 망각 완화: base가 얼어 일반 지식 보존.
한계·사이드이펙트 (2개 이상)
- 표현력 상한: “
ΔW가 저랭크”라는 가정이 안 맞으면(큰 변화 필요 시)r이 작아 full FT보다 성능↓. 랭크가 학습량의 천장. - base 메모리는 그대로(흔한 오해): 학습·옵티마이저 메모리만 절감되고 거대한 base는 통째로 로드해야 한다 → 이를 4bit 양자화로 보완한 게 QLoRA.
- 하이퍼파라미터 민감성:
r·α·적용 레이어 선택에 성능이 흔들려 튜닝이 필요.
꼬리질문
- 왜 저랭크로 충분한가? → 적응 시 가중치 변화가 낮은 내재적 랭크(intrinsic rank)를 갖는다는 관찰.
B를 0으로 초기화하는 이유는? → 시작 시ΔW=0으로 base와 동일 출발, 초반 붕괴 방지.- QLoRA와의 차이는? → base를 4bit 양자화해 base 메모리까지 절감.
- adapter와의 차이는? → adapter는 레이어 추가(지연↑), LoRA는 merge 가능(지연 0).
[AI] RAG 성능 강화
RAG(Retrieval-Augmented Generation, 검색 증강 생성)는 LLM이 답을 생성하기 전에 외부 지식 저장소에서 관련 문서를 검색해 프롬프트에 근거로 붙여주는 구조다. 모델을 다시 학습하지 않고도 최신·사내 지식을 반영하고, 근거가 생겨 환각(hallucination)이 줄어든다. 핵심은 RAG가 단일 기술이 아니라 인덱싱 → 검색 → 후처리 → 생성 파이프라인이라는 것. 그래서 성능 개선도 단계별로 접근한다.
기출: “RAG 성능 강화 방법 2개 이상 설명하시오.”
실무 체감 1·2위
① 하이브리드 검색(Hybrid Search) — 검색 단계
- 벡터 검색(dense) + 키워드 검색(sparse, BM25) 결합(RRF 융합).
- 왜: 벡터는 정확한 고유명사·에러코드·약어를 놓치고, 키워드는 의미를 못 잡음 → 합치면 recall↑. (SW 직관: 풀텍스트 검색 + 벡터 검색 결합.)
② 리랭킹(Reranking) — 후처리 단계
- 빠른 bi-encoder로 top-50 후보 → 정밀 cross-encoder로 재정렬 → top-5만 LLM에 전달.
- 왜: cross-encoder는 쿼리·문서를 함께 넣어 상호작용을 봐서 정확하다(느려서 후보에만). (SW 직관: 싼 필터 → 비싼 정밀 정렬 2단계 퍼널.)
단계별 전체 메뉴
| 단계 | 기법 |
|---|---|
| 인덱싱/전처리 | 청킹 전략(의미 단위·크기·중첩) ←내 답, 메타데이터 필터, parent-child(작게 검색·크게 전달), 좋은 임베딩 모델 |
| 쿼리 변환 | 쿼리 재작성/확장, Multi-query, HyDE(가상 답변으로 검색), 쿼리 분해·라우팅 |
| 검색 | 하이브리드 검색, MRL/adaptive(짧은 벡터 1차 → 전체 정밀) ←내 답 |
| 후처리 | 리랭킹(cross-encoder), 컨텍스트 압축, lost-in-the-middle 완화(중요 문서 앞/뒤 배치) |
| 생성 | 프롬프트 그라운딩(“근거 없으면 모른다”), 인용 강제 |
| 평가·구조 | RAGAS 지표(faithfulness·context precision)로 측정 → 개선, Corrective/Self-RAG(검색 실패 시 재검색) |
메모
실제 답안은 청킹(기초 토대, 좋음) + MRL(고급이지만 “효율/coarse-to-fine” 기법이라 왜 성능이 오르는지 근거를 함께 대야 방어됨). 검색·후처리 단계 기법(하이브리드·리랭킹)을 하나씩 더 얹으면 완벽.
꼬리질문
- dense vs sparse? → dense=의미 임베딩, sparse=단어 빈도(BM25). 하이브리드로 보완.
- bi- vs cross-encoder? → 따로 인코딩(빠름·확장 유리) vs 함께 인코딩(정확·느림). 검색=bi, 리랭킹=cross.
- 청킹 잘 하려면? → 고정 크기보다 의미/구조 단위 + overlap + parent-child.
- 검색이 엉뚱한 걸 가져오면? → 리랭킹 + 하이브리드 + 청킹 재설계 + 쿼리 재작성, RAGAS로 문제 단계 측정.