¿Cómo evitar la falta de transacciones en MongoDB?


139

Sé que hay preguntas similares aquí, pero me están diciendo que vuelva a los sistemas RDBMS normales si necesito transacciones o uso operaciones atómicas o confirmación en dos fases . La segunda solución parece la mejor opción. El tercero no deseo seguirlo porque parece que muchas cosas podrían salir mal y no puedo probarlo en todos los aspectos. Me está costando refactorizar mi proyecto para realizar operaciones atómicas. No sé si esto proviene de mi punto de vista limitado (hasta ahora solo he trabajado con bases de datos SQL), o si realmente no se puede hacer.

Nos gustaría probar piloto MongoDB en nuestra empresa. Hemos elegido un proyecto relativamente simple: una puerta de enlace SMS. Permite que nuestro software envíe mensajes SMS a la red celular y la puerta de enlace hace el trabajo sucio: en realidad se comunica con los proveedores a través de diferentes protocolos de comunicación. El portal también gestiona la facturación de los mensajes. Cada cliente que solicita el servicio tiene que comprar algunos créditos. El sistema disminuye automáticamente el saldo del usuario cuando se envía un mensaje y niega el acceso si el saldo es insuficiente. Además, debido a que somos clientes de proveedores de SMS de terceros, también podemos tener nuestros propios saldos con ellos. Tenemos que hacer un seguimiento de esos también.

Empecé a pensar en cómo puedo almacenar los datos requeridos con MongoDB si reduzco cierta complejidad (facturación externa, envío de SMS en cola). Viniendo del mundo SQL, crearía una tabla separada para usuarios, otra para mensajes SMS y otra para almacenar las transacciones relacionadas con el saldo de los usuarios. Digamos que creo colecciones separadas para todos aquellos en MongoDB.

Imagine una tarea de envío de SMS con los siguientes pasos en este sistema simplificado:

  1. verificar si el usuario tiene saldo suficiente; negar el acceso si no hay suficiente crédito

  2. envíe y almacene el mensaje en la colección de SMS con los detalles y el costo (en el sistema en vivo el mensaje tendría un statusatributo y una tarea lo recogería para la entrega y establecería el precio del SMS de acuerdo con su estado actual)

  3. disminuir el saldo de los usuarios por el costo del mensaje enviado

  4. registrar la transacción en la colección de transacciones

¿Cuál es el problema con eso? MongoDB puede hacer actualizaciones atómicas solo en un documento. En el flujo anterior, podría ocurrir que algún tipo de error se deslice y el mensaje se almacene en la base de datos, pero el saldo del usuario no se actualiza y / o la transacción no se registra.

Se me ocurrieron dos ideas:

  • Cree una colección única para los usuarios y almacene el saldo como un campo, transacciones relacionadas con el usuario y mensajes como subdocumentos en el documento del usuario. Debido a que podemos actualizar los documentos atómicamente, esto realmente resuelve el problema de la transacción. Desventajas: si el usuario envía muchos mensajes SMS, el tamaño del documento podría aumentar y alcanzar el límite de 4 MB. Tal vez pueda crear documentos históricos en tales escenarios, pero no creo que sea una buena idea. Además, no sé qué tan rápido sería el sistema si inserto cada vez más datos en el mismo documento grande.

  • Cree una colección para usuarios y otra para transacciones. Puede haber dos tipos de transacciones: compra de crédito con cambio de saldo positivo y mensajes enviados con cambio de saldo negativo. La transacción puede tener un subdocumento; por ejemplo, en los mensajes enviados, los detalles del SMS se pueden incrustar en la transacción. Desventajas: no almaceno el saldo actual del usuario, así que tengo que calcularlo cada vez que un usuario intenta enviar un mensaje para saber si el mensaje podría pasar o no. Me temo que este cálculo puede volverse lento a medida que aumenta el número de transacciones almacenadas.

Estoy un poco confundido acerca de qué método elegir. ¿Hay otras soluciones? No pude encontrar ninguna de las mejores prácticas en línea sobre cómo solucionar este tipo de problemas. Supongo que muchos programadores que intentan familiarizarse con el mundo NoSQL se enfrentan a problemas similares al principio.


