MongoDB의 트랜잭션 부족을 해결하는 방법은 무엇입니까?


139

나는 여기에 비슷한 질문이 있다는 것을 알고 있지만 트랜잭션이 필요하거나 원자 연산 또는 2 단계 커밋을 사용하는 경우 일반 RDBMS 시스템으로 다시 전환 하라고 지시합니다 . 두 번째 솔루션이 최선의 선택 인 것 같습니다. 세 번째는 많은 일이 잘못 될 수 있고 모든 측면에서 테스트 할 수 없기 때문에 따르고 싶지 않습니다. 원자 작업을 수행하기 위해 프로젝트를 리팩토링하는 데 어려움을 겪고 있습니다. 나는 이것이 제한된 관점에서 왔는지 (지금까지는 SQL 데이터베이스로만 작업 했음) 또는 실제로 수행 할 수 있는지 여부를 알 수 없습니다.

우리 회사에서 MongoDB를 시범 테스트하고 싶습니다. 우리는 SMS 게이트웨이라는 비교적 간단한 프로젝트를 선택했습니다. 그것은 우리의 소프트웨어가 셀룰러 네트워크에 SMS 메시지를 보낼 수있게하며 게이트웨이는 더러운 작업을 수행합니다. 실제로 다른 통신 프로토콜을 통해 공급자와 통신합니다. 게이트웨이는 또한 메시지 청구를 관리합니다. 서비스를 신청하는 모든 고객은 약간의 크레딧을 구매해야합니다. 메시지가 전송 될 때 시스템은 자동으로 사용자의 잔액을 줄이고 잔액이 부족하면 액세스를 거부합니다. 또한 우리는 타사 SMS 제공 업체의 고객이므로 그들과 자체 균형이있을 수도 있습니다. 우리도 그것들을 추적해야합니다.

복잡성 (외부 청구, 대기중인 SMS 전송)을 줄이면 MongoDB에 필요한 데이터를 저장하는 방법에 대해 생각하기 시작했습니다. SQL 세계에서 온 사용자를 위해 별도의 테이블을 만들고, 다른 하나는 SMS 메시지를 위해, 다른 하나는 사용자 균형에 관한 트랜잭션을 저장하기위한 테이블을 만듭니다. MongoDB의 모든 콜렉션에 대해 별도의 콜렉션을 작성한다고 가정 해 봅시다.

이 단순화 된 시스템에서 다음 단계로 SMS 전송 작업을 상상해보십시오.

  1. 사용자에게 잔액이 충분한 지 확인하십시오. 크레딧이 충분하지 않으면 액세스 거부

  2. 세부 정보 및 비용과 함께 SMS 컬렉션에 메시지를 보내고 저장합니다 (라이브 시스템에서는 메시지에 status특성이 있고 작업이 배달을 위해 메시지를 받아 현재 상태에 따라 SMS 가격을 설정 함).

  3. 보낸 메시지 비용으로 사용자 잔액을 줄입니다.

  4. 트랜잭션 콜렉션에 트랜잭션을 기록하십시오.

이제 문제가 무엇입니까? MongoDB는 하나의 문서에서만 원자 업데이트를 수행 할 수 있습니다. 이전 흐름에서는 일종의 오류가 발생하여 메시지가 데이터베이스에 저장되지만 사용자 잔액이 업데이트되지 않거나 트랜잭션이 기록되지 않을 수 있습니다.

