MyRSS 제작기 - HJ-Rich/2022-MyRSS GitHub Wiki

Welcome to the 2022-MyRSS wiki!

2022. 11. 17 (목) 엔티티에 Index 및 FK 명시, EmbeddedRedis 및 H2 적용을 통한 즉시 기동성 확보

Index 명시

@Table(indexes = @Index(name = "idx_subscribe_member_id_rss_id", columnList = "memberId, rssId", unique = true))
@Entity
public class Subscribe {

EmbeddedRedis 및 H2 적용을 통한 즉시 기동성 확보

  • 서브모듈에 보안이 필요한 설정값이 있고, 이 서브모듈은 프라이빗 리포지토리에서 관리
  • 해당 보안 설정값 없이도 clone 후 즉시 기동 가능하도록 프로파일 리팩터링
  • 이를 위해 Optional 설정 임포트, EmbeddedRedis, H2 를 적용.
  • 다만 Github OAuth를 위한 Client_id, Client_Secret은 대체가 불가하여 로그인 기능은 Clone후 즉시 테스트 불가
  • https://github.com/HJ-Rich/2022-MyRSS/pull/63 clone-and-run



2022. 11. 10 (목) 캐시 적용 구상

  • 공통 피드
    • 모든 사용자가 동일한 데이터를 사용함 - Shared Cache
    • 1시간에 한 번 업데이트 됨
    • CacheControl의 maxAge 설정을 통해 브라우저가 아예 요청을 보내지 않게 처리해버리자
    • 단, FixedDelay로 fetching중인데, 다음 fetching까지 남은 시간을 maxAge시간에 주입시켜야 한다
    • WAS 레벨에서도 eh캐싱 등을 적용해서 1시간에 한 번만 DB호출이 일어나게 할 수 있다.
    • fetching이 수행될 때 Evict를 수행시키면 될 듯 하다
    • 여기까지하면 브라우저단 캐싱, 애플리케이션단 캐싱은 완료다.
    • Nginx 캐싱이나 Redis를 통한 캐싱은 우선순위가 다른 작업에 밀리는 듯 하다.
  • 사용자별 구독, 북마크 피드
    • 개인화된 캐시이므로 Private Cache
    • 공통 피드에 적용했던 것과 유사한 컨셉인데 evict 처리를 추가로 고민해야 한다
    • 구독 또는 북마크 정보가 추가 혹은 삭제 되어 변경이 일어날 경우에 대한 처리가 필요하다
    • 브라우저단 캐싱의 경우 maxAge와 더불어 no-cache 설정을 사용해서 매번 재사용 전 변경 여부를 검증하도록 해야 한다
    • 애플리케이션단 캐싱의 경우, 개인화된 데이터에 대한 변경이 일어났을 때 evict 처리를 해줘야 한다.
    • 이러면.. 변경이 일어날 때마다 새로 읽어야하는데.. evict를 이벤트 처리해서 미리 캐시를 생성해둬야할까..?

2022. 11. 09 (수) 무중단 배포

  • 블루 그린 배포 선택
  • 이중화는 진행하지 않음
  • Github Webhook Trigger -> Jenkins build, test, Jar 전송, shell 실행 -> shell 파일 내에서 블루 그린 배포 수행
  • 현재 실행중인 포트가 A, B 중 어느 것인지 식별. 실행중이지 않은 포트를 신규 배포 포트로 설정
  • 신규 배포 포트로 jar파일을 실행한 뒤 20초 대기 후 헬스 체크 수행.
  • 헬스 체크 실패 시 프로세스 중단, 성공시 nginx에 include 될 파일을 수정후 nginx reload
  • 초당 10번 요청을 전송하도록 구성해둔 채로 배포를 수행했을 때, 총 2건이 정상응답되지 않았음.
  • reload가 이루어지는 찰나에 정상 응답이 수행되지 않는 것으로 예상.
  • 주 1회 배포, 배포 마다 0.5초 다운타임으로 가정했을 때, 연 30초 미만 이므로 six nine으로 충분한 가용성으로 판단.



2022. 11. 08 (화) 인덱스 설정