61
Perdóneme si estoy equivocado pero parece que este proyecto va a usar un almacén de datos NoSQL independientemente de si se beneficiará o no. Los NoSQL no son una alternativa al SQL como una opción "fashion" sino para cuando la tecnología de los RDBMS relacionales no se ajusta al espacio del problema y lo hace un almacén de datos no relacional. Gran parte de su pregunta tiene "Si fue SQL entonces ..." y eso me suena a advertencia. Todos los NoSQL han surgido de la necesidad de resolver un problema que SQL no pudo y luego se han generalizado para facilitar su uso y, por supuesto, el carro comienza a rodar.
PurplePilot

44
Soy consciente de que este proyecto no es exactamente el mejor para probar NoSQL. Sin embargo, me da miedo si comenzamos a usarlo con otros proyectos (digamos un software de administración de colecciones de bibliotecas porque estamos en la administración de colecciones) y de repente llega algún tipo de solicitud que requiere transacciones (y en realidad está ahí, imagina que un libro se transfiere de una colección a otra) necesitamos saber cómo podemos superar el problema. Tal vez soy solo yo quien es de mente estrecha y cree que siempre hay una necesidad de transacciones. Pero podría ser que hay una manera de superarlos de alguna manera.
NagyI

3
Estoy de acuerdo con PurplePilot, debe elegir una tecnología que se adapte a una solución, no tratar de injertar una solución que no sea apropiada para un problema. El modelado de datos para las bases de datos de gráficos es un paradigma completamente diferente al diseño RDBMS y debe olvidar todo lo que sabe y volver a aprender la nueva forma de pensar.

9
Entiendo que debería usar la herramienta adecuada para la tarea. Sin embargo, para mí, cuando leo respuestas como esta, parece que NoSQL no es bueno para nada donde los datos son críticos. Es bueno para Facebook o Twitter, donde si se pierden algunos comentarios, el mundo continúa, pero todo lo anterior está fuera del negocio. Si eso es cierto, no entiendo por qué a otros les importa construir, por ejemplo. una tienda web con MongoDB: kylebanker.com/blog/2010/04/30/mongodb-and-ecommerce Incluso menciona que la mayoría de las transacciones pueden superarse con operaciones atómicas. Lo que estoy buscando es el cómo.
NagyI

2
Usted dice "parece que NoSQL no es bueno para nada donde los datos son críticos" no es cierto cuando no es bueno (tal vez) es el procesamiento transaccional de tipo ACID transaccional. Además, los NoSQL están diseñados para almacenes de datos distribuidos que los almacenes de tipo SQL pueden ser muy difíciles de lograr cuando ingresa a los escenarios de replicación de esclavos maestros. NoSQL tiene estrategias para la consistencia eventual y garantiza que solo se use el último conjunto de datos, pero no ACID.
PurplePilot

Respuestas:


23

A partir de 4.0, MongoDB tendrá transacciones ACID de documentos múltiples. El plan es habilitar primero a aquellos en implementaciones de conjunto de réplicas, seguidas de los clústeres fragmentados. Las transacciones en MongoDB se sentirán como si los desarrolladores de transacciones estuvieran familiarizados con las bases de datos relacionales: serán de múltiples declaraciones, con semántica y sintaxis similares (me gusta start_transactiony commit_transaction). Es importante destacar que los cambios en MongoDB que permiten las transacciones no afectan el rendimiento de las cargas de trabajo que no los requieren.

Para más detalles ver aquí .

Tener transacciones distribuidas no significa que deba modelar sus datos como en bases de datos relacionales tabulares. Aproveche el poder del modelo de documento y siga las buenas y recomendadas prácticas de modelado de datos.


1
Las transacciones han llegado! 4.0 GA'ed. mongodb.com/blog/post/…
Grigori Melnik

Las transacciones de MongoDB todavía tienen una limitación en el tamaño de la transacción de 16 MB, recientemente tuve un caso de uso en el que necesito colocar 50k registros de un archivo en mongoDB, por lo que para mantener la propiedad atómica pensé en usar transacciones, pero desde 50k json records excede este límite, arroja el error "El tamaño total de todas las operaciones de transacción debe ser inferior a 16793600. El tamaño real es 16793817". para más detalles, puede consultar el boleto oficial de jira abierto en mongoDB jira.mongodb.org/browse/SERVER-36330
Gautam Malik

