JPA 사용 할 때 발생할 수 있는 N+1 문제와 관련해서 Spring Data JDBC에서 기능 추가를 준비하고 있나 봅니다.(2023년 8월 31일 글 업데이트 됨)
 - 참고. N+1문제)  교실과 학생이 1:N인 관계라면, 교실 10건을 조회하는 한번의 질의를 실행해도 학생 테이블은 교실 갯수 10번의 쿼리가 실행되어 총 11번 쿼리가 실행되는 문제
 
참고 링크
 - https://spring.io/blog/2023/08/31/this-is-the-beginning-of-the-end-of-the-n-1-problem-introducing-single-query
- https://github.com/spring-projects/spring-data-relational/issues/1445

아래 글을 읽었는데 얻는게 많아서 메모해 둡니다.

 - 추후 제가 즐겨 쓰는 기술스택으로 프로토타이핑해서 개발을 진행해볼 예정입니다.

https://medium.com/@Games24x7Tech/prevent-fraud-and-collusion-the-graph-way-503984bb3133

개인 서버의 mysql을 8.x로 버전업하면서 간략하게 정리한 내용입니다.

메모 목적으로 정리한거라서 생략된 부분이 조금 있습니다.

 

 

#설치가능한 MySQL 8.0 저장소 확인
https://dev.mysql.com/downloads/repo/yum/

#MySQL 8.0 저장소 설치
yum install https://dev.mysql.com/get/mysql80-community-release-el7-9.noarch.rpm


# MySQL 8.0을 설치
yum install mysql-server


#설치된 MySQL 버전 확인
mysqld -V

#MySQL 시작 및 자동 실행 등록
systemctl start mysqld
systemctl enable mysqld


#초기 비밀번호 확인
grep 'temporary password' /var/log/mysqld.log


#비밀번호 변경(위 초기 비밀번호 확인 후 해당 값으로 접속)
mysql -u root -p  로 mysql 콘솔 접속 후

아래 명령어로 비밀번호 변경
ALTER USER 'root'@'localhost' IDENTIFIED BY '비밀번호';


#아래는 필요시 mysql 서버 설정 수정 사항들
vi /etc/my.cnf 후 아래 내용을 [mysqld] 영역에 추가

bind-address = 0.0.0.0

character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
init_connect=SET collation_connection = utf8mb4_unicode_ci
init_connect=SET NAMES utf8mb4


이후 아래 명령어로 mysql서버 재 시작
systemctl restart mysqld


#계정 생성 및 권한 추가 관련 기타
# MySQL 8에서는 5와 다르게 계정 생성과 DB 권한 부여를 각각 해줘야함
# 참고로 기존 5버전과 동일한 방식으로 계정을 만들고 싶으면 'mysql_native_password' 방식으로 만들어야함. 잘못하면 로그인 힘듬

mysql> create user '계정ID'@'%' identified by '비밀번호' ;
mysql> grant all privileges on DB이름.* to '계정ID'@'%' with grant option;
mysql> flush privileges;

# 기존방식
mysql> create user '계정ID'@'%' identified WITH mysql_native_password by '비밀번호' ;

#샘플
create user '계정'@'%' identified by '암호블라블라' ;
grant all privileges on test_db.* to '계정'@'%' with grant option;
flush privileges;

#참고. MySQL에서 caching_sha2_password 을 mysql_native_password으로 플러그인을 변경
ALTER user '유저명'@'localhost' IDENTIFIED WITH mysql_native_password by '비밀번호';


#참고: root 계정을 모든 IP에서 접근허용 처리(보안 조심)
GRANT ALL PRIVILEGES ON *.* to 'root'@'%';

#결과 확인 용 쿼리
#SELECT Host,User,plugin,authentication_string FROM mysql.user;

mysql> use mysql 후에 아래 명령어로 확인
mysql> SELECT host, user, plugin, LEFT(authentication_string,15) AS password, password_last_changed FROM user ;


#기타: virtualbox 등에 설치해서 방화벽으로 차단되고 있다면 내리거나 허용
# 방화벽 중지
systemctl disable firewalld
systemctl stop firewalld

Cassandra disk 튜닝과 관련해서 몇가지 메모들


