개발 노트 10

[Postgresql] 쿼리 플랜으로 의사 결정하기

서비스를 개발하다보면 쿼리가 점차 복잡해집니다. 쿼리가 복잡해지는 만큼, 부하를 줄 가능성이 크기 때문에 쿼리 튜닝이라는 용어들이 나오는 것 같습니다.이름부터 근사한 쿼리 튜닝이라는 것을 하기 위해서는 쿼리 플랜을 볼 줄 알아야 합니다. 제가 개발한 간단한 기능을 통해 쿼리 플랜을 해석해보고, 간단하게 개선한 경험에 대해서 풀어보려고 합니다. 상황 구현하려고 했던 기능은 꽤나 간단합니다. 대출 상품을 다루는 loan_product 테이블과 대출 상품의 금리 정보를 다루는 daily_interest_rate 테이블이 존재합니다. 이름에서도 유추할 수 있지만, 금융사의 금리 정보는 매일 변경되기 때문에 매일 크론 잡이 돌면서 수집하고 있습니다. 금융사의 금리정보는 주로 영업일 기준으로 변경되기 때문에 변경되..

개발 노트 2025.03.16

무중단 배포 적용기

오늘은 다양한 무중단 배포 전략에 대해서 알아보고 그중 하나인 blue/green 전략을 도입한 경험에 대해서 공유해 보겠습니다. 예상 독자EC2 한 대에 docker compose로 nginx와 애플리케이션을 띄우고 계신 분 무중단 배포란?가장 단순한 방법의 배포라면 기존 버전의 서버를 내리고 새로운 버전의 서버를 올리는 방법일 것입니다.그렇다면 아주 잠깐이라도 서버가 내려간 순간에는 사용자들이 응답을 받지 못하게 됩니다.이러한 중단 상태를 잠시라도 만들지 않고 배포하는 것을 무중단 배포라고 합니다. 주로 많이 사용하는 3가지 전략에 대해 알아보겠습니다.롤링 배포 (Rolling deployments)롤링 배포 전략은 인스턴스를 하나씩 새로운 버전으로 바꾸는 방식입니다.바뀔 인스턴스는 트래픽이 전달되지..

개발 노트 2025.01.19

Spring boot - bean validation

Validation이란? Controller는 클라이언트가 보낸 요청의 데이터를 검증할 책임을 갖습니다. 예를 들어 회원가입 로직을 처리하려면 다음과 같은 정보가 필요합니다. { "email": String, "password": String, "nickname": String, "job": String } 만약 클라이언트에서 email을 보내지 않고 다음과 같은 데이터를 보냈다면 어떨까요? { "password": "password", "nickname": "닉네임", "job": "대학생" } 이 경우 email 필드는 null로 처리될 것이고, 회원가입 로직을 처리할 수 없겠죠. 이와 같은 상황을 미리 방지하기 위해 요청의 데이터를 검증해야 합니다. 검증할 객체를 맵핑하자 먼저 검증을 하기 위해서는..

[출시] 백그라운드 위치 정보 권한 선언을 업데이트해야합니다.

소비자와 꽃집을 연결해 주는 서비스 플룸을 출시하며 다음 오류를 만났습니다🥲 첫 번째, 세 번째가 같은 원인으로 발생한 것 같습니다. ‘민감한 앱 권한으로 이동’으로 이동했더니 다음과 같이 나왔기 때문입니다. 원인 꽃집의 위치를 직관적으로 보여주기 위해 flutter_naver_map을 사용했습니다. 지도가 기본으로 보여주는 위치를 현재 위치로 하기 위해 위치 정보 권한 선언이 필요했습니다. 그래서 AndroidManifest.xml에 다음과 같이 추가했습니다. fine → 정확한 위치 정보 coarse → 대략적인 위치 정보 background → 항상 위치 정보 허용 위와 같은 권한 요구가 포함된 appbundle을 심사에 올리려고 하니 다음 문구를 받았습니다. 앱에 백그라운드 위치 정보 액세스 권한..

[기능] Firebase_messaging 연동 및 페이지 이동

푸시 알림은 모바일 앱을 운영할 때, 굉장히 중요한 기능 중 하나입니다. 오늘은 푸시 알림을 구현하는 방법에 대해 기록하고 공유해 보겠습니다. 푸시 알림 관련 설루션으로는 가장 유명한 것은 Firebase를 사용했습니다. 일단 무료인 점이 가장 크고, Firebase를 Flutter에서 잘 사용할 수 있도록 flutterfire라는 플러그인도 운영하고 있어 많은 Flutter 개발자들이 활용하고 있을 것이라고 생각됩니다. FCM을 이용하여 푸시 알림을 수신할 때에는 크게 3가지 로직이 필요합니다. Firebase로부터 메시지를 수신한다. 수신한 메시지를 notification으로 보여준다. 함께 오는 데이터인 link 정보를 활용해서 적절한 페이지로 이동시킨다. 3번에서 말씀드린 link 정보는 백엔드와..