MongoDB 4.2 (actualmente en beta, RC4) admite transacciones grandes. Al representar las transacciones en múltiples entradas de oplog, podrá escribir más de 16 MB de datos en una sola transacción ACID (sujeto al tiempo de ejecución máximo predeterminado de 60 segundos existente). Puedes probarlos ahora - mongodb.com/download-center/community
Grigori Melnik

MongoDB 4.2 ahora es GA con soporte completo de transacciones distribuidas. mongodb.com/blog/post/…
Grigori Melnik el

83

Viviendo sin transacciones

Las transacciones admiten propiedades ACID , pero aunque no hay transacciones MongoDB, sí tenemos operaciones atómicas. Bueno, las operaciones atómicas significan que cuando trabajas en un solo documento, ese trabajo se completará antes de que alguien más vea el documento. Verán todos los cambios que hicimos o ninguno de ellos. Y utilizando operaciones atómicas, a menudo puede lograr lo mismo que hubiéramos logrado utilizando transacciones en una base de datos relacional. Y la razón es que, en una base de datos relacional, necesitamos realizar cambios en varias tablas. Por lo general, las tablas que deben unirse y, por lo tanto, queremos hacer todo de una vez. Y para hacerlo, dado que hay varias tablas, tendremos que comenzar una transacción y hacer todas esas actualizaciones y luego finalizar la transacción. Pero conMongoDB, vamos a incrustar los datos, ya que vamos a unirlos previamente en documentos y son estos documentos ricos que tienen jerarquía. A menudo podemos lograr lo mismo. Por ejemplo, en el ejemplo de blog, si quisiéramos asegurarnos de actualizar atómicamente una publicación de blog, podemos hacerlo porque podemos actualizar toda la publicación de blog de una vez. Donde como si se tratara de un montón de tablas relacionales, probablemente tendríamos que abrir una transacción para poder actualizar la colección de publicaciones y la colección de comentarios.

Entonces, ¿cuáles son nuestros enfoques que podemos adoptar MongoDBpara superar la falta de transacciones?

  • reestructurar : reestructurar el código, de modo que trabajemos dentro de un solo documento y aprovechemos las operaciones atómicas que ofrecemos dentro de ese documento. Y si hacemos eso, entonces generalmente estamos listos.
  • implementar en software : podemos implementar el bloqueo en software creando una sección crítica. Podemos construir una prueba, probar y establecer usando buscar y modificar. Podemos construir semáforos, si es necesario. Y de alguna manera, esa es la forma en que el mundo más grande funciona de todos modos. Si lo pensamos bien, si un banco necesita transferir dinero a otro banco, no están viviendo en el mismo sistema relacional. Y cada uno tiene sus propias bases de datos relacionales a menudo. Y deben poder coordinar esa operación aunque no podamos comenzar la transacción y finalizar la transacción en esos sistemas de bases de datos, solo dentro de un sistema dentro de un banco. Así que ciertamente hay formas en el software para solucionar el problema.
  • tolerar : el enfoque final, que a menudo funciona en aplicaciones web modernas y otras aplicaciones que absorben una gran cantidad de datos, es tolerar un poco de inconsistencia. Un ejemplo sería, si estamos hablando de un feed de amigos en Facebook, no importa si todos ven la actualización de tu muro simultáneamente. Si está bien, si una persona está a unos segundos de distancia y se pone al día. A menudo no es crítico en muchos diseños de sistemas que todo se mantenga perfectamente consistente y que todos tengan una vista de la base de datos perfectamente consistente y la misma. Así que simplemente podríamos tolerar un poco de inconsistencia que es algo temporal.

UpdateLas operaciones findAndModify, $addToSet(dentro de una actualización) y $push(dentro de una actualización) operan atómicamente dentro de un solo documento.


2
Me gusta la forma en que lo hace esta respuesta, en lugar de seguir preguntando si deberíamos volver a la base de datos relacional. Gracias @xameeramir!
DonnyTian

3
una sección crítica del código no funcionará si tiene más de 1 servidor, tiene que usar un servicio de bloqueo distribuido externo
Alexander Mills

@AlexanderMills ¿Puedes elaborar por favor?
Zameer

Answere parece ser una transcripción de video desde aquí: youtube.com/watch?v=_Iz5xLZr8Lw
Fritz

