반응형
Notice
Recent Posts
Recent Comments
Link
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

Ethan's Values

개발 기초 지식 본문

MLOps

개발 기초 지식

Ethan_hyk 2026. 1. 29. 11:17
반응형

개발자로 대면면접을 처음봤는데, 처참했고 복기를 위해 질문받은 것들이나 내가 알지 못했던 것들 작성.

1) Python에서 상속(Inheritance)

개념 정리

상속은 기존 클래스(부모 클래스)의 속성과 메서드를 물려받아 자식 클래스를 만들고, 공통 로직을 재사용하거나 기능을 확장하는 객체지향 개념이다. 자식 클래스는 필요 시 부모 메서드를 오버라이딩해서 동작을 재정의할 수 있고, super()로 부모 로직을 재사용할 수도 있다.
실무에서는 공통 로깅/설정/검증 로직을 베이스 클래스에 모아두고, 각 기능별 구현만 자식 클래스에서 확장하는 형태로 자주 활용한다. 다만 상속 계층이 깊어지면 결합도가 커질 수 있어 조합(composition)과 함께 균형 있게 사용한다.

class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return "..."

class Dog(Animal):  # 상속
    def speak(self): # 오버라이딩
        return f"{self.name}: 멍멍"

d = Dog("초코")
print(d.speak())
print(isinstance(d, Animal), isinstance(d, Dog))

실행 결과

실행 결과
초코: 멍멍
True True

내가 답변한다면?

Python의 상속은 부모 클래스의 기능을 자식 클래스가 재사용하고 확장하기 위한 개념입니다. 공통 로직을 한 곳에 모아 중복을 줄이고, 동일한 인터페이스를 유지하면서 자식 클래스에서 필요한 부분만 오버라이딩해 구현할 수 있습니다. 다만 상속 계층이 과도해지면 결합도가 올라가 유지보수가 어려워질 수 있어서, 공통 기반 기능 제공에 제한적으로 쓰고 조합도 함께 고려합니다.

 

2) array, list, set, dict + dict가 빠른 이유(해시 테이블)

개념 정리

  • list: 타입 제한이 전혀 없고, 객체 참조 배열이다. list 안에 들어가는 것들이 다 객체들임. 그래서 순서 O, 중복 O, 서로 다른 타입 저장 가능. 인덱스 접근 O(1), 값 검색 O(n).
  • array (from array): 같은 타입만 저장 가능한 C 스타일 배열. 메모리 효율이 좋지만 유연성 낮음(실무 수치데이터는 NumPy가 보통 대체).
  • set: 순서 X, 중복 X. 해시 기반이라 포함 여부(in)가 평균 O(1).
  • dict: key-value 구조. 해시 테이블 기반이라 key 조회/삽입이 평균 O(1). (Python 3.7+ 입력 순서 유지)

코드 1: list는 다양한 타입 가능 / array는 타입 제한

from array import array

lst = [1, "hello", [1,2], {"a":1}]
print("list:", lst)

arr = array('i', [1, 2, 3])
print("array('i'):", arr.tolist())

try:
    array('i', [1, "a", 3])
except Exception as e:
    print("array mixed types error:", repr(e))

실행 결과

list: [1, 'hello', [1, 2], {'a': 1}]
array('i'): [1, 2, 3]
array mixed types error: TypeError("'str' object cannot be interpreted as an integer")

코드 2: membership(포함 여부) 속도 비교 (list vs set vs dict)

from timeit import timeit

N = 200_000
data = list(range(N))
data_set = set(data)
data_dict = {x: True for x in data}
targets = list(range(0, N, 10))  # 20k lookups

def list_membership():
    c = 0
    for t in targets:
        if t in data:
            c += 1
    return c

def set_membership():
    c = 0
    for t in targets:
        if t in data_set:
            c += 1
    return c

def dict_membership():
    c = 0
    for t in targets:
        if t in data_dict:
            c += 1
    return c

print("sanity counts:", list_membership(), set_membership(), dict_membership())

t_list = timeit(list_membership, number=1)
t_set  = timeit(set_membership, number=1)
t_dict = timeit(dict_membership, number=1)

print(f"list  membership time: {t_list:.4f}s")
print(f"set   membership time: {t_set:.4f}s")
print(f"dict  membership time: {t_dict:.4f}s")

실행 결과 (실제 측정)

sanity counts: 20000 20000 20000
list  membership time: 9.6914s
set   membership time: 0.0012s
dict  membership time: 0.0014s

해시 테이블(Hash Table)이란?

핵심 구성 요소 3가지

  1. Key
  2. Hash Function
  3. Bucket(Array)

동작 과정 (조회 기준)

key ──▶ hash(key) ──▶ index ──▶ value
예시:

d = {"apple": 100}

1. "apple" → hash 함수 실행
2. 정수값(hash value) 생성
3. 배열 크기에 맞게 index 계산
4. 해당 위치에 value 저장
👉 처음부터 끝까지 탐색하지 않음

dict가 왜 빠른가?

dict는 key를 해시 함수로 변환해 나온 값을 인덱스로 사용하여, 리스트처럼 처음부터 끝까지 탐색하지 않고 바로 접근한다. 그래서 일반적으로 조회가 평균 O(1)이다.

list
[1, 3, 7, 10, 20]
 ↑   ↑   ↑   ↑
원하는 값 찾으려면 처음부터 순회
평균 시간복잡도: O(n)

hash table
index:  0   1   2   3   4
        ─────────────────
        |   | A |   |   |
