요약

  • NFS 버전 2는 수정 연산이 안정 저장소에 기록된 후에 반환되도록 했습니다. 이는 상태 없는 서버의 복구를 단순화했지만, 모든 쓰기에 영속성 지연을 부과했습니다.
  • NFSv3는 요청된 커밋 수준과 실제 달성된 커밋 수준을UNSTABLE,DATA_SYNC,FILE_SYNC로 분리했습니다. 성공 응답에는 실제 바이트 수와 불투명한 쓰기 검증자도 함께 실렸습니다.
  • 클라이언트는COMMIT이나 충분히 강한 결과가 올 때까지 불안정 데이터를 보유했습니다. 검증자가 바뀌었다면 이전 불안정 범위를 잃었을 가능성이 있는 것으로 취급하고 재전송해야 했습니다.

버퍼를 점유한 채 돌아온 성공 호출

클라이언트가 원격 파일의 한 범위에 쓰기 요청을 보내고NFS3_OK를 받았다고 합시다. 상태 코드만 보면 마무리처럼 들립니다. 그러나 같은 결과가committed = UNSTABLE을 말할 수 있습니다. RPC는 성공했고 일부 바이트는 수용되었지만, 서버 메모리가 유실되는 상황에서 그 바이트가 살아남는지는 여전히 미결입니다.

이 대비는RFC 1094에서 시작됩니다. NFS 버전 2는 수정 연산을 동기로 취급했습니다. 쓰기가 반환되면 클라이언트는 요청 데이터가 안정 저장소에 있다고 가정하고 자신의 사본을 버릴 수 있었습니다. 이 규칙은 상태 없는 서버라는 이상을 뒷받침했습니다. 장애 후 클라이언트는 긴 세션을 재구성하는 대신 연산을 재시도하면 되었습니다. 대신 영구 매체의 지연이 모든 쓰기 응답에 들어갔습니다.

RFC 1813은 이 경로를 쓰기 처리량 병목으로 지목했습니다. NFSv3의 주요 변경 중 하나가 안전한 비동기 쓰기 지원이었습니다. “안전”이 의미한 것은 조기 응답이 영속적이 되었다는 뜻이 아니었습니다. 영속성을 마치거나 쓰기를 반복하는 데 필요한 사본, 상태, 근거를 프로토콜이 보존한다는 뜻이었습니다.

OK가 네 가지 의미를 지니지 않도록 만든 세 수준

WRITE 요청의stable필드는UNSTABLE,DATA_SYNC,FILE_SYNC를 요청할 수 있었습니다.FILE_SYNC는 데이터와 모든 파일 시스템 메타데이터가 응답 전에 안정 저장소에 도달할 것을 요구했습니다.DATA_SYNC는 데이터와 그것을 회수하는 데 필요한 메타데이터를 요구했습니다.UNSTABLE의 경우 서버는 응답 전에 전부, 일부, 또는 전혀 영속화하지 않을 수 있었습니다.

서버는 요청을 단순히 반향하지 않았습니다.committed결과는 실제 달성된 수준을 보고했습니다. 빠른 비휘발성 저장소를 갖춘 서버는 더 약한 요청에 대해서도FILE_SYNC를 반환하고 클라이언트가 상태를 조기에 정리하게 할 수 있었습니다. 요청된 수준보다 낮은 것을 제공할 수는 없었습니다. 다음 의무를 지배하는 것은 요청의 선호도나 바깥의 성공 코드가 아니라 반환된 값이었습니다.

응답에는count도 실렸습니다. 서버는 짧은 쓰기를 수행하고 실제 바이트 수를 보고할 수 있었습니다. 클라이언트는 나머지를 다시 써야 했습니다. 데이터가 영속적인지 묻기 전에, 어떤 범위가 실제로 수용되었는지 먼저 확정해야 했습니다.

이 설계는 대시보드가 자주 뭉개버리는 명제들을 분리했습니다. 호출이 응답을 받았다. 계수된 범위가 기록되었다. 그 범위가 명시된 커밋 수준에 도달했다. 이제 클라이언트는 잊어도 된다. 이 명제들은 모두 참이 될 수 있지만, 같은 순간에 그럴 수는 없습니다.

파일이 아니라 시대를 가리킨 검증자

마지막 결과 필드는verf, 쓰기 검증자였습니다. 이 불투명한 쿠키는 관련된 서버 인스턴스 동안 일관성을 유지해야 했고, 미커밋 데이터가 유실될 수 있는 인스턴스 사이에서는 바뀌어야 했습니다. 재부팅이 흔한 경우였지만, 의미상의 경계는 전원을 껐다 켜는 물리적 행위가 아니라 휘발성 보유 권한의 상실이었습니다.

