Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

특정 시점의 이벤트를 불러올 때 제대로 실행하지 못하는 부분 #5

Open
ebang091 opened this issue Jul 22, 2023 · 24 comments

Comments

@ebang091
Copy link

현재 기록중인 binary log file에서만 찾기 때문에 발생하는 문제 해결

@ebang091
Copy link
Author

멘토님께서 설명해주신 설명을 듣고 구현해도 좋을 것 같아서 올려보았습니다!

@BeautterLife
Copy link

BeautterLife commented Jul 23, 2023

이 이슈가 julien-duponchelle#404 요 이슈 맞나요?

@ebang091
Copy link
Author

ebang091 commented Jul 23, 2023

@BeautterLife 넵! 이부분 맞습니다

@jaehyeonpy
Copy link

이거 멘토님께 설명 들었을 때 되게 흥미로운 문제라고 생각했습니다!

문제 해결에 있어 논점이 될 만한 부분을 잠시 생각해 보았습니다:


예전 binlog file에 있는 timestamp 정보 미리 취득해 주어야 이슈를 해결할 수 있으리라 생각합니다.

이를 위해 인덱스를 구현해야 할까요? 인덱스가 있다면 사용자 요청에 따라 이전 binlog file 매번 뒤적거릴 일도 없고, 사용자가 요청한 timestamp 찾을 때도 퍼포먼스 향상도 꾀할 수 있으리라 생각합니다.

@BeautterLife
Copy link

BeautterLife commented Jul 25, 2023

@jaehyeonpy 님, 말씀하신 인덱스의 이점을 활용할 수 있는 환경은 어떤 환경인가요?
사용자의 요청을 고려하신 것은 동일 노드에서 새로운 python-mysql-replication 프로세스를 생성한다는 것으로 이해했는데, 멀티프로세싱 환경을 말씀하신 걸까요?

@dongwook-chan
Copy link

dongwook-chan commented Jul 26, 2023

@jaehyeonpy @BeautterLife
제가 모임 시간에 빈로그 파일명과 생성 시점 매핑 테이블 만들어서 데이터 채워넣는 솔루션을 말씀드렸던 것 같아요!
그 테이블에 인덱스 넣는 방안을 말씀주신걸까요??

@jaehyeonpy
Copy link

jaehyeonpy commented Jul 26, 2023

@BeautterLife, @dongwook-chan 님, 제가 의견을 제시하게 된 경위를 아래에 적어 두었습니다.


  • 문제상황
    • pymy replication은 가장 최근의 binlog file에서 이벤트를 취득하되, position 지정 않을 시 파일 처음부터 iterate 하여 이벤트를 취득한다.
    • 이 때, 가장 최근 binlog file에서 사용자가 원하는 시점 이후의 이벤트가 없을 시 불편하다.
    • 이전 binlog file 포함해서, 사용자가 원하는 timestamp 지정하면 그 이후의 이벤트를 취득할 수 있도록 해달라.
    • 사족입니다만, 일일히 Iterate 하여 지정한 timestamp 이후를 찾는 것도 worst case로는 O(n)인지라 이것도 개선 대상이 되지 않을까 싶습니다.

  • 해결책
    • 대용량의 binlog file 대상으로 사용자가 timestamp 지정해야 한다면, binlog file에 있는 이벤트를 미리 인덱싱 할 필요가 있을 것 같다.
    • 생성 시점 매핑 테이블 관련해서는 아직 제 이해가 부족한 것 같아 해결책으로는 적지 못하였습니다.

꼼꼼히 검토해서 해결책을 내놓은 것이 아니고 어디까지나 브레인스토밍 느낌으로다가 끄적인 것입니다.

많은 피드백 바랄게요!

@dongwook-chan
Copy link

@jaehyeonpy
좋은 의견 감사합니다.
많은 분들이 이진 트리(혹은 그에 준하는 시간 복잡도의 자료구조)에 저장하거나 이진 탐색하는 솔루션 말씀 주셨네요 :)
사용자가 입력한 timestamp에 상응하는 binlog 파일 혹은 이벤트를 O(logn)에 가져올 수 있으면 효율적일 것 같아요
혹시 구현도 해주시나요?

@jaehyeonpy
Copy link

jaehyeonpy commented Jul 27, 2023

@dongwook-chan

구현까지 할 의향 있습니다!

다만, 이 이슈의 우선순위를 얼마로 두어야 하는지에 대해 질문을 드리고 싶습니다.

과제 1번도 병행해야 하고, 좀 더 best practice 찾을 시간도 필요할 것 같고요(혹시 DBMS 단에서 공수를 줄일 수 있을 만한 방법을 제공하고 있지는 않을지, 여러 자료구조, 알고리즘 중 최선은 없을지), 다른 분들과도 논의를 할 기회도 있으면 더욱 좋을 것 같기도 해서요. 이러면 필연적으로 어느 정도 시간이 소요되리라 생각합니다.

만일 이 이슈가 급박하게 해결해야 하는 이슈라면, 위 내용 중 어느 정도는 스킵해가며 구현을 시도해 볼 수 있을 겁니다. 반대라면 꼼꼼하게 검토해 본 이후 구현에 들어가 볼 생각이고요.

말씀주신 사항도 감사히 참고 하겠습니다.

@dongwook-chan
Copy link

@jaehyeonpy
너무 좋은 의견이십니다. 👍🏻
급박한 이슈 아니니까 충분히 고민해보시고 구현해보시면 될 것 같아요.
여기서 논의 이어나가셔도 되고, 아젠다별로 논의를 구분하고 싶으시다면 아젠다별로 이슈 새로 파셔서 진행하시면 될 것 같아요.
혼자서 고민하시는 것보다 함께 고민하면 더 좋은 솔루션이 나올 수 있으니까 다른 분들이랑 의견도 나누시고 챗지피티한테도 여쭤보시면 좋을 것 같아요!

@jaehyeonpy
Copy link

jaehyeonpy commented Jul 27, 2023

@dongwook-chan

말씀 주신대로 본 이슈의 논의를 여기서 이어나가거나 따로 분리를 하거나 하겠습니다.

다른 분들도 편하게 논의 주시면 감사하겠습니다!

@jaehyeonpy
Copy link

@BeautterLife 님, @dongwook-chan 님과 의논해 본 결과,

우선 binary search 이용해 구현하고, 추후 여유가 되면 다른 방식을 시도해 보는 것으로 의견을 수렴하였습니다.

@ebang091
Copy link
Author

@jaehyeonpy님 넵 알겠습니다! 저도 구현해보고 싶은데 혹시 바로 구현하시나요..?

@jaehyeonpy
Copy link

@ebang091

당분간 다른 분들로부터 의견을 기다리다가 구현을 시작하는 것으로 의견을 교환 하였습니다만,

개인적인 생각으로는 바로 구현하시고 코드 리뷰를 신청하셔도 무방하리라 생각합니다.

@ebang091
Copy link
Author

@jaehyeonpy 아하 넵 알려주셔서 감사합니다 ㅎㅎ 저는 추후 시간이 날 때 구현 할 것 같아서 조율이 힘들 것 같아 여쭤봤습니다!
각자 코드를 구현하고 리뷰 하는 것 좋은 방법인 것 같습니다 말씀 감사합니다..!

@jaehyeonpy
Copy link

@dongwook-chan @BeautterLife @ebang091

이 이슈를 해결하기 위한 구현으로써 binary search 이용한 접근법이 아닌 다른 접근법을 사용해야 할 것 같습니다. pseudo code 수준에서 binary search 적용해 보려고 구상 중입니다만 어려움을 겪고 있습니다.

binlog file은 bytearray로 작성되어 있기에, 곧바로 binary search 사용해서 사용자가 지정한 특정 timestamp 찾고 그 이후 이벤트를 fetch 할 수 없습니다.

‘사용자가 지정한 특정 timestamp 찾고 그 이후 이벤트를 fetch 하기 위해 binary search 사용한다’ 라는 방식으로 본 이슈를 해결하겠다고 저번에 논의를 하긴 했지만, 이는 timestamp와 그에 대응하는 이벤트가 이미 binlog file에서 파싱되어 python object화 되어 있다는 ‘암묵적’ 전제를 부지불식간에 하고 있었기 때문이라고 생각이 듭니다.

