[코딩몰라여] steem-python으로 포스트와 댓글 실시간으로 읽어오기 기초 ~속도개선~ 편

in #kr-dev2 years ago (edited)

front3.png

아직도 알아야할게 있다니! 뭔지 모르겠지만 예외처리도 잘 따라했는데?

# 스팀잇에서 실시간 처리란?


  스팀잇에서 실시간이란 블록이 생성되는 주기, 3초에 해당합니다. 스팀잇에서 실시간 봇이란 최근 블록을 읽어서 필요한 행동을 다음 블록이 생성되기 전에 끝마치는 절차를 계속해서 반복하는 프로그램이라고 표현할 수 있습니다. 주어진 일을 3초마다 해내지 못하는 봇은 불완전합니다. 점점 일이 밀리게 될테니까요.

불완전한 봇에는 어떤 것이 있나요? 이런 봇이 있습니다.

result03.PNG

  실시간편 기초에서 계속 보여드렸던, kr 태그가 붙은 포스트와 댓글을 보여주는 프로그램입니다. 아주 간단한 프로그램이죠? 실행해보신 분들은 눈치 채셨을지 모르겠지만 이 봇은 불완전한 봇으로써 아주 지옥의 성능을 갖고 있습니다.

실행한 직후에는 현재 시간으로부터 약 1분 전의 글을 읽어오지만 처리 속도가 떨어져 10분만 지나도 2~3분이 밀리게 됩니다. 실행해둔 시간이 길어질 수록 '실시간' 과는 거리가 멀어집니다.

속도개선 편에선 이에 대해 자세히 살펴보고자 합니다.

gomen.jpg
저도 일상글 쓰고싶어요! 미안해요! 문과 친구들!

# Blockchain.py에 숨겨진, 잠드는 시간


07.PNG

의도하지 않았는데 부제목이 흑기사님이 쓰신 것 같다.

  Blockchain 클래스의 stream 함수에는 프로그램이 잠드는 시간이 포함되어있습니다. blockchain.py를 살펴보면 곳곳에서 time.sleep 이라는 코드를 통해 잠드는 부분이 있는 것을 확인하실 수 있습니다.

이는 실시간 스트림을 하면서 생성되지 않은 블록을 프로그램이 접근하지 않도록 여유 시간을 준 것이라고 판단합니다.

그렇다고 blockchain.py 에서 time.sleep을 전부 삭제하는 것은 안정성을 고려했을 때 별로 좋지 못한 선택이 됩니다. 그래서 Blockchain 클래스의 stream 함수를 대신하는 함수를 새로 만들기로 했습니다.

계속해서 작성되는 포스트와 댓글을 감시하려면 프로그램이 줄곧 블록을 읽어와야하는 것은 사실입니다. 하지만 모든 기록이 발생한 순간에 읽어야만 하는 것은 아닙니다.

왜냐면, 우리는 3초 단위의 세상에 살고 있으니까요. 그리고 그 단위를 블록이라고 부르지요. 그럼 블록이 생성될 때마다 읽어야할까요? 아니요. 딱히 그렇지만도 않을 것입니다. 생성될 때마다 읽을 수 있다면 좋지만, 딱히 그럴 필요는 없죠. 우리가 느끼기에 '충분히 빠르다.' 라고 생각할 정도의 속도만 가지고 있으면 된다고 생각했습니다.

lazy.gif

  블록 5개씩 읽어들이는 프로그램을 만들었다고 가정해볼게요. 블록이 5개 생성되는데엔 15초가 걸리니 5개의 블록을 읽어들이고, 원하는 기능에 따라 처리하는 과정을 15초 내에 전부 해내기만 하면 해당 프로그램은 안정성을 가지게 됩니다. 이렇게 계산을 미뤄두고 한 번에 처리하는 것은 느긋한 계산법(Lazy evaluation)에 포함되는 개념입니다.

스팀잇에서 일어나는 일에 대한 반응이 15초 안에 일어난다면 충분히 빠르다고 볼 수 있겠죠? 예를 들면 가이드독을 부르는 댓글을 달고나서 15초 후에 나타난다해도 우리는 충분히 빠르다고 생각합니다.

이렇게 굳이 계산을 미뤄뒀다 한 번에 처리하는 이유는 프로그램에서 느린 행위 중에 손꼽히는게 입출력(I/O)이기 때문에 마음의 준비를 하고 한 번에 처리하는게 더 빠르기 때문입니다. 설거지를 조금씩 하는 것보다 한 번에 하는게 더 빠른 것과 마찬가지죠. 세제도 적게 쓸 수 있습니다. :D