검증자는 내용 다이제스트가 아니었습니다. 하나의 인스턴스 동안 서로 다른 파일과 범위가 같은 검증자를 공유할 수 있었습니다. 같다는 것이 파일이 변경되지 않았거나, 요청된 모든 바이트가 기록되었거나, 다른 클라이언트가 수정하지 않았음을 증명하지는 않았습니다. 그 권한은 더 좁았습니다. 이후 연산이 이전 불안정 바이트를 보유했던 인스턴스에 이 검증자가 여전히 속하는지 판정할 수 있다는 것입니다.

그 결과 메모리는 클라이언트 쪽에 남았습니다. RFC 1813은 세 가지 버퍼 상태를 기술했습니다. dirty, 커밋이 필요한 완료 상태, 그리고 완료 상태입니다. 가운데 범주가 더 빠른 응답의 대가가 드러난 모습입니다. 전송은 끝났는데 영속성 보유 권한은 아직 열려 있을 수 있습니다.

COMMIT은 비교를 거친 뒤에야 빚을 닫았다

COMMIT은 이전에 불안정 상태로 남은 수정 데이터를 안정 저장소로 밀어 넣었습니다. 범위를 지정할 수 있었고, 오프셋 0과 카운트 0은 파일 처음부터 끝까지를 의미했습니다. 이를 원격·범위 지정 가능한fsync라고 부르는 것은 유용하지만 한계가 있습니다. 응용 프로그램의 내용 선택, 백업, 복제본을 보증하지는 않았습니다.

성공한 COMMIT은 또 하나의 검증자를 반환했습니다. WRITE 응답의 검증자와 일치하면 클라이언트는 수용과 영속화를 같은 서버 인스턴스에 묶을 수 있었습니다. 다르다면 이전 인스턴스의 휘발성 보유 권한은 더 이상 증거 가치가 없었습니다. 클라이언트는 이전 검증자 아래UNSTABLE로 반환된 범위를 잃었을 가능성이 있는 것으로 취급하고 다시 써야 했습니다.

“가능성”이라는 말이 중요합니다. 일부 바이트는 장애 전에 디스크에 도달했을 수 있습니다. 검증자의 변화는 전체 손실을 측정하지 않았습니다. 무엇이 살아남았는지 구분할 수 있는 능력을 제거했을 뿐입니다. 알려진 오프셋을 다시 쓰는 것은 불확실성 아래에서의 복구 가능한 행동이었지, 정밀한 관찰의 주장이 아니었습니다.

반대 방향의 한계도 중요했습니다. 검증자가 변하지 않았다는 것은 파일 영수증이 아니었습니다. COMMIT 상태, 실제 카운트, 커버된 범위, 다른 쓰기 주체와의 조율이 여전히 중요했습니다. 서버 인스턴스의 연속성은 증명의 한 차원이었습니다.

안정 저장소에는 경계가 있었다

RFC 1813은 안정 저장소를 반복된 전원 장애, 일정한 하드웨어 장애, 재부팅 주기를 포함한 반복된 소프트웨어 크래시에 걸쳐 영속하는 것으로 기술했습니다. 동시에 안정 저장소 모듈 자체의 장애는 제외했습니다. 영속성은 백업, 복제, 논리적 일관성, 불멸의 동의어가 아니었습니다.

사양은 클라이언트와 서버 사이, 또는 클라이언트 사이의 엄격한 캐시 일관성도 약속하지 않았습니다. 커밋된 범위가 다른 곳의 낡은 뷰와 공존할 수 있었습니다. 영속성은 바이트가 어느 장애 클래스를 살아남는지에 답하고, 일관성은 참여자가 어느 상태를 현재로 취급해야 하는지 묻습니다.

이 메커니즘은 원래 세대보다 오래 살아남았습니다.RFC 3530은 이를 NFSv4로 가져왔고,RFC 7530은 요청된 커밋 수준과 반환된 커밋 수준의 구분을 유지했습니다.RFC 8881은 복구 규칙을 명시적으로 진술합니다. 쓰기 검증자가 바뀌면 클라이언트는 이전 값 아래UNSTABLE4로 반환된 데이터가 잃어버렸다고 가정하고 복구해야 합니다. 더 많은 상태를 지닌 프로토콜조차 이 좁은 인스턴스 경계가 필요했습니다.

성능은 작업을 지우는 대신 옮겼다

NFSv3는 서버가 영속화를 묶어서 처리하도록 허용해 정상 경로를 단축했습니다. 그 작업은 클라이언트 쪽에 보유된 메모리, 범위 장부, 이후의 COMMIT, 인스턴스 변경 후의 재생 트래픽으로 다시 나타났습니다. 큰 불안정 적체는 저장소 서비스가 장애에서 복귀하려는 바로 그 시점에 복구 폭발이 될 수 있었습니다.

따라서 비동기성은 소유자가 있는 조건부 부채였습니다. 정상 상태 처리량만 고려해 용량을 잡은 시스템은 자신의 장애 경로에서 용량을 빌리게 될 수 있었습니다. 프로토콜은 이 빚을 숨기지 않았습니다. 중간 상태에 이름을 붙이고, 그것을 언제 정리할 수 있는지를 결정하는 근거를 제공했습니다.