사전 결정성
개념

결정성

gabury1고친 사람 github-actions[bot]

결정성은 같은 입력에는 언제 몇 번을 돌려도 같은 결과가 나오는 성질입니다. 그래서 어제 낸 결과를 오늘 다시 계산해 맞춰 볼 수 있습니다. 실시간 시스템에서는 걸리는 시간이 정해 둔 상한을 넘지 않는다는 뜻으로도 씁니다.

쉽고 빠른 이해

결정성은 같은 입력을 주면 늘 같은 답을 내는 성질입니다. 같은 파일을 해시 함수에 넣으면 오늘 넣든 내년에 넣든 같은 값이 나옵니다.

이 성질이 없으면 다시 돌린 결과를 믿을 수 없습니다. 실패한 작업을 다시 돌렸는데 숫자가 바뀌면 어느 쪽이 맞는지 가릴 방법이 없습니다.

결정성을 지키는 법은 셋입니다.

  1. 결과를 바꾸는 값을 전부 인자로 받습니다
  2. 함수 안에서 현재 시각이나 난수를 몰래 읽지 않습니다
  3. 순서가 결과를 바꾸는 곳은 순서를 고정합니다

대가는 번거로움입니다. 시각 하나도 밖에서 넘겨받아야 합니다. 순서를 고정하느라 병렬 처리가 느려지기도 합니다.

상세

오르골은 몇 번을 감아 돌려도 같은 곡을 냅니다. 곡을 정하는 것은 끼워 둔 원통 하나뿐입니다. 오늘 날씨도 돌리는 사람도 곡을 바꾸지 못합니다. 원통을 바꿔 끼우면 그때 곡이 바뀝니다.

프로그램이나 함수가 이렇게 움직이면 결정적이라고 부릅니다. 결과를 정하는 것이 입력뿐이라는 뜻입니다. 같은 입력을 넣으면 오늘 돌리든 한 달 뒤에 돌리든, 이 서버에서 돌리든 저 서버에서 돌리든 같은 출력이 나옵니다. 이 성질이 없는 것은 비결정성이라고 부릅니다.

이 문서는 결정성의 쓰임 가운데 데이터 처리와 계산에서 쓰는 뜻을 중심으로 봅니다. 실시간 시스템처럼 다른 분야에서 쓰는 뜻은 마지막 소절에서 짚습니다.

입력은 인자만이 아니다

결정성을 따질 때 입력은 함수의 인자만 가리키지 않습니다. 함수가 읽는 모든 값이 입력입니다. 인자로 받지 않았는데 결과를 바꾸는 값을 암묵적 입력이라고 부릅니다.

암묵적 입력의 흔한 예가 현재 시각입니다. 함수 안에서 시계를 읽으면 호출할 때마다 다른 값이 들어옵니다. 인자는 같아도 결과가 달라지므로 그 함수는 결정적이지 않습니다.

아래 코드는 어제 날짜를 골라 그날 치 데이터를 처리하는 작업입니다. 처리할 날짜를 인자로 받지 않고 함수 안에서 오늘 날짜를 읽습니다.

Python
from datetime import date, timedelta

def target_day():
    return date.today() - timedelta(days=1)

# 6월 3일에 돌린다
target_day()        # 06-02
# 6월 10일에 다시 돌린다
target_day()        # 06-09

인자는 둘 다 비어 있는데 결과가 다릅니다. 6월 2일 치가 틀려서 다시 돌리려 해도, 이 함수는 돌리는 날의 전날만 고릅니다. 지난 날짜를 다시 만들 방법이 없습니다.

오늘 날짜를 인자로 끌어내면 결정적인 함수가 됩니다. 이제 결과를 정하는 값이 인자 목록에 다 보입니다.

Python
def target_day(run_day):
    return run_day - timedelta(days=1)

d = date(2026, 6, 3)
target_day(d)       # 06-02
target_day(d)       # 06-02

같은 날짜를 넘기면 언제 돌려도 06-02 가 나옵니다. 6월 10일에도 6월 3일을 넘기면 그날의 작업을 다시 만들 수 있습니다.

