락스텝 알고리즘에 대한 고찰
27 Dec 2022
락스텝 알고리즘은 멀티 플레이에서 클라이언트 간의 동기화를 유지하기 위해 사용되는 동기화 방식 중 하나이다. 모든 클라이언트가 동일한 입력을 받아 동일한 시뮬레이션을 수행함으로써 모두가 동일한 게임 상태를 유지한다.
원칙: 입력이 동일해야 같은 결과를 낸다
‘동일한 입력을 제공했을 때에 동일한 결과를 내야 한다’는 것은 락스텝 알고리즘의 핵심 원칙이다. 이를 어렵게 한마디로 말하면, 락스텝 알고리즘은 결정론적(deterministic) 특성을 가진 알고리즘이라고 할 수 있다. 결국 모든 로직은 이 원칙에 맞추어서 구현되어야 하며, 이에 거슬러서는 안 된다. 모든 클라이언트가 동일한 입력을 받아 동일한 시뮬레이션을 수행하고 일관된 게임 상태를 유지하는 것이 목표다.
원칙을 깨는 방해 요소들
앞서 언급한 원칙을 방해하는 요소들이 있을 수 있다. 즉, 결정론적 알고리즘 원칙을 깨는 요소들을 그대로 남겨둔 채로 게임 로직을 구현하면 심각한 문제가 발생할 수 있다. 대표적인 방해 요소들은 다음과 같다:
- 하드웨어 차이: 하드웨어 환경 차이는 부동소수점 연산 결과에 영향을 줄 수 있다. 따라서 부동소수점은 게임 시뮬레이션 로직 코드에서는 제외되어야 한다.
- 컴파일러 최적화: 멀티 플랫폼을 지원하는 경우, 서로 다른 컴파일러나 컴파일러 최적화 설정은 동일한 코드를 다르게 실행할 수 있다.
- 비결정적 코드: 시뮬레이션 로직 코드에서 비결정적 요소가 존재하면 결과가 달라질 수 있다. 난수, 멀티 스레딩에서의 경합 상태 등과 같은 요소들이 시뮬레이션에 영향을 주어선 안된다.
알고리즘 취약성 1: 레이턴시
락스텝 알고리즘은 모든 클라이언트가 동일한 입력을 동일한 순서로 처리해야 하기 때문에, 한 클라이언트의 네트워크 지연은 전체 시스템의 지연을 초래할 수 있다. 즉, 어떤 클라이언트가 지연되면 다른 모든 클라이언트도 그 클라이언트를 기다려야 하는 상황이 발생하는 것이다. 그 외에도 전반적인 네트워크 상태가 불안정한 경우, 게임이 자주 반복적으로 멈칫하거나 끊겨서 유저의 플레이 경험을 해칠 수 있다.
이런 문제를 보완하기 위해, 클라이언트와 서버 간의 입력 데이터를 버퍼링하여 네트워크 지연에 의해 발생하는 문제를 어느 정도 해소할 수 있다. 입력 데이터에 대해 일정 기간의 버퍼를 두고서 로직을 수행하는 것이다. 이를 통해 어느 정도 완만한 반응성을 유지할 수 있으며, 또한 실시간 네트워크 상태 변화에 따라 적절히 이 버퍼의 크기를 조정함으로써 전반적인 반응성을 수정할 수 있다.
알고리즘 취약성 2: 클라이언트 해킹
락스텝 알고리즘에서는 각 클라이언트가 독립적으로 게임 로직을 시뮬레이션하게 된다. 이 말인즉슨, 모든 게임 로직은 클라이언트 상에서 모두 노출되어 있다는 의미이기도 하다. 어떠한 환경보다도 게임 데이터 조작은 더 쉽게 일어날 수 밖에 없다.
이런 약점을 보완하기 위해 흔히 서버 기반 검증을 활용할 수 있다. 단순히 클라이언트 간의 결과를 비교하여 이질적인 결과를 가진 클라이언트를 선별하는 방법도 있을 수 있고, 특정 시점에서 서버가 직접 시뮬레이션을 돌려서 이 결과를 검증 기준으로 삼는 방식도 가능하다.
레퍼런스
Lockstep Implementation in Unity3D - Part 1