/src/dbnode/storage/index/postings_list_cache_test.go

https://github.com/m3db/m3 · Go · 372 lines · 295 code · 43 blank · 34 comment · 40 complexity · 6777e99acddb422bedf73ec5945ebc4c MD5 · raw file

  1. // Copyright (c) 2019 Uber Technologies, Inc.
  2. //
  3. // Permission is hereby granted, free of charge, to any person obtaining a copy
  4. // of this software and associated documentation files (the "Software"), to deal
  5. // in the Software without restriction, including without limitation the rights
  6. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  7. // copies of the Software, and to permit persons to whom the Software is
  8. // furnished to do so, subject to the following conditions:
  9. //
  10. // The above copyright notice and this permission notice shall be included in
  11. // all copies or substantial portions of the Software.
  12. //
  13. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. // THE SOFTWARE.
  20. package index
  21. import (
  22. "fmt"
  23. "sort"
  24. "strconv"
  25. "sync"
  26. "testing"
  27. "github.com/m3db/m3/src/m3ninx/postings"
  28. "github.com/m3db/m3/src/m3ninx/postings/roaring"
  29. "github.com/m3db/m3/src/x/instrument"
  30. "github.com/pborman/uuid"
  31. "github.com/stretchr/testify/require"
  32. )
  33. const (
  34. numTestPlEntries = 1000
  35. )
  36. var (
  37. // Filled in by init().
  38. testPlEntries []testEntry
  39. testPostingListCacheOptions = PostingsListCacheOptions{
  40. InstrumentOptions: instrument.NewOptions(),
  41. }
  42. )
  43. func init() {
  44. // Generate test data.
  45. for i := 0; i < numTestPlEntries; i++ {
  46. var (
  47. segmentUUID = uuid.Parse(
  48. fmt.Sprintf("00000000-0000-0000-0000-000000000%03d", i))
  49. field = fmt.Sprintf("field_%d", i)
  50. pattern = fmt.Sprintf("pattern_%d", i)
  51. pl = roaring.NewPostingsList()
  52. )
  53. pl.Insert(postings.ID(i))
  54. patternType := PatternTypeRegexp
  55. switch i % 3 {
  56. case 0:
  57. patternType = PatternTypeTerm
  58. case 1:
  59. patternType = PatternTypeField
  60. pattern = "" // field queries don't have patterns
  61. }
  62. testPlEntries = append(testPlEntries, testEntry{
  63. segmentUUID: segmentUUID,
  64. key: newKey(field, pattern, patternType),
  65. postingsList: pl,
  66. })
  67. }
  68. }
  69. type testEntry struct {
  70. segmentUUID uuid.UUID
  71. key key
  72. postingsList postings.List
  73. }
  74. func TestSimpleLRUBehavior(t *testing.T) {
  75. size := 3
  76. plCache, stopReporting, err := NewPostingsListCache(size, testPostingListCacheOptions)
  77. require.NoError(t, err)
  78. defer stopReporting()
  79. var (
  80. e0 = testPlEntries[0]
  81. e1 = testPlEntries[1]
  82. e2 = testPlEntries[2]
  83. e3 = testPlEntries[3]
  84. e4 = testPlEntries[4]
  85. e5 = testPlEntries[5]
  86. )
  87. putEntry(t, plCache, 0)
  88. putEntry(t, plCache, 1)
  89. putEntry(t, plCache, 2)
  90. requireExpectedOrder(t, plCache, []testEntry{e0, e1, e2})
  91. putEntry(t, plCache, 3)
  92. requireExpectedOrder(t, plCache, []testEntry{e1, e2, e3})
  93. putEntry(t, plCache, 4)
  94. putEntry(t, plCache, 4)
  95. putEntry(t, plCache, 5)
  96. putEntry(t, plCache, 5)
  97. putEntry(t, plCache, 0)
  98. putEntry(t, plCache, 0)
  99. requireExpectedOrder(t, plCache, []testEntry{e4, e5, e0})
  100. // Miss, no expected change.
  101. getEntry(t, plCache, 100)
  102. requireExpectedOrder(t, plCache, []testEntry{e4, e5, e0})
  103. // Hit.
  104. getEntry(t, plCache, 4)
  105. requireExpectedOrder(t, plCache, []testEntry{e5, e0, e4})
  106. // Multiple hits.
  107. getEntry(t, plCache, 4)
  108. getEntry(t, plCache, 0)
  109. getEntry(t, plCache, 5)
  110. getEntry(t, plCache, 5)
  111. requireExpectedOrder(t, plCache, []testEntry{e4, e0, e5})
  112. }
  113. func TestPurgeSegment(t *testing.T) {
  114. size := len(testPlEntries)
  115. plCache, stopReporting, err := NewPostingsListCache(size, testPostingListCacheOptions)
  116. require.NoError(t, err)
  117. defer stopReporting()
  118. // Write many entries with the same segment UUID.
  119. for i := 0; i < 100; i++ {
  120. if testPlEntries[i].key.patternType == PatternTypeRegexp {
  121. plCache.PutRegexp(
  122. testPlEntries[0].segmentUUID,
  123. testPlEntries[i].key.field,
  124. testPlEntries[i].key.pattern,
  125. testPlEntries[i].postingsList,
  126. )
  127. } else {
  128. plCache.PutTerm(
  129. testPlEntries[0].segmentUUID,
  130. testPlEntries[i].key.field,
  131. testPlEntries[i].key.pattern,
  132. testPlEntries[i].postingsList,
  133. )
  134. }
  135. }
  136. // Write the remaining entries.
  137. for i := 100; i < len(testPlEntries); i++ {
  138. putEntry(t, plCache, i)
  139. }
  140. // Purge all entries related to the segment.
  141. plCache.PurgeSegment(testPlEntries[0].segmentUUID)
  142. // All entries related to the purged segment should be gone.
  143. require.Equal(t, size-100, plCache.lru.Len())
  144. for i := 0; i < 100; i++ {
  145. if testPlEntries[i].key.patternType == PatternTypeRegexp {
  146. _, ok := plCache.GetRegexp(
  147. testPlEntries[0].segmentUUID,
  148. testPlEntries[i].key.field,
  149. testPlEntries[i].key.pattern,
  150. )
  151. require.False(t, ok)
  152. } else {
  153. _, ok := plCache.GetTerm(
  154. testPlEntries[0].segmentUUID,
  155. testPlEntries[i].key.field,
  156. testPlEntries[i].key.pattern,
  157. )
  158. require.False(t, ok)
  159. }
  160. }
  161. // Remaining entries should still be present.
  162. for i := 100; i < len(testPlEntries); i++ {
  163. getEntry(t, plCache, i)
  164. }
  165. }
  166. func TestEverthingInsertedCanBeRetrieved(t *testing.T) {
  167. plCache, stopReporting, err := NewPostingsListCache(len(testPlEntries), testPostingListCacheOptions)
  168. require.NoError(t, err)
  169. defer stopReporting()
  170. for i := range testPlEntries {
  171. putEntry(t, plCache, i)
  172. }
  173. for i, entry := range testPlEntries {
  174. cached, ok := getEntry(t, plCache, i)
  175. require.True(t, ok)
  176. require.True(t, cached.Equal(entry.postingsList))
  177. }
  178. }
  179. func TestConcurrencyWithEviction(t *testing.T) {
  180. testConcurrency(t, len(testPlEntries)/10, true, false)
  181. }
  182. func TestConcurrencyVerifyResultsNoEviction(t *testing.T) {
  183. testConcurrency(t, len(testPlEntries), false, true)
  184. }
  185. func testConcurrency(t *testing.T, size int, purge bool, verify bool) {
  186. plCache, stopReporting, err := NewPostingsListCache(size, testPostingListCacheOptions)
  187. require.NoError(t, err)
  188. defer stopReporting()
  189. wg := sync.WaitGroup{}
  190. // Spin up writers.
  191. for i := range testPlEntries {
  192. wg.Add(1)
  193. go func(i int) {
  194. for j := 0; j < 100; j++ {
  195. putEntry(t, plCache, i)
  196. }
  197. wg.Done()
  198. }(i)
  199. }
  200. // Spin up readers.
  201. for i := range testPlEntries {
  202. wg.Add(1)
  203. go func(i int) {
  204. for j := 0; j < 100; j++ {
  205. getEntry(t, plCache, j)
  206. }
  207. wg.Done()
  208. }(i)
  209. }
  210. stopPurge := make(chan struct{})
  211. if purge {
  212. go func() {
  213. for {
  214. select {
  215. case <-stopPurge:
  216. default:
  217. for _, entry := range testPlEntries {
  218. plCache.PurgeSegment(entry.segmentUUID)
  219. }
  220. }
  221. }
  222. }()
  223. }
  224. wg.Wait()
  225. close(stopPurge)
  226. if !verify {
  227. return
  228. }
  229. for i, entry := range testPlEntries {
  230. cached, ok := getEntry(t, plCache, i)
  231. if !ok {
  232. // Debug.
  233. printSortedKeys(t, plCache)
  234. }
  235. require.True(t, ok)
  236. require.True(t, cached.Equal(entry.postingsList))
  237. }
  238. }
  239. func putEntry(t *testing.T, cache *PostingsListCache, i int) {
  240. // Do each put twice to test the logic that avoids storing
  241. // multiple entries for the same value.
  242. switch testPlEntries[i].key.patternType {
  243. case PatternTypeRegexp:
  244. cache.PutRegexp(
  245. testPlEntries[i].segmentUUID,
  246. testPlEntries[i].key.field,
  247. testPlEntries[i].key.pattern,
  248. testPlEntries[i].postingsList,
  249. )
  250. cache.PutRegexp(
  251. testPlEntries[i].segmentUUID,
  252. testPlEntries[i].key.field,
  253. testPlEntries[i].key.pattern,
  254. testPlEntries[i].postingsList,
  255. )
  256. case PatternTypeTerm:
  257. cache.PutTerm(
  258. testPlEntries[i].segmentUUID,
  259. testPlEntries[i].key.field,
  260. testPlEntries[i].key.pattern,
  261. testPlEntries[i].postingsList,
  262. )
  263. cache.PutTerm(
  264. testPlEntries[i].segmentUUID,
  265. testPlEntries[i].key.field,
  266. testPlEntries[i].key.pattern,
  267. testPlEntries[i].postingsList,
  268. )
  269. case PatternTypeField:
  270. cache.PutField(
  271. testPlEntries[i].segmentUUID,
  272. testPlEntries[i].key.field,
  273. testPlEntries[i].postingsList,
  274. )
  275. cache.PutField(
  276. testPlEntries[i].segmentUUID,
  277. testPlEntries[i].key.field,
  278. testPlEntries[i].postingsList,
  279. )
  280. default:
  281. require.FailNow(t, "unknown pattern type", testPlEntries[i].key.patternType)
  282. }
  283. }
  284. func getEntry(t *testing.T, cache *PostingsListCache, i int) (postings.List, bool) {
  285. switch testPlEntries[i].key.patternType {
  286. case PatternTypeRegexp:
  287. return cache.GetRegexp(
  288. testPlEntries[i].segmentUUID,
  289. testPlEntries[i].key.field,
  290. testPlEntries[i].key.pattern,
  291. )
  292. case PatternTypeTerm:
  293. return cache.GetTerm(
  294. testPlEntries[i].segmentUUID,
  295. testPlEntries[i].key.field,
  296. testPlEntries[i].key.pattern,
  297. )
  298. case PatternTypeField:
  299. return cache.GetField(
  300. testPlEntries[i].segmentUUID,
  301. testPlEntries[i].key.field,
  302. )
  303. default:
  304. require.FailNow(t, "unknown pattern type", testPlEntries[i].key.patternType)
  305. }
  306. return nil, false
  307. }
  308. func requireExpectedOrder(t *testing.T, plCache *PostingsListCache, expectedOrder []testEntry) {
  309. for i, key := range plCache.lru.keys() {
  310. require.Equal(t, expectedOrder[i].key, key)
  311. }
  312. }
  313. func printSortedKeys(t *testing.T, cache *PostingsListCache) {
  314. keys := cache.lru.keys()
  315. sort.Slice(keys, func(i, j int) bool {
  316. iIdx, err := strconv.ParseInt(keys[i].field, 10, 64)
  317. if err != nil {
  318. t.Fatalf("unable to parse: %s into int", keys[i].field)
  319. }
  320. jIdx, err := strconv.ParseInt(keys[j].field, 10, 64)
  321. if err != nil {
  322. t.Fatalf("unable to parse: %s into int", keys[i].field)
  323. }
  324. return iIdx < jIdx
  325. })
  326. for _, key := range keys {
  327. fmt.Println("key: ", key)
  328. }
  329. }