나는 두 가지 아이디어를 생각해 냈습니다.

  • 사용자에 대한 단일 콜렉션을 작성하고 잔액을 필드, 사용자 관련 트랜잭션 및 메시지를 사용자 문서의 하위 문서로 저장하십시오. 문서를 원자 적으로 업데이트 할 수 있기 때문에 실제로 트랜잭션 문제가 해결됩니다. 단점 : 사용자가 많은 SMS 메시지를 보내면 문서 크기가 커지고 4MB 문서 제한에 도달 할 수 있습니다. 그런 시나리오에서 기록 문서를 만들 수는 있지만 이것이 좋은 생각은 아닙니다. 또한 더 큰 데이터를 동일한 큰 문서로 푸시하면 시스템이 얼마나 빠른지 알 수 없습니다.

  • 사용자를위한 하나의 컬렉션과 트랜잭션을위한 하나의 컬렉션을 만듭니다. 거래 는 긍정적 인 잔액 변경으로 신용 구매 와 부정적인 잔액 변경으로 전송 된 메시지 의 두 가지 종류가 있습니다. 트랜잭션에는 하위 문서가있을 수 있습니다. 예를 들어, 전송메시지 에서 SMS의 세부 사항은 트랜잭션에 임베드 될 수 있습니다. 단점 : 현재 사용자 잔액을 저장하지 않으므로 사용자가 메시지를 보낼 때마다 메시지를 보낼 때마다 계산해야합니다. 저장된 트랜잭션 수가 증가함에 따라이 계산이 느려질 수 있습니다.

어떤 방법을 선택 해야할지 조금 혼란 스럽습니다. 다른 해결책이 있습니까? 이러한 종류의 문제를 해결하는 방법에 대한 온라인 모범 사례를 찾을 수 없었습니다. NoSQL 세계에 익숙해 지려는 많은 프로그래머들이 처음에 비슷한 문제에 직면하고 있다고 생각합니다.


61
내가 잘못했지만이 프로젝트가 NoSQL 데이터 저장소를 사용하여 이점을 얻을지 여부와 관계없이 사용하는 것처럼 보입니다. NoSQL은 "패션"선택으로서 SQL의 대안이 아니라 관계형 RDBMS 기술이 문제 공간에 적합하지 않고 비 관계형 데이터 저장소에 적합 할 때 사용합니다. 많은 질문에 "SQL 인 경우 ..."가 있으며 경고 벨이 울립니다. 모든 NoSQL은 SQL이 할 수 없었던 문제를 해결해야 할 필요성에서 왔으며, 사용하기 쉽게 만들기 위해 다소 일반화되었습니다.
PurplePilot

4
이 프로젝트가 NoSQL을 시도하기에 가장 적합하지 않다는 것을 알고 있습니다. 그러나 다른 프로젝트 (우리가 컬렉션 관리에 있기 때문에 라이브러리 컬렉션 관리 소프트웨어라고합시다)와 함께 사용하기 시작하고 갑자기 거래가 필요한 일종의 요청이 오는 경우 두려워합니다 (그리고 실제로 거기에 책이 있다고 상상해보십시오) 우리는 문제를 어떻게 극복 할 수 있는지 알아야합니다. 어쩌면 내 마음이 좁은 사람 일 수도 있고 항상 거래가 필요하다고 생각할 수도 있습니다. 그러나 어떻게 든이를 극복 할 수있는 방법이있을 수 있습니다.
NagyI

3
PurplePilot에 동의합니다. 문제에 적합하지 않은 솔루션을 접목하지 말고 솔루션에 맞는 기술을 선택해야합니다. 그래프 데이터베이스에 대한 데이터 모델링은 RDBMS 디자인과 완전히 다른 패러다임이며, 알고있는 모든 것을 잊고 새로운 사고 방식을 다시 학습해야합니다.

9
나는 작업에 적절한 도구를 사용해야한다는 것을 이해합니다. 그러나 나에게-이 같은 대답을 읽을 때-NoSQL은 데이터가 중요한 곳에서는 좋지 않은 것 같습니다. 일부 의견을 잃어 버릴 경우 세계가 계속 진행되는 Facebook 또는 Twitter에 유용하지만 그 이상의 내용은 비즈니스에서 벗어납니다. 그것이 사실이라면 나는 왜 다른 사람이 건축에 관심을 가져야하는지 이해하지 못합니다. MongoDB가있는 웹 스토어 : kylebanker.com/blog/2010/04/30/mongodb-and-ecommerce 또한 원자력 운영으로 대부분의 트랜잭션을 극복 할 수 있다고 언급합니다. 내가 찾고있는 것은 방법입니다.
NagyI

