10년 전에 Clojure로 짠 트위터 인용봇을 Elixir로 재작성한 후기

8 minute read

이제는 Clojure를 거의 안 쓴다. 요즘 많이 쓰고 있는 Elixir로 다시 구현해 볼까? 언제 이런 생각이 들었을까? 아마 Heroku에서 무료 인스턴스(dyno)를 중지한단 소식을 들었을 때였던 걸로 기억한다.

Starting October 26, 2022, we will begin deleting inactive accounts and associated storage for accounts that have been inactive for over a year. Starting November 28, 2022, we plan to stop offering free product plans and plan to start shutting down free dynos and data services. We will be sending out a series of email communications to affected users.

Heroku’s Next Chapter

그동안 무료로 잘 사용했다. 고마워 Heroku.

월 $5를 지불하며 Eco Dynos Plan을 구독해서 이전처럼 Heroku로 트위터 인용봇인 @book_quote_bot@bquote_bot을 서비스할 것인지. 아니면 무료로 인스턴스를 사용할 수 있는 서비스를 알아보고 이전할 것인지. 둘 중 하나를 선택해야 한다.

Heroku를 떠나 다른 서비스로 옮길 생각을 해보니 걸리는 게 있었다. 트위터 봇을 Clojure 언어로 짰는데, 이제는 사용하지 않는 언어라서 유지보수가 힘들다. 다른 서비스에서 Clojure 툴체인을 디폴트로 제공하면 다행인데, 그게 아니라면 직접 구성하거나 다른 사람이 만든 걸 찾아서 적용해야 한다.

더 이상 사용하지 않는 프로그래밍 언어 트러블슈팅으로 시간을 쓰기는 싫다. Heroku 무료 인스턴스가 사라졌다. 하지만 한 달에 $5는 부담되는 금액은 아니다. 2013년에 Clojure로 트위터 봇을 짰다. 벌써 10년 전이다. 사이드 프로젝트 주력 프로그래밍 언어인 Elixir로 다시 짜고 싶다. Heroku에서 서비스하는 건 유지하고 구현 언어를 바꾸자. 이후에 Heroku가 아닌 다른 서비스로 옮기는 걸 고려해 보자.

이후에 새로운 프로그래밍 언어를 배우게 된다면 제일 먼저 트위터 봇을 짜서 교체하는 것도 재미있겠단 생각을 했다.

그사이에 Twitter API 과금 모델이 변경

Elixir로 구현하는 중에 Twitter API 과금 모델이 변경된다는 소문이 들렸다.

Starting February 9, we will no longer support free access to the Twitter API, both v2 and v1.1. A paid basic tier will be available instead

Over the years, hundreds of millions of people have sent over a trillion Tweets, with billions more every week.

Twitter data are among the world’s most powerful data sets. We’re committed to enabling fast & comprehensive access so you can continue to build with us.

We’ll be back with more details on what you can expect next week.

XDevelopers - twitter.com

이제까지 Twitter API 무료 access를 사용하고 있었는데, 없어진다고 한다. 현재 트위터 봇은 2개가 있다. 1시간당 1개의 트윗을 한다. 하루에 24개 트윗을 하니깐 한 달이면 720개가 된다. 트위터 계정이 분리되어 있으니 720개를 공짜로 트윗할 수 있으면 계속 구현하고 만약 과금해야 하면 Elixir로 포팅도 멈추고 지금 돌아가고 있는 트위터 봇도 정지시켜야겠다.

우선순위가 뒤쳐져 Elixir로 재구현하는 작업이 지연되고 있다. 이 뉴스를 보니 좀 더 기다리면서 재구현 여부를 결정해야겠단 생각을 했다.

일주일 뒤에 공지가 떴다.

A new form of free access will be introduced as this is extremely important to our ecosystem – limited to Tweet creation of up to 1,500 Tweets per month for a single authenticated user token, including Login with Twitter.

XDevelopers - witter.com