[테스트] JUnit5의 ParameterizedTest

안녕하세요. 오늘은 junit5의 ParameterizedTest에 대해 알아보겠습니다. 다음은 문자열로 입력받은 수식을 계산해서 출력해 주는 계산기에 대한 테스트입니다. @Test fun testCalculateAddExpression() { val outputStream = ByteArrayOutputStream() Calculator("10 + 2".byteInputStream(), outputStream).run() assertEquals(String(outputStream.toByteArray()), "12") } @Test fun testCalculateSubtractExpression() { val outputStream = ByteArrayOutputStream() Calculator("1..

Gradle: 안드로이드의 빌드 툴킷

Gradle Practice라는 비어있는 안드로이드 프로젝트를 하나 생성해서 Gradle의 구조와 하는 일에 대해 알아보겠습니다. gradle wrapper 가장 먼저 gradle wrapper를 살펴보겠습니다. gradle wrapper는 gradlew라고도 표기합니다. gradlew는 gradle을 다운로드하고 실행하는 하나의 애플리케이션입니다. 이것이 개발자들이 일관적으로 빌드를 할 수 있게끔 도와줍니다. graddle-wrapper.properties에는 사용할 버전의 gradle이 저장된 url을 가지고 있습니다. 이를 통해 다수의 개발자들이 빌드할 때 gradle 버전이 변경되지 않도록 보장합니다. #Sat Aug 12 05:28:21 KST 2023 distributionBase=GRADLE..

late 변수의 할당 여부 검사

Problem 외부로 요청을 보내 비동기로 받아오는 데이터가 상태값으로 필요한 경우에는 가장 쉽게 접할 수 있는 키워드가 late 입니다. 대체로 데이터를 기다리는 동안 사용자에게 빈 화면을 보여줄 수 없기에 응답으로 받아온 데이터가 올바르게 할당되었는지의 할당 여부를 통해 사용자에게 다른 화면을 보여주고자 합니다. 이 때 late 키워드를 사용하여 변수를 선언하게 되면, 해당 변수가 null인지 검사할 때 에러가 발생합니다. 이는 아직 late 키워드를 활용하여 선언한 변수가 초기화되지 않기 때문입니다. 이에 대한 답을 찾기 위해 dart 공식 문서에서 제공해주는 권장사항을 살펴보았습니다. Solve late 변수가 초기화 되었는지 아닌지를 판단할 필요가 있다면 late 변수를 사용을 지양해야한다는 d..

ESLint에서 react/no-array-index-key 규칙을 만든 이유가 뭘까?

사건의 발단.. react에서는 Array.prototype.map을 통해 동일하지만 값만 다른 컴포넌트를 쉽게 렌더링 할 수 있습니다. 보통 다음과 같은 형태로 많이 사용합니다. ['a', 'b', 'c'].map((item) => {item}) 리액트에서는 위와 같이 동일한 컴포넌트에 대해 각각의 컴포넌트를 구별할 수 있도록 key props를 입력해야합니다. Array.prototype.map 메서드를 간단하게 살펴보면 첫 번째 매개변수로 콜백 함수를 받습니다. 이 콜백 함수의 두 번째 인자는 index임을 알 수 있습니다. arr.map(callback(currentValue[, index[, array]])[, thisArg]) 이 점을 활용하여 과거에 저는 종종 다음과 같이 사용하고는 했습니다..

개발 노트/FE 2022.03.16

useQuery onComplete를 data 프로퍼티로 리팩토링하기

문제의 발견 회사에서 서비스의 사용자 리스트를 페이지당 100명씩 가져와서 보여주는 페이지를 작업 중이었습니다. 이 페이지는 사용자 데이터를 받아서 화면에 렌더링 되기까지 시간이 상당히 걸리는 문제가 있었는데, 그 이유에 대해 파헤쳐 보았습니다. 아래 이미지는 사용자 페이지의 간단한 구조입니다. 먼저 기존에 데이터를 처리하던 방식은 다음과 같습니다. (많은 부분을 생략했습니다..) function UserList() { const [userList, setUserList] = useState([]); const { loading } = useQuery(userList를 불러오는 gql, { variables: {}, onComplete: (res) => { setUserList(res에서 뽑아낸 user..

개발 노트/FE 2022.02.06