Skip to content

Hyeon9mak/WIL

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

지식파편

너무너무 좋은 아티클 모음

BDDMockito 를 사용할 때 primitive type 에 대해서는 any() 를 사용할 수 없다.

만약 정수형일 경우?

  1. anyInt() 를 사용한다.
  2. 혹은 타입을 Integer 로 변환한다.

gradlew 명령어는 sudo 로 만들어진 파일 편집 권한이 없다.

만약 sudo 명령을 통해 관리자 권한으로 파일, 디렉토리를 생성한 경우 ./gradlew 명령어를 통해 이를 편집할 수 없다.

$ ./gradlew clean build

(clean 명령어를 통해 .build 디렉토리를 제거할 수 없음.)

때문에 만약 아래와 같이 sudo ./gradlew build 명령어 등을 통해서 디렉토리를 개설한 경우, 수동으로 디렉토리와 파일들을 제거해주어야 한다.

$ sudo ./gradlew clean build

$ sudo rm -rf ...

## argocd helm chart 에 주석을 달면 안된다. argocd helm chart 를 이용한 spring batch parameter 관리 중 아래와 같이 주석을 달아두었었다.
{{=sprig.date("20060102", sprig.dateModify("+9h", sprig.now()))}} # 금일

그러자 아래와 같이 sql syntax 에러가 발생했다.

PSQLException: ERROR: invalid input syntax for type timestamp: \"20240321 # 전 일 \"

argocd helm chart 파일은 주석 기호를 구분하지 못하고 곧이 곧대로 문자열을 기입한다.

@ConfigurationProperties 를 통해 환경변수를 불러올 때 간혹 실패하는 경우

  • SpringBoot 버전을 확인할 필요가 있다.
  • SPringBoot 3.0 미만 버전에서는 @ConstructorBinding 이 필수였다.
@ConstructorBinding  
@ConfigurationProperties(prefix = "aws.s3")  
data class AwsS3Properties(  
  val bucket: Bucket,  
  val mockS3: MockS3 = MockS3(),  
) {  
  data class Bucket(  
    val name: String,  
    val cloudFrontBaseUrl: String  
  )  
  
  data class MockS3(  
    val port: Int = 8111,  
    val mockObjectDirectory: String = "s3"  
  )  
}
  • SpringBoot 3.0 이상 버전에서는 생성자가 여러 개일 경우를 제외하면 @ConstructorBinding 을 생략할 수 있다.
@ConfigurationProperties(prefix = "aws.s3")  
data class AwsS3Properties(  
  val bucket: Bucket,  
  val mockS3: MockS3 = MockS3(),  
) {  
  data class Bucket(  
    val name: String,  
    val cloudFrontBaseUrl: String  
  )  
  
  data class MockS3(  
    val port: Int = 8111,  
    val mockObjectDirectory: String = "s3"  
  )  
}

추가 내용은 https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-3.0.0-M2-Release-Notes 참고.

성능 부하 발생 명령어

  • 특정 데몬을 실행, 중지, 재시작
$ systemctl
  • yum 을 통해 프로그램을 설치할 때 추가 패키지를 받아오도록 하는 명령어
$ amazon-linux-extras install -y epel
  • 부하 툴
$ yum install -y stress-ng
  • CPU 부하 발생(70%)
$ stress-ng --cpu 1 --cpu-load 70% --timeout 10m --metrics --times --verify
  • CPU 정보 확인
# CPU 정보 확인
$ cat /proc/cpuinfo | grep name

# 메모리 용량 확인
$ cat /proc/meminfo | grep MemTotal

# 프라이빗 IP 주소 확인
$ ip -br -c addr show eth0

# 퍼블릭 IP 주소 확인
$ curl ipinfo.io/ip

# 스토리지 (EBS 볼륨 확인)
$ lsblk
$ df -hT -t xfs

PostgreSQL major upgrade 시 챙겨야할 주의점

