es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

Cómo prevenir que la base de datos contenga datos antiguos no actualizados previos.

Estoy tratando de trabajar con postgresQL con graphql en un servidor express.
El problema al que me enfrento es que quiero actualizar el valor de la base de datos cuando el usuario llame a la API en el cliente. Cuando se llama dos veces en poco tiempo, dado que el dinero del usuario y la cantidad del producto aún no se han actualizado, ambas funciones obtendrán el mismo dinero, se ejecutarán y la cantidad del producto y el dinero del usuario serán negativos, lo cual no es correcto.
¿Cuál es una forma sencilla de implementar la función de bloqueo para que los datos del usuario específico y los datos del producto no puedan ser accedidos y modificados hasta que el proceso anterior esté terminado, de modo que sea seguro en el hilo de ejecución?

Editar: Sé que puedo agregar un bloqueo en la caché local, pero me gustaría preguntar cuál es el método de manejo típico. ¿Debería simplemente devolver que el proceso está en ejecución cuando está bloqueado? ¿O debería crear un bucle para esperar hasta que se haya completado la primera solicitud? Y si hay algún buen ejemplo de código que pueda seguir.

@Mutation(() => Boolean)
async pay(@Arg(‘amount’) amount: number) {
let userId = 1
let proeductId = 1
const user = await User.findOne(userId); // user = {userId: 1, money: 50}
const product = await Product.findOne(userId); // product= {userId:1, quantity: 1, price: 50}
await new Promise(resolve => setTimeout(resolve, 3000)) // algo de lógica
if(user && product && user?.money >= product.price && product.quantity > 0){
await getConnection().getRepository(User).decrement({ userId }, ‘money’, 50);
await getConnection().getRepository(product).decrement({ userId }, ‘quantity’, 1);
}
}

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Para garantizar la seguridad de hilos y el bloqueo para la fila de datos del usuario específico y la fila de datos del producto en una base de datos PostgreSQL con GraphQL en un servidor Express, puede usar las funciones de bloqueo de asesoramiento de PostgreSQL.

    Cuando un usuario llama a la API de pago, puede obtener un bloqueo en las filas de datos del usuario y del producto utilizando un bloqueo de asesoramiento con las ID del usuario y del producto como argumentos. El bloqueo se puede liberar una vez que se completa la ejecución de la API.

    Una implementación simple de la función de bloqueo se puede hacer de la siguiente manera:

    const LOCK_DELAY_MS = 1000; // retardo entre intentos de bloqueo
    const USER_LOCK_ID = 123; // ID único para el bloqueo de asesoramiento del usuario
    const PROD_LOCK_ID = 456; // ID único para el bloqueo de asesoramiento del producto
    
    async function pay(amount) {
        let userId = 1;
        let productId = 1;
        const userLock = await acquireAdvisoryLock(USER_LOCK_ID); // obtener bloqueo de asesoramiento para el usuario
        const prodLock = await acquireAdvisoryLock(PROD_LOCK_ID); // obtener bloqueo de asesoramiento para el producto
    
        const user = await User.findOne(userId);
        const product = await Product.findOne(userId);
        await new Promise(resolve => setTimeout(resolve, 3000)); // lógica adicional
    
        if (user && product && user?.money >= product.price && product.quantity > 0) {
            await getConnection().getRepository(User).decrement({ userId }, 'money', 50);
            await getConnection().getRepository(product).decrement({ userId }, 'quantity', 1);
        }
    
        await releaseAdvisoryLock(userLock); // liberar bloqueo de asesoramiento del usuario
        await releaseAdvisoryLock(prodLock); // liberar bloqueo de asesoramiento del producto
    }
    
    async function acquireAdvisoryLock(lockId) {
        while (true) {
            const res = await getConnection().query(`SELECT pg_try_advisory_lock(${lockId})`);
            if (res[0].pg_try_advisory_lock) {
                return lockId;
            }
            await new Promise(resolve => setTimeout(resolve, LOCK_DELAY_MS));
        }
    }
    
    async function releaseAdvisoryLock(lockId) {
        await getConnection().query(`SELECT pg_advisory_unlock(${lockId})`);
    }
    

    En este ejemplo, la función acquireAdvisoryLock intenta obtener el bloqueo de asesoramiento para el lockId dado. Esto se hace con un bucle que espera entre los intentos de adquirir el bloqueo. Una vez que se obtiene el bloqueo, se devuelve.

    La función releaseAdvisoryLock simplemente libera el bloqueo de asesoramiento para el lockId dado.

    En la función pay, los bloqueos de asesoramiento se obtienen antes de acceder a las filas de datos del usuario y del producto. Si la ejecución de la API se llama dos veces en un corto período de tiempo, la segunda llamada tendrá que esperar a que se liberen los primeros bloqueos de asesoramiento antes de continuar.

    Tenga en cuenta que los bloqueos de asesoramiento son bloqueos a nivel de sesión, lo que significa que solo afectan a la sesión de conexión actual de la base de datos. Otras conexiones de base de datos aún pueden acceder a las filas de datos bloqueadas. Sin embargo, en la mayoría de los casos, los bloqueos a nivel de sesión son suficientes para garantizar la seguridad de hilos y prevenir condiciones de carrera.

Comments are closed.