2
"NoSQL은 데이터가 중요한 모든 곳에 적합하지 않은 것 같습니다"라고 말하면 트랜잭션 ACID 유형 트랜잭션 처리가 좋지 않은 경우에는 사실이 아닙니다. 또한 NoSQL은 분산 데이터 저장 소용으로 설계되었으므로 마스터 슬레이브 복제 시나리오에 들어갈 때 SQL 유형 저장소를 달성하기가 매우 어려울 수 있습니다. NoSQL에는 최종 일관성을위한 전략이 있으며 최신 데이터 세트 만 사용되지만 ACID는 사용하지 않도록합니다.
PurplePilot

답변:


23

4.0부터 MongoDB는 다중 문서 ACID 트랜잭션을 갖습니다. 계획은 복제본 세트 배포를 먼저 활성화 한 다음 샤드 클러스터를 활성화하는 것입니다. MongoDB의 트랜잭션은 개발자가 관계형 데이터베이스에서 익숙한 트랜잭션처럼 느껴질 것입니다. 비슷한 의미 및 구문 (와 같은 start_transaction및 유사한)을 가진 다중 명령문이 commit_transaction됩니다. 중요하게, 트랜잭션을 가능하게하는 MongoDB의 변경 사항은이를 필요로하지 않는 워크로드의 성능에 영향을 미치지 않습니다.

자세한 내용은 여기를 참조 하십시오 .

분산 트랜잭션이 있다고해서 테이블 관계형 데이터베이스 에서처럼 데이터를 모델링해야한다는 의미는 아닙니다. 문서 모델의 힘을 받아들이고 데이터 모델링 의 우수하고 권장되는 사례 를 따르십시오 .


1
거래가 도착했습니다! 4.0 GA'ed. mongodb.com/blog/post/…
Grigori Melnik

MongoDB 트랜잭션은 여전히 ​​트랜잭션 크기 16MB에 제한이 있습니다. 최근에 파일에서 mongoDB에 50k 레코드를 넣어야하는 유스 케이스가 있었으므로 원자 속성을 유지하기 위해 트랜잭션을 사용하려고 생각했지만 50k json 레코드 이후 이 제한을 초과하면 "모든 트랜잭션 작업의 총 크기는 16793600보다 작아야합니다. 실제 크기는 16793817"입니다. 자세한 내용은 당신은 MongoDB의에서 공식 JIRA 티켓 개방을 통해 갈 수 jira.mongodb.org/browse/SERVER-36330
가우 탐 말리크

MongoDB 4.2 (현재 베타 버전 인 RC4)는 큰 트랜잭션을 지원합니다. 여러 oplog 항목에 걸쳐 트랜잭션을 나타내면 단일 ACID 트랜잭션에 16MB 이상의 데이터를 쓸 수 있습니다 (기존 60 초 기본 최대 실행 시간에 따름). 당신은 지금 그들을 시도 할 수 있습니다 -mongodb.com/download-center/community
Grigori Melnik

MongoDB 4.2는 이제 분산 트랜잭션을 완벽하게 지원하는 GA입니다. mongodb.com/blog/post/…
Grigori Melnik

83

거래없는 생활

트랜잭션은 ACID 속성을 지원 하지만에 트랜잭션이 없지만 MongoDB원자 적 연산이 있습니다. 원자 작업은 단일 문서에서 작업 할 때 다른 사람이 문서를보기 전에 해당 작업이 완료된다는 것을 의미합니다. 그들은 우리가 변경 한 모든 것을 보거나 전혀 보지 못할 것입니다. 원자 연산을 사용하면 관계형 데이터베이스에서 트랜잭션을 사용하여 달성 한 것과 동일한 결과를 얻을 수 있습니다. 그 이유는 관계형 데이터베이스에서 여러 테이블을 변경해야하기 때문입니다. 일반적으로 결합해야하는 테이블이므로 한 번에 모두 수행하려고합니다. 이를 위해서는 여러 테이블이 있으므로 트랜잭션을 시작하고 모든 업데이트를 수행 한 다음 트랜잭션을 종료해야합니다. 하지만 함께MongoDB데이터 를 문서 에 미리 포함시킬 것이므로 계층 구조가있는 이러한 풍부한 문서 이기 때문에 데이터를 포함 할 것 입니다. 우리는 종종 같은 일을 할 수 있습니다. 예를 들어 블로그 예제에서 블로그 게시물을 원자 적으로 업데이트하려면 전체 블로그 게시물을 한 번에 업데이트 할 수 있기 때문에 업데이트 할 수 있습니다. 마치 여러 관계형 테이블 인 것처럼 포스트 컬렉션 및 주석 컬렉션을 업데이트 할 수 있도록 트랜잭션을 열어야 할 것입니다.