  • MemberRepository에 사용되는 메서드는 로그인 시 조회 및 저장으로 이루어짐
  • Github Provider Id로 조회해서 존재하면 로그인처리, 없으면 저장 후 로그인처리 로직
  • Provider Id에 대한 인덱스 적용 전과 후 비교 진행
    • 천만건 데이터 기준, 4.2초에서 0.08초로 단축 (약 50배 성능 차이)
  • 인덱스 유무에 따른 천만건 삽입 시 소요시간은 195초에서 211초로 증가.
    • 인덱스 설정으로 인한 저장 성능 저하는 사실상 유의미 하지 않다고 판단
  • 천만건 데이터가 존재하는 상황에서 인덱스를 최초 설정시 38초 소요.
    • 인덱스 설정은 애플리케이션 규모가 커지기 전에 선제적으로 적용하는 것이 필요하다는 학습



2022. 11. 03 (목) DB Replication 구성

  • Infra2 인스턴스에 MySQL 추가 설치 및 Replication 구성
  • WAS에 Multi DataSource 설정은 아직 적용하지 않음



2022. 11. 02 (수) Loki를 이용한 로그 모니터링 & 알림 구축

  • log파일들이 존재하는 서버에는 Promtail을 설치. Agent로서 Loki 서버로 log 전송
  • 로그를 적재할 서버 역할을 할 Loki 설치.
  • Grafana에서 DataSource로 Loki 추가. job 별로, 파일명 별로 로그를 필터링하여 쿼리, 카운팅 등 할 수 있음
  • Grafana Alert 를 사용하여 지정 시간 내 특정 로그 카운팅이 설정 숫자 이상일 경우 Slack 알림 오도록 구성
image image image



2022. 10. 18 (화) 1.0.0 릴리즈

  1. 드디어 1.0.0 릴리즈 완료.

  2. 구독 추가 기능 구현 관련

  • 문제 해결의 시작은 구독 관리 화면구독 화면의 분리
    • 기존: 구독 화면에서 바로 구독을 추가, 모든 새로은 포스팅들을 응답 받아 재렌더링
    • 변경: 구독 화면에서는 구독 관리 화면으로 이동하는 버튼만 제공. 처리 성공시 추가된 채널의 기본 정보만 응답 받아 재랜더링
  • 구독 추가 시 1번의 AJAX, 라이브러리를 통한 1번의 문자열 파싱, n번의 INSERT 문이 실행된 뒤 응답이 나간다.
    • AJAX 및 파싱이 정상 완료됐다면 정상적인 RSS 주소라고 할 수 있다.
    • 여기까지만 수행되었다면 정상 처리됐다고 고객에겐 응답을 즉시 나가는 것도 괜찮을 듯 하다.
    • n번의 INSERT를 bulk insert로 개선하거나, 비동기적으로 처리하는 식으로 개선할 수 있을 듯 하다.
  1. RSS 변환 기능과 첫 테스트 코드
  • 그동안 비즈니스 복잡도가 현저하게 낮고 MVP출시가 시급해서 테스트 코드 없이 진행
  • 그러나 사용자 편의성을 위해 티스토리, velog, 브런치, medium의 경우 특정 블로그의 어느 URL을 주더라도 자동 변환처리를 해주고 싶었음
  • 티스토리, velog, medium은 패턴이 일정해서 정규식을 통해 블로그 주인 아이디만 추출한 뒤 패턴에 주입하는 식으로 RSS 주소를 얻을 수 있음
  • 브런치는 RSS 주소 패턴은 있으나, PathVariable로 사용자 아이디를 사용하지 않고 랜덤 문자열을 사용함
    • 어쩔 수 없이 브런치 주인 ID를 정규식으로 추출한뒤, 해당 브런치로 AJAX를 보낸 뒤, JSOUP 라이브러리로 RSS 주소가 담긴 요소를 추출해내야만 했다.
  • 로직이 복잡하기도 하고 다양한 테스트 케이스에 대해 정상 대응됨이 확인되어야 했기에 TDD로 진행
  • 구현하기엔 공수가 꽤 들었지만, 사용성 개선 측면에선 상당한 개선이라 뿌듯함
image image
  1. 사용자 피드백 수집 시작.
  • 어느정도 누적되면 취합해서 리스팅한 뒤 우선순위를 매겨 ISSUE에 등록한 뒤 처리해봐야겠다.
image image



여기까지 구현되면 MVP 완료.

피드를 공유하며 글쓰기, 댓글 기능도 필요하지만 가장 핵심적인 기능에 대한 구현은 완료이다.

주변에 공개해서 피드백을 받고 앞으로 구현 방향에 대해 다시 영점을 잡아보자.



2022. 10. 07 (금) RSS 등록 관련 네오께 자문 구함, 알림 시스템 구상

image

문제 상황