한 달에 1,500개 트윗을 포스팅하는 건 무료로 된다고 한다. 충분하다. 다시 고고고.

GitHub template 생성

ohyecloudy/template-elixir 템플릿 저장소(repository)를 만들어서 사용했다. 템플릿 저장소가 나오기 전에 저장소 하나에 템플릿을 모아두고 참고해서 사용했다. 프로젝트 템플릿을 모아두는 github 저장소를 만들었다 - ohyecloudy.com 이런 글도 적었었는데, 이제 더 편한 템플릿 저장소가 나와서 적극적으로 사용해 볼 생각이다.

아키텍처 및 주요 로직

인용구 원본이 있고 그걸 tweet 제약에 맞춰 가공한다. 이걸 하나씩 Twitter API를 사용해 tweet 한다.

flowchart TD A([raw quotes]) B([refined quotes]) C([quote HTML pages]) D[tbot-800.ex] A-->D D-->B D-->C

인용구를 추가해서 GitHub에 push하면 GitHub Actions가 실행된다. 이때 인용구를 tweet할 수 있게 가공하는 작업을 한다. 전체 인용구는 HTML 페이지를 만들어서 GitHub Pages를 만든다.

HTML 페이지를 만드는 이유는 tweet 글자 제한이 있기 때문이다. 인용구가 길어 글자 제한에 걸릴 때는 GitHub Pages URL을 tweet에 포함해 전체 인용구를 볼 수 있는 링크를 제공한다.

140자가 넘는 긴 인용구를 예로 들어 설명해 보자.

실용주의 프로그래머는 무엇이 다른가? 우리는 태도와 스타일 그리고 문제와 해법에 접근하는 철학에 차이가 있다고 생각한다. 그들은 직면한 문제 너머를 생각하며, 문제를 항상 더 큰 맥락에 놓으려 노력하고, 항상 더 큰 그림을 보려 한다. 어쨌건 이런 더 큰 맥락 없이 도대체 어떻게 실용적일 수 있겠는가? 어떻게 똑똑한 절충안을 내고, 정확한 사실에 근거한 결정을 내릴 수 있겠는가? <실용주의 프로그래머>

전체 인용구를 담은 HTML 페이지를 만들어서 GitHub Pages로 호스팅한다. 위 예로 든 인용구는 다음 페이지에서 볼 수 있다. e4f1a20e6ec174b8f16d231bf9127da46ec873a8.html

이제 tweet 자르고 HTML URL을 추가해서 tweet 할 수 있는 길이로 만든다.

실용주의 프로그래머는 무엇이 다른가? 우리는 태도와 스타일 그리고 문제와 해법에 접근하는 철학에 차이가 있다고 생각한다. 그들은 직면한 문제 너머를 생각하며, 문제를 항상 더 큰 맥락에 놓으려 노력하고, 항상 더 큰 그림을 보려 한다. 어쨌건 이런 더 큰 맥락 없이… https://ohrepos.github.io/pquotes-repo/quotes/e4f1a20e6ec174b8f16d231bf9127da46ec873a8.html

이런 식으로 트윗들을 만들어 놓는다.

flowchart TD A([refined quotes]) B[tbot-800.ex] C[Twitter API] A-->B B-->C

런타임에는 이렇게 만들어 놓은 트윗을 하나씩 빼서 twitter API를 호출하면 된다.

tweet에 최대한 많은 정보를 담을 수 있게 tweet 길이를 최적화

이전에는 대충 계산해서 140자가 넘으면 100자까지 자르고 인용구 전체가 담긴 웹페이지 링크를 남겼다. 영어는 되게 길게 쓰는 것 같아서 찾아보고 최적화를 진행했다.

Twitter began as an SMS text-based service. This limited the original Tweet length to 140 characters (which was partly driven by the 160 character limit of SMS, with 20 characters reserved for commands and usernames). Over time as Twitter evolved, the maximum Tweet length grew to 280 characters - still short and brief, but enabling more expression.