https://www.ibm.com/docs/en/cloud-app-management/2019.4.0?topic=performance-optimizing-disk-cassandra

 

JDK 17로 버전업을 준비하면서 ZGC도 간략하게 정리해봤습니다.

기존에 알고 있던 내용도 있었지만, 리마인드차원에서 정리한 부분도 있습니다.

ZGC 간략한 정리 마인드맵 버전

-----

아래는 위 마인드맵의 아웃라인 텍스트 버전입니다.

 

 

ZGC 간략한 정리
ZGC란?
적용 버전
JDK 11에서 실험적 기능으로 추가
JDK 15에서 정식 GC로 인정
LTS 버전인 JDK 17에도 반영
ZGC 메모리 구조
메모리를 ZPage라는 논리적인 단위로 구분
G1 GC에서는 region 이라는 논리 단위 사용
ZPage는 3가지 타입 존재하고, 들어갈 수 있는 객체 크기가 제한됨
small
medium
large
주의: 단 하나의 객체만 할당 가능
colored pointer
Linux x86-64 아키텍쳐에서 가상 메모리 주소를 위해 48bit 사용해 256TB 대역의 가상 메모리를 사용
ZGC는 6bit적은 42bit를 사용
42~45번째 bit는 colored pointer로 사용해 GC처리에 활용
colored pointer에는 marked0, marked1, remapped, finalizable이 있다
marked0과 marked1, remapped는 각 포인터를 사용하는 CG 단계에서 마스킹을 통해 가상 메모리 주소를 가져오는 데 사용
그렇기 때문에 ZGC를 사용하는 환경에서는 RSS(resident set size)가 실제 메모리 사용량보다 3배 크게 관측
주의: 트래픽이 많은 환경에서 JVM heap 설정에 맞게 maxmapcount 값을 수정하지 않으면  JVM 크래시
이를 확보하지 않고 JVM을 실행해 OOM(out of memory)가 발생하게 하면 크래시가 발생한다
(max_capacity/ZGranuleSize) x 3 x 1.2'로 설정
sudo cat /proc/sys/vm/max_map_count
AWS와 같은 클라우드에서 OOM killers에 의해 강제 종료 되는걸 조심
예) -xmx=4G → RSS ~= 12G : 실제 메모리는 4G 까지 써도 RSS 사이즈는 12G정도까지 관측될 수 있음
load barrier
load barrier는 쉽게 말해 heap으로부터 참조가 일어날 때마다 실행되는 코드
참조전에 방어막처럼 막음
ref가 유효한지 체크해서 fast path, slow path 등
ZGC의 처리 방식
10단계를 거쳐서 처리
coloring
phase 1~phase 5
이전 세대 GC의 marking과 동일
Java heap에 있는 객체 중 reachable 객체와 unreachable 객체를 탐색해 표시(marking)하는 과정
unreachable 객체는 더 이상 참조가 일어나지 않는 객체이기 때문에 GC의 대상
relocation
phase 6~phase 10
coloring 단계를 거친 객체를 재배치하는 단계
특징
STW 상태를 10ms 아래로 가져가는 것
대기 시간이 짧은 Application에 적합한 GC
Thread가 실행중일때 동시 작업을 수행하기에 모든 작업을 동시에 수행 (병렬처리)
8MB~16TB까지의 heap 크기를 지원
적용 방법
JDK 17이상(가능하면) 사용
-XX:+UseZGC
참고 링크
https://d2.naver.com/helloworld/0128759
https://www.blog-dreamus.com/post/zgc%EC%97%90-%EB%8C%80%ED%95%B4%EC%84%9C
https://ionutbalosin.com/2020/01/hotspot-jvm-performance-tuning-guidelines/

최근에 읽었던 글들 중에서 좋았던 글 중 1개 메모

 

https://www.allthingsdistributed.com/2023/07/building-and-operating-a-pretty-big-storage-system.html

 

Building and operating a pretty big storage system called S3

Three distinct perspectives on scale that come along with building and operating a storage system the size of S3.

www.allthingsdistributed.com


https://careerly.co.kr/comments/88808?utm_campaign=user-share
에 간략히 한글로 요약해두셨음

