반응형
이 내용에서 이어집니다.
스꾸공지 개발기
스꾸공지앱 '스꾸공지'를 기획, 개발하고 이후의 성과들에 대해 담았다.기획한 앱과 그 동기'학교 홈페이지 공지사항을 편하게 볼 수 있게 하는 앱'을 만들고자 하였다.나는 평소에 학교 공지사
juneforpay.tistory.com
서문
작년 여름에 스꾸공지를 처음 배포하고 난 이후 올봄에 대형 업데이트를 진행했다. 대형 업데이트를 하면서 고민한 과정들이 담겨있다.
왜 업데이트를 해야 했는가?
- 5Whys 기법을 사용해서 어떤 고민을 했는지를 표현해 보았다.
- 1. 사용자들이 왜 불편해하지?
→ 복수전공자 혹은 그 외의 학과 외의 시설 공지사항을 원하는 사람들은 원하는대로 공지를 받아볼 수 없어서
2. 왜 복수전공 혹은 그 외의 시설 공지사항을 원하는 사람들은 공지를 받아볼 수 없는 거지?
→ 앱 위젯이 4개(전체, 학부통합, 소속대학, 학과)로 나누어져 있다. 그래서 그 공지에 해당되지 않는 도서관 같은 기타 시설을 보고 싶은 사람이나 학과 공지를 여러 개 보고 싶은 복수전공자들은 불편을 겪는다. 즉 애초에 위젯을 4개로 나눈 것 자체가 MECE(모두 포함, 중복 및 배재 없음) 하지 않았던 것이다.
3. 왜 그렇게 나누었지?
→ 하나의 통합된 위젯이 아닌 4개의 위젯으로 구분한 데에는 나름의 이유가 있었다. 만약 모든 항목을 하나의 위젯에 표시한다고 가정하자. 사용자 입장에서는 당연히 글이 최신순으로 정렬되어 있을 거라고 생각할 것이다. 여러 게시판의 게시글이 섞여있지만, 가장 최근 것이 위에 올라와있다고 말이다. 하지만 학교 홈페이지에서는 게시글이 올라온 날짜는 알 수 있어도 시간은 알기 어려웠다. 가령 서로 다른 게시판 A와 B에서 같은 날 게시글이 올라온 경우, 날짜는 사이트에 표시되기에 알 수 없지만 무엇이 먼저 올라왔는지는 알 수 없다. 사용자는 당연히 가장 위에 있는 게 최신이라고 생각하기에, 사용자가 같은 날에 앱을 2번 이상 열어서 확인할 경우 공지 순서에 대해 혼란이 올 수 있다.
4. 왜 시간순으로 최신순으로 정렬할 수 없지?
→ 즉 따로 몇 시에 올라왔는지 표시하거나 기록하는 아키텍쳐가 없기 때문이다.
5. 왜 그런 부분이 없지?
→ 근본적으로 back단에 그런 걸 추적하는 서버를 도입하지 않아서이다. - → 각 시간단위로 공지사항 정보를 받아오는 서버 도입하여야 한다!
업데이트를 통해 어떤 걸 추가로 바꾸고자 했는가
- 각 시간 단위로 공지사항 정보를 받아오는 서버를 도입함으로써, 아예 유저단에서 크롤링하던 기존의 형식을 중지하고, 이 서버에서 담당하기로 했다.
- 서버는 게시판 별로 최신 50개의 게시글을 저장해 둔다. 어떤 게시판이든 최신 50개면 알아야 할 모든 공지는 담을 수 있기 때문이다.
- 그 과정에서의 이점으로는 '디커플링'을 뽑을 수 있다. 가령 학교 홈페이지의 DOM 변경으로 크롤링 코드가 달라지게 되면, 앱 자체를 뜯어고쳐서 다시 스토어에 배포해야 한다. 이 과정은 빌드과정과 앱스토어의 심사과정이 포함되기에 느리고 불편하다.
- 하지만 이렇게 서버를 분리했기에 훨씬 더 빠르고 유연한 대처가 가능해졌다. 그냥 서버 쪽의 코드만 고치면 즉시 적용이 되었기 때문이다.
- 또한 1개의 위젯으로 바꾸기로 한 만큼, 프런트엔드 단에서 기존 위젯 또한 고쳤다.
프런트엔드에서도 바뀐 기획안
- 어차피 위젯 구조가 통째로 변경되었기에 Flutter 기획도 다시 갈아엎고 다시 짰다.
- 첫 번째 위젯은 공지사항을 최신순으로 표시한다
- 두 번째 위젯에서는 어떤 게시판에서 불러올 것인가? 를 사용자가 선택하게 한다.
- 마지막 위젯은 각종 설정을 넣는다. 여기서 3번째 위젯이 남아서 그냥 없애버릴지 아니면 새로운 걸 넣을지 고민했는데, 우리 학교 학생들이 사용하기 유용한 바로가기 버튼들을 넣기로 했다.
- 어떤 게시판을 선택하는가? 에서 사용자가 한 게시판만 골랐을 경우, 그러니까 하나의 게시판에서 요청되는 최대의 게시글 수는 50개다. 따라서 앱에서도 최신 게시글을 최대 50개까지 표시할 수 있도록 한다.
완성된 앱 화면
개발일지
날짜 | 내용 |
25.02.24 | 파이어베이스- 크롤링 코드 연동 최초 성공, 임시 도커파일 빌드 |
25.02.25 | -문서 이름으로 고유 URL 번호 설정 -객체 지향으로 뼈대 잡음 -에러 정책, 중복 게시글 해결 등 기본적인 함수 설계 모두 완료 |
25.02.26 | - 기존의 모든 학과들 다 각자에 맞는 방식으로 74개 모두 잘 작동 확인, 데이터 읽기 최소화 방안 고민해야함 |
25.02.27 | - 기숙사, 도서관, 법전원, 건강센터 추가 - 로딩대기 추가 - 공지 끌올여부 모두 처리 - 가장 최신 게시글만 저장해놓았다가 그때그떄 읽기 : 이제 업데이트 없다면 이론상 최소 1회읽기 1회 쓰기임. - chineese 함수 폐기 후 다른 함수에 통합 |
25.03.01 | - 데이터 구조 변경으로 읽기 및 쓰기 횟수 수십 배 감소 - 최종 크롤링시간 저장 - 대학원 추가 - 도서관 에러 추가(다시 수정해야 함) - dockerfile 빌드 완료 - cloud run 올림 - cloud scheduler에 등록함 |
25.03.02 | - 노트북 플러터 다트 설치 - 튜토리얼 초기 버전 개발 |
25.03.03 | - 누락된 학과들 따로 찾아서 새로 크롤링 코드 짬(양자공학과, 나노공학과, 화학공학과, 퀀트응용경제학과, 의과대학 추가) - 노트북으로 선 작업후 key 파일같은 것은 데스크탑에서 덮어쓰기로 결정 |
25.03.04 | - 튜토리얼에서 진행한 부분 배열로 로컬에 저장하도록 설정 - 파이어스토어 데이터 불러오기 실패.. |
25.03.06 | - 계속된 노트북에서 빌드 실패로 아예 데스크탑에서 이전코드 덮어쓰기로 결정. - 파이썬 크롤러의 key파일을 연동되던 db로 옮김. - cloud run 말고 cloud task로 올림. 그러나 더 안됨. - 근데 집와서 데스크탑에서 기존 key랑 dataset 되어있는 파일 덮어쓰는거 성공하고 프론트엔드 많이 진행함. - 튜토리얼 결과 연동 - 기본 위젯 구성 - 메인 페이지 기본 UI - 학과 게시판 수정,추가,삭제 - 설정에서 다크와 라이트테마와 가져오는 개수 |
25.03.07 | -튜토리얼 초기화 방지 -튜토리얼 단과대학일 경우 생기는 오류 잡기 -웹뷰 설정 및 설정 창에서 앱 버젼, 개발자 소통도구 추가 - 3번째 커스텀 위젯 일부 구현 |
25.03.08 | - 백엔드 추가한 센터들 반영하기 - 튜토리얼 아이콘 이미지 추가 - 3번째 커스텀 위젯 아이콘 추가 - 3번째 위젯도 설정 영향 받게 바꿈.. |
25.03.09 | - 아이콘 작업 마무리 |
25.03.10 | - 깃허브 액션 도입 시도 |
25.03.11 | -에러시 재시도한번은 하게하여 일 줄이기 - newest 관련 읽기, 수정 에러 고침 - 카운슬링센터 추가 |
25.03.12 | - 백엔드 프로그램 데코레이션 적용, 에러 로그 코드 일단 넣고 검증은 아직 안함 |
25.03.13 | - 검증 했는데 큰 문제 사항 없음. - 클라우드 런과 클라우드 스케쥴러에 정상적으로 올라감 |
25.03.14 | - 프론트엔드 공공기기원 공동기기원으로 수정, 카운슬링센터 추가 - 성균타임즈, 성균지 추가하기 - 설정창이 튜토리얼 링크 넣기 - 3번째 위젯 수정, 삭제기능 까지 구현 - 정렬순서 버그 수정 |
25.03.16 | -보관함 기능 추가 - 3번째 사용자 지정 바로가기 기능 - 2번째 게시판 선택 UI 분리해서 개선함 - new 글자 적용함. 확인 해봐야 됨. |
25.03.17 | - new 글자 제대로 적용되게 바꿈。 - 업데이트 프론트엔드에서 이미 눌렀으면 처리 안하게 바꿈 - 로그 알림 설정 만듬 |
25.03.18 | - 에러리스트 파이어베이스 수정 - 생명과학과 에러 수정 |
25.03.20 | -new 텍스트 색깔, 게시판, 아이콘 글자 색 -게시판 추가했을때도 스낵 바 알림 - 스꾸공지 티스토리에 있는것도 설정에 링크로 넣기 - 튜토리얼 끝나고 뒤로가기시 다시 튜토리얼로 이동하는 버그 수정 - 꾹 누르면 공유기능 |
25.03.21 | - 쓰기 권한 따로 설정해서 앱에서는 쓰기 안되게 확인 - 라이트모드 색상 조정 - 하루에 50번이상 업데이트 누르는 미친놈 감지 시스템 - 네비게이션 색상 조정 및 업데이트 함수 객체화 - 백엔드 다시 돌림 |
25.03.23 | - NEW 문구 표시 관련 에러 - 튜토리얼 초안, FAQ 작성 |
25.03.25 | - 튜토리얼 작성 - 3번째 위젯 바텀내비바 아이콘 변경 - 인터넷 연결 끊기면 에러 띄우게 - 최종 빌드 - 앱스토어 제출하고 홍보 이미지랑 문구도 변경 |
25.03.26 | - 업데이트본 최초 배포 후 피드백 - 튜토리얼 사이트에 튜토리얼이 어디있는지 적기 |
25.03.28 | - 서버 index 오류 수정 - 스꾸공지 튜토리얼 URL 시작할때 넣기, - 북마크 이미 있는 건 채워진걸로 적기, - 전환 터치 범위 넓히기, - 튜토리얼에서 복수선택 나중에 하면 된다고 나오기, - 안드로이드 다시 제출 - IOS 시험 빌드, 다 좋은데 inapp에서 여는게 잘 안되는 거 같음.아예안되는건 아닌데 느림 |
25.03.29 | - 갑자기 DB 오류떠서 일단 서버코드 일부는 고침. 완전히는 잘 작동할지 봐야됨. 갑자기 앞 몇개에 Alert Text: DB접속 오류입니다. 라고 뜨는 오류. - 알고보니 건설환경공학부에서 발생한 오류였는데 이게 앞부분에 있어서 에러 처리상 뒤에까지도 영향을 준것이었음. - 아이패드 빌드성공은 했는데 빌드 OS version 부터 갈아 엎어야 함. - Xcode 다시 다운로드 |
25.03.30 | - 서버코드 고치기. alert 문때문에 에러나는게 이후에 alert 없는것들에도 한두번 영향을 주어서 그냥 그 학과들을 마지막으로 뺌. - 스꾸공지 앱스토어 심사 올림 |
25.03.31 | - IOS 버젼 맞추다가 오류난 부분 버젼 다시 맞춤. dart를 downgrade 함으로써 해결함. 그래서 Inappview 패키지는 IOS와 갤럭시 서로 버젼을 다르게 해야함. |
25.04.01 | - 아이폰 이미지 누락되어서 다시 조절해서 재제출 |
25.04.03 | -아이패드 이전 이미지 삭제 + 카카오톡은 외부로만 열리게 , 튜토리얼하단버튼 잘림. -개선해서 ios랑 안드로이드 2.0.2로 재출시 |
25.04.07 | - 예술대학 URL 변경으로 서버코드 수정 - 구글 폼 작성시 메일 알림 수정 - 읽기 횟수 초과시 알림오도록 테스트 설정하여놓음 |
25.04.08 | - 스꾸공지 패치노트 엑셀화(버젼, 수정한 내용, 날짜, 스토어에 올린 문구) - 알림 테스트 완료된거 확인해가지구 수치 바꾸고 7일마다 안꺼지도록? 한번 설정해봄 |
아키텍쳐도
FireStore를 선택한 이유
- 우선 가장 큰 이유는 Firestore는 비용이 상대적으로 저렴했다. 기본적으로 매일 50000회의 읽기와 매일 20000회의 쓰기가 무료로 제공되었다. 이 안에서만 사용한다면 사실상 0원에 가깝게 프로그램을 돌릴 수 있었다.
- google cloud와 뛰어난 연계성도 한 몫했다. google cloud run을 통해 작동시킬 예정이었는데 같은 google이라 호환이 잘 되었다.
- Firestore에서는 flutter 앱 애플리케이션을 위한 별도의 공식문서와 가이드라인을 제시해 준다. 즉 모바일 이식성이 뛰어났으며, 스꾸공지에서 읽고 받아들이는 데이터도 텍스트 단위의 간단한 데이터였기에 쓰기 가장 적절했다.
Firestore 안의 DB 구성
- 학과별로 다른 Document로 구분되어 있고 각 Document는 하나의 50칸 배열 안에 들어있다.
- 그래서 각 배열은 딕셔너리로 이루어져 있는데 항목은 다음과 같다. 왜 유
- 게시 날짜 (YYYY-MM-DD)
- 게시글 URL (string)
- 게시글 제목 (string)
- 상단고정여부 (bool)
- URLIndex (string) : URL에서 추출한 게시글 고유의 번호
- TimeIndex (YYMMDDHH0000): 크롤링한 시점의 연, 월, 시각 + 고유번호 4자리, 총 12자리의 고유번호. Primary Key 역할로 절대 겹치지 않는 고유성을 가진다.
어떻게 비용을 최소화할 것인가?
- 비용을 최소화하기 위해서는 파이어스토어의 매일 읽기/쓰기/삭제 횟수를 최소로 줄여야 한다. 만약 매일 무료사용량을 넘지 않는다면 더욱이 좋아진다. 그래서 줄여야 하는 것을 분류하면 다음과 같다.
- 1. 크롤링 중 읽기/쓰기 횟수 줄이기
- 2. 사용자의 읽기 횟수 줄이기 (사용자는 쓰기 권한이 없다.)
어떻게 크롤링 중 읽기/쓰기 횟수를 최소화할 것인가?
- 크롤링 중에 어떻게 읽고 쓰는 횟수를 줄일 수 있을까? 가장 많이 고민한 부분이었다. 우선 고려는 했지만 파기된 안들을 소개하면
- 1. 그냥 크롤링 주기 자체를 24시간마다 하기, 이 경우 굳이 시간 비교할 필요하는 로직도 필요 없고, 그냥 게시판 자체에 적힌 날짜만 보고도 판단하면 된다. 그리고 어느게 먼저 올라왔는지는 그냥 게시판 가나다 순으로 임의로 부여해준다.
- 파기된 이유: 실제 게시판과의 간극이 24시간이면 너무 크다. 그리고 이 방식이면 애초에 처음 기획에서 하나의 위젯에서 표시하기 위해 올라온 글을 추적하는 백엔드 단을 둘 필요가 없다.
- 2. 정해진 시간마다 꼭 하는 것이 아니라 사용자에게 요청이 들어오면 최근에 크롤링을 했나 확인하고, 한 지 일정 시간이 지났다면 그제야 그 게시판만 lazy 하게 크롤링하고 결과를 알려주기
- 파기된 이유: 사용자가 업데이트 요청을 했을 때 시간이 너무 오래 걸림
- 결론: 정해진 시간마다 크롤링하되, 읽기/쓰기 횟수를 줄이는 요소를 추가하기로 했다.
- 이하는 추가된 요소들에 대한 설명이다.
- 1. Firebase 공식문서 읽고 읽기/쓰기 횟수의 정산 방식 이해하여 적용하기
- firebase의 firestore은 NoSQL 방식으로 기본적으로 root 컬렉션 >콜렉션 >도큐먼트로 구성된다.
- 처음에는 root 콜렉션 자체를 게시판 별로 따로 두고, 각 게시글 하나를 컬렉션으로, 그리고 그 게시글에 대한 정보를 도큐먼트 안에 넣었다.
- 하지만 이 경우 읽기/쓰기 횟수가 상당히 많이 발생하였다.
- 그래서 모든 데이터를 하나의 root 컬렉션에 통합하고, 이후 게시판 명을 컬렉션에, 각 게시글에 대한 정보를 배열 + 딕셔너리 형식으로 하나의 문서에다가 50개 모든 게시글 정보를 넣었다.
- 읽기/쓰기는 document 단위로 책정이 되므로, 이렇게 구조를 바꾸니 읽기 쓰기 횟수가 무려 몇십 배나 감소하였다!
- 2. 캐시파일 적용하기
- 2시간 단위로 크롤링한다 했을 때 거의 대부분의 게시판들은 새 글이 올라오지 않는다.
- 그런데 굳이 모든 게시판을 다 따로 '읽기'를 소비하여 판단하여야 할까?
- 그래서 각각의 모든 게시판에 최신 게시글이 무엇인지 판단하는 캐싱파일 하나를 추가하였다.
- 이 캐시파일 하나는 크롤링을 시작하기 전에 불러와지며, 각 게시판에 관련된 DB를 읽어내기 전에 미리 판단을 한다. 현재 해당하는 캐싱파일의 최신글과 크롤링해서 나온 최신글이 일치하는지 말이다.
- 그래서 만약 최신글이 일치한다면, 이 게시판은 새글 업데이트가 되지 않은 것이므로 그냥 읽기/쓰기 횟수 소모 없이 패스해 다음 게시판으로 넘어간다.
- 즉 게시판이 150개라고 치면 이전에는 게시판이 업데이트되었는지 판단하기 위해, 게시판 정보가 담긴 DB와 크롤링한 결과를 비교하는데 150번의 읽기 횟수를 써야 했다. 하지만 캐싱파일을 한 번만 불러와서 판단하니 읽기 횟수 1회만 소모하면 된다.!
- 구어체로는 '모든 게시판에 대한 캐시파일 한번 읽고, 뭐야 새 글 안 올라왔네~ 그냥 볼 것도 없이 빠르게 패스하자!'라고 할 수 있겠다.
- 이 방식이 적용되기 위해서는 두 가지 로직을 추가로 구현해야 했다.1. 어떻게 가장 최신글을 판단할 것인가? 2. 어떻게 DB와 크롤링한 결과물이 일치하는지(중복인지) 판단할 것인가? 이 부분은 아래에서 후술 한다.
- 3. 한 번에 읽고 쓰기
- 새 글이 올라왔다고 치자, 그러면 이후의 게시글에 대해서도 중복 확인이 필요하다.
- 이때 중복인지 아닌지 매번 읽기 횟수를 소모해서 확인하는 것이 아니라, 그냥 그 게시판의 기존 DB를 모두 불러왔다가 그 불러온 데이터를 보고 판단하는 방식을 적용했다. 이 경우 그냥 한 번만 읽으면 된다.
- 그리고 각 게시글이 업데이트되었다고 DB에 바로 쓰는 게 아니라, 한 번에 모든 50개 데이터 셋을 구축하고 덮어써주는 방식을 사용했다.
- 즉 , 하나의 게시판에서는 몇 개가 올라오든 간에 최대 1번의 읽기와 1번의 쓰기만으로 충분해진 것이다.
- 한 번에 많은 게시글이 올라오는 게시판에서 특히 효과적이었다.
- 이 경우 덮어 써주기에 별도로 이전 데이터를 삭제할 필요 없이, 자연스럽게 옛날 데이터는 덮어쓰기에서 제외되어 삭제되는, 삭제 횟수도 아낄 수 있는 방식이었다.
그러면 어떻게 최신을 판단할 것인가?
- 학교의 모든 게시판은 최신순으로 자동 정렬된다. 하지만 그렇다고 맨 위에 있는 것을 최신이라고 판단하기에는 문제가 생겼다.
- 바로 '상단고정' 기능 때문이다. 공지사항 관리자가 상단고정을 해놓는다면 이미지와 같이 가장 최신이 아니어도 맨 위로 나오게 된다. 또한 여러 게시글이 상단고정이 가능하다.
- '상단고정'이 되고 나면 '상단고정' 부분 외에서는 볼 수가 없다. 즉 옛날 최신순 나열에서는 중복으로 보이지 않는다는 것이다.
- 그래서 가장 최신글을 가져올 때 다음과 같은 알고리즘으로 해결하였다.
- 1. '상단고정' 게시글이 있는가?: HTML 요소로 판단
- 2. 없다면 가장 위의 게시글이 가장 최신으로 return 하지만 상단고정이 있다면 3번 진행
- 3. 상단 고정 중에서 가장 최신을 뽑고, 상단고정이 되지 않은 게시글 중 가장 최신을 뽑는다. 그리고 둘의 날짜를 비교해 최신을 가린다.
- 4. 날짜까지 같다면 일반적으로 늦게 올라올 글의 UrlIndex가 크므로, UrlIndex가 큰 쪽으로 정한다.
어떻게 중복을 판단할 것인가?
- 크롤링된 결과물이 이미 DB에 있는가? 를 판단하는 로직이다.
- 들으면 간단해 보인다. 그냥 게시글과 그 DB의 일치하는 제목이 있는지로 판단하면 될 것 같다.
- 하지만 제목의 경우 종종 수정된다. 원래 있었던 게시글 뒤에 (마감) 이 붙는다거나, (수정됨)이 붙는다거나 하는 식으로 말이다. 이 경우 제목이 달라졌기에 중복이라고 판단하지 않고 수집할 것이며, 이는 문제를 일으킬 수 있다.
- 글이 수정되더라도 변하지 않는 부분이 필요했다. 그래서 중복을 판단하는 데이터로 URL을 이용했다. URL은 글이 수정되더라도 변하지 않기 때문이다. 그런데 URL을 통째로 저장하는 것은 비효율 적이기에 일부만 파싱 하여 URLIndex를 각 게시글의 attribute로 저장하였다.
- 같은 게시판의 서로 다른 게시글의 경우, URL의 일부만 달라진다. 이 부분만 잡으면 된다. 예시를 들어 설명하겠다.
https://www.skku.edu/skku/campus/skk_comm/notice01.do?mode=view&
articleNo=126700&article.offset=0&articleLimit=10
https://www.skku.edu/skku/campus/skk_comm/notice01.do?mode=view&
articleNo=127018&article.offset=0&articleLimit=10
- 위의 두 링크는 같은 게시판의 서로 다른 게시글이다. 'articleNo=' 바로 뒤만 다르다. 그 앞의 정보는 게시판에 관련된 정보기에 동일하다.
- 크롤링은 게시판 단위로 이루어지고, 게시판 별로 따로 DB의 컬렉션에 저장되기에 우리는 'articleNo=' 바로 뒤만 게시글의 중복을 판단하는 고유번호로 저장하면 된다. 이 방식으로 URLIndex에다가 해당 부분만 잘라서 사용하였다.
- 이렇게 적용하는데 학교 게시판 별로 URL 형식이 달라서 전략패턴을 사용해 각자 어느 부분을 자를지를 적용하였다.
- 이 방식은 의외의 부분에서 효과적이었는데, 현재 학교 게시판에서는 일부 게시글이 중복으로 뜨는 오류가 발생한다. 그런데 이걸 사용하면 URLIndex에 따라 중복을 차단하기에 오히려 이 앱에서 보면 중복이 제거되어 더 편하게 볼 수 있다. 응용프로그램이 원본보다 더 효과적인 역설적 상황이었다.
어떻게 사용자의 읽기 횟수를 최소화할 것인가?
- 사용자가 업데이트 버튼을 눌렀을 때, 그 시간을 기억해 두었다가, 그 버튼을 누른 지 얼마 되지 않았었으면, 서버에서 정보를 받아오지 않고, 그냥 바로 가장 최신이라고 snackbar를 띄운다. 이는 반복클릭하는 매크로를 방지하는 효과도 있다.
- 매번 사용자단에서 정보가 업데이트될 때마다, 가장 최 상단 게시글의 TimeIndex를 기억해 둔다. 이후, 업데이트 버튼을 누를 때, 정보를 바로 가져오는 것이 아니라 크롤링 시간 정보가 담긴 캐싱파일과 비교해 그 시간과 동일하다면 가져오지 않는다.
- 단 만약 사용자가 어떤 게시판에서 글을 볼지, 선택한 게시판 자체가 바뀌었다면, 위의 사항들은 모두 논리적으로 예외이므로, 무조건 업데이트해준다.
- 추가로 하루 최대 업데이트 수를 넉넉하게 지정해 놓아 매크로도 막으면서 사용자 편의성은 떨어뜨리지 않았다.
Primary Key를 만들어 내는 방식과 버그, 해결 방법
- 각 게시글은 Primary Key로 timeindex를 가진다. 이 timeindex는 크롤링을 진행하는 시간 기준의 YYMMDDHH + 고유번호 3자리를 가졌다. (urlIndex는 같은 게시판 도큐먼트 내에서는 고유성을 띄지만 모든 게시판
- 이 고유번호 3자리는 크롤링을 하면서 이전에 데이터베이스에 없었는데 새롭게 생겨난 글, 즉 새로 올라온 글이자 중복이 아닌 글을 새로 쓰면서 숫자를 늘려주면서 선정했다. 가령 25년 5월 2일 오후 18시 36분에 크롤러가 동작하였으면, 새로 처음 수집된 글의 고유 번호는 25050218000 이 되고 이후에는 25050218001.. 이런 식으로 정했다. 그런데 여기에 예상치 못한 버그들이 발생하였다.
문제 1
- 매일 정해진 시간에 크롤러가 cloud schelduler로 작동하지만, 이 시간대가 내가 따로 로컬에서 크롤러를 돌릴 때 발생하였다.
- 크롤러는 정시에 돌아가는데 내가 '같은 시'에 로컬에서 디버깅으로 돌린다면 고유번호가 겹칠 수도 있다. 왜냐하면 연, 월, 일, 시는 같은데 고유번호는 내가 따로 크롤러를 돌렸기에 000부터 시작하기 때문이다. 구체적인 예시를 들면 25년 5월 2일 오후 18시에 scheduler가 수집한 첫 글의 고유 번호 : 25050218000 , 내가 크롤러의 오류를 찾기 위해 25년 5월 2일 오후 18시 42분에 돌렸을 경우 그 사이 올라온 새 글 중 첫 글의 고유번호 : 2505021800025050218000로 둘의 고유번호가 같아버린다!
- 해결 )그래서 내가 로컬에서 디버깅을 할 때는 데이터베이스에 쓰는 기능을 제거하는 flag 파라미터를 넣었으며, 데이터베이스에 써야만 할때는 크롤러가 동작하지 않는 시간에 작업하든가 스케쥴러를 끄던가 했다.
문제 2
- 이렇게 timeindex라는 primary key 가 오염된 다음에는 데이터를 싹 지우고 다시 크롤러를 돌렸는데 이 과정에서 문제가 발생했다.
- 아무런 데이터가 없는 초기상태에는 모든 게시글에서 전부다 50개씩 퍼오기 때문에 4 자릿수 단위로 게시글이 수집되었고 고유번호 3자리를 초과해 버렸다!
- 해결) 그래서 고유번호를 4자리로 바꾸어 주었다.
문제 3
- 이 timeindex라는 Primary key는 어떤 역할을 하느냐? 클라이언트 단에서 숫자가 큰 것이 최신이라 판단해, 더 상단에 표시해 주는 역할을 한다.
- 그런데 조금 생각해 보면 숫자가 클수록 최신이라는 뜻은 숫자가 클수록 늦게 나왔다는 뜻이다. 그리고 크롤링은 게시판의 상단 즉 가장 최신글부터 시작된다.
- 그러면 같은 시간에 수집되었을 때 고유번호가 0000에서 +1 씩 연산이 되는 방식은 sort가 반대로 되어버린다.
- 해결) 그래서 최종적으로 고유번호 4자리에다가 9999에서 시작하며 1씩 제거해 주는 방식으로 바뀌었다.
적용한 OOP
- 크롤링코드에 전략패턴을 적용했다. 학교홈페이지는 DOM에 따라 파싱해야 하는 부분이 다르다. 이를 모든 URL에 따로 IF문을 만들어서 적용하는 건 비효율적이었다.
- 심지어 달라야 하는 부분도 조립식 로봇처럼 달랐다! 가령 어떤 게시판은 A part+B part+C part 방식으로 가져오면 된다고 해보자. 그러면 다른 게시판은 D part + E part + F part 이런 식으로 아예 다른 경우도 있었지만, A part + E part + C part 이런식으로 일부만 다른 경우가 있었다.
- 이렇게 일부가 다른 경우도 따로 if로 빼서 별도의 코드를 짜는 건 비효율적이었다.
- 그래서 애초에 URL은 따로 database로 빼놓고, 함수를 실행시킬 때 database의 URL과 어떤 part의 함수를 실행시켜야 하는지를 파라미터로 넣어주었다, crawler(URL, A funct, E funct, C funct) {} 이런 식으로 말이다.
- 또한 통합적으로 겹치는 코드의 경우에는 @decorater 문법을 활용하여 코드의 재사용성과 이식성을 늘렸다.
- 프런트엔드의 경우 기존에 appTheme가 별도의 코드에 흩어져 있었는데 Theme 관련은 하나의 dart 파일로 합치고 import 하는 식으로 적용하여 디자인의 일관성을 높였다.
튜토리얼 설정
- 앱을 처음 사용하는 사람을 위해, 그리고 대형 업데이트로 바뀐 UI에 대해 적응이 필요한 사용자를 위해 튜토리얼을 추가했다.
- 튜토리얼에서는 학부생인지, 대학원생인지 그리고 어느 대학 소속인지 학과 소속인지 등을 질문하여 우선적으로 default 게시판을 설정해 준다.
- 이 게시판은 사용자가 나중에 얼마든지 고칠 수 있으나 일단 편의를 위해 우선 정해주었다는 문구도 추가하였다.
- 이후 노션링크로 구체적인 사용법을 한 번 띄우고, 원래 앱의 홈 화면으로 넘어간다.
최종 구성 화면 (실제로 스토어에 게시된 이미지)
이 외에 추가된 기능
- 튜토리얼 기능
- 꾹 눌렀을때 카카오톡 등으로 공유할 수 있는 기능
- 다시 보고싶은 공지는 보관함에서 다시 볼 수 있는 기능
- 학교 주요시설 링크를 바로가기 버튼으로 쉽게 접속할 수 있는 기능
- 앱을 앱뷰에서 열지 외부 브라우저에서 열지 선택하는 기능
- 앱 테마를 정할 수 있는 기능
- 앱 사용법 링크 추가
배포 후 식겁했던 순간
- 한 사용자가 튜토리얼에서 다음 버튼을 찾지 못한다고 연락을 주었다.
- 이 앱은 튜토리얼에서 하단에 있는 버튼을 눌러 다음으로 넘어가야 진행이 정상적으로 된다. 하지만 화면 크기상 잘려 다음 버튼이 보이지 않는다는 것이다.
- 튜토리얼에서 하단 버튼을 누를 수 없다면 아예 진행할 수 있는 게 없다! 마치 막을 열 수 없는 무대처럼 말이다.
- 대부분의 화면은 아이콘 크기도 작았고, scrollview 처리가 되어있어 신경 쓰지 않았으나, scrollview 가 되어있지 않고 큰 아이콘이 있던 튜토리얼 첫 화면에서 문제가 발생한 것이었다. 또한 이는, 내 기기와 다른 기기들 대부분에서 잘 표시되어서 QA 되지 않았다.
- 특정 기종에서만 드물게 나타난 문제였지만 애초에 앱 진행이 안 되는 fatal 한 문제였기에, 빠르게 크기를 조정해서 바로 배포하였다.
- 그리고 이 외에도 카카오톡 오픈채팅 링크가 appview로 링크를 보기로 되어있다면 작동하지 않았다. 카카오톡이 앱 내에서 보는 기능을 제공하지 않은 탓이었다.
- 그래서 카카오톡 오픈채팅 링크는 유저가 어떻게 설정해두든 간에 무조건 외부 앱인 카카오톡에서 열리게 재설정 하였다.
지속적인 운영을 위한 에러 감지 대책
- 내가 이 프로젝트에 계속 신경을 쓰는 것은 불가능하다. 그래서 에러 발생 할 때만 빠르게 조치할 수 있도록 몇 가지 장치를 두었다.
- 1. cloud logging과 alert: 매번 크롤링을 마칠 때마다 관련해서 로그를 남기고, 정기적으로 로그 push 알림을 내 폰으로 오게 해 두었다. 그래서 나는 주기적으로 최종로그내역만 한번 보고 확인만 하면 된다.
- firestore의 읽기 횟수가 초과되었을 경우도 alert 해두었다. budget 이상으로 비용이 넘어가거나 매크로, DDOs를 방지할 수 있도록 말이다.
- 2. 사용자의 보고: 앱 내에 카카오톡 오픈채팅링크와 구글 폼 링크를 남겼다. 구글 폼은 스프레드시트와 연동해서 새로운 사항이 있다면 나에게 gmail로 발송된다. 사용자가 느끼는 에러나 개선사항도 발생 시 바로 feedback 할 수 있도록, 빠른 조치를 취할 수 있는 수단을 두었다.
추가적으로 해결해야 될 사항들, 수정해야 할 것들
- 현재 로그 방식을 엑셀 방식으로 데이터를 정리해 놓는 방식으로 수정할 것이다.
- 이 앱은 모바일에서만 작동하기에 PC로 보는 사람들을 위해 크롬익스텐션으로 만들어서 배포해도 좋을 것 같다.
- new 표시 관련 버그가 남아있다. fatal 한 버그는 아니지만 업데이트할 때 수정해야 한다.
- ios 가 업데이트 되었으므로 그에 맞게 나중에 firebase 관련 package들도 업데이트해야 더 안정적일 것이다.
- 많은 사용자들이 알림 기능이 있다면 좋겠다고 피드백해서 나중에는 fcm을 통한 알림 기능까지 넣을 것이다.
결론
이상 스꾸공지 버젼 2.0.2 에 대한 개발후기였다. 다운로드 링크는 아래와 같다.
안드로이드
스꾸공지 - Google Play 앱
성균관대 공지사항들을 앱을 통해 빠르게 확인할 수 있습니다!
play.google.com
IOS
스꾸공지
새롭게 달라진 스꾸공지로 성균관대의 원하는 공지사항을 한번에 확인하세요! 스꾸공지가 새단장을 했습니다! 새롭게 개선된 기능들을 소개합니다: -UI 대폭 개선 -위젯 제한 없이 복수전공생
apps.apple.com
반응형