When upgrading major versions pg_upgrade does not copy existing statistics over to the new version of the DB. It is recommended to run VACUUM ANALYZE or at least ANALYZE after pg_upgrade.

  • postgresql major upgrade 이후에는 vaccum 등을 이용해서 통계 테이블 또한 복제를 해주어야만한다.
  • 그렇지 않을 경우 대단히 속도가 저하된다.

RDB 테이블 정규화는 왜 중요한가?

  • 각 테이블 컬럼이 어떤 역할을 수행하는지 한 눈에 확인하고 이해할 수 있다.
    • 반대로 역정규화가 되어 있으면 각 컬럼이 어떤 시점에, 어떤 유스케이스에 활용되는지 이해하기 어렵다.
  • 신규 기능 개발 / 리팩터링 작업을 진행할 때 논의 포인트가 줄어든다.
    • 반대로 역정규화가 되어 있으면 각 데이터를 마이그레이션 할 것인지, 저장을 어떻게 할 것인지, 조회 시기에는 null 검증을 할 것인지 등 논의할 요소가 많아진다.
    • 한 마디로 생산성이 크게 저하된다.
  • 정규화된 테이블이 단단하게 잡혀있어야 데이터 일관성이 잘 지켜진다.
    • 역정규화된 테이블이 흩뿌려져서 중복데이터를 가지면 일관성이 깨지기 시작한다.
    • 데이터를 신뢰하기 어려워진다. 중심 테이블이 하나 잡혀야한다.
    • 이러면 RDB 를 사용하는 것에 의미가 사라진다.
  • 정규화된 테이블은 추후 NoSQL, Redis 등으로 역정규화 하여 사용할 수 있다.
    • 정규화를 미리 잘 해두어야 신규 역정규화 모델을 만들어 활용하기 수월하다.
    • 만약 RDB 테이블에 역정규화가 되어 있다면, NoSQL 은 어떤 테이블을 기반으로 신규 모델을 만들어야할지 애매해진다. (생산성 저하)

intellij 에서 gradle 프로젝트를 정상적으로 찾지 못할 때

  • build.gradle 에서 커넥 확인

ES 몇 가지..

  • es 는 샤드를 기반으로 스코어링을 한다.
    • 때문에 특정 샤드에 몰리면 스코어링이 어려워진다.
    • 검색 시간이 느려진다는 뜻.
  • 루씬 인덱스가 너무 많아지면 검색 히트율이 많이 낮아질 수 있다.

query IN (null)

  • 쿼리의 IN 혹은 NOT IN 은 null 이 포함되면 예상 외의 결과를 반환한다.
  • JPA 는 파라미터가 null 일 때 이것을 걸러주지 못하고 자동적으로 IN (null) 쿼리를 생성한다.
  • 이에 대한 대응책으로 queryDSL 로 직접 쿼리를 작성하거나, 별개 쿼리를 2벌 준비해서 사용하자.

sub query NOT IN

  • https://choihyuunmin.tistory.com/93
  • 서브쿼리의 IN 은 하나라도 일치하면 반환
  • 서브쿼리의 NOT IN 은 "모든 요소들과 일치하지 않는 값"을 체크하여 반환한다.
    • 만약 비교 컬럼이 nullable 하다면 예상외의 결과가 나올 수 있다.
    • null 은 무조건적으로 false 를 내뱉기 때문

모듈별 코드 옮기기 트러블 슈팅

겪은 문제

  • A 모듈에서 F 모듈 configuration bean scan 에 실패함
  • A 와 비슷한 형상의 B 모듈에서는 문제가 발생하지 않음

원인

  • B 모듈과 F 모듈은 패키지 구조가 완전히 동일함
    • 때문에 B 모듈 Main 이 F 모듈 내부 bean scan 을 모두 성공
  • A 모듈은 F 모듈과 패키지 구조가 다름
    • 때문에 실패

해결방법

  • F 모듈 내부 bean scan 범위를 명시해주는 Config 추가
  • 또 다른 해결 방법으로는 B 모듈 Main 에 탐색 패키지 명칭을 상위로 올리면 되는데, 이건 딱히 좋은 방법이 아닌 것 같음.