결정성을 깨는 암묵적 입력

현재 시각 말고도 결과를 몰래 바꾸는 값은 여럿입니다. 어디서 새어 드는지 알아야 막을 수 있습니다. 아래 표는 자주 만나는 넷과 고정하는 법을 모았습니다.

암묵적 입력 왜 결과가 달라지나 고정하는 법
현재 시각 돌리는 때마다 다른 값을 읽는다 기준 시각을 인자로 받는다
난수 뽑을 때마다 다른 수가 나온다 시드(난수를 뽑기 시작하는 첫 값)를 인자로 받는다
실행 순서 여러 스레드가 끝나는 순서가 매번 다르다 결과를 모은 뒤 정렬한다
바깥 상태 파일·데이터베이스·네트워크 응답이 그새 바뀐다 읽은 값을 적어 두고 그것을 입력으로 쓴다

난수 줄의 시드는 난수 생성기가 수열을 만들기 시작하는 첫 값입니다. 컴퓨터가 만드는 난수는 대개 시드에서 계산으로 뽑아냅니다. 그래서 시드가 같으면 뽑히는 수열도 같습니다. 시드를 입력으로 받으면 난수를 쓰는 코드도 결정적이 됩니다.

실행 순서 줄은 여러 스레드가 일을 나눠 할 때 생깁니다. 어느 스레드가 먼저 끝날지는 운영체제가 스레드를 어떻게 돌리느냐에 달려 있어 매번 다릅니다. 끝나는 대로 결과를 이어 붙이면 붙는 순서도 매번 바뀝니다. 그래서 다 모은 뒤 정렬해 순서를 고정합니다.

여러 스레드가 같은 값을 동시에 고치면 문제가 한 겹 더 깊어집니다. 먼저 닿는 스레드에 따라 값 자체가 갈립니다. 이 상황을 경쟁 조건이라고 부릅니다. 이때는 정렬로 못 막습니다. 한 번에 한 스레드만 그 값을 고치게 막아야 합니다.

순서는 계산 결과도 바꿉니다. 부동소수점은 실수를 유한한 비트로 어림해 담는 방식입니다. 더할 때마다 끝자리를 반올림하므로 (a + b) + c 와 a + (b + c) 가 다를 수 있습니다. 병렬로 나눠 더한 합계가 돌릴 때마다 끝자리만 조금씩 다른 까닭이 이것입니다.

키로 값을 찾는 해시 테이블을 처음부터 끝까지 도는 순서도 조심합니다. 어떤 언어는 프로세스마다 해시값을 다르게 섞습니다(해시 무작위화). 그러면 같은 데이터를 담아도 실행할 때마다 도는 순서가 바뀝니다. 그 순서대로 결과를 쓰면 같은 데이터에서 다른 파일이 나옵니다. 그래서 키를 정렬한 뒤 그 순서로 돕니다.

처방은 둘로 모입니다. 시각·난수·바깥 상태는 함수 밖으로 끌어내 인자로 받습니다. 순서는 결과를 모은 뒤 정렬해 고정합니다. 아래 그림은 앞의 처방, 인자로 끌어내는 쪽입니다.

flowchart TD
    subgraph B["고치기 전"]
        A1["인자"] --> F1["함수"]
        T1["현재 시각"] -.->|몰래 읽는다| F1
        R1["난수"] -.->|몰래 읽는다| F1
        S1["바깥 상태"] -.->|몰래 읽는다| F1
        F1 --> O1["출력 · 돌릴 때마다 다르다"]
    end
    subgraph A["고친 뒤"]
        A2["인자 · 기준 시각 · 시드 · 읽어 둔 값"] --> F2["함수"]
        F2 --> O2["출력 · 늘 같다"]
    end

위 그림에서 점선이 암묵적 입력입니다. 고친 뒤에는 결과를 정하는 값이 전부 인자 한 줄로 들어옵니다.

결정성에 기대는 곳