Creo que esto parece estar bien hasta que tengamos que operar con una sola colección. Pero no podemos poner todo en un solo documento debido a una razón variada (tamaño del documento o si está utilizando referencias). Creo que entonces podemos necesitar transacciones.
user2488286

24

Mira esto , por Tokutek. Desarrollan un complemento para Mongo que promete no solo transacciones, sino también un aumento en el rendimiento.


@Giovanni Bitliner. Tokutek ha sido adquirido desde entonces por Percona, y en el enlace que proporcionó, no veo ninguna referencia a ninguna información de nada que haya sucedido desde la publicación. ¿Sabes qué pasó con su esfuerzo? Envié un correo electrónico a la dirección de correo electrónico en esa página para averiguarlo.
Tyler Collier

¿Qué necesitas específicamente? Si necesita tecnología toku aplicada a Mongodb, pruebe github.com/Tokutek/mongo , si necesita la versión mysql, tal vez la agregaron a su versión estándar de Mysql que generalmente proporcionan
Giovanni Bitliner

¿Cómo puedo integrar tokutek con nodejs?
Manoj Sanjeewa

11

Llévelo al punto: si la integridad transaccional es imprescindible, entonces no use MongoDB, sino que use solo componentes en el sistema que admitan transacciones. Es extremadamente difícil construir algo sobre el componente para proporcionar una funcionalidad similar a ACID para componentes que no cumplen con ACID. Dependiendo de los casos de uso individuales, puede tener sentido separar las acciones en acciones transaccionales y no transaccionales de alguna manera ...


1
Supongo que quiere decir que NoSQL se puede usar como una base de datos secundaria con RDBMS clásico. No me gusta la idea de mezclar NoSQL y SQL en el mismo proyecto. Aumenta la complejidad y posiblemente también introduce algunos problemas no triviales.
NagyI

1
Las soluciones NoSQL rara vez se usan solas. Las tiendas de documentos (mongo y couch) son probablemente la única excepción de esta regla.
Karoly Horvath

7

¿Cuál es el problema con eso? MongoDB puede hacer actualizaciones atómicas solo en un documento. En el flujo anterior, podría ocurrir que se produzca algún tipo de error y el mensaje se almacene en la base de datos, pero el saldo del usuario no se reduce y / o la transacción no se registra.

Esto no es realmente un problema. El error que mencionó es un error lógico (error) o IO (falla de red, disco). Este tipo de error puede dejar tanto las tiendas sin transacciones como las transaccionales en un estado no coherente. Por ejemplo, si ya ha enviado SMS pero se produjo un error al almacenar el mensaje: no puede deshacer el envío de SMS, lo que significa que no se registrará, el saldo del usuario no se reducirá, etc.

El verdadero problema aquí es que el usuario puede aprovechar las condiciones de carrera y enviar más mensajes de los que permite su saldo. Esto también se aplica a RDBMS, a menos que envíe SMS dentro de la transacción con bloqueo de campo de saldo (lo que sería un gran cuello de botella). Como una posible solución para MongoDB estaría usando findAndModifyprimero para reducir el saldo y verificarlo, si es negativo, no permita el envío y reembolse la cantidad (incremento atómico). Si es positivo, continúe enviando y en caso de que falle, reembolse el importe. La recopilación del historial de saldos también se puede mantener para ayudar a corregir / verificar el campo de saldo.


¡Gracias por esta gran respuesta! Sé que si uso almacenamientos con capacidad de transacción, los datos pueden corromperse debido al sistema de SMS, por lo que no tengo control. Sin embargo, con Mongo existe la posibilidad de que el error de datos también ocurra internamente. Digamos que el código cambia el saldo del usuario con findAndModify, el saldo se vuelve negativo pero antes de que pueda corregir el error se produce un error y la aplicación debe reiniciarse. Supongo que quiere decir que debería implementar algo similar a la confirmación de dos fases basada en la recopilación de transacciones y hacer una verificación de corrección regular en la base de datos.
NagyI

9
No es cierto, las tiendas transaccionales retrocederán si no realiza una confirmación final.
Karoly Horvath