MongoDB트랜잭션 부족을 극복하기 위해 취할 수있는 접근 방식은 무엇 입니까?

  • 재구성 -코드를 재구성하여 단일 문서 내에서 작업하고 해당 문서 내에서 제공하는 원자 적 작업을 활용합니다. 우리가 그렇게하면 보통 모든 것이 준비됩니다.
  • 소프트웨어로 구현 -중요한 섹션을 만들어 소프트웨어 잠금을 구현할 수 있습니다. find와 modify를 사용하여 테스트, 테스트 및 설정을 할 수 있습니다. 필요한 경우 세마포어를 만들 수 있습니다. 어쨌든 그것은 더 큰 세상이 작동하는 방식입니다. 우리가 그것에 대해 생각하면, 한 은행이 다른 은행으로 돈을 이체 해야하는 경우 동일한 관계형 시스템에 살고 있지 않습니다. 그리고 그들 각자는 종종 자신의 관계형 데이터베이스를 가지고 있습니다. 그리고 한 데이터베이스 내에서 하나의 시스템 내에서만 해당 데이터베이스 시스템에서 트랜잭션을 시작하고 트랜잭션을 종료 할 수 없더라도 해당 작업을 조정할 수 있어야합니다. 따라서 소프트웨어를 통해 문제를 해결할 수있는 방법이 있습니다.
  • 관용 -현대 웹 응용 프로그램과 엄청난 양의 데이터를 사용하는 다른 응용 프로그램에서 종종 작동하는 최종 접근 방식은 약간의 불일치를 허용하는 것입니다. 예를 들어 Facebook에서 친구 피드에 대해 이야기하는 경우 모두가 벽 업데이트를 동시에 보는 것이 중요하지 않습니다. 괜찮다면, 한 사람이 몇 초 동안 뛰고 그들은 따라 잡습니다. 많은 시스템 설계에서 모든 것이 완벽하게 일관성을 유지하고 모든 사람이 완벽하게 일관성 있고 동일한 데이터베이스 뷰를 갖는 것이 중요하지 않은 경우가 많습니다. 따라서 일시적인 약간의 불일치를 간단히 허용 할 수 있습니다.

Update, findAndModify, $addToSet(업데이트 내) $push(업데이트 이내) 동작들은 하나의 문서 내에서 원자 적으로 동작한다.


2
관계형 DB로 돌아 가야하는지 계속 질문하는 대신이 답변의 방식이 마음에 듭니다. 감사합니다 @xameeramir!
DonnyTian

3
코드의 중요한 섹션 것이다 외부 분산 잠금 서비스를 사용할 수 있고, 더 1 개 서버보다가 아닌 경우 일
알렉산더 밀스

@AlexanderMills 좀 더 자세히 설명해 주시겠습니까?
Zameer

answere는 여기에서 비디오 대본 인 것 같습니다 : youtube.com/watch?v=_Iz5xLZr8Lw
Fritz

나는 우리가 단일 컬렉션에서 작동해야 할 때까지 괜찮다고 생각합니다. 그러나 다양한 이유 (문서 크기 또는 참조를 사용하는 경우) 때문에 모든 것을 단일 문서에 넣을 수는 없습니다. 그때 우리는 거래가 필요할지도 모른다고 생각합니다.
user2488286

24

Tokutek 확인하십시오 . 그들은 거래뿐만 아니라 성능 향상을 약속하는 Mongo 플러그인을 개발합니다.