결정성은 「다시 계산해서 맞춰 본다」는 일이 있는 곳이면 어디서나 필요합니다. 한 번 계산한 결과와 다시 계산한 결과가 같아야 앞의 결과를 믿거나 대신할 수 있기 때문입니다.

쓰임 결정성이 없으면
해시 함수 저장할 때와 찾을 때 고른 위치가 달라져 값을 못 찾는다
재처리 · 백필 지난 기간을 다시 돌린 결과가 처음 결과와 달라 어느 쪽이 맞는지 모른다
테스트 코드를 안 고쳤는데 통과와 실패가 번갈아 나온다
상태 기계 복제 같은 명령을 받은 서버들이 서로 다른 상태로 갈라진다
재현 가능한 빌드 같은 소스로 빌드했는지 결과물만 보고 확인할 수 없다

해시 함수는 결정성이 첫째 성질입니다. 해시 테이블은 키의 해시값으로 값을 넣을 위치를 고릅니다. 찾을 때도 같은 방식으로 위치를 고릅니다. 두 번의 계산이 같은 값을 내지 않으면 넣은 값을 다시 못 찾습니다.

데이터 파이프라인에서는 재처리가 결정성에 기댑니다. 재처리는 지난 데이터를 다시 흘려 결과를 새로 만드는 일입니다. 데이터를 결과로 바꾸는 규칙이 결정적이어야 다시 돌린 결과를 처음 결과 대신 쓸 수 있습니다.

백필은 재처리의 한 가지로, 비어 있는 과거 기간의 결과를 뒤늦게 채우는 일입니다. 이때는 처리할 날짜를 골라 돌릴 수 있어야 합니다. 앞에서 본 두 번째 target_day 처럼 날짜를 인자로 받는 함수여야 지난 날짜를 하루씩 넘겨 채울 수 있습니다.

테스트에서는 결정성이 없으면 불안정한 테스트가 생깁니다. 불안정한 테스트는 코드를 그대로 두고 여러 번 돌렸을 때 통과와 실패가 섞여 나오는 테스트입니다. 실패해도 코드 탓인지 운 탓인지 알 수 없어 결국 아무도 그 테스트를 믿지 않게 됩니다.

상태 기계 복제는 여러 서버가 같은 상태를 나눠 갖게 하는 방법입니다. 서버마다 같은 명령을 같은 순서로 적용합니다. 명령 처리가 결정적이면 모든 서버가 같은 상태에 닿습니다. 명령 안에서 현재 시각이나 난수를 읽으면 서버마다 다른 값을 읽어 상태가 갈라집니다.

재현 가능한 빌드는 같은 소스를 누가 언제 빌드해도 바이트 하나까지 같은 결과물이 나오는 빌드입니다. 빌드 도구가 빌드한 시각이나 빌드한 컴퓨터의 폴더 경로를 결과물에 적어 넣으면 이 성질이 깨집니다. 둘 다 소스에 없는 암묵적 입력입니다. 결과물이 같아야 배포된 파일이 공개된 소스에서 나왔는지 누구나 다시 빌드해 맞춰 볼 수 있습니다.

멱등성과 다르다

결정성과 자주 함께 불리는 성질이 멱등성입니다. 멱등성은 같은 값을 한 번 적용하든 여러 번 적용하든 남는 상태가 같은 성질입니다. 결정성은 계산이 내는 값의 성질입니다. 멱등성은 그 값을 적용한 뒤 남는 상태의 성질입니다.

둘은 따로 놉니다. 날짜별 합계를 결정적으로 계산하고 그 결과를 표에 한 줄씩 덧붙이는 작업을 떠올려 봅시다. 같은 날짜로 두 번 돌리면 두 번 다 같은 합계를 냅니다.

그런데 표에는 같은 줄이 두 번 쌓입니다. 결정적이지만 멱등하지 않은 작업입니다.

반대로 날짜를 키로 삼아 값을 덮어쓰는 쓰기는 멱등합니다. 같은 값으로 몇 번 덮어써도 그 날짜에는 그 값 한 줄만 남습니다.

