ElasticSearch의 데이터 무결성, 409 conflict

조갱·2024년 7월 28일
0

이슈 해결

목록 보기
15/15

ElasticSearch 데이터 보정 중에 휴먼에러로 인해 이슈가 생길'뻔' 했던, 그리고 그 과정에서 알게된 내용을 공유합니다.

이슈 쿼리 (혹시 무엇이 문제인지 눈치채셨나요?)

POST {IndexName}/_update_by_query
{
 "script": {
   "source": "ctx._source.{field1} = 0; ctx._source.{field2} = 0;",
   "lang": "painless"
   }
 },
 "query": {
   "terms": {
     "seq": [
       // seq(PK) 리스트
     ]
   }
 }
}

이슈의 발단

현재 내가 개발하고있는 샵바이 의 통계 서비스는 ElasticSearch를 통해 제공되고 있다.
그리고, 한 고객사의 통계 데이터를 보정해줄 일이 있었다.

이미 이전에 한번 보정을 진행했었는데, 바로 다음날 추가적인 요청이 들어왔다.
내용은 동일한거라, 기존에 사용했던 쿼리를 재활용 했는데 이 과정에서 실수가 있었다.

이전에 보정한 쿼리는 각 seq마다 다른 값으로 보정이 필요했기에 params를 사용했지만,
다음날에 보정한 쿼리는 모두 0으로 보정하면 됐기에, params를 날렸다.
그렇게 해서 위에 이슈 쿼리가 만들어졌다.

그리고, 아무런 의심 없이 리얼(운영)환경에 바로 날려버렸다. 😱

싸늘하다...

쿼리를 날렸는데, 너무 오래걸리는 것이다. 한,, 10초는 기다렸던 것 같다.

이상하다,,, seq가 400개라서 오래걸리는건가? 서버가 잠깐 느린건가?

그리고 kibana의 응답값에 409 conflict 가 발생했다.
쿼리를 다시 봤는데......

그렇다.. 중괄호 하나를 실수로 지우지 않아서 WHERE 절이 없는 UPDATE 쿼리가 만들어졌고, 그대로 날렸다...
전체 데이터를 업데이트 하느라 시간이 오래 걸린 것이다.

싸늘할 '뻔' 했다.

Elasticsearch 는 Transaction 개념이 없는걸로 아는데?
그러면 충돌나기 전 데이터는 전부 반영된거 아닌가?

그래서 확인을 해봤다.

GET {indexName}/_search
{
  "_source": ["{field1}", "{field2}"],
  "version": true
}
// 응답
"hits": [
   {
     "_index": "{indexName}",
     "_id": "114613",
     "_version": 1, // 최초 document 생성 시 version은 1임. 즉 문서 update가 안됐다는 의미
     "_score": 1,
     "_source": {
       "{field1}": 1, // 0으로 업데이트 안돼있음
       "{field2}": 20 // 0으로 업데이트 안돼있음
     }
   },
   ...
]

다행히 document에 반영은 안되어있었다.
그렇다. 409 conflict가 나면 해당 요청 내에서 이미 데이터가 수정 됐을 지라도 반영이 안된다.

Elasticsearch의 데이터 무결성

문서가 Update 되는 과정

Elasticsearch는 document를 업데이트 할 때 아래 순서에 따라 업데이트 된다고 한다.

  1. 원본 문서들을 읽어서 복사한다.
  2. 복사된 문서에 수정 사항을 반영한다
  3. 수정된 문서들을 원본 인덱스로 re-indexing 한다.

나는 2번에서 에러가 발생했기에 3번 re-indexing 이 진행되지 않은 케이스이다.

낙관적 lock (OCC, Optimistic concurrency control)

일반적으로 RDB에서는 트랜잭션 격리수준에 따라 데이터나 row를 Lock하여 충돌을 사전에 방지한다 (= Pessimistic Lock)

Elasticsearch는 데이터가 충돌할 가능성이 낮다고 판단하여 사전에 lock을 걸지 않는다. (= Optimistic Lock)
하지만 데이터 충돌이 일어나면, update 자체를 실패시키고 사용자에게 결정하도록 한다.

ES의 OCC는 데이터 무결성을 유지하는데 도움을 주며, 아래의 원칙에 기반한다.

  • document에 대한 각 update는 현재 버전 정보와 함께 제공된다.
  • update 시 버전 번호가 일치하지 않으면 409 conflict 가 반환된다.

트랜잭션이랑 뭐가 다르지?

문득 이런 생각이 들었다.

ES는 트랜잭션 개념이 없는데, 데이터를 쓰다가 실패하면 전부 롤백이 되는건 트랜잭션이랑 뭐가 다를까?

내가 생각한 차이점은

  • RDB에서의 트랜잭션은 Commit 과 Rollback 개념이 있어서 여러 쿼리를 롤백할 수 있다.
  • ES는 단순히 하나의 요청에 대해서만 Rollback이 가능하다.

단순히 이정도였다. 하지만 트랜잭션의 본질(?) 인 ACID를 생각해보니 더 쉽게 이해가 되었다.

A (Atomicity) : 트랜잭션 내의 '모든' 요청들이 완전히 실행되거나 실행되지 않아야 함
C (Consistency) : 트랜잭션이 완료되면 DB는 항상 일관된 상태
I (Isolation) : 하나의 트랜잭션이 실행중인 동안 다른 트랜잭션은 접근 못함
D (Durability) : 트랜잭션이 성공적으로 완료되면 그 결과는 영구적으로 반영

또한, RDBMS에서의 롤백은 일단 데이터를 쓰고, 원래대로 되돌리는 것을 의미하는 반면에
ES는 애초에 원본 document를 수정하는 것이 아니라 복사된 document에 값을 쓰고 index에 덮어쓰는 방식이기 때문에 되돌린다(Rollback) 는 개념으로 보기는 어렵겠다는 생각이 들었다.

재발 방지를 위해

이번 케이스에서는 운좋게 살아남았(?)지만, 앞으로도 이런 일은 없어야 한다.

  • 알파(테스트 환경)에서 반드시 쿼리 확인하기
  • kibana가 주는 에러메시지 잘 보기


(사실 이런 에러메시지가 있었는데, 그냥 키바나 버그인줄 알고 넘어갔었다,, 🙁)

번외 - 만약 데이터가 모두 업데이트 돼버렸다면?

이번에는 409 conflict가 발생해서 데이터가 써지지 않았지만,
만약 모든 데이터가 업데이트 되버리면 어떻게 될까?

슬쩍 롤백하기

통계 데이터는 RDB (mysql)에 먼저 저장되고, ES에 데이터가 복사된다.
따라서 ES의 데이터가 잘못됐을 경우

  1. 로직에서 통계데이터를 RDB를 보도록 수정하여 배포한다.
    (이미 RDB를 보는 Service로직과 Handler는 개발이 되어있다. 그래서 End-point에 연결되어있는 핸들러만 수정해주면 된다.)
  2. ES에서 잘못 수정된 인덱스를 날려버린다
  3. RDB -> ES 로 마이그레이션을 진행한다. (소요시간은 약 30분정도 예상)
  4. 로직에서 통계데이터를 ES보도록 수정하여 배포한다.

위와 같은 플로우로 롤백이 진행된다.

Reference

https://www.elastic.co/guide/en/elasticsearch/guide/master/version-control.html
https://www.elastic.co/kr/blog/elasticsearch-versioning-support

profile
A fast learner.

0개의 댓글