-
Notifications
You must be signed in to change notification settings - Fork 0
김수현 8주차 학습일지
네, JPA에서 Pageable
객체를 사용하면, 내부적으로 LIMIT
와 OFFSET
을 활용하여 페이징을 구현합니다.
Pageable
객체는 JPA와 함께 사용되어 데이터베이스 쿼리에 페이징 기능을 추가하는 데 사용됩니다. Pageable
은 페이지 번호와 페이지 크기를 포함하는 페이징 정보와 정렬 정보를 캡슐화하는 인터페이스입니다.
pageable은 jpql에 적용가능하고
Chunked Encoding을 사용하는 경우, 청크의 크기는 서버에서 데이터가 준비되는 대로 결정됩니다. Spring이나 Java 코드에서 청크의 크기를 직접 설정할 수 없으며, 이는 기본적으로 스트리밍을 제어하는 서버와 클라이언트 사이에서 처리됩니다.
Spring에서 InputStream
을 HTTP 응답의 body에 쓰면, Spring 자체가 이를 자동으로 버퍼링하여 청크 단위로 클라이언트에 전송합니다. 이를 통해 전체 데이터를 메모리에 올리지 않고, 스트리밍 방식으로 데이터를 처리할 수 있습니다.
커서 기반 페이징과 노 오프셋(No-Offset) 페이징은 비슷한 개념이지만, 완전히 같은 것은 아닙니다. 이 둘을 구분해서 설명해드리겠습니다.
커서 기반 페이징은 데이터베이스에서 많은 양의 데이터를 페이지 단위로 나누어 가져올 때 사용하는 방법입니다. 이 방식에서는 페이지 번호 대신, "커서"라고 불리는 특정 위치를 가리키는 값을 사용합니다. 이 커서는 주로 마지막으로 가져온 항목의 고유 식별자(예: ID, 타임스탬프)입니다.
- 작동 방식: 클라이언트는 현재 페이지에서 마지막으로 가져온 항목의 ID나 타임스탬프와 같은 커서를 서버로 전달합니다. 서버는 이 커서를 기준으로 다음 페이지의 데이터를 가져옵니다. 이 방식으로 페이지 간 이동이 이루어집니다.
-
장점: 대용량 데이터셋에서
OFFSET
을 사용하는 방식보다 성능이 좋습니다. 특히, 데이터가 중간에 추가되거나 삭제되는 경우에도 정확한 데이터를 제공할 수 있습니다.
노 오프셋 페이징은 커서 기반 페이징의 변형된 방식 중 하나입니다. 여기서도 OFFSET
을 사용하지 않고 특정 위치부터 데이터를 가져오는 방식을 사용합니다. 다만, 이 방식은 커서를 사용하는 대신, SQL 쿼리에서 OFFSET
을 사용하는 것을 피하고, 바로 특정 위치에서 시작하도록 합니다.
-
작동 방식: 일반적으로
id > last_id
와 같은 형태로 쿼리를 실행합니다. 이는OFFSET
없이 특정 ID 이후의 데이터를 가져오게 됩니다. -
장점:
OFFSET
을 사용하는 것보다 더 빠른 성능을 제공하며, 대규모 데이터셋에서 더 효율적입니다.
- 커서 기반 페이징은 클라이언트가 커서를 받아서 이후 요청 시 이 커서를 사용해 다음 데이터를 가져오는 방식입니다. 이 커서는 보통 문자열 형태로 암호화되어 클라이언트에게 전달될 수 있습니다.
-
노 오프셋 페이징은 커서를 사용하는 대신 단순히
id > ?
와 같은 비교 연산을 통해 다음 데이터를 가져오는 방법입니다.
두 방식 모두 전통적인 OFFSET
기반 페이징의 성능 문제를 해결하는 방법이지만, 커서 기반 페이징은 더 복잡한 논리를 포함할 수 있고, 노 오프셋 페이징은 상대적으로 단순한 형태로 구현됩니다. 데이터 양이 많거나 실시간으로 변화하는 데이터셋에 대해서는 두 방식 모두 유용할 수 있습니다.
-
커서 기반 페이징 사용:
-
pageSize
에 의존하는 대신, 서버가 다음 데이터 세트를 가리키는 커서나 토큰을 반환할 수 있습니다. 이 커서가null
이라면 더 이상 데이터가 없음을 의미합니다.
-
-
명시적인 "hasMore" 표시기:
- 일부 API는 데이터와 함께 추가 페이지가 있는지 여부를 직접 나타내는
hasMore
또는hasNext
불리언 플래그를 반환합니다.
- 일부 API는 데이터와 함께 추가 페이지가 있는지 여부를 직접 나타내는
-
하나 더 요청하기:
- 요청할 때
pageSize + 1
개의 항목을 요청한 다음 클라이언트에게 첫 번째pageSize
항목만 반환하는 방법도 있습니다. 이 경우 정확히pageSize + 1
개의 항목을 받으면 더 많은 데이터가 있는 것입니다. 그렇지 않다면 더 이상 데이터가 없다는 것을 알 수 있습니다.
- 요청할 때
- count 쿼리를 방지하지 위해서
MyBatis에서는 XML 매퍼 파일을 사용하여 SQL 쿼리를 작성할 수 있습니다. 아래는 주어진 쿼리를 MyBatis로 작성하는 예시입니다.
<mapper namespace="com.example.mapper.FileMetadataMapper">
<select id="findFilesByCursor" resultType="com.example.domain.FileMetadata">
SELECT f.*
FROM FileMetadata f
WHERE f.parentFolderId = #{folderId}
AND f.id > #{cursorId}
AND f.uploadStatus = #{uploadStatus}
ORDER BY f.id
LIMIT #{pageable.pageSize} OFFSET #{pageable.offset}
</select>
</mapper>
public interface FileMetadataMapper {
List<FileMetadata> findFilesByCursor(@Param("folderId") Long folderId,
@Param("cursorId") Long cursorId,
@Param("uploadStatus") UploadStatus uploadStatus,
@Param("pageable") Pageable pageable);
}
QueryDSL을 사용하면 타입 안전한 방식으로 쿼리를 작성할 수 있습니다. QueryDSL은 JPA와 함께 사용할 수 있으며, 아래는 주어진 쿼리를 QueryDSL로 작성하는 예시입니다.
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.springframework.data.domain.Pageable;
import java.util.List;
public class FileMetadataRepositoryImpl implements FileMetadataRepositoryCustom {
private final JPAQueryFactory queryFactory;
public FileMetadataRepositoryImpl(JPAQueryFactory queryFactory) {
this.queryFactory = queryFactory;
}
@Override
public List<FileMetadata> findFilesByCursor(Long folderId, Long cursorId, UploadStatus uploadStatus, Pageable pageable) {
QFileMetadata fileMetadata = QFileMetadata.fileMetadata;
return queryFactory.selectFrom(fileMetadata)
.where(fileMetadata.parentFolderId.eq(folderId)
.and(fileMetadata.id.gt(cursorId))
.and(fileMetadata.uploadStatus.eq(uploadStatus)))
.orderBy(fileMetadata.id.asc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}
}
import org.springframework.data.domain.Pageable;
import java.util.List;
public interface FileMetadataRepositoryCustom {
List<FileMetadata> findFilesByCursor(Long folderId, Long cursorId, UploadStatus uploadStatus, Pageable pageable);
}
import org.springframework.data.jpa.repository.JpaRepository;
public interface FileMetadataRepository extends JpaRepository<FileMetadata, Long>, FileMetadataRepositoryCustom {
}
- MyBatis: XML 매퍼 파일을 사용하여 SQL 쿼리를 직접 작성하며, 인터페이스에서 쿼리를 호출합니다.
- QueryDSL: 타입 안전하고 동적인 쿼리 생성을 지원하며, 코드에서 직접 쿼리를 작성할 수 있습니다.
두 가지 방법 모두 장단점이 있으며, MyBatis는 SQL 작성의 유연성을 제공하고, QueryDSL은 타입 안전성과 코드 내에서 쿼리를 작성하는 편의성을 제공합니다. 선택은 프로젝트의 요구사항에 따라 달라질 수 있습니다.
Repository Bean Registration Conflict:
- This issue can also arise if there’s a conflict between the Spring Data JPA repository and a custom bean definition that registers another repository with the same name.
Solution:
- Ensure that each repository or mapper is uniquely named or qualified, and make sure you’re not accidentally registering two beans with the same name and class.
- The issue you're encountering occurs because when you use
@MapperScan
to scan MyBatis mappers in a package, Spring might also accidentally pick up Spring Data JPA repositories if they are located in the same package. This can lead to conflicts since both MyBatis mappers and Spring Data JPA repositories might have similar names, leading to the error you mentioned earlier. - . Separate Packages for Repositories and Mappers
[Annotation-specified bean name 'fileMetadataRepository' for bean class [com.woowacamp.storage.domain.file.repository.FileMetadataRepository] conflicts with existing, non-compatible bean definition of same name and class [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean]](https://www.notion.so/Annotation-specified-bean-name-fileMetadataRepository-for-bean-class-com-woowacamp-storage-domain-11e6943715194ce4b8ba0482cc59444a?pvs=21)
java.lang.IllegalStateException: The following classes could not be excluded because they are not auto-configuration classes:
- com.woowacamp.storage.domain.folder.mapper.FolderMetadataMapper
- com.woowacamp.storage.domain.file.mapper.FileMetadataMapper
autoconfigure에 들어가는 빈으로 등록하는 어노테이션 없음
B-tree 의 등장 배경
- balanced tree가 왜 나왔는지
- 디스크 접근시 블록 단위로 접근하는 것을 사용해서. 디스크 접근 횟수를 최소화할 수 있음.
- 각 노드가 가질 수 있는 자식 포인터와 키의 개수를 최대한으로 늘려서 노드의 크기가 디스크 블록 크기에 맞춰지도록 설계. 하나의 디스크 블록이 4KB 크키라면B-Tree의 노드는 그 블록 크기에 맞춰 키와 포인터를 최대로 채우게 됨.
- "디스크 블록 크기에 맞춰서 설계된다”
B-tree 의 동작 방식
-
키(Key): 노드에 저장된 값들로, 각 키는 오름차순으로 정렬되어 저장됩니다.
-
자식 포인터(Child Pointer): 키와 키 사이의 범위에 해당하는 자식 노드를 가리킵니다.
-
검색
- 루트 노드에서 시작: 검색은 루트 노드에서 시작합니다.
-
키 비교: 현재 노드에서 각 키를 탐색하는 값과 비교합니다.
- 탐색하려는 값이 현재 노드의 키 중 하나와 일치하면, 해당 키가 가리키는 데이터를 반환합니다.
- 탐색하려는 값이 현재 노드의 키들과 일치하지 않으면, 탐색하려는 값이 어느 두 키 사이에 위치하는지를 확인한 후, 해당 범위의 자식 포인터를 따라 다음 노드로 이동합니다.
- 리프 노드에 도달할 때까지 반복: 이 과정을 리프 노드에 도달할 때까지 반복합니다. 리프 노드에서도 값을 찾을 수 없다면, 해당 값은 트리에 존재하지 않습니다.
-
- 적절한 리프 노드 찾기: 삽입하려는 값을 기준으로 트리를 탐색하여 적절한 리프 노드를 찾습니다.
-
리프 노드에 삽입: 해당 리프 노드에 삽입하려는 값을 추가합니다.
- 만약 노드에 빈 공간이 있다면, 단순히 키를 삽입하고 트리를 재정렬합니다.
-
노드 분할: 만약 노드가 가득 차게 되면(즉, 노드의 키 개수가
m-1
개를 초과하게 되면), 노드를 분할해야 합니다.- 중간 키를 선택해 노드를 두 개로 분할하고, 중간 키를 부모 노드로 올려보냅니다.
- 부모 노드가 가득 찼다면, 부모 노드 역시 분할하고, 이 과정이 루트 노드까지 반복될 수 있습니다. 이 경우 트리의 높이가 1 증가하게 됩니다.
-
삭제
B-tree와 B+ tree의 차이점
- b-tree는 여러개의 노드를 가질 수 있음. 하나의 블록에 여러 데이터들을 동시ㅔ 저장하기 위해서
- b-tre는 정렬되어있고 높이가 Log이기 때문에 빠르게 찾을 수 있다.
- b- tree는 균형 트리 구조를 유지하기 위해서 추가적인 연산이 수행된다. b-tree에서 몇 가지 규칙이 추가된 등장하게 되었음. 탐색을 위해서 노드를 찾아서 이동해야한다. 같은 레벨의 모든 키 값들이 정렬되어 있고 같은 레벨의 sbiling node는 연결리스트 형태로 이어져있음.
- 키 값은 중복될 수 있고 데이터 검색을 위해서는 반드시 leaf node까지 내려가야함. 검색 속도가 빠름?
- b-tree는 데이터베이스에서 쓰고 b+ 트리는 멀티 레벨인덱싱에서 쓴다.
- 이로 인해 B+Tree에서는 리프 노드들만 순차적으로 접근해도 모든 데이터를 쉽게 조회할 수 있습니다. 이는 특히 범위 검색(range query)에서 B+Tree가 더 유리하게 작용합니다.
- 하나의 B-Tree 노드(키와 자식 포인터를 포함한 데이터 구조 전체)가 디스크에서 한 번에 읽어올 수 있는 4KB 블록 크기와 일치하도록 설계
- 인덱스가 디스크에 저장되기 때문에, 인덱스를 사용한 검색 과정에서도 디스크 I/O가 발생합니다. 하지만 인덱스의 크기는 일반적으로 테이블의 데이터보다 훨씬 작기 때문에, 인덱스를 사용하는 것이 디스크 I/O를 최소화하고 성능을 크게 향상시킬 수 있습니다.
- 인덱스를 디스크에서 읽어올때의 효율성을 위해서 인덱스의 각 노드를 페이지 블록 크기와 맞추는 거야?
- 디스크 블록 크기는 운영체제와 파일 시스템에 의해 결정되며, 일반적으로 4KB, 8KB, 16KB 등으로 설정됩니다.
- 디스크 블록 크기는 B-Tree의 차수에 영향을 미칠 수 있습니다.
논클러스터드 인덱스의 키는 특정 열의 값을 기준으로 인덱스를 생성하지만, 인덱스는 실제 데이터의 위치를 직접 포함하지 않고, 데이터가 저장된 위치(즉, "포인터" 또는 "RID - Row Identifier")를 저장합니다.
정을 "북마크 조회" 또는 **"키 룩업"**이라고도 합니다.
- 데이터는 별도의 위치(기본 테이블 또는 클러스터드 인덱스가 적용된 테이블)에서 관리되며, 논클러스터드 인덱스는 그 위치를 참조하는 역할을 합니다.
- 데이터베이스 테이블의 특정 행(레코드)이 디스크의 어느 위치에 저장되어 있는지를 나타내는 고유한 식별자입니
- 논클러스터드 인덱스에서 키가 가리키는 **RID(Row Identifier)**는 클러스터드 인덱스를 가리키거나, 클러스터드 인덱스가 없는 경우 디스크에서의 실제 데이터 위치를 가리킵니다. 이
- 힙 테이블 : 인덱스 없이 데이터만 쌓음. oracle default
- 클러스터드 인덱스
- nonclusted index
지도 정보를 MySQL에 저장하기 위해서는 공간 데이터와 관련된 도구와 방법을 사용해야 합니다. MySQL은 지리적 좌표를 처리하고 저장할 수 있는 GIS(Geographic Information System) 기능을 제공합니다.
MySQL은 다음과 같은 공간 데이터 유형을 제공합니다:
-
POINT
: 좌표를 나타내는 단일 점을 저장합니다. 예를 들어, 특정 위치의 위도와 경도를 저장할 때 사용됩니다. -
LINESTRING
: 여러 점을 연결한 선을 저장합니다. 예를 들어, 도로 경로를 저장할 때 사용됩니다. -
POLYGON
: 닫힌 다각형을 저장합니다. 예를 들어, 특정 구역이나 지역을 정의할 때 사용됩니다. -
MULTIPOINT
,MULTILINESTRING
,MULTIPOLYGON
: 각각 다중 점, 다중 선, 다중 다각형을 저장할 수 있습니다.
아래는 MySQL에서 POINT
유형을 사용해 좌표를 저장하고, 공간 인덱스를 사용하는 방법에 대한 예시입니다.
sql코드 복사
CREATE TABLE locations (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
coordinates POINT,
SPATIAL INDEX(coordinates)
);
- 데이터 삽입:
sql코드 복사
INSERT INTO locations (name, coordinates) VALUES
('Central Park', ST_GeomFromText('POINT(40.785091 -73.968285)')),
('Times Square', ST_GeomFromText('POINT(40.758896 -73.985130)'));
공간 데이터는 특정 기능을 사용하여 검색할 수 있습니다. 예를 들어, 특정 반경 내의 모든 지점을 찾는 쿼리는 다음과 같이 작성할 수 있습니다:
sql코드 복사
SELECT name
FROM locations
WHERE ST_Distance_Sphere(coordinates, ST_GeomFromText('POINT(40.758896 -73.985130)')) < 1000;
위 쿼리는 Times Square에서 반경 1,000 미터 이내에 있는 모든 위치를 검색합니다.
- MySQL은 다양한 인덱스 유형을 제공하며, 각각의 인덱스는 특정한 용도에 맞게 사용됩니다.
- 지도 정보나 지리적 데이터를 MySQL에 저장하려면, 공간 데이터 유형과 함께 공간 인덱스를 사용하여 효율적으로 데이터를 저장하고 검색할 수 있습니다. 이를 통해 GIS 응용 프로그램에서 지도 정보를 관리하는 데 MySQL을 효과적으로 사용할 수 있습니다.
MySQL은 다양한 인덱스 유형을 지원하며, 각 인덱스는 특정한 용도에 따라 사용됩니다. 아래는 MySQL에서 지원하는 주요 인덱스의 종류와 그 용도입니다.
-
PRIMARY KEY (기본 키 인덱스)
- 설명: PRIMARY KEY는 테이블에서 각 행을 고유하게 식별하는 열에 대해 정의됩니다. PRIMARY KEY는 자동으로 클러스터드 인덱스를 생성합니다.
- 용도: 각 행을 고유하게 식별해야 할 때 사용되며, 테이블 내에서 하나만 존재할 수 있습니다.
-
UNIQUE INDEX (유니크 인덱스)
- 설명: UNIQUE INDEX는 특정 열의 값이 고유하도록 보장합니다. 중복된 값을 허용하지 않습니다.
- 용도: 고유한 값을 보장해야 하는 열에 사용됩니다. 예를 들어, 이메일 주소나 사용자 ID 등에서 사용됩니다.
-
INDEX (비클러스터드 인덱스)
- 설명: 일반적인 인덱스로, 테이블의 데이터를 정렬하거나 특정 검색 작업을 빠르게 수행하기 위해 사용됩니다. 데이터의 물리적 순서에 영향을 주지 않습니다.
- 용도: 자주 조회되는 열에 대해 인덱스를 설정하여 검색 성능을 향상시킵니다.
-
FULLTEXT INDEX (전문 검색 인덱스)
-
설명: 텍스트 기반의 데이터에서 전체 텍스트 검색을 수행할 수 있는 인덱스입니다. 주로
VARCHAR
,TEXT
등의 열에 적용됩니다. - 용도: 문서, 게시글 등에서 특정 단어 또는 문구를 검색할 때 사용됩니다. 예를 들어, 블로그 게시글 검색에 유용합니다.
-
설명: 텍스트 기반의 데이터에서 전체 텍스트 검색을 수행할 수 있는 인덱스입니다. 주로
-
SPATIAL INDEX (공간 인덱스)
-
설명: 공간 데이터를 효율적으로 검색하기 위한 인덱스입니다. MySQL의 공간 데이터 유형(예:
POINT
,LINESTRING
,POLYGON
)에 사용됩니다. - 용도: 지도 정보나 지리적 데이터를 저장하고 검색할 때 사용됩니다. GIS(Geographic Information System)와 같은 응용 프로그램에서 유용합니다.
-
설명: 공간 데이터를 효율적으로 검색하기 위한 인덱스입니다. MySQL의 공간 데이터 유형(예:
-
COMPOSITE INDEX (복합 인덱스)
- 설명: 여러 열을 결합하여 하나의 인덱스를 생성합니다. 검색 조건에 여러 열이 포함될 때 유용합니다.
-
용도: 다중 열을 사용한 쿼리의 성능을 최적화할 때 사용됩니다. 예를 들어,
WHERE
절에 여러 열이 포함된 경우, 복합 인덱스가 성능을 크게 향상시킬 수 있습니다.
- b트리는 왜 쓰나? 빠른 탐색을 위해서
- 페이지 단위로 저장되어있지 않아서 “디스크 i/o” 가 적합하지 않음
- 메모리기반 인덱스에 적합한 자료구조? 해시테이블, bst, avl트리 등, 트라이
- mongo db mysql 무조건 b + tree
- b+tree
- b+ tree는 리프노트에만 데이터가 저장됨
- b tree는 중간 노드에도 데이터가 저장됨
- 리프노드에만 키가 있음. 중복을 허용하지 않는다면 중간에 같은 값이 올 수 있음
- 리프에 다음 노드에 대한 포인터가 있어서 range 검색이 좋음
- 양방향 포인터가 있으면 backward 검색에 좋음
- clustered index 데이터가 인덱스를 기준으로 정렬되어 있다. 리프노드에 데이터가 정렬되어 있다. (vs 인덱스의 키값을 기준으로 데이터가 정렬되어 있지 않음 non clustered
- 클러스터드 인덱스가 있는 경우 테이블의 데이터는 따로 저장되지 않음. 클러스터드 인덱스가 곧 테이블 데이터임.
- 인덱스 페이지 + 데이터 페이지 레코드에 대한 포인터
- mysql inno db 왜 nonclustered인덱스에서는 실제 레코드 주소가 아닌 pk를 가리켜? mvcc때문에 구현상 더 유리
- b+는 높이는 최대 4~5
- key를 저장하지 않으면 내부적으로 key를 생성함.
- unique column중 젤 작은것
- row_id를 pk로 만들어야함
- row store: 데이터에 row가 저장됨
- column store: 집계함수 분석 쿼리 OLAP 할때는 oclumn store가 유리. red shift
- create database → folder, craat tabel → file ibd 클러스터드 인덱스, data page, secondary index
- 각 idb파일은 여러개의 페이지로 이루어져있음.
- rid 페이지 번호 + 슬롯번호. 레코드가 저장된 페이지의 번호.
- mysql은 가변길이 varcar
- 리프 노드에는 실제 데이터가 저장됩니다. 이는 B-트리와의 주요 차이점 중 하나입니다.
공통적으로 [@transactional](https://github.com/transactional)(readOnly = true) 를 사용하여 불필요한 더티 체킹이 일어나지 않게 막고(실수 방지) 필요한 메소드에는 따로 [@transactional](https://github.com/transactional)(readOnly = false) 를 사용해주는 방식으로 사용했었습니다.
github code review bot
jacoco → test coverage
- 용량이 큰 파일 전체를 메모리 또는 disk에 적재하지 않으면서 s3에 파일을 올리는 방법.
- 언제 사용하나?
- 멀티 파트 업로드는 160GB로 업로드
- 특정 파트가 실패한 경우 그 파트만 다시 업로드하면 됨
- 병렬로 여러 파트를 동시에 업로드할 수 있기 때문에 업로드 속도를 최적화할 수 있음.
- 과정
- initalizeMultipartUpload: uploadId 응답
- part uplload: 최소 5MB. 각 part에 대한 eTag를 응답
- completeMultipartUpload
- abort multipart upload: 업로드가 중단되거나 실패한 경우 업로드를 중단할 수 있음.
- eTag가 반환되는 이유: 파트의 해시값. etag를 이용해 업로드된 데이터가 손상되지 않았는지 확인.
- 의문
- 클라이언트에서 서버까지의 전송 속도는 일정한데 part upload를 한다고 속도가 빨라지나?
- trnasfer-encoding:chucnked에서 청크 단위는 고정되어 있지 않음.
- 이때 서버가 버퍼 크기를 5MB로 설정하더라도, 이는 서버가 데이터를 임시로 저장하는 크기일 뿐 클라이언트에서 보내는 청크 크기와는 직접적으로 연관이 없습니다.
- 맞습니다! 클라이언트가
Transfer-Encoding: chunked
를 사용하여 데이터를 전송할 때, 서버에서 청크가 모두 도착하기를 기다리지 않고, 도착하는 대로 버퍼를 사용해 즉시 S3에 업로드하는 것이 성능 면에서 훨씬 효율적입니다. 이렇게 하면 다음과 같은 이점이 있습니다:- 실시간 데이터 처리, 메모리 사용 효율성.
- 왜 청크단위 업로드는 직접 구현해야하는데 다운로드는 직접 구현하지 않아도 되는가
- Http Range 헤더를 사용해서 여러 파트로 나누어 병렬로 다운로드한 후 클라이언트 측에서 파트들을 결합하여 원본 파일을 복구할 수 있음.
- 스트리밍: 스트리밍 방식은 서버가 데이터를 순차적으로 전송하고, 클라이언트가 이를 실시간으로 수신하는 방식입니다. 스트리밍은 데이터를 부분적으로 전송하며, 파일 전체를 한 번에 다운로드할 필요가 없으므로 메모리 사용이 최적화됩니다.
-
Transfer-Encoding: chunked
: 이 방식은 HTTP/1.1에서 데이터를 스트리밍 방식으로 전송하는 하나의 방법입니다.Transfer-Encoding: chunked
는 데이터를 청크 단위로 분할하여 전송하며, 각 청크는 클라이언트가 처리할 수 있습니다. 서버는 데이터를 스트리밍 방식으로 전송하면서 각 청크의 크기를 포함하여 전송하고, 클라이언트는 청크 단위로 데이터를 받아 처리합니다. -
응답 메타데이터 접근:
ResponseInputStream
은 스트림 데이터를 읽는 것 외에도, 요청에 대한 응답 메타데이터에 접근할 수 있습니다. 예를 들어, S3에서 객체를 다운로드할 때GetObjectResponse
객체를 통해 콘텐츠 유형, 콘텐츠 길이 등의 메타데이터를 얻을 수 있습니다.
Transfer-Encoding: chunked
는 HTTP/1.1에서 사용하는 스트리밍 방식 중 하나입니다. 그러나, 이 방식 외에도 여러 가지 스트리밍 방식이 존재하며, 상황과 필요에 따라 사용됩니다.
-
HTTP Range 요청:
- 설명: HTTP 클라이언트가 특정 바이트 범위만을 요청할 수 있는 방법입니다. 예를 들어, 큰 파일을 여러 부분으로 나누어 병렬로 다운로드하거나, 특정 부분만을 다운로드할 때 사용됩니다.
- 사용 예시: 대용량 비디오 파일을 스트리밍할 때 사용되며, 비디오 플레이어가 사용자의 재생 위치에 따라 특정 범위의 데이터를 요청합니다.
-
헤더 예시:
Range: bytes=500-999
-
WebSocket:
- 설명: WebSocket은 HTTP를 업그레이드하여 양방향 통신을 가능하게 하는 프로토콜입니다. 서버와 클라이언트 간의 지속적인 데이터 스트림을 제공합니다.
- 사용 예시: 실시간 채팅 애플리케이션, 주식 거래 시스템 등에서 실시간으로 데이터를 주고받을 때 사용됩니다.
- 특징: 지속적인 연결을 유지하며, 양방향으로 데이터가 교환될 수 있습니다.
-
Server-Sent Events (SSE):
- 설명: 서버가 클라이언트로 지속적으로 데이터를 푸시할 수 있는 방식입니다. 주로 실시간 업데이트가 필요한 애플리케이션에서 사용됩니다.
- 사용 예시: 뉴스 피드, 실시간 대시보드 등에서 서버가 클라이언트에게 실시간으로 업데이트를 보내는 데 사용됩니다.
- 특징: 서버가 클라이언트로 텍스트 스트림을 지속적으로 전송할 수 있으며, 클라이언트는 이를 실시간으로 처리합니다.
-
HTTP/2 Server Push:
- 설명: HTTP/2의 기능으로, 서버가 클라이언트의 요청 없이도 리소스를 미리 전송할 수 있습니다.
- 사용 예시: 웹 페이지 로딩 시 서버가 클라이언트의 요청을 예상하고, 필요한 리소스를 미리 전송하여 성능을 최적화합니다.
- 특징: 클라이언트의 요청 전에 서버가 데이터를 푸시할 수 있어 웹 성능을 개선할 수 있습니다.
-
RTSP (Real-Time Streaming Protocol):
- 설명: 멀티미디어 스트리밍을 위한 네트워크 제어 프로토콜로, 주로 실시간 비디오 스트리밍에서 사용됩니다.
- 사용 예시: IP 카메라, 비디오 스트리밍 서비스에서 실시간 스트리밍을 제어하는 데 사용됩니다.
- 특징: 실시간 미디어 스트리밍을 위해 설계된 프로토콜로, 클라이언트가 스트림을 시작, 중지, 제어할 수 있습니다.
네, S3에 데이터를 스트리밍 방식으로 업로드할 때 사용할 수 있는 객체가 있습니다. AWS SDK for Java에서는 RequestBody.fromInputStream
메서드를 사용하여 스트리밍 방식으로 데이터를 S3에 업로드할 수 있습니다. 이 방법을 사용하면 파일의 전체 내용을 메모리에 로드하지 않고, 스트림을 통해 데이터를 S3로 직접 전송할 수 있습니다.
-
대용량 파일 업로드 제약: AWS S3는 단일
PUT
요청으로 업로드할 수 있는 최대 파일 크기가 5GB로 제한됩니다. 이보다 큰 파일을 업로드할 때는 이 방식으로는 불가능합니다. - 오류 처리: 업로드 도중에 네트워크 오류가 발생하면, 전체 업로드를 다시 시도해야 합니다. 부분적으로 업로드된 데이터를 복구할 수 있는 방법이 없습니다.
단일 스레드가 InputStream
에서 데이터를 읽고, 동시에 그 데이터를 OutputStream
을 통해 S3에 업로드하는 작업을 수행한다면, 스레드가 동기적으로(순차적으로) 작업을 수행하기 때문에 한 번에 하나의 작업만 처리할 수 있습니다. 이 의미는, 스레드가 InputStream
에서 데이터를 읽는 동안에는 OutputStream
으로 데이터를 쓸 수 없고, 반대로 OutputStream
에 데이터를 쓰는 동안에는 InputStream
에서 데이터를 읽을 수 없다는 것입니다
이 문제를 해결하기 위해서는 비동기 I/O, 멀티스레딩, 또는 다른 형태의 비동기 작업 처리 기법을 사용해야 합니다.
- 한 스레드가
InputStream
에서 데이터를 읽고, 이를 버퍼에 저장하는 역할을 하고, - 다른 스레드가 그 버퍼에서 데이터를 읽어
OutputStream
으로 데이터를 전송하는 방식으로 구현할 수 있습니다.
이렇게 하면, 두 작업이 병렬로 진행될 수 있으며, 데이터 읽기와 쓰기가 서로 블로킹하지 않게 됩니다.
- 병렬 처리로 인해 CPU를 보다 효율적으로 사용할 수 있습니다.
- 작업이 동시에 수행되므로 전체 작업 시간이 단축될 수 있습니다.
- 스레드 간의 동기화 문제가 발생할 수 있습니다
- 스레드 간의 동기화 문제가 발생할 수 있습니다. 공유 자원에 접근할 때는 동기화 처리가 필요하며, 이는 복잡성과 성능 저하를 초래할 수 있습니다.
- 많은 스레드를 생성하면 메모리 소비가 증가하고, 컨텍스트 스위칭 오버헤드가 발생할 수 있습니다.
- 자바에서는
NIO
(Non-blocking I/O) 라이브러리를 사용하여 비동기적으로 I/O 작업을 수행할 수 있습니다. - 비동기 I/O를 사용하면, 하나의 스레드가 비동기 작업을 등록하고, 그 작업이 완료될 때까지 다른 작업을 수행할 수 있어 효율적인 I/O 처리가 가능합니다.
- Reactor 또는 RxJava와 같은 라이브러리를 사용하여 비동기 스트림 처리 방식을 구현할 수 있습니다.
- 이 방식에서는 데이터가 스트림으로 흘러가며, 각 단계에서 비동기적으로 처리됩니다. 이를 통해
InputStream
과OutputStream
간의 효율적인 데이터 전송이 가능해집니다. - 비동기 I/O 방식은 특히 대규모 네트워크 애플리케이션에서 자원을 효율적으로 사용할 수 있는 장점이 있으며, 자바에서는 NIO 및 비동기 AWS SDK를 활용하여 구현할 수 있습니다.
스프링 부트에서 Netty를 사용하면 AsynchronousSocketChannel
과 같은 저수준 비동기 소켓 채널을 직접 다룰 필요 없이, 훨씬 더 간단하고 효율적으로 비동기 서버를 구현할 수 있습니다. Netty는 비동기 이벤트 기반 네트워크 애플리케이션 프레임워크로, 고성능 비동기 서버를 쉽게 만들 수 있게 도와줍니다.
서블릿 자체가 inputstream자체가 블로킹 방식이기 때문에
InputStream
으로 데이터를 받고, 그 데이터를 AsyncRequestBody
를 사용하여 S3에 비동기적으로 전송하는 방식은 매우 효율적이고 실용적인 방법입니다. 이
InputStream
과 OutputStream
을 각각 다른 스레드에서 처리하는 멀티스레드 방식과, 비동기 I/O를 사용하여 AsyncRequestBody
로 S3에 비동기적으로 데이터를 전송하는 방식에는 중요한 차이점이 있습니다
EXPLAIN
명령어는 MySQL 쿼리의 실행 계획을 분석할 때 사용됩니다. 쿼리 성능을 평가할 때 중요한 지표들은 다음과 같습니다:
-
type
컬럼은 쿼리에서 사용하는 조인 유형을 나타냅니다. 이 값은 쿼리 성능에 중요한 영향을 미칩니다. 일반적으로 다음 순서로 성능이 좋다고 판단할 수 있습니다:-
system
>const
>eq_ref
>ref
>range
>index
>ALL
-
-
ALL
은 테이블 전체를 스캔하는 것으로 성능이 가장 낮습니다. 가능한 경우,range
이상의 조인 유형을 사용하는 것이 좋습니다.
-
possible_keys
는 MySQL이 쿼리 실행에 사용할 수 있는 인덱스를 나타냅니다. 가능한 인덱스가 많을수록 쿼리 성능을 개선할 수 있는 여지가 있습니다.
-
key
는 실제로 쿼리에서 사용된 인덱스를 나타냅니다. 쿼리가 올바르게 인덱스를 사용하고 있는지 확인할 수 있습니다.
-
rows
는 MySQL이 검색해야 할 행의 수를 나타냅니다. 이 값이 작을수록 쿼리 성능이 좋을 가능성이 높습니다. 인덱스를 적절히 사용하면 이 값이 줄어들게 됩니다.
-
filtered
는 MySQL이 필터링 후 남은 데이터의 비율(%)을 나타냅니다. 이 값이 100에 가까울수록 더 많은 행이 조건을 충족시키고, 더 많은 데이터가 결과에 포함될 수 있음을 의미합니다.
-
Extra
컬럼에서Using index
,Using where
,Using temporary
,Using filesort
등의 정보가 표시됩니다. 이 중에서 성능 저하를 유발할 수 있는 요소(예:Using temporary
,Using filesort
)를 주의 깊게 살펴봐야 합니다.
이 단계는 인덱스에서 행을 필터링한 후 또는 테이블에서 직접 행을 읽어야 할 때 계산됩니다.
즉, filtered
는 rows
값에서 조건을 적용한 후 몇 퍼센트의 행이 쿼리 결과로 남는지를 나타냅니다.
-
높을수록 좋음:
-
filtered
값이 높다는 것은 MySQL이 필터링을 통해 많은 행을 제거하지 않았음을 의미하며, 쿼리 조건이 많은 행을 포함하고 있음을 나타냅니다. -
filtered
값이 100%에 가까울수록 더 많은 행이 결과에 포함될 수 있음을 의미합니다.
-
-
작을수록 좋음:
-
filtered
값이 낮다는 것은 MySQL이 필터링을 통해 많은 행을 제거했음을 의미합니다. 즉, 조건이 잘 작동하여 불필요한 데이터를 많이 걸러냈다는 뜻입니다. - 하지만 이 값이 낮다는 것은 최종적으로 쿼리에 필요한 행이 적다는 의미이기도 합니다. 따라서, 이 값이 항상 작을수록 좋은 것은 아니지만, 인덱스가 잘 작동하고 있음을 나타낼 수 있습니다.
-
- 좀 확장성이 없는 방법이지 않나?
- 정렬 조건이 추가될때마다 인덱스도 추가해주고 where 조건 , 정렬 조건 둘다 필요하고, requets parameter도 추가해야함.
explain select *
from
folder_metadata_test fm1_0
where
fm1_0.parent_folder_id= 6089 AND
fm1_0.folder_metadata_id > 0
order by
fm1_0.created_at desc,
fm1_0.folder_metadata_id
limit
5;
최신순 정렬을 위해서 index를 추가하고자 해. 최신순 정렬은 날짜순으로 날짜가 같다면 Id를 기준으로 정렬하고 싶어. created_at필드에만 인덱스를 거는게 좋을까? created_at, id 복합 인덱스를 거는게 좋을까? 복합인덱스를 걸지 않아 id는 Primary key 인덱스가 걸려있으니깐 비슷하게 동작하지 않아?
최신순 정렬(ORDER BY created_at DESC, id DESC
)을 자주 수행하고, created_at
필드가 동일한 레코드가 많은 경우, created_at
과 id
필드를 포함하는 복합 인덱스를 사용하는 것이 성능 최적화에 더 유리합니다.
- 쿼리는 하나밖에 못탐. where 절에서 필터링할때 인덱스를 쓰든 order by에서 정렬할때 조건을 쓰든 둘 중에 하나 선택필요
스레드별 로컬 메모리 = 스택 메모리 = cpu 캐시 메모리야?
- lock in share mode pess
- select for update pessimistic write
- Read only는 트랜잭션을 가지지 않고 1차 캐시만 가진다.
- lock을 걸 수 없다