그런데 덮어쓸 값을 돌리는 순간의 실시간 가격에서 계산하면, 계산이 돌릴 때마다 다른 값을 냅니다. 쓰기는 멱등하지만 계산은 결정적이지 않은 작업입니다.

백필처럼 지난 기간을 다시 돌리는 일은 둘 다 필요합니다. 결정성은 다시 계산한 값이 같다는 것을 지킵니다. 멱등성은 그 값을 다시 써도 두 벌이 안 쌓인다는 것을 지킵니다.

순수 함수와 다르다

순수 함수는 결정성에 조건이 하나 더 붙은 것입니다. 같은 입력에 같은 출력을 내는 데 더해 부수 효과가 없어야 합니다. 부수 효과는 함수가 값을 돌려주는 것 말고 바깥에 남기는 흔적입니다. 파일에 쓰기, 데이터베이스 행 고치기, 로그 남기기가 부수 효과입니다.

그래서 결정적이지만 순수하지 않은 함수가 있습니다. 합계를 계산해 돌려주면서 매번 로그를 한 줄 쓰는 함수는 늘 같은 합계를 냅니다. 로그라는 흔적을 남기므로 순수하지는 않습니다.

결정성이 치르는 대가

결정성을 지키면 코드가 번거로워집니다. 시각 하나, 시드 하나까지 인자로 받아야 하므로 인자 목록이 길어집니다. 부르는 쪽도 그 값들을 챙겨 넘겨야 합니다.

순서를 고정하는 비용도 있습니다. 병렬로 나눠 계산한 결과를 정해진 순서로 모으려면 정렬하거나 먼저 끝난 쪽이 기다려야 합니다. 순서를 신경 쓰지 않을 때보다 느려집니다.

일부러 결정성을 버려야 하는 곳도 있습니다. 암호 키나 솔트는 남이 예측할 수 없어야 합니다. 이런 값을 고정된 시드에서 뽑으면 시드를 아는 사람이 같은 값을 만들어 냅니다.

다른 분야의 결정성

실시간 운영체제에서 결정성은 결과가 아니라 걸리는 시간의 성질입니다. 언제 돌려도 걸리는 시간이 정해 둔 상한을 넘지 않는다는 뜻입니다. 이 성질이 있어야 한 작업이 가장 오래 걸릴 때의 시간을 미리 따질 수 있습니다. 그 값을 믿어야 마감을 약속할 수 있습니다.

계산 이론에서는 결정적 유한 오토마톤이라는 말로 만납니다. 오토마톤은 입력 글자를 하나씩 읽으며 상태를 옮겨 가는 계산 모형입니다. 지금 상태와 읽은 글자가 정해지면 다음 상태가 하나로 정해질 때 결정적이라고 부릅니다. 다음 상태가 여럿일 수 있으면 비결정적입니다.

관련 항목

결정성과 짝을 이루는 성질

멱등성 · 순수 함수 · 참조 투명성 · 부수 효과 · 불변성 · 재현성

결정성을 깨는 원인

비결정성 · 암묵적 입력 · 경쟁 조건 · 난수 · 시스템 시계 · 부동소수점 · 해시 무작위화

결정성을 지키려고 쓰는 수단

시드 · 의사 난수 생성기 · 의존성 주입 · 가짜 시계 · 테스트 더블 · 스냅샷

결정성에 기대는 데이터 작업

해시 함수 · 해시 · 재처리 · 백필 · 변환 · 데이터 파이프라인 · 데이터 계보

결정성에 기대는 복제와 검증

상태 기계 복제 · 복제 로그 · 이벤트 소싱 · 재현 가능한 빌드 · 불안정한 테스트 · 결정적 시뮬레이션 테스트

결정성을 일부러 버리는 쓰임

솔트 · 암호학적 난수 생성기 · 논스 · 무작위 알고리즘

시간과 계산 이론에서 쓰는 결정성

실시간 운영체제 · 최악 실행 시간 · 지터 · 결정적 유한 오토마톤 · 비결정적 유한 오토마톤

다른 이름: determinism · deterministic · 결정적