그리고 사용자가 지정한 특정 timestamp 찾고 그 이후 이벤트를 fetch 할 목적으로, timestamp_pos = (start_pos_of_binlog_file + end_pos_of_binlog_file)/2 하는 등의 binary search 하는 방식도 적절하지 않을 것 같습니다. timestamp_pos 계산한 값이 timestamp 바이트가 있는 곳이 아니라, server id 가리키는 바이트라면 일이 번거로울 듯 합니다.


결국엔 binlog file이 bytearray 형식이기 때문에 문제 해결에 어려움을 겪고 있는 것이며, binlog file 파싱하여 python object 만들어야 할 것 같습니다. 따라서 다음의 방식을 제안합니다:

  • 지금까지 축적되어 있는 모든 binlog file의 내용을 파싱해 python object로 만든다.

    • 모든 binlog file의 timestamp, 이벤트를 iterate 하면서 파싱하고 python object화 한다. 이 때의 시간복잡도는 O(n)이다.
      • mysql에서 인덱스 생성 시 걸리는 시간이 크더라도 신경쓰지 않는 것처럼(일단 생성하여 두면 이후에 요긴하게 쓰이므로 trade-off 한다고 생각하여), 모든 binlog file의 timestamp, 이벤트를 파싱해 python object화 할 시 시간이 걸리더라도 신경쓰지 않는 것으로 한다.
    • 아니면 fetchone() 내에 파싱 및 python object화 로직을 추가하는 것도 가능하다.
  • 모든 binlog file의 내용을 파싱해 python object로 만들되, 리스트 자료구조를 활용하여 만든다.

    • 모든 binlog file 내 timestamp를 파싱하고 integer 형식으로 변환하여 5500 ~ 11000의 숫자를 얻었다면, python_object[5500~11000] = each_event_class 하는 식이 가능하다. 그러나 이는 메모리 낭비이며 python_object_name[(5500-5500) ~ (11000-5500)] = each_event_class 같은 방식으로 한다.
    • 7500이라는 integer 형식의 timestamp 이후 이벤트를 fetch 하는 것이라면, python_object[(7500-5500):] 하면 된다.
      • 특정 timestamp 이후 이벤트의 fetch는 O(1)으로 가능하므로, binary search 사용하지 않고 단순히 리스트를 사용하는 선에서 본 이슈 해결이 가능하다.
  • 리스트 외 다른 자료구조도 검토하여 본 결과 해시도 적합하다 생각했지만, 이 경우 hash function에 의한 collision 고려해야 한다.


이 때 우려되는 점은 다음과 같습니다:

  • 대규모 binlog file 파싱해 python object 만들 시, 할당될 메모리가 어느 정도일지 우려스럽다.

@dongwook-chan
Copy link

@jaehyeonpy
깊은 고민의 흔적이 보입니다.
binlog 파일의 내용은 binary가 맞고, 그렇기에 binlog 내 log position을 이진 탐색으로 찾기엔 어렵겠지요
하지만 어떤 binlog 파일을 읽을지 자체를 이진탐색으로 결정할 수는 없을까요?

@sean-k1
Copy link
Collaborator

sean-k1 commented Aug 11, 2023

@jaehyeonpy
저도 이 문제대해 조금 고민을해봤는데 저 보다 더 깊게 생각하셨겠지만... 제가 생각했던 방법 말씀드려봅니다.
굳이 모든 binlog 파일에있는것을 메모리에 담을 필요가 없다고생각합니다.
binlog의 공통적 항목 file description에 있는 timestamp를 가져올수있습니다.

binlog00001 file description timestamp
binlog00002 file description timestamp

이렇게 순서대로 가져올수있지않을까요? 이걸 key : pair로 timestamp,binlog file_name 로 담고
모든 binlog들의 timestamp들을 담은 배열 하나 만들어서 실제 원하는 타임스탬프값을 배열에서 이진탐색으로 적절한 위치를 찾은 후
해당 binlog 파일에 대한것만 메모리에 담아두면될거 같습니다.

@jaehyeonpy
Copy link

@dongwook-chan @sean-k1

제 방식보다 두 분께서 제시해 주신 방식이 훨씬 효율적이라 생각합니다. 의견 제시 감사합니다!

조만간 생각 다듬어서 다시 논의 올리겠습니다.

@yangbaechu
Copy link

@sean-k1님, @jaehyeonpy님과 예은님, 멘토님 의견 참고하여 의견 남겨보겠습니다.

