지연은 줄일 수 없고 감출 수만 있다
Esc

    지연은 줄일 수 없고 감출 수만 있다

    –

    혼자 도는 게임에서는 입력과 화면 사이에 아무것도 없습니다. 버튼을 누르면 그 프레임에 값이 바뀌고, 다음 프레임에 그려집니다.

    둘이 하면 사이에 거리가 들어옵니다. 내가 누른 것을 상대 화면에도 그리려면 그 사실이 상대에게 가야 하고, 가는 데 시간이 걸립니다.

    이 시리즈가 다루는 것은 전부 그 시간에 대한 대응입니다. 그래서 첫 편은 그 시간이 무엇으로 이루어져 있는지부터 봅니다. 무엇을 줄일 수 있고 무엇은 못 줄이는지 모르면, 뒤에 나올 예측이나 보간이 왜 필요한지도 설명이 안 됩니다.

    왜 왕복으로만 재는가

    네트워크 지연을 이야기할 때 거의 항상 RTT(round-trip time, 왕복 시간)를 씁니다. 보낸 뒤 그에 대한 답이 돌아오기까지의 시간입니다.

    정작 알고 싶은 것은 한쪽 방향에 걸리는 시간인데, 왜 왕복으로 잴까요.

    양쪽 시계가 안 맞기 때문입니다. 단방향 지연을 재려면 보낸 시각과 받은 시각을 빼야 하는데, 그 두 시각은 서로 다른 기계의 시계에서 나옵니다. 두 시계가 몇 밀리초만 어긋나 있어도 결과는 그만큼 통째로 틀립니다. 그 어긋남은 재는 대상보다 클 수도 있습니다.

    왕복은 한 시계에서만 읽습니다. 보낸 시각도 받은 시각도 내 기계의 시계입니다. 그래서 시계가 맞든 안 맞든 상관이 없습니다. 한 시계 안에서의 뺄셈이니까요.

    단방향 시간이 왕복의 절반이라는 보장은 없습니다. 가는 길과 오는 길이 다른 경로일 수 있고, 한쪽 방향만 혼잡할 수도 있습니다. 그래도 왕복은 잴 수 있고 단방향은 못 재니, 실무에서는 왕복을 쓰고 절반을 짐작으로 씁니다.

    줄일 수 없는 바닥이 있다

    여기서 먼저 짚을 것이 있습니다. 지연에는 하한이 있습니다.

    신호는 빛보다 빨리 갈 수 없습니다. 게다가 광섬유 안에서는 진공보다 느립니다. 대략 3분의 2 정도입니다.

    그래서 거리만으로 이미 바닥이 정해집니다.

    구간 대략 거리 왕복 하한
    같은 도시 30km 0.3ms
    서울 ↔ 도쿄 1,200km 12ms
    서울 ↔ 싱가포르 4,700km 47ms
    서울 ↔ 서부 미국 9,000km 90ms

    광섬유 안의 속도를 초당 20만km 로 잡으면 계산이 단순해집니다. 거리(km)를 100으로 나누면 왕복 밀리초입니다.

    직선 거리로 계산한 값이라 실제로는 이보다 항상 큽니다. 케이블은 직선으로 깔리지 않고, 중간에 장비를 여러 번 지납니다. 서울과 서부 미국 사이의 실제 왕복은 보통 130~180ms쯤입니다.

    여기서 바로 나오는 결론이 하나 있습니다.

    대륙 간 대전에서 100ms 는 최적화로 없앨 수 있는 값이 아닙니다. 코드를 아무리 고쳐도, 장비를 아무리 좋은 것으로 바꿔도 그 바닥은 그대로입니다. 할 수 있는 일은 그 시간을 감추는 것뿐입니다. 이 시리즈의 4편부터가 전부 그 이야기입니다.

    지연은 네 조각으로 되어 있다

    그 바닥 위에 나머지가 얹힙니다. 패킷 하나가 한 구간을 지날 때 드는 시간은 넷으로 나뉩니다.

    조각 무엇 무엇에 비례하나 우리가 건드릴 수 있나
    전파 지연 신호가 선을 타고 가는 시간 거리 못 합니다. 서버 위치를 옮기는 것 말고는
    전송 지연 패킷을 선 위로 밀어 넣는 시간 패킷 크기 ÷ 회선 속도 거의 못 합니다. 요즘 회선에서 작은 패킷은 무시할 수준
    큐잉 지연 장비 안에서 순서를 기다리는 시간 그 순간의 혼잡 여기가 유일한 자리입니다
    처리 지연 장비가 주소를 보고 갈 길을 정하는 시간 장비 성능 못 합니다. 보통 아주 작습니다

    넷 중 셋은 물리와 장비가 정합니다. 우리가 손댈 수 없습니다.

    남는 것은 큐잉뿐입니다. 그리고 큐잉은 우리가 얼마나 보내느냐에 따라 우리가 스스로 만들어내는 지연이기도 합니다. 7편에서 대역폭을 아끼는 이야기를 길게 하는 이유가 여기 있습니다. 덜 보내면 큐가 덜 차고, 큐가 덜 차면 지연이 줄어듭니다. 많이 보내면 그 반대가 됩니다. 내가 보낸 것이 내 지연을 늘립니다.

    관이 굵어져도 짧아지지 않는다

    여기서 자주 섞이는 것이 있습니다. 대역폭과 지연은 다른 축입니다.

    • 대역폭은 한 번에 얼마나 흘릴 수 있는가입니다
    • 지연은 한 조각이 저쪽에 닿기까지 얼마나 걸리는가입니다

    물을 보내는 관으로 생각하면, 대역폭은 관의 굵기이고 지연은 관의 길이입니다. 관을 두 배로 굵게 만들어도 길이는 그대로입니다.

    그래서 회선을 100Mbps 에서 1Gbps 로 바꿔도 핑은 안 떨어집니다. 같은 이유로, 위치 정보 몇 바이트를 보내는 게임에서 대역폭은 대개 문제가 아닙니다. 문제는 그 몇 바이트가 도착하는 시각입니다.

    관이 굵어져서 나아지는 것이 하나 있긴 합니다. 위 표의 전송 지연과, 굵은 관에서는 잘 안 차는 큐잉입니다. 큰 것을 보낼 때는 이쪽이 커지니, 대역폭이 지연에 전혀 영향을 안 준다는 말은 아닙니다. 다만 작은 패킷을 자주 보내는 게임에서는 굵기로 해결되는 부분이 거의 없습니다.

    평균이 아니라 흔들림이 문제다

    핑이 50ms 라고 하면 보통 평균을 말합니다. 그런데 게임에서 실제로 아픈 것은 평균이 아닙니다.

    왕복이 매번 정확히 50ms 라면, 모든 것이 50ms 늦게 온다는 사실만 알면 됩니다. 50ms 뒤를 기준으로 그림을 맞춰 두면 화면은 부드럽습니다.

    문제는 그것이 40ms 였다가 90ms 였다가 45ms 인 경우입니다. 이 흔들림을 지터(jitter)라고 합니다.

    흔들림은 큐잉에서 나옵니다. 내 패킷이 도착했을 때 장비의 큐가 비어 있었는지 차 있었는지는 매번 다릅니다. 그 차이가 그대로 도착 시각의 차이가 됩니다.

    지터가 왜 평균보다 나쁜지는 간단합니다. 늦게 오는 것 하나가 그 프레임의 그림을 망칩니다. 앞의 아홉 개가 제때 왔어도, 열 번째가 안 오면 그 순간 화면은 멈추거나 튑니다. 평균은 좋아 보이는데 체감은 나쁜 상태가 이렇게 만들어집니다.

    그래서 받는 쪽은 대개 버퍼를 둡니다. 온 것을 바로 쓰지 않고 조금 쌓아 두었다가 씁니다. 늦게 온 것이 따라잡을 시간을 벌어 주는 것입니다. 6편에서 다룰 보간이 바로 이 버퍼 위에서 돌아갑니다.

    장점
    • 흔들림을 거의 다 흡수합니다. 웬만큼 늦게 온 것도 제 차례 전에 도착합니다.
    • 화면이 부드럽습니다. 멈추거나 튀는 일이 줄어듭니다.
    단점
    • 그만큼 과거를 봅니다. 버퍼 길이가 곧 추가 지연입니다.
    • 조작이 늦게 느껴집니다. 반응 속도가 중요한 장르에서는 이쪽이 더 아픕니다.
    장점
    • 최신에 가깝게 보여 줍니다. 추가로 늘어나는 지연이 적습니다.
    단점
    • 흔들림에 그대로 노출됩니다. 하나만 늦어도 버퍼가 비고, 그리면 화면이 멈춥니다.
    • 빈 자리를 짐작으로 메우게 됩니다. 짐작이 틀리면 튑니다.

    정답이 없습니다. 어느 쪽으로 기울일지는 장르가 정합니다. 격투나 슈터는 짧은 쪽으로, 여러 명이 넓게 움직이는 쪽은 긴 쪽으로 기울입니다.

    잃어버리거나 순서가 바뀌거나

    늦게 오는 것 말고도 두 가지가 더 일어납니다. 사라지는 것과 순서가 바뀌는 것입니다.

    사라지는 이유는 대개 큐가 넘쳐서입니다. 장비가 받은 것을 처리하기 전에 다음 것이 계속 들어오면, 자리가 없어서 버립니다. 무선에서는 신호가 깨지는 경우도 더해집니다.

    순서가 바뀌는 이유는 가는 길이 하나가 아니어서입니다. 먼저 보낸 것이 좀 돌아가는 길로 가고, 나중에 보낸 것이 짧은 길로 가면 뒤엣것이 먼저 도착합니다.

    여기서 중요한 것은, 이 둘 다 보내는 쪽에서는 일어난 줄 모른다는 점입니다. 보내는 쪽 입장에서는 그냥 보냈을 뿐입니다. 받는 쪽에서 비어 있거나 뒤집혀 있는 것을 발견해야 알게 됩니다.

    보낸 쪽은 1·2·3·4·5 를 순서대로 보냈는데, 받는 쪽에 쌓이는 것은 1·2·5·3 이고 4는 아예 오지 않습니다. 이 상태를 어떻게 처리할지가 다음 두 편의 주제입니다. 2편의 TCP 는 이것을 전부 대신 정리해 주고, 3편의 UDP 는 하나도 안 해 줍니다.

    프레임으로 환산하면

    지금까지의 숫자를 게임이 쓰는 단위로 바꿔 봅니다.

    60fps 면 한 프레임이 16.6ms 입니다. 왕복 100ms 는 그 프레임으로 여섯 개입니다.

    무슨 뜻인가 하면 이렇습니다. 내가 지금 프레임에서 버튼을 누르고, 그것을 서버가 판단해서 답을 돌려준다면, 그 답은 여섯 프레임 뒤에 도착합니다.

    그동안 화면이 멈춰 있을 수는 없습니다. 그 여섯 프레임도 그려야 합니다. 그려야 하는데 정답은 아직 없습니다.

    이 빈자리를 무엇으로 메우느냐가 게임 네트워크의 거의 전부입니다.

    • 내 캐릭터는 답을 기다리지 않고 먼저 움직입니다. 답이 오면 그때 맞춥니다 (5편)
    • 남의 캐릭터는 조금 지난 것을 보여 줍니다. 대신 부드럽습니다 (6편)
    • 쏘는 순간의 판정은 서버가 그 사람의 시점으로 되감아서 봅니다 (6편)

    전부 지연을 없애는 방법이 아닙니다. 지연이 있다는 사실을 안 보이게 만드는 방법입니다.

    정리

    • 지연은 왕복(RTT)으로 잽니다. 양쪽 시계를 맞출 수 없기 때문입니다
    • 거리가 만드는 하한이 있습니다. 대륙을 건너면 100ms 대는 최적화 대상이 아닙니다
    • 지연은 전파·전송·큐잉·처리로 나뉘고, 우리가 건드릴 수 있는 것은 큐잉뿐입니다
    • 대역폭과 지연은 다른 축입니다. 관이 굵어져도 길이는 그대로입니다
    • 평균보다 흔들림(지터)이 아픕니다. 그래서 버퍼를 두고, 버퍼는 곧 추가 지연입니다
    • 큐가 넘치면 사라지고, 길이 갈리면 순서가 바뀝니다. 보낸 쪽은 모릅니다
    • 왕복 100ms 는 60fps 에서 여섯 프레임입니다. 그 여섯 프레임도 그려야 합니다

    다음 편에서는 이 문제들을 전부 대신 처리해 주는 프로토콜을 봅니다. TCP 는 사라진 것을 다시 보내고 뒤집힌 것을 바로 세워 줍니다. 그 대가로 무엇을 내놓는지가 다음 편의 주제입니다.