我在postgres上构建了一个基于节点的微服务,它使用sequelize执行查询。我有一张Pets表,每个表都有一个id
(uuid)和一个name
(字符串)。而且,我有一个用于按名称从数据库中获取Pets的函数,该函数包装了看起来很讨厌的sequelize调用:
async function getPetByName( petName ) {
const sqlzPetInstance = Database.Pet.findOne({
where: { name: { [Sequelize.Op.iLike]: petName } }
})
if(!sqlzPetInstance) return undefined
return sqlzPetInstance
}
效果很好。
稍后,为了提高性能,我向该函数添加了一些非常短暂的缓存,如下所示:
async function getPetByName( petName ) {
if( ramCache.get(petName) ) return ramCache.get(petName)
const sqlzPetInstance = await Database.Pet.findOne({ ... })
if(!sqlzPetInstance) return undefined
return ramCache.set(petName, sqlzPetInstance) // persists for 5 seconds
}
现在,我注意到从缓存中投放的项目有时会删除其id
道具! WTF?!
我添加了日志记录,发现ramCache
条目仍在可靠地定位,并且该值仍然是sqlz Pet模型的实例。模型上的所有其他属性仍然存在,但是dataValues.id
未定义。我还注意到_previousDataValues.id
的值正确,这对我来说确实是我想要的模型实例,但是由于某些原因而进行了修改。
这可以解释什么?如果获得该模型的调用者通过分配给id
来对其进行突变,这是我会看到的吗?是什么导致_previousDataValues
和dataValues
分开?我可以使用一些很酷的sqlz技术来捕获问题的根源(也许通过定义记录或抛出的自定义设置器)?
编辑:实验表明,我 无法通过分配id
来覆盖它。太酷了,但是现在我几乎没有主意了。如果这不是某种不负责任的突变(我可以防止这种突变),那么我就不会想到会导致删除ID的任何sqlz实例方法。
答案 0 :(得分:0)
我没有抽烟的枪,但是我可以描述我所写的修复方法以及形成该修复方法的假设。
正如我所说,我将序列化模型实例存储在RAM中:
ramCache[ cacheKey ] = sqlzModelInstance
我的假设是,通过为每个调用者提供相同的实例,我造成了一种顽皮的调用者可以使共享实例发生变异的情况。
我从不知道这种突变是如何发生的。通过实验证明,我无法通过覆盖来修改id
属性:
// this does not work
sqlzModelInstance.id = 'some-fake-id'
// unchanged
但是,我阅读了sqlz文档中的a few things,该文档建议每个实例都保留某种指向中央授权机构的不可见链接,因此有可能“远距离执行怪异的动作”。
因此,为了切断该链接,我修改了缓存系统以存储原始数据(而不是sqlz模型实例),并在检索时自动重新水合该原始数据。
严重:
function saveInCache( cacheKey, sqlzModelInstance ) {
cache[ cacheKey ] = sqlzModelInstance.get({ plain: true })
}
function getFromCache( cacheKey ) {
let data = cache[ cacheKey ]
if(!data) return undefined
return MySqlzClass.build( data, { isNewRecord: false, raw: true } )
}
我从来没有找到调皮的调用者-我的常规做法是避免更改参数,因此不太可能发生任何直接的更改-但我描述的更改已修复了我遇到的易于复制的错误。所以,我认为我的假设是正确的。
我将暂时避免将我的答案标记为正确,以希望有人能进一步阐明问题。