  • RSS 신규 등록 시, DB에 없었던 RSS면 AJAX, FEED저장 작업이 필요함

    • 이때, AJAX도 그렇고, 피드 파싱, 최소 5개 이상 DB 인서트도 해야하니, 응답시간이 걱정됨
  • DB에 있었던 RSS면 findBy만 해서 바로 끝날 수 있음

  • 사용자는 Subscribe 화면에서 해당 작업을 진행했기에, 추가를 했을 때, 추가한 RSS의 피드들이 현재 보고 있는 화면에 즉시 반영되는 것을 선호할 것으로 예상됨

    • 기존 저장되어있던 거는 findBy해서 있었다면 최근 피드들 더 긁어와서 응답해주면 프론트에서 시간순으로 정렬해버리면 끝임
    • 그러나 없었다면… 시간이 너무 오래걸림.
    • 이 지점에 대한 고민이었음…
  • 사용자를 아예 블락시켜버리고, AJAX, 파싱, 인서트, 응답까지 한번에 해주면 확실하게 프론트 재랜더링이 가능함

    • 다만 당연하게도 실패에 대한 문제가 심각하고.. 응답시간도 문제다
  • 블락시키지 않는다면, 응답은 빨리 줄 수 있겠지만… 진행하겠다고..

    • 처리 결과를 어떻게 줘야하나…
    • 프론트에서 그냥 딜레이주고 다시 AJAX를 한다??
    • 성능적으로도 그렇고.. 신뢰할 수 없는 시스템이다… 어떻게 해야할까

어느쪽 길을 가야할지 고민이어서 네오께 이 부분을 여쭤봄

네오의 답변

  • 양쪽 다 연결된 부분이 있다
  • 블락시키는 방법으로 구현하더라도, 그 과정에서 고려해야할 지점이 매우 많다
  • 그 지점에서의 고민들이 넌블락으로 옮겨가더라도 다시 재사용될 것이다
    • 가령 병렬 처리 후 결과 취합이나 배치 인서트 등이 그것이다.
  • 한 번 구현해보고 시간을 측정하고, 사용자로서 직접 그 시간을 경험해보라
  • 어느정도 시간을 목표로 하는지, 현재 시간이 얼마나 걸리는지, 직접 체감했을 때 그 시간이 긴지 짧은지 이대로 괜찮은지 괜찮지 않은지 느껴봐야 한다
  • 한쪽으로 우선 구현해본 뒤 다시 이야기해보자.

답변에 대한 느낌

  • 역시 최고시다 네오….
  • 시원해졌다…!!!!
  • 일단 블락으로 구현하면서 학습하고, 그 다음 네오께 공유드리고, 넌블락이 가능할지 고려해보자

알림 시스템 구상

