희우 위키

HOME ABOUT ME STUDY RSS

캐시 일관성 문제 (Cache Coherence Problem with Service Architecture) #1 - CPU Bus Snooping

  • OS,
  • Architecture

들어가며

캐시 레이어를 도입한 서비스 아키텍처를 운영하다보면 다음과 같은 문제에 직면할 수 있다.

캐시 레이어가 여러 계층으로 나뉘어져 있고 각 서비스에 독립적인 캐시레이어가 존재한다면, 이 레이어 간의 일관성은 어떻게 유지하지?

이 포스팅은 약 3편에 나누어 업로드 할 예정이다. 첫 번째 포스팅에서는 간략한 용어 정리와 CPU 아키텍처에서 캐시 일관성을 유지하기 위한 기법(프로토콜, 그 중에서도 버스 스누핑과 관련된)을 몇 가지 소개하겠다.

Cache Coherence Problem

다음과 같은 4개의 코어를 갖는 CPU를 살펴보자. 현대의 CPU는 데이터 접근의 성능을 개선하기 위해 대부분 L1 캐시 (~L2 캐시), L3 캐시 레이어를 보유하고 있다.

CPU 아키텍처

만약, 다음 사진과 같이 하나의 코어(Core#1) 에서 캐시 라인에 쓰기(Write)작업을 수행하고자 한다면 어떤 일이 발생될까?

쓰기 작업 발생

당연히, 같은 캐시 라인의 다른 데이터들은 모두 무효화(갱신)을 해주어야 한다.

쓰기 작업 무효화

이 때, 무효화가 되기 전에 데이터를 사용하거나, 쓰기 작업을 진행하면 어떻게 될까? 일관성이 깨진다.

CPU 아키텍처에서는 이런 일관성 문제를 해결하기 위해 어떤 방법을 사용하는지 알아보기 전에 몇 가지 용어에 대해서 설명하고자 한다.

Coherence & Consistency

Coherence (일관성)Consistency (동시성)은 다른 개념이다. 물론, 동시성 이슈(배타적인 영역에 대한 동시 접근)로 인해 일관성이 깨지는 경우가 많기에 이 두가지는 서로 연관성이 있다.

다만, 데이터의 관점에서 일관성(Coherence)은 데이터가 항상 최신의 상태를 만족하는가, 내가 바라보는 데이터가 Dirty(변경분) 상태는 아닌가에 대해 정의하고 동시성(Consistency)는 공유 데이터에 접근할 때 의도한 대로 올바른 처리가 이루어지는가 (이를테면, 연산의 순서를 보장해야한다든지) 에 대해서 정의한다고 보면 될 것 같다.

Write Policy (To Write on Memory)

메모리에 데이터를 쓸 때의 정책 두 가지를 간단하게 짚고 넘어가겠다.

쓰기 버퍼(Write Buffer) 영역 내에서 메모리로 데이터를 쓰고자 할 때는 다음과 같은 두 가지 방식이 존재한다.

  • Write Through : 데이터를 쓰면서 메모리로 바로 flush 한다.
  • Write Back : 데이터를 쓰고, 향후에 메모리로 flush 한다. (일시적인 Inconsistency 상태를 감내한다.)

전자가 후자에 비해 느리다. 캐시라인에 데이터를 쓰면서 메모리에 쓰는 과정을 추가적으로 작업해주어야 한다. 반대로, Write Back은 캐시 영역에만 써두고 향후에 메모리에 쓰는 것을 의미한다.


Cache Coherence in CPU Architecture (캐시 일관성 프로토콜)

여기까지, 간략한 용어에 대해서 살펴보았다. 용어를 설명한 이유는 이 포스팅을 포함한 앞으로의 포스팅을 조금 더 쉽게 이해하기 위함이다.

CPU 아키텍처(Symmetric Multi-Processor, Chip Multi-Processor)에서는 각 코어에 존재하는 캐시라인에 대해서 어떻게 일관성을 유지할 수 있을까?

버스 스누핑 (Bus Snooping)

컴퓨터의 각 유닛들은 컨트롤 시그널, 데이터, 어드레스를 주고받기 위한 통로로 버스(Bus)를 사용한다는 것은 알고 있을 것이다.

이 통로 역할을 하는 버스(Bus)를 모니터링하여 제어하는 버스 스누핑 프로토콜이 존재한다. 버스 스누핑 프로토콜은 각 캐시라인을 모니터링하여 분산된 공유 자원에 대해 일관성을 맞추기 위한 목적이다.

버스 스누핑 프로토콜의 종류는 여러가지가 있지만, 그 중에서도 개념을 잡기 좋은 MSI, MESI, MOESI에 대해서 알아보겠다.


MSI (Modified - Shared - Invalidate) Protocol

MSI 프로토콜은 다음과 같은 세 가지의 상태값을 관리한다. 각 상태의 앞글자를 취득하여 MSI 프로토콜로 명명하였다.

  • M (Modified) : 해당 캐시 라인의 데이터는 이 캐시에서만 변경이 이루어진 상태이다.
  • S (Shared) : 해당 캐시 라인의 데이터는 다른 위치에도 같은 값으로 존재한다. (메인 메모리와 값이 동일하다)
  • I (Invalidate) : 해당 캐시 라인의 데이터는 더이상 유효하지 않은 상황이다. (초기 상태)

로컬 캐시에서 READ/WRITE 작업이 발생할 때 상태 전이

MSI프로토콜

  • 초기 상태의 캐시라인은 모두 I(Invalidate) 상태이다.
  • 캐시라인에 쓰기(Write) 작업을 하기 전에는 반드시 캐시 라인을 M(Modified) 상태로 설정하고, 다른 캐시라인은 모두 I(Invalidate) 상태로 업데이트 한다.
  • 만약 I(Invalidate) 상태에서 데이터를 읽으려고 시도한다면, 먼저 S(Shared) 상태 혹은 M(Modified) 상태인 캐시라인이 존재하는지 보고, 존재한다면 그 캐시라인으로부터 데이터를 받고, 그게 아니라면 메모리로부터 데이터를 받는다.
  • 내가 M(Modified) 상태인데, 누군가가 M(Modified) 라인으로 설정한다면, 내가 가지고 있는 캐시를 무효화한다.
  • 내가 M(Modified) 상태인데, 누군가가 Read(Shared) 신호를 보낸다면 한다면, 내가 가지고 있는 캐시를 메모리로 플러쉬(WriteBack)한다.

다만, 이러한 MSI 프로토콜 모델은 단점이 존재한다.

예를 들어, 다음과 같이 코어가 4개인 CPU에서 한 CPU만 데이터를 가지고 있다고 해보자.

Core#1만 데이터를 가지고 있는 Shared 상태에서 쓰기 작업을 통해 M(Modified) 상태로 업데이트 하려고 한다면, 다른 캐시라인도 모두 I(Invalidate)로 설정해야 한다.

다른 캐시라인은 현재 이미 Invalidate 상태이므로, 상태 업데이트를 전파하지 않아도 됨에도 불구하고 버스 트래픽을 과도하게 유발한다.

스크린샷 2023-11-28 오후 11 44 31


MESI (Modified - Exclusive - Shared - Invalidate) Protocol

위와 같은 문제를 해결하기 위해, MSI 프로토콜에서 E(Exclusive) 상태를 추가한 것이 MESI 프로토콜이다. 이 프로토콜은 인텔 CPU 아키텍처에서 주로 사용된다. (약간 변형된 MESIF를 사용하기도 한다.)

  • M (Modified) : 해당 캐시 라인의 데이터는 이 캐시에서만 변경이 이루어진 상태이다.
  • E (Exclusive) : 해당 캐시 라인의 데이터는 오직 자기 자신만이 가지고 있다.
  • S (Shared) : 해당 캐시 라인의 데이터는 다른 위치에도 같은 값으로 존재한다. (메인 메모리와 값이 동일하다)
  • I (Invalidate) : 해당 캐시 라인의 데이터는 더이상 유효하지 않은 상황이다.

로컬 캐시에서 READ/WRITE 작업이 발생할 때 상태 전이

MESI프로토콜

  • 초기 상태의 캐시라인은 모두 I(Invalidate) 상태이다.
  • MSI는 자기 자신만 데이터를 가지고 있어도 S(Shared)상태이나, MESI는 혼자만이 가지고 있는 상태를 E(Exclusive)로 설정한다. (최소한 두 곳 이상에서 참조할 경우에만 S(Shared) 상태가 된다.)
  • E(Exclusive) 상태에서는 어떠한 작업이 발생되어도, 다른 캐시 라인의 상태 변경은 없다. (즉, 쓰기 작업을 수행하여 M상태로 변경되더라도 버스에 시그널을 보내지 않는다.)
  • 그 외 상태는 모두 MSI와 동일하게 동작한다.

다만, 이러한 MESI 프로토콜에서도 단점이 존재한다. 만약 Write Back인 상황에서 Modified 상태인 캐시라인에 대해 Shared 상태로 업데이트할 때 반드시 메모리로 flush로 해주어야 하는가?

캐시 간의 전송(Cache-To-Cache Transfer)의 비용은 저렴하지만, 메모리 쓰기 작업 비용은 생각보다 높다.


MOESI (Modified - Owned - Exclusive - Shared - Invalidate) Protocol

이러한 문제를 해결하기 위해, 다음과 같은 상태(O, Owned)가 하나 더 추가된 프로토콜이 고안되었다. (AMD 아키텍처에서 사용된다.)

  • M (Modified) : 해당 캐시 라인의 데이터는 이 캐시에서만 변경이 이루어진 상태이다.
  • O (Owned) : 해당 캐시 라인의 데이터는 이 캐시가 주인이다.
  • E (Exclusive) : 해당 캐시 라인의 데이터는 오직 자기 자신만이 가지고 있다.
  • S (Shared) : 해당 캐시 라인의 데이터는 다른 위치에도 같은 값으로 존재한다. (메인 메모리와 값이 동일하다)
  • I (Invalidate) : 해당 캐시 라인의 데이터는 더이상 유효하지 않은 상황이다.

로컬 캐시에서 READ/WRITE 작업이 발생할 때 상태 전이

스크린샷 2023-11-28 오후 11 57 25

  • M(Modified)인 상태에서 다른 캐시라인으로부터 READ 신호를 받으면, O(Owned)로 상태를 변경하고, 메모리 대신 데이터를 전송한다. (즉 메모리의 역할을 대신 수행하는 것이다.)
  • 그 외 동작은 모두 MESI 프로토콜과 동일하다.

정리하며

OS(운영체제)를 공부하면서 CPU 아키텍처, 버스, 버스 스누핑 등에 대해서 들어본 적이 있다. CPU 아키텍처에서 각 캐시 레이어의 일관성을 유지하기 위한 몇 가지 방법(프로토콜)에 대해서 간단하게 알아보았다.

제품을 운영하는 회사의 서비스 아키텍처에서도 분명히 캐시 일관성 문제는 발생한다. 이 문제를 해결하기 위한 다양한 방법이 있겠지만, 아이디어가 떠오르지 않는다면 컴퓨터 공학에서 비슷한 사례를 이미 우아하게 해결해놓은 사례를 살펴보는 것이 도움이 될 것 같다.

다음 포스팅에서는 캐시 일관성을 위한 디렉터리 프로토콜에 대해서 다루어 보려고 한다.

읽어주셔서 감사합니다.

참고 내용