key → index로 즉시 이동
평균 시간복잡도: O(1)

 

- 해시 충돌(Hash Collision)이란?

서로 다른 key가 같은 index로 매핑되는 현상

hash("abc") % 5 == 2
hash("xyz") % 5 == 2
👉 같은 버킷을 쓰게 됨

🔹 충돌 해결 방법
1. 체이닝 (Chaining)
bucket[2] → ("abc", 10) → ("xyz", 20)
Python dict 방식, 연결 리스트처럼 저장
2. 오픈 어드레싱
빈 공간 찾을 때까지 이동 (linear probing 등)

!! 충돌 나면 O(1) ??
👉 그래서 평균적으로 O(1), 최악의 경우 O(n)
하지만, 좋은 해시 함수와 적절한 테이블 크기, 리사이징(resize) 하면 실무에서는 거의 O(1)

내가 답변한다면?

list는 순서가 있고 다양한 타입을 담을 수 있는 범용 컬렉션이며 인덱스 접근은 O(1)이지만 탐색은 O(n)입니다. set과 dict는 해시 테이블 기반이라 포함 여부/조회가 평균 O(1)로 빠르고, set은 중복 제거 및 membership 체크에, dict는 key-value 매핑과 빠른 조회에 적합합니다. array는 동일 타입만 저장하는 구조라 메모리 효율은 좋지만 유연성이 낮아 수치 데이터는 보통 NumPy로 처리합니다.

 

 

3) 비동기 처리 + 세마포어 + 데드락

개념 정리 (비동기 & 블로킹)

동기 방식에서 I/O(DB, API, 파일 등)를 수행하면 스레드가 결과를 기다리며 블로킹될 수 있다. 즉 스레드가 아무것도 못 하고 대기하므로 같은 스레드 수로 처리 가능한 작업이 줄어 **처리량(throughput)**이 떨어질 수 있다. 비동기는 I/O 대기 시간 동안 다른 작업을 수행해 자원 활용을 높이고 처리량을 개선하기 위한 실행 방식이다.

세마포어(Semaphore)와의 관계

세마포어는 비동기/동시성 환경에서 동시에 실행 가능한 작업 수를 제한하는 도구다. 예를 들어 외부 API rate limit, DB 커넥션 고갈, GPU 자원 경합을 방지하기 위해 “동시에 최대 N개만 실행” 같은 제한을 건다.
비동기는 실행 모델, 세마포어는 동시성 제어 수단이다.

 

코드: 세마포어로 동시 실행 제한 (threading.Semaphore 예시)

import threading, time
from threading import Lock

sem = threading.Semaphore(3)  # 동시에 3개만
running = 0
peak = 0
m = Lock()

def worker(i):
    global running, peak
    with sem:
        with m:
            running += 1
            peak = max(peak, running)
            print(f"start {i} (running={running})")
        time.sleep(0.1)
        with m:
            running -= 1
            print(f"end   {i} (running={running})")

threads = [threading.Thread(target=worker, args=(i,)) for i in range(8)]
for t in threads: t.start()
for t in threads: t.join()
print("peak concurrency observed:", peak)

 

실행 결과

start 0 (running=1)
start 1 (running=2)
start 2 (running=3)
...
peak concurrency observed: 3

 

 

데드락(Deadlock) 개념 정리

데드락은 둘 이상의 작업이 서로 자원을 점유한 채 상대 자원을 기다리면서 무한 대기하는 상태다. 락을 서로 반대 순서로 잡는 경우 대표적으로 발생한다. 실무에서는 “락 획득 순서 통일”, “timeout”, “락 개수/범위 축소” 등으로 예방한다.

 

코드: 데드락 상황(유사) 재현 + 해결(락 순서 통일)

import threading, time
from threading import Lock

lock_a = Lock()
lock_b = Lock()
deadlock_like = threading.Event()

def t1():
    with lock_a:
        time.sleep(0.05)
        acquired = lock_b.acquire(timeout=0.2)  # timeout으로 무한 대기 방지
        if not acquired:
            deadlock_like.set()
            return
        lock_b.release()

def t2():
    with lock_b:
        time.sleep(0.05)
        acquired = lock_a.acquire(timeout=0.2)
        if not acquired:
            deadlock_like.set()
            return
        lock_a.release()

th1 = threading.Thread(target=t1)
th2 = threading.Thread(target=t2)
th1.start(); th2.start()
th1.join(); th2.join()
print("deadlock-like condition detected via timeout:", deadlock_like.is_set())

# 해결: 락 획득 순서 통일 (항상 A -> B)
def safe_worker():
    with lock_a:
        with lock_b:
            pass

ths = [threading.Thread(target=safe_worker) for _ in range(20)]
for t in ths: t.start()
for t in ths: t.join()
print("safe lock ordering finished without deadlock:", True)

실행 결과

deadlock-like condition detected via timeout: True
safe lock ordering finished without deadlock: True

내가 답변한다면?

비동기는 I/O 대기 중 스레드가 블로킹되어 처리량이 떨어지는 문제를 줄이기 위한 실행 방식입니다. 세마포어는 비동기/동시성 환경에서 동시 실행 수를 제한해 DB 커넥션 고갈이나 API rate limit 같은 자원 문제를 방지합니다. 데드락은 여러 작업이 서로 락을 점유한 채 상대 락을 기다리며 무한 대기하는 상태로, 실무에서는 락 획득 순서를 통일하거나 timeout을 두는 방식으로 예방합니다.

Comments