@tpdns90321 님의 이 포스트를 참고해서 b.stream을 대신할 함수를 아래와 같이 만들었습니다.

  • 블록 내용을 JSON으로 가져온다.
  • 블록을 가져올 땐 gevent 모듈을 이용하여 빠르게 가져온다.
  • 블록 내용에서 코멘트만 간추려내는 것도 다중 프로세싱(Multiprossing)으로 구성하여 속도를 높인다.

get_block 함수 내에 FalseTrue로 바꾸면 보상(Reward) 관련 활동만 가져올 수도 있습니다.

01.PNG

02.PNG

# 편리함의 댓가를 지독하게 치르게 만드는 Post


  이전까지는 stream = map(Post, b.stream(filter_by=['comment'])) 를 통해, 읽어들인 블록의 내용을 Post 객체로 바꾸어서 처리했습니다. Post 객체를 사용하면 코드의 가독성이 높아지고 각종 편리한 기능을 사용할 수 있다는 장점이 있습니다.

읽어들인 내용이 포스트(본문)인지, 댓글인지 판단하려면 is_main_post, is_comment 함수만 호출하면 바로 알려줍니다. 내용은 ['body']만 붙이면 바로 가져올 수 있습니다. 하지만 그 편리함에는 엄청난 문제가 있었으니, 블록의 내용을 Post 객체로 바꾸기만 해도 어마어마한 시간을 소모한다는 것입니다. 한 블록의 처리가 3초가 넘을 정도로 말이죠.

실험을 위한 메인 함수는 아래와 같습니다.

05.PNG

  1. 프로그램 실행 직후에 가장 최근의 블록 번호를 수집합니다.
  2. 7초 후(2 ~ 3블록 생성)에 가장 최근의 블록 번호를 수집합니다.
  3. 수집한 블록 번호에 해당하는 블록을 처리합니다.
  4. 수집한 블록을 처리하는 동안 새로운 블록이 생성되었을테니 생성된 블록을 처리하는 과정을 반복합니다.

## 1. Post 객체로 변환하지 않는 경우

03.PNG

## 2. Post 객체로 변환하는 경우

04.PNG

## 각 경우에 대한 처리해야할 블록 수 변화

06.PNG

  Post 객체를 사용하지 않는 경우엔 쌓여있는 블록을 처리하는 속도가 빨라 처리해야할 블록의 갯수가 줄어듭니다. 4회 이후로는 처리 속도가 훨씬 빨라서 다음 블록이 생성될 때까지 기다리고 있다가 처리합니다. 그러나 Post 객체를 사용하는 경우에는 별 다른 기능도 없이, Post 객체로 변환하는 것만으로도 처리가 버거워 점점 처리해야할 블록이 늘어납니다.

스팀잇과 관련된 프로그램을 만들 때 Post 객체를 사용하는 횟수를 최소한으로 줄여야합니다. Post 객체를 사용할 땐 정말 필요한지 고민하고 사용합시다. :D

그럼 Post 객체를 사용하지 않고 내용은 어떻게 확인할 수 있나요?
포스트를 보시면 됩니다. 구조가 똑같아요. (찡긋)

# 실시간 기초편은 진짜 끝? 제발... 저도 그러길 바라요.


original.gif

  이번 포스트의 내용은, 저로써는 참 오랫동안 해결하려고 노력했던 것입니다. 더 많은 내용을 안내해드리기 전에 제가 깨달아서, 알려드릴 수 있어서 다행입니다. 마아냐봇(@maanyabot)을 시작으로 실시간 처리를 위한 프로그램을 만들었는데 왠걸, 처리속도가 안나오는겁니다. 점점 처리해야 할 댓글은 많아지고 1시간이 지나면 20분이 밀리고, 2시간이 지나면 40분이 밀리더라고요.

이 문제를 해결하기 위해 제가 취한 긴급 조치는 봇이 30분만 동작하고 처리 못한 댓글들은 다 버리게 하는 것이었습니다. 그리고 봇을 두 개를 실행하는 것이죠. 15분 간격으로. 마아냐봇1 이 9시부터 동작하면, 마아냐봇2 는 9시 15분부터 동작하기 시작합니다.

봇1 은 9시 30분이 되면 그 때 처리할 수 있는 댓글까지만 처리하고(약 20분 치) 나머지는 다 버린 후에 새로 시작해서 9시 30분의 댓글부터 처리하는 것이죠.