adoptium(현 eclipse재단쪽 관리) JDK17 설치 방법 메모

 - java 17로 프로젝트를 모두 업그레이드 중이라서 정리 중입니다.

 

#OpenJDK17 을 ~/apps 디렉토리 하위에 설치하는 명령어
# https://github.com/adoptium/temurin17-binaries/releases 에서 최신 버전 다운로드 가능

# 다운로드. 2023-08-03 기준 최신 버전
wget 'https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.8%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.8_7.tar.gz' -O ~/apps/OpenJDK17U-jdk_x64_linux_hotspot_17.0.8_7.tar.gz


# 압축해제 및 삭제
cd ~/apps && tar -xzf OpenJDK17U-jdk_x64_linux_hotspot_17.0.8_7.tar.gz && rm -f OpenJDK17U-jdk_x64_linux_hotspot_17.0.8_7.tar.gz


# 심볼릭 링크 (필요시) 심볼릭 링크가 존재한다면, 삭제 후 재생성
cd ~/apps && rm jdk_17 && ln -s jdk-17.0.8+7 jdk_17



## DNS TTL 무제한 -> 10초로 수정 (어플리케이션마다 달라야 할 수 있음)
echo 'networkaddress.cache.ttl=10' >> ~/apps/jdk_17/conf/security/java.security

## 환경 변수 추가(jdk 17을 설정할지 판단)
# 아래는 환경변수만 추가
echo 'export JAVA_17_HOME=~/apps/jdk_17' >> ~/.bashrc && source ~/.bashrc
# 아래는 환경변수 및 디폴트 jdk 설정
echo 'export JAVA_17_HOME=~/apps/jdk_17' >> ~/.bashrc && echo 'export PATH=$JAVA_17_HOME/bin:$PATH' >> ~/.bashrc && source ~/.bashrc

# 기타 - jdk 버전 확인
$JAVA_HOME/bin/java -version
$JAVA_17_HOME/bin/java -version

파일 업로드 기반의 기능을 종종 개발해야할 일이 있습니다.
이때 업로드 파일의 유효성(특히, 보안문제로)을 잘 진행해줘야합니다.
 
간단하게 범용적으로 사용할만한 커스텀 어노테이션을 만들어서 메모 목적으로 글을 작성해둡니다.
 
 
* 요약: 파일명의 확장자 검사뿐만 아니라, 확장자가 수정된 파일인지도 잘 검사해줘야함
 
1. apache tika 라이브러리를 추가해서 mime타입 검사에 사용

<!-- 업로드 파일의 확장자 검사 등의 목적 -->
<dependency>
    <groupId>org.apache.tika</groupId>
    <artifactId>tika-core</artifactId>
    <version>2.8.0</version>
</dependency>

 
2. 업로드 허용 파일들에 대해서 정의 enum 작성
 - 코드 관리를 조금 더 타이트하게 하려고 정의했는데 안쓰고 구현해도 무방

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 업로드 허용 파일에 대한 정의 Enum
 *  - 실제 사용되는 일부 파일들에 대한 정의만 추가되어 있으니, 신규 정의가 필요하면 내용 작성해서 사용
 *
 * @author 
 */
@Getter
@AllArgsConstructor
public enum UploadAllowFileDefine {

	// @formatter:off
	CSV("csv", new String[]{"text/csv", "text/plain"}), //텍스트 에디터에서 수정되는 text/plain 도 허용함
	;
	
	// @formatter:on

	private String fileExtensionLowerCase; //파일 확장자(소문자)
	private String[] allowMimeTypes; //허용하는 mime type array(파일 내용 변조 후 확장자 변경하는 공격을 막기 위해서 사용. 2023-07-31 기준 apache TIKA로 detect 중)

}

 
3. 커스텀 valid 인터페이스 작성

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 업로드 파일의 유효성 검사 체크용
 *
 * @author 
 */
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = FileUploadValidator.class)
public @interface FileUploadValid {

	String message();

	Class<?>[] groups() default {};

	Class<? extends Payload>[] payload() default {};

	/** 업로드 허용 파일들의 정의 array(여러 종류의 파일 타입을 허용할 수도 있기에 array) */
	UploadAllowFileDefine[] allowFileDefines();

}

 
4. 커스텀 validator 구현체 작성