@Giovanni Bitliner. Tokutek은 Percona에 의해 인수되었으며 귀하가 제공 한 링크에서 게시물 이후에 발생한 정보에 대한 언급은 없습니다. 그들의 노력에 무슨 일이 있었는지 아십니까? 확인을 위해 해당 페이지의 이메일 주소로 이메일을 보냈습니다.
Tyler Collier

구체적으로 무엇이 필요합니까? 당신이 MongoDB를 적용 푸는 기술이 필요한 경우 시도 github.com/Tokutek/mongo을 당신은 아마도 MySQL의 버전을 필요로하는 경우 그들은 일반적으로 제공하는 MySQL의 자신의 표준 버전에 추가,
조반니 Bitliner

tokutek과 nodejs를 어떻게 통합 할 수 있습니까?
Manoj Sanjeewa

11

트랜잭션 무결성이 필수 인 경우 MongoDB를 사용하지 말고 트랜잭션을 지원하는 시스템의 구성 요소 만 사용하십시오. 비 ACID 호환 구성 요소에 ACID와 유사한 기능을 제공하기 위해 구성 요소 위에 무언가를 구축하는 것은 매우 어렵습니다. 개별 사용 사례에 따라 어떤 방식으로 조치를 트랜잭션 및 비 트랜잭션 조치로 분리하는 것이 좋습니다.


1
NoSQL을 고전적인 RDBMS와 함께 조수 데이터베이스로 사용할 수 있음을 의미한다고 생각합니다. 동일한 프로젝트에서 NoSQL과 SQL을 혼합하는 아이디어가 마음에 들지 않습니다. 복잡성이 증가하고 사소한 문제도 발생할 수 있습니다.
NagyI

1
NoSQL 솔루션은 거의 단독으로 사용되지 않습니다. 문서 저장소 (mongo 및 couch)는 아마도이 규칙에서 유일하게 예외입니다.
Karoly Horvath

7

이제 문제가 무엇입니까? MongoDB는 하나의 문서에서만 원자 업데이트를 수행 할 수 있습니다. 이전 흐름에서는 일종의 오류가 발생하고 메시지가 데이터베이스에 저장되지만 사용자의 잔액이 줄어들지 않거나 트랜잭션이 기록되지 않을 수 있습니다.

이것은 실제로 문제가되지 않습니다. 언급 한 오류는 논리적 (버그) 또는 IO 오류 (네트워크, 디스크 오류)입니다. 이러한 종류의 오류는 트랜잭션이없는 저장소와 트랜잭션 저장소가 일관성이없는 상태가 될 수 있습니다. 예를 들어, 이미 SMS를 전송했지만 메시지 저장 중 오류가 발생하면 SMS 전송을 롤백 할 수 없습니다. 즉, 기록되지 않으며 사용자 잔액이 감소하지 않습니다.

여기서 실제 문제는 사용자가 경쟁 조건을 이용하고 자신의 잔액보다 더 많은 메시지를 보낼 수 있다는 것입니다. 이는 균형 필드 잠금 (큰 병목 현상이 발생 함)을 사용하여 트랜잭션 내부에서 SMS 전송을 수행하지 않는 한 RDBMS에도 적용됩니다. MongoDB에 대한 가능한 해결책은 findAndModify음수 전송이 허용되지 않고 금액 (원자 증가)이 아닌 경우 먼저 잔액을 줄이고 잔액을 확인하는 데 사용하는 것입니다. 긍정적 인 경우 계속 발송하고 금액을 환불하지 못한 경우. 잔액 이력 수집을 유지하여 잔액 필드를 수정 / 확인할 수 있습니다.


이 위대한 답변에 감사드립니다! 트랜잭션 가능 스토리지를 사용하면 SMS 시스템으로 인해 데이터가 손상 될 수 있음을 알고 있습니다. 그러나 Mongo를 사용하면 내부에서 데이터 오류가 발생할 수도 있습니다. 코드가 findAndModify를 사용하여 사용자의 잔액을 변경하면 잔액은 음수이지만 실수를 수정하기 전에 오류가 발생하고 응용 프로그램을 다시 시작해야한다고 가정 해 봅시다. 트랜잭션 수집을 기반으로 2 단계 커밋과 비슷한 것을 구현하고 데이터베이스에서 정기적으로 수정 검사를 수행해야한다는 것을 의미한다고 생각합니다.
NagyI

