moleculerjs / moleculer-db

:battery: Database access service mixins for Moleculer
https://moleculer.services/
MIT License
152 stars 122 forks source link

Memory cacher clear keys depend on "." (dot) in cached value #253

Closed RickAtreides closed 3 years ago

RickAtreides commented 3 years ago

Versions

"moleculer": "^0.14.12",
"moleculer-db": "^0.8.12",

Cache type - Memory, db-type mongoose

This issue was found, when using moleculer-db, so, may be, that module could be affected.

Brief description

clearCache method in moleculer-db package calls

this.broker.cacher.clean(`${this.fullName}.*`);

In moleculer/src/cachers/memory.js - clean() method - loop of cached keys, and check each against passed pattern, in our case ${this.fullName}.*

If cache key contains "." (dot), this key will not be cleared from cache.

How to reproduce

const { ServiceBroker } = require("moleculer");

const broker = new ServiceBroker({
    cacher: {
        type: "Memory",
        options: {
            ttl: 30, // Set Time-to-live to 30sec. Disabled: 0 or null
            clone: true // Deep-clone the returned value
        }
    },
    logger: {
      type: 'Console',
      options: {
        level: 'debug',
      }
    },

});

// Create a service
broker.createService({
    name: "users",
    actions: {
        list1: {
            // Enable caching to this action
            cache: true, 
            handler(ctx) {}
        },
        list2: {
            // Enable caching to this action
            cache: true, 
            handler(ctx) {}
        }
    }
});

broker.start()
    .then(() => {
        return broker.call("users.list1", { data: "str1" });
    })
    .then(() => {
        return broker.call("users.list2", { data: "str.1" });
    })
    .then(() => {
        return broker.cacher.clean("users.*");
    })
    .then(() => {
        return broker.call("users.list1", { data: "str1" });
    })
    .then(() => {
        return broker.call("users.list2", { data: "str.1" });
    });

Current Behavior

>node index.js | grep CACHER
[2021-03-01T11:20:29.125Z] DEBUG host-28476/CACHER: GET users.list1:data|str1
[2021-03-01T11:20:29.127Z] DEBUG host-28476/CACHER: SET users.list1:data|str1
[2021-03-01T11:20:29.127Z] DEBUG host-28476/CACHER: GET users.list2:data|str.1
[2021-03-01T11:20:29.127Z] DEBUG host-28476/CACHER: SET users.list2:data|str.1
[2021-03-01T11:20:29.128Z] DEBUG host-28476/CACHER: CLEAN users.*
[2021-03-01T11:20:29.128Z] DEBUG host-28476/CACHER: REMOVE users.list1:data|str1
[2021-03-01T11:20:29.129Z] DEBUG host-28476/CACHER: GET users.list1:data|str1
[2021-03-01T11:20:29.129Z] DEBUG host-28476/CACHER: SET users.list1:data|str1
[2021-03-01T11:20:29.130Z] DEBUG host-28476/CACHER: GET users.list2:data|str.1
[2021-03-01T11:20:29.130Z] DEBUG host-28476/CACHER: FOUND users.list2:data|str.1
[2021-03-01T11:20:29.130Z] DEBUG host-28476/CACHER: SET users.list2:data|str.1

users.list2 - is not cleared from cache and FOUND by cacher.

Expected Behavior

Both users.list1 and users.list2 cached results are cleared

If broker.cacher.clean("users.**") used - then cache cleared, so it could be possible of moleculer-db inappropriate call.

icebob commented 3 years ago

You have to use this.broker.cacher.clean("users.**"); to surely clear all keys.

RickAtreides commented 3 years ago

So issue should be addressed to moleculer-db ?

moleculer-db/src/index.js#L466

Here code example, when issue described above , produce incorrect result.

user1_cache, user_2_cache - expected to be empty

const { ServiceBroker } = require("moleculer");
const DbService = require("moleculer-db");
const MongooseAdapter = require("moleculer-db-adapter-mongoose");
const mongoose = require('mongoose');

const broker = new ServiceBroker({
    cacher: {
        type: "Memory",
        options: {
            ttl: 30, // Set Time-to-live to 30sec. Disabled: 0 or null
            clone: true // Deep-clone the returned value
        }
    },
    logger: {
      type: 'Console',
      options: {
        level: 'debug',
      }
    },
});

// Create a service
broker.createService({
    name: "users",
    mixins: [DbService],
    adapter: new MongooseAdapter("mongodb://localhost/moleculer-demo"),
    model: mongoose.model("user", mongoose.Schema({
       user: { type: String },
    })),
    actions: {
        getUser: {
            // Enable caching to this action
            cache: {
              keys: ['data']
            },
            data: { type: 'object', optional: true },
            handler(ctx) {
              const params = this.sanitizeParams(ctx, ctx.params);

              return this._list(ctx, params);
            } 
        }
    }
});

broker.start()
    .then(async () => {
        await broker.call("users.create", { user: "user1" });
        await broker.call("users.create", { user: "user.2" });
        const user1 = (await broker.call("users.list", { query: { user: "user1" } })).rows[0];
        const user_2 = (await broker.call("users.list", { query: { user: "user.2" } })).rows[0];

        console.log(user1, user_2);

//        await broker.cacher.clean("users.*");
        await broker.call("users.remove", { id: user1._id });
        await broker.call("users.remove", { id: user_2._id });

        const user1_cache = await broker.call("users.list", { query: { user: "user1" } });
        const user_2_cache = await broker.call("users.list", { query: { user: "user.2" } });

        console.log(user1_cache, user_2_cache);
    });
icebob commented 3 years ago

Yeah, I move it.