....

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tika.Tika;
import org.springframework.web.multipart.MultipartFile;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.io.IOException;

/**
 * 업로드 파일의 유효성 검사 체크용
 *  - 사용 방법 예) @FileUploadValid(allowFileDefines = {UploadAllowFileDefine.CSV}, message = "유효한 CSV파일만 업로드 가능합니다.")
 *
 * @author 
 */
@Slf4j
public class FileUploadValidator implements ConstraintValidator<FileUploadValid, MultipartFile> {
	private FileUploadValid annotation;

	@Override
	public void initialize(FileUploadValid constraintAnnotation) {
		this.annotation = constraintAnnotation;
	}

	@Override
	public boolean isValid(MultipartFile multipartFile, ConstraintValidatorContext context) {

		if (multipartFile.isEmpty()) {
			context.buildConstraintViolationWithTemplate("업로드 대상 파일이 없습니다. 정확히 선택 업로드해주세요.(There is no file to upload. Please upload correctly)").addConstraintViolation();
			return false;
		}

		final String fileName = multipartFile.getOriginalFilename();
		if (StringUtils.isBlank(fileName)) {
			context.buildConstraintViolationWithTemplate("업로드 요청한 파일명이 존재하지 않습니다.(not exist file name)").addConstraintViolation();
			return false;
		}

		try {
			int targetByte = multipartFile.getBytes().length;
			if (targetByte == 0) {
				context.buildConstraintViolationWithTemplate("파일의 용량이 0 byte입니다.(The size of the file is 0 bytes.)").addConstraintViolation();
				return false;
			}
		} catch (IOException e) {
			log.error(e.getMessage(), e);
			context.buildConstraintViolationWithTemplate("파일의 용량 확인 중 에러가 발생했습니다.(An error occurred while checking the file size.)").addConstraintViolation();
			return false;
		}

		//허용된 파일 확장자 검사
		final String detectedMediaType = this.getMimeTypeByTika(multipartFile); //확장자 변조한 파일인지 확인을 위한 mime type 얻기

		final UploadAllowFileDefine[] allowExtArray = annotation.allowFileDefines();
		final String fileExt = FilenameUtils.getExtension(fileName);
		for (UploadAllowFileDefine allowDefine : allowExtArray) {

			//파일명의 허용 확장자 검사
			if (StringUtils.equals(allowDefine.getFileExtensionLowerCase(), fileExt.toLowerCase()) == false) {
				StringBuilder sb = new StringBuilder();
				sb.append("허용되지 않는 확장자의 파일이며 다음 확장자들만 허용됩니다. This is a file with a disallowed extension, and only the following extensions are allowed.");
				sb.append(": ");
				sb.append(ArrayUtils.toString(allowExtArray));
				context.buildConstraintViolationWithTemplate(sb.toString()).addConstraintViolation();

				return false;
			}

			//파일 변조 업로드를 막기위한 mime타입 검사(예. exe파일을 csv로 확장자 변경하는 업로드를 막음)
			if (ArrayUtils.contains(allowDefine.getAllowMimeTypes(), detectedMediaType) == false) {
				StringBuilder sb = new StringBuilder();
				sb.append("확장자 변조 파일은 허용되지 않습니다.(Modified files with extensions are not allowed.)");
				context.buildConstraintViolationWithTemplate(sb.toString()).addConstraintViolation();

				return false;
			}
		}

		return true;
	}

	/**
	 * apache Tika라이브러리를 이용해서 파일의 mimeType을 가져옴
	 *
	 * @param multipartFile
	 * @return
	 */
	private String getMimeTypeByTika(MultipartFile multipartFile) {

		try {

			Tika tika = new Tika();
			String mimeType = tika.detect(multipartFile.getInputStream());
			log.debug("업로드 요청된 파일 {}의 mimeType:{}", multipartFile.getOriginalFilename(), mimeType);

			return mimeType;

		} catch (IOException e) {
			log.error(e.getMessage(), e);
			return null;
		}
	}

}

 
사용 예

@FileUploadValid(allowFileDefines = {UploadAllowFileDefine.CSV}, message = "유효한 CSV파일만 업로드 가능합니다.")
private MultipartFile targetCsvFile;

+ Recent posts