Preventing race condition when user executes multiple graphql mutations in a row

Viewed 784

I have async GraphQL mutation, which does the following:

  1. finds photo by id, and removes it from db (mongoDB, using mongoose).
  2. finds user by id, removes photo from users list of photos, and then saves the user.

So I have one db for photos, and one for users. All photos are separate documents in photos-db, and photo-id's are also saved to user-db.

Problem is, that if I run this many times quickly, it does remove photos from photo-db, but failed to remove photo-id's from user-db. It removes first photo-id, but subsequent photo-id's are not removed.

Code below:

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;
}

Output of trying to remove 5 photos:

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 schema below:

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
}

So user is updated one time, but 4 following updates failed. How can I prevent this happening?

1 Answers

Solution for me was this. I'm updating UserModel with function findByIdAndUpdate using $pullAll operator, as it guarantees that object is not modified between fetching it and saving it.

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;
}
Related