신입개발자의 두번째 프로젝트 후기
시간은 흐르고 흘러 두번째 프로젝트도 마무리가 되었다.
이제 4개월차에 접어드니 어느 정도 회사생활에 적응이 된것 같다.
그런데 왜 아침에 일어나는건 여전히 힘든걸까 ㅋㅋㅋㅋ
그리고 JavaScript나 TypeScript도 많이 적응이되 손에 익은것 같아 기분이 좋다!

JavaScript에 대해서 더 깊이 있게 알아야 할 필요가 있지 않을까해서 스터디를 찾고 있지만, Java처럼 엄청 활성화가된 느낌은 아니다…
그래서 2021년에는 nodeJS를 백엔드에 활용하는 동아리 활동도 찾아볼 생각이다.
이슈
1. ROWNUM
유저 프로필 화면에서 통계 query를 작성해야할 일이 있었다.
각각의 분야에서 상위 몇 %인지 통계를 subquery로 작성을 했는데 ORM을 이용해서 쿼리를 작성하기에 무리가 있다고 판단해, row 쿼리로 작업을 진행했다.
처음에는 WHERE절로 조건을 걸고 subquery에 rownum을 이용해서 조건에 대한 순위를 뽑아내고 이 데이터를 %화 했었다.
아래는 그 예시이다(일부).
SELECT id userId
, ROW_NUMBER() over (ORDER BY IFNULL(exampleCount), 0) DESC) rowNumber
, exampleCount count
FROM (
SELECT IFNULL(countData.count, 0) count
, u.id
FROM users u
LEFT JOIN (
SELECT userId
, IFNULL(COUNT(*), 0) count
FROM example
GROUP
BY userId
) example
where u.id = countData.userId
) exampleCount
GROUP
BY id
처음에는 잘 동작하는것 같았는데,
동일한 값을 가지고 있는 rowNumber 대해서는 순서가 매번 변경되어 조회할때마다 %가 변동이 된다는 문제가 발생하였다.
방금전에는 상위 30%였는데 다시 와보니까 상위 60%라구요?
물론 정보가 많아질수록 %의 오차는 줄어들겠지만 조회할때 마다 달라지는 통계가 무슨 신뢰성을 가질 수 있겠는가.

해결방법은 의외로 간단했는데 ROW_NUMBER를 RANK() 함수로 바꿔주면 되는것이었다.
그 이유를 알아보니 ROWNUM은 가상의 Column으로, DB에서 꺼내지는 순서대로 숫자가 붙게 되고 이 순서는 Optimazer라고 하는 DB의 실행계획에 의해서 그 순서가 달라질 수 있다는 것이었다.
내 경우에는 SELECT 문에서 ROWNUM을 사용해 ROWNUM 에 의한 OPTIMIZER MODE 의 변화로 PLAN 이 달라진것으로 생각한다.출처
난관
1. DB Schema Modeling
DB Schema를 설계할때는 몰랐던…
코딩을 시작하고 시간이 많이 지나고 나서야 깨달았던…
Schema의 잘못된 설계로 인해서 작업을 두배로 하고 있다고 스스로 느낀건 프로젝트가 거의 다 마무리가 되었을 때였다.

공통된 속성마저도 너무 작은 단위까지 쪼개서 설계한게 문제가 되었던것 같다.
이미 작업은 마무리 되었고, 기능에 문제가 있던건 아니지만,
DB Schema설계가 clean하지 않고, 그로 인해서 같은 일을 여러번 했다는게 스스로가 용납이 안되서 스트레스 아닌 스트레스를 받았던것 같다.
설계 miss로 인한 스노우볼링을 얕보고, 좋은 경험했다고 생각한다 끌끌
그래도 약간 매운맛으로 고생하고 중요성을 느꼈으니 운이 좋았다.
2. Dev environment
이번 프로젝트는 커뮤니티 앱인 만큼 채팅 기능이 들어가게 되었다.
구현은 socket.io를 사용했다. 사실 구현자체는 어려지 않았는데,
문제는 DB의 선정과 설계였다.
우리가 채팅앱을 이용할때를 생각해보면 한번에 수십개의 채팅을 연달아 보내고,
이방 저방을 왔다갔다 하기도 한다.
저만 그러나요?
그만큼 DB의 부하가 많이 일어나게 되고,
또 채팅이라는 것이 log와 같은 성질이기도 하니
데이터의 처리 속도도 빠르고, 수평적 확장이 용이한
NoSQL을 이용해서 DB 서버를 하나 더 구축하는게 어떨까 생각했다.
하지만…
이제 막 서비스를 시작하는 고객사의 특성상 당장의 필요성을 느끼지 못하는데,
매달 운영비에 부담을 늘릴 수도 없는 노릇이었다.
그렇다고 API 서버가 있는 EC2 안에 채팅 DB를 따로 구축하려고 하니,
가장 사양이 낮은 EC2의 스펙으로는 두마리 토끼를 잡으려다가 두마리를 다 놓칠것 같다라고 판단이 되었다.