봇2 는 9시 45분이 되면 마찬가지로 처리 못한 것은 다 버리고 새로 시작하는 방식으로 처리 속도에 대한 문제를 땜빵했습니다. 그러다보니 봇1과 봇2가 겹치는 시간대가 존재하고, 댓글이 두 개씩 작성하는(...) 현상이 생겼던 것입니다.

오랫동안 저를 괴롭힌 문제를 해결해서 기분이 좋기도 하고 허탈하기도 합니다. 급하게 만드느라 확인을 제대로 못했다고 변명하고 싶지만, 그런 것까지 확인할 정도로 실력이 좋지 않기 때문에... ;ㅂ;// Post를 사용할 때 코드가 더 이뻐지는 것도 맞거든요. 허허.

스캐머들은 계속해서 새로운 링크로 사람들을 낚으려고 하고 있고, 그러한 링크를 봇에 추가할 때마다 봇은 더욱 느려졌고, 약 2주 전부터는 도저히 제 기능을 못하는 상태가 됐습니다. 이제 고쳐줄 수 있어서 기쁩니다. 먼저 손을 들고 나선 이상, 져야 할 책임이 있기 때문이죠. 이 이야기는 코딩몰라여 시리즈와 어울리는 이야기는 아니군요 (...) 여기까지만.

커피가 땅깁니다. 커피 한 잔 마시면서 잠깐 스팀잇 둘러보다가 고치러 갈게요. 즐거운 하루 되세요. :D




이번 편에서 작성된 코드는 아래의 링크에서 다운로드 가능합니다.
https://github.com/maaanya/codingmolayo-steempy
코딩몰라여 시리즈는 아래의 사이트에서 모아볼 수 있습니다.
https://codingmola.herokuapp.com/

Sort:  

새글이 올라왔다는 알람이 와서 달려왔습니다. !!!!
요새 제가 고민하고 있는걸 정확히 찍어주셨네요
그런데...............
저 함수를 대체할 수 있다고 해주셨는데..
어떻게 써야 할지가 감이 안 잡힙니다.
본문 처음 심플한 봇에 적용을 한다면 어떻게 해야 할까요?
파이썬 생초보라... 감히 부탁 드립니다. ㅠㅠ
예시 py 하나 올려주시면 안될까요?

기존에 올린 예제에서 코멘트를 추출하는 부분이 종속적이었던 부분을 해소한 v2 예제를 업로드하였습니다.

본문 처음의 심플한 봇은 v2에서 filter_reply 함수 내에 and comment['op'][1]['parent_author'] != '' 조건을 제거해주세요. 그리고 main의 댓글 처리하는 봇의 조건을 그대로 붙여주시면 됩니다. 영화 시간 다 되가서 급하게 달고 갑니다. 으아앙ㅇ.... 미안해요! 나중에 다시 확인할게요!

V2 예제를 돌리면 아래와 같은 오래 메시지가 나옵니다.여기 이미지가 작아서 잘 보일려나 모르겠습니다.1.JPG

오래 기다리셨습니다. Google Compute Engine에서 작동시키기 위한 버전을 GitHub에 다시 추가했습니다. 두 가지 이슈가 있었는데,

하나는 올려주신 내용대로 urllib의 문제가 있었습니다. 검색해보니 GitHub에서 관련 내용 을 발견할 수 있었고 반영했습니다.

또 다른 문제는 multiprocess 부분에서 블록을 읽어서 가져온 메시지가 너무 길다며 오류가 났기 때문에 multiprocess 부분을 빼버렸습니다.

Gevent를 통해 블록을 가져오는 것과 Post() 변환 횟수만 없어도 충분한 속도가 보장이 되는 것을 확인하였기 때문에 그대로 사용하셔도 된다고 생각합니다.

와우 팽팽 돌아갑니다.

