Memcached의 확장성 개선

9,892 views

Published on

0 Comments
11 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
9,892
On SlideShare
0
From Embeds
0
Number of Embeds
6,386
Actions
Shares
0
Downloads
181
Comments
0
Likes
11
Embeds 0
No embeds

No notes for slide

Memcached의 확장성 개선

  1. 1. INTEL® CORPORATION Memcached의 확장성 개선
  2. 2. 1! MEMCACHED 및 웹 서비스 소개Memcached는 Facebook[1], Twitter[2], Reddit[3] 및 YouTube[4]와 같은 클라우드 및 웹 서비스 제공회사에서 사용하는 key-value 메모리 캐시로, 웹 데이터를 소비자에게 서비스하는 데 있어 지연 시간을 줄이고 데이터베이스 및 컴퓨팅 서버[5]에 대한 증설을 줄여주게 한다. Latency를 줄이는 것 외에도memcached의 확장성 있는 아키텍처 (scale-out) 는 memcached 서버를 간단하게 추가만 하여 처리량을 높일 수 있다. 그러나 코어 수가 4개를 넘으면 성능 저하가 발생하기 때문에 수직 scalability (scale-up)에는 문제가 있다[1][6].이 연구에서는 왕복 시간 (RTT, Round-Trip Time)에 대한 1밀리초 이하의 지연에 대한 SLA (ServiceLevel Agreement)를 유지하면서 concurrent data structure, 새로운 캐시 교체 알고리듬 및 네트워크 최적화를 활용하여 Intel® Xeon® E5 서버 프로세서에서 현 버전(v1.6)의 오픈 소스와 비교했을 때 6배 이상처리량을 높이는 memcached 최적화 방법을 소개한다. 그림 1은 v1.6 memcached 베이스라인에 비해6배 빠른 속도의 최적화된 버전을 보여준다. 여기서는 32개의 논리적 코어에서 최적화된 버전이 초당 3억1천 5백 만 처리량 (RPS)을 제공하며, 1밀리초의 절반 수준의 RTT를 보인다. 그림 1 기본 오픈 소스 코드 버전 1.6.0_beta1 및 최적화된 버전의 구성을 사용하여 측정한 최대 처리량 비교Memcached는 key-value 쌍으로 이뤄진 간단한 데이터 타입을 저장하며, NoSQL 데이터베이스와 유사하지만 NoSQL처럼 영구적 (persistent) 이지는 않다. Memcached는 모든 key-value 쌍을 메모리에 저장하므로 서버장애나 오류가 발생했을 때 저장된 데이터가 모두 손실된다. 이 글에서는 키 (key)와 해당 키의 값(value)을 지칭할 때 ‘캐시 항목 (cache item)’ 이라는 용어를 사용한다. 이때 키는 고유한 값이다. 웹 1
  3. 3. 서비스 아키텍처에서 memcached 애플리케이션은 그림 2에서 보는 바와 같이 프론트엔드 (Front-end)웹 서버 (또는 계층 (tier))와 백엔드 (Back-end) 데이터베이스 사이에 위치한다. 그림 2 프론트엔드 웹 계층 및 백엔드 데이터베이스 사이에 위치하는 memcached 서버Memcached의 용도는 데이터 요청을 가로채어 가능한 경우 이를 캐시 (시스템 메모리)에서 직접 서비스하게 만들고, 백엔드 데이터베이스에 연결된 디스크 스토리지 access를 줄이는 것이 목적이다. 그리고 미리 계산된 값을 캐시에서 저장하고 조회하게 하여, 요청때 마다 많은 양의 계산을 피할 수도 있다. 두 경우모두 조회 또는 data 결과를 계산하는 시간이 줄어들게 된다. Memcached 논리적 캐시에는 서버군들로구성이 되어, 각 서버는 전체 논리적 캐시의 일부분으로 시스템 메모리를 제공한다 [7][8][9]. 예를 들어, 각노드에 128GB의 메모리 공간이 있는 10개 노드로 구성된 memcached 클러스터는 전채적으로 1.28TB의 논리적 캐시를 제공한다. 논리적 캐시는 웹 서비스를 위한 영구 데이터 저장소인 백엔드 데이터베이스또는 컴퓨팅 서버의 query/계산 결과 데이터로 채워진다. 캐시 항목은 LRU
  4. 4.  (Least Recently Used) 정책과 TTL (Time To Live)을 사용하여 유지 관리된다. 항목이 제거되면 해당 슬롯은 최신 항목으로 채워진다.웹 서비스에서 하나의 요청 (http request) 은 memcached, 데이터베이스 및 기타 서비스로의 여러 번의요청이 필요할 수 있다. 효율적인 캐싱 전략을 사용하면 memcached에 대한 조회 횟수는 데이터베이스조회 횟수보다 몇 십배 많을 수 있다 (DB 조회를 줄임). Memcached의 시스템 메모리에서 데이터 조회는같은 데이터를 데이터베이스에서 조회하는 것보다 열배 이상 빠르며 (마이크로초 대 밀리초 단위), 대부분의 경우 데이터를 on-demand로 계산/조회 하는 것보다 몇 십배 이상 빠르다. 따라서 웹 서비스 요청에 대해 빠른 사용자 응답 시간을 제공하기 위해서는 데이터베이스 조회 및 on-demand 계산은 피해야 한다. 2
  5. 5. 1.1! 용어이 글에서는 다음과 같은 용어를 사용한다.LRU (Least Recently Used) – memcached에서 캐시 항목 추가를 위한 공간을 확보하기 위해 캐시에서필요없는 항목을 결정 eviction 알고리듬. 가장 오래된 항목들을 evict하는 알고리듬.Base – 수정되지 않은 기본 버전(1.6.beta_0)의 memcached이며, memcached.org에서 직접 다운로드 가능.Bags  – 성능 향상을 위해 최적화된 버전의 memcached 약어. ‘Bags’은 ‘Bag LRU’라는 수정된 LRU 캐시 업데이트 전략이고, 개발된 코드의 첫 번째 섹션이다.RTT (Round Trip Time) – memcached 요청이 경과된 시간으로, 클라이언트로 응답이 올 때까지 클라이언트에서 서버로 가는 요청 경과 시간 포함.SLA (Service Level Agreement) – 사용자에 대한 수용할 수 있는 웹 서비스 요청 응답 시간을 유지하기위해 허용되는 최대 RTT. 응답 시간이 SLA를 넘어서는 경우 웹 서비스 요청을 생성하는 데 수용할 수 없는 응답 시간이 나타난다. (프론트엔드 웹 계층으로 전송되는) 웹 서비스 요청당 (멤캐시된 논리적 캐시로전송되는) 여러 memcached 요청이 있을 수 있다. 이 글에서는 프론트엔드 웹 계층 클라이언트 요청에 대한 memcached SLA 응답 시간은 1밀리초이다.NIC (Network Interface Card) – 시스템에 사용된 네트워크 카드.2! MEMCACHED 아키텍처2003년 memcached가 도입될 당시에는[5] 여러 코어가 있는 x86 프로세서가 매우 적었으며 멀티 소켓서버의 가격이 매우 비쌌다. Intel® Xeon® 듀얼코어는 2005년, 쿼드코어 프로세스는 2009년까지는 출시되지 않았다. 오늘날, 듀얼 소켓, 8코어 및 16코어 서버 시스템은 웹 서비스를 위한 구성 요소이며, 프로세서 개발 로드맵에 따르면 코어 개수의 증가는 계속 될 것으로 보인다[10]. 이러한 첨단 시스템은 여러가지 워크로드에서 여러 스레드 또는 프로세스를 사용하여 동시에 실행할 수있게 되어 있으나, memcached는 현재의 데이터 구조 및 소프트웨어 아키텍처 때문에 아직 그것이 불가능하다 [1][6].2.1! 데이터 구조memcached는 다음과 같은 4가지 주요 데이터 구조가 있다. • 캐시 항목을 찾기 위한 해쉬 테이블 • 캐시가 가득 찼을 때 캐시 항목 제거 (eviction) 순서를 결정하는 LRU (least recently used) list 3
  6. 6. • 키 (key), 데이터 (value), 플래그 및 포인터들을 담고 있는 캐시 데이터
  7. 7.  구조 • 캐시 항목 데이터 메모리 관리자인 슬랩 할당자 (slab allocator)해쉬 테이블 데이터 구조는 bucket 배열이다. 배열 크기 (k) 는항상 2의 거듭제곱이고 ‘2k-1’의 값을 구해hash 마스크로 사용한다. Bit-wise AND (예: hash_value hash_mask) 계산으로 hash 값이 들어 있는bucket을 신속하게 찾아낸다. bucket들은 NULL로 끝나는 캐시 항목의 linked-list이다. 그림 3는 이러한 데이터 구조를 보여준다. 그림 3 캐시 항목 조회에 사용되는 해쉬 테이블 데이터 구조제거 (eviction) 순서를 결정하는 데 사용되는 LRU는 단일 크기의 슬랩 (아래 설명된 메모리 할당 단위) 마다 존재하며, 그 슬랩의 모든 캐시 항목들을 접근 순서대로 유지하는 double linked-list이다. Doublelinked-list를 형성하기 위한 포인터들은 각 캐시 항목 구조에 유지되며, 캐시 항목이 LRU에서 위치 조정될때마다 수정된다. LRU list에서 어떤 캐시 항목을 제거 (eviction)하는 경우 이 list의 tail에 있는 캐시 항목부터 검사하여 메모리 재사용이 가능한 가장 오래된 캐시 항목을 찾아낸다. 그림 4는 LRU 데이터 구조를보여준다. 4
  8. 8. 그림 4 캐시 항목 제거 순서를 결정하는 데 사용되는 현 버전의 오픈 소스 LRU 데이터 구조캐시 항목 데이터 구조에 key/value 쌍 데이터가 들어있고, 또 다음과 같은 metadata 들이 들어있다. • 해쉬 테이블에서 bucket 당 single linked-list를 가르키는 포인터 • LRU에서 double linked-list에 사용되는 포인터 • 캐시 항목에 동시에 접근하는 스레드 수를 나타내는 reference counter • 캐시 항목 상태 플래그 • 키 (key) • 값 길이 (바이트) • 값 (value)슬랩 할당자는 캐시 항목에 대한 메모리 관리 기능을 제공한다. 캐시 항목은 상대적으로 크기가 작아서, 시스템 call (malloc/free)을 사용한 작은 메모리 청크 (chunk)의 할당 및 해제는 속도가 느리고 스래싱(thrashing)이 발생할 가능성이 높다. 그래서 memcached는 이 방법 대신 메모리 할당 단위로 슬랩을 사용한다. 슬랩은 내부에 많은 항목들을 포함할 수 있는 큰 메모리 chunk이다. 예를 들어 1,024 byte 메모리 chunk의 슬랩은 64바이트 이하 캐시 항목을 16개까지 저장가능하다. 슬랩 할당자는 이렇게 큰 메모리들를 할당하고 free list를 유지한다. 캐시 항목이 접근 될 때마다 슬랩 할당자는 저장할 값 크기를 확인하고 수용할 수 있을 만큼 큰 슬랩내의 캐시 항목을 돌려준다. 어떤 경우에는 이 방법이 공간을 비효율적으로 사용하기도 하지만 성능이 좋고 메모리 스래싱을 피할 수 있다.2.2! 명령어Memcached 서버는 3가지 기본 명령어를 지원한다. 5
  9. 9. • GET - 캐시 항목 조회 • STORE - 캐시 항목 추가 • DELETE - 캐시 항목 제거또한 통계 (stats), 교체 (replacements), 연산 (arithmetic), 플러시 (flush) 및 업데이트 (updates)를 비롯한 15가지의 기타 명령들이 있다. 이런 명령어들의 코드 경로는 일반적으로 위 기본 명령의 순서를 따른다.예를 들어, 교체 (replacement)는 먼저 캐시 항목을 삭제 (DELETE)한 후 새로운 캐시 항목을 해쉬 테이블및 LRU에 저장 (STORE) 한다. GET 명령은 memcached 여러 명령 중 workload에서 가장 많이 사용되는 명령이어서 여기서는 GET 명령을 중점적으로 다룬다[11].3가지 기본 명령어 (GET, STORE, DELETE)에 대한 memcached에서의 실행 흐름은 다음과 같다. 1. NIC에
  10. 10.  요청
  11. 11.  packet이
  12. 12.  도착하고
  13. 13.  libevent에
  14. 14.  의해
  15. 15.  처리 2. worker
  16. 16.  thread • 연결
  17. 17.  및
  18. 18.  데이터
  19. 19.  패킷을
  20. 20.  받기 • 명령
  21. 21.  및
  22. 22.  데이터를
  23. 23.  확인 3. Hash
  24. 24.  값은
  25. 25.  키
  26. 26.  데이터에서
  27. 27.  생성 4. 해쉬
  28. 28.  테이블과
  29. 29.  LRU
  30. 30.  처리를
  31. 31.  위해
  32. 32.  global
  33. 33.  cache
  34. 34.  lock
  35. 35.  획득
  36. 36.  (Critical
  37. 37.  Section
  38. 38.  시작) • (STORE만
  39. 39.  해당)
  40. 40.   • 캐시
  41. 41.  항목
  42. 42.  메모리
  43. 43.  할당 • 캐시
  44. 44.  항목
  45. 45.  데이터
  46. 46.  구조에
  47. 47.  데이터를
  48. 48.  저장
  49. 49.  (플래그,
  50. 50.  키,
  51. 51.  값) • 해쉬
  52. 52.  테이블에서
  53. 53.  캐시
  54. 54.  항목을
  55. 55.  GET,
  56. 56.  STORE,
  57. 57.  DELETE하기
  58. 58.  위해
  59. 59.  해쉬
  60. 60.  테이블을
  61. 61.  탐색 • 캐시
  62. 62.  항목을
  63. 63.  LRU
  64. 64.  앞에
  65. 65.  삽입
  66. 66.  (GET,
  67. 67.  STORE)
  68. 68.  또는
  69. 69.  제거
  70. 70.  (DELETE)
  71. 71.  하면서
  72. 72.  LRU
  73. 73.  수정 • 캐시
  74. 74.  항목
  75. 75.  플래그를
  76. 76.  업데이트 5. Global
  77. 77.  cache
  78. 78.  lock
  79. 79.  해제
  80. 80.  (Critical
  81. 81.  Section
  82. 82.  끝) 6. 응답
  83. 83.  구성 7. (GET만
  84. 84.  해당)
  85. 85.  global
  86. 86.  cache
  87. 87.  lock
  88. 88.  확인
  89. 89.  (assert) • 캐시
  90. 90.  항목
  91. 91.  ref
  92. 92.  count를
  93. 93.  1
  94. 94.  감소
  95. 95.   • global
  96. 96.  cache
  97. 97.  lock
  98. 98.  해제 8. 요청한
  99. 99.  클라이언트
  100. 100.  (프론트엔드
  101. 101.  웹
  102. 102.  서버)로
  103. 103.  응답
  104. 104.  전송2.3! 프로세스 흐름그림 5에 데이터 요청 처리 시 cache cluster 관점에서 memcached 프로세스 흐름이 설명되어있다. 여러 서버가 함께 작동하여 더욱 큰 단일 논리적 데이터 캐시 역할을 수행한다. 기본적으로 이러한 서버는 대규모 DHT (distributed hash table: 분산해쉬테이블)을 구성한다. 그림 5를 찬찬히 살펴보면 클라이언트(일반적으로 사용자를 위한 응답을 구성하는 웹 tier 서버 형태나 계산용 데이터가 필요한 컴퓨팅 서버 형태)가 memcached로의 요청을 담당하는 것을 알 수 있다. Memcached가 시작되면 이러한 요청을 처리하기 위해 일반적으로 서버의 프로세서 (core) 갯수에 맞게 일정한 수의 worker thread가 생성된다. 6
  105. 105. 그림 5 오픈 소스 v1.6에서 캐시 항목 명령 (STORE/GET/DELETE)을 위한 프로세스 흐름클라이언트 요청들은 각 worker thread로 분산되어 처리된다. GET 명령의 경우, 각 스레드는 키와 그 키의 값 (value) 데이터 위치를 찾기 위해 해쉬 테이블 조회를 해야 한다. 또한 그 key-value가 최근에 액세스되었음을 표시하고 제거 (eviction) 순서를 업데이트하기 위해 LRU list 앞으로 이동한다. 안타깝게도 이 두가지 code path에 공유 데이터 구조에 대한 serialization (순차화)이 필요하다. 그렇게 데이터를 찾으면worker thread가 시스템 메모리에서 데이터를 조회하고 요청한 클라이언트로 전송한다.해쉬 테이블로의 접근을 보호하는 global cache lock을 통해 해쉬 테이블의 스레드 안정성 (thread-safety)을 보장한다. 또한 캐시 제거 (eviction)를 관리하기 위해 유지되는 LRU linked-list에서는, 스레드안정성을 보장하기 위해 캐시 항목의 LRU linked-list가 수정될 때도 동일한 global cache lock을 사용한다. 스레드는 네트워크 프로토콜 래퍼 (wrapper)인 ‘libevent’에 의해 만들어진다. 스레드는 ‘libevent’에서 요청을 대기하고 있다가 요청을 받으면 패킷에서 데이터를 빼내고 명령을 디코딩한다. 캐시 항목에 대한명령 (GET/STORE/DELETE/ARITHMETIC)이라고 가정한다면, 스레드는 키에서 hash 값을 계산하고 7
  106. 106. global cache lock을 획득한다. 스레드는 명령이 완료될 때까지 lock을 유지한 후 lock을 해제하고 응답을 구성한다. 이렇게 하면 memcached가 효과적으로 순차화 (serialize) 되어 데이터 일관성 및 스레드 안전성이 보장된다.2.3.1해쉬 테이블 조회Lock을 획득하고 난 후 해쉬 테이블 탐색 (traversal) 및/또는 수정 (manipulation)이 수행된다. 순차처리(serialized) 된 해쉬 테이블 접근은 STORE 또는 DELETE 명령 중에 포인터 쓰래싱 (pointer thrashing)과 linked-list bucket이 corrupt 되는 것에 대한 염려를 덜어준다. 해쉬 테이블 구현을 현재 있는 그대로남겨두고 lock만 제거하면, linked-list에서 서로 인접한 두 캐시 항목이 삭제되고 작업이 완료되었을 때linked-list가 정상적인 체인에 있는 캐시 항목을 더 이상 가리키지 않게 되어 버린다. 이 내용은 그림 6을통해 확인할 수 있으며 각 행은 시간 경과 순서이다. 그림 6 두 개의 스레드에서 캐시 항목을 동시에 제거 시 linked-list 손상2)에서 한 스레드는 노란색 캐시 항목 (#3)을 제거하고, 한 스레드는 주황색 캐시 항목 (#4)을 제거한다. 두개의 스레드 모두 이전 캐시 항목 포인터를 수정한다. 3)에서는 두 개 모두 다음 캐시 항목을 가리키는 포인터를 NULL로 변경한다. 마지막으로 4)에서는 캐시 항목이 제거되면 dangling pointer가 되어 버린다.GET 명령에도 lock이 필요하므로 linked-list를 변경하는 동안 bucket을 탐색하지 않는다. 변경 되고 있는linked-list를 탐색하면 스레드에서 존재하지 않는 메모리를 가리키는 포인터를 참조하여 세그멘테이션오류(segmentation fault)를 일으키거나 존재하는 캐시 항목을 누락 (miss)시킬 수 있다. 해쉬 테이블 데이터구조의 레이아웃은 그림 3을 참조한다.2.3.2LRU 수정해쉬 테이블 수정이 완료되면 캐시 항목은 LRU eviction에 사용되는 double linked-list 구조 (그림 4)에추가 (STORE)되거나 삭제 (DELETE)된다. GET 명령을 처리할 때 캐시 항목은 LRU의 현재 위치에서 제거되고 리스트의 head에 추가된다. 같은 LRU 리스트에 위치한 두 개의 캐시 항목을 제거하거나 head에 8
  107. 107. 동시에 삽입할 경우 리스트가 손상될 수 있으므로, LRU를 수정하는 동안에는 global cache lock을 유지한다. 깨지는 포인터가 하나 더 많다는 것을 제외하면 앞서 설명한 해쉬 테이블의 linked-list 손상과 동일하다.2.3.3 기타 순차처리된 (SERIAL) 명령해쉬 테이블 조회 및 LRU 수정 global 캐시 lock에 의해 보호되는 기본 코드 세그먼트이지만 다른 명령도lock으로 보호되어야 한다. 캐시 제거 (evictions), 할당(allocations) 및 플러시 (flushes)에서도 globalcache lock을 사용해야 한다. 이러한 명령은 LRU 또는 해쉬 테이블에 접근함으로 여러 스레드에 의한 동시 접근 (concurrent) 로 인해 손상이 발생할 수 있다. 또한 이 명령으로 수정되는 캐시 항목 플래그는global cache lock에 의해 보호되어야 스레드 안정성이 보장된다. 플래그 사용의 예로 ITEM_LINKED 플래그가 있으며 이 플래그는 해쉬 테이블에서 캐시 항목을 제거할 때 지워져야 한다. Global cache lock을통해 한 번에 하나의 worker 스레드만 이 플래그를 수정할 수 있도록 되어있다.2.3.4 병렬 작업Memcached 명령에 대한 대부분의 프로세스 흐름은 global cache lock에 의해 보호되어야 한다. 이 기능 외에 병렬 처리되는 작업에는 네트워크 전송 (수신 및 송신), 패킷 디코딩, 키 해싱 및 데이터 응답 구성이 있다.2.4! 성능 병목Global cache lock은 [1] 및 [6]에 설명된 것처럼 스레드가 4개 이상인 경우 성능 병목이 되는 주 원인이다. 이 증상은 테스트 (그림 7 및 표 1)를 통해서도 확인할 수 있다. 그림 7은 오픈 소스 memcached에서코어가 4개를 넘어설 때 처리량이 감소되는 것으로 나타난다. 이 lock은 coarse-grained (긴 code path를 serialize하기 때문에) 이어서, 순차 실행 시 소요되는 시간이 많으며 worker 스레드 간의 lock 경합이높다.이를 확인하기 위해 Intel® Vtune™ Amplifier XE11을 사용하여 stack trace를 생성하여 profiling 한 결과 대부분의 실행 시간이 lock (global cache lock 대기 등)에서 소요된 것으로 나타났다. Stack trace를더 자세히 확인한 결과 global cache lock이 경합을 유발한 것으로 나타났다. 문제의 신속한 확인을 위해global cache lock을 제거하자 (안전하지 않지만 효율적이라는 가정 하에), read-only (GET) 워크로드에서 처리량이 크게 증가했다.여러 다른 연구에서도 밝혀진 것과 같이 lock 경합이 프로그램의 병렬 성능 저하를 야기한다. 참고 문헌[12]는 경합과, 경합이 프로그램 성능 저하에 어떤 영향을 주는지에 대한 심도 있는 분석이 되어 있다. 우리는 global cache lock에 대한 경합을 피하면 병렬 성능 및 애플리케이션 성능 scalability가 개선될 것이라는 결론을 내렸다. 점유율 함수 DSO (Dynamic Shared Object) 60.40% _spin_lock [kernel.kallsyms] 1.40% ixgbe_poll /lib/modules/…/ixgbe/ixgbe.ko1 Vtune은 미국 및 다른 국가에서 통용되는 Intel Corporation의 상표이다. 9
  108. 108. 1.10% net_rx_action [kernel.kallsyms] 1.10% __pthread_mutex_lock_internal /lib64/libpthread-2.12.so 1.10% assoc_find /usr/local/lib/memcached/default_engine.so 1.10% copy_user_generic_string [kernel.kallsyms] 1.00% tcp_sendmsg [kernel.kallsyms] 0.90% irq_entries_start [kernel.kallsyms] 0.80% pthread_mutex_unlock /lib64/libpthread-2.12.so 0.70% GI_vfprintf /lib64/libc-2.12.so 0.70% audit_syscall_exit [kernel.kallsyms] 0.60% ip_route_input [kernel.kallsyms] 0.60% ixgbe_resume /lib/modules/…/ixgbe/ixgbe.ko 0.50% tcp_ack [kernel.kallsyms] 표 1 8개의 스레드로 memcached가 실행되는 서버에서 캡처한 routine 별 CPU 사용량 순위 그림 7 3~4개의 코어에서 최대 성능을 표시하는 Base (1.6 버전) memcached의 확장 성능3! MEMCACHED 최적화이 절에서는 multicore 서버에서 다중 스레드 실행을 위한 애플리케이션 확장성을 개선하기 위해memcached를 최적화하는 것에 대해 간략히 설명하려고 한다. 현재 memcached 아키텍처를 확인한 후global cache lock과 기본 성능 병목 현상을 최소화하거나 제거하는 기술을 알아본다.목표는 다음과 같다. 10
  109. 109. • 키의 수와 관계없는 동일한 성능 (즉, 1개 키와 100만 개의 키 조회) 제공 • LRU 제거를 위해 업데이트된 캐시 교체 전략에서는 그 이상의 hit rate. Benchmarking에서 사용된 워크로드에서 캐시된 값 조회에 대해 90% 이상의 hit rate • 캐시 관리를 위한 LRU 정확도 (LRU 상태를 기준으로 정확한 값이 제거 됨)이러한 목표에 대한 진행 상태를 측정하기 위해, 트랜잭션 set를 replay한 가상 워크로드를 사용하여 Base의 hit rate를 측정했다. 이 절에 설명된 최적화는 오픈 소스 memcached 1.6 버전에 새로운 엔진으로 구현되었다. 2012년 7월 현재, 이 최적화된 버전은 GitHub (https://github.com/rajiv- kapoor/memcached/tree/bagLRU) 에서 소스 코드를 다운로드할 수 있다.3.1! 데이터 구조최적화를 위해 두 개의 데이터 구조, 해쉬 테이블과 LRU가 수정되었다. • 해쉬 테이블 lock 메커니즘이 병렬 액세스를 허용되게 바뀜 • Bag LRU – 데이터 구조가 single linked-list bag (즉, 컬렉션)을 가진 서로 다른 크기의 LRU list의 배열로 변경됨해쉬 테이블에서 알고리즘은 변경하고 있지만 해쉬 테이블 데이터 구조에 대한 물리적 변경은 없다. 해쉬테이블 bucket을 수정하는 데 필요한 striped lock을 구현한다. striped lock은 해쉬 테이블 영역에 구현된 세분화된 lock의 집합 (global 해쉬 테이블 lock과 대조적)이다. 이 구현을 위해 먼저 Z개의 lock 집합을 생성하여 초기화한다. 여기서 Z는 2의 거듭제곱이며 2의 거듭제곱을 유지하면서 비트마스크를 사용하여 lock ID를 신속하게 계산한다. linked-list을 이동하기 위해 ‘Z-1’ 값을 잠글 bucket과 bit-wise AND를수행하여 해당 bucket을 보호하는 lock이 획득된다. 이 프로토콜은 모든 Z번째 bucket (그림 8)에 대해 공유된 lock을 제공하여 해쉬 테이블 lock에 대한 경합을 줄인다. 테스트에서는 lock의
  110. 110.   개수로 Z=32가 적합한 값이었다.LRU는 대규모 데이터 구조 변경이 필요한 위치이다. 병렬 LRU를 더 많이 구축하여 병렬 해쉬 테이블의 이점을 얻기 위해 병렬 LRU 데이터 구조에 대한 개념을 조사했다. Lock 회피 LRU 알고리즘과 데이터 구조에는 [13] 및 [14]와 같은 두가지 좋은 아이디어가 있다.이 두 개념으로부터 힌트를 얻은 bag LRU 개념으로 캐시 항목의 single linked-list ‘bags’을 구현했다. 각‘bag’은 비슷한 시간대에 insert나 touch된 항목들이 들어있고, atomic insert가 가능하다. 이 모델에 필요한 캐시 항목 ‘clean-up’ 기능이 필요하며
  111. 111.   이 용도로 ‘cleaner thread’가 존재한다. 이 스레드는만료되었거나 유효하지 않은 캐시 항목을 제거하고 bags에서 유지관리를 수행한다. 다이어그램 및 로직을비롯한 새로운 데이터 구조에 대한 보다 자세한 설명은 3.5 절을 참조하면 된다.3.2! 명령2.2 절에 설명된 memcached 명령이나 프로토콜에는 변경된 내용이 없다. 차이점이라면 명령 실행 방법론뿐이다. 11
  112. 112. 그림 8 각 bucket을 보호하는 striped lock 추가3.3! 프로세스 흐름그림 9는 수정 후 업데이트된 프로세스 흐름을 나타낸다. 위에서부터 아래까지 클라이언트, libevent 및worker thread 기능이 동일하게 유지된다. 해쉬 테이블 조회를 위한 순차화된 명령과 LRU 처리가 병렬 방식으로 작동하도록 수정된다.DELETE 및 STORE 명령은 striped lock (이전에 설명됨) (그림 8)이 있는 병렬 해쉬 테이블 방식을 사용하며 GET 명령은 non-blocking 및 병렬 방식으로 실행된다. 12
  113. 113. 그림 9 최적화된 버전의 1.6.0_beta1에서 캐시 항목 명령 (STORE/GET/DELETE)에 대한 프로세스 흐름아래 설명된 알고리즘 변경은 hash chain 손상을 방지하고 제거 중인 캐시 항목 포인터를 유지한다. 캐시항목 제거시 스레드가 preempt 되더라도 올바른 bucket에서 진행할 가능성이 높다. 이는 차단 해제된GET과는 미묘한 차이가 있다. 캐시 항목에 대해 스레드가 오랫동안 preempt 된 후 그 캐시 항목이 이동되었다면 GET은 false negative를 반환한다 (memcached가 캐시 항목이 해쉬 테이블에 실제로 있음에도불구하고 ‘캐시 항목 없음’을 반환).이 방법에 대한 찬반 양론이 있는데, memcached는 영구적 (persistent) 이지 않은 “cache” data를 다루기 때문에 false negative 문제보다 lock을 제거하여 추가 처리량을 실현하는 것에 촛점을 맞추었다. Falsenegative가 문제가 되는 경우 striped lock을 사용하면 된다 (어느 정도의 성능 저하가 있다). 하지만 예상한 대로 코어 수가 늘어나면 striped lock이 다시 경합 지점이 될 수 있다.이렇게 하여 LRU 처리는 위의 3.2 절과 3.5 절에 설명된 것처럼 non-blocking 및 병렬화 되었다. 각bags에서는 single linked-list가 있어서 캐시 항목을 atomic하게 삽입할 수 있어서 새로운 캐시 항목을 13
  114. 114. bag에 넣는 동안에 lock이 필요가 없이졌다. 여전히 CAS (Compare-and-Swap) 명령이 필요하며 이 경우 STORE 명령들만 수행되는 경우에는 경합 지점이 될 수 있다. 그러나 가장 많이 쓰이는 명령인 GET에대해서 최적화를 했기때문에 lock 또는 GET에 필요한 CAS 없이 bag LRU를 구현할 수 있었다.3.4! 병렬 해쉬 테이블 조회Memcached 트랜잭션 실행해보면 가장 처음으로 나타나는 성능저하 문제는 순차화된 해쉬 테이블 조회이다. 이전의 대부분의 Memcached 병렬 성능 고도화 노력들은 스레드가 캐시의 특정 부분 (예. 파티션)만 액세스하는 해쉬 테이블 분할에 중점을 두었다. 이렇게 하면 [16]에서 보여진 대로 lock 경합을 제거하고 성능을 높일 수 있다. 이 방식의 단점은 단일 파티션의 캐시 항목을 여러 차례 자주 액세스하여 해당 파티션을 처리하는 스레드들이 포화 상태일 수 있다는 점이다. 마찬가지로 어떤 파티션을 선호하는 트래픽 패턴을 가진 워크로드에서는 처리 성능이 저하될 수 있다. 파티션의 전체적인 성능은 lock 경합을 유발하지않는 캐시를 조회 하는 스레드들로 제한된다.이런 구현 방식은 트래픽의 대부분을 차지하는 GET 명령의 처리량을 최적화한다. 참고 문헌 [11]에서는Facebook의 캐시 서버의 상당 부분을 차지하는 트래픽이 GET 명령임을 보여준다. 이 비율은 불과 몇 개되지 않는 write가 대부분인 workload의 캐싱 tier만 제외하면, 어떤 경우 GET이 99.8%를 차지할 만큼높다. 이 내용은 [15] 및 [16]에서 수행한 성능 테스트와 일치한다 (두 테스트 방법 모두 GET 명령에 대한처리량만 측정했다).이러한 특징을 염두에 두고 GET 명령을 처리할 때 어떠한 lock도 필요없는 병렬 해쉬 테이블이 설계되었다. [12에서 보면 lock 경합은 아무리 짧더라도 성능에 영향을 준다 (트랜잭션 지연, Latency SLA에 의한전체 처리량). 미래에는 CPU가 더 빨라지고 코어 수도 늘어나므로 lock 경합도 증가할 것이다. 새로운 설계에서는 이러한 경향을 고려하여 GET에 대한 lock 경합을 완전히 제거했다.3.4.1GET LOCK 제거GET에서 lock을 제거하기 위해서는 hash chain 확장 (expansion) 과 수정 (즉, 삽입 및 제거) 이라는 두가지 경우를 해결해야 한다.해쉬 테이블이 bucket보다 훨씬 많은 양의 캐시 항목을 포함하는 경우 ‘확장 (expansion)’이 필요하다. 이경우 캐시 항목을 더 많이 추가하면 hash chain의 길이가 늘어나고 캐시 항목을 찾는데 필요한 시간과RTT가 증가한다. 이렇게 시간상 손해를 보지 않기 위해서, bucket 양의 2배인 새로운 해쉬 테이블을 다음과 같이 할당한다. • 모든 캐시 항목을 이전 테이블에서 가져옴 • 키들을 해싱 • 캐시 항목들을 새로운 테이블에 삽입Assoc_get() 로직: 1. bucket
  115. 115.  결정을
  116. 116.  위한
  117. 117.  hash
  118. 118.  해쉬
  119. 119.  마스킹 14
  120. 120. 2. If
  121. 121.  (!
  122. 122.  expanding) 3. |
  123. 123.  
  124. 124.   hash
  125. 125.  chain
  126. 126.  bucket을
  127. 127.  확인하여
  128. 128.  있는
  129. 129.  경우
  130. 130.  캐시
  131. 131.  항목
  132. 132.  반환 4. |
  133. 133.  
  134. 134.   If
  135. 135.  (캐시
  136. 136.  항목이
  137. 137.  없음) 5. |
  138. 138.  
  139. 139.   |
  140. 140.  
  141. 141.  
  142. 142.  
  143. 143.  
  144. 144.  
  145. 145.  If
  146. 146.  (확장
  147. 147.  
  148. 148.  확장
  149. 149.  bucket이
  150. 150.  bucket보다
  151. 151.  큼) 6. |
  152. 152.  
  153. 153.   |
  154. 154.  
  155. 155.  
  156. 156.  
  157. 157.  
  158. 158.  
  159. 159.  |
  160. 160.  
  161. 161.  
  162. 162.  
  163. 163.  
  164. 164.  
  165. 165.  새로운
  166. 166.  큰
  167. 167.  테이블에서
  168. 168.  bucket
  169. 169.  찾기 7. |
  170. 170.  
  171. 171.   |
  172. 172.  
  173. 173.  
  174. 174.  
  175. 175.  
  176. 176.  
  177. 177.  |
  178. 178.  
  179. 179.  
  180. 180.  
  181. 181.  
  182. 182.  
  183. 183.  이전
  184. 184.  bucket이
  185. 185.  비워질
  186. 186.  때까지
  187. 187.  대기 8. |
  188. 188.  
  189. 189.   |
  190. 190.  
  191. 191.  
  192. 192.  
  193. 193.  
  194. 194.  
  195. 195.  |
  196. 196.  
  197. 197.  
  198. 198.  
  199. 199.  
  200. 200.  
  201. 201.  새로운
  202. 202.  hash
  203. 203.  bucket을
  204. 204.  확인하여
  205. 205.  있는
  206. 206.  경우
  207. 207.  캐시
  208. 208.  항목
  209. 209.  반환 9. else
  210. 210.  if
  211. 211.  expanding 10. |
  212. 212.  
  213. 213.   확장이
  214. 214.  이전
  215. 215.  테이블에
  216. 216.  있는지
  217. 217.  확인 11. |
  218. 218.  
  219. 219.  
  220. 220.  
  221. 221.  
  222. 222.  
  223. 223.  
  224. 224.  hash
  225. 225.  chain
  226. 226.  bucket을
  227. 227.  확인하여
  228. 228.  있는
  229. 229.  경우
  230. 230.  캐시
  231. 231.  항목
  232. 232.  반환 12. |
  233. 233.  
  234. 234.  
  235. 235.  
  236. 236.  
  237. 237.  
  238. 238.  
  239. 239.  If
  240. 240.  (캐시
  241. 241.  항목이
  242. 242.  없음) 13. |
  243. 243.  
  244. 244.  
  245. 245.  
  246. 246.  
  247. 247.  
  248. 248.  
  249. 249.  |
  250. 250.  
  251. 251.  
  252. 252.  
  253. 253.  
  254. 254.  
  255. 255.  If
  256. 256.  (확장
  257. 257.  bucket이
  258. 258.  bucket보다
  259. 259.  큼) 14. |
  260. 260.  
  261. 261.  
  262. 262.  
  263. 263.  
  264. 264.  
  265. 265.  
  266. 266.  |
  267. 267.  
  268. 268.  
  269. 269.  
  270. 270.  
  271. 271.  
  272. 272.  새로운
  273. 273.  큰
  274. 274.  테이블에서
  275. 275.  bucket
  276. 276.  찾기 15. |
  277. 277.  
  278. 278.  
  279. 279.  
  280. 280.  
  281. 281.  
  282. 282.  
  283. 283.  |
  284. 284.  
  285. 285.  
  286. 286.  
  287. 287.  
  288. 288.  
  289. 289.  |
  290. 290.  
  291. 291.  
  292. 292.  
  293. 293.  
  294. 294.  
  295. 295.  
  296. 296.  이전
  297. 297.  bucket이
  298. 298.  비워질
  299. 299.  때까지
  300. 300.  대기 16. |
  301. 301.  
  302. 302.  
  303. 303.  
  304. 304.  
  305. 305.  
  306. 306.  
  307. 307.  |
  308. 308.  
  309. 309.  
  310. 310.  
  311. 311.  
  312. 312.  
  313. 313.  |
  314. 314.  
  315. 315.  
  316. 316.  
  317. 317.  
  318. 318.  
  319. 319.  
  320. 320.  새로운
  321. 321.  hash
  322. 322.  bucket을
  323. 323.  확인하여
  324. 324.  있는
  325. 325.  경우
  326. 326.  캐시
  327. 327.  항목
  328. 328.  반환캐시 항목을 찾을 때 확장 중인지 확인한다. 확장 중이 아니라면 GET 명령이 정상적으로 실행되고, 항목이있는 경우 찾은 캐시 항목을 반환한다. 이때 확장이 다시 시작이 되었을 경우가 있는데, 그 사이 캐시 항목이 이동될 수 있다. 이 경우 프로세스는 이전 bucket (이동 전에 있었던 hash table의 bucket)이 비워질때까지 대기한 후 그 캐시 항목에 대한 새로운 해쉬 테이블과 bucket을 확인한다.만약 해쉬 테이블의 확장이 진행 중인 경우 이전 bucket이 이미 이동되었는지 확인한다. 이전 bucket이이동되지 않은 경우 이전 bucket에서 캐시 항목을 찾는다. 비슷하게, 캐시 항목이 없는 경우 이전 bucket에 ‘bucket 이동’이 발생했는지 확인한다. 그런 경우 새로운 해쉬 테이블을 확인한다.다음으로 해결해야 할 문제는 STORE 및 DELETE 코드에서 처리되는 hash chain 수정이다. 포인터가 올바른 순서로 변경되기만 한다면 GET에는 hash chain traversal 대한 문제가 없다.3.4.2STORE/DELETE LOCK 제거STORE 및 DELETE 명령은 현재 오픈 소스 구현과 매우 비슷하다. Hash chain 및 동일한 chain 또는 캐시 항목에 대한 동시에 여러 건의 삽입/삭제를 안전하게 처리하기 위해 STORE 및 DELETE는 그림 8에서설명된 striped lock을 사용한다.Assoc_delete() 로직: 1.
  329. 329.  캐시
  330. 330.  항목
  331. 331.  bucket
  332. 332.  확인 2.
  333. 333.  bucket
  334. 334.  lock
  335. 335.  획득 3.
  336. 336.  If
  337. 337.  (expanding) 4.
  338. 338.  |
  339. 339.   If
  340. 340.  (확장
  341. 341.  bucket이
  342. 342.  bucket보다
  343. 343.  큼) 5.
  344. 344.  |
  345. 345.   |
  346. 346.  
  347. 347.  
  348. 348.  
  349. 349.  
  350. 350.  bucket
  351. 351.  lock
  352. 352.  해제 6.
  353. 353.  |
  354. 354.   |
  355. 355.  
  356. 356.  
  357. 357.  
  358. 358.  
  359. 359.  새로운
  360. 360.  bucket
  361. 361.  확인 7.
  362. 362.  |
  363. 363.   |
  364. 364.  
  365. 365.  
  366. 366.  
  367. 367.  
  368. 368.  bucket
  369. 369.  lock
  370. 370.  획득 8.
  371. 371.  |
  372. 372.   캐시
  373. 373.  항목이
  374. 374.  있는
  375. 375.  경우
  376. 376.  bucket에서
  377. 377.  캐시
  378. 378.  항목
  379. 379.  삭제 9.
  380. 380.  |
  381. 381.   bucket
  382. 382.  lock
  383. 383.  해제 10.
  384. 384.  Else
  385. 385.  if
  386. 386.  not
  387. 387.  expanding 11.
  388. 388.  |
  389. 389.   캐시
  390. 390.  항목이
  391. 391.  있는
  392. 392.  경우
  393. 393.  bucket에서
  394. 394.  캐시
  395. 395.  항목
  396. 396.  삭제 15
  397. 397. 12.
  398. 398.  |
  399. 399.   bucket
  400. 400.  lock
  401. 401.  해제확장 중인 bucket 이동을 비롯한 모든 hash chain 수정에는 striped lock이 필요하다. 이렇게 하면bucket lock을 얻은 후 확장을 확인하기 때문에 STORE 및 DELETE가 보다 간편해진다. STORE 또는DELETE routine에서 수정이 필요한 bucket이 확장으로 인하여 이동 중 이라면, 이전 bucket이 비워지고새로운 bucket을 확인할 때까지 bucket lock을 잡고 있어야 한다. Lock을 획득한 후 이동이 시작되면lock이 해제 될 때까지 확장하지 못한다. 이 로직은 STORE, DELETE 및 MOVE에 동일하게 적용된다.Assoc_insert() 로직: 1.
  402. 402.  캐시
  403. 403.  항목
  404. 404.  bucket
  405. 405.  확인 2.
  406. 406.  bucket
  407. 407.  lock
  408. 408.  획득 3.
  409. 409.  If
  410. 410.  (expanding) 4.
  411. 411.  |
  412. 412.   If
  413. 413.  (확장
  414. 414.  bucket이
  415. 415.  현재
  416. 416.  bucket보다
  417. 417.  큼) 5.
  418. 418.  |
  419. 419.   |
  420. 420.  
  421. 421.  
  422. 422.  
  423. 423.  bucket
  424. 424.  lock
  425. 425.  해제 6.
  426. 426.  |
  427. 427.   |
  428. 428.  
  429. 429.  
  430. 430.  
  431. 431.  새로운
  432. 432.  bucket
  433. 433.  찾기 7.
  434. 434.  |
  435. 435.   |
  436. 436.  
  437. 437.  
  438. 438.  
  439. 439.  bucket
  440. 440.  lock
  441. 441.  획득 8.
  442. 442.  |
  443. 443.   bucket
  444. 444.  조회.
  445. 445.  캐시
  446. 446.  항목이
  447. 447.  없는
  448. 448.  경우,
  449. 449.  bucket에
  450. 450.  삽입 9.
  451. 451.  |
  452. 452.   bucket
  453. 453.  lock
  454. 454.  해제 10.
  455. 455.  Else
  456. 456.  if
  457. 457.  (not
  458. 458.  expanding) 11.
  459. 459.  |
  460. 460.   bucket
  461. 461.  조회.
  462. 462.  캐시
  463. 463.  항목이
  464. 464.  없는
  465. 465.  경우,
  466. 466.  bucket에
  467. 467.  삽입 12.
  468. 468.  |
  469. 469.   bucket
  470. 470.  lock
  471. 471.  해제3.5! 병렬 LRU EVICTION캐시 항목 제거 순서를 결정하기 위해서는 일반적인 double linked list LRU 구현체를 대신할 수 있는 병렬 알고리즘이 필요하다. ‘Bag’ 구현의 개념은 캐시 항목을 타임스탬프 기반의 ‘bags’로 그룹화하는 것이다. 이러한 bag에서는 캐시 항목의 single linked list가 저장되므로 CAS (Compare-And-Swap) 를 사용하여 list의 tail에 atomic하게 삽입할 수 있다. 이 설계는 STORE만 수행되는 경우에는 CAS가 병목 지점이 될 수 있지만, 새로운 캐시 항목을 bag에 배치하는 동안 lock 이 필요 없다는 장점이 있다. 다시 언급하지만 대부분의 명령인 GET 명령을 위주로 최적화하려는 취지이기 때문에, lock이나 CAS 없이 GET이 수행될 수 있도록 bag LRU를 구현하였다.새로운 설계에서도 이전 캐시 항목을 제거하고 새로운 bag LRU 구조를 유지하는 방법이 필요하다. 이를위해 백그라운드로 실행할 ‘cleaner thread’를 구현한다. 이 스레드는 만료되거나 유효하지 않은 캐시 항목을 제거하고 LRU에서 유지관리를 수행한다. 이전 버전에서 GET 명령을 수행했던 명령 (즉, 만료 시 제거)은 이 백그라운드 스레드로 이동하므로 GET 명령의 처리량이 증가하고 GET 명령 트랜잭션당 오버헤드가 감소한다. cleaner thread 역시 캐시의 적중 비율을 높이는 이점을 제공한다. 빠른 만료로 삽입된 캐시항목은 캐시가 오래될 때까지 공간을 차지하는 대신 만료되기 몇 분 이내로 정리된다.3.5.1LINKED-LIST 구조새로운 Linked-list 구조는 기존의 LRU의 prev 그리고 next 포인터를 재사용하므로 추가 메모리 할당이 필 16
  472. 472. 요하지 않다. next 포인터는 새로운 single linked-list에서의 다음 캐시 항목을 기존과 같이 가리키고 prevpointer는 그 캐시 항목의 bag membership인 마지막으로 조회된 시점의 “최신 bag”을 표시한다.Cleaner thread는 bag을 정리하는 동안 이 포인터를 사용한다.3.5.2 추가 데이터 구조 그림 10 bag 데이터 구조 그림 11 bag list 데이터 구조Bag LRU는 bag 식별을 위한 LRU 배열과 실제 bag 데이터 구조, 이 두 가지 데이터 구조가 필요하다. 17
  473. 473. 첫 번째 데이터 구조는 슬랩 id당 하나의 bag head 배열인 bag LRU list이다 (그림 10). 이 구조는 global변수에서 참조되며 bag LRU가 초기화될 때 초기화된다. 이 배열의 각 bag head는 다음 field들이 필요하다. • 최신 bag에 대한 포인터 (Newest bag) • 최신 대체 bag에 대한 포인터 (Newest Alternative bag) • Bag list에서 가장 오래된 bag (Oldest bag) • 통계를 위한 bag의 개수“최신 bag”은 현재 새로 할당된 캐시 항목으로 채워지는 bag이다. 가장 오래된 bag은 cleaner thread가bag 정리를 시작하고 제거 (eviction) 스레드가 캐시 항목을 제거하는 bag이다. Cleaner thread는 최신bag으로의 삽입으로 인한 lock 경합을 피하기 위해 “최신 대체 bag”을 사용한다. bag에 이미 존재하고 최근에 액세스된 캐시 항목들은, 제거 순서 (eviction order)를 유지하기 위해, cleaner thread에 의해 최신bag으로 이동되어야 할 캐시 항목들은 “최신 대체 bag”에 들어가게 된다.두 번째로 필요한 데이터 구조는 캐시 항목의 single linked-list인 bag이다 (그림 11). bag은 다음 field 들을 포함한다. • Bag에서 “가장 최신” 및 “가장 오래된” 캐시 항목을 가르키는 포인터 • Bag의 캐시 항목 개수 카운터 ‘count’ • lock • Bag 열고 닫은 시간 (타임스탬프) - 현재 사용되지는 않음Bag을 정리할 때 cleaner thread는 bag에 있는 가장 오래된 캐시 항목부터 정리하기 시작한다. Workerthread의 eviction (제거) 작업도 같은 위치에서 시작된다. “가장 최신” 캐시 항목 포인터는 캐시 항목이 삽입될 때나 bag이 병합될 때 빠르게 bag의 끝에 추가할 수 있도록 해준다. ‘count’는 새 bag을 열거나 이전 bag을 최신 bag에 병합하는 시기를 결정하는데 쓰인다. ‘count’ 필요한 경우 통계 및 디버깅에 사용될수 있다. 모든 bag에 lock이 필요한 이유는 cleaner thread와 worker thread의 ‘eviction’ 간의 스레드 안정성을 위해서이다. Eviction worker thread와 cleaner thread가 같은 bag에 있는 캐시 항목을 이동하는경우 스레드 중 하나가 bag을 손상시킬 수 있다 (그림 6에 linked-list 손상이 표시됨). Cleaner thread는각 bag에서 작업 시간이 매우 적으므로 cleaner thread와 worker thread 사이에 lock 경합이 발생할 가능성은 낮다. 또한 worker thread는 상위 호출 스택에서 serialized 되므로 이러한 스레드간의 lock 경합은 없다.3.5.3 데이터 구조에 대한 명령이 절에서는 새로운 데이터 구조 및 알고리즘에서 초기화, 캐시 항목 삽입 및 LRU list 정리와 같은 명령을설명한다.먼저, 추가된 모든 lock을 초기화해야 하며 사용할 각 슬랩 크기에 맞는 초기 bag을 생성한다. 모든 구조가 18
  474. 474. 초기화된 후에는 cleaner thread가 생성되고 cleaning (정리) 루프에서 시작된다.Bag LRU 초기화 1. global
  475. 475.  eviction
  476. 476.  lock
  477. 477.  초기화 2. cleaner
  478. 478.  lock
  479. 479.  초기화
  480. 480.  (현재
  481. 481.  하나의
  482. 482.  lock만
  483. 483.  슬랩
  484. 484.  id당
  485. 485.  하나의
  486. 486.  cleaner
  487. 487.  thread가
  488. 488.  될
  489. 489.  수
  490. 490.  있음) 3. Bag
  491. 491.  head
  492. 492.  구조
  493. 493.  배열
  494. 494.  생성 4. 각
  495. 495.  슬랩
  496. 496.  id에
  497. 497.  대해 5. |
  498. 498.  
  499. 499.  
  500. 500.  
  501. 501.  
  502. 502.  
  503. 503.  첫
  504. 504.  번째
  505. 505.  bag
  506. 506.  초기화 6. |
  507. 507.  
  508. 508.  
  509. 509.  
  510. 510.  
  511. 511.  
  512. 512.  bag
  513. 513.  lock
  514. 514.  초기화 7. |
  515. 515.  
  516. 516.  
  517. 517.  
  518. 518.  
  519. 519.  
  520. 520.  bag
  521. 521.  배열
  522. 522.  head가
  523. 523.  최신
  524. 524.  bag으로
  525. 525.  이
  526. 526.  id를
  527. 527.  가리킴 8. |
  528. 528.  
  529. 529.  
  530. 530.  
  531. 531.  
  532. 532.  
  533. 533.  bag
  534. 534.  카운터
  535. 535.  증가 9. cleaner
  536. 536.  thread
  537. 537.  생성
  538. 538.  및
  539. 539.  실행정리 루프에서는 스레드가 bag이 가득 차 있는지 계속해서 확인하며 필요한 경우 새 bag을 해당 슬랩에추가한다. 주기적으로 cleaner thread는 bag에 있는 모든 캐시 항목을 확인하여 유효하지 않거나 만료된캐시 항목을 제거하고 최소 캐시 항목 개수 임계값 미만인 bag을 병합한다.Cleaner thread 루프 1. 무한
  540. 540.  루프 2. |
  541. 541.  
  542. 542.  
  543. 543.  
  544. 544.  
  545. 545.  cleaner
  546. 546.  lock
  547. 547.  획득 3. |
  548. 548.  
  549. 549.  
  550. 550.  
  551. 551.  
  552. 552.  각
  553. 553.  슬랩
  554. 554.  id에
  555. 555.  대해 4. |
  556. 556.  
  557. 557.  
  558. 558.  
  559. 559.  
  560. 560.  |
  561. 561.  
  562. 562.  
  563. 563.  
  564. 564.  
  565. 565.  “최신
  566. 566.  bag”이
  567. 567.  가득찬
  568. 568.  경우 5. |
  569. 569.  
  570. 570.  
  571. 571.  
  572. 572.  
  573. 573.  |
  574. 574.  
  575. 575.  
  576. 576.  
  577. 577.  
  578. 578.  |
  579. 579.  
  580. 580.  
  581. 581.  
  582. 582.  
  583. 583.  새로운
  584. 584.  bag
  585. 585.  생성
  586. 586.  및
  587. 587.  초기화 6. |
  588. 588.  
  589. 589.  
  590. 590.  
  591. 591.  
  592. 592.  |
  593. 593.  
  594. 594.  
  595. 595.  
  596. 596.  
  597. 597.  |
  598. 598.  
  599. 599.  
  600. 600.  
  601. 601.  
  602. 602.  최신
  603. 603.  bag
  604. 604.  다음
  605. 605.  bag
  606. 606.  포인터가
  607. 607.  새로
  608. 608.  초기화된
  609. 609.  bag을
  610. 610.  가리킴 7. |
  611. 611.  
  612. 612.  
  613. 613.  
  614. 614.  
  615. 615.  |
  616. 616.  
  617. 617.  
  618. 618.  
  619. 619.  
  620. 620.  |
  621. 621.  
  622. 622.  
  623. 623.  
  624. 624.  
  625. 625.  bag
  626. 626.  holder
  627. 627.  =
  628. 628.  최신
  629. 629.  bag 8. |
  630. 630.  
  631. 631.  
  632. 632.  
  633. 633.  
  634. 634.  |
  635. 635.  
  636. 636.  
  637. 637.  
  638. 638.  
  639. 639.  |
  640. 640.  
  641. 641.  
  642. 642.  
  643. 643.  
  644. 644.  최신
  645. 645.  bag
  646. 646.  =
  647. 647.  새로
  648. 648.  초기화된
  649. 649.  bag 9. |
  650. 650.  
  651. 651.  
  652. 652.  
  653. 653.  
  654. 654.  |
  655. 655.  
  656. 656.  
  657. 657.  
  658. 658.  
  659. 659.  |
  660. 660.  
  661. 661.  
  662. 662.  
  663. 663.  
  664. 664.  최신
  665. 665.  대체
  666. 666.  (Newest
  667. 667.  alternate)
  668. 668.  =
  669. 669.  bag
  670. 670.  holder 10. |
  671. 671.  
  672. 672.  
  673. 673.  
  674. 674.  
  675. 675.  |
  676. 676.  
  677. 677.  
  678. 678.  
  679. 679.  
  680. 680.  |
  681. 681.  
  682. 682.  
  683. 683.  
  684. 684.  
  685. 685.  atomic
  686. 686.  increment
  687. 687.  of
  688. 688.  bag
  689. 689.  count 11. N번째
  690. 690.  iteration시
  691. 691.  bag
  692. 692.  정리 12. cleaner
  693. 693.  lock
  694. 694.  해제 13. 지정된
  695. 695.  간격
  696. 696.  동안
  697. 697.  sleep캐시 항목이 LRU에 삽입될때, 캐시 항목의 슬랩 크기에 맞는 최신 bag에 삽입된다. bag이 정해지면 캐시항목은 CAS를 사용하여 최신 bag에서 최신 캐시 항목 바로 뒤 (끝)에 삽입된다. Atomic 삽입이 실패하면스레드는 bag의 끝을 나타내는 NULL 포인터를 찾아 다시 CAS를 시도한다. 성공적으로 삽입될 때까지CAS를 시도한다.LRU에 없는 새로운 캐시 항목 삽입 1.
  698. 698.  새로운
  699. 699.  캐시
  700. 700.  항목
  701. 701.  next
  702. 702.  포인터
  703. 703.  =
  704. 704.  NULL 2.
  705. 705.  If
  706. 706.  (현재
  707. 707.  open
  708. 708.  bag이
  709. 709.  비어
  710. 710.  있음) 3.
  711. 711.  |
  712. 712.   If
  713. 713.  (
  714. 714.  CAS
  715. 715.  (현재
  716. 716.  bag
  717. 717.  최신
  718. 718.  캐시
  719. 719.  항목,
  720. 720.  NULL,
  721. 721.  새로운
  722. 722.  캐시
  723. 723.  항목)){ 4.
  724. 724.  |
  725. 725.   |
  726. 726.  
  727. 727.  
  728. 728.  
  729. 729.  
  730. 730.  
  731. 731.  새로운
  732. 732.  캐시
  733. 733.  항목
  734. 734.  prev
  735. 735.  포인터
  736. 736.  =
  737. 737.  현재
  738. 738.  bag 5.
  739. 739.  |
  740. 740.   |
  741. 741.  
  742. 742.  
  743. 743.  
  744. 744.  
  745. 745.  
  746. 746.  현재
  747. 747.  bag
  748. 748.  최신
  749. 749.  캐시
  750. 750.  항목
  751. 751.  -
  752. 752.  새로운
  753. 753.  캐시
  754. 754.  항목 6.
  755. 755.  |
  756. 756.   |
  757. 757.  
  758. 758.  
  759. 759.  
  760. 760.  
  761. 761.  
  762. 762.  bag
  763. 763.  캐시
  764. 764.  항목
  765. 765.  카운터
  766. 766.  atomic
  767. 767.  increment 19
  768. 768. 7.
  769. 769.  |
  770. 770.   |
  771. 771.  
  772. 772.  
  773. 773.  
  774. 774.  
  775. 775.  
  776. 776.  반환 8.
  777. 777.  swap
  778. 778.  캐시
  779. 779.  항목
  780. 780.  =
  781. 781.  현재
  782. 782.  bag

×