9
마지막 커밋을 수행하지 않으면 트랜잭션 저장소가 롤백됩니다.
Karoly Horvath

9
또한 SMS를 보내지 않고 DB에 로그인하면 문제가 발생합니다. 먼저 모든 것을 DB에 저장하고 최종 커밋을 수행 한 다음 메시지를 보낼 수 있습니다. 이 시점에서 여전히 문제가 발생할 수 있으므로 보내려고하지 않으면 메시지가 실제로 전송되었는지 확인하는 크론 작업이 필요합니다. 아마도 전용 메시지 대기열이 더 좋을 것입니다. 그러나 모든 것이 당신이 거래 방식으로 SMS를 보낼 수 있는지에 달려 있습니다.
Karoly Horvath

예, 그게 제가 의미 한 바입니다. 확장 성을 쉽게하기 위해 거래의 이점을 거래해야합니다. 기본적으로 응용 프로그램은 서로 다른 컬렉션의 두 문서가 일관성이없는 상태에있을 수 있고이를 처리 할 준비가되어 있어야합니다. @yi_H 롤백되지만 상태는 더 이상 실제 상태가 아닙니다 (메시지에 대한 정보가 손실됩니다). 이것은 단지 부분적인 데이터를 갖는 것 (밸런스가 감소되었지만 메시지 정보가 없거나 그 반대)보다 훨씬 낫습니다.
pingw33n

내가 참조. 이것은 실제로 쉬운 제약이 아닙니다. 어쩌면 RDBMS 시스템이 트랜잭션을 수행하는 방법에 대해 더 많이 알아야 할 것입니다. 내가 읽을 수있는 온라인 자료 나 책을 추천 해 줄 수 있습니까?
NagyI

6

이 프로젝트는 간단하지만 지불을위한 거래를 지원해야하므로 모든 것이 어려워집니다. 예를 들어 포럼이나 채팅 항목을 잃어 버리면 아무도 신경 쓰지 않기 때문에 수백 가지 컬렉션 (포럼, 채팅, 광고 등)이있는 복잡한 포털 시스템이 더 간단합니다. 반면에 심각한 문제인 결제 거래를 잃어버린 경우.

따라서 MongoDB의 파일럿 프로젝트를 정말로 원한다면 점 에서 간단한 프로젝트를 선택하십시오 .


설명해 주셔서 감사합니다. 안타깝 네요 나는 NoSQL의 단순함과 JSON의 사용을 좋아합니다. 우리는 ORM의 대안을 찾고 있지만 잠시 동안 고수해야합니다.
NagyI

MongoDB가이 작업에 대해 SQL보다 나은 이유를 알 수 있습니까? 파일럿 프로젝트는 약간 어리석은 소리입니다.
Karoly Horvath

MongoDB가 SQL보다 낫다고 말하지 않았습니다. SQL + ORM보다 나은지 알고 싶습니다. 그러나 이제는 이런 종류의 프로젝트에서 경쟁력이 없다는 것이 명확 해졌습니다.
NagyI

6

유효한 이유로 MongoDB에 트랜잭션이 없습니다. 이것은 MongoDB를 더 빠르게 만드는 것 중 하나입니다.

귀하의 경우, 거래가 필수적이라면 mongo는 적합하지 않은 것 같습니다.

RDMBS + MongoDB 일 수 있지만 복잡성이 추가되어 응용 프로그램을 관리하고 지원하기가 더 어려워집니다.


1
프랙탈 기술을 사용하여 50 배의 성능 향상을 제공하고 동시에 완전한 ACID 트랜잭션 지원을 제공하는 TokuMX라는 MongoDB 배포판이 있습니다 : tokutek.com/tokumx-for-mongodb
OCDev

9
트랜잭션이 "필수"가되지 않은 방법 2 개의 테이블을 업데이트 해야하는 간단한 경우 1이 필요한 즉시 mongo가 더 이상 적합하지 않습니다. 그것은 많은 유스 케이스를 전혀 남기지 않습니다.
Mr_E