Counting characters Docs Twitter Developer Platform - developer.twitter.com

뭐라. 280자라고? 하지만 이건 영문일 때고 한글은 다르게 계산한다. 정확히는 280byte까지 트윗할 수 있는데, 영문은 1byte로 계산하고 한글은 2byte로 계산한다.

latin-1 영역은 1byte로 계산하고 그 외 영역은 2byte로 계산해서 최대한 tweet에 많은 인용구가 담기게 최적화했다. URL은 주소 전체를 카운팅하는 걸까?

URLs: All URLs are wrapped in t.co links. This means a URL’s length is defined by the transformedURLLength parameter in the twitter-text configuration file. The current length of a URL in a Tweet is 23 characters, even if the length of the URL would normally be shorter.

Counting characters Docs Twitter Developer Platform - developer.twitter.com

무조건 23byte로 퉁친다. 이전에는 이걸 몰라서 여유 있게 40byte를 할당해서 100자까지만 트윗을 했다. 이것도 반영해 더 많은 인용구가 tweet에 담기게 했다.

출처 표기법 확인

인용구에 출처를 <출처> 형식으로 표시하고 있다. 이런 표기법이 맞는 건지 찾아봤다.

겹낫표(『』)와 겹화살괄호(≪ ≫)

  • 우리나라 최초의 민간 신문은 1896년에 창간된『독립신문』이다.
  • 『훈민정음』은 1997년에 유네스코 세계 기록 유산으로 지정되었다.
  • ≪한성순보≫는 우리나라 최초의 근대 신문이다.
  • 윤동주의 유고 시집인 ≪하늘과 바람과 별과 시≫에는 31편의 시가

겹낫표나 겹화살괄호 대신 큰따옴표를 쓸 수 있다

  • 우리나라 최초의 민간 신문은 1896년에 창간된 “독립신문”이다.
  • 윤동주의 유고 시집인 “하늘과 바람과 별과 시”에는 31편의 시가 실려 있다.

한국어 어문 규범 - kornorms.korean.go.kr

홑낫표(「 」)와 홑화살괄호(< >)

  • 이 곡은 베르디가 작곡한 「축배의 노래」이다.
  • 사무실 밖에 「해와 달」이라고 쓴 간판을 달았다.
  • <한강>은 사진집 ≪아름다운 땅≫에 실린 작품이다.
  • 백남준은 2005년에 <엄마>라는 작품을 선보였다.

[붙임] 홑낫표나 홑화살괄호 대신 작은따옴표를 쓸 수 있다.

  • 사무실 밖에 ’해와 달’이라고 쓴 간판을 달았다.
  • ’한강’은 사진집 “아름다운 땅”에 실린 작품이다.

한국어 어문 규범 - kornorms.korean.go.kr

책의 제목이나 신문 이름을 쓸 때는 겹화살괄호(≪ ≫)를 쓰고 소제목, 그림이나 노래와 같은 예술 작품의 제목, 상호, 법률, 규정 등은 홑화살괄호(< >)를 쓰면 된다. 구분이 까다롭다.

한편, 간혹 홑낫표(또는 홑화살괄호나 작은따옴표)와 겹낫표(또는 겹화살괄호나 큰따옴표) 중에서 어느 것을 써야 할지 구분하기가 어려울 때가 있는데, 이때는 홑낫표(또는 홑화살괄호나 작은따옴표)를 우선 선택하면 된다.

한국어 어문 규범 - kornorms.korean.go.kr

구분하기가 어려울 때는 홑화살괄호를 쓰면 된다고 하니 <출처> 이걸로 대충 때우자.

ExTwitter 라이브러리를 쓰지 않고 직접 구현

twitter 라이브러리가 몇 개 없다. 대세인 것도 안 보인다. 개중에 가장 많이 다운로드한 ExTwitter 라이브러리를 사용하고 있었다. 테스트 tweet이 잘 되는 걸 확인하고 다른 작업을 진행했다. 이제 실제 tweet이 되는지 확인하려고 하는데, 이전에 잘 동작했던 tweet이 안 된다. 그 사이에 twitter 무료 access를 없앤다고 했다가 무료로 1,500개까지 tweet 가능하다는 공지가 나왔다.

뭔가 불안하다. ExTwitter 라이브러리가 이후에 twitter API 변동 사항이 생기면 잘 대응해 줄까? 지금 twitter 1.1 API 상태가 deprecated인데, 언제 없어질지 모르겠다. 이참에 twitter 2 API를 사용하게 고치고 복잡한 게 들어가는 게 아니라 단순히 트윗만 하면 되니깐 직접 구현한다.

간단하니깐 Hackney를 사용해 HTTP request를 보내려고 했는데, 익숙하지 않으니 트러블슈팅을 제대로 못 하겠다. 거창하더라도 익숙한 Tesla 라이브러리를 사용해서 구현했다. 권한 문제가 생겨서 확인해 보니 twitter API 토큰 권한이 readonly로 되어 있었다. 찾아보고 read and write 권한으로 바꿔서 해결했다.

인용구 빌드에 GitHub Actions를 사용

10년 전에 Clojure로 트위터 인용봇을 구현할 때는 Travis-CI가 핫했다. 이제는 GitHub에 GitHub Actions라는 CI/CD 대세가 됐다. 써보니 굳이 다른 서비스를 사용할 필요가 있겠나 싶다.

지금 생각하면 GitHub에 없다고 CI/CD 툴을 비즈니스 모델로 삼는 건 꽤 위험 부담이 큰일이다. GitHub이 만들면 바로 뒤집어질 수 있기 때문이다. GitHub이 CI/CD 툴을 만들어도 그걸 이길 수 있는 툴을 만들어서 서비스해야 하는데, 참 어려운 일이다.

Application Layering

Application Layering - A Pattern for Extensible Elixir Application Design 글에서 본 패턴을 따라 해 봤다. 뭔가 복잡고 귀찮은데 규칙이 있어서 따라 하다 보니 적응이 됐다. 이 패턴을 발전시켜서 계속 사용하고 싶다.

이 내용에 대해서는 별도로 포스팅할 계획이다.

아직 Heroku를 사용하고 있다

뭐 그동안 Heroku 무료 인스턴스를 잘 썼으니 유료로 좀 써주자. 누가 Heroku를 왜 쓰냐고 물어보면 이렇게 대답한다. 사실은 진행하던 다른 사이드 프로젝트를 잠시 멈추고 Clojure로 짠 트위터 인용봇을 Elixir로 다시 짠 거라 에너지가 고갈됐다.

하던 걸 마무리하고 다시 돌아와 다른 곳으로 갈 준비를 할 생각이다. 한 달에 $5 정도가 나가는데, 그리 부담되는 금액은 아니다. 시간과 집중에 좀 더 우선순위를 둬서 결정한다.

마치며

Elixir로 만든 tbot-800.ex 리포지토리 주소는 ohyecloudy/tbot-800.ex이다

하염없이 길어지는 걸 어떻게든 막으려고 했다. “Done is better than perfect”를 항상 되뇌었다.

Clojure로 만든 이전 프로젝트인 ohyecloudy/tbot-800, ohyecloudy/tbot-quote-builder를 archive 했다. 그리고 Elixir로 만든 ohyecloudy/tbot-800.ex 프로젝트를 공개로 바꿨다.

그리고 그런 생각을 했다. 만약 이후에 내 사이드 프로젝트 언어가 바뀌면 바뀐 언어로 인용구 트윗 봇을 만들면 어떻겠냐는 생각이 들었다. 언어를 배우면 구현해 볼 좋은 프로젝트. 그리고 익숙한 프로젝트가 생겨서 기쁘다.

프로젝트를 진행하며 배우다

링크