실행결과
{'trx_id': 'a6f8dc52a02d001c7320e1a4bfdb213f0b1ec362', 'block': 21896692, 'trx_in_block': 32, 'op_in_trx': 0, 'virtual_op': 0, 'timestamp': '2018-04-26T05:53:09', 'op': ['comment', {'parent_author': 'stella12', 'parent_permlink': '6av
lbb', 'author': 'xinnong', 'permlink': 're-stella12-6avlbb-20180426t055309752z', 'title': '', 'body': '그저께 블록체인 미디어 관련 강의에서 유니오 소개 듣고왔는데\n7일 보상이 무제한이 된다는 점이 가장 흥미롭더라고요 ㅎㅎ', 'json_metad
ata': '{"tags":["kr"],"app":"steemit/0.1"}'}]}

쭉쭉 나옵니다.
여기서 필요한 정보만 받아오고 싶습니다.
print(post["body"]) 처럼
print(reply["body"]) 이렇게 쓰면 에러가 자꾸 나오는데요..

print(post.export()['body']) 아니면 .. 처럼
print(export.export()['body']) 이렇게 써도 에러가 나오네요 ㅠㅠ

다 온거 같은데...

숟가락 까지 쥐게 해줬는데 먹여달라는 거 같은 느낌이네요
에휴... 궁금해서 죽어리겠습니다. ^^

print(reply[‘op’][1][‘body’]) 라고 쓰시면 나올거에요 :) 링크 타고 가셔서 get_account_history 구조를 그려둔거 한 번 보시면 원하는 데이터 뽑는 법을 이해하시는데 도움이 될거에요!

3.JPG
성공 했습니다. 그런데 문제가 있네요
한 10분 정도 돌고나면..
멈쳐버려요
4.JPG

조금더 고민해 봐야겠습니다.

노드를 api.steem.com으로 한 10분정도 켜두면 database lock 이러면서 에러 납니다.
그래서 노드를 바꿀려고 했죠
노드가 함수 안에도 있다는 걸 몰랐어요
두 군데를 모두 https://rpc.buildteam.io 로 바꾸고 나서
뻑 안납니다. !!!! 대박

댓글도 달고 싶은데..
post.reply를 여기서 쓰면 에러가 납니다. 이것도 해결 방법이 있을거 같은데 .. 시간내서 공부해 볼려구요

.reply 함수로 댓글을 달려면 포스트 객체로 변환해야합니다. 그럴 땐 permlink를 만들어서 Post 객체를 만드는게 좋겠죠.

while
try:

exception 

이거 쓰면 에러를 피해가려나 했는데..
계속 에러뜨고 멈쳐 있어요 ㅠ

@dailypro님이 만드시려는 것이 dailypro님 포스트에 @금손 이라는 댓글을 달면 댓글과 함께 보팅을 해주는 봇 맞죠? :)

정독 완료!!!
post 객채로 변환한다는 것과 아님의 차이를 알았습니다.
올려주신 코드로 실제 사용을 쭉하고 있는 결과
post 객체(댓글을 단다든지, 업보트)를 안하는 경우에는
댓글이 달리면 바로 보입니다. 한 2초..

사실 관찰하는것보다는 소통을 위해서는 댓글도 달고 보팅도 해야 하는데 말이죠.. ^^

이제 댓글을 달거나 업보트를 해야할 때만 Post 객체로 변환하시면 됩니다. :)

정확도와 속도는 프로그램의 생명이죠!

프로그램의 생명이 프로그래머의 생명도 만들죠.. 흑흑 ;ㅂ; 댓글 감사합니다.

오늘도 잘배우고 갑니다. 이따 노트북 켜서 꼼꼼히 다시 뜯어 봐야겠습니다.

언제든지 질문 편하게 해주세요 :)

와우~ 빨리 따라 해보고 싶네요.

감사합니다 ~ㅁ~ // 성공하시길 바라요!

그러니까... 언제 완성 되는거??

간절히 기도하시면 전 우주의 마아냐가 완성해줄 것

역시... 나중에 몰아서 봐야되요. 언젠간 제가 이해할 날이 있겠죠. ^^;

초보는 전혀 알 수 없는 시리즈가 되어가고 있습니다. 망했어요.

ㅋㅋㅋ 아무래도 그리 쉬운 것이라면 너도 나도 프로그래머 하고 있겠죠.
아시나요님 같은 분들은 도움이 크게 되지 않을까 싶어요. ^^

흑기사는 무슨 만화인지 모르겠네요. 내용은

흑기사는 @cagecorn님을 의미합니다. 스팀잇의 새벽 지킴이지만 지금은 마감에 시달리는 참한 웹툰 작가이시죠.

속도 문제는 1도 고민 못 해본 부분이었는데... 저는 별로 시간이 안 밀렸던 것 같은데 무슨 차이가 있는지, 마아냐님 덕분에 한 번 체크해봐야겠습니다 :)

Post 객체로 변환하는게 아주 많은 시간을 요구합니다 -_ㅠ

동시성 처리는 역시 go나 rust가 답인 것 같네요.
ps. 중간고사기도 해서 휴스팀잇하다가 삽질할 게임에 등장으로 글 쓰기도 힘드네요. 그리고 컴퓨터 맞춘다고 알바를 해야할 것 같아서 글쓰는 거랑 프로젝트들이 표류하고 있네요ㅠㅠ