콘텐츠로 이동

Ch.1 이해와 암기, 코딩과 CS

< 왜 CS를 알아야 하는가


앞에서 키워드를 모르면 무슨 일이 벌어지는지를 봤다. 이번에는 왜 그런 차이가 나는지, 그리고 "코딩을 잘하는 것"과 "CS를 아는 것"이 왜 별개가 아닌지를 이야기하겠다.

이해와 경험은 다르다

이해(Understanding)

어떤 개념이나 사실을 명확히 인식하고, 그 의미와 연관성을 파악하는 능력이다.

CS에서의 이해란, Software Engineering, OS, DB, Network, Security, Computer Architecture에 대한 Background Knowledge가 본인의 뿌리에 확고하게 자리 잡았느냐를 묻는 것이다.

여기서 중요한 구분이 하나 있다. "외우고 있음(Memorization)"과 "이해하고 있음(Understanding)"은 차이가 크다.

  • 외우고 있음: 정보를 기억하고 필요할 때 회상할 수 있다
  • 이해하고 있음: 정보의 의미를 깊이 파악하고, 그 원리를 다른 상황에 적용할 수 있다

외우기만 한 정보는 컨텍스트(상황)를 벗어나면 적용할 수 없다. 이해한 정보는 즉각적 적용은 어려울 수 있지만, 새로운 상황에서도 연결할 수 있다.

무엇을 우선시해야 하는가? 뒤에서 더 자세히 다루겠지만, 결론만 먼저 말하면 이해가 우선이다.

경험(Experience)

직접적인 관찰이나 실제로 어떤 일을 해보면서 얻은 지식이다.

CS에서의 경험이란, Production 환경의 Service에 대한 Trial & Error가 있느냐를 묻는 것이다.

"만들어 봤다"와 "출시하고 운영해 보았다"는 큰 차이가 있다.

95%의 개발은 (비교적) 쉽다. 하지만 나머지 5%의 마무리가 어렵다. 복잡한 요구사항과 현실의 제약을 조율하기 위한 Trial & Error. 그리고 그걸 극복해서 서비스를 출시하기까지. 그 다음은? 출시한 서비스를 운영하면서 생기는 온갖 문제들을 해결해야 한다.

이해와 경험은 상호 보완적이다. 이론적 이해를 바탕으로 경험을 쌓으면 이해가 더 깊어지고, 경험을 통해 이론의 의미를 체감하게 된다.

그러면 Junior 레벨에게는 이해와 경험 중 뭐가 더 중요한가?

경험이 부족한 건 당연하다. 그래서 이해가 더 중요하다. CS에 대한 깊은 이해가 있으면, 경험이 부족하더라도 문제의 방향을 잡을 수 있다. 그리고 경험을 쌓을 때도 훨씬 빠르게 배운다.

깊이 이해하면 학습 효율이 달라진다

이해와 경험의 관계를 좀 더 생각해보면, 재미있는 현상이 하나 있다. 똑같은 공부를 해도 사람마다 성과가 다르다. "시간을 얼마나 갈았느냐"와 반드시 비례하지 않는다.

왜 그런가? CS에서는 한 분야에서 생긴 개념을 다른 분야에 차용하는 경우가 굉장히 빈번하기 때문이다. A 분야의 특정 개념을 깊이 이해하고 있으면, B에 차용된 개념을 별도의 학습 없이 바로 이해할 수 있다.

예를 들어 네트워크에서 Sliding Window를 완벽히 이해하고 있으면, 시계열 분석에서 같은 개념이 나왔을 때 별도의 학습 시간 없이 바로 이해할 수 있다. 깊이 이해한 하나의 개념이, 그걸 차용한 열 개의 분야를 열어준다.

반대로 열 개를 얕게 외우면, 열한 번째가 나올 때 또 외워야 한다. 시간 대비 효율이 완전히 다르다.

면접에서도 마찬가지다. "이 정도까지만 알면 되지 않을까?"의 기준이 굉장히 모호하다. "이걸 어디까지 알고 있는지"가 중요하고, 그걸 넘어서서 "이걸 이렇게 깊게 알고 있다니"에서 감탄과 호의가 나온다. 깊은 이해가 학습의 질을 가른다.

이 강의는 "깊이 이해하는 방법"을 보여주는 강의다.

CS를 외워온다

그런데 현실에서는 어떤가. 면접관으로서 면접을 보다 보면 꽤나 느끼는 게 많다. 가끔 좋은 쪽으로든, 그 반대로든 상상할 수 없는 답변이 나오는 경우도 있지만, "어딘가에 정리된 것을 외워왔다"는 느낌을 지울 수 없을 때가 제법 많다.

2021년, 어떻게 이런 천편일률적인 답변이 나오는 거지? 라고 고민하던 중에 알게 된 사실이 있다. Backend 면접 질문을 모아놓고 답변을 정리해놓은 GitHub 저장소들이 꽤 많이 존재한다는 것이다. 그 후로 면접 질문을 준비할 때 해당 사이트에서 뭐라고 답변이 되어있는지 가끔 찾아본다.

재미있게도, 피면접자들의 답변이 해당 사이트의 내용과 비슷한(그렇지만 제대로 외우지도 못한) 경우를 많이 본다.

해당 자료를 정리한 사람이 얼마나 훌륭한 사람인지는 알 수 없다. 하지만 그 사람은 "핵심"만 요약해 놓았다는 건 분명하다. 최소한 그걸 더 깊게 파보거나, 자신의 실전적인 경험에 비추어 생각해볼 의지는 있어야 하지 않나?

놀라운 점은 신입, 3년차, 7년차 모두 똑같은 답변을 하는 경우가 꽤 있다는 것이다. 더욱 놀라운 점은 그 답변의 퀄리티와 관계없이, 파고드는 질문을 하면 답변을 못한다. 면접관인 내가 뭐 그렇게 갑자기 요점을 찌르는 질문을 할 정도로 똑똑한 사람은 아닌데 말이다.

그래서 내린 결론은 이거다. 물론 틀릴 수도 있다.

정리해서 외운다. 관용구를 외우듯이.

면접 질문을 다시 보자

여러 면접 질문 모음집을 보면 느껴지는 게 있다. Framework에 의존하는(Framework Dependent) 질문이 생각보다 많지 않다.

여기서 "Spring DI/IoC 같은 질문이 있지 않느냐"는 이야기를 할 수 있다. 하지만 DI/IoC는 Software Engineering적인 질문이다.

DI/IoC (Dependency Injection / Inversion of Control)

DI(의존성 주입)는 객체가 필요로 하는 의존성을 외부에서 주입받는 설계 패턴이다.

IoC(제어의 역전)는 프로그램의 흐름 제어를 개발자가 아닌 프레임워크가 담당하는 원칙이다.

Spring Framework의 핵심 디자인 철학과 긴밀히 연결되어 있지만, 이 개념 자체는 Software Engineering의 범주에 속한다.

Ch.20에서 관심사의 분리를 다룰 때 더 자세히 다룬다.

IoC가 Spring Framework의 핵심 디자인 철학과 긴밀히 연결되어 있기 때문에 Framework 질문처럼 보이는 거지, 본질적으로는 Software Engineering 질문이다. 진짜 Framework Dependent한 질문이라면 "Servlet Filter와 Spring Interceptor의 작동방식 차이" 같은 질문이다. 이런 질문은 신입에게 잘 하지 않는다.

Language에 의존하는(Language Dependent) 질문도 생각보다 많지 않다. 대부분은 Modern Language라면 공통으로 갖고 있는 개념에 대한 질문이다. Try-Catch-Finally 구조라든가, Concurrency Control이라든가.

결국 대다수의 면접 질문은 CS라는 학문에 대한 Background Knowledge를 묻는 것이다.

그러니 특정 Language나 Framework를 공부하기보다는, 그것이 갖고 있는 핵심 디자인 철학을 이해하고, 그에 수반되는 CS 지식을 깊이 있게 탐구하는 게 더 가치가 있다. 핵심 디자인 철학을 깊이 이해하면, 그 위에서 작동하는 방식을 더욱 직관적으로 이해할 수 있다.

어렸을 때 많이 들었던 말이 있다. "출제자의 의도를 파악하라." 물론 문제 풀 때처럼 궁예가 되라는 게 아니다. 보통 핵심 디자인 철학은 공식문서 Overview에 다 써있다.

코딩과 CS는 관계가 없다?

가끔 보면 코드를 작성하는 것과 CS에 대한 깊이는 관계가 없다고 생각하는 사람들이 있다. ChatGPT에게 물어주면 답을 알려주고, 에러를 검색하면 Stack Overflow에 해결책이 나오니까 그런 걸까?

백 번 양보해서, 코드를 "작성"하는 행위 자체에는 CS가 덜 필요할 수 있다고 치자. 하지만 점점 어려운 코드를 작성하게 되고, 지엽적인 문제를 해결해야 할 때 CS 지식이 없는 건 큰 허들이 된다.

이건 Git을 제대로 못 쓰는 것과 비슷한 수준의 문제다. rebase를 할 줄 몰라서 checkout-stash-commit-push를 엮어서 쓰는 경우를 떠올려보자. 돌아가긴 하는데, 영 아닌 모양새다.

01에서 본 WORD size 에피소드(임베디드)와 검색·AI 사례 외에, 코딩 현장에서 CS 키워드를 모르면 길을 잃는 경우를 두 개 더 보자. 하나는 최신 Java(Virtual Thread)에서, 다른 하나는 백엔드 일상(Timezone)에서.

사례: Virtual Thread와 Pinning

Java를 안 쓰는 독자에게

이 섹션은 Java 진영의 구체적 사례지만, 핵심 메시지는 언어에 무관하다. "키워드(여기서는 'Pinning')를 모르면 검색 자체가 불가능하다"가 전부. 세부 원리가 버겁다면 이 섹션 마지막의 Python 버전 함정만 읽고 넘어가도 된다.

JDK 21에 정식 출시된 기능 중에 Virtual Thread가 있다.

Virtual Thread

Java 21에서 정식 도입된 경량 스레드다. 기존의 Platform Thread(OS 스레드와 1:1 매핑)와 달리, JVM이 관리하는 가벼운 스레드다. Python의 Coroutine이나 Go의 Goroutine과 비슷한 개념이라고 보면 된다.

하나의 Platform Thread(Carrier Thread) 위에서 여러 개의 Virtual Thread를 돌릴 수 있어서, I/O 대기가 많은 서버 애플리케이션에서 동시성 처리 성능이 크게 향상된다.

Virtual Thread 개발을 하다 보면 매번 나오는 이야기가 "Pinning"이다.

synchronized (동기화 블록)

여러 스레드가 동시에 같은 코드 블록에 접근하지 못하도록 잠그는 Java의 키워드다. Python의 threading.Lock()과 비슷한 역할이라고 보면 된다.

동시성 문제를 방지하기 위해 사용하지만, Virtual Thread 환경에서는 예상치 못한 부작용을 일으킬 수 있다.

Pinning이 뭐냐면, Virtual Thread가 synchronized 블록이나 native method(JNI) 안에서 blocking 작업을 하면, 해당 Virtual Thread가 Carrier Thread에서 빠져나오지(unmount되지) 못하고 Carrier Thread 자체를 점유해버리는 상태를 말한다. (출처: OpenJDK JEP 444, Java 21 기준. JDK 24에서 JEP 491로 synchronized에서의 Pinning은 해결되었다. 하지만 JNI 기반 Pinning은 여전히 존재한다.)

Carrier Thread 하나당 여러 개의 Virtual Thread를 돌려야 하는데, Pinning이 발생하면 Carrier Thread 수만큼밖에 스레드가 돌지 못한다. Virtual Thread의 장점이 사라지는 거다.

그런데 이게 만약 내가 제어할 수 없는 라이브러리 코드에서 발생한다면? 예를 들어 JDBC 드라이버가 내부적으로 JNI를 쓰고 있다면? 한참 뒤에야 발견된다면?

그때 JVM 스레드가 8개뿐인 걸 보고 "아, Carrier Thread가 8개 생성되었구나"라는 것까지 스스로 추론해야 한다. 그리고 "Pinning"이라는 키워드를 떠올려야 한다. 그 키워드를 모르면? 검색을 할 수가 없다.

이게 CS 키워드를 아느냐 모르느냐의 차이다. 키워드를 알면 검색으로 해결할 수 있다. 키워드를 모르면 검색 자체가 불가능하다.

Python 버전 — 같은 구조의 함정, 다른 이름

Python이면 동일한 구조가 GIL + asyncio에 있다.

  • FastAPI의 async def 엔드포인트 안에 torch 추론이나 pandas.read_parquet 같은 동기·Blocking 호출을 그대로 넣는다
  • 그 순간 이벤트 루프가 동기 호출이 끝날 때까지 전체가 멈춘다. 다른 요청은 전부 대기
  • "async로 짰는데 왜 느리지?" 하며 며칠 헤맨다
  • 관련 키워드: GIL, Event Loop, Blocking I/O, CPU Bound — Ch.3에서 k6로 직접 측정한다

Java는 Carrier Thread가 점유되고, Python은 Event Loop가 멈춘다. 다른 언어 · 같은 구조적 함정 · 같은 해법 원칙(키워드를 알면 검색이 된다)이다.

사례: Timezone 9시간 밀림 (백엔드 평행 에피소드)

01의 WORD size 에피소드는 임베디드라 멀게 느껴질 수 있다. 백엔드에서 자주 만나는 같은 구조의 함정을 하나 보자.

글로벌 서비스 오픈을 앞둔 어느 팀. 주문 데이터를 DB에 DATETIME 타입으로 저장했다. 코드에서는 LocalDateTime.now()로 찍었다. KST 서버에서는 문제 없었다.

글로벌 런칭 당일, 미국 리전 서버에서 주문 확인 알림이 9시간 늦게 발송되어 CS가 폭주했다.

  • 원인: Timezone. LocalDateTime은 "타임존 정보가 없는 시간"이다. KST 서버에서 찍은 2026-01-01 09:00과 UTC 서버에서 찍은 2026-01-01 09:00은 같은 문자열, 다른 물리 시간
  • 해결: TIMESTAMP WITH TIME ZONE 또는 UTC 고정 저장 + 표시 시점 변환. Java면 OffsetDateTime/Instant 사용

WORD size 에피소드와 구조가 같다. "Timezone"이라는 키워드를 모르면 "시간이 이상하네, 서버 재시작해볼까?"에서 3일을 날린다. 아는 사람은 5분 만에 "Timezone 이슈잖아"로 잡는다.

Timezone 말고도 문자 인코딩(UTF-8 vs EUC-KR), Parquet 타임존 메타데이터, pandas groupby 후 dtype 변환 같은 "데이터가 이상한데 코드는 멀쩡한" 버그는 전부 같은 패턴이다. "모르는 키워드"가 범인이다.

Framework의 작동 원리를 알 필요가 있는가

"Framework의 작동 원리를 알 필요가 있을까?"라는 질문에 대해서는 "글쎄요"라고 답하겠다.

하지만 "Framework의 작동 원리를 알면 좋을까?"라는 질문에 대해서는 "당연한 거 아닌가요?"라고 답하겠다.

이 시스템이 왜 느린지 파악하는 건 크게 어렵지 않다. 요즘은 Metric 도구도 잘 되어 있고, 모니터링 Tool도 많다.

근데 이 시스템이 왜 느릴지 예측하는 건 상당히 어렵다. 방대한 지식과 경험이 필요하다.

"파악"과 "예측"의 차이다. 파악은 문제가 이미 발생한 후에 원인을 찾는 거다. 예측은 문제가 발생하기 전에 "이런 구조면 여기서 병목이 생기겠다"를 미리 아는 거다. Framework의 작동 원리를 이해하고, 그 밑에 깔린 CS를 아는 사람만이 예측할 수 있다.

코드를 작성하는 것과 CS가 관계없다고 생각할 수 있다. 정확히 말해서, 코드를 "작성"하는 행위 자체에는 CS가 덜 필요할 수 있다. 하지만 내가 짠 코드가 어떻게 돌아가는지 알기 위해서는 CS를 알아야 한다.

컴퓨터로 먹고사는 게 뭐 그렇게 크게 어렵고 위대한 일은 아니지만, 소위 "좀 치는" 개발자가 되려면 좀 어렵다.

이 강의는 BE 개발자를 위한 것이다. FE에도 CS는 중요하지만, 깊이 파야 할 과목이 다르다(브라우저 엔진, DOM, CSS 렌더링 파이프라인 등). 그래서 scope을 BE로 한정했다.


< 왜 CS를 알아야 하는가 | 어떻게 공부할 것인가 >