{"id":24064656,"url":"https://github.com/crissalvarezh/pessimistic-concurrency-control","last_synced_at":"2026-05-08T15:06:24.791Z","repository":{"id":53444217,"uuid":"521423759","full_name":"CrissAlvarezH/pessimistic-concurrency-control","owner":"CrissAlvarezH","description":"Ejemplo de como controlar el asincronismo en las escrituras de las bases de datos para evitar duplicidad e inconsistencias","archived":false,"fork":false,"pushed_at":"2022-08-08T03:38:22.000Z","size":17,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-02-26T18:14:47.010Z","etag":null,"topics":["database-lock","django","postgresql","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CrissAlvarezH.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-04T21:43:54.000Z","updated_at":"2022-08-08T17:53:16.000Z","dependencies_parsed_at":"2022-09-09T03:10:51.205Z","dependency_job_id":null,"html_url":"https://github.com/CrissAlvarezH/pessimistic-concurrency-control","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/CrissAlvarezH/pessimistic-concurrency-control","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrissAlvarezH%2Fpessimistic-concurrency-control","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrissAlvarezH%2Fpessimistic-concurrency-control/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrissAlvarezH%2Fpessimistic-concurrency-control/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrissAlvarezH%2Fpessimistic-concurrency-control/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CrissAlvarezH","download_url":"https://codeload.github.com/CrissAlvarezH/pessimistic-concurrency-control/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CrissAlvarezH%2Fpessimistic-concurrency-control/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271314373,"owners_count":24738161,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-20T02:00:09.606Z","response_time":69,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database-lock","django","postgresql","python"],"created_at":"2025-01-09T10:39:35.184Z","updated_at":"2026-05-08T15:06:24.748Z","avatar_url":"https://github.com/CrissAlvarezH.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pessimistic concurrency control\n\nEn aplicaciones altamente concurrentes es necesario tener en cuenta la consistencia de los datos a la hora de hacer funcionalidades que interactuen con la base de datos, ya que se podrían crear registros duplicados e incosistencias debido a que varios hilos de la aplicación acceden a las mismas partes de la base de datos al mismo tiempo en situaciones donde este caso no es el deseado.\nEn alguno casos querremos que la lectura y escritura de la base de datos sea forzosamente sincrona para evitar duplicados, vemos un ejemplo aquí:\n\n# Caso de uso\n\nEn este repositorio tenemos como ejemplo un caso de uso donde es necesario generar un control sobre la concurrencia debido a que el requerimiento así lo exige para evitar duplidad en la base de datos, el caso es el siguiente:\n\n    Se require un programa que agregue personas en una cola, cada persona que sea agregada \n    a la cola se le asignará una posición la cual será una unidad posterior a la de \n    la persona anterior, por ejemplo: para la cola llamada \"banco\" se forman las personas\n    \"Carlos\", \"Juan\" y \"Cristian\", en ese orden, despues de ser agregados a la cola\n    cada uno tendra las siguientes posiciones asignadas:\n    \"Carlos_i1\", \"Juan_i2\", \"Cristian_i3\"\n    IMPORTANTE: No pueden existir dos personas con la misma posición\n\n\n# Implementación\n\n## Opción 1. No thread safe\nPara el primer caso (Sin control sobre la concurrencia) tenemos el siguiente metodo\n\n    def add_person_v1(person_name: str, queue_name: str) -\u003e Person:\n        person = Person.objects.create(name=person_name)\n\n        # create queue if not exists\n        queue, _ = Queue.objects.get_or_create(name=queue_name)\n\n        last_position = 1\n        last_person_in_queue = (\n            PersonInQueue.objects.filter(queue_id=queue.id).order_by(\"-created_at\").first()\n        )\n        if last_person_in_queue:\n            last_position = last_person_in_queue.position + 1\n\n        person.name = person_name + \"_i\" + str(last_position)\n        person.save()\n\n        PersonInQueue.objects.create(person=person, queue=queue, position=last_position)\n\n        return person\n\n\u003e *persons_queue/services.py*\n\nEl problema con este metodo es que no es seguro cuando es sometido a hilos.\nPara probar esto tenemos el seguiente test:\n\n    class ServiceAddPersonV1AsynchronousTest(TransactionTestCase):\n        def test_two_asynchronous_add(self):\n            \"\"\"Test add_person_v1 on asynchronous case\n\n            both will have the same position because the method is not thread safe\n            \"\"\"\n            thread1 = Thread(target=add_person_v1, args=[\"juan\", \"jqueue\"])\n            thread2 = Thread(target=add_person_v1, args=[\"juan\", \"jqueue\"])\n\n            thread1.start()\n            thread2.start()\n            thread1.join()\n            thread2.join()\n\n            persons = Person.objects.all().order_by(\"name\")\n\n            self.assertEqual(len(persons), 2)\n            self.assertEqual(persons[0].name, f\"juan_i1\")\n            self.assertEqual(persons[1].name, f\"juan_i1\")\n\n\u003e *persons_queue/tests.py*\n\nEste test nos muestra que al correr la función `add_person_v1` en dos hilos genera\nuna duplicidad en las posiciones, **lo cual es algo que no queremos.**\n\n\n## Opción 2. Thread safe\n\nPara este caso nos aseguramos de bloquear los registros de la base de datos que\nestamos usando para que otros hilos tengan que esperar hasta que se desbloquee para \npoder acceder a ellos. Utilizando `select_for_update` y `transaction.atomic`\n\n    def add_person_v2(person_name: str, queue_name: str) -\u003e Person:\n        person = Person.objects.create(name=person_name)\n\n        # create queue if not exists\n        Queue.objects.get_or_create(name=queue_name)\n\n        with transaction.atomic():\n            queue = Queue.objects.select_for_update().filter(name=queue_name).first()\n\n            last_position = 1\n            last_person_in_queue = (\n                PersonInQueue.objects.filter(queue_id=queue.id)\n                .order_by(\"-created_at\")\n                .first()\n            )\n            if last_person_in_queue:\n                last_position = last_person_in_queue.position + 1\n\n            person.name = person_name + \"_i\" + str(last_position)\n            person.save()\n\n            PersonInQueue.objects.create(person=person, queue=queue, position=last_position)\n\n        return person\n\n\u003e *persons_queue/services.py*\n\nAquí cada instancia de este metodo que este siendo llamada en diferentes hilos tendran que\nencolarse para tener acceso a la base de datos en la cola correspondiente, ya que se hace un lock\nal registro del modelo `Queue` mientras se agrega una persona a la cola. El test respectivo es el siguiente:\n\n    class ServiceAddPersonV2AsynchronousTest(TransactionTestCase):\n        def test_two_asynchronous_add(self):\n            \"\"\"Testing add_person_v2 on asynchronous case\n\n            In that case both must be have different positions because the method\n            lock de transaction in on specific slice of the database, in this case\n            is the 'queue' selected, and avoid the position duplicity\n            \"\"\"\n            thread1 = Thread(target=add_person_v2, args=[\"juan\", \"jqueue\"])\n            thread2 = Thread(target=add_person_v2, args=[\"juan\", \"jqueue\"])\n\n            thread1.start()\n            thread2.start()\n            thread1.join()\n            thread2.join()\n\n            persons = Person.objects.all().order_by(\"name\")\n            self.assertEqual(len(persons), 2)\n            self.assertEqual(persons[0].name, f\"juan_i1\")\n            self.assertEqual(persons[1].name, f\"juan_i2\")\n\n\u003e *persons_queue/tests.py*\n\nAquí vemos que se evalua que las personas agregadas a la cola se les asigne una posicion diferente\ny consistente, siguiente la secuencia que debe llevar la cola.\n\n\n# Ejecutar tests\n\nPara ejecutar test basta con correr el siguente comando\n\n    make test","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrissalvarezh%2Fpessimistic-concurrency-control","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcrissalvarezh%2Fpessimistic-concurrency-control","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcrissalvarezh%2Fpessimistic-concurrency-control/lists"}