1
MongoDB를이 :) 좀 바보 같은 이유 @Mr_E는 그의 동의
알렉산더 밀스

6

이것은 아마도 mongodb 기능과 같은 트랜잭션 구현과 관련하여 내가 찾은 최고의 블로그 일 것입니다.!

동기화 플래그 : 마스터 문서에서 데이터를 복사하는 데 가장 적합

작업 대기열 : 매우 일반적인 목적으로 95 %의 사례를 해결합니다. 대부분의 시스템에는 어쨌든 최소한 하나 이상의 작업 대기열이 있어야합니다!

2 단계 커밋 :이 기법은 각 엔티티가 항상 일관된 상태에 도달하는 데 필요한 모든 정보를 갖도록합니다.

로그 조정 : 금융 시스템에 이상적인 가장 강력한 기술

버전 관리 : 격리를 제공하고 복잡한 구조를 지원합니다

자세한 내용은 다음을 읽으십시오 : https://dzone.com/articles/how-implement-robust-and


답변 내에 질문에 답변하는 데 필요한 링크 된 리소스의 관련 부분을 포함하십시오. 있는 그대로, 귀하의 답변은 부패한 링크에 매우 민감합니다 (예 : 링크 된 웹 사이트가 다운되거나 귀하의 답변이 잠재적으로 쓸모없는 경우).
mech

제안 해 주셔서 감사합니다 @ mech
Vaibhav

4

늦었지만 이것이 도움이 될 것이라고 생각합니다. 이 문제를 해결하기 위해 대기열 을 만들기 위해 Redis 를 사용합니다 .

  • 요구 사항 :
    아래 이미지는 2 개의 조치가 동시에 실행되어야하지만 2 단계와 1 단계의 3 단계는 조치 2의 2 단계를 시작하기 전에 완료하거나 반대로 수행해야 함을 보여줍니다 (단계는 요청 REST API, 데이터베이스 요청 또는 JavaScript 코드 실행 일 수 있음). ). 여기에 이미지 설명을 입력하십시오

  • 대기열이 당신을 도와주는 방법 대기열 과 많은 기능
    사이의 모든 블록 코드가 동시에 실행되지 않도록 분리하십시오.lock()release()

    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");
    }
  • 대기열을 만드는
    방법 백엔드 사이트에 대기열을 만들 때 경쟁 조건 부분을 피하는 방법에만 중점을 둡니다 . 대기열의 기본 아이디어를 모른다면 여기로 오십시오 .
    아래 코드는 개념만을 보여 주므로 올바른 방식으로 구현해야합니다.

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

그러나 당신은 isRunning() setStateToRelease() setStateToRunning()자기 자신을 격리하거나 다른 경쟁 조건에 다시 직면해야합니다. 이를 위해 ACID 목적과 확장 성을 위해 Redis를 선택합니다 .
Redis 문서 는 거래에 대해 이야기합니다.

트랜잭션의 모든 명령은 직렬화되고 순차적으로 실행됩니다. 다른 클라이언트가 발행 한 요청이 Redis 트랜잭션 실행 중에 제공되는 것은 결코 불가능합니다. 이를 통해 명령이 단일 격리 작업으로 실행됩니다.


추신 : 내 서비스에서 이미 사용하고 있기 때문에 Redis를 사용합니다. 다른 방법으로 지원 격리를 사용하여 그렇게 할 수 있습니다. 내 코드는 다른 사용자를 차단하지 않는 사용자 A의 사용자 A 블록 동작이들만 조치 1 호를 필요로 할 때에 이상입니다. 아이디어는 각 사용자를 잠그기위한 고유 키가 있습니다.
action_domain


당신은 이미 높은 점수를 받았다면 더 많은 투표를 받았을 것입니다. 그것이 가장 많이 여기에서 생각하는 방식입니다. 귀하의 답변은 질문과 관련하여 유용합니다. 난 당신을 upvoted했습니다.
Mukus

3

거래는 MongoDB 4.0에서 가능합니다. 여기 샘플

// 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();
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.