当用户连续执行多个graphql突变时防止竞争条件

Posted

技术标签:

【中文标题】当用户连续执行多个graphql突变时防止竞争条件【英文标题】:Preventing race condition when user executes multiple graphql mutations in a row 【发布时间】:2020-12-08 14:04:10 【问题描述】:

我有异步 GraphQL 突变,它执行以下操作:

    按 id 查找照片,并将其从 db 中删除(mongoDB,使用 mongoose)。 按 id 查找用户,从用户照片列表中删除照片,然后保存用户。

所以我有一个用于照片的数据库,一个用于用户。所有照片都是photos-db中的单独文档,photo-id也保存到user-db中。

问题是,如果我快速运行多次,它确实会从 photo-db 中删除照片,但无法从 user-db 中删除 photo-id。它会删除第一个 photo-id,但不会删除后续的 photo-id。

代码如下:

deletePhoto: async (_root: undefined, args:  id: string , context: UserInContext): Promise<Photo | null> => 
    const currentUser = context.currentUser;
    const id = args.id;
    const isOwnPhoto = currentUser.photos.includes(id);

    console.log('user', currentUser.id);

    if (!currentUser || (!currentUser.isAdmin && !isOwnPhoto)) 
        throw new AuthenticationError('Not authenticated');
    

    const photo = await PhotoModel.findByIdAndDelete(args.id);
    const user = await UserModel.findById(currentUser.id);

    if (user) 
        console.log('photo removed, id:', id);
        user.photos = user.photos.filter(item => item != id);

        try 
            await user.save();
            console.log('user saved');
         catch (error) 
            console.log('error', error.message);
        
    

    return photo;

尝试删除 5 张照片的输出:

user 5f058ca0bf08318028019059
user 5f058ca0bf08318028019059
user 5f058ca0bf08318028019059
user 5f058ca0bf08318028019059
user 5f058ca0bf08318028019059
photo removed, id: 5f3cf6fdbe09d26e70efcd95
photo removed, id: 5f3cf700be09d26e70efcd96
photo removed, id: 5f3cf705be09d26e70efcd97
photo removed, id: 5f3cf709be09d26e70efcd98
photo removed, id: 5f3cf70cbe09d26e70efcd99
user saved
error No matching document found for id "5f058ca0bf08318028019059" version 116 modifiedPaths "photos"
error No matching document found for id "5f058ca0bf08318028019059" version 116 modifiedPaths "photos"
error No matching document found for id "5f058ca0bf08318028019059" version 116 modifiedPaths "photos"
error No matching document found for id "5f058ca0bf08318028019059" version 116 modifiedPaths "photos"

GraphQL 架构如下:

type User 
    username: String!
    password: String!
    email: String!
    fullname: String!
    isAdmin: Boolean!
    photos: [Photo!]
    id: ID!

  
type Photo 
    mainUrl: String!
    thumbUrl: String!
    filename: String!
    thumbFilename: String!
    originalFilename: String!
    name: String!
    description: String
    dateAdded: String
    user: User!
    id: ID!

  
type Mutation 
    deletePhoto(id: ID!): Photo

所以用户更新了一次,但随后的 4 次更新失败。我怎样才能防止这种情况发生?

【问题讨论】:

模式的查询和/或相关部分是什么样的? 遍历user.photos,如果文件不存在则删除,然后保存用户 问题不在于 GraphQL,您的解析器需要通过一些事务或锁定来管理此问题 不需要锁定,只是不保存包含已删除 ID 的 user.photos - mongo 尝试将它们与匹配(可能已删除)文档链接...过滤 user.photos 使用 PhotoModel.exists(),甚至不需要过滤掉当前id 好的,现在我看到了问题所在!但是你能帮助我如何使用exists()吗?我试过了:user.photos = user.photos.filter(item =&gt; PhotoModel.exists( id: item )); - 没有发生错误,但它不再过滤掉任何照片,user.photos 包含所有已删除的照片 ID。 【参考方案1】:

我的解决方案是这样的。我正在使用 $pullAll 运算符更新 UserModel 和函数 findByIdAndUpdate,因为它保证对象在获取和保存之间不会被修改。

deletePhoto: async (_root: undefined, args:  id: string , context: UserInContext): Promise<Photo | null> => 
    const currentUser = context.currentUser;
    const id = args.id;
    const isOwnPhoto = currentUser.photos.includes(id);

    if (!currentUser || (!currentUser.isAdmin && !isOwnPhoto)) 
        throw new AuthenticationError('Not authenticated');
    

    const photo = await PhotoModel.findByIdAndDelete(args.id);
    await UserModel.findByIdAndUpdate( _id: currentUser.id ,  $pullAll:  photos: [id]  );

    return photo;

【讨论】:

以上是关于当用户连续执行多个graphql突变时防止竞争条件的主要内容,如果未能解决你的问题,请参考以下文章

当多个进程尝试同时写入然后从文件中读取时,如何防止竞争条件

在将 aws cdk 与 appsync 一起使用时,如何将突变添加到 graphql 架构并防止部署失败?

GraphQL 在突变时返回 null [重复]

当发生写入数据库的并发 API 调用(或服务器速度较慢时)时,防止出现竞争条件

调用 GraphQL 突变的 If/Else 条件

GraphQL .NET 上部分更新突变的空字段