presignedURL

  • "우리가 어떠한 경로에 이미지를 올릴거야" 클라이언트에 올리라고 응답을 준다.
  • 경로랑 파일명을 내가 이미 알고 있으니까, 거기에 등록되었을거라고 알아서 저장되도록
  • 일단 컨텐츠 ID 를 0으로 저장한 후에 다시 변경하는 것으로.....

mongo db and search

  • mongo 검색 히트률이 낮아서 보통 별�개의 검색용 서버를 둔다.
  • 그리고 mongo 는 read model 만 갖고 있는다.
    • 검색 서버를 통해 ID 를 가져온다음, ID 로 mongo 에 질의해서 read model 조회

@Transactional(propagation = Propagation.REQUIRES_NEW)`

  • propagation requires new 는 새로운 스레드를 생성하는 것이 아니다.
  • 하나의 스레드에서 새로운 커넥션을 얻는 것 뿐이다.
  • 때문에 자식 트랜잭션에서 예외가 발생했을 때 부모측에서 try-catch 로 예외처리를 해주지 않으면 부모 트랜잭션까지 모두 롤백된다.
  • 부모는 롤백되지 않고 자식만 롤백을 원할 경우 try-catch 로 예외를 관리해주어야 한다.

spring actuator

@RestController
fun HealthCheckController {

	@GetMapping("/actuator/health")
	fun healthCheck(): String {
		return "OK"
	}
	
	@GetMapping("/actuator/info")
	fun healthCheck(): String {
		return "OK"
	}
}
  • spring actuator 의 다른 기능을 활용하면서 하는 방법으로는 Indicator 구현
@Component
class CustomHealthIndicator : HealthIndicator {

  override fun health(): Health = Health.up().build()
}

@Component
class CustomInfoContributor : InfoContributor {

  override fun contribute(builder: Info.Builder) {
    builder.withDetail("info", "OK")
  }
}

ConstructorBinding

@ConstructorBinding  
@ConfigurationProperties("work-book")

https://codingdog.tistory.com/entry/spring-boot-constructorbinding-%EC%83%9D%EC%84%B1%EC%9E%90%EB%A1%9C-binding%EC%9D%84-%EC%8B%9C%ED%82%A8%EB%8B%A4

제네릭 공변성과 반공변성

Redis 도입을 통[[hyeon9mak/wil/지식파편]]한 성능 개선

서비스의 성능은 여러 단계로 확인할 수 있다.

  • 브라우저를 통해 직접 QA하며 전구간 테스트
  • webpagetest 등을 통한 인터넷 구간만 테스트
  • 부하 테스트로 (브라우저 렌더링을 제외한) 전구간을 테스트

nGrinder를 이용해 테스트를 진행한다.

테스트 시나리오 대상

가장 중요한 것은 어떤 성능을 중점적으로 측정할 것인가?
사용자 트래픽이 많은 파트, 가장 중요한 파트를 측정하는게 효율적이다. 경쟁사이트 또는 유사 사이트의 성능을 조사해서 지표로 삼는 것도 좋다. 즉, 자기 서비스에 대한 이해가 우선 되어야 한다.

성능 목표

성능 목표를 지정하는 방법은 여러가지가 있지만, CU 강의에서 제시해주신 방법은 크게 3가지

  1. DAU (1일 사용자 수)
  2. 피크 시간대 집중률 (최대 트래픽 / 평소 트래픽)
  3. 1명당 1일 평균 요청 수

3가지가 모두 합쳐져서 Throughput: 1일 평균 rps ~ 1일 최대 rps가 만들어진다.

계산 방법은 아래와 같다.

  • 1일 사용자 수 (DAU) * 1명당 1일 평균 접속수 = 1일 총 접속수
  • 1일 총 접속수 / 86,400 (초/일) = 1일 평균 rps
  • 1일 평균 rps * (최대 트래픽/ 평소 트래픽) = 1일 최대 rps

java reflection

getMethods()

상속한 메서드를 포함해서, 접근제한자가 public 인 메서드들만 가져온다.

Class<?> animalCLass = Student.class;
Method[] methods = animalClass.getMethods();

"toString", "getName", "getAge", 
"wait", "wait", "wait", "equals", "hashCode",
"getClass", "notify", "notifyAll"

별별 거 다 가져온다.

getDeclaredMethods()

상속한 메서드를 제외하고, 접근제한자에 관계없이 직접 코드로 선언된 메서드들을 가져온다.

Class<?> animalClass = Student.class;
Method[] methods = animalClass.getDeclaredMethods();

"getAge", "toString", "getName"

getConstructor, getDeclaredConstructor도 마찬가지.

LogBack JPA query 설정

logback.xml 설정에서 JPA query는 application.yml 프로필의 hibernate 옵션 설정을 따른다.

Querydsl 은 insert query 를 지원하지 않는다.

Spring Data Envers 는 navtive query 를 대상으로 동작하지 않는다.

  • spring data envers 는 entitry 의 변경을 감지하여 동작한다.
  • 때문에 native query 를 이용한 테이블 업데이트는 감지하지 못한다.
  • 이에 대한 현상은 jpql 을 이용한 변경감지 불가, querydsl 을 이용한 변경감지 불가가 있다.
  • https://hibernate.atlassian.net/browse/HHH-10318

성능 테스트 및 하드웨어 스펙 조절

  • 보통은 스케일 아웃으로 대응하지 스케일 업으로 대응하진 않는다.
  • 만약 2대에서 4대로 띄워서 못버티면, 10대 20대까지 띄워봐야한다.
  • 트래픽이 늘어나면 스케일 아웃으로 대응하는 법을 먼저 생각해봐야한다.
  • "pod 1대로 버틸 수 있는게 정해지고 코어를 올린 것인지?" 이런 기준이 중요함

enum 에 없는 타입이 전달 되었을 때 예외를 발생시키지 않고 기본 enum 을 선택하게 할 수 있는 방법

@JsonEnumDefaultValue 을 이용한다.

json 은 정규표현식 사용이 어렵다.

{} 를 이용한 depth 가 깊기 때문에 정규표현식 활용이 어렵다. json parser 라이브러리를 적극 활용하자. 대부분 readTree 를 통해 해결 가능하다.

캐싱할 땐 항상 중복주의

해싱을 하기 때문에 같은 데이터도 별개 id 로 저장될 수 있다.

CPU 가 기하급수적으로 튈 때

  • db connection pool 부족 문제일 수 있다.
    • 아마 내부 원인은 db connection pool 을 빠른 속도로 주고 받으면서 부하가 발생하는 듯

request dto no args constructor 왜 안됨?

intellij settings > Build, Execution, Deployment > Build Tools > Gradle 메뉴의 Build and Run 설정을 Gradle 로 변경

Redis 공부

JPA Bulk delete

  • JPA 는 bulk delete 를 수행할 때 select -> delete 를 진행한다.
  • 때문에 select 단계에서 엔티티가 조회되지 않으면 예외를 발생시킴.

Request header is too large

https://goateedev.tistory.com/126

CDC 가 추적하지 않는 것

CDC 는 truncate 를 추적하지 않는다.

디버깅이 너무 오래걸릴 때

AWS VPC

VPC

  • AWS 서비스 내에서 정의한 논리적으로 격리된 가상 네트워크
  • 집에서 공유기를 사용할 때 내 컴퓨터의 IP주소를 확인해보면(시스템 환경설정 > 네트워크) 192.168.x.x, 10.0.x.x 등인 것을 볼 수 있다. 이건 공유기를 통해 관리되는 내부망에서 할당된 IP 주소다. 네이버에 “IP 주소“를 검색해보면 위의 주소와 다른 IP가 나오는데, 이 주소는 우리집 네트워크에 할당된 IP 주소이다.
  • 위 내용 중 192.168.x.x와 같이 우리집 안에서 할당된 네트워크를 사설 IP, 가상 IP, private IP라고 부른다. 반대로 외부에서 접속할 수 있는 IP주소를 공인 IP, public IP라고 부른다. 사설 IP는 특정 네트워크 내에서 사용되는 주소이므로, 다른 네트워크에서도 동일한 주소를 사용할 수 있다.
  • 사설 IP? https://namu.wiki/w/%EC%82%AC%EC%84%A4%20IP

Subnet

  • VPC에 설정한 IP 중 일부를 나타낸다. VPC에서 할당할 수 있는 IP 중 일부를 어떤 가용 영역에서 사용할지 관리한다.

  • 서브넷은 public, private 등으로 관리할 수 있다. public 서브넷은 외부에서 접근할 수 있고, private 서브넷은 외부에서 직접 접근할 수 없다.

  • 더 궁금하면 인터넷 게이트웨이, NAT 게이트웨이를 찾아보자.

Availability Zone (AZ, 가용 영역)

  • 데이터 센터라고 생각하면 된다. AWS도 서비스 제공을 위해 서버를 어딘가에 설치하고 제공할텐데, 해당 서버가 있는 구역이다.
  • 현재 서울 리전에는 4개의 가용 영역이 있다. 하나의 가용 영역에서 서비스를 제공하면 데이터 센터에 자연재해, 화재 등의 피해가 발생했을 때 서비스가 중지될 수 있다.

Security Group (SG, 보안 그룹)

  • AWS 내에서 사용할 수 있는 방화벽이라고 생각하면 된다.
  • 어떤 트래픽이 들어오고 나갈 수 있는지에 대한 규칙을 지정할 수 있다.

Querydsl 에서 Projections.constructor 표현식에 nullable 한 인자 전달하기

java.lang.NullPointerException: Parameter specified as non-null is null: method com.kidsworld.main.domain.dto.PaperSubDto.<init>, parameter linkType
  • 불가능함. 애초에 non-nullable 한 인자만 전달하기 떄문.
  • 대체 가능한 해결 방법은 2가지
  1. groupBy 절로 묶어서 Map 으로 가져와서 일일히 매핑한다.
  2. 별개 DTO 를 만들어서 @QueryProjection 을 붙인다.

1번 방법의 경우 간단한 테이블에서는 문제가 해결된다. 그러나 1:N:N 으로 2depth 이상 계위가 들어가는 구조에서는 상당히 복잡하다. 때문에 엔티티가 붙는게 조금 껄끄럽지만, @QueryProjection 를 사용해서 문제 해결했음.

AWS Gateway API

  • aws 자체적으로 gateway 솔루션을 제공한다.

New module -- jackson-module-no-ctor-deser -- now included in jackson-modules-base -- added to support a very specific use case of POJOs that do not have either:

  1. No-arguments ("default") constructor, nor
  2. @JsonCreator annotated constructor or factory method

in which case module can force instantiation of values without using any constructor, using JDK-internal implementation (included to support JDK serialization itself). Note that this module may stop functioning in future, but appears to work at least until JDK 14.

jackson 라이브러리 2.13 버전부터는 default 생성자 없이도 @RequestBody 어노테이션을 이용하는 request DTO 생성이 가능하다. 언제 지원이 끊길지는 알 수 없으나, JDK 14 버전까지는 잘 동작하는 중.

AutoConfigureTestDatabase

포트어댑터 패턴에서 집중해야 하는 것은 코드 재사용성이 아니라, 어댑터 모양에 알맞는 포트는 무엇이든 끼울 수 있다는 뜻의, 결국 포트의 규격만 맞춰서 어떻게 구현하든 상관없는, 다른 유스케이스에서 사용하는 혹은 다른 의존체의 모양을 신경쓸 필요 없이 나의 유스케이스에만 맞춰서 코드를 작성하면 되는 격리성에 집중해야하는거 아닐까?

About

학습한 내용을 정리하자!

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published