  • 최초 서비스 구상할 때, 내가 구독한 RSS에 대해 매주 1회 메일링 서비스를 제공하고 싶었다.
  • 가령, 우형, 네이버를 구독했다면, 우형과 네이버의 지난 1주간 작성된 포스팅을 요약해서 메일로 전송해주는 것이다.
  • 그러면 서비스에 들어오지 않더라도 메일로 간편하게 매주 1회 요약해서 받아보고, 필요한것만 가서 보면 되지 않을까?
  • 추가적으로 메일에 이어 Slack Webhook URL을 받아서 알림을 보내주면 어떨까? 좀 더 빨리 받아보고 싶은 사람도 있지 않을까?
  • 스케일 아웃 가능한 알림 서비스는 어떻게 설계해야할지 학습하고 고민해본 결과를 그림으로 그려봤다.
image



2022. 10. 04 (화) 북마크 처리

  • 검색 페이지에 대한 임시 페이지 처리
  • 페이지 이동, 로그인 시 스피너 처리
  • 로그인/비로그인에 대한 버튼 분기처리
  • 북마크 페이지 및 북마크 추가 구현
  • 북마크 상태관리 및 북마크 처리 성공 시 아이콘 재랜더링 구현
  • 프로필 페이지 구현. 아이디, 닉네임, 프로필 이미지, 로그아웃 버튼 나타남.



이제는 날짜를 세는 것이 무의미해짐

  • 미션 및 기타 일정으로 인해 MyRSS에 공수를 할당할 수 있는 날이 매우 적어짐
  • 특정 기능이 구현된 것에 대해서만 기록해야할 듯



15일차

  • 부하 테스트 및 설정 최적화를 위한 학습
    • 쓰레드 풀 관련 학습 및 포스팅
    • JMeter 관련 학습
    • 자바 성능 튜닝 이야기, 자바 개발자와 시스템 운영자를 위한 트러블 슈팅 이야기 발췌독
  • 프로젝트 코드 작성은 계속 못하넹.. ;ㅅ;



14일차 - DI 학습테스트, @MVC 미션, 스터디 수행으로 인해 진행사항 없음



13일차 - 피드 디자인, 하단 네브바, Web Share API

  • 피드 디자인 개선 및 목업 네브바 구현
  • Web Share API를 이용한 공유하기 구현
image



12일차 - 무한 스크롤

큰 틀에서 구현 완료

  • loading, hasNext, pageNumber, feeds 등의 상태관리에 대해
    • axios로 가져온 피드들은 useState로 의도대로 관리되었음
    • 그러나 loading, hasNext, pageNumber 들에 대한 useState가 적용되지 않는 이슈 발생
    • 적용되지 않는 변수들에 대해 useState없이 let 선언한 순수 자바스크립트 flag값으로 사용하는 것으로 일단 해결
  • 백엔드 API 관련 변경
    • 피드 조회 시, 다음 페이지 존재 여부, 다음 페이지 번호를 함께 응답하도록 개선
    • 피드 응답값에 Description 추가
  • 피드 컴포넌트 디자인 개선
    • 좋아요, 공유하기 기능은 미구현 상태
    • 제목, 날짜, 요약, 클릭시 링크이동 구현
    • icon 제공, 밑줄 제거, 색감 개선, Description 길이 줄이기 먼저 진행 예정
  • favicon, title 적용

favicon

feed



10, 11일차 - 휴식

9일차 - AWS CloudWatch

  • IAM 사용자 생성 및 On-Premise 방식으로 AWS CloudWatch 를 구성해봄
  • 대시보드로 CPU 사용률, 네트워크I/O, 로그 스트림을 시각화함
  • 다만 프리 티어에서 제공하는 월간 메트릭 수가 적어 과금 이슈 발생
  • CloudWatch는 구성 방법 및 제공하는 기능이 무엇인지 파악하는 정도에서 마무리
  • 오픈 소스 기반의 Grafana를 구축하여 사용하기로



8일차 - logback, RestDocs

