2025년 5월 회고
5월 동안에는 TIL을 기록하는 데에 좀 더 집중했다.
부족함을 정면으로 마주하며 하루 하루를 버텨내기가 벅차, 일기를 기록하는 것이 못내 괴로웠기 때문이다. 다만 5월에는 모든 부족함을 마주하기 보다, 견뎌낼 수 있을 만큼만 마주하는 것도 정신 건강을 위해 꼭 필요하다는 것을 깨달았다.
아래는 위안이 되었던 글귀들이다.
- 어떤 사람이 내가 굉장히 힘들어 하는 일을 손쉽게 해낸다면, 그건 그 사람이 그 행동을 정말 많이 반복했기 때문이다. 그러니까 나도 그 행동을 반복하면 된다.
- 잘될 수록 겸손하고, 안될 수록 드러내야 한다. 안되는 데도 겸손하면 그건 인생에 대한 예의가 아니다.
- 단단한 사람이란 무엇이든 척척 해내는 사람이 아니라, 부족함을 마주하고도 끝까지 자기 자신을 포기하지 않는 사람이다.
그리고 앞으로는 블로그 글을 좀 더 자주 쓰고 싶다는 생각이 들었다. TIL 을 기록하더라도 두어 달만 지나면 머릿 속에서 깨끗이 씻겨져 나가버리기 때문이다.
어떤 것을 주제로 글을 쓸 지에 관한 것은 간단하다. 최소 1달이 지난 TIL 을 싹 긁어 모은 뒤, AI에게 내가 아는지 물어봐 달라고 하고, 대답하지 못한 것을 주제로 삼으면 된다.
5월 동안 쓴 토막글들
오늘의 토막글 95
- 케인즈의 고용, 이자 및 화폐의 일반이론 1편(1장)까지 읽었습니다. 단어들이 어려워서 한 장 한 장 넘기기가 무겁습니다. 고작 각주 따위에서 ‘독자들은 내가 이러이러한 지점에서 사용자비용을 공제했다는 것을 눈치챘을 것이다’ 하는 데 억울하지만 전혀 눈치 못챘고, 사용자 비용이 뭔지도 몰랐습니다. ㅋㅋ 1편에서는 고전경제학파들의 이론적 뼈대가 되는 공준이 얼마나 순진한지 대차게 까내리는 한 편, 유효수요가 무엇인지 정의하며 앞으로 펴낼 논리의 토대를 다집니다. 그런데 여기서 정의하는 내용이 현대 거시경제의 기반이라 매우 당연하게 느껴지고, 고전경제학은 왜 그렇게 생각했던 거지? 하고 의구심을 갖게 하는 게 쏠쏠한 재미를 줍니다.
- 구글 계정을 새로 만들 때 식상한 gmail.com이 아니라 커스텀 도메인을 쓰고 싶어서 예전부터 봐오던 도메인을 새로 구입하고 도메인 레코드를 연결했는데, 인증메일이 오지 않습니다. 테스트로 메일 주고받는 것 까지 다 해봤는데도요. 시간 날 때 원인을 좀 알아봐야겠어요.
- 면접관 입장에서 내가할 시스템 디자인 질문에 답이 스스로 떠오르지 않으면 질문이 망설여지는데, 그래도 그냥 해버리는게 낫겠다는 생각이 드네요. 같이 문제를 풀어가면서 합의된 해결책을 도출하는 과정을 보는 거니까요.
오늘의 토막글 96
- 4월 동안 썼던 글들을 다시 읽어보며 회고를 했습니다. 토막글에 비해 TIL 이 더 적어서 살짝 아쉬웠어요. 그래도 나름 멘탈 관리도 하고 그랬네요. 수고했고, 수고해라 ㅎㅎ
- 케인즈의 고용, 이자 및 화폐의 일반이론은 너무 어려워서 결국 GG 쳤어요. AI의 도움을 받아 내용을 주욱 정리했거든요. 모자란 지능에도 이해에 도움 받을 수 있어 다행인 세상입니다. 예전에 썼던 토막글에서 2차 산업혁명은 인간의 체력적 한계를 극복했고, 4차 산업혁명은 인간의 지능적 한계를 극복했다고 썼었는데, 그 글이 떠올랐어요. 다음 혁명에서는 어떤 한계를 극복할까요? 개인적으로는 유전적 한계 극복을 내심 기대하고 있어요.
- 여자친구가 심장 희귀병이 의심된다는 건강검진 소견이 있어 오늘 같이 병원을 갔습니다. 심장 전문의라 그런지 사람이 많아 4시간을 기다리고 결과를 들었는데 걱정했던 그 희귀병이 맞다고 합니다. 다행히 대학 병원에 며칠 입원해서 수술하면 재발율 4%밖에 안된다고, 남자였으면 군 면제였는데 아쉽다는 등 여러 이야기를 하긴 했지만 그래도 듣는 내내 마음은 무거웠어요.
오늘의 토막글 97
- 경제 뉴스 스크랩을 시작했습니다. 추경부터 액티브 ETF, 비영리법인과 공익법인의 차이 등등 몇 개만 읽어도 모르는 개념이 우수수 나옵니다.
- 환헤지에 대해 찾아보다가 뜬금 국민연금이 나와서 재미있는 이야기를 가져왔습니다. 국민연금이 2014년(2018년엔 채권까지)까지만 해도 환헤지 비율을 0%로 유지했다가, 최근 2022년에 환헤지 비율을 10%로, 지금은 15%(2024년 부)까지 운용하는 것을 원칙으로 하고 있습니다. 왜 0% 였다가 올렸냐? 하면 환율 고정으로 원화 약세로부터 오는 손실을 피하기 위해서도 있겠지만, 환율을 안정시키기 위해서이기도 합니다. 국민연금은 현재 총액이 1200조 정도 되는데, 이 중 10%인 120조만 외환 시장에 풀려도 환율에 영향을 줄 수 있는 커다란 규모이기 때문입니다. 선물환 매도 계약을 맺어서 이 날 달러가 풀릴거야라는 신호를 시장에 주면 환율 방어를 할 수 있게 되는 거죠. 이번에 계엄으로부터 촉발된 원화 약세를 방어하기 위해서도 국민연금이 쓰였습니다.
- 중국의 통화 가치는 다른 나라 통화와 상대적인 가치 비교로 정해지는 것이 아니라 중국 인민은행이 매일 아침 일어나서 ‘음, 위안화는 이 정도면 되겠군’ 하고 그냥 정해버립니다. 이걸 ‘관리변동환율제도’라고 하는데, 다른 나라 통화가 전부 약세고 달러가 강세여도 위안화는 평가 절하를 어느 정도 피할 수 있는 이점을 갖습니다. 국제적으로 환율 조작 비판을 받아오면서도 중국은 이 시스템을 계속 유지하고 있는데, 마냥 장점만 있는 것은 아닙니다. 사실 정한다기 보다는 관리한다에 가까워서요. 상대적으로 더 큰 규모의 외환보유고를 운용하여 시장의 매도/매수세를 대응하거든요. 외환은 보통 수익률이 낮은 국채로 관리를 하게 되니, 그 규모가 크면 클수록 더 큰 기회비용이 발생하는 거죠.
오늘의 토막글 98
- github cli (gh) 를 최근에 알게 되었는데, 노트북에는 왜인지 이미 설치되어 있더라구요(머쓱..). PR 올리고 머지하고할 때마다 브라우저에 들어가는 게 귀찮았었는데 좋은 도구를 찾은 것 같습니다
- 노드가 여러 개 떠 있는 redis cluster 에 해시 태그라는 개념이 있는데, 이 태그에 따라서 어떤 노드에 저장할 지 결정하고, 키-값 쌍을 분산합니다. 어차피 분산되어 봤자 노드가 3개라면 최대 3개로 분산되니, mget 과 같이 한꺼번에 여러 개의 값을 가져오는 연산에서 어차피 파이프라이닝으로 3번 다녀오는 거 아니야? 생각했었는데, 슬롯 별로 요청을 보내더라구요. 아… 잘못 알고 있었음을 깨달은 순간이었고 지금이라도 알게 되어 다행입니다.
- gpt batch API 가 나왔더라구요. LLM에게 대규모 요청을 쏘는 인터페이스고, 비동기로 처리가 되는 대신 더 많은 요청을 처리할 수 있고 비용이 저렴합니다. 거의 반값으로요.
오늘의 토막글 99
- 아침에 뉴스를 보다가 산업은행의 BIS 비율이 낮아져서 추경으로 2000억원을 추가 증자(기존 3000억원)했다는 데 이게 무슨 말이지? 싶어서 찾아봤어요. BIS 비율은 자본을 위험가중자산으로 나눈 값으로, 얼마나 위험한 걸 덜 들고 있어? 하는 값입니다. 금융당국은 13%를 건전 마지노선으로 보고 있는데(시중은행은 8%를 마지노선으로 봄) 지금은 13.9%까지 떨어진 상황이고, 이번 추경으로 0.22% 상승을 기대한답니다.
- 트럼프는 미 연준 의장을 해임하겠다느니 Mr. Too late라느니 조롱하고 난리도 아닙니다. 트럼프가 막나가는 것처럼 보여지는데 한 몫 하는 부분인 것 같은데 대체 왜 그럴까요? 결론부터 말하자면 정부 정책이 효과를 보려면 중앙은행의 협력이 있어야하기 때문입니다. 파울은 트럼프 정부와의 독립성을 가져가려는 편인 것 같고요.
- 중앙은행의 의사결정은 신탁같은 게 아닌 바, 불확실성 위에서 내려지기 때문에 틀리기도 하는데요. 정부의 압력대로 금리 인하를 진행했다가 인플레를 잡기 위해 폴 볼커가 살인적인 금리를 유지했던 것처럼, 정부가 하라는 대로 하는 게 항상 정답은 아닙니다. 하지만 그렇다고 매번 협력하지 않으면 경제가 제대로 잡히지 않겠죠.
오늘의 토막글 100
- 남해 일정을 끝내고 집에 돌아와 피곤함에 절어 밤 9시부터 잠들었는데 아침 10시에 장애전화를 받고 깼습니다. 외부 서버에 캐싱 여부를 확인하지 않았던 것(다른 API는 확인했는데 문제가 된 친구는 확인못했음), 효율적으로 구조를 개선하면서 기존에 있던 서비스 캐시를 제거했는데 요청량이랑 부하 계산을 똑바로 하지 않았던 것 이 두 가지 실수가 DB CPU를 잡아먹어서 슬로우쿼리를 유발했어요.
- 저는 군 전역 후 봤던 멘사코리아 IQ테스트에서 최고점(156)을 받은 적이 있습니다. 심지어 다 풀고 25분이 남아 끝날 때까지 엎드려 잤었어요. 다중지능이 맞다고 생각하는 입장이라 IQ가 공신력 있는 지표라고 생각하진 않지만 최근에는 예전에 비해 머리가 심각하게 많이 나빠진 것 같다는 생각을 많이 해요. 집중력도 쉽게 깨지고 사람들과 말하다 어느 순간 딴 생각을 하고, 가끔은 사람들이 무슨 말을 하는 건지 이해도 가지 않습니다. 이게 고지혈증으로 뇌로 가는 혈관이 엉망이 되어버려 그런 것이 아님을 가정한다면, 뇌가 정리가 안되어서 이렇게 멍청해진 게 아닌가 합니다. 그래서 ‘12시 전 잠들기’와 ‘시간을 내서 멍때리기’를 좀 해보려고 해요. 생각도로들이 정리가 좀 되었으면 해서요. 명상은 해봤는데 하는 방법을 잘 몰라서 그런지 무언가 나아지는 느낌은 별로 못느껴봤어요. 오 그런데 찾아보니 고지혈증과 멍청해지는 건 관련이 있다고 하는 군요.. 대박
- Jetbrain http는 python 프레임워크도 지원해 줍니다. 짱짱…
오늘의 토막글 101
- 인지행동치료(CBT)라는 것을 처음 알게 되었습니다. 부정적인 자동사고 흐름을 대안사고로 흐르도록 만들어 결국 생각의 흐름들이 더 큰 길을 따라가도록 하는 방법으로 이해했어요.
- 오늘은 카프카 렉이 생겨서 서비스가 제대로 동작하지 않던 문제를 경험했어요. 문제는 너무 늦게 발견했다는 겁니다. 처음에만 모니터링 하고 잘 컨슘되는 걸 확인했었는데 나중에 유저들이 많이 들어옴으로 인해 쌓였던 렉을 놓쳤습니다. 어떤 걸 해도 렉이 쉽게 해소되지 않아서 결국 발행하는 쪽에 부탁드려 메시지 발행속도를 조절했어요.
- 비트코인 가격이 치솟고 있습니다. 원래 비트코인은 주가를 따라갔는데, 이번에 트럼프 관세전쟁 영향으로 주가 하락하는 동안 비트코인은 반등했어요. 인플레이션 헤지 수단으로 바라보기 시작하게 된 것 같습니다. ‘디지털 골드’ 라는 별명이 생겼다는 걸 처음 알았네요.
오늘의 토막글 102
- 카프카 컨슈머의 concurrency 옵션에 대해 조금 더 자세히 알게 되었습니다. 인스턴스당 하나의 파티션을 컨슘하고 있는 상황에선 concurrency 를 늘려도 별 소용이 없더라구요. 컨슈머 코드를 뜯어볼 땐 아 이렇게 동작하는 구나 싶었는데 브레이크 포인트로 잡아보니 생각과는 다르게 동작하는 것들도 발견하고 해서 조만간에 이런 저런 헷갈렸던 부분들 정리를 좀 해야겠다는 생각이에요.
- 일을 하고 회의를 하고 하다보면 어느새 그 날의 시간이 다 가 있고, 해야할 일은 아직 많은데 진행된 건 아무것도 없고… 그 와중에 다들 공부나 정리는 언제 하나 싶어요. 잠깐 손 놓으면 순식간에 죄다 잊어버리는데 말이에요.
- 오늘은 더 쓸 내용이 없네요. 뭐 그런 날도 있는 거겠죠. 샤워하고 잠이나 자야겠어요.
오늘의 토막글 103
- 김종원의 ‘태어나려는 자는 하나의 세계를 깨뜨려야 한다’ 라는 책을 추천 받아 보고 있습니다. 별 생각 없이 시큰둥하게 보다가 ‘잘 안될 때는 겸손하지 말라. 그것은 자신에 대한 경멸이자 인생에 대한 예의가 아니다’ 라는 문장을 보고 괜히 멈추어 한 번 더 읽었어요
- 부동산 PF에서는 시행사가 프로젝트 실행 주체로써 자금을 끌어다 모으고 시공사와 협업하여 수익을 창출하고자 하지만, 사업이 실패하게 되면 법적으로 담보물을 초과하는 책임을 물을 수 없는 비소구 구조이기 때문에 도미노처럼 투자자, 금융기관, 시장, (대위변제를 했다면) 시공사까지 피해가 고스란히 퍼지게 됩니다. 너무 많고 거대한 금융 주체들이 영향을 받다보니 자금시장 전반이 위축되는 결과도 가져오는데요, 부동산 시장을 볼 때 미분양 아파트 비중을 보는 이유가 바로 여기에 있습니다. 정부도 이런 구조 때문에 부동산 거품을 꺼뜨리지 못하고 있는 모양새인데, 보다 더 지속 가능한 구조가 만들어져야 할 시점이지 않을까(사실 진즉부터…) 싶어요.
- 그거 아시나요? 연어 메밀면 샐러드 처음 먹어봤는데 존맛이에요. 두 번 연속 먹었는데 둘 다 맛있었으니 이건 추천할 만 하다고 봅니다.
오늘의 토막글 104
- 아침에 뉴스를 보는 김에 대선 후보들의 공약을 한 번 슥 훑어보았습니다. 뉴닉에서 정리를 잘해둔 것 같더라구요
- 어제 오늘 저녁 약속에서 많이 주워듣고 배웁니다. 새로운 사람들과 만나 이야기하면 여러 생각과 인식이 오가는 게 재미있어요. 더 깊은 이야기를 나눌수록 더 재미있는 거 같구요
- Redis 에서 0.1초마다 만료키 삭제될 때 전체 키 중 샘플링을 해서 그 중 삭제해야하는 키가 25%가 넘으면 다시 삭제를 돌게 되는데 이로 인해 성능저하가 발생할 수 있습니다. 그래서 일괄 만료시키기 보단 jitter 를 넣어서 만료시각을 흩뿌리는 게 좋아요. 최근 redis 문서 찾아서 따로 정리해봐야겠어요
오늘의 토막글 105
- 지금껏 도전해본 적 없던 1일 1커밋에 도전해보려고 합니다. 범근님의 추천을 받아 핸드폰 화면에 깃헙 잔디 위젯을 보이게 해두었어요. 물론 자극도 같은 사람에게 받았습니다.
- 최근에 마음을 다루는 방법에 대한 깨달음을 얻었어요. 손에 쥔 걸 내려놓아야 다른 걸 집을 수 있다는 것, 쥘 땐 긍정적인 생각을 더 많이 쥐어야 한다는 것, 그리고 부정적인 생각 역시 그 역할이 있어 너무나 소중하다는 것. 요근래 조금 힘들었는데, 이걸 깨닫고 마음이 많이 편해졌습니다.
- 오늘 가까스로 회사 업무 외에 1일 1커밋에 성공했어요. 18줄 짜리 수정이었는데 이거 은근히 기분이 좋네요 ㅋㅋ
오늘의 토막글 106
- 오늘 범근님이랑 점심먹으며 이야기를 나눴어요. 이런저런 이야기가 오가다 인간의 욕망은 집착할수록 멀어진다는 이야기를 해주시더라구요. 잘하고 싶은 마음에서 그걸 얼핏 느끼고 있었는데 생각해보니 사랑도, 돈도, 명예도 다 그런 것 같더라는 생각이 들었습니다. 생각 지평이 조금 더 열리는 듯한 느낌을 받았어요
- 어처구니 없는 실수로 생각지도 못하게 사고를 쳐서 수습 중입니다. 구현이 끝난 뒤 테스트할 때도, 셀프로 코드 리뷰를 할 때도 결국 아는 선 안에서만 테스트와 리뷰를 하게 되니 모르는 부분에 대해서는 끝내 사고로 이어지는 게 반복이 됩니다. 앞으로는 작업에 앞서 문서를 작성할 때 롤백 계획까지도 같이 적어두는 게 좋겠고 커밋도 더 잘게 쪼개야겠습니다.
- 새로 배운 걸 적는 거나, 구현 전 문서 작성하는 거나, 커밋 쪼개는 연습은 개발자를 시작한 지 얼마 되지 않았을 때 하던 연습들인데, 개발에 대한 열정이 식으면서 어느 순간 놓아버렸던 것 같아요. 지금도 개발이 영 신나는 건 아니지만 그래도 최소한의 전문성을 위해선 역시 기본기가 가장 중요하고 바탕이 되어야 한다는 사실을 상기했습니다.
오늘의 토막글 107
- 함양에 가족여행을 가서 총합 16시간 정도 운전을 했습니다. 운전에 전혀 익숙하지 않았는데 이제 좀 익숙해진 것 같아요. 그리고 … 인생 첫 교통사고를 냈습니다. 다차선 로터리에서 빠져나와서 차선 변경을 하다가 뒤에서 이제 막 나오는 차량과 충돌했는데요, 로터리는 차선이 곡률이 있어서 그런지 사이드랑 백미러를 봤는데도 헷갈리더라구요. 대물접수도 처음 해봤어요.
- 여행가는 바람에 1일 1커밋에 대차게 실패했어요. 노트북은 챙겨갔지만 하루종일 긴장한 채로 운전하니까 온 몸이 뻐근해서 도저히 할 수가 없었습니다. 이제 다시 시작해보려고 합니다. 1일 1토막글도요.
- 오랜만에 수영을 했습니다. 몸은 그래도 기억하더라구요. 그런데 체력이 확실히 형편없어져서 이제 주 중에도 운동을 가보려고 해요.
오늘의 토막글 108
- 오늘은 출근 길에 뉴스 보면서 끄적거렸던 내용으로 후딱 대체해봅니다
- 원화 스테이블 코인에 대한 기사를 흥미롭게 봄
- 지난 21일 국회의원회관에서 원화 스테이블 코인을 발행해야 한다는 목소리가 나옴
- 현재 스테이블 코인 시장은 미국 테더와 써클이 시장점유율 90퍼를 차지하고 있음
- 이런 스테이블 코인은 가치가 달러같은 법정 통화에 페깅(유동성 조절로 가치 고정)하고 발행시마다 미국채를 담보해야한다는 점에서 기존 비트코인과는 다름. 탈중앙화된 통화라는 비전을 정면으로 벗어남. 결국 원하면 언제든 달러로 바뀔수 있기 때문
- 하지만 실용성을 생각하면 어쩔 수 없는 선택이었음. 가격변동이 너무나 가파르면 그걸로 결제나 송금을 할 수가 없기 때문임. 게다가 스테이블 코인을 사용하면 해외 송금이 엄청나게 간편해지고 수수료도 1,000원 남짓임. 실제로 국내 상품을 구매하기 위해 해외에서 스테이블 코인으로 결제/송금 하겠다는 니즈가 점점 커지고 있다고 함.
- 개인적으로 스테이블 코인이 탈중앙화를 벗어났기 때문에 그 가치를 별로 느끼지 못하고 있었으나 스테이블 코인을 도입함으로써 얻는 여러가지 이득과 미국의 스테이블코인 법안(GENIUS Act) 통과로 다시금 그 가치를 생각해보게 됨. 분명한 건, 디지털 금융에 뭔가 큰 물결이 일고 있다는 것임
- 미국정부는 지금 빚이 최고한도(36조달러)까지 차있고 곧 만기 상환이 다가오고 있어 단기채를 발행 후 되갚는 행태를 반복하고 있음
- 이런 상황은 인플레를 억제하는 힘도 떨어질 뿐더러 단기채를 찍어내는 바람에 채권 가치도 상실하기 때문에 경제적으로 위험한 상황임
- 스테이블 코인은 앞서 언급했듯 국채를 담보로 하기 때문에 공급을 받아내는 수요처가 될 수 있음. 그래서 이 때문에 트럼프가 CBDC가 아닌 민간 스테이블 코인에 집중하고 있다는 이야기도 나오고 있음
- 실제로 현재 테더와 서클은 미국채를 한국보다도 큰 규모로 가지고 있음. 한국은 미국채를 꽤나 많이 가지고 있는(1200억 달러) 나라중 하나임.
- 36조 달러와 1200억 달러를 비교하면 차이가 많이 나긴 하지만 미국 입장에선 단기채로 돌려갚기 하는 것보단 나을 것임
오늘의 토막글 109
- finviz 라는 stock screener 사이트를 알게 되었습니다. 주식 투자할 때 참고하면 좋을 것 같습니다
- 틈틈이 https://owl.zoobox.life/ 라는 사이트를 만들고 있어요. 언론들의 보도 편향을 벗어났으면 하는 니즈에서 출발했고, 1일 1커밋을 하고 있는 그 친구에요.
- VRRP에 대해 알게 되었어요. 여러 네트워크 장비에 하나의 가상 IP를 묶어서 master 노드 장애시 failover 를 해주는 프로토콜이에요. failover 당시 ARP 캐싱이 되어 있거나 순단이 지속되면 패킷 누락이 생길 수 있어요
5월 동안 쓴 TIL
DB Wait timeout vs HikariCP idleTimeout, maxLifetime 설정
- DB wait timeout은 DB 에서 연결을 기다리다 일정 시간이 지나면 연결을 끊는 설정이 아니라, DB에 연결을 맺고 있지만 너무 오랫동안 아무 쿼리도 보내지 않고 놀고 있으면 연결을 끊어버리는 설정임
- 이로 인해, DB 서버에서는 커넥션을 끊어버렸는데 애플리케이션에서 모르는 경우가 발생함
- 이렇게 끊어져버린 상태에서 쿼리를 날리게 되면 Communications link failure 같은 예외가 발생함
- HikariCP 에서는 maxLifetime 이라는 설정이 있는데, 위와 같은 경우를 방지 하기 위해 wait_timeout 보다 이 값을 더 적게 설정해야 함
- 왜 idleTimeout 이 아니라 maxLifetime 을 조정해야 하느냐? 하는 질문이 남는데, 이건 idleTimeout 이 정확하게 그 시간을 보장하지 않기 때문에 예외가 여전히 발생할 수 있음
- 만약 쿼리가 실행되는 도중에 maxLifetime이 지나면, Hikari는 쿼리 결과를 받는 즉시 해당 커넥션을 폐기하고 새로운 커넥션을 맺음
카프카 파티션이 리벨런싱되는 경우 순서 보장은 어떻게 할까?
- 파티션이 3개라서 키가 3개로 분산해서 들어가고 있었는데 갑자기 5개로 파티션이 늘어나는 상황
- 안정해시를 쓴다고 하더라도, 마이그레이션이 필요한 메시지가 생기기 때문에 순서를 보장할 수 없음
- 이전 3개 였을 시점에 쌓인 렉 때문에 여러 파티션에 같은 컨슈머가 가져가야 하는 메시지가 공존하는 상황이 발생하기 때문
- 이럴 경우 순서 보장이 중요하다면 redis 같은 외부 저장소를 사용하는 게 가장 깔끔함
- 처리를 할 때 마다 offset 이 +1 되었을 때만 처리하도록 하고 offset이 건너뛴 메시지는 재처리
- 그렇지 않은 메시지는 redis 에 쌓아두었다가 매번 redis 검사해서 순서 보장하여 처리
- 다만 이렇게 되면 kafka 트래픽이 심한 상황에서 redis가 병목지점이 될 수 있으니 신중하게 생각해야 하고, 유실 후 재처리를 하는 게 나을 수도 있음
파이썬이 멀티 코어를 쓸 수 없는 이유
- 파이썬은 java 로 치면 GC를 하기 위해 reference counting 을 사용함
- 멀티 코어에서 이 counting 에 접근하게 되면 동시성 이슈가 생김
- 그래서 GIL(Global Interpreter Lock)을 걸어 한 순간에 단 한 개의 스레드만이 코어를 점유하게 함
- 이 때문에 CPU bound 작업에는 병렬처리가 안되어 스레드를 나눠서 병렬로 계산하는 것이 의미 없음
- 이를 우회하기 위해 multiprocessing 을 대신 사용
- 예외적으로 IO bound 작업에서는 기다리는 동안 파이썬 코드가 실행되지 않기 때문에 GIL을 해제하여 동시성을 확보함
- 파이썬 코루틴 역시 GIL 을 거치기 때문에 동시성을 획득하나 병렬성은 획득하지 못함
- 그렇지만 멀티 스레드와는 컨텍스트 스위칭이 발생하지 않는다는 점 등이 다름
AWS Lambda cold start
- 서버리스로 람다를 구동시키면 API 엔드포인트마다 콜드 스타트 문제가 발생함
- 매 엔드포인트 마다 람다가 따로 동작하고, 이 말은 곧 독립된 컨테이너에서 어플리케이션을 매번 구동하는 것임
- 이를 해결하기 위해 모든 API 엔드포인트를 단일 람다로 구성할 수도 있음
- 혹은 Provisioned Concurrency 를 사용할 수 있는데, 이는 지정한 수만큼의 Lambda 실행 환경을 항상 워밍 상태로 유지하는 옵션임(추가 비용 발생)
- Chalice 는 AWS 서버리스를 구현하기 위한 프레임워크인데, 아직 Provisioned Concurrency 를 지원하지 않고 있음
Github CLI - gh: 알게 된 뒤로 브라우저 열 일이 줄어듦
- gh issue create: 이슈 만들기
- gh issue list : 이슈 보기
- gh issue view: 이슈 상세 보기
- gh issue develop {
| } --name {브랜치 이름} --checkout : 해당 이슈와 연결된 브랜치 만듦. - gh browse: 브라우저에서 현재 레포 열기
- gh pr create : PR 만들기
- gh pr list : PR 리스트 보기
- gh pr checkout {
| | }: PR 브랜치를 가져와서 체크아웃함 - gh workflow list: 깃헙 워크플로우 목록 보기
- gh workflow run: 워크플로우 실행
Redis cluster hash slot 분산
- redis cluster 에서는 hash slot 을 지정하기 위해 {} 를 쓰게 되는데, redis-key-user:{userId}:key 이런 식임
- 예를 들어 userId 가 같다면 hash slot이 같아지므로 같은 노드에게 요청하게 되어 클러스터 내의 모든 노드를 탐방하지 않아도 됨
- 그런데 어차피 해시 슬롯을 강제로 지정하지 않아도 redis에서는 알아서 해시 슬롯 중 하나에 할당함
- userId 를 % 19 이런 식으로 mod 를 하게 되면 0~16384 범위 중 아주 초반부만 쓰게 되니, 노드 쏠림 현상이 있지 않을까 하는 의문이 있었음
- 그런데 생각해보면 어차피 숫자여도 문자열로 처리되는데다, 이걸 해시 슬롯으로 바꾸기 위해 CRC16 연산을 돌리기 때문에 딱히 걱정할 필요가 없었음
- 다만 mget 과 같은 다중키 연산에서는 슬롯 마다 요청을 하게 되어 트레이드 오프를 잘 계산하여야 함
- 또, 모든 키들의 해시 슬롯이 같지 않으면 CROSSSLOT 이라는 오류가 발생하는데, 결국 노드 3개면 3번 나눠서 mget 을 해야 함. 이건 lettuce 같은 cluster aware client 에서 자동으로 해줌
web RTC 의 동작 원리
- websocket 과 다르게 web RTC는 서버를 거치지 않고 다이렉트로 직접 P2P 연결 한 뒤 데이터를 송수신함
- 서버를 거치지 않고 피어 투 피어로 데이터를 스트리밍 하기 때문에 레이턴시가 적어 음성/화상 데이터 처리에 용이함
- 그런데 나는 상대방의 공인 IP 밖에 모를 텐데 NAT gateway 를 지나고 나면 어떻게 상대 Peer 에 도달할 수 있을까?
- 이결 하기 위해 STUN과 TURN 이라는 기술을 사용함
- 각 Peer 는 STUN 서버에게 자신이 공인IP/Port 정보를 획득
- P2P 시그널링을 통해 해당 정보를 각 Peer가 공유
- P2P 연결 시도(Hole Punching)
- 실패 시 TURN 서버 경유(사실상 P2P가 아닌 중계 서버)
- 라우터는 내부 패킷이 라우터 밖으로 나갈 때 NAT 테이블에 저장하고 나중에 같은 목적지로부터 패킷이 들어오면 테이블을 참조해 원래 소스로 라우팅함
- 이를 이용해 A , B 가 서로 UDP 패킷을 보냄(Hole Punching)으로써 NAT 테이블을 채워두는 것 (참고로 TCP는 핸드쉐이크가 필요해서 안통함)
- 그러나 학교/대기업 같은 대규모 라우팅 환경에서는 NAT/방화벽 설정이 달라 이 방법이 통하지 않을 수 있어서, 실패시 TURN 서버를 경유함
- STUN 서버는 대부분 무료 공개 서버를 활용하거나 직접 운영하지만, TURN 은 트래픽 부담때문에 사용서비스나 직접 운영이 필요함
Hole Punching
- A, RouterA(NAT), Internet, RouterB(NAT), B 이렇게 P2P 네트워크 구간이 있다고 할 때
- A, B 는 STUN 서버 통해서 서로의 공인 주소(RouterB, RouterA)를 조회함
- NAT 테이블 특성상 내부 -> 외부 통신은 막지 않으며, 이 매핑 정보를 기록함
- A 는 B의 공인 주소로 UDP 패킷을 보냄으로써 RouterA에 A 와 RouterB 네트워크 정보 매핑을 기록
- B 는 A의 공인 주소로 UDP 패킷을 보냄으로써 RouterB에 B 와 RouterA 네트워크 정보 매핑을 기록
- 시그널링 서버(자체 운영)가 각 Peer 에게 시그널을 보내면 A, B 가 동시에 위 과정을 수행
- 그렇게 되면 서로가 통신할 수 있는 구멍이 뚫림
- 이 과정을 표준화 한게 ICE 프레임워크임
- 카프카 컨슈머 수평확장으로 처리량을 늘렸다가 DB 부하가 심해져서 쓰로틀링을 고민하게 됨
- 이를 통해 컨슈머 수를 다시 줄임으로 인해 발생하게 될 리벨런싱을 회피하고 싶었음
- 파티션 6개, 인스턴스 1개, consumer concurrency 3으로 ListenerContainer 당 파티션 2개 할당으로 테스트
- Thread.sleep()을 걸게 되면 max.poll.interval.ms 내로 poll()이 발생하지 않게 되어 브로커가 컨슈머를 비정상으로 판단하고 리벨런싱이 일어날 수 있음
- 이를 피하기 위해 pause()와 resume()을 사용함. pause()를 하게 되면 주기적으로 poll()을 통해 empty records 를 가져옴
- ConcurrentMessageListenerContainer 내부의 3개 MessageListenerContainer 에 각각 pause / resume 를 하도록 수정함
- 그랬더니 이미 해당 컨테이너가 pause 상태라는 로그 메시지가 찍히기 시작
- Record 리스너는 pause()를 걸더라도 직전 poll()로 가져온 records 가 전부 처리되기 전까지 pause 되지 않고 그로인해 중복 pause 가 발생 가능함
- Batch 리스너는 records 를 모아서 한 번에 처리하기 때문에 원하는 시점에 pause 가 가능함
- Record 리스너에서도 ConsumerInterceptor 를 사용하면 어찌저찌 구현이 가능하긴 하나, 복잡해짐
월마트의 Trillion message kafka setup
- 월마트에서 주로 카프카 운영하면서 겪는 과제 3가지
- 컨슈머 리벨런싱
- 컨슈머 그룹에 컨슈머 파드가 가입/탈퇴하는 경우
- 컨슈머가 종료되어 버리거나 JVM gc stop-the-world 로 인해 heartbeat를 브로커로 전송하지 못해 비정상 판단
- heartbeat는 지속하지만 poll() 을 임계값이상 멈춰버린 경우
- poison pill message - 재처리 해도 계속 실패하는 메시지
- 잘못된 데이터 (invalid json format, required field 누락 등)
- 컨슈머 로직 버그(NPE 등)
- 비용 문제
- 파티션당 컨슈머가 하나씩 붙어 있다면 더 많은 컨슈머를 허용하려면 파티션 증설이 유일한 방법임
- 브로커당 4,000개 파티션을 가지고 있는데 이 수를 더 늘리려면 브로커 인스턴스를 증설해야 하고 이건 꽤나 비용임
- 컨슈머 리벨런싱
- 이런 과제들을 극복하기 위해 MPS(Messaging Proxy Service)를 설계 & 구현함
- 이를 구현하기 위해 Kafka Connect 를 사용, MPS는 카프카 싱크 커넥터임
- Reader, Writer thread 를 나눠서 Consumer 에 REST api 를 콜하는 방식으로 proxy함
- pending queue 를 두어 처리를 쓰로틀링하고 큐가 차면 pause(), 비워지면 resume() - 리벨런싱 해결
- Http code 를 정의해 DLQ 넣을지, Retry 할 지 등을 규약으로 정함 - poison pill message 해결
- 일부러 stateless 하게 설계해 확장에 용이하게 함. 이를 통해 비용 문제 해결
- Passive way와 Active way 가 있음
- Passive way
- 만료된 키에 접근하면 그때야 비로소 해당 키를 제거함
- Active way
- TTL 값이 있는 키 20개를 랜덤하게 뽑음
- 뽑은 키 중 만료된 키 제거
- 위 과정에서 25%이상 키가 제거되었다면 아직 만료할 키가 더 많은 것으로 보고 샘플링을 다시함
- 이 과정에는 타임아웃이 있어서 너무 많은 시간을 소요하면 반복문을 탈출함
- 25%는 옛날기준이고 지금은 설정 가능한 것으로 보임, CPU 얼마나 넉넉한지 보고 해당 비율을 결정함
- 즉, 만약 옛날버전 redis 를 쓰고 있다면 해당 로직을 잘 확인해서 일제히 만료시키지 않고 jitter 를 두어 키 만료 시점을 분산하여야 함
- 참고로 만료 키 삭제는 unlink 처럼 keyspace 삭제만 하는게 아니라 memory free 까지 같이 함
- 근데 이것도 설정으로 변경 가능(redis.conf 에서
lazyfree-lazy-expire
설정을 yes 로 두면 비동기 free)
- 근데 이것도 설정으로 변경 가능(redis.conf 에서
frontend 프로젝트에서 리버스 프록시가 필요한 이유
- 서버가 도메인이 달라지는 경우 CORS 문제가 생김
- CORS 는 브라우저가 출처(origin)가 달라서 못믿겠다면서 응답 안받는 것
- 이 때 nginx 같은 리버스 프록시가 있다면 같은 도메인에서 받고, 프록시해서 응답 갖다줌
- 서버가 http 고 frontend 가 https 인 경우 mixed content 문제가 생김(반대는 문제 없음)
- Mixed Content 는 브라우저가 암호화된 https 컨텐츠 사이에 평문 http 가 섞여 있어서 못보여주겠다는 것
- 이 때 nginx 같은 리버스 프록시가 ssl termination 을 수행하고 백엔드에 http 로 전달
- 인증서는 nginx 에서 일원화해서 관리 가능
- 성능
- 정적 파일(.js, .css, .jpg, etc)을 nginx 가 gzip 압축, 캐시 헤더 추가해서 빠르게 서빙
- 보안
- 이 외에도 1차적으로 gateway 역할을 수행하면서 내부 인프라를 보호할 수 있음
1억 비트를 and 연산하면 시간 복잡도가 어떻게 될까?
- 1억 비트는 메모리로 치면 12.5메가바이트임. 1억 비트 2개 and 연산한다고 치면 적어도 25메가 바이트가 필요
- 따라서 힙 메모리 할당이 필요하고, 바이트 배열이나 BitSet 클래스를 활용해야 함
- 할당을 하는 데에도 O(n)의 시간 복잡도 가 듦
- 결국 잘라서 처리를 하기 때문에 O(n)의 시간복잡도가 듦
- 256x256 사이즈의 8바이트 비트 연산 결과를 모두 저장한 룩업 테이블을 두어 처리하는 것도 괜찮은 방법
- 그렇게 되면 (할당 제외) O(n/8)이 됨
SNI(Server Name Indication)란 무엇인가
- TLS 프로토콜의 확장 기능으로, 핸드셰이크 과정에서 클라(여기선 nginx)가 서버의 호스트 이름을 미리 알려주는 것
- AWS 서비스들은 SNI를 필수로 요구함. SNI가 없으면 핸드셰이크 단계에서 연결이 거부(
SSL_do_handshake() failed
)됨 - 참고로, AWS 에서는 기본적으로 TLS1.2v 부터 지원하는데 그 미만 버전은 보안 취약점이 있음. (SNI 는 TLS1.0v 부터 추가됨)
- Nginx 는
proxy_ssl_server_name on;
설정을 통해 SNI 확장을 지원할 수 있음 - 즉, ec2 -> nginx -> api gateway (aws)-> backend 의 과정에서 api gateway 로부터 악수 거절을 안당하려면 상대방 이름을 알고 있어야 한다는 것이 됨
- 하나의 서버가 여러 도메인을 처리하는 경우에도 유용하게 쓰임
- 예를 들어 api.myserver.com, web.myserver.com, admin.myserver.com 과 같이 같은 도메인을 쓰는 경우 하나의 nginx 프록시에서 처리가 가능함
- 각 도메인 별로 다른 백엔드로 라우팅이 가능하고, 인증서도 도메인 별로 따로 쓸 수 있음
try-catch-finally 에서 finally 가 실행되지 않는 경우
- try-catch-finally 를 포함한 Daemon Thread 가 도는 도중 JVM 강제 종료되는 경우
- User Thread 의 경우에는 강제 종료해도
finally
가 실행됨 - Daemon Thread 는 스레드의 상태와 무관하게 JVM이 기다리지 않고 곧바로 종료시키지만,
- User Thread 의 경우에는 강제 종료해도
- kill -9 와 같은 프로세스 킬
- 이 때는 User Thread 더라도
finally
가 실행 기회 없이 프로세스 강제 종료됨 SIGKILL
이 아닌SIGTERM
으로 프로세스를 종료하는 경우에는finally
가 실행됨 (InterruptedException
을 catch 함)SIGTERM
은 Daemon Thread 의 경우에는finally
가 실행되지 않음- JVM은 User Thread 만 기다리기 때문임
- 이 때는 User Thread 더라도
- 무한루프로 OOM, SOF 등 JVM 크래시 유발하는 경우
- 이 때에도 User Thread 더라도
finally
가 실행 기회 없이 프로세스 강제 종료됨
- 이 때에도 User Thread 더라도
Runtime.halt()
호출- 인터럽트 불가능한 무한 루프
- 데드락으로 인한 무한 대기
Raft Consensus 알고리즘
- Raft consensus 는 분산 노드 클러스터 환경에서 노드 다운 시 전체 시스템이 일관된 상태를 유지하게 하는 합의 알고리즘임
- 전체 시스템이 일관된 상태를 유지한다는 의미는 모든 노드가 동일한 명령을 같은 순서로 실행한다는 것임
- 다음과 같이 동작함
- 리더는 주기적으로 follower 에게 ‘아빠 안잔다’ heart beat를 보냄
- follower 는 heart beat 를 못받으면 스스로 candidate 가 됨
- candidate 가 vote 요청을 하면 다른 노드들이 한 번씩 투표하고, 과반수가 동의하면 leader 로 승격함
- 클라이언트가 leader에게 명령을 보내면 일단 자기 로그에 기록하고 follower 에게 복제하라고 시킴(AppendEntries 메시지 전송)
- follower 들이 과반수가 나 복제했음! 응답을 주면 leader 는 로그를 commit 하고 client 에게 처리 완료 응답을 보냄
- 즉 기본적으로 follower 과반수가 살아 있으면 문제 없는 상태로 보는 간단한 알고리즘임
- 너무 간단하니 여러가지 의문점이 듦
- 투표가 동률이라면? - 다시 재선거를 함
- 네트워크 지연으로 구 리더가 아직 자신이 리더라고 생각하는 split brain 상황이라면? - 리더가 선출될 때마다 heart beat의 term 이라는 카운터가 증가하는데, 나중에 네트워크가 복구되면 구 리더는 자신이 out of date 됨을 깨닫고 스스로 follower가 됨
- 클라이언트는 리더가 바뀌었을 때 어떻게 알아차림? - 리더가 아닌 노드가 응답을 받으면 리더에게 redirect 시킴
- 로그 복제에 실패하는 경우? - follower 가 누락분 재요청을 하고 리더가 AppendEntries 재시도해서 log index 를 맞춤
- 이 합의 알고리즘은 k8s의 etcd 에서도 쓰이고, impala KUDU 에서도 쓰이고, zookeeper 를 버린 Kraft(Kafka + Raft) 에서도 쓰임
- redis 클러스터의 경우에는 합의 시 Raft 를 쓰고 있지 않은데, 그 이유는 Consistency 를 포기하고 Availability 를 선택했기 때문임
VRRP (Virtual Router Redundancy Protocol)
- 여러 대의 라우터를 묶어서 하나의 IP를 서빙하는 프로토콜
- 한 대의 라우터가 Master 가 되고 나머지가 Backup 으로 동작함
- Master 가 죽으면 Backup 중 하나가 Master 역할을 이어받음
- 이 때 외부에선 Virtual IP 로 보여지기 때문에 게이트웨이가 항상 살아 있는 것처럼 보여짐
- 마스터가 교체될 때, Backup 중 우선 순위가 높은 장비가 즉시 자신의 네트워크 인터페이스에 VIP를 바인딩하고 트래픽을 받기 시작함
- VRRP에서 패킷 손실이 발생할 수 있는 경우들
- 위와 같이 failover 일어나는 경우 (Master 죽고 새로 교체되기까지의 트래픽)
- TCP 세션이 끊어져서 교체 이후에도 retry 하지 않으면 패킷 추가 손실 가능
- Client 가 ARP 캐싱으로 예전 master mac 으로 패킷을 전송하는 경우
- 이를 방지하기 위해 교체 즉시 Gratuitous ARP 를 클라이언트에게 보냄(VIP의 새 mac 주소를 알리는 것)
HashMap은 조회 성능이 정말 O(1) 일까?
- HashMap 은 기본적으로 해싱된 키를 기반으로 O(1) 으로 값을 조회를 할 수 있음.
- 기본적으로 배열(bucket array) 자료구조를 떠올리면 되는데, 해시함수는 Capacity - 1 으로 mod 했을 때의 배열 인덱스로 들어가게끔 해시코드를 생성함
- 이 때 capacity 를 2의 배수로 강제하면 capacity - 1 로 mod 하는 것을 단순 and 연산으로 치환 가능
- 그런데 당연히 해시함수 구현에 따라 같은 배열 인덱스를 가리키는 경우가 있음
- 이렇게 되면 체이닝(여러 엔트리를 한 배열 인덱스에 묶음)이 일어나고, 한 배열 안에서 해시 값이 완전히 일치하면서 key.equals() 가 true인 인덱스로 엔트리를 찾음 = O(n)
- 체이닝이 너무 많이 되면 성능이 떨어지니 체이닝 사이즈가 8 이상이 되면 레드 블랙 트리로 체이닝 트리를 만들어 조회 성능을 O(log(n))으로 최적화 함
- 배열 크기가 작으면 해시 충돌이 너무 많이 생겨 체이닝 사이즈가 늘어날 것. 그래서 load factor 를 두어 capacity 를 2의 배수로 늘림
- java에서 load factor 는 0.75이고, 이 값이 넘어가서 capacity가 resize되면 체이닝되었던 트리들도 rebalancing 이 일어남
- 따라서 HashMap의 조회 성능은 항상 O(1)이라고 말할 수는 없으며, 평균적으로 O(1)이다가 더 맞는 표현임
RSS(Resident Set Size)
- RSS 는 프로세스에 의해 물리적 RAM에 얼마만큼의 메모리가 할당되어 있는지를 나타냄
- Linux와 k8s 컨테이너 환경 모두 cgroup 으로 리소스의 할당 제한을 컨트롤함
- cgroup은 2006년에 구글에서 개발해 리눅스 커널에 포함시킨 기능으로, 프로세스 그룹 단위로 리소스(cpu, memory, network)를 제한하고 격리시킴
- 다만 리눅스는 프로세스의 rss가 다 찬다고 해서 곧바로 oom killer를 작동시키진 않고, 오래된 메모리를 swap해서 여유 공간을 확보함(swap in/out)
- 이 스왑은 RAM이 아니라 디스크에 메모리를 옮겨두는 것이고, 스왑된 메모리를 접근하게 되면 page fault가 발생하여 프로세스가 중단되었다가 실행되는 데, 이는 RAM 대비 수천배 느림
- 예를 들어 JVM GC 가 stw한 뒤 스왑 메모리에 접근하면 중단이 크게 발생하게 될 수 있음
- 그래서 고성능이 필요한 분산 환경에선 swap 옵션을 끄는 것, 기본적으로 k8s 환경은 엄격한 하드리밋을 함
- 참고로, rss라는 이름은 귀중한 땅인 RAM 안에 거주하는(Resident) 놈들(set) 이라서 그렇게 지어진 것
Lua 스크립트 사용 시 주의사항
- Redis는 싱글 스레드 이기 때문에 Lua 스크립트안에 헤비한 연산이 들어가지 않도록 주의해야 함
- 예를 들어 무한루프가 스크립트 내에 들어가게 되면 해당 redis 노드에 들어가는 모든 연산이 중지됨
- 이 때 실행 시간이 기본 설정값(5초)를 넘어간다면 뒤 따라 들어오는 다른 요청들은 대기하지 않고 fast fail 시킴
- SCRIPT KILL 명령어를 통해 돌고 있는 루아 스크립트를 중단 시킬 수 있지만, WRITE 연산이 한 번이라도 진행되었다면 중단이 불가능
- redis 핵심 철학인 원자성에 문제가 생길 가능성이 있기 때문에 곧장 에러를 뱉게 됨
- 결국 redis 의 모든 메모리를 포기하고 redis 프로세스를 강제 종료한 뒤 노드를 재시작하는 것 외에는 답이 없음
- 따라서 어지간하면 100개를 넘어가는 연산을 스크립트 하나 안에 다 담지 않도록 주의가 필요함.