차선책을 찾던중 DB Engine중 MyISAM을 알게되었다.
우리가 일반적으로 사용하는 InndoDB 보다 Insert, Select 속도가 빠르고,
채팅의 특성상 transaction의 처리가 필요할 것 같아 보이지 않고,
또 Table 단위로 DB Engine의 설정을 바꿔 줄 수 있으니 채팅메시지 Table만 설정을 바꿔주면되니 최고의 솔루션을 만난것 같았다.
만난것 같았다…
하지만 CRUD 모든 작업에 있어서 Table 단위의 Lock이 걸린다는 걸 알게 된 후 생각을 바꾸게 되었다.
단순 Insert, Select가 아니라 상대가 채팅을 읽었는지, 언제 읽었는지와 같은 Update 작업도 정말 수시로 이루어지는데,
그때마다 채팅메시지 Table 전체에 Lock이 걸릴걸 생각하니,
내가 잘못된 생각을 하고 있구나 생각이 들었다.
MyISAM진짜 Only Log용으로는 최적의 선택이 아닐까 싶다!
그래서 결국엔 InnoDB를 사용한 RDBMS로 채팅을 구현하게 되었다.
비록 프로젝트에 적용하지는 못했지만 유익하고 재미있는 고민이었다.
개선
1. DTO
Entity 그대로가 아닌 DTO를 사용해 코드를 작성하니,
데이터를 직접 Control한다는 느낌을 많이 받았고 Front에서의 요구사항이 변경 되었을때 처리가 수월하다고 느꼈다.
{
"user": {
"id": 1,
"name": "sampleUser",
"sampleScore": {
"count": {
"value": 1
},
"score": {
"value": "1.0"
}
}
}
}
위의 코드는 첫번째 프로젝트때 내가 화면에 전달해주던 데이터의 예시이다.
subQuery를 이용해서 socre라는 table을 user tabler과 조인한 예시로, value 라는 값이 굳이 필요할까 생각이 드는 형태이다.
그래서 이에 대한 해결책으로 DTO와 mapping method 를 이용해서 아래와 같이 data를 가공해줬다.
{
"user": {
"id": 1,
"name": "sampleUser",
"sampleScore": {
"score": "1.0",
"count": 1
}
}
}
이렇게 작업을 해주니 가독성도 많이 떨어지고, Front 개발자의 재활용성이 떨어져 보이는 JSON Object는 내 프로젝트에서 사라지게 되었다.
그리고 객체간의 관계도 간략하게나마 볼 수 있으니 적절한 솔루션이 아니었나 생각한다.
현재는 DTO의 활용을 persistent layer -> presentation layer로 전달할때만 사용하고 있는데,
좀 더 연구해서 presentation layer -> persistent layer로의 활용성도 넓힌다면 좀 더 깔끔한 코드가 짜여지지 않을까 싶다.
정답이 없는 고민은 언제나 어렵다.
2. 임시변수의 네이밍
각 메소드에서 Data를 return 할때 메소드 통째로 return 하는 것이아니라 임시변수에 담아서 return 하려고 나름의 노력을 취했다.
puiblic async getBoard(id: number): Promise<Board> {
try {
const data: Board = await this.boardDAO.getBoard(id);
return data;
} catch(e) {
throw e;
}
}
임시변수에 담아서 처리하는것이 더 직관적이고 나중에 유지보수하기에 더 용이할거라 판단이 되었기 때문이다. 그런데 임시변수의 이름을 항상 data라고 지어왔었다… ㅋㅋㅋㅋ
작업을 하다보니 아무런 의미도없는 data라는 네이밍을 가진 변수 값을 넣어준들
이게 과연 직관적인 처리일까?
라는 의문을 가지게 되었다. 또 한달 뒤, 두달뒤에 이 코드를 보며 고개를 갸우뚱하는 내 모습이 선명하게 보여서 좀더 의미있는 네이밍을 하기로 마음 먹었다.

아직 이렇다할만한 naming rule을 정하지는 못했다.
관련 서적을 보면서 프로젝트에 반영하는게 best일것 같은데 자격증 시험으로 인해서 책을 보기도 쉽지가 않았다.
그리고 고민없이 책을 먼저 보는것보다 스스로 고민해보고 책을 보는게 더 효과가 크다고 생각하기 때문에 미루는 이유도 있다!
절대 핑계가 아니다. 아무튼 그렇다.
일단 생각한 방법은 행위와 모델 이름을 앞에다 붙여주는것이다.
puiblic async getBoard(id: number): Promise<Board> {
try {
const getBoardData: Board = await this.boardDAO.getBoard(id);
return getBoardData;
} catch(e) {
throw e;
}
}
그냥 data라고 naming하는것보다 훨씬 더 의미있는 naming이 됐다.
일단은 그렇다고 생각하자.
이 방법이 괜찮은가? 에 대해서는 서비스가 운영되고, 유지보수 작업을 하면서 몸소 깨달을줄 알고, 기대한다. 하하
결론
한달이라는 짧은 기간동안 진행된 프로젝트였지만 많은 걸 느낀 프로젝트였다.
채팅이라는 기능을 처음으로 구현해봤는데 정말 재미있었다.
HTML로 화면을 만들어 Test를 했을때는 별느낌이 없었는데,
앱에서 채팅이 구현됐을때는 개발자분 뒤에 가서 신기하다고 막 추임새도 넣고 감탄하기도 했다.
너가 구현해놓고 왜 좋아하냐? 라는 느낌을 받았다 ㅋㅋ;;
최근에는 NestJS를 사용해보고 있는데 Spring 같은 느낌도 받았고,
Framework에서 구조를 잡아주니 편리함을 많이 느꼈다.
내부적으로 Express로 동작한다고 알고 있었는데, Fastify라고 하는 Framework로도 동작이 가능하다고 한다.
adapter를 이용해서 fastify 사용시 plain Express 보다 처리속도가 약 2배 가까이 빠르다!
공부를 좀 더 해서 회사 프로젝트에도 적용해 보고 싶다.
JavaScript를 더 잘 활용하고 싶은데 자료가 많이 없어서
Front 공부를 하면서 다른사람의 활용방안을 참고 해보려고 한다.
nomad coder의 강의가 참 괜찮더라

다가오는 새해는 기초 cs를 비롯한 알고리즘 공부를 중점적으로 해야겠다.
목표대로 살기는 참 어렵지만 2020년에도 열심히 달려온것 처럼
2021년에는 더 열심히 달리는게 최종 목표다!
Leave a comment