  • 로그백을 이용해 로컬, 개발, 운영 환경별 로그 출력을 설정
    • 로컬은 콘솔, 개발과 운영은 비동기 방식으로 로그 레벨별 파일로 분리해서 출력하게 했다.
    • 스프링부트에 기본적으로 제공되는 로그백 출력 패턴을 이용해서 평소에 자주 보던 로그 패턴과 동일하게 유지했다.
    • 로컬과 개발은 DEBUG 이상, 운영은 INFO 레벨 이상을 로깅하도록 했다.
  • RestDocs를 이용한 API 문서 자동화
    • build.gradle 파일에 설정 추가
    • DocumentationTest 작성
    • snippet을 조합하는 adoc 작성
    • adoc 문서들을 통합해주는 adoc 작성
    • index.html 파일 생성 확인
RestDocs Example



7일차 - 피드 엔티티에 Description 필드 추가

  • 피드를 보여주는 View에서 카드 형태로 보여주되, 제목과 더불어 내용의 일부를 함께 보여주는 것도 좋을 것 같았다.
  • 이 용도에 맞는 엘리먼트가 있다. RSS 규격상 Description이다.
  • 그러나 atom, rss 여부에 따라, RSS를 제공하는 사이트에 따라 상황이 다양하다.
  • 다행히 ROME 라이브러리가 정말 많은 일을 해주지만.. Description이 없는 경우도 있다.
  • 임시적으로 Description이 없지만 Contents를 제공해주는 경우, Contents 중 앞 부분 중 일부를 잘라서 담기로 했다.
  • Description, Contents 모두 없을 경우 아쉽지만 빈 문자열을 일단 담아주기로 했다.



6일차 - 피드 View 시작

  • 티스토리 블로그, Naver D2, 우아한형제들 기술블로그 이렇게 세 RSS를 등록함.
  • 피드 컨트롤러 구현. PageRequest 를 적용하여 기본 페이징 처리 구현.
  • RSS에 추천 여부를 설정. 추천 true인 RSS는 디렉터's 초이스. 비로그인 상태의 기본 피드에 활용됨.
  • 기본 피드 View 작업 시작



5일차 - RSS

  • RSS 기능 관련
    • RSS가 자동으로 변경감지하여 알림을 전송해주는 매직인 줄 알았지만.. 현재까지 확인한 바로는 거는 아닌 듯 하다
    • 다행히도 RSS URL 만 전달하면 파싱해주는 훌륭한 라이브러리가 존재. Rome
    • RSS 공식 문서를 확인하여 element들 중 필수값이 무엇이 있는지, 사용할만한 값이 무엇이 있는지 확인
    • atom 방식과 rss 방식이 있음을 확인, 감사하게도 Rome 라이브러리가 모두 지원
  • RSS와 Feed 관련
    • RSS 주소와 RSS 주소로 fetch하여 parsing한 Feed 두 가지를 도메인 객체로 정의
    • 1시간 간격으로 등록된 RSS 주소를 모두 가져와서 새로운 피드를 확인하여 DB에 저장하는 기능까지 구현
    • 다만, 변경감지 처리는 없이, link가 중복되지 않는 Feed들을 저장하도록 구현
  • Feed 탐지 관련 개선 가능성
    • Feed 를 DB에 저장할 때, fetch한 item하나를 통으로 해싱한 값을 같이 저장한다면?
    • 추후 item이 업데이트 되었는지도 해싱하여 검증한 뒤, 변경되었다면 업데이트 처리를 해줄 수 있을 듯 하다.
    • 다만 현재 MyRSS 서비스에서 구상하는 바는 제목과 링크 정도만 제공할 예정이기에..
    • 제목의 변경 여부 정도만 직접 검증하는 게 오히려 나을 듯 하다.
    • 해싱을 이런 식으로 활용할 수도 있겠구나 하는 새로운 컨셉을 얻었다.



4일차 - 로그인