9
Además, no envía SMS y luego inicia sesión en DB, eso es simplemente incorrecto. Primero almacene todo en DB y haga una confirmación final, luego puede enviar el mensaje. En este punto, algo podría fallar, por lo que necesita un trabajo cron para verificar que el mensaje se envió realmente, si no intenta enviarlo. Quizás una cola de mensajes dedicada sería mejor para esto. Pero todo se reduce a si puedes enviar SMS de forma transaccional ...
Karoly Horvath

@Nagy: Sí, a eso me refería. Uno tiene que intercambiar los beneficios de las transacciones para facilitar la escalabilidad. Básicamente, la aplicación tiene que esperar que dos documentos en diferentes colecciones puedan estar en un estado inconsistente y estar listos para manejar esto. @yi_H retrocederá pero el estado ya no será real (se perderá información sobre el mensaje). Esto no es mucho mejor que solo tener datos parciales (como saldo reducido pero sin información de mensaje o viceversa).
pingw33n

Veo. Esto en realidad no es una restricción fácil. Tal vez debería aprender más sobre cómo los sistemas RDBMS realizan transacciones. ¿Me puede recomendar algún tipo de material en línea o libro donde pueda leer sobre esto?
NagyI

6

El proyecto es simple, pero debe respaldar las transacciones de pago, lo que hace que todo sea difícil. Entonces, por ejemplo, un sistema de portal complejo con cientos de colecciones (foro, chat, anuncios, etc.) es en cierto sentido más simple, porque si pierde un foro o una entrada de chat, a nadie realmente le importa. Si, por otro lado, pierde una transacción de pago que es un problema grave.

Entonces, si realmente desea un proyecto piloto para MongoDB, elija uno que sea simple a ese respecto.


Gracias por explicarlo. Triste de oír eso. Me gusta la simplicidad de NoSQL y el uso de JSON. Estamos buscando una alternativa a ORM, pero parece que debemos mantenerla por un tiempo.
NagyI

¿Puede dar buenas razones por las cuales MongoDB es mejor que SQL para esta tarea? El proyecto piloto suena un poco tonto.
Karoly Horvath

No dije que MongoDB es mejor que SQL. Simplemente queremos saber si es mejor que SQL + ORM. Pero ahora se hace más claro que no son competitivos en este tipo de proyectos.
NagyI

6

Las transacciones están ausentes en MongoDB por razones válidas. Esta es una de esas cosas que hacen que MongoDB sea más rápido.

En su caso, si la transacción es imprescindible, mongo parece no encajar bien.

Puede ser RDMBS + MongoDB, pero eso agregará complejidades y dificultará la administración y el soporte de la aplicación.


1
Ahora hay una distribución de MongoDB llamada TokuMX que utiliza tecnología fractal para ofrecer una mejora del rendimiento de 50x y brinda soporte total de transacciones ACID al mismo tiempo: tokutek.com/tokumx-for-mongodb
OCDev

9
¿Cómo podría una transacción no ser un "deber"? ¿Tan pronto como necesite 1 caso simple en el que necesite actualizar 2 tablas, Mongo ya no es una buena opción? Eso no deja muchos casos de uso en absoluto.
Mr_E

1
@Mr_E está de acuerdo, por eso MongoDB es un poco tonto :)
Alexander Mills

6

¡Este es probablemente el mejor blog que encontré con respecto a la implementación de la transacción como característica para mongodb.!

Indicador de sincronización: mejor para copiar datos de un documento maestro

Cola de trabajo: propósito muy general, resuelve el 95% de los casos. ¡La mayoría de los sistemas necesitan tener al menos una cola de trabajos de todos modos!

Compromiso en dos fases: esta técnica garantiza que cada entidad siempre tenga toda la información necesaria para llegar a un estado coherente

Log Reconciliation: la técnica más robusta, ideal para sistemas financieros

Versionado: proporciona aislamiento y admite estructuras complejas.

Lea esto para obtener más información: https://dzone.com/articles/how-implement-robust-and


Incluya las partes relevantes del recurso vinculado necesarias para responder la pregunta dentro de su respuesta. Tal como está, su respuesta es muy susceptible a la descomposición del enlace (es decir, si el sitio web vinculado deja de funcionar o cambia, su respuesta es potencialmente inútil).
Mech

Gracias @mech por su sugerencia
Vaibhav

4