file description에 있는 timestamp를 조회하는 시점에서, 원하는 time과 비교할 수 있기 때문에, timestamp 값을 저장한 배열을 만들어 이진 탐색을 수행하지 않아도 된다고 생각합니다!

이를 기반으로 간단하게 수도코드 생각해보았습니다.

  1. Show binary logs로 첫번째 파일명 읽어오기
  2. BinLogStreamReader에 logfile 인자로 읽어온 첫번째 파일명 전달해서 해당 파일부터 읽어오기
  3. only_events 로직 활용해 Binlog file에서 Format Description Event만 읽기
  4. skip_to_timestamp를 초과하는 TimeStamp가 있다면, 그 Binlog 전 파일에서 탐색 시작
  5. 해당 파일에서, Timestamp가 skip_to_timestamp 이후인 첫 이벤트 찾기

@jaehyeonpy
Copy link

잠시 다른 이슈 작업하다, 본 이슈 해결 위한 구체적인 코드 구상에 들어간 상태입니다. 제 방식에 대해 의견이 있으시면 말씀 주세요.

  • @yangbaechu 께서 제시한 해법은, binlog file 개수 적지만, 각각의 binlog file 내에 있는 이벤트 개수가 많은 상황을 전제하고 있는 것이 맞나요?

    • 일전에 서로 나눈 논의를 떠올려봐도 그렇고, @dongwook-chan 께서 프로덕션 환경에서는 그럴 것이라 말씀하시기도 했고, 무엇보다도 반대로 binlog file 개수 많고, 각각의 binlog file 내 이벤트 개수가 적은 경우를 전제하신 것이라면 제시하신 해법은 성능 측면에서 재고해야 하기 때문입니다.
  • 만일 그렇다면 제시하신 해법 중 5단계에서 확실히 해야할 것이 있습니다.

    • binlog file 내 이벤트는 바이트 형식으로 쓰여져 있어, 바이트를 읽어가며 각각 이벤트의 timestamp 확인해야 합니다. 찾아보니까 binlog file의 max file size가 1,073,741,824 바이트가(1GB) 된다고 합니다. 최악의 경우 1GB 가까이 바이트를 읽어야 할 수 있습니다.
    • 바이트를 읽어가는 것 외의 방식에 대해 나름 생각해 보았지만 해결을 보지 못했습니다.
  • 위의 바이트를 읽어가며 timestamp 확인하는 방식의 경우, 성능을 어느정도 포기해야 할 것 같은데 괜찮을까요?

    • C/C++ 기준으로 대개의 CPU는 1초에 몇 억회 연산이 가능하다고 합니다(책 '한권으로 합격하는 취업 코딩테스트' 10페이지에서 인용). 그러나 본 프로젝트는 python 사용 중이고, 정확히 몇 회의 연산이 필요한지 계산할 방도도 알지 못합니다.
    • Mysql/MariaDB에서 binlog file 가져올 때마다, COM_BINLOG_DUMP 이벤트를 Mysql/MariaDB에 보내고 받아오는 것을 반복하게 되는데 이 때의 network cost가 존재한다고 @dongwook-chan 께서 말씀하셨습니다.
      • 한번에 모든 binlog file 받아올 수도 있겠지만(혹은 소수의 횟수에 걸쳐 모든 binlog file 받아올 수 있겠지만), 그러려면 pymysql 프로젝트까지 넘어가서 작업해야 하는 것으로 보입니다. 정확하게는 python-mysql-replication 코드 중 이 작업과 관련된 코드를 볼 수 없었습니다. 그렇다면 pymysql에 관련 작업해 PR 날리는 공수가 생기는 등의 부담이 있습니다.

@sean-k1
Copy link
Collaborator

sean-k1 commented Sep 5, 2023

@jaehyeonpy 구현의 차이겠지만 모든 binlog event 를 다 읽을 필요가없습니다.
binlog file description 맨 앞의 이벤트만 읽고 패킷자체를 버리면됩니다. (time cost 절약)
물론 말씀하신것처럼 DB와의 network cost는 탈수밖에없는 구조로 보이네요.

binlog 000001 2023 -09-01-00(file description timestamp)
binlog 000002 2023- 09-03-00(file description timestamp)
binlog 000003 2023- 09-03-01(file description timestamp)
binlog 000004 2023- 09-04-00(file description timestamp)

