TIL로 좀더 편하게 정리 한 글입니다.
여러 관점, 생각 과정으로 봐주시면 감사하겠습니다.
TMI
알림 서비스 개발하면서 생각보다 볼게 많고, 확인할 게 많고, 그러다 혼자 좀 빠져있다가 ... 사알짝 번아웃인가 ... 이번 주 많이 내려놓으며, 오늘 좀 다시 봐볼까 싶어 졌습니다 ㅎㅎ
어떻게 정리해야 할지 고민이 많았습니다. 지금도...
- Long-Polling과 SSE 차이에서 느낀점은 실시간 DB 조회와 갱신로직이 아예 삭제 된 점이었습니다. 너무 매력적이었어요.💛
- Redis 구현 하며 본 Pub/Sub 패턴
- 토비님 유투브로 Observable과 Pub/Sub 구현을 보며 pull와 push 차이점이 나왔고, 짧게 Reactive stream 을 보았습니다. (유투브는 재밌게 보고 이해한 듯 한데, 왜 모르겠지? 🤫)
- 백기선님 디자인 패턴 Obserble을 봐 봤어요. 채팅 구현 해보며 Redis 가 이런가? (싱글스레드와 이벤트 루프-_-, 카프카는 어떻게 보관하고 있지? stop).. 스프링 예시에서 stop (모르겠어요. 묻지 말아주세요.🤫)
- Pull 모델과 Push 모델을 알게 됐어요.
- Fan Out On Read, Fan out on write 로 페이스북과 트위터 요구사항과 로직들 stop ?🤫
- 거기서 인덱스, 트랜잭션 ...MySQL 다시보고 DB I/O 처리방식 등 트레이드 오프 stop ?🤫
- 알림 서비스와 뉴스 피드 설계 예시들 🤖
- HTTP 커넥션, 웹소켓 ???🤫
- 그 외 ... stop
패턴이나 방식들이 있는거 같은데, 관련하여 좋은 책이나 정보 알려주시면 감사하겠습니다. 💛
Polling
Polling(Long-Polling)을 보면 요청하고 응답하는 웹요청의 일반적인 거 같은데 왜 폴링이라 할까? 실시간이 관련있나? 의문이 들었습니다.
구현 해보자로 시작 했었습니다.
- 생각보다 할 게 많았다.
- 구현하며 느낌점들은 폴링의 문제 였던 거 같습니다. 그렇게 보게 된 다른 예시는
인터럽트
였습니다.
인터럽트와 폴링
- 반복
- 알림이 없어도 클라이언트는 요청 합니다.
- 알림이 없어도 조회해서 체크 합니다.
- 알림이 없어도 커넥션을 가지고 있어요.
- 계속 반복
서버하나 = 프로세스 하나가 작업하는 걸로 생각하면 CPU에 일정 시간 위의 반복과정이 진행 돼야 합니다.
- 스레드가 여러개라 하더라도, CPU 하나만 있다 생각하면 ...
OS만 봐도 주요 작업은 늦춰 집니다.
주요 로직과 분리가 중요하게 생각 됐습니다.
- DB 알림 테이블을 다른 도메인에서 사용하지 않기
- MySQL의 경우 인덱기반 잠금 처리 되고, 읽기 공유로 잠기지는 않지만, 변경시 쓰기잠금 발생
그래서
- 스케줄러는 별도로 동작하게 했는데, CPU 하나면 어떻게 ? 의문 (필요없는..? 구현하면서... 공포 요소가 많아요)
- 트랜잭션 단위를 최소화 하는 리팩토링 작업을 했습니다. (정리해서 블로그 올리려고 했는데,.. 모르겠네요)
위의 과정을 운영체제는 어떻게 했을까?
- CPU는 작업에 집중하면서 외부에서 알려주게 해서 전달한 정보를 확인하면서 처리하는 방식으로 진행 합니다.
(개인적으로 느낀점이라, 맞는지 모르겠지만,) SSE 로 구현하면서, 클라이언트 요청이 subcribe로 기록되어있고, 외부에서 댓글이 달렸을 때 알림 발생할 때 subcribe 를 확인하여 응답 합니다.
Pull 모델, Push 모델
자세한 내용은 토비님 유툽
pull 🆚 push 에 대해서 Java의 Iterable 과 Observable 로 설명해 주십니다.
제가 봤던 키포인트 1가지 : 메서드로 DATA 를 받는지(가져오는지), 전달하는지(밀어내는지) 설명해 주셨는데요. (잼잼잼)
DATA method( void) 🆚 void method ( DATA )
DATA 를 받는지 or 전달하는지
Iterable : pull
- next()
- pull ? 리소스 사용하는 쪽에서 가져온다
Observable : push
- publisher : Observable - 이벤트 or 데이터 갱신
- subscriber : Observer
p.s.
Observer pattern
채팅 서버에서 구독 요청을 통해 Observer(subscriber)들을 등록하고, 이벤트 발생시 순회하며 갱신 정보를 Observer 들에게 전달
(간단히 코드를 올리자니,... 자세한 설명과 코드 예시는 백기선님 디자인패턴 강의를 참고 했습니다.)
public interface Observer {
void publish(String message);
}
구현 코드는 ..
다시 Polling 과 SSE 로 pull 과 push 를 봐보면
(부족함이 많습니다... Pub/Sub 패턴전에 SSE 정도 까지만 정리 했습니다)
Long-Polling은 요청 시점에 데이터를 조회 합니다.
- Post 작성 시점에 저장
- 조회 결과 데이터 있어서 전송시 재조회 막기 위해 데이터 갱신
- 이 부분도 조회 요구사항에 따라 달라질 수 있지만, 저는 목록화 해서 해봤습니다. bulk 인점 등 고려해보고 싶었습니다.
- 최신 데이터 기준이라면, 페이징처리도 커서 기반으로 하는 등 달라질 게 많아서 ... 제 코드 기준으로 가보자 싶었습니다. (고해의 시간은 피하려 했으나 ... 안 되네요)
SSE 는 이벤트 발생 시점에 메시지 보냅니다. (send Alarm)
- Post 작성 시점에 전송
- 전송 시점에 저장
- Post 작성과 Alarm 저장을 분리할 수 있습니다.
모델링시엔 저장한 데이터 활용하는게 좋아 보일 수도 있고,
애플리케이션 계층에서 DB I/O를 줄일 수 있는 면도 좋습니다.
DB I/O 감소로 캐싱 등 생각할 수 있고, 조회도 안 하는 알람 왜 저장할까란 생각은…
- 알람 중복 방지와 서비스상 알람 1번은 필수 전송 이라면, 체크하기 위한 알람 로그기록 등에 쓰일 수 있을 거 같아요. (사용자 데이터 분석 등)
Long-Polling
알람의 경우 저장과 갱신이 동시에 일어날 거 같았습니다.
- 게시글 비활성화 하거나 삭제라도 하면, 변경 작업 등도 고민 되드라구요. (많은 것을 skip...)
사실 트랜잭션은 ...
일정시간 마다 요청으로 일정 시간 동안의 알람 전송 가정과 최신 알람이 아닌 목록 전송으로 중간에 요구사항 변경하면서 RDB 이고, bulk update 작업으로 ... 잠시 다른 트라이를 💬
어떤 상황일까?
팔로워 등록까진 아니더라도, 회원들의 댓글 등록시 알람 부분에 대해 생각해 보겠습니다.
- 포스트 작성자가 인기가 많다면?
- 포스트 게시 되는 순간부터 많은 사용자들의 호응으로 댓글들이 넘실넘실
- 기대하며 기다리는 작성자에게 알람도 넘실넘실
게시글 등록 시점에 댓글 많이 등록되는 상황이라면,
- 댓글 등록 로직에
- 게시글 검증
- 게시글 FK 로 댓글 저장
- 게시글 FK 로 알람 저장
- 방금 포스트 등록한 사용자의 클라이언트가 알람 요청
SSE
Long-Polling 구현하면서 스케줄러 돌리면서 실시간 조회하고 갱신로직이, 사라졌습니다. 💛
- 하지만, 그림에서 처럼 저장함 Alarm 데이터는 사용하지 않아요. 처음에도 캐시 정도로 생각했지만, 기존 로직에서 큰 차이 외에는 끝까지 남겨봤습니다. (이것이 문제다 ....)
머리속에 멤돌았던 것들
- 실시간 이지만, 시간 지나면 의미 없는 데이터
- 하지만 그 과정에서 체크가 필요한 데이터, 어딘가 쓸수도 있다면?
기능은 동일 할까?
- 실시간 이란 ? better ?
- 구독 정보 : 서버측에서는 클라이언트 단말이나 브라우저 정보가 필요합니다.
Post 에서는 알람 전송만 관심사 일 뿐, 어디 저장하는지 등 알 필요가 없어졌습니다.
📬 Mail Box
Long-Polling 은 Queue 구조로 구현 했습니다. 실시간에 가까우려면 요청 순서대로 처리해주고자 FIFO 방식을 이용했습니다.
SSE
- subscribe 로 알림 받을 사용자들을 보관합니다.
목록
- Observable : push
- Post 에 댓글 등록
- Post 작성자 subscriber에게 알람 전송 - publisher
- subscrition 목록의 subscriber 에게 전달
- 실시간은 이벤트 발생시점에 맞춰 집니다.
DB 외에도,
제가 지금 구현한 프로젝트는 layerd architecher 기반 입니다.
모듈화든, 도메인 별이든 패키지 구조를 볼 때 어떨 때 분리할까를 좀더 생각 해보지만 모르지만 ...
요청 ≠ 알람 ?
- Long-Polling 은 요청 부터가 알람 대상 (controller → service → DB, 요청 + 스케줄링)
- 클라이언트 알람 요청 ≠ 댓글 등록 시점에 알람 전송 메시지
- 요청이 없거나, 스케줄링 순서가 안 되거나, 데이터가 없으면 알람은 가지 않는다고 생각하면 PostService로 인한 관련 데이터가 있다는 알람 저장이 주요해 보였지만, AlarmService 인터페이스 사용 순간부터 Post와 분리 된건가 싶기도 했습니다.
- SSE 구현하면서 이벤트가 일단 알람 전송 = 전송의 실시간
- 중간에 사용자 알람 상태 체크 등
- SseEmitter를 로컬 내에서는 언제 해제할지 애매 했었습니다.(대충..🙄)
TTL 등 시간 별 리소스 관리해주면 좋겠다 싶었는데, Observer 패턴도 리소스 관리가 중요 한 것 같았습니다.
FAN-OUT
- fan in: 게이트에 연결될 수 있는 최대입력수
- fan out: 게이트의 출력에 연결될 수 있는 입력게이트의 최대 수
- 팬 아웃이 크다는 말은 하나의 출력이 많은 논리게이트의 입력으로 사용된다는 뜻이다.
(뭔가 많네요. 대단하시다. … 모르겠습니다. 호롤롤롤 )
fanout-on-read = pull model
fanout-on-write = push model
- 쓰기 시점에 fanout
- 쓰기 시점에 시간 소모
- LongPolling 은 요청 시점에 읽기 작업을 통해 전송
- SSE 는 작성 시점에 전송
부족한 그림 자꾸 내밀고, 좀 조심스러워져서… ㅎㅎ 여기까지만 하겠습니다. 👻
Pull 모델은 어떻게 볼까?
지금까지 Polling을 제 모자른 코드와 연결 시키면서 안 좋은 예만 보인 거 같은데요.
사실 Push/Pull 모델은 다른 예시가 적당해 보였습니다.
- SNS 에서 계정 비활성화 됐거나 활발하지 않은 사용자에게 실시간 서비스란?
- 정보 관심 없는 사용자에게 무분별한 알람은 오히려 부담스럽고 앱 삭제 욕구를..
- 알람 off : 알람 전송 전 사용자 상태 확인 필요
- 클라이언트에서 사용자 정보 통해 polling 요청을 안 하면, 서버에서 조회대상도, 전송도 일어나지 않지만, SSE는 주기적 subscriber 관리 해야 하지 않을까...
- 사용자 활성도에 따른 별도 관리 Pull model
- 타임라인이라면, 팔로우 신청한 사용자 이지만, 활동이 매우 적은 사용자인 경우는 빠른 읽기를 위한 조회를 줄이고자 쓰기 비용을 감안하여 보관해두기 보다, 접속하여 요청 할 때 보여주기
- 채팅 알림 off 상태로, 채팅방 나왔다가 다시 들어 갔을 때 개인별 목록
- 페이스북은 팔로우 제한, TAO, mem-cache 등 보완 등 (사실 Push model도 쓰기 비용에 대한 보완 등)
요청 당시에 맞춰 보여줘야 할 때가 해당 사용자에게 맞춘 실시간 서비스 일 수 있겠구나 싶었습니다.
개인적으로 이런 부분이 중요하지 않을까 생각합니다.
위에서 본 차이점들이 실시간에 대한 차이로 귀결시켜 보고 싶었는데요..... 실시간에 대해 좋은 경험이었습니다.