Esto es tarde, pero creo que esto ayudará en el futuro. Utilizo Redis para hacer una cola para resolver este problema.

  • Requisito: la
    imagen a continuación muestra que 2 acciones deben ejecutarse simultáneamente, pero la fase 2 y la fase 3 de la acción 1 deben finalizar antes de iniciar la fase 2 de la acción 2 u opuesta (una fase puede ser una solicitud REST api, una solicitud de base de datos o ejecutar código javascript ... ) ingrese la descripción de la imagen aquí

  • Cómo le ayuda una
    cola. Asegúrese de que cada código de bloque entre lock()y release()en muchas funciones no se ejecute al mismo tiempo, haga que se aíslen.

    function action1() {
      phase1();
      queue.lock("action_domain");
      phase2();
      phase3();
      queue.release("action_domain");
    }
    
    function action2() {
      phase1();
      queue.lock("action_domain");
      phase2();
      queue.release("action_domain");
    }
  • Cómo construir una cola
    Solo me enfocaré en cómo evitar la condición de la raza al crear una cola en el sitio de back-end. Si no conoce la idea básica de la cola, venga aquí .
    El siguiente código solo muestra el concepto, debe implementarlo de la manera correcta.

    function lock() {
      if(isRunning()) {
        addIsolateCodeToQueue(); //use callback, delegate, function pointer... depend on your language
      } else {
        setStateToRunning();
        pickOneAndExecute();
      }
    }
    
    function release() {
      setStateToRelease();
      pickOneAndExecute();
    }

Pero debe isRunning() setStateToRelease() setStateToRunning()aislarse a sí mismo o de lo contrario enfrentará la condición de raza nuevamente. Para hacer esto, elijo Redis para fines ACID y escalable.
El documento de Redis habla sobre su transacción:

Todos los comandos en una transacción se serializan y ejecutan secuencialmente. Nunca puede suceder que una solicitud emitida por otro cliente se atienda en medio de la ejecución de una transacción de Redis. Esto garantiza que los comandos se ejecuten como una sola operación aislada.

P / s:
uso Redis porque mi servicio ya lo usa, puede usar cualquier otra forma de soporte de aislamiento para hacerlo.
El action_domainen mi código está arriba para cuando solo necesita la acción 1 llamada por el usuario A bloquear la acción 2 del usuario A, no bloquee a otro usuario. La idea es poner una llave única para el bloqueo de cada usuario.


Habría recibido más votos a favor si su puntaje ya hubiera sido más alto. Así es como la mayoría piensa aquí. Su respuesta es útil en el contexto de la pregunta. Te he votado.
Mukus

3

Las transacciones están disponibles ahora en MongoDB 4.0. Muestra aquí

// Runs the txnFunc and retries if TransientTransactionError encountered

function runTransactionWithRetry(txnFunc, session) {
    while (true) {
        try {
            txnFunc(session);  // performs transaction
            break;
        } catch (error) {
            // If transient error, retry the whole transaction
            if ( error.hasOwnProperty("errorLabels") && error.errorLabels.includes("TransientTransactionError")  ) {
                print("TransientTransactionError, retrying transaction ...");
                continue;
            } else {
                throw error;
            }
        }
    }
}

// Retries commit if UnknownTransactionCommitResult encountered

function commitWithRetry(session) {
    while (true) {
        try {
            session.commitTransaction(); // Uses write concern set at transaction start.
            print("Transaction committed.");
            break;
        } catch (error) {
            // Can retry commit
            if (error.hasOwnProperty("errorLabels") && error.errorLabels.includes("UnknownTransactionCommitResult") ) {
                print("UnknownTransactionCommitResult, retrying commit operation ...");
                continue;
            } else {
                print("Error during commit ...");
                throw error;
            }
       }
    }
}

// Updates two collections in a transactions

function updateEmployeeInfo(session) {
    employeesCollection = session.getDatabase("hr").employees;
    eventsCollection = session.getDatabase("reporting").events;

    session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );

    try{
        employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } );
        eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } );
    } catch (error) {
        print("Caught exception during transaction, aborting.");
        session.abortTransaction();
        throw error;
    }

    commitWithRetry(session);
}

// Start a session.
session = db.getMongo().startSession( { mode: "primary" } );

try{
   runTransactionWithRetry(updateEmployeeInfo, session);
} catch (error) {
   // Do something with error
} finally {
   session.endSession();
}
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.