Skip to content

deleteByTag in combination with grace period doesn't invalidate cached valueΒ #71

@yss14

Description

@yss14

I've encountered an issue where calling deleteByTag does not properly invalidate cached values when a grace period is configured. After invalidation, the cache still returns the stale ("old") value for getOrSet.

Initially, I suspected this behavior was limited to distributed setups using a bus, but it also occurs with a single cache instance. This suggests the issue is not related to multi-node synchronization but possibly to how deleteByTag interacts with the grace logic.

For reproduction, I quickly wrote two test cases in tagging.spec.ts on my local machine:

test('remove by tags with grace period', async ({ assert }) => {
    const [cache] = new CacheFactory().withL1L2Config().create()

    const v1 = await cache.getOrSet({
      key: 'baz',
      factory: () => 1,
      tags: ['x', 'z'],
      ttl: '2min',
      grace: '10min',
    })

    assert.deepEqual(v1, 1)

    await cache.deleteByTag({ tags: ['x'] })

    const v2get = await cache.get({ key: 'baz' })
    const v2getorset = await cache.getOrSet({
      key: 'baz',
      factory: () => 2,
      tags: ['x', 'z'],
      ttl: '2min',
      grace: '10min',
    })

    assert.isUndefined(v2get)
    assert.deepEqual(v2getorset, 2)
  })

  test('remove by tags with bus and grace period', async ({ assert }) => {
    const [readCache] = new CacheFactory().withL1L2Config().create()
    const [invalidationCache] = new CacheFactory().withL1L2Config().create()

    const v1 = await readCache.getOrSet({
      key: 'baz',
      factory: () => 1,
      tags: ['x', 'z'],
      ttl: '2min',
      grace: '10min',
    })

    assert.deepEqual(v1, 1)

    await invalidationCache.deleteByTag({ tags: ['x'] })
    //await invalidationCache.delete({ key: 'baz' })  <- this works btw.

    const v2get = await readCache.get({ key: 'baz' })
    const v2getorset = await readCache.getOrSet({
      key: 'baz',
      factory: () => 2,
      tags: ['x', 'z'],
      ttl: '2min',
      grace: '10min',
    })

    assert.isUndefined(v2get)
    assert.deepEqual(v2getorset, 2)
  })

Result:

> cross-env NODE_NO_WARNINGS=1 node --enable-source-maps --loader=ts-node/esm bin/test.ts "--suite=unit" "--files=tests/tagging.spec.ts"


unit / Tagging | Internals (tests/tagging.spec.ts)
  βœ” deleteByTag should store invalidated tags with timestamps (7.08ms)
  βœ” set should store value with tags (2.1ms)

unit / Tagging | deleteByTag (tests/tagging.spec.ts)
  βœ” basic (2.94ms)
  βœ” can remove by tag (6.51ms)
  βœ” can remove multiple tags (3.6ms)
  βœ” remove by tags with bus (6.01ms)
  βœ– remove by tags with grace period (2.74ms)
  βœ– remove by tags with bus and grace period (2.99ms)
  βœ” remove multiple tags with bus (8.59ms)
  βœ” tags and namespaces (3.56ms)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── ERRORS ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

❯ Tagging | deleteByTag / remove by tags with grace period

- Expected  - 1
+ Received  + 1

- 2
+ 1

β„Ή AssertionError: expected 1 to deeply equal 2

 ⁃ at Assert.deepEqual (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@japa/assert/build/index.js:282:19)
 ⁃ at Object.executor (tests/tagging.spec.ts:192:12)

   187 ┃        ttl: '2min',
   188 ┃        grace: '10min',
   189 ┃      })
   190 ┃  
   191 ┃      assert.isUndefined(v2get)
 ❯ 192 ┃      assert.deepEqual(v2getorset, 2)
   193 ┃    })
   194 ┃  
   195 ┃    test('remove by tags with bus and grace period', async ({ assert }) => {
   196 ┃      const [readCache] = new CacheFactory().withL1L2Config().create()
   197 ┃      const [invalidationCache] = new CacheFactory().withL1L2Config().create()

 ⁃ at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
 ⁃ at async #wrapTestInTimeout (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]/node_modules/@japa/core/build/index.js:1054:7)
 ⁃ at async TestRunner.run (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]/node_modules/@japa/core/build/index.js:1102:7)
 ⁃ at async Test.exec (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]/node_modules/@japa/core/build/index.js:1482:5)
 ⁃ at async GroupRunner.run (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]/node_modules/@japa/core/build/index.js:345:7)
 ⁃ at async Group.exec (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]/node_modules/@japa/core/build/index.js:513:5)
 ⁃ at async SuiteRunner.run (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]/node_modules/@japa/core/build/index.js:1809:7)
 ⁃ at async Suite.exec (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]/node_modules/@japa/core/build/index.js:1936:5)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────[1/2]─

❯ Tagging | deleteByTag / remove by tags with bus and grace period

- Expected  - 1
+ Received  + 1

- 2
+ 1

β„Ή AssertionError: expected 1 to deeply equal 2

 ⁃ at Assert.deepEqual (/Users/yss/Dev/open-source/bentocache/node_modules/.pnpm/@[email protected]_@[email protected]/node_modules/@japa/assert/build/index.js:282:19)
 ⁃ at Object.executor (tests/tagging.spec.ts:222:12)

   217 ┃        ttl: '2min',
   218 ┃        grace: '10min',
   219 ┃      })
   220 ┃  
   221 ┃      assert.isUndefined(v2get)
 ❯ 222 ┃      assert.deepEqual(v2getorset, 2)
   223 ┃    })

At this point, I'm not sure whether this is intended behavior or a bug. However, I would lean toward it being a bugβ€”especially since the interaction between deleteByTag and the grace period doesn’t appear to be covered by the existing test suite.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions