2018년 12월 26일 수요일

여기에 압축 풀기 기능에서 한글 파일 깨짐

zip 압축파일에 한글 파일명이 포함되어 있을 때 zip -F 명령을 거치는 방법은 예전에 봤었고, unzip -O cp949 식으로 옵션을 줄 수 있는 건 새로 알았다.

그놈 기본 파일 관리자인 노틸러스를 그냥 쓰고 있는데, 압축파일을 우클릭하면 나오는 "여기에 압축 풀기" 항목으로는 여전히 한글이 깨진 채로 압축이 풀린다. 이 기능을 쓰면 얼마 전까지도 file-roller 창이 그냥 떴던 것 같은데 언젠가부터 노틸러스 창 안쪽에 동그라미로 진행률이 표시되다가 끝나는 방식으로 바뀌었다. 그래서 아마 nautilus 자체 기능으로 뭔가 들어갔겠거니 싶어서 찾아보았다.

apt source nautilus 명령으로소스를 받아온 다음 grep Extract Here -ri로 전체를 찾아보았다. nautilus-files-view.c 파일에 이것저것 있다. extract_here로도 찾아보니 action_extract_here, extract_files 같은 그럴듯한 함수들이 나온다. 타고 들어가보면 nautilus_file_operations_extract_files 함수가 노틸러스에 진행률이 표시되도록 연결하는 부분이고 마지막에 언급되는 extract_task_thread_func라는 함수가 실제로 압축 해제를 진행한다.

압축 해제 기능은 gnome-autoar 패키지에 의존한다. libgnome-autoar-0-0 패키지가 있고, apt remove 시도해보니 nautilus 패키지도 같이 제거될 거라고 나온다. https://github.com/GNOME/gnome-autoar/tree/master/gnome-autoar 여기에 소스가 있다.

autoar_extractor_do_sanitize_pathname 함수를 거쳐 autoar_common_get_utf8_pathname 함수를 보면 UTF-8 파일명이 아닌 경우에 3가지 인코딩을 하드코딩해두고 차례로 변환을 시도한다. 이 과정에서 EUC-KR 한글 파일명이 깨진 문자열로 바뀌는 걸 텐데, 현재 소스코드에서는 환경변수를 참조하는 등의 열린 구조가 아니어서 바로 해결은 안 되겠다. G_FILENAME_ENCODING 같은 게 아직 쓰이나 모르겠네.

2018년 12월 23일 일요일

사용자가 업로드한 파일을 즉시 CDN 주소로 제공하는 방법 구상

사용자가 방금 업로드한 파일을 즉시 사용자에게 다시 보여줘야 하는 경우에 CDN을 어떤 구성으로 적용할지 궁리를 해왔다. 사용자에게 항상 CDN으로 파일을 제공한다는 전제로 구성하다 보면 사용자가 자체 서버로 업로드한 파일을 CDN으로 다시 배포하는 시간만큼 사용자에게 제공하는 시간이 지연된다는 부분에서 늘 막혔다. 사용자-자체서버-외부저장소-CDN 순서로 전송이 이루어지다보면 파일의 크기에 비례해 2~3배 만큼의 지연이 발생하는 구조가 되는 거였다.


그래서 이런 시나리오를 생각해봤다.
  1. 사용자는 자체 서버로 파일을 업로드를 한다. (트래픽 1차)
  2. 자체 서버에서는 직접 파일을 서빙할 수 있는 모든 준비를 마친다.
  3. 자체 서버는 미리 설정된 외부 저장소에 0바이트 크기로 blob을 만들고, 해당 blob에 custom header를 설정해서 Location 헤더가 앞서 준비한 자체 파일 서빙 URL을 가리키도록 한다.
  4. 자체 서버는 미리 설정된 CDN URL을 사용자에게 제공한다.
  5. 서버가 제공한 CDN URL에 사용자가 접속한다.
  6. CDN은 Location 헤더로 자체 서버의 URL로 다시 이동한다.
  7. 사용자는 CDN URL에서 redirection된 자체 서버의 URL로 실제 파일을 제공받는다.
  8. 그 사이 서버는 외부 저장소에 앞서 생성한 blob의 본문과 custom header를 실제 파일 내용으로 교체한다. (트래픽 2차)
  9. blob에 교체되면 CDN에서는 적당한 시점에 재배포된다. (트래픽 3차)
  10. 외부 저장소에 CDN 배포에 반영된 후에는 사용자의 접속으로 CDN이 직접 파일을 제공한다.
  11. 자체 서버는 백업용 혹은 CDN으로 제공할 수 없는 경우에 사용한다.

AWS로 중요 부분을 구현해보면 이렇다. 수동으로 했지만 아마 API로도 될 것이다.
  1. S3 버킷을 만들고 static website 기능을 켠다.
  2. 빈 파일을 하나 만들어서 blob으로 올리고 Website-Redirect-Location 헤더를 적당한 다른 URL로 지정한다.
  3. 해당 S3 blob에 static website URL로 접속했을 때 Location 헤더가 붙어오는 걸 확인한다. 그냥 blob 자체의 URL로 접속하면 x-amz-website-redirect-location 헤더로 나온다.
  4. CloudFront 배포를 하나 만들고 필요한 설정을 한다.
  5. CF의 Origin은 설정화면에서 제공하는 S3 항목을 그대로 선택하면 안되고, S3 static website 활성화 후에 생성된 도메인을 적어준다. (https://stackoverflow.com/a/41132075/6629746 참조)
  6. 해당 blob을 CF 배포의 URL을 통해서 접속했을 때 location 헤더가 붙어오는 걸 확인한다.

GCP나 Azure는 확인하지 않았지만 대체로 비슷한 수준의 지원을 할 것으로 예상한다.

아예 파일을 외부 저장소로 직접 업로드하는 방법도 있다.
FORM 내의 INPUT 항목에 (보통이라면 request header로 포함했을) 인증값과 저장할 blob 이름, custom header 등을 지정해서 S3에 직접 POST를 날리면 된다고 한다. 이쪽이 당연히 간편하긴 하겠는데, 제공하는 파일이 전체공개가 아니어서 외부 CDN으로 배포하지 않아야 하는 경우를 처리할 수가 없어서 깊게 검토하지 않았었다. 전체공개 파일을 아예 분리해서 다루는 방식이라면 채택할만하겠다.

2018년 12월 21일 금요일

나이스페이먼츠 모바일 연동방식 개선 - 아임포트 공지 기준으로

2018-10-17 날짜로 "[공지] 나이스페이먼츠 모바일 연동방식 변경 안내"라는 제목의 메일이 아임포트에서 왔었다. 아임포트를 검토만 했을 뿐 직접 쓰고 있지는 않았기 때문에 미뤄뒀다가 나중에 내용을 자세히 읽어봤는데 이미 2016년경부터 적용한 방식이었다. 메일 자체는 온라인에 기록된 건 없는 것 같고 github에 해당 안내가 있다. "niceMobileV2"라고 찾아보면 나온다.

의외로 경력자 중에 결제 연동 경험이 없는 경우도 많았던 것 같고, 나도 개념을 까먹기 전에 기록해두기 위해, 아임포트의 공지가 어떤 내용이고 어떤 문제를 해결하는 것인지 짤막하게나마 정리를 해보려고 한다. 아래 내용은 나이스페이 기준으로 설명하는 것이지만 모바일 환경이기 때문에 발생하는 문제라서 꼭 어느 PG사에 국한되지는 않을 것이라 생각한다. (대충 기억하기로는 이니페이도 비슷한 방식이었다)

나이스페이 결제는 기본적으로 인증요청-인증결과응답-승인요청-승인응답 순서로 진행된다. 카드번호를 넣거나 카드사별 간편결제 절차를 진행하는 것이 인증요청에 해당하고, 인증 정보를 가지고 다시 서버가 PG사와 통신해서 실제로 결제를 일으키는 것이 승인요청이다. 승인까지 진행되면 '결제가 되었다'고 할 수 있고 카드사에서 문자나 푸시로 결제가 발생했음을 알려준다. (이 시점에 받은 승인응답을 DB 같은 자체 기록으로 남겨두면 된다)

PC 결제에서는 iframe 혹은 다른 방식으로 웹페이지 안에 다른 화면을 표시하는 것이 화면 구성상 크게 무리가 없기 때문에 위의 인증 및 승인 절차를 서비스의 URL을 벗어나지 않고도 진행할 수 있다.

하지만 모바일 결제는 얘기가 다르다. 나이스페이는 인증요청 시작 단계부터 서비스를 벗어나서 나이스페이 URL로 화면이 완전히 바뀌어야 하고, 그 안에서 카드사 간편결제 앱이라도 호출하면 현재의 브라우저 혹은 앱 조차도 벗어나게 된다. 따라서 결제 정보가 어디에서 어디로 가는지, 그때마다 화면은 어떻게 나오는지 신경을 써야 한다. 다시 돌아오는 것조차도 난관이다.

모바일 환경에서도 인증 및 승인 절차는 당연히 필요하고 사용자가 보면서 인증을 진행하는 건 바뀔 구석이 없다. 승인 절차가 어떻게 되는지가 문제다. 나이스페이의 모바일 결제에서 승인은 두 갈래로 나뉜다.
  1. 하나는 승인요청을 나이스페이가 미리 처리한 다음 승인응답 결과만을 서버로 직접 건내주는 방식이다. 그 뒤에 나이스페이는 사용자를 처음 인증요청할 때 지정했던 URL로 돌아가라고 보낸다. 돌아온 URL에서는 (아까 서버가 받은 승인응답 결과에 따라 처리가 끝난) 완료  화면을 보여주게 된다.
  2. 다른 하나는 PC와 동일하게 사용자가 돌아온 시점에서야 승인요청을 진행하는 것이다. 아임포트가 공지한 niceMobileV2: true 옵션은 이 방식을 따르도록 만든다.

첫번째 방식대로 진행이 되었을 때 생기는 문제는 이렇다. 모바일 결제에서 사용자가 인증을 마친 뒤에 화면이 완전히 바뀔 때까지 서버는 반드시 승인요청과 승인응답 및 그 기록 절차를 마쳐야 한다. 그런데 여러가지 이유로 가끔 이 전제가 지켜지지 못할 때가 있다. 이러면 이미 인증을 마쳤고 나이스페이가 카드사와의 승인 절차도 끝내서 카드결제 문자는 받았으니 사용자는 다 잘 된 걸로 아는데, 정작 서비스는 결제가 안된 상태로 남아있게 된다. 그렇게 누락된 결제건이 있다는 사실 자체도 모를 수 있다. 항의 전화가 들어올 차례다.

두번째 방식은 첫번째 방식에서 생기는 누락을 완전히 없앤다. 사용자가 다시 돌아오지 못해서 결제가 승인되지 못하면 그냥 그뿐이다. 서비스의 전환률이 낮아질지언정 (이건 고치면 된다) 고객 불만과 불필요한 환불 업무는 생기지 않는다. 덤으로 PC 결제에서 쓰는 코드를 그대로 가져다 쓸 수 있다는 장점도 있다. 해피 엔딩.

2018년 12월 20일 목요일

본 대출자는 00세 남성으로 공공기관에 재직 중입니다-로 시작하는 투자 상품 설명을 읽으며

나는 소위 재테크라고 하는 분야에 밝지 못하다. 투자 성향 진단을 하면 보수적인 타입으로 나온다. 손에 쥔 것도 없고 손에서 놓아내는 걸 겁내기도 하고. 그래서 경험치를 높이고자 은행에서 대출도 소액으로 해보았고, 약간의 여유자금이 생기고부터는 예적금도 만들고, CMA도 이것저것 만들고 P2P 투자도 해보았다. 증권도 소액으로 몇 번 사고 팔고 하면서 주식 시세라는 것이 어떻게 흘러가는지 대강은 체감하고 있다.

P2P 투자도 업체가 많고 아직은 원숙기라고 할만한 시장은 아니니 몇 군데 업체에 소액으로 투자를 했었다. 렌딧이 이모저모로 귀에 많이 들리던 업체라 가장 먼저 찾았고, 부동산 투자를 주로 한다는 업체도 기웃거리다가 그 중에 하나를 골라서 두 번 투자금을 넣어보았다. (토스가 한 때 스스로 대부업체 이름을 달려고 했다가 황급히 발을 뺀 뒤에 서비스에 연결시킨) 8퍼센트도 써보았다.

8퍼센트는 내 기준에는 웹사이트나 웹서비스 구성이 탐탁치 않아서 (초기 가입 때 주는 포인트가 써먹기에 유명무실한 느낌이라 별로라고 판단했던 걸로 기억한다. 웹사이트가 좀 가벼운 느낌이고 그래프 곡률 표시가 정확하지 않은 것도 한 이유였다) 딱 한 번 10만원의 투자를 했다. 1만원씩 10개의 채권을 사들였다.

상품명 "0000호 개인자금", 이것이 현재 8퍼센트에서 사들였던 10개 채권 중 유일하게 남은 채권이다. 8퍼센트가 표기한 등급은 A-, 10개 중 두번째로 높고, 12개월동안 원리금균등 25%, 월이자지급 75% 방식으로 상환할 계획이었다. 2회의 연체를 포함한 10회의 지급, 2500원을 끝으로 이 대출자는 개인회생을 신청하였고, 3주 정도 뒤에는 "개인회생금지명령"이라는 분류의 내역이 추가되었다. 그로부터 시일이 꽤 지났음에도 8퍼센트가 별다른 변제계획안을 제시하지 않고 있으니 아마도 이 채권의 남은 금액은 물 건너간 것일 게다.

그래서 나는 이 "투자 상품"을 "자세히 보기"하였다. 이 대출자는 30대 남성으로, 12년 가까이 공기업 혹은 공무원에 속하는 직장에 정규직으로 일했다. 월 평균 소득도 나보다 훨씬 높았다. 한 달에 카드를 쓰는 금액은 나와 비슷했다. (카드는 신용정보사에 집계되지 않는 경우도 있다고 알고 있으니 완전히 신뢰할 수 있는 정보는 아니다) 그리고 빚이 9천만원 있었다. 8퍼센트가 표시하는 부채 분류 중 "은행/보험/학자금"으로 잡혀 있으니 나이나 규모를 생각하면 학자금은 아닐 것이고 대충 주택 관련 1금융권 대출이 아니었을까 싶다. 신용등급은 KCB 기준 560점 정도로 낮게 유지되다가 심사 4개월 전부터 아래로 출렁거리더니 어쩐 일인지 심사 시점에는 급하게 올라 832점을 기록했다. 이 대출자는 8퍼센트를 통해 4천만원을 빌렸다. 그 뒤는 나도 안다. 비교를 위해 10개 채권 중 첫번째였던 A 등급의 다른 상품도 같이 보았다. 다른 대출자는 30대 여성으로, 월 소득은 더 많고 더 오래 재직했다. 은행 빚도 비슷하게 9천6백만원 있었다. 신용등급은 앞서와 달리 꾸준히 높았다. 5천만원의 대출금은 특이사항 없이 모두 상환되었다.

12년째 공기업에 일하던 30대 남성은 왜 나보다 많이 벌고 카드 소비도 많지 않고 주거도 안정적이었을 것 같은데 굳이 금융권도 아닌 P2P 대부업체까지 와서 대출을 일으키고 변제를 마치지 못한 채 개인회생 제도를 찾게 되었을까? 나이가 많은 건 아니니 본인이 아프거나 한 것은 아니었을 테고, 본인이 사고를 당했다면 대출을 일으키는 것부터가 힘들었을 테니, 어쩌면 부모나 다른 가족이 급환이었던 걸까? P2P 업체에 오기 전까지 금융권은 이미 돌만큼 돌았을 테고 (그래서 몇 달간 신용점수가 떨어졌을 것이다) 실제 부채는 더 많았겠지. 애초에 잡혀있던 9천만원의 부채도 추심이 들어갔을 것이다.

주변에 P2P 투자를 언급할 때 쉽게 표현하기 위해 "쩐주에게 돈을 댄다"고 해왔다. 표현이 좀 속되지만 의미가 다르지는 않으리라 생각한다. 실제로 연체가 발생했다거나 하는 통지가 오면 '내 돈 내놔' 싶은 심정이 되기도 했다. 하지만 마음이 편하지는 않다. 지금도 P2P 업체의 "투자상품" 목록에는 "개인자금"이니 "대환대출"이니 "사업자금"이니 하는 구분으로 여러 줄이 나열된다. 상품이란 이름이 붙어있지만 그 한 줄마다의 너머에는 사람이 있고, 그 사람들은 모두 각자 열심히 살다가 힘든 일이 있어 여기까지 온 것이다. 자본주의 사회에서 힘든 일이란 결국 돈의 문제 아니겠는가.

나는 아직은 다행스럽게도 큰 돈이 녹아날만한 일이 없어서, 마음 편하게 기껏해야 은행 이율보다 높은 이율을 받을 데가 어디 없나 하는 정도로 투자라는 걸 하고 있다. 하지만 언제 어떻게 될지 모르는 일이다. 나보다 더 좋은 직장에 더 오래 일하면서 수입도 더 많던 사람도 빚쟁이가 되어서 낯모르는 사람에게 추측의 대상이 되고 있으니.

2018년 10월 21일 일요일

지메일에 새 메일이 오면 확인해서 후처리하기

메일 받는 것들 중에 사람이 읽어서 끝날 게 아니라 뒷처리를 해야 하는 메일이 있다.
매번 수동으로 하다가, 이걸 그냥 코드로 하나 짜면 어떨까 싶어서 찾아봤다.

크게 나눠서, 메일이 왔는지 파악하는 단계와 그 메일을 가져다가 내용에 맞게 처리하는 단계가 있겠다.

우선 메일이 왔는지 파악하는 건 API가 있다. https://developers.google.com/gmail/api/guides/push
내용을 요약하면, 메일함에  변동이 있을 때 알림을 쏘는 위치가 있으니 그 알림을 받기 위한 채널을 설정하고 변동이 있다고 알림을 받으면 실제 변동이 무엇인지 확인하는 절차를 진행하라는 거다.
알림을 받겠다고 한 번 등록하면 한 주 동안 유지되고 계속 등록을 갱신해줘야 하는 구조인 건 특이하다. 서비스 규모가 크니까 알림도 무한정 유지해줄 수 없겠지. (ᅟGCP 웹 콘솔에서 구독을 만들 때는 무한정의 구독도 만들 수 있긴 하다)

알림 채널은 Cloud Pub/Sub API라는 이름인데 RabbitMQ를 써본 입장에서는 그냥 큐가 서비스 형태로 제공된다고 이해된다. Topic이나 Subscription이라고 용어만 다를 뿐.

알림이 어떤 식으로 오는지도 문서에 적혀있다. URL로 푸시를 받는 방식이라면 참고하면 되겠다.
https://cloud.google.com/pubsub/docs/quickstart-console 에서는 gcloud 명령을 통해 pull로 가져오는 방법도 적혀 있다. https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.subscriptions/pull 에는 API로도 설명이 있다.

https://console.cloud.google.com/project/_/cloudpubsub/topicList 콘솔 화면에서 topic을 만들 수도 있고, 지메일 계정에서 푸시를 게시할 수 있게 권한을 줄 수도 있다. (gcloud 명령으로도 될 것 같긴 하다)
구독 만드는 것도 구독 화면에서는 바로 안 되고, 토픽을 만들고 나서 거기에 대해 구독을 추가 메뉴를 선택하면 된다.


알림을 받은 후에 메일함 내용을 확인하는 절차는 대강의 흐름을 보면 이런 것 같다.
  1. https://developers.google.com/gmail/api/v1/reference/users/history/list 로 알림을 받은 실제 변경이 뭔지 목록으로 추려낸다.
    • https://developers.google.com/apis-explorer/#search/gmail.users.history.list/m/gmail/v1/gmail.users.history.list?userId=me&historyTypes=messageAdded&_h=2& 에서 gmail.users.history.list API를 실행해볼 수 있다.
    • historyId가 필수로 있어야 한다. gmail.users.messages.list 결과에서 하나 가져와보면 잘 동작한다.
  2. https://developers.google.com/gmail/api/v1/reference/users/messages/get 로 list에서 가져온 id의 실제 내용을 가져온다.
  3. https://developers.google.com/gmail/api/v1/reference/users/messages#resource 는 내용의 구성 설명인데, payload.body가 실제 본문이다.
  4. https://developers.google.com/gmail/api/v1/reference/users/messages/attachments#resource 는 payload.body의 설명인데, 그냥 본문이 들어가 있거나 multipart 메일인 경우에 별도로 가져올 수 있는 첨부의 id가 있다.

여기까지 문서를 검토해보니 귀찮은 절차들이 좀 있다. 누군가 구현해두지 않았을까?

인증은, 생각해보다는 간단했다. https://developers.google.com/gmail/api/auth/web-server 에서 안내하는 "Create credentials > Service account key" 부분을 보고, '서비스 계정 키' 종류를 하나 만들어서 json 파일로 받은 다음에 credentials.json라고 파일명을 바꿔서 샘플 코드에 먹여보니 잘 돌아간다.

2018년 10월 6일 토요일

불꽃놀이 하는 날에는


서울시에서 불꽃놀이 행사를 연다고 홍보하던 첫 해였을 거다. 그날 나는 약속이 있어서 한강다리를 건너야 했다. 버스를 타고 한강철교를 지나는데 버스에 걸인이 타서 구걸을 돌기 시작했다. 그래서 나는 수중에 있는 천 원짜리 몇 장을 다 꺼내서 건냈다. 한강물 위로 불꽃이 펑펑 터지는 밤을 뒤로 하고.

그렇게 나는 불꽃놀이를 모순적인 행사로 기억하게 되었다. 싫어하게 되었다.

2018년 서울에 불꽃이 다시 터지는 날이다. 행사 참석 때문에 저녁까지 강 건너에 있던 나는 지하철을 타고 강을 건너왔다. 지하철 역에서 승강장으로 내려가는 계단 끝에 이상한 장면을 보았다. 허술하지만 등산복이라고 할만한 차림에 배낭까지 맨 나이 있는 남자가 쓰레기통을 뒤적거리고 있었다. 일회용 플라스틱 컵을 들어서 쓰레기통 위로 줄지어 세우고 잡지로 보이는 종이뭉치도 들어 펼쳐보는데 술주정 특이하다 싶어서 그냥 지나쳤다.

하지만 그게 아니었다. 전차를 기다리는 동안 어깨 넘어로 지켜본 그 남자는 컵에 든 음료수를 차곡차곡 모으고 있었다. 중간중간 한 모금씩 마시기도 했다. 모을만큼 모았다 싶었는지 그 남자는 곧 불편해보이는 걸음을 옮겼다.

오늘 또 하나의 이미지가 새겨졌다. 나는 여전히 불꽃놀이가 싫다.

2018년 9월 4일 화요일

Be My Eyes

https://www.bemyeyes.com/

시각장애인과 정안인을 연결해준다는 취지의 서비스이자 폰에 설치되는 앱.

이런 게 있다는 소식을 들은지는 오래 되었는데 제대로 통화를 한 적은 여태 고작 두 번이다. 처음엔 영어, 신발인가 옷인가 색을 봐달라는 게 첫 통화였는데 그때는 영어 쓰는 사람이기도 하고 용건도 간단해서 통화가 금방 끝났다.

약간의 뿌듯함을 기억하고 다음 통화를 기다렸지만 서비스 초반이라 그런지 아니면 워낙 서비스 원산지랑 멀리 떨어져서 그런지 연락이 잘 들어오지 않았다

그리고 한참 시간이 지나서 연락이 들어왔는데, 전화기를 들고 헬로 여보세요를 몇 번이나 해도 저쪽에는 내 목소리가 들리질 않는지 반응이 없었다. 이상하다 싶었는데, 그 뒤로 두번쯤 그런 일이 더 있었다. 전화기를 바꿨는데 아무래도 (아이폰에는 용도별로 여러 마이크가 있다고 하던데) 영역별 마이크 중에 이 앱이 쓰는 마이크가 고장인 듯 했다. 시리를 불러도 인식을 못 하고, 행복한소리라는 앱에서 목소리 녹음을 해도 모기 소리보다 작게 녹음되는 걸 보면 짐작이 틀린 것 같지 않았다.

그래서 한동안은 연락이 들어와도 받질 않았다. 이어폰을 쓰지 않고서는 통화가 안 되는데 평소에 이어폰을 잘 들고다니질 않기 때문이었다.

그러다 방금 연락이 들어왔다. 마침 집이었고 마침 이어폰이 눈 앞에 있었다. 받아보니 화면이 검게만 나와서 통신상태가 안 좋은가 하고 인사를 해보았더니 목소리는 잘 들렸다. 약간 왜곡되는 목소리이긴 했지만 전화통화도 상태 안 좋을 때는 그 정도는 하니까.

용건은 쇼핑몰 사이트에서 비밀번호를 바꾸고 싶다는 것. 처음에는 얼김에 잘못 알아들어서 비밀번호 찾기 화면으로 들어갔는데, 이미 계정과 비밀번호는 알고 있는 데서 비밀번호를 바꾸고 싶다는 거여서 절차를 밟아갔다. 사실 이런 걸 대신 해줘도 되나 싶은 생각이 안 든 건 아니었는데, 기왕에 계정이며 비밀번호를 알고 있고, 휴대폰 본인인증 문자도 불러줄만큼(문자 불러주는 목소리가 어딘가 어색했는데 기계음이었던 걸까?) 상황은 되었으니 별 문제는 안 되지 않을까 생각했다.

용무가 끝나고 통화를 마칠 때는 서로 허허 웃으면서 끝났다. 다음에 혹시 연락이 닿으면 또 통화하자면서.

그래도, 혹시 정말 진짜 모르니까, 이로써 내가 어떤 나쁜짓에 연루되었다면 그건 나의 호의와  멍청함에서 비롯한 것임을 밝혀둔다.

--
20190120 통화가 들어온 알림을 의도치 않게 눌러서 급하게 이어폰을 찾아 꽂았다. 그 사이 상대방의 화면과 음성은 정상인 걸 확인했었고. 근데 갑자기 통화가 끝났다. 화면 안내에 뜬 문장은 정확히 기억은 안 나지만 상대방이 끊었다는 의미로 기억한다. 아마도 부부가 전화해서 여성쪽이 '자기'라 불린 남성한테 나랑 얘기하라고 넘기는 과정에서 (여기까지 들었다) 터치를 잘못 해서 끊기지 않았을까 싶은데, 정안인도 자주 하는 실수니까 정말 시각장애인이면 그러기가 매우 쉬울 테지. 잠깐 기다려봤지만 같은 호출이 다시 들어오진 않았는데 부디 다른 사람과 연결되어서 용건이 잘 해결되었기를 빈다. 잠깐 본 화면으로는 통신사 모뎀 같은 게 보였으니 아마 선 연결 문제가 아니었을까 싶은데.

2018년 9월 1일 토요일

로봇청소기의 귀소기능을 장애인 보조기구에

2018년 드라마 라이프 1화에 장애인이 집으로 돌아와 자기 한 몸 누이는 장면이 나왔다. 여러 물건이 있어야 할 자리로 정돈되는 게 다른 동거인의 손을 거쳐서 이루어졌다. 그래서 그런 생각이 들었다.

로봇청소기가 제 자리를 찾아가듯이 휠체어 같은 보조기구들이 제 자리를 찾아가면 좋겠다.

하지만 이내 부질없는 생각이다 싶어졌다. 휠체어가 자유로이 움직이려면 문턱이 없어야 하고 적당한 행동반경이 보장될만큼 생활공간이 넉넉해야 한다. 배리어 프리하면서 충분한 공간을 지닌 주거환경이 흔한 게 아니지.

장애인이 멀쩡한 직업을 가지고 정기적인 수입이 있는 것도 드라마에서나 나올법한 이야기리라. 잘은 몰라도 취업은커녕 혼자를 건사하는 것도 힘들어 보조인의 도움을 받아야 하는 경우가 대부분이겠지. 그리고 그 보조인은 가족구성원이기 십상일 테고. 둘이 벌어도 모자랄 시대에 둘이서 한 사람 몫의 경제활동도 하기 힘든 상황이니 형편이 좋을 리 없고. 넓직하고 매끈한 집안이라는 게 과연 현실에서도 가능할까. 제자리를 찾아갈 수 있는 전동 휠체어란 또 얼마나 비싼 물건이겠는가.

그래도 여전히 소위 ‘스마트’ 기기가 장애인 보조기구의 세계에 접목되는 건 좋겠다는 생각이 든다. 일전에 이동이 힘든 장애인이 집에 불이 났는데도 피하지 못하고 그대로 죽었다. 어떤 식으로든 그런 죽음은 없어야 하지 않겠는가.

2018년 8월 6일 월요일

내가 왓챠플레이를 만든다면 - 개인별 예고편

내가 왓챠플레이를 만든다면, 특히 예고편을 만든다면 어떤 단계를 거쳐야 할까 좀 더 생각해 보았다.

일단 영상 전체에서 각 장면을 구분해야 된다. 장면이 구분되고 나서야 그 장면이 어떤 성격이고, 사용자 취향에 맞는지 어떤지 판단할 수 있다.
장면을 정의한다면, 영상이란 프레임의 연속이고, 각 프레임마다 분석해서 프레임의 성격이 크게 바뀌는 시점마다 나누었을 때 그 조각들을 장면이라고 할 수 있다.

가장 먼저, 프레임을 나누어야 한다. 간단하게 ffmpeg 명령을 써서 동영상을 이미지 파일로 저장했다. (https://www.wired.com/2014/09/cinema-is-evolving/ 기사에 따르면 영어권 영화에서 한 장면은 평균 12초 길이에서 2.5초 길이로 바뀌었다고 한다. 이미지 파일은 짧게 잡아도 2초당 한 번이면 충분할 것이다)
$ ffmpeg -i 동영상파일 -r 1 (혹은 -vf fps=1/2) -f image2 image-%4d.jpeg
저장된 이미지들을 분석해서 어떤 성격인지 구분하는 건 tensorflow의 이미지 인식 튜토리얼에 있는 classify_image.py를 써보았다.
$ pip3 install tensorflow # tf 미설치 상태라면
$ git clone https://github.com/tensorflow/models
$ cd models/tutorials/image/imagenet/
$ for f in image*.jpeg; do logfile=output.$(basename $f); python3 classify_image.py --image_file=$f > $logfile; done

사실 tensorflow보다 먼저 떠오른 건 Google의 Vision API였지만, 분석할 갯수가 아주 많은데 외부 API를 쓴다는 건 시간과 비용면에서 도저히 선택할 수 없는 방법이어서 접었다. tensorflow는 희미한 개념만 알 뿐 잘 다뤄본 적이 없어서, 아마 결과물의 품질이나 신뢰도가 높지는 않을 거라고 생각하지만, 일단 샘플이니까.

한참 돌아가고 나면 output.* 파일에 아래와 같이 그 이미지의 속성이 분석된 걸 확인할 수 있다.
$ cat output.image-1000.jpeg
sewing machine (score = 0.59451)
restaurant, eating house, eating place, eatery (score = 0.04678)
tank, army tank, armored combat vehicle, armoured combat vehicle (score = 0.02917)
desk (score = 0.02453)
dining table, board (score = 0.01770)
$ cat output.image-1713.jpeg
sock (score = 0.22062)
sandal (score = 0.17339)
clog, geta, patten, sabot (score = 0.15232)
shoe shop, shoe-shop, shoe store (score = 0.01656)
Loafer (score = 0.01599)

개별 파일로 뒀더니 눈에 잘 들어오지 않기도 하고, 2차원으로 가공하기 위한 전 단계로, 한 파일로 합쳤다.

for f in output.*; do N=$(echo $f | sed 's/[^0-9]//g'); cat $f | grep -v "^$" | sed 's/\(.\+\) (score = \(.\+\))/\2\t\1/' | sed "s/^/$N\t/"; done > linear.data


이제 이 값들을 시간순으로 비교해서 연속된 장면인지 아닌지 판단할 차례인데, 어떤 방법을 써야 좋을지 잘 모르겠다. 처음부터 비지도 학습으로 막 계산만 하는 건 적절하지 않으니 초반에는 지도 학습을 위해 프레임이 장면으로 분절되는 걸 사람이 인식할 수 있는 형태로 보여줘야 할 텐데, 그러려면 시각화 방법 중에 어떤 걸 써야 하는 걸까? 아마도 항상 100% 높이를 가지면서 구성요소의 비율을 보여주는 걸 1차원으로 죽 나열하는 게 좋을 것 같다. (percent stack area chart 정도의 명칭인 것 같다) 변곡점이나 분포가 크게 바뀌는 지점을 기준으로 분절을 나누면 되겠지. score가 너무 낮은 건 그 프레임이 어떤 장면인지 파악할 때 영향이 낮게 계산되어야 할 텐데 얼마나 낮아야 낮은 건지도 사람이 개입해야 하는 지점일 것 같다.

시간축과 그 시간마다의 속성 배분을 2차원으로 나열해서 그래프로 그리면 되겠지 싶었다. 문제는 그래프로 그렸을 때 보기 쉽게 하려면 나름대로 연속과 단절이 눈에 띄도록 해야 될 텐데 손으로 직접 짜기엔 마땅히 떠오르는 방법이 없다. 무슨 그래프를 그리면 되는지 정확히 안다면 그런 그래프를 지원하는 도구를 찾으면 되겠지만 그것도 아니고.

어쩌면 word2vec 정도를 가져와서 돌려보면 뭔가 결과가 나올 것 같긴 한데, 이것도 개념만 대충 알고 다뤄본 적은 없어서 잘 모르겠다.
그러고보니 예전에 이런 비슷한 구현체 소식을 들은 것도 같은데 뭐였더라. https://github.com/ryanjay0/miles-deep 이건 것 같다. 포르노 장면마다 무슨 장면인지 구분해주는 물건. 뭘로 학습을 시키면 나오는 모델이려나. 2차 시료인 텍스트를 매개로 구분하는 것보다는 영상 자체를 구분하는 게 훨씬 좋긴 하겠다.
왜 진작 검색을 안해봤나 모르겠네. https://blog.coast.ai/five-video-classification-methods-implemented-in-keras-and-tensorflow-99cad29cc0b5 이런 게 있다. 프레임마다 나눠서 자료를 준비하는 건 같고 그 중에서 학습 자료와 검증 자료를 둬서 모델을 만드는 게 다르다. 가고 있는 방향은 맞다는 얘기라고 생각해도 되려나.

2018년 7월 19일 목요일

인터넷과 또래 집단의 확장

인터넷에 떠도는 각종 유행 자료를 보면 경향이 너무 쏠려있다. 남녀 유별하게 격리되어 있는데다 혈기방장하고 넘치는 에너지를 주체 못하는 10대 남자놈들이나 보고 낄낄댈만한, 살색 가득하거나 어처구니 없는 장면들, 혹은 그런 장면을 연출하는 자신들.

또래 집단(peer group)이란 개념이 있다. 학교 같이 동일 연령대의 집단이 모여있을 때 그 집단 안에서 서로 영향력을 미치고 그게 어느 정도 강제성을 띄는(peer pressure) 현상을 설명하는 데 쓰인다. 직장 내의 집단도 언급되는 걸 보면 꼭 연령만으로 구분하지는 않나보지만, 그동안 사회 안에서 집단은 대체로 유사한 연령대로 구성되었으므로 연령으로 구분한다고 생각해도 무방하다고 본다.

인터넷은 그 태생부터가 읽기와 쓰기를 통해 자료가 오가는 것을 기본으로 한다. 온라인 커뮤니티라는 이름 아래 읽기와 쓰기를 빈번히 하는 일군의 집단은 자연히 집단으로서 동질성이 늘어난다. 그만큼 peer pressure도 커진다.

그리고 인터넷은 또한 거의 제한없는 접근성을 기본으로 한다. 더군다나 대다수 온라인 커뮤니티는 규모를 키울수록 운영주체에게 좋기 때문에, 잘만 모이면 어디까지든 커지려고 하는 게 속성이다.

이렇다 보니, 온라인 커뮤니티는 말초적인 취향을 즐기는 거대한 집단을 만들어냈다. 그 안에서 어린 것들은 되바라지고, 나이 먹은 것들은 유치해진다.

오늘 이런 생각이 들었다.

추가.
https://twitter.com/garyvee/status/1041299670935511040 영상에 잠깐 나오는 youthification란 말이 일맥상통하는 것 같아서 더 찾아보니 글이 나왔다. https://www.linkedin.com/pulse/20140108220327-10486099-youthification-it-s-a-word-now 길어진 글에서는 맥락이 좀 다르게 느껴지긴 한다. 가령 요즘은 나이에 0.8을 곱해야 기존 사회의 나이대가 된다는 얘기도 생각나고, 온라인 활동이 활발하고 시장 영향력이 더 있다고 여겨지는 젊은이들의 의견이 과대표되다보니 다른 나이대의 활동도 (시장이 제공하는 거의 유일한 선택지인) 젊은이들의 공간 안으로 편입된다고 해석할 여지도 있다.

2018년 7월 10일 화요일

Re: 8년째 같은 제품을 만들고 있습니다

"8년째 같은 제품을 만들고 있습니다"

이래저래 머릿속이 복잡하고 한번쯤 회고를 해야겠다는 생각이 들던 차에, 나 또한 같은 시간을 지나왔기 때문에 그 제목을 따올만한 글이 보여서 내 나름의 지난 일들을 정리해보고자 한다.

1. 해온 일
  • 한 온라인 서비스의 미약하던 시절부터 개발팀이라는 이름의 조직에 속해 그 서비스의 눈에 보이는 것과 눈에 보이지 않는 것 사이의 대부분에 손을 댔다. IE6의 CSS 핵을 열심히도 찾아다니던 때가 있었는가 하면, MySQL의 특정 버전에서 어떤 쿼리를 쓸 수 있고 쓸 수 없는지 실험하기도 했고, nginx와 haproxy, keepalived라는 이름이 어떤 역할을 하는지 뒤적거리기도 했다. AWS니 Azure니 하는 것들을 좇아 몇 가지를 서비스에 도입하기도 했다.
  • 한 때는 2인 팀의 팀원이기도 했고, 또 한 때는 1인 팀처럼 지낸 적도 있었고, 팀장이라는 직함이 붙은 적도 있었다. 팀장과 팀원이라는 역할에 대해 고민해볼 수 있는 시간이었다. 팀장으로서의 나는 이를 테면 '어디선가 누군가에 무슨 일이 생기면 틀림없이 나타난다 홍반장' 정도였다는 평을 들었었다. 내가 원하던 방식과도 결이 같은 평가라고 생각한다.
    • 팀장일 때에 내가 생각한 가장 큰 책임은 제 시간에 팀원들을 퇴근시키는 것이었다. 나름의 소신 때문이기도 했고, 서비스 초반에 뻔질나게 했던 나의 야근들 때문이기도 했다.
  • 회사에 사람이 적을 때는 맞은 편 책상으로 걸려온 전화가 어떤 내용인지에 따라 미리 필요한 자료를 찾아서 던져주기도 했다. 서로 유기적으로 돌아가고, 서로 불필요한 수고를 덜 할수록 좋다고 생각해서 그랬던 거지만, 사람이 적다고 할 수는 없는 상황이 되고 팀제가 강해지고 역할이 나뉘면서 계속 그렇게 하기는 어려워졌다. 대신 자주 손이 가는 작업은 백오피스 도구를 제공하고, 혹시 작은 개선으로 일을 줄일 수 있는지 종종 물어보는 것으로 대신했다.

2. 다뤄본 것
  • CentOS 6.x를 썼다.
    • CentOS 7이 나왔을 때 여러 변화가 있었다고 듣기는 했지만 그 변화들이 장점으로 다가오기엔 변화의 폭이 너무 크게 느껴졌다. ifconfig와 netstat가 구식이 된 것 만큼은 손에 아직 완전히 익지가 않았다.
    • 기왕에 알짜 리눅스 때부터 쓰기도 했고 우분투를 계속 최신 버전으로 올려가며 쓰기 때문에 심각하고 전문적인 수준이 아니면 OS 범위에서 크게 어색한 적은 없었다.
  • 아파치를 썼다.
    • 하지만 아파치를 집요하게 파고들었다고는 할 수 없다. 웹서버 튜닝에 시간을 들여서 얻을 수 있는 소득보다는 다른 걸 하는 게 나은 나날들이 이어졌다.
    • 시간이 좀 지난 뒤에는 PHP CLI를 쓰는 방식을 부분적으로 도입해버렸고, 오히려 PHP-FPM은 제대로 써본 적이 없었다.
  • MySQL은 대체로 흔한 수준의 경험이었다. 여전히 DB에 일가견이 있다고 할 수준은 못 되다 보니 매번 아쉽다.
    • replication을 이런저런 면에서 만지작거려야 하는 시점들이 있었다. 클러스터 같은 구성은 경험해본 바가 없었다. replication도 확장이 쉽다거나 한 건 아니다보니 TiDB 같은 시도가 궁금하긴 하다.
    • PHP에 mysqlnd_ms라는 걸 붙여서 replication 구조 하에서 master/slave를 번거롭지 않게 다루는 방법을 도입했었고, 그 전에 직접 양쪽 연결을 모두 관리하던 것에 비해서는 아주 쓸만한 방식이라고 생각한다. 의외로 다른 언어 다른 프레임워크에서는 잘 다뤄지지 않는 영역인 것 같아서 아쉽다.
    • mysqlnd_ms의 다른 기능으로 slave들의 consistency를 처리해주는 건 여건상 정착시키지 못하고 haproxy로 slave를 하나로 묶는 선에서 끝이 났다. mysqlnd_ms 기본 코드 자체에 버그가 있어서 정상인(=차이가 0인) slave를 무시하는 문제도 있었고, slave의 상태를 확인하기 위해서 SUPER 권한이 필요하기도 했던 것으로 기억한다.
    • SQL 쿼리를 작성하는 방식으로 ORM도 쿼리 빌더도 써봤지만 아무래도 완성된 쿼리를 가지고 있는 게 좋다고 생각한다. DB 포팅을 고려한다고 하지만, 대부분 환경에서 DB 전체를 갈아치우는 일이 얼마나 생긴다고. 그보다는 완성된 쿼리를 가지고 있고 그걸로 성능을 높이고 병목을 없앨 수 있는 가능성을 키우는 게 좋다고 생각하게 됐다.
  • PHP를 쓰면서, 버전마다 새로 도입되는 기능들을 활용하기 위해 노력했다. 본래 설계 목적과는 좀 다를 것 같지만 trait는 초기부터 살이 붙어 비대해진 코드를 쪼갤 때 잘 써먹었다.  generator도 메모리 잠식이 우려되는 부분에서 간간히 쓰곤 한다.
    • PhpStorm을 한동안 썼지만 그리 제대로 썼다고 할 수는 없다.
    • Visual Studio Code를 써봤는데 (phpstorm 라이센스도 오래 됐고, editorconfig 플러그인에 제대로 동작을 안 해서였다) 구미에 잘 맞았다. 설정 몇 개 조정하고, 플러그인 몇 개 깔아주니까 쓰기 편하다.
  • RabbitMQ를 도입해, PHP에서 비동기식 처리가 필요할 때 아예 하나의 task로 큐에 등록하는 방식을 썼다. 꼭 RabbitMQ가 아니라 DB를 써도 되었겠지만 읽고 쓰기의 유연함이나 확장성, 우선순위 적용 같은 걸 생각하면 올바른 선택이었다고 생각한다.
    • 당시에 여러 *MQ를 겉핥기로 찾아봤고, 그나마 RabbitMQ가 OpenStack에서 쓰인다는 점 때문에 믿을만한 것 같아서 선택했다. 초기 도입한 버전에서 몇 가지 문제를 겪긴 했다. :P
    • 차라리 ReactPHP 같은 걸 진작에 도입해버렸으면 어땠을까 싶기도 하지만, 그것도 나름대로 여러가지 문제가 있었겠지.
  • 이메일 대량 발송을 매주 해야 해서, 어쩌다보니 RabbitMQ 기반으로 여러 대의 발송 서버를 구축하는 데까지 갔다.
    • 대체로 쓸만하지만 자동화할 여지가 있긴 하다. 작성 단계가 민감한 수작업의 성격을 완전히 벗지 못했고, 메일의 특성상 이따금 문제가 생길 때가 있어서 발송도 사람 손을 완전히 안 타지는 않는다.
    • DNS에 SPF니 뭐니 이런저런 것들을 설정하는 건 지금도 남한테 설명해줄 만큼 정확히는 모르겠다.
  • AWS는 익숙해질만큼 오래 쓰진 않았다.
    • Route 53은 개인 도메인을 옮겨간 상태다. DNSever를 잘 썼지만 AWS의 각종 기능에 잘 녹이려면 어쩔 수가 없었다.
    • S3 + CloudFront는 미투데이 백업 html을 올려서 아주 가끔 보곤 한다.
  • Azure. 애증의 MS. 몇몇 부분에서 요긴하게 쓰고는 있지만 개인적으로는 쓰고 싶지 않다고 매번 투덜거린다.
    • S3 + CloudFront에 대응하는 역할로 쓰고 있다.
    • Azure Function은 웹서버로 접속이 한 번에 확 몰릴 때를 대비하기 위해서 도입했다. 처음에는 도입만 했지 제대로 구성을 못해놔서 한바탕 곤란을 겪고, 그 뒤에 Consumption Plan이라는 걸 썼어야 한다는 걸 배웠다. 지금 구성도 아주 깔끔하다고 생각하지는 않지만 개념 자체는 복잡하지 않기 때문에 앞으로도 원래 의도했던 역할은 잘 해줄 거라고 본다.
    • Azure Function이 대외적으로는 PHP나 Node.js를 지원한다고 선전하지만 실제로는 런타임 적재를 하느라 생기는 것 같은 초기 지연이 상당하다. 단순히 콜드 스타트 문제라고 하기엔 양상이 달랐던 걸로 기억한다. 콜드 스타트를 방지하기 위해 Consumption Plan이 아니라 App Service Plan을 써야 한다는 얘기도 있던데 필요한 방향이랑 안 맞아서 채택할 수 없었다.
  • C#은 맛만 봤지만 느낌이 좋았다.
    • Azure Function 동네에선 역시 C#이 1등 시민이다. 덕분에 PHP와 Node.js로 짠 코드를 C#으로 똑같이 다시 짜는 경험을 해야 했다.
    • 그 뒤로 PHP와 달리 타입이 전제되는 언어에 호감이 생겼다. 익숙하지 않은 Azure Function 환경에서 PHP로 짰을 때는 어디선가 실제 서비스에서 오류가 터지고 나서야 놓친 부분이 있는 걸 알았지만, C#으로 짰을 때는 빌드 단계에서부터 오류가 보였기 때문에 실제 서비스에는 문제를 내보내지 않을 수 있었다. Application Insights라는 Azure의 모니터링 도구에도 오류가 거의 없다시피 하다. 그 전에는 드문드문 잡히는 오류에 이유를 짐작할 수도 없어서 그냥 PHP 구동 문제 아니면 네트워크 통신 문제이려나 짐작만 할 뿐이었는데.
  • React를 도입했다.
    • ReactDOM 모듈이 분화되기 직전의 버전으로 시작했었는데, 구버전 IE를 지원하기 위함이기도 했지만, JSX 렌더링을 Node가 아니라 PHP의 v8js 모듈로 처리하다 보니 굳이 React 버전을 올려가면서 쓸만한 상황까지 되지 않았다. v8 엔진 자체의 발전은 소문이 들려오면 종종 구경거리 삼아 보곤 한다.
    • 눈에 보이는 영역이고 다양한 상태가 존재한다면 JSX를 쓰는 게 직관적이라고 생각한다. jQuery로 이미 그려진 DOM을 바꾸기보다는 한 방향으로만 흘러서 DOM을 만들어내는 게 머리도 덜 복잡하다. JSX를 도입하지 않은 부분에서도 JSX처럼 '주어진 조건에 맞게 그려내기만 하는' 방식을 쓰기도 했다.
    • 덕분에 함수형 프로그래밍이라는 것에도 호감이 있다.
    • 다만 업무 담당자가 분화될 때 JSX라는 게 진입장벽으로 작용해서 아쉽다. 흔히 눈에 보이는 것을 다룬다면 HTML5에 CSS3 정도면 전문적이라고 여겨지는데, 거기에 자바스크립트라는 생소한 물건을 들이밀면 그게 아무리 HTML처럼 생겼다 할지라도 동작 방식이 쉽사리 전달되지는 않았다. v8js를 채택한 게 이런 상황을 더 나쁘게 만들었던 것 같기도 하다. 차라리 흔한 Node 환경이었으면 적응하기는 낫지 않았을까 싶다.
  • PG사를 옮겨봤다.
    • 이니페이, 나이스페이, 그리고 검토만 했던 여러가지들.
    • 아임포트 같은 중간 서비스가 왜 존재하는지 너무나도 잘 알 것만 같다.
      • 가끔 아임포트에서 오는 메일 같은 걸 보면 진작에 자체적으로 처리한 내용이 있기도 하다. 2018.10. 쯤에 모바일 결제 방식이 v2로 바뀌었다는 메일이 왔는데, 내용을 보니 나이스페이에서 모바일 결제의 구조상 너무 취약한 부분이 있어서 2년쯤 전에 채택한  방식이었다.
    • 매번 '일단 되게'하는 데 급급하다 보니 내재한 논리는 설명해야만 알 수 있고 코드 수준에서 정리는 거의 안 되어 있지만. 간단한 라이브러리 수준까지능 정리를 해봤지만, 독립적인 결제 서비스 수준의 고도화는 필요하겠다는 생각까지만 하고 손대지는 않았다.
    • 거래 고유번호(!)가 겹칠 수도 있고, PG사도 부하가 걸리면 새벽에 통보없이 자사 IP를 변경할 수 있다는 것도 알게 되었다.
  • Google Tag Manager를 도입했다.
    • 어느 정도 서비스가 궤도에 오르고 나서는 서비스의 현황을 파악하는 것도 일이었는데, 이런저런 분석 도구를 서비스에 붙이는 게 빈번해지고, 어떤 조건에서 어느 시점이 측정되어야 하는지도 점점 복잡해졌기 때문에 이 부분을 해소할 도구를 찾다가 ᅟGTM이 적당해서 서비스에 붙였다.
    • Google Analytics를 비롯해 이런저런 분석 도구를 상당한 자유도로 엮어넣을 수 있어서 좋다.
    • 하지만 역시 학습곡선이 가파르다. 그냥 기본 사용법대로 쓰는 수준을 벗어나기 시작하면, HTML 페이지의 렌더링이 어떤 방식으로 일어나는지 알아야 딱 맞는 설정을 할 수 있는 식이다 보니 아무래도 '측정은 해야 하지만 기술과는 거리가 있는' 직군의 사람들에게 친근하게 다가갈만한 물건은 못 되는 것 같다. 그래도 SuperTag라는 GTM 유사 제품의 소개 행사에 마케팅 관련 인원이 관심을 보였던 걸 생각하면 기능적으로는 필요한 물건을 맞게 찾았구나 하고 생각했다.
    • GA 외에 FB Pixel 등도 같이 지원해야 한다거나, 유튜브에 가서 일어난 사용자의 활동을 사이트 안에 붙은 GA와 연계할 수 없는 등의 한계가 있었다. 검색에 딱히  나오는 것도 없고 논리적으로 생각해보면 불가능한 게 맞을 것 같아서 깊이 파고들지는 않았다.
  • Atlassian의 JIRA, Confluence, Bitbucket을 썼다. Crowd를 기반으로 서로 연동되는 건 좋은데 그만큼 세팅에 신경을 써줘야 하고, 그러려니 할만한 수준보다 조금 더 자주 죽어서 거추장스럽기도 했다.
  • 분석업무에 R과 Python 쓰는 걸 도왔다.
    • 내가 분석업무를 직접 긴밀하게 다뤄본 적은 없지만 눈으로 따라가면서 막힌 부분을 설명해줄 수 있는 정도는 되다 보니 중간중간 나도 키보드 너머로 구경을 좀 했다. IPython Notebook이 Jupyter라는 이름이 되면서 다룰 수 있는 게 아주 많아진 것 같아 신기했다. 
    • 분석업무의 부담을 덜어줄 생각으로 아예 DB 내용을 가져다가 시각화까지 해준다는 곳을 몇 가지 구경해봤지만 개인정보 제3자 제공의 문제도 생기고 해서 본격적으로 검토하지는 않았다. 아파치 재단 산하에 Spark, Zeppelin 같은 게 존재하는 건 알고 있지만 깊이 검토해본 적은 없다.
  • SVN을 쓰다가 Git으로 넘어왔다. FTP에 직접 접속해 편집하던 시절에 비하면 (하략)
  • 외부 서비스 연동도 이것저것 해야 했었다.
    • 제공되는 API를 다루는 건 OAuth 등으로 여러 서비스를 붙여봤다. API 버전이 바뀔 때마다 손을 대야하거나, OAuth 토큰에 생명주기가 들어가면서 생긴 번거로움은 서비스 자체의 동작 방식과 상충되는 면이 있어서 깔끔한 해결책을 찾지는 못했다.
    • 그래도 API가 제공되는 게 나은 편이라, SMS 발송 때 발신자 번호표시 정책 때문에 업체를 바꿀 때는 HTTP API를 제공하는 업체를 선정했다. 여기도 API 버전이 한 번 바뀌어서 급하게 대응을 하긴 했지만 내부 서버에 데몬을 돌리는 것보다는 훨씬 만족스럽다.
    • 소위 크롤링이라고 하는, 외부 사이트의 멀쩡한 HTML 페이지 긁어서 원하는 작업을 수행하는 것도 해야 했었다. GET뿐 아니라 POST도 했으니 거의 API 껍데기를 씌운 셈이었는데 다행히 이걸 쓰는 동안 그 사이트가 바뀌지는 않았는지 따로 손을 본 적은 없었다.
    •  네이버 지도를 표시하고, 자바스크립트가 안 되는 환경일 때를 위해 고정 이미지 지도도 제공하고, 네이버가 한반도 인근만 제공하니 그 외에는 구글 지도로도 똑같은 걸 제공하는 작업들을 했었다.
    • 지도에 좌표를 찍으면 그 행정구역을 파악하는 건 처음엔 구글 밖에 없어서 구글을 택했다가, 2018.07.16 이후로 구글 API 정책이 바뀌어서 네이버로 바꿨다.

3. 다뤘어야 하는 것
  • DB를 읽는 시점에 많은 계산을 하면 안 되는 거였다. DB에 자료가 기록되는 시점에 이런 저런 일련의 작업들이 일어나고, 읽을 때는 그 흔적만 가져오는 식이어야 했다. 나중에 일어나는 많은 문제들이 여기서 비롯하는 셈이었다.
    • PHP가 아니라 이벤트 처리가 가능한 언어였으면 대응하기 간단했을 건데.
    • 이 부분을 고민하면서 CQRS라는 게 매력적으로 느껴졌다. CQRS를 찾으면 무슨 모델 무슨 패턴이라고 많이 나오던데 그런 방향보다는, 쓰기와 읽기를 분리한다는 개념 자체가 당시의 문제 해결에 도움이 될 거라는 예상이 되었기 때문이다.
  • 캐시 관리를 진작 강화해야 했었다. DB의 읽기/쓰기를 똑똑하게 다루지 못했다면 캐시 정책이라도 똑똑해야 했다. Decorator 입히는 수준으로 캐시 적용을 쉽게 만들었어야 했는데 그러지 못했다. 한편으로는 static site generator의 껍데기를 입히면 어떨까 생각도 해보았지만 거기까지 가기엔 그 사이에 건너야 할 것들이 많았다.
  • 미들웨어 개념이 있는 프레임워크를 도입해야 했다. 꼭 Laravel 같이 널리 알려진 물건일 필요는 없어도, 하나의 HTTP Request가 최종 Response로 끝나기 전에 여러 동작을 다양한 층위에서 다루는 방식이 필요했다. 미들웨어라는 게 없는 상태에서 여러 다양한 로직을 처리하려다 보니 길고 난삽한 코드가 만들어지기 십상이었다. klein.php라는 걸 라우팅 강화 목적으로 도입하면서 미들웨어를 지원하는 기본적인 틀은 갖추었는데 더 발전을 시켰어야 했다. 
  • 결제한 금액을 포인트로 처리하는 중간 단계를 둬서 써먹기 편하게 구조를 만들면 좋았을 건데.  PG사가 제공하는 결제정보를 너무 그대로 써먹다 보니 쿠폰이니 부분취소니 하는 걸 도입할 때마다 결제 쪽 설계가 너무 복잡해졌다.
  • 검색 엔진. Elasticsearch를 도입하려는 시도는 했지만, 결과적으로 접혀버렸다. 검색 엔진 자체보다는 인덱싱 부문에 공을 들여야 했는데 아무래도 내가 너무  쉽게 덤볐다.
  • 하나의 큰 목표를 작은 과제로 나누고 단계적으로 진행하는 걸 정착시켜야 했는데 여러 정황으로 볼 때, 그렇게 해야 하고 그렇게 하고 있다는 걸 다른 사람들에게는 인식시키지 못했던 것 같다.
  • 다 적자면 끝이 없겠지.

4. 다루고 싶은 것
  • 여전히, 자료를 다양한 기준으로 표현하는 게 충분하지 않다고 생각한다. 시간 순서로는 iCalendar 같은 게 있고, 위치 기준으로는 지도가 있고, 그 밖에도 여러 기준축이 존재할 수 있는데 쌓여있는 데이터에 비해 가공과 정리와 표현의 노력이 충분치 않았다.
  • PHP 외의 환경을 다뤄야 한다. 꼭 PHP를 벗어나기 위함이라기보다는 다른 언어나 프레임워크에서 어떤 접근방식을 쓰는지, 어떤 간단한 방법이 있는지를 구경하다 보면 딱 이럴 때 쓰면 좋겠구나 싶은 지점들이 있어서 도움이 된다. 개인적으로는 Django를 선호하는 편이고, Node도 좋고.
  •  템플릿 엔진을 개선하고 싶다. 흔히 템플릿 엔진이라는 건 무슨 자료가 채워져야 하는지 다 아는 상태에서 미리 준비를 하고 끝에 오는 템플릿 엔진으로 넘겨주는 식인데, 방향을 바꿔서 템플릿에 어떤 요소들이 있는지에 따라 그에 맞는 동작을 하면 장점이 있다고 본다. 
    • 가령 흔히 프론트엔드라고 하는 작업 단계의 결과물을 백엔드라고 하는 작업 단계에서는 다 분해해서 새로 조립하는 식인데, 일이 이렇게 돌아가면 어떤 수정이 필요할 때 프론트엔드 작업자가 기존 작업물을 그대로 수정하는 것만으로는 부족해서, 백엔드 환경까지를 어느 정도 이해할 수 있거나, 짐작으로 글자만 수정하거나, 아니면 백엔드 작업자가 수정을 하는 식이어야 한다. 이 방식을 바꿔서 템플릿을 그대로 둔 상태로 작업을 한다면 서로 작업영역이 겹치지 않고 해야 할 걸 할 수 있다.
    • Level-2/Transphporm이라는 게 DOM+CSS 선택자(내부에선 XPath) 구성으로 이런 게 가능하도록 만들었는데 잠깐 검토한 바로는 CSS 선택자의 해석을 직접 구현하고 있어서 버그라고 할까 의도와 다른 동작들도 보이고 해서 실사용엔 적합하지 않아 보였다. 하지만 개념만은 충분히 훌륭하다.
  • 프로비저닝과 배포 자동화, 혹은 CI라고 하는 것까지를 다뤄야 한다. 생각이 여기에 이르면 매번 지치는 기분이 들지만 필요한 작업이다. 그동안 기술적인 고민을 나름대로는 많이 했지만 당장 매출이 나는 서비스에 나쁜 영향이 조금이라도 갈 것 같다면 쉽게 저지를 수가 없었다. 배포 쪽이 매끄럽게 다듬어진다면 이런 부분이 덜 무서워지겠지. 
    • Docker는 환경 빌드 차원에서 만지작거리기만 했고, 도입했을 때의 뒷감당을 할 여건이 되지 않는다고 판단하여 완전히 적용하지는 못하였다.


2018년 7월 5일 목요일

코드로 인프라 관리하기, 왜?

http://www.aladin.co.kr/shop/wproduct.aspx?ItemId=103384462

이런 책이 있다. 꽤 마음에 드는 내용이었다.

가상화 이후에 컨테이너 시대가 되었는데 흔히 거론되는 장점은 확장성이나 배포의 편리, 구동 환경을 동일하게 유지한다거나, 테스트를 실서버에서처럼 한다 같이 인프라를 대체하는 수준의 얘기인데, 그런 장점을 취하지 않더라도 (규모가 작아서 서버 한 대 안에서 다 돌릴 수 있더라도) 이런 류의 기술을 도입할 장점이 충분히 있다고 생각한다.


서비스 하나를 구성하는 데는 여러 데몬이 개입하게 된다. 흔하게는 DB, 혹은 캐시. 무슨 서비스가 어떤 설정으로 돌아가고 있는지 알아야 서비스를 구성할 수 있고, 어느 하나라도 같이 돌지 않으면 서비스에 지장이 생긴다.

그럼 서비스를 만들 때 그런 주변 요소들도 같이 고려할 수 있어야 한다. 가상머신이든 컨테이너든  미리 청사진을 준비하고 그 청사진에 맞춰 서비스를 구성하는 거다. 청사진 자체를 코드로 관리할 수 있으면 코드의 변화와 함께 청사진도 바꿀 수 있기도 하다.

이런 관점으로 설명하는 얘기도 자주 보이면 좋겠다.

서비스형 블로그 DNS 설정

유명한 가입형 블로그 서비스는 대체로 커스텀 DNS를 붙일 수 있게 지원한다.
한동안은 텀블러를 2차 도메인에 붙여놨었다. 텀블러는 2차 도메인을 직접 지원하기 때문에 보기에 그럴듯해서다.
그러다가 구글 블로거로 다시 옮겼다. 구글은 2차 도메인 자체를 유지할 수는 없고 2차 도메인에 접속하면 3차 도메인으로 이동하는 수준까지는 지원한다.

여기에 한 가지 더해서, 3차 도메인으로 이동까지 했는데 원하는 주소가 없으면 다른 도메인으로 이동하는 것까지 지원한다. 자기 도메인 유지하는 사람은 다른 서비스를 썼을 수도 있으니 지원하는 기능일 테다. 텀블러에 물렸던 2차 도메인이 한동안 구글 검색에 노출됐던 모양인데 그래서 검색 결과가 텀블러 주소 형식으로 들어오게 된다. 이걸 구글이 받아서 3차 도메인의 텀블러로 이동시켜준다. 편한 기능이다.

HTTPS 지원도 잘 되기 때문에 요즘 시대에 뒤떨어지는 느낌도 없다.

2018년 6월 23일 토요일

Droid Sans Fallback 폰트 버그

http://cjonmart.net 라는 데를 들어가서 이것저것 장바구니에 담는데 중간중간 잘못 적힌 글자가 보였다. 처음 몇 번은 주말에 바빠서 오타를 냈나 싶었지만 같은 패턴이 반복되고 있었다.
가령 "건강식품관"이 "건가ㅈ식품관"처럼. "떡갈비"는 "떡가ᄅᅠ비"로. "밀감"이 "밀가ㅁ"로.

잘못 적힌 글자를 긁어다 복사해서 다른 창에 붙여보니 멀쩡하게 나왔다. 크롬이 폰트를 깨먹거나, 폰트가 잘못 만들어졌을 터였다.

브라우저에서 F12를 눌러 개발자 도구를 열고 잘못된 글자가 보이는 HTML 요소를 선택했다. 개발자 도구에서는 글자가 멀쩡하게 보였다.
 str=document.querySelector('[title="건강식품관"]').childNodes[0].text; for(i=str.length;i-->0;){console.log(str.charAt(i), str.charCodeAt(i), str.charCodeAt(i).toString(16));}
라고 가져다가 찍어봐도 "강"은 AC15로 잘 나왔다. 어쩌다 입력된 NFD가 잘못 표시되는 것도 아니란 얘기였다.

document.querySelector로 그 요소를 가져다가 window.getComputedStyle(x).fontFamily를 봤다. AppleSDGothicNeo-Medium, "Apple SD Gothic Neo Medium", "Noto Sans CJK Korean", "Noto Sans", "Droid Sans Fallback", "Malgun Gothic", sans-serif - 한켠에 gedit 설정창을 띄워서 폰트 이름을 찾아봤지만 애플도 Noto도 나오지 않았다. 잘못 표시되는 글자를 gedit 폰트 설정의 미리보기 칸에 넣고 차례로 폰트를 봤더니 Droid Sans Fallback이 깔려있었고, 실제로 글자도 크롬에서와 같이 잘못 나왔다.


하지만, 크롬이 gtk를 쓰던가? 폰트 파일의 잘못일 수도 있지만, 리눅스 세계이니만큼 폰트 래스터라이저를 먼저 의심해볼 필요가 있었다.

다시 파이어폭스에서 같은 주소를 열어봤다. 글자가 멀쩡하게 나왔다. F12를 눌러 요소를 선택하고 오른쪽에 어떤 폰트가 적용되었다는 설명을 열어보았다. 이번엔 Noto가 적용된 걸로 나왔다. Korean이라는 꼬리표가 붙은 폰트가 없기는 하지만 자동으로 KR로 적용하는 건지, 아니면 항상 Noto로 적용하는 건지. 이래서야 비교가 안 됐다. 다른 한글 폰트를 몽땅 제거해서 영문만 나오는 상태로 만든다면 확인이 되겠지만 그렇게까지 하고 싶지는 않았다.
혹시나 해서 Droid Sans Fallback을 웹폰트로 보여준다는 웹페이지도 몇 개 열어봤지만 증상을 재현해줄만한 상황은 아닌 것 같았다.

찾아보니 Droid Sans Fallback은 구글 안드로이드 때문에 배포되는 폰트인 것 같았다. 널리 쓰이는 걸 고려한 폰트인만큼 다국어 부문에서 잘못된 정보를 포함할 수도 있겠다 싶었다. gedit 설정창에 한글 AC00부터 11184글자를 모두 긁어다 붙여봤다. (설정창을 닫고 본문창에서는 증상이 나타나지 않았다) 과연, "가" 다음에 "각-갛" 범위의 글자가 모두 받침이 떨어진 상태로 나오고 있었다. "나" 이후로는 폰트 영역이 없을 때 나오는 채움 표시가 나오고 있었고 중간은 너무 많아서 다 못 봤지만, 끝에서는 다시 "힢힣" 두 글자가 나오는 걸로 끝이 났다. 빈 영역이 대부분인 걸로 봐서, 의도적으로 채워진 걸로는 보이지 않는다.

dpkg -l | grep -i droid 해보니 fonts-droid-fallback 패키지가 나온다. dpkg L fonts-droid-fallback 해보면 ttf 파일이 2개. fontstools 패키지의 ttx 명령으로 풀어보니 과연 HANGUL 어쩌고 하는 줄이 잔뜩 있다.

아마도 폰트가 잘못 만들어진 것 같긴 한데, 이걸 어디에 어떻게 전달을 할 수 있으려나 모르겠네.

https://github.com/google/fonts/issues/1352 첫번째 반응을 보면 Droid는 이제 Noto로 대체되었으니 그냥 바꾸고 말라는 얘기다. 그것도 2017년 말.
그래서 그냥 지웠다. apt remove fonts-droid-fallback -y

2018년 6월 17일 일요일

리눅스에서 5.1채널 스피커 테스트

이리저리 물건을 정리하다 보니 스피커 연결선을 모두 뽑았다가 다시 꽂았다.

맞게 연결했는지 확인하려고 늘 쓰는 검사 동영상을 틀었는데 smplayer + mpv 조합으로 음분리가 잘 되지 않았다. speaker-test 명령이 있는 건 알고 있어서 몇 가지로 실행해봤는데 딱히 이거다 싶은 결과가 나오지 않았다.

그래서 아래와 같이 성공한 방법을 적어둔다.
  • aplay -L | grep -i pdif -B2 | grep MAYA -B1 -A1
  • pasuspender -- speaker-test -Ddca:CARD=U5,DEV=0 -c6 -l1 -twav -s1

MAYA U5 외장 사운드에서 광출력을 뽑아 야마하 TSS-10 쪽으로 신호가 넘어간다.
https://github.com/darealshinji/dcaenc 에서 dcaenc를 가져다 설치한 상태라서 dca 장치를 썼다.
pulseaudio가 기본으로 구동되는 환경이다 보니 pasuspender를 씌워서 pulseaudio를 거치지 않고 장치에 직접 접근했다.
s 옵션뒤의 숫자를 하나씩 바꿔주면 각 스피커 이름을 담은 음성이 재생된다. 스피커가 맞게 연결된 걸 알 수 있다.

자, 이제 smplayer + mpv에서 뭐가 문제인지 알아볼 차례구나(...)
왜인지 모르지만, 껐다 켜고 나니 /etc/pulse/ 설정 하나 건드린 거 없고 smplayer는 모든 오디오 옵션을 기본, 자동 혹은 끈걸로 뒀을 때 mpv에서 샘플 영상으로 음 분리가 잘 된다. mplayer를 통해서는 소리가 깨지는 걸로 봐서 뭔가 다른 게 있긴 한가본데.
패스쓰루 옵션을 켜면 안 되는 건 이상하긴 한데, 일단 잘 되니까 넘어간다.

2018년 6월 9일 토요일

kubectl을 다른 호스트에서도

k8s 클러스터를 3개 가상머신으로 구성한 뒤에 항상 master에 직접 들어가 kubectl 명령을 실행해왔다.

최근 vscode를 쓰고 있는데, terraform 확장을 테스트해볼 겸 해서, kubectl은 우분투 snap으로 설치하고 master의 ~/.kube 디렉토리를 몽땅 복사해서 로컬로 가져왔다. kubectl을 실행하니 과연 잘 된다. .kube/config 파일에 적힌 IP가 192인 걸 보니 master 안에서도 127.0.0.1이 아니라 DHCP가 부여하는 IP를 써서 접속하는 구조였던 것 같다.

이렇게 되니 로컬에서 terraform 테스트하는 건 그냥 가능하게 되었다. 문제는 master에서라면 어떻게든 접속할 수 있었던 k8s 내부 네트워크에 접근할 길이 없어졌다는 것. external loadbalancer 대응 문제가 좀 더 크게 느껴진다.

kubectl expose --type=LoadBalancer

traefik 설치 과정에서도 검토해봤지만, 외부 접속을 k8s 내부로 어떻게 끌어올지가 여전히 문제다. 여러 서비스가 k8s 세계 안에서 조화롭게 돌아가고 ingress controller를 traefik으로 설정해서 특정 도메인과 포트로 접속하면 k8s 내부의 원하는 리소스에 접근할 수 있게 되어 있다 한들, 실제 공인IP의 세계로 연결하는 게 오리무중인 상황이어서야 의미가 없다.

terraform 설명 글을 찾아봐도 당연스럽게 LoadBalancer 타입을 사용하도록 되어 있을 뿐, on-premise 환경에서 어떤 처방이 있는지를 언급하는 데가 없다.
(https://www.hashicorp.com/blog/managing-kubernetes-applications-with-hashicorp-terraform 를 보고 복붙만 해봤는데 plan에서 주루룩 지정될 항목이 나오는 점은 좋았다. apply가 끝이 나질 않아서 살펴보니 LoadBalancer를 사용하고 있어서 중단했을 뿐)

https://github.com/kubernetes/kubernetes/issues/36220#issuecomment-351782407 얘기에 https://kubernetes.io/docs/tasks/administer-cluster/developing-cloud-controller-manager/ 같이 CCM을 직접 만들면 될 거라는 얘기가 그럴듯해 보인다. type:LoadBalancer에 대응하는 구현체가 있으면 되는 건 맞을 테니까.

CCM은 
https://zetawiki.com/wiki/%EC%BF%A0%EB%B2%84%EB%84%A4%ED%8B%B0%EC%8A%A4_%EC%9A%A9%EC%96%B4 설명을 보면 k8s system의 일부로 돌아가면서 해당 환경에 맞는 관리 동작을 하는 정도로 이해가 된다. ingress controller가 k8s 안쪽으로 진입한 트래픽을 다루는 거라면 CCM은 k8s 가장 바깥이 그 밖의 세계와 어떻게 이어져야 하는지를 처리해야 하겠지.

https://www.reddit.com/r/kubernetes/comments/a0vrw7/metallb_and_traefik_decision_on_premise/ealnhun/
MetalLB가 여기 대응하는 물건이라고 한다. (ingress controller와는 다르다는 걸  명시해줘서 이해에 도움이 된다)

https://medium.com/@JockDaRock/metalloadbalancer-kubernetes-on-prem-baremetal-loadbalancing-101455c3ed48
https://medium.com/@JockDaRock/kubernetes-metal-lb-for-on-prem-baremetal-cluster-in-10-minutes-c2eaeb3fe813
여기 좀 더 내용이 있다.

2018년 6월 6일 수요일

Blogger 첫 페이지에 글 노출하지 않기

커스텀 도메인이나 HTTPS 적용 등 여러 면에서 블로그 서비스로는 구글의 Blogger가 가장 무난해서 쓰고 있다.

하지만 도메인으로 처음 들어왔을 때 무턱대고 글을 보여주는 게 마음에 들지 않아서, 첫 페이지라는 개념을 적용할 수 있는지 찾아봤다. https://www.quora.com/How-can-I-edit-a-home-page-on-blogger-com/answer/Elliyas-Ahmed 이런 답이 나온다.

Blogger 테마도 XML 수준에서 직접 편집하면서 필요한 부분에 조건문을 넣으라는 거다.
  • https://support.google.com/blogger/answer/46995?hl=ko&ref_topic=6321969
  • https://support.google.com/blogger/answer/47270?hl=ko
 아래와 같은 태그를 필요한 위치 앞뒤에 넣으면 첫 페이지가 아닐 때만 영역이 보이게 된다.
<b:if cond='data:blog.url != data:blog.homepageUrl'>
</b:if>

하지만 아무 데나 되는 건 아니고 넣을 수 있는 위치에 제약이 있다. 테마 마크업 구조를 조금 알아야 이해가 되는데, 크게 봐서 b:section과 b:widget 태그가 페이지에 표시되는 단위라고 할 수 있고 b:includable 태그로 영역을 잡아주면 그걸 b:include 태그로 가져가서 쓰는 식이다. 그리고 모바일 레이아웃이 따로 있는 테마라면 b:if 태그로 data:mobile 값을 검사해 어떤 경우에 블럭을 표시할지 말지 결정하는 부분도 있을 것이다.

그래서 지금은 일단 모바일 접근이 아닐 때 글을 표시하는 영역이 안 나오도록만 해놨다. 본문과 본문 위에 날짜 나오는 부분 2개를 막는 걸로 처리가 됐다.

비워둔 영역은 따로 안내 페이지를 하나 만들고 그 본문을 가져오는 수준에서 채우고 싶었는데 이건 방법이 있는지 어떤지도 모르겠다. 아마 안될 것 같긴 한데.

Firefox CSD 기능을 쓰려고 시작한 모험

CSD란?

client-side decoration이라고 해서, 창 관리자가 그려주는 창 테두리와 버튼들 대신 프로그램이 직접 그런 부분들을 창 안에 표시하는 기능이 있다. decoration을 없애는 거야 일찌감치 가능했다. Openbox 같은 경우엔 창 메뉴를 열어서 "Un/Decorate" 기능으로 끄고 켤 수 있다. 프로그램이 창을 띄울 때 undecorate 창이라고 선언할 수도 있다. (wmctrl로도 될 줄 알고 찾아봤는데 그런 거 없나보다)

그렇게 없앤 영역이 담당해야 할 기능 그러니까 창의 크기를 바꾸고 창을 닫거나 옮기는 걸 어떻게 처리하는지가 문제가 되는 건데, 개별 프로그램들이 알아서 잘 할 방법을 찾아보자는 얘기가 본격적으로 나오는 거다.

CSD 도입 상황

얼마전까지 CSD가 두드러지게 적용된 건 구글 크롬이었다. 처음 깔았을 때는 탭이 맨 위에 오는 크롬 특유의 모습이지만 설정에서 '시스템 제목 표시줄 및 테두리 사용' 옵션을 켜면 창 관리자가 그려주는 테두리가 붙는 걸 볼 수 있다.

그리고 csd에 대한 건 아니지만 gtk3에 (OS X의 동작 같이) '창 메뉴바를 분리해서 최상단 패널에 몰아서 보여주는' 패치가 들어간 것도 맥을 같이 하는 흐름이었다고 생각한다. 그 뒤로 Unity나 gnome-shell이 '창 관리자' 역할을 넘어 프로그램 안쪽의 기능들과 긴밀하게 연계하면서 창 관리자가 중간에 끼는 게 데스크탑 환경을 만들어가는 입장에서 거추장스럽게 느껴졌을 것 같기도 하다.
 그러다가 (클라이언트 창이 뜰 수 있도록 해주는 공간 자체를 만들어내는 게 서버인데) 창 시스템 서버가 X, X.org의 틀을 벗어나 Wayland라는 걸로 넘어가면서 CSD를 진지하게 다뤄야 하는 상황이 되었나보다. 아래는 참고할만한 글이다.
  • (영어) https://blogs.gnome.org/tbernard/2018/01/26/csd-initiative/
  • (영어) https://blog.martin-graesslin.com/blog/2018/01/server-side-decorations-and-wayland/
  • (영어) https://kver.wordpress.com/2014/10/25/presenting-dwd-a-candidate-for-kde-window-decorations/
  • (한국어) https://nemoux00.wordpress.com/2014/10/29/wayland-csd-ssd-dwd/

사실 윈도우 동네에서는 WIN16 API 운운하는 시절부터도 이미 나온 얘기였다. 윈도우 환경이 제공하는 기본 모양이 워낙 고정적이다 보니 프로그램마다 자기 취향대로 창을 그려대는 통에 전반적인 통일감이 없고 사용성이 떨어지고 윈도우가 발전해도 개별 프로그램은 발전을 따라가지 못해 구닥다리가 된다는 거였다. 꼭 데스크탑 얘기만이 아니라 웹이나 모바일 앱 환경에서도 OS가 제공하는 환경을 따를지 말지에 대해서 논란이 많다.

Firefox와 CSD

한편, 이런 '주어진 환경을 따를지 아니면 직접 해결할지'를 더 많이 고민해온 게 Firefox 같이 여러 환경을 지원하는 프로그램이다. 크롬이야 워낙 독창적인 인터페이스를 들고 나왔기 때문에 모든 걸 무시하고 바닥부터 시작할 수 있었지만 파이어폭스는 오랜 기간을 거치면서 그때그때 OS의 변화와 UI의 유행을 고스란히 따라와야 했다.

그리고 드디어 파이어폭스가 CSD 옵션을 포함한 게 60 버전부터다. 방침이나 당부를  따지지 않더라도, 어떤 식으로 동작할지 궁금하긴 했다. 실은 58 버전 정도부터도 개발 버전에는 들어갔었기 때문에 일찌감치 깔아서 CSD 옵션을 켜봤지만 왜인지 탭이 있는 줄의 공간 배치가 조금 변하는 것 말고 실제로 창 테두리가 없어지거나 하지는 않았다.

Openbox 때문일 거라고 생각하지는 않았다. 그냥 아직 도입 초반이니까 뭔가 문제가 있는 거라고만 짐작했다. 하지만 정식 버전에 포함이 된 뒤에도 여전히 창 테두리가 남아있는 건 이상했다.

CSD 적용된 Firefox를 향한 모험

그러다가 Openbox가 아닌 다른 세션을 잠깐 썼을 때 파이어폭스에 온전히 CSD가 적용된 걸 확인했다. gnome-session의 변종으로 존재하는 것들에선 모두 CSD가 잘 동작하는 것 같았다. 특이하다면 어떤 환경에선 (윈도우에서의 크롬처럼) 탭 위에 여백이 하나도 없고 어떤 환경에서는 (OS X에서의 크롬처럼) 약간 여백이 생겼다. 나는 여백이 없는 걸 선호하기 때문에 이것저것 바꿔가면서 띄워봤다.

하지만 그대로 Openbox를 버리고 데스크탑 환경을 갈아타기에도 문제가 있었다. 애초에 단촐한 구성이라 Openbox를 쓰고 있던 터에 다시 이런저런 프로그램이 잔뜩 뜨고 구성에도 제약이 있는 환경으로 가는 게 손에 딱 맞질 않았다.

가장 걸림돌이 된 건 여러 모니터에 배경 화면을 각각 띄울 수 없다는 거로, gnome-shell은 왜인지 배경 화면을 하나로만 제공했다. 아마 Xinerama 같은 걸로 panning 옵션을 통해 모든 화면을 하나로 뭉쳐서 쓰기 위해 그런 것 같았다. 이리저리 뒤져보면 gnome-shell 안에서 panning을 풀어버리는 방법도 있을 거라고 생각은 했지만 시간도 오래 걸리고, 다른 모든 설정 프로그램이 단일한 배경만을 전제하는 상황에서 강제로 환경을 바꾸는 게 얼마나 효과가 있을지도 의문스러웠다.
그리고 이것도 아마 gnome-shell의 특성 때문에 생기는 문제인 듯한데, xrandr에서 여러 모니터 중에 하나를 제대로 사용하지 못하고 꺼버리거나 해상도를 엉뚱하게 맞추는 문제가 있었다. 인텔 내장 그래픽을 쓰기 때문에 어쩔 수 없는 건가(웃음) 싶기도 했다. 찾아보니 EDID나 mode 같은 용어가나오면서 직접 정하라는 얘기도 있었고, gnome-shell이 하드웨어 가속을 쓰려고 하기 때문에 생기는 증상이라는 의심도 들었다. Openbox를 쓰면서는 겪은 적이 없는 상황이었기 때문에 그냥 Openbox로 빨리 전환하는 걸 택했다.

Openbox를 기본으로 띄우고 뭔가 (아마도 XAtom 수준까지 내려가는) 영향을 주는 프로그램을 찾아서 똑같이 띄워주면 Firefox CSD를 그대로 쓸 수 있을까 싶어 ps 명령으로 각 환경마다 어떤 프로세스가떠 있는지도 확인해봤지만 이거다 싶은 게 없었다. 그래서 방법을 바꿨다.

lightdm에서 Openbox를 직접 선택하지 않고 다른 세션 안에서 openbox --replace 명령으로 창 관리자만 바꾸면 원하는 결과물을 만들어낼 수 있을 것 같았다. 실험해보니 파이어폭스는 여전히 CSD가 잘 동작했고, 그러면서도 Openbox의 기능을 쓸 수 있었다.

"apt-cache pkgnames | grep -- -session$" 명령으로 어떤 세션들이 있는지 찾아봤다. openbox-gnome-session 패키지는 혹해서 깔아봤지만 lightdm에서 진입하지 못하고 튕겼다. 찬찬히 보다가 선택한 게 xfce4-session이었다. 너무 요즘 거라서 gnome-shell의 영향을 강하게 받을만한 것도 아니고 적당히 독립적인 노선을 유지하기 때문에 Openbox랑 조합해서 쓸만할 것 같았다. 물론 파이어폭스 CSD도 잘 됐고, 탭 위로 여백도 없었다.

xfce4 환경은 만족스러웠다. lightdm에서 xfce4 세션을 선택하고 진입했을 때 처음 환경은 온통 xfce4 기본 프로그램들로 채워져 있었지만, 얼마든지 교체할 수 있었다. openbox --replace로 창 관리자를 바꾸고, ~/.config/openbox/autostart 명령을 세션 관리자에 등록해서 그동안 Openbox에서 쓰던 초기 실행 명령들을 그대로 살려 쓸 수도 있었다. 바탕화면에 아이콘을 보여주는 xfdesktop4는 삭제를 해도 다른 패키지 의존성에 전혀 영향을 주지 않을 정도였다. xfce4는 오직 세션 매니저만 제공하는 셈이었다.

이렇게 해서 Openbox 환경에 Firefox를 쓰면서 CSD 기능을 쓸 수 있게 되었다.

2018년 6월 2일 토요일

ZOTAC Nano CI327

윈도우 10 홈 라이센스가 동봉된, 32기가 저장장치가 내장된 모델을 굳이 샀었다. 꼭 윈도우를 써야 하는 경우가 어쩌다 있기도 하고, 저장장치가 하나라도 더 붙은 걸 고르면 쓸모가 있겠지 싶은 마음이었다.

그리고 9개월 여가 지난 지금, 32기가 디스크가 인식되지 않는 걸 발견했다. 32기가로는 단독으로 쓰기에 한계가 있어서 애초부터 SSD를 따로 붙여서 쓰고 있었다. 한동안 종종 윈도우로 부팅을 하다가 안 건드린지 한참 되긴 했는데 그래도 이렇게 속절없이 망가져 있으니 아까운 건 어쩔 수 없다. 아낀다고 뭐 많이 쓰지도 않았는데.

전화를 넣어본다 한들 수리를 할 수나 있을지 의문이다.

2018년 5월 26일 토요일

한글 soundex

구글의 Did you mean 같은 걸 어떻게 만드나 하는 생각 다음에 떠오른 게 soundex인데, 그럼 한글은 어떻게 하나 하는 생각 다음에 떠오른 게 자소 분리를 해서 알파벳으로 바꾸고 soundex를 돌리면 되겠네, 였다.
검색해보니 역시나 똑같은 구성으로 이미 만든 사람들이 있다.

영문 soundex 까지는 괜찮은 결과가 나오는 걸 봤었는데 한글 자소를 영문으로 바꿔서 하는 것도 괜찮을지는 잘 모르겠다.

2018년 4월 28일 토요일

AirPort 단종 뒤에 AirPlay 기술은 어떻게 되나?

가로 세로가 어른 손가락 길이 정도 밖에 안 되는 크기에 무선 공유기와 프린터 공유, 오디오 출력 공유 기능이 들어가 있다. 소비전력을 찾아보니 구동중에도 6와트 정도라고 하던데 계속 켜두었을 때 좀 따뜻해지는 정도의 발열을 생각하면 그보다는 좀 더 나오지 않을까 싶다.

다른 기능보다는 주로 AirPlay 때문에 쓴다. (원래는 AirTunes라는 이름이었다. iTunes와 연관성을 주고 싶었나보다.) 네트워크로 받은 음성 신호를 기기에 내장한 3.5파이 minijack 내지 원형 광출력 단자를 통해 출력하는 기능이 있고, 그 위에서 DAAP나 DACP 프로토콜 기반으로 재생과 제어를 담당하는 기술을 합한 게 AirPlay라고 할 수 있다.

유선과 무선 모두를 통해 신호를 받을 수 있기 때문에 거실에 스피커를 갖추어 놓고 방에서 iTunes로 재생하는 노래를 끌어다 듣는 식으로 쓰는 게 기본 용법이고 (DAAP 담당), 아예 iTunes에 해당하는 음원 저장 및 재생/제어까지를 분리하는 구성도 가능하다. (DACP 담당) iOS에 있는 Remote 앱이 제어만 분리한 형태다.



오디오 기기부터 자동차 옵션까지 몇몇 호환장비가 존재한다. 당연히 리눅스에서도 구현체가 있다. 가장 일반적이라고 할 수 있는 게 (FreeNAS에도 포함된 것으로 아는) forked-daapd로, iTunes의 재생 기능에 AirPlay로 출력을 보내는 기능이 있고 Remote 앱의 제어를 받아들인다. 음원을 구비하는 게 합법적이기만 하다면 아주 유동적인 구성으로 음악을 감상할 수 있다.

물론 전체 과정에서 핵심이 되는 건 스피커에 소리를 전달해주는 AirPlay 수신기의 존재다. 그리고 그게 AirPort 장치다.

AirPort가 공식적으로 단종된다고 발표가 났다. 새로운 기계가 안 나온지 이미 오래되긴 했다. 그리고 애플이라는 기업의 라인업 면에서는 AirPlay가 없어진다고 할수는 없을 거다. 그 사이에 Apple TV가 나오면서 AirPlay가 소리만이 아니라 영상도 전달하는 방식이 되었다. 소리만으로 한정해도 HomePod이라는 기계가 나왔다. HomePod이 AirPlay 기능이 있는 스피커라서 지금의 AirPort와 완전히 같지는 않지만.

지금 시장에 나와있는 AirPort 장치가 모두 없어지기 전까지야 AirPlay 기술이 계속 쓰이긴 할 것이다. 하지만 그 다음엔 어떻게 될까? 수중에 있는 장치가 고장이라도 나면 그 다음부터는 forked-daapd고 뭐고 써먹을 수가 없게 된다. 아니면 좀 더 연구를 해서 AirPlay 리시버 자체도 라즈베리파이 같은 걸로 구성해서 쓸 수 있으려나?shairport-sync 프로젝트가 있으니 지금도 가능한 일이다.

2018년 4월 23일 월요일

렌딧 중단

작년 여름부터 적금 말고 다른 방법도 찾아보기로 했다.
그 과정에서 P2P 금융이라는 이름이 붙은 업체들을 몇 개 정해서 조금씩 넣었다.

렌딧은 그 중에서도 5천원으로 단위 금액이 가장 적은데, 그만큼 위험이 분산되며, 소액에 세금이 붙는 거기 때문에 절세 효과가 생긴다는 설명이 그럴듯하게 보여서, 자동투자도 유지하고 개중 중점적으로 유지했다.
그런데 이제 몇 달이 지나고 보니 렌딧에서 계산해주는 수익률이 너무 떨어져 있었다. '실질 연환산 수익률' 항목은 8.37%로 낮지 않은데 '연체 채권의 추정손실률'이 7%라서 '예상 연환산 수익률'이 1.37%이 되었다. 세후 1.37이면 무난한 적금 정도로도 달성할 수 있는 이율이라서 아무래도 흥이 식는다.

2018년 4월 하순 현재 단기연체 5, 장기연체 3.

혹시 '수익추구형'으로 선택했던 수동 투자에서 연체가 발생한 건가 싶어서 채권상태 화면을 뒤적거려봤는데 그렇지도 않았다. '균형투자형'으로 만든 2017-08-21 첫 투자 100건 중에서 단기4, 장기3, 그리고 그 다음의 '수익추구형' 2017-11-23 투자 20건 중에서 단기1이 있다.
무엇보다 첫 투자에서 발생한 것들이기 때문에, 나는 이걸 '시간이 지나서 터질 게 터진' 상황으로 인지한다. 바꿔 말하면 '시간이 더 지나면 다른 투자건에서도' 연체가 더 계속 꾸준히 생길 거라고 본다.

이미 몇 년째 업계에 있었고 꽤 이름있는 업체인 렌딧의 채권에 대한 분류와 예측이 시원찮다는 의미일 수도 있겠고, 그 사이에 사람들 자금 사정이 한층 나빠져서 예측 모델을 벗어날만큼의 상황이 되었다고도 할 수 있겠지. 혹은 그냥 내가 투자에 대해서 너무 감이 없어서 지금 상황을 완전히 잘못 이해하고 있는 걸지도 모르겠다.

어쨌거나 일단은 자동투자를 껐고, 앞으로 회수되는 금액은 모두 출금할 생각이다.

2018년 4월 10일 화요일

로그 남기는 코루틴

일전에 뭔가 긴 코드를 짜다가, 로그 남기는 동작을 코루틴으로 돌리면 좋겠다는 생각이 들었었다.
어설프게 개념만 이해하고 실제로 써본 적은 없는 터여서 재밌겠다는 생각이 들었지만 내 것 아닌 코드에 집어넣기가 저어해서 진행은 하지 않았다.
그리고 오늘 마침 그때 생각이 나서 예제 코드를 짜보려고 했는데 막상 손을 대니 그럴듯한 코드가 나오지 않는다. 그때는 어떤 착상을 했던 건지 제대로 떠오르지 않고. 그냥 개념을 오해해서 잘못 생각했던 걸까?

2018년 4월 8일 일요일

kubernetes cluster 하나 설치, 일단 성공

https://blog.alexellis.io/kvm-kubernetes-primer/

위의 내용대로 br0 네트워크 인터페이스를 만들고 virt-install를 통해 vm을 생성하고 k8s를 설치했다. 손수 vm을 만들고 손수 docker와 k8s 패키지를 깔아서 세팅했을 때 네트워크 문제로 보이던 pod 실패도 없었고 반응 속도도 매우 빠르다.
br0 장치를 따로 만든거나 sysctl 설정을 바꾸고 진행한 게 vm끼리의 통신 문제를 해결한 것 같은데 어느 쪽이 맞는지는 모르겠다. 나중에 다시 궁금해지면 확인해봐야겠다.

대시보드는 github에 나와있는 명령대로 설치하고, master의 crontab에 kubectl proxy를 실행하도록 한 다음, ssh -L 명령으로 로컬 8001에 접속하면 master의 8001에 접속하게 했다.

대시보드 인증은 https://blog.heptio.com/on-securing-the-kubernetes-dashboard-16b09b1b7aca 에 나온대로 serviceaccount를 하나 만들어서 token을 썼다.

minikube라는 선택지도 있었지만, 그렇게 간단한 구성만으로는 충분히 맛을 볼 수가 없을 것 같아서 손을 대지 않았다.

2018년 4월 4일 수요일

rabbitmq priority queue support in php

https://www.rabbitmq.com/priority.html

rabbitmq 3.7.0
phpamqplib


큐 선언
    $args = new AMQPTable();
    $args->set('x-max-priority', 10);

    $ch->queue_declare($queue, false, true, false, false, false, $args);


메시지 생성
    if ($priority > 1) {
      $args['priority'] = $priority;
    }
    $msg = new AMQPMessage($message, $args);


x-max-priority 속성이 있는 임의의 큐에 10개 메시지를 priority 속성 부여한 상태로 publish 했을 때, consumer가 10개를 받는 순서대로 AMQPMessage의 priority 속성을 확인하면 숫자가 높은 순서대로 전달되는 것을 확인함

큐에 x-max-priority 속성이 없이 선언되었으면 각 메시지에 priority 속성을 부여해도 효과가 없는 것으로 보임

traefik, the kubernetes ingress controller

https://docs.traefik.io/user-guide/kubernetes/

kubernetes 클러스터도 하나 생겼으니 기념삼아 이것저것 생각나는 걸 깔아봤다.

안내문에 적힌 치즈 웹서비스를 다음과 같이 테스트해봤고, 까먹었을 때 보기 위해 적어둔다.

export ADDR=kube-node-3 PORT=32479; for h in cheeses.minikube cheddar.minikube stilton.minikube wensleydale.minikube; do curl $ADDR:$PORT -H "Host: $h" -v; done

kube-node-3은 ds가 아니라 deployment (=그 노드에서만 동작하는) 방식으로 설치한 traefik이 테스트 시점에 구동중인 노드였다.

원래 하고 싶었던 건 kubernetes 클러스터에 external loadbalancer를 붙이고 (그게 traefik이 되면 좋고) 거기서 다시 hostname 기반으로 분산을 하는 거였다. 외부에서 접근했을 때 traefik이 실행중인 노드로 접근하게 만드는 걸 아직 몰라서, 일단 hostname 방식으로 동작한다는 걸 확인하기 위해 Host 헤더를 강제로 집어넣었다. 원했던 대로 각 호스트의 웹서버가 각각 반응해줘서 기뻤다.

http://alesnosek.com/blog/2017/02/14/accessing-kubernetes-pods-from-outside-of-the-cluster/ 여기서 설명하는 내용을 보면 traefik이 제공하는 ingress controller를 잘 쓰면 애초에 원하던 걸 처리할 수 있는 것 같다.

여전히 남는 의문은, 그럼 ingress controller가 동작중인 node를 어떻게 다시 공인IP의 세계로 노출시키는가 하는 점인데.

https://stackoverflow.com/a/37796383 를 보면 externalIPs라는 설정을 언급하고 있지만, 이건 hostname에 반응하는 것처럼 특정IP를 목표로 지정된 트래픽이 (미리 설정된 외부의 route에 따라) k8s 안쪽까지 들어가면, 그 IP에 반응하도록 서비스를 설정하는 것인데, 임의의 hostname을 지정할 수도 있는 것과 달리 숫자IP를 고정적으로 지정해야 해서 그다지 좋은 방식은 아닌 것 같다. 내가 찾던 방향도 아닌 것 같고.

https://medium.com/@maniankara/kubernetes-tcp-load-balancer-service-on-premise-non-cloud-f85c9fd8f43c 내용을 보면 yaml에서부터 externalIPs 설정을 적어주지 않고 kubectl expose 명령으로도 정할 수 있나보다. expose 명령의 옵션을 자세히 보지 않았던 게 잘못이다.
(이어서 tcp-echo-server라는 예제를 통해 NodePort 대신 hostNetwork:true 속성을 켜는 방법을 보여주는 것도 재밌는 방법처럼 보이지만, 권장되는 방법도 아니고, traefik을 쓸 거니까 관심사는 아니다)

expose 명령의 --external-ip 옵션에 traefik이 떠 있는 node의 IP를 지정하고 (kubernetes dashboard에 EXTERNAL-IP 항목으로 나온다) 80 포트를 80 포트로 연결하라고 한 다음에, (/etc/hosts에 kube-node-3와 같은 IP로 지정된) curl cheddar.minikube 명령을 실행하면 Cheddar라는 페이지가 출력된다. 앞서 curl에 Host 헤더를 강제로 먹였던 것과 같은 결과다.

AWS 같은 클라우드 서비스에서야 LoadBalancer 타입으로만 지정해도 expose에 사용될 공용IP를 받아서 지정하면 끝이니 복잡할 게 없이 클라우드 API 호출로 IP만 받으면 바로 끝날 동작이다.

공유기 안쪽에서 192.168.x.x 범위로 구성할 때를 기준으로 바꿔서 생각해보면 이렇게 되겠다.
  1. 공유기 밖에서 공유기 안으로 넘어오는 단계 - 포트포워딩, DMZ 등
  2. 넘어온 트래픽을 받을 내부IP를 어떻게 고정적으로 유지할지 - DNS에서 IP 고정
  3. 그 내부IP가 받은 신호가 traefik 같은 ingress controller로 넘어가는 과정을 어떻게 유지할지 - expose 명령을 쓰고, 고정된 내부IP를 external-ip로 지정

2018년 4월 3일 화요일

노조

예전에 (아마도) 금속노조에 가입했다는 어느 얘기를 본 기억이 있다.

왜 기억하냐면, 거기가 금속노조 하면 떠올릴만한 강성 파워 거친 업장이 아니어서다. 아마도 아줌마로 불릴만한 노동자가 대부분인 곳이었던 것 같다.

왜 하필 금속노조인가 물으니 여기도 쇠를 쓴다며 기왕 노조 들 거면 쎈 데가 좋지 않을까 해서 금속노조를 택했다고 답했다고 본 걸로 기억한다. 남아있는 이미지는 활기찬 인터뷰 분위기에서 마지막에 말하는 본인도 웃겨서 깔깔 웃으며 답하는 것.

오늘 네이버에서 노조가 출범했다는 기사를 봤다. 여기는 화학섬유노조, 2017년 11월부터는 화학섬유식품노조 산하로 들어갔다고 한다.

네이버가 화섬이라니, 하는 생각 끝에 일전의 금속노조 얘기가 새삼 떠올랐다. 금속노조 지부마다 지회 목록은 있나본데 하나씩 다 뒤져보기도 뭐하고 해서 정확히 찾아보진 않았다. 그렇다고 금속노조나 민주노총 게시판에 뜬금없이 '혹시 이러저러한 지회가 정말로 있는 것입니까?' 하고 물어보는 것도 분위기 상 안 맞을 것 같고.

그 금속노조인 이유가 식판이었나 하는 생각에 식당 쪽으로 검색해보니 나오긴 한다. http://www.redian.org/archive/14204 현대푸드. 근데 2006년 얘기라서 이건 아닌 것 같다.

어디 물어볼만한 데도 없고. 궁금하네.

찾았다. https://twitter.com/coke_cloud/status/955701669328666624 담터. '차'를 만드니까 금속노조, 라고. '금속노조 이색 지회' 따위로 찾았을 때는 안 나왔고, 하다 하다 '우리도 금속노조'라고 던져봤는데 덥석 나온다.

https://twitter.com/labordream/status/959698451914084352 노조를 뭘로 들든 업종이랑은 상관없다니 과연 헌법이 보장하는 권리.

2018년 3월 31일 토요일

내가 왓챠플레이를 만든다면

일전에 왓챠플레이 앱한테서 분발하라는 격려를 받기도 했거니와, 실제로도 왓챠플레이에 다달이 돈은 내고 있지만, 그다지 긴요하게 써먹지는 못하고 있다. 책을 사놓고 안 읽는 것처럼, 왓챠도 볼 수 있는 영화가 많고 찜해둔 것도 있지만 막상 보려고 고르면 이거다 싶게 땡기는 게 없다.

예전에도 어디에 적었었는지 기억은 안 나는데, 보유한 영상물들이 티비처럼 항상 준비되어 있는 건 어떨까? Kodi 류의 미디어 재생기를 보면 그런 확장기능이 실제로도 유통되고 있다. 왓챠에서라면 앞뒤로 어울리는 영화를 연결한다거나, 중간중간 다른 영화의 예고편을 틀어주는 식으로 하루종일 돌아가는 채널을 자체 구성할 수도 있지 않을까? 그러다가 중간부터 본 영화가 괜찮은 것 같다 싶으면 그것부터 새로 봐도 될 거고. (꼭 계속 스트리밍을 할 필요는 없을 거다. 미리 짜여진 시간표만 놔두고, 사용자가 실제로 접속했을 때만 그 시간표대로 틀어주면 되니까)

혹은 자체적으로 예고편이랄까, 미리보기를 만들어서 그 사람한테 소구할만한 장면을 보여주는 건 어떨까? 왓챠 안에서 영상물을 분석해서 클라이막스 직전이라거나 풍광이 좋은 장면이라거나, 로맨틱한 분위기의 장면 같은 특징 있는 부분을 조금 잘라서 미리 볼 수 있으면 그 장면이 무슨 맥락인지 궁금해지고 영화에 진입할 여지가 더 있을 것 같다. 그 사람의 축적된 취향에 맞는 장면이라면 더 그럴 테고. (어떤 영화들은 도입부가 너무 안 땡겨서 그걸 견뎌내고 다음 장면 다음 장면으로 넘어가는 게 고역일 때도 있었다. 뒤로 가면서 재밌어질지라도.)

2018년 3월 29일 목요일

perplexity

오늘 또 새로운 걸 들었다.

군집을 나눌 때 몇 개로 나눠야 좋은가를 볼 수 있는 지표라고 하는데.

진작 알았으면 조금은 덜 고민했으려나.

2018년 3월 16일 금요일

영단어 한글 표기 폰트

Opentype의 GSUB 기능을 극단적으로 써먹으면, alphabet이라는 문자의 연속을 알파벳이라는 glyph로 묶어서 보여주는 식이 가능하지 않을까?

일반적인 용법이 무리라면 고유명사에 써먹을 수 있지 않을까?

가령 아래는 PPAP라는 유명한 밈을 폰트의 GSUB 기능으로 소화한 것이다. 다운로드 받아서 ttx 명령으로 xml 포맷으로 바꿔보면 기본 영문 글리프에 더해서, 파인애플·애플·펜 그리고 그것들이 조합된 글리프가 이어진다.
  • http://dainippon.type.org/DTAP/
  • http://joonfont.com/forum/?mod=document&uid=9
  • https://github.com/dainippon-type/DTAP

그럼, alphabet을 알파벳으로 대치하려면 "알파벳" 전체를 한 글리프로 가지고 있어야 된다는 건가?
그렇진 않은 것 같다. https://www.adobe.com/devnet/opentype/afdko/topic_feature_file_syntax.html#5 설명에 따르면 GSUB도 몇 가지 종류가 있다고 한다. 위의 DTAP는 표현하려는 최종 글리프가 조합마다 모두 다른 형태여서 하나씩 따로 만든 것이겠고.

2018년 3월 15일 목요일

열쇠공방

아마 열쇠를 받아서 수시로 드나든다는 개념으로 열쇠라는 이름을 붙인 것 같다.

내가 찾는 건 이런 쪽이라고 한다.

2018년 3월 11일 일요일

카카오 API — Vision API, 아직은 좀

 
 카카오에서 안내 메일이 왔길래 대충 그렇구나 하고 있다가, 주말이 다 가기 전에확인이나 해보자 싶어서 몇 가지를 실행해봤다.
 
일전에 구글 비전 API에서 OCR 기능을 테스트한 적이 있어서 그 코드를 바꿔서 쓰려고 했는데, 카카오에서 이번에 공개한 기능 중에 OCR은 없었다.

API 문서 자체에서 데모 기능을 제공하고 있어서 보유한 이미지를 몇 개 제출해봤다. curl로 호출하는 방법도 제시하고 있어서 두어 번 해봤다.

간단한 인상으로는 검출 범위가 그리 폭넓지는 않다. 태깅에 몇 가지 이미지를 제출했는데 얼굴이나 상품이라고 할만한 게 없는 이미지에는 아무 태깅도 걸리지 않았다. 얼굴 검출과 상품 검출, 태깅까지는 feature detection에서 파생된 용법일 테니 사실상 한 묶음일 거고, 그래서 그 범위 내에서만 다듬어진 게 아닐까 싶다.

태깅에 가장 먼저 제출한 이미지는 이거다. 포스터, 일본어,우산, 신발 정도를 예상했는데 아무 결과도 없어서 아쉬웠다.

 
성인 이미지에는 몇 가지 제출을 했을 때 그럭저럭 맞는 결과가 나왔다. 아마도 서비스 내에서 워낙 수요가 높아(…) 일찌감치 진척이 있었던 게 아닐까 싶다.

2018년 3월 7일 수요일

ABAP, Drools

새로운 걸 들었다.

내가 잘 모르는 세계의 말이어서 전혀 못 들을만하긴 한데.

신기하네.

2018년 2월 8일 목요일

핀테크와 기술 지체

일전에 세븐일레븐에서 엘페이 결제가 매번 문제라고 적었다.

집 주변에 편의점이 여럿이고 그 중엔 노년이라고 밖에 할 수 없을 분들이 계산대를 지키는 때도 왕왕 있다.

그리고 나는 여기서도 불편을 감수해야 한다. 바코드 리더기로 차곡차곡 물건이 찍히면 통신사 포인트 차감 할인을 먼저 내밀고 그 다음에 내가 무슨 결제수단을 쓰려는지 얘기하고 폰에서 결제 앱을 켜고 지문으로 바코드를 표시한다. 그럼 계산대의 점원은 익숙하지 않아 보이는 얼굴로 이런저런 버튼을 누르고 바코드 리더기를 폰 화면이 가져다 댄다. 한 번에 될 때도 있지만 안될 때도 자주 겪는다.

방금도 그게 제대로 되지 않았다. 분명 결제 바코드를 찍고 비닐봉투를 받아들고 가게를 나서서 몇 걸음 가는데, 결제 앱에 떠야 할 영수증이 없는 거다. 통신사 포인트 차감은 기록이 떠 있었다.

결제가 안 됐구나 해서 가게에 되짚어 가니 계산대 앞의 할머니는 계산이 됐는지 어떤지도 모른 채로 다른 돈 문제로 고심하고 있었다. 이러저러해 다시 왔다고 설명해서 계산대에 남은 기록을 봐달라고 했는데 정말로 결제 기록은 없었다. 다시 결제를 했지만 현금으로 결제했다는 영수증이 뜨는 등 몇 번이나 결제가 진행되지 않다가 겨우 결제 앱으로 결제가 되었다.

그 와중에 이미 차감된 통신사 포인트는 취소가 되지도, 결제 금액에서 차감이 제대로 되지도 않고 그냥 공중에 흩어져 버렸다. 아마 다음 달 즈음에 그 가게의 차감분으로 정산되겠지. 얼마 되지 않는 금액이고 통신사 포인트는 달리 쓰임새가 많은 것도 아니어서 별 말은 안 했다.

--
https://www.instagram.com/p/BocExN4FTK4/

노년은 이 시대의 소비자로서도 소외되었다.

2018년 1월 21일 일요일

어떤 생각에 도달했다

자력갱생-이라고 할 수 있겠지.

생각해보면 이런저런 호스팅 서비스와 클라우드 서비스를 두고서 나는 여전히 자체 서버를 굴리고 있고, 그게 돈 버는 일에 도움이 된다.

프로그래밍이라고 다를 게 없다는 생각이 든다. 내 뜻대로 할 꺼리가 필요하다.

토이 프로젝트 수준보다는 높게, 창업보다는 낮게, 지속적으로 손을 댈 필요가 있다.

2018년 1월 14일 일요일

선물(futures) 거래와 비동기 프로그래밍의 Future

…를 엮어서 설명하는 게 있을까나?