만약 사용자가 원하는 시간이 2023-09-03-00-30 이라고 가정한다면
binlog 000001 의 timestamp는 전이기때문에 바로 패킷을 버립니다.
binlog 000002 도 timestamp가 전이기 때문에 패킷을 버립니다.
binlog 000003에서는 timestamp가 요청한시간 이후이기 때문에 다시 binlog000002의 이벤트를 모두 읽어 디테일한 timestamp비교 후 거기서부터 사용자에게 보여주면됩니다.

위에서 말씀드린것처럼 이 방법은 네트워크 비용이 많이들어가는 작업일거같습니다.
재기동시에 매번 가져오는 것을 방지하기위해서 binlog별 timestamp를 파일로 떨궈 두던가 redis 캐쉬를 사용해도 될것같긴합니다.

저도 다른 접근방법은 떠오르지않네요..!

@yangbaechu
Copy link

@jaehyeonpy
말씀하신 것처럼 binlog file 개수 적지만, 각각의 binlog file 내에 있는 이벤트 개수가 많은 상황을 생각하고 말씀드린 방식입니다!
저도 @sean-k1님의 의견처럼, 맨 앞의 이벤트의 timestamp만 읽고, 원하는 timestamp가 들어있는 binlog file을 찾는 방식을 생각했습니다.

네트워크 비용 측면에서는 캐시를 사용하면 좋을 것 같습니다. 다만 python-mysql-replication이 다시 실행될 때, 기존에 존재하던 binlog file이 Master에 그대로 저장되어 있다고 가정해도 되는걸까요? 만약 그렇지 않다면 캐시를 적용하기는 어려울 것 같습니다..!

@jaehyeonpy
Copy link

@sean-k1 @yangbaechu 님 의견 감사합니다!


주신 의견 찬찬히 잘 살펴 보며 제 의견을 다시 보았습니다. 두 분 의견은 제 의견과 거의 일치하지만 한 가지 다른 지점이 존재합니다. 제 저번 의견이 불명확해서 다시 한 번 더 정리해 보겠습니다.

만약 사용자가 원하는 시간이 2023-09-03-00-30 이라고 가정한다면
binlog 000001 의 timestamp는 전이기때문에 바로 패킷을 버립니다.
binlog 000002 도 timestamp가 전이기 때문에 패킷을 버립니다.
binlog 000003에서는 timestamp가 요청한시간 이후이기 때문에 다시 binlog000002의 이벤트를 모두 읽어 디테일한 timestamp비교 후 거기서부터 사용자에게 보여주면됩니다.

제가 말씀을 드리고 싶었던 것은, 'binlog000002의 이벤트를 모두 읽어 디테일한 timestamp비교 후 거기서부터 사용자에게 보여주면됩니다.' 라는 대목에서 time cost 발생 시 이를 감수할 의도가 있는 것이 맞느냐는 것입니다.

아시다시피 binlog file 개수는 적지만, 각각 binlog file 내 이벤트의 개수는 많은데, 파일 내 이벤트를 읽어가며 사용자가 원하는 timestamp 도달하는 것도 상당한 시간이 걸릴 수 있다는 점입니다.

이 지점을 제외하고 두 분 의견과 저의 의견이 일치합니다.


network cost 관련해서는 두 분 다 캐시 얘기를 주셨습니다. 제 생각에는 redis 등 이용한 캐시보다 파일을 이용한 캐시가 좋아 보입니다. binlog file 개수가 적어 file I/O 이용해 저장하고 불러올 FORMAT_DESCRIPTION_EVENT의 timestamp 개수도 적어 부담 얼마 없을 듯하고, 반면 redis 사용 시 이를 위한 테스트용 workflow 파일 작성 등 파일에 비해 추가적인 공수 부담이 생길 것 같습니다.

다만 python-mysql-replication이 다시 실행될 때, 기존에 존재하던 binlog file이 Master에 그대로 저장되어 있다고 가정해도 되는걸까요? 만약 그렇지 않다면 캐시를 적용하기는 어려울 것 같습니다..!

다만, 이 경우에 대해서는 추가적인 논의가 필요할 것 같습니다. 이 부분 관련해서 생각을 더 해보고 추가적으로 의견 드리도록 하겠습니다.


우선은 시간이 급하니 캐시 부분은 제외하고 구현을 진행해 보도록 하겠습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants