컴퓨터는 주사위 놀이를 하지 않는다 - 프로그래밍에서 랜덤을 제대로 다루는 방법

2025. 9. 3. 18:13Tech Insights/개발 잡소리

반응형

우리가 게임에서 몬스터가 가끔 레어 아이템을 떨구길 기대하고, 추천 시스템이 적당히 다양하게 보여주길 바랄 때, 눈에 보이지 않는 곳에서 수많은 랜덤이 일하고 있습니다. 그런데 솔직히 말하면, 컴퓨터는 진짜 주사위를 던질 줄 모릅니다. 대신 "그럴싸하게 보이는 무늬"를 빠르게 그려내죠. 오늘은 그 무늬의 비밀과 랜덤의 메커니즘과 대표 알고리즘, 그리고 제대로 쓰는 방법에 대해 이야기해보려고 합니다.


랜덤의 두 얼굴: TRNG vs PRNG vs CSPRNG

  • TRNG (True Random Number Generator)

물리 세계의 노이즈(열, 방사선, 전기적 작용 등)을 이용해 예측 불가능한 값을 뽑습니다. 보안 토큰, 암호키 생성처럼 진짜 무작위성이 필요한 상황에서 쓰죠. 다만 하드웨어/OS 의존성이 있고 느릴 수 있어요.

  • PRNG (Pseudo Random Number Generator)

수학적 규칙으로 "랜덤처럼 보이는" 수열을 만듭니다. 시드(Seed)만 같으면 결과가 동일하니깐 재현성이 좋습니다. 시뮬레이션, 게임 로직, 난수 샘플링 등에 널리 사용됩니다.

  • CSPRNG (Cryptographically Secure PRNG)

예측이 사실상 불가능하도록 설계된 PRNG. 암호학적 안정성이 필요할 때 사용합니다. 일반 PRNG로 토큰을 만들면 큰일납니다. (이유는 Seed 기반이기 때문에, 에측이 가능하기 때문입니다.)

OS의 "/dev/urandom", Web Crypto, SecureRandom 같은 게 여기에 속하죠.


시드(Seed)와 재현성: 실험 가능한 우연

PRNG는 시드가 같으면 결과가 같습니다.

이건 단점이자 장점이죠. A/B 테스트나 시뮬레이션을 재현해야 할 땐 최고입니다. 반대로, 시드를 시계시간으로만 정하면 여러 프로세스가 같은 밀리초에 시작해 동일 패턴을 뽑는 사고가 생기기도 합니다.

실무 팁:

  • 시드는 시간 + 프로세스 ID + 카운터 + OS 랜덤 등으로 섞어 충돌을 줄이세요.
  • 모든 서버에서 같은 랜덤 문제가 있으면 서버/유저별 고유 시드를 부여하세요.

언어별 사용법: "이 함수 안전해요?"

Javascript (브라우저) 또는 Node.js

  • 흔히 사용하는 "Math.random()" 함수는 PRNG 알고리즘입니다. 그렇기에 비보안 또는 게임, UI 에만 사용하는 것이 안전합니다.
  • 보안/토큰/UUID 등은 "crypto.getRandomValues()" 를 사용하는 것이 옳습니다.

Python

  • "random" 모듈: MT19937 기반이기에 비보안적인 상황에서만 쓰는 것이 좋습니다.
    • MT19937은 C++ 에서 사용되는 PRNG 기반의 난수 생성기입니다. 
  • 보안용은 "secrets"(내부적으로 OS CSPRNG 를 사용): "secrets.token_hex(32)"
  • 수치/통계용 으로는 numpy.random (PCG64 등) 을 권장합니다. 

Java

  • java.util.Random: 해당 기능은 오래되었고 성능적으로도 불리합니다. (비보안)
  • 성능/병렬: "SplittableRandom"는 스트림을 분할하여 충돌과 상관을 최소화하는 데 유리합니다. "ThreadLocalRandom" 는 스레드마다 독립적인 인스턴스를 제공하기 때문에, 락/경합 문제에서 자유롭습니다. 
  • 보안: "SecureRandom"

품질 검증을 믿괴, 과신하지 말 것

우리의 비즈니스나 서비스는 다양한 문제와 상황에 놓여있습니다. 

난수기는 테스트 스위트(Dieharder, TestU01 등) 를 통과해도, 특정 문제에서는 패턴이 드러날 수 있어요. 그렇기에 벤치마크 결과만 보고 덥석 쓰지 말고, 자신의 도메인 에서 원하는 통계적 성질을 만족하는지 샘플 테스트를 돌려볼 필요가 있습니다.

 

상황별 체크리스트

  • 보안에 쓰이는가? -> CSPRNG
  • 재현성이 필요한가? -> 시드 관리
  • 병렬/분산인가? -> 스트림 분리 및 시드 충돌 방지
  • 라이브러리 기본값 맹신 금지(특히 Math.random, rand()

마무리: "좋은 우연"을 만드는 기술

랜덤은 행운에 맡기는 게 아니라, 문제를 모델링하는 기술입니다. 어떤 무작위가 필요한지(보안? 재현성? 분포?), 어떤 엔진과 분포를 고를지, 어떻게 기드와 스트림을 관리할지 정하는 순간, 우리는 우연처럼 보이는 질서를 설계하는 사람이 됩니다.

여러분의 프로젝트에서 어떤 랜덤을, 어떤 이유로 선택하고 있는지, 그리고 그 랜덤이 진짜로 그 상황에 맞는 우연인지 점검해보세요. 

 

반응형