  • 서버에서 Set-Cookie 응답을 했음에도 프론트에서 Cookie가 할당되지 않는 이슈 발견
  • Set-Cookie가 되지 않으니 쿠키값을 바디로 응답해서 프론트에서 직접 쿠키에 주입시키는 방법에 대해 시도해봄
    • 그러나 Redis에 저장되는 SessionID와 Set-Coookie에 응답되는 값이 다름을 확인
    • 디버깅을 통해 SessionID가 응답시점엔 base64로 인코딩되어 Set-Cookie로 응답되고 있음을 확인
    • 따라서 추후 프론트에서 전달한 쿠키값을 base64로 디코딩해서 세션 존재 여부를 검증하는 방법을 검토했음
  • 근본 원인을 확인함
    • 개발, 운영에선 프론트와 백엔드가 Same-Origin에 있지만, 로컬에선 3000과, 8080포트로 분리되어 있어서 Same-Origin이 아님
    • 그로인해, CORS설정 뿐만 아니라 추가적으로 credential에 대한 옵션을 더해줘야 함을 확인
    • 프론트에서 요청을 전송 시에도, 백엔드에서 CORS설정 시에도 credential에 대한 설정을 'include'로 추가하여 해결



3일차 - Spring Session, Redis

  • Spring Session을 이용한 Github 로그인 구현 시도
  • 인프라1 인스턴스에 Redis 설치
  • Spring Session 라이브러리를 이용해, HttpServletRequest.getSession 시, Redis에 30분 TTL로 세션 저장 확인
  • React Router Dom 을 이용한 Github OAuth 구성 시작
  • 프론트 로컬, 개발, 운영 환경별 프론트 환경변수 구성
    • npm start 명령어 시점과, npm run build 시점에 사용되는 .env 파일의 우선순위가 다름을 확인
    • CRA 공식문서 중 일부
    • .env.development 를 npm start에서 우선순위로 하여 로컬 개발시에 사용
    • .env.production 을 npm run build 하여 운영 배포시에 사용
    • 개발 배포시에는 npm run build 시점에 직접 전달하여 .env.production 에 정의된 값을 오버라이드 처리
  • Jenkins를 이용한 프론트 배포를 위해 NodeJS 플러그인 설치 및 배포 구성



2일차 - 오라클 클라우드

  • 오라클 클라우드 프리티어 계정으로 인스턴스 4개 생성 및 구성 설계
    • 인스턴스 1 운영 - BE, FE 운영 배포
    • 인스턴스 2 개발 - BE, FE 개발 배포
    • 인스턴스 3 인프라1 - Nginx, MySQL
    • 인스턴스 4 인프라2 - Jenkins, SonarQube 등
  • myrss.ga 도메인 확보
  • Nginx 리버스 프록시 구성 및 SSL 설정 완료
  • MySQL 구성 완료
  • Jenkins 구성 완료



1일차 - 초기 설정

  • 메인 프로젝트 및 서브모듈 프로젝트 리포지토리 생성
  • 메인 프로젝트 리포지토리에 backend, frontend 기본 프로젝트 생성
  • Slack 워크스페이스 생성
  • Slack, Notion 연동
  • Slack, Github 리포지토리 연동



0일차 - 컨셉 구상

  • 전체 조회 - 기술 블로그들을 RSS로 모아서 보여주자
  • 구독 조회 - 내가 구독한 RSS만 모아서 보여주자
  • RSS 추가 - 전체 조회에서 나오지 않는 블로그들도 RSS로 추가할 수 있게 해주자
  • 메일링 - 내가 구독한 RSS들을 일주일 단위로 메일 보애줄까?
  • Slack 서비스 - 내가 구독한 RSS가 신규로 올라오면 등록한 Slack 채널로 보내줄까?
  • 피드 쓰기 - 공유하고 싶은 RSS 피드를 공유하면서 본인의 생각을 추가해서 피드로 작성하기
  • 소셜 - 팔로우, DM
⚠️ **GitHub.com Fallback** ⚠️