아카이브는 오라클 디비의 데이터가 저장되는 스토리지의 물리적인 디스크가 깨지거나 고장 나는 문제가 발생했을 때 디비 전체 백업이 되어있다면 내가 원하는 시간으로 복구할 수 있는 걸 의미한다. 중요 시스템이라면 아카이브 로그가 꼭 필요하다. 리두 로그가 스위치 할 때 아카이브 로그 모드로 운영한다면 아카이브 로그가 파일 형태로 만들어지고 저장된다.
ARCHIVE Mode
ARCHIVELOG Mode
: B2C, OLTP
NOARCHIVELOG Mode
: B2B, OLAP, DW, 개발 DB
아카이브 로그 모드는 대용량의 빠른 스토리지가 필요하고 추가로 백업 서버가 있어야 운영하기 수월하다.
ARCHIVE 관리
1일
2일
3일
4일
5일
6일
7일
23G
21G
15G
19G
80G
100G
10G
평균 약 38G여서 50G로 만들어야지라고 생각하면 큰일난다. 최대 크기의 20 ~ 30%로 설정한다. 약 120G ~ 130G
아카이브 로그 모드에서 생성된 아카이브 로그 파일은 하루가 지나면 백업 서버를 사용해서 아카이브 로그 백업 관리를 하는 게 가장 좋다.
ARCHIVELOG 파라미터
log_archive_dest는 아카이브 로그 파일이 저장되는 위치이고
log_archive_format는 아카이브 로그 파일의 이름을 설정할 수 있다.
fast_start_mttr_target는 데이터베이스의 인스턴스를 복구할 때 소유되는 시간을 제어할 수 있다. Default 값은 0이고 보통 300초로 설정한다.
파라미터 파일(spfile) 백업
cd /opt/oracle/oradata/dbconfig/XE
cp spfileXE.ora spfileXE.ora_20221118
-- 예전에 만들 백업 파라미터는 backup_parameter 폴더(디렉터리)로 이동
mv spfileXE.ora_20221118 backup_parameter
spfileXE.ora 파일은 중요한 파일이므로 작업 전에 꼭 백업을 해놓고 작업을 해야한다.
Archivelog 디렉터리 생성
cd /
mkdir /ARC_STR
chown oracle.dba /ARC_STR
Archive Log 파라미터 변경
ALTER SYSTEM SET log_archive_dest='/ARC_STR' SCOPE=SPFILE;
ALTER SYSTEM SET log_archive_format='ARCH_STR_%r_%t_%s.ARC' SCOPE=SPFILE;
ALTER SYSTEM SET FAST_START_MTTR_TARGET=300 SCOPE=SPFILE;
/ARC_STR 경로에 ARCH_STR_%r_%t_%s.ARC으로 파일을 저장한다. 또한 FAST_START_MTTR_TARGET 300으로 변경한다.
Archivelog Mode 변경
-- 현재 Archivelog Mode 확인
archive log list;
shutdown immediate;
startup mount;
select status from v$instance;
alter database archivelog;
alter database open;
select status from v$instance;
archive log list;
show parameter log_archive_dest
show parameter log_archive_format
show parameter FAST_START_MTTR_TARGET
-- Redo(리두) 스위치로 아카이브 로그 파일 생성
alter system switch logfile;
alter system switch logfile;
alter system switch logfile;
alter system switch logfile;
alter system switch logfile;
alter system switch logfile;
alter system switch logfile;
Linux Shell을 이용한 Archivelog File 자동 삭제 스크립트
cd /home/oracle
mkdir /home/oracle/oraclelog
cd /home/oracle/scripts
vi rman_del_arch.sh
#!/bin/bash
export ORACLE_HOME=/oracle/app/oracle/product/19.0.0.0/db_1
export ORACLE_SID=STR
export PATH=$ORACLE_HOME/bin:$PATH
export DATE=`date +%Y%m%d`
export HOSTNAME=`hostname`
rman log=/home/oracle/oraclelog/${HOSTNAME}_del_arch_${DATE}.log << EOF
connect target /
DELETE noprompt ARCHIVELOG ALL COMPLETED BEFORE 'sysdate -1';
CROSSCHECK ARCHIVELOG ALL;
DELETE EXPIRED ARCHIVELOG ALL;
exit
EOF
-- shell 스크립트 권한 변경
chmod 755 rman_del_arch.sh
--crontab 추가
crontab -e
00 05 * * * /home/oracle/scripts/rman_del_arch.sh
crontab에 등록한다. 매일매일 새벽 5시가 되면 저 Shell이 실행된다.
NOArchivelog Mode 변경
shutdown immediate;
startup mount;
select status from v$instance;
alter database noarchivelog;
alter database open;
select status from v$instance;
리두는 DB에 변경되는 모든 이력을 저장, 서버가 다운되었을 때 복구할 수 있는 파일, DB 복구, 인스턴스 복구, Fast Commit(데이터 블록을 기록하는 방식이 랜덤이 아니라 어펜드 방식을 사용하기 때문에 빠르게 커밋한다.)
REDO 파일 위치
/oracle/app/oracle/oradata/DB이름
내 로컬 경로
/opt/oracle/oradata/XE
REDO 관리
리두 로그 파일은 최소 두개가 필요하고 오라클 기본 설치하면 3개의 리두 로그 파일이 생성된다. 리두 로그 파일은 redo01.log, redo02.log, redo03.log 스위치 하면서 재사용한다. 이걸 Round-Robin 방식이라고 한다. 디비 백그라운드 프로세스인 LGWR가 리두에 저장되는 시점은 대표적으로 4가지가 있다.
3초에 한번
리두 로그 버퍼의 3분의 1이 되었거나 1MB가 넘었을 때
사용자에 의한 커밋 또는 롤백을 수행했을 때
DBWR, LGWR에게 쓰기를 요청했을 때
디비의 트렌젝션이 많고 데이터 변경량도 많을 때 리두 로그 사이즈가 너무 작으면 CPU 사용량이 증가하고 DBWR, ARCn의 사용량이 증가한다. 리두 로그 사이즈를 적절하게 적용하면 CPU 사용량이 감소하고 디스크 I/O 향상된다. 오라클이 권고하는 리두 로그의 스위치는 20분에 한번 정도 발생하는 것이고 업계에서는 보통 10분에서 20분 사이에 한번 발생하는 것으로 추천한다.
REDO 추가
ALTER DATABASE ADD LOGFILE GROUP 4
(
'/파일위치/REDO4_1.LOG',
'/파일위치/REDO4_1.LOG'
) SIZE XXMB;
같은 그룹의 여러개의 리두 로그를 만든다면 물리적으로 서로 다른 디스크에 저장하는 것을 권고한다. 그래야 한쪽 디스크에 문제가 생겨도 안전하게 복원할 수 있다. 사이즈는 디비를 분석해서 규모가 있다면 1GB나 그 이상 10분에서 20분 사이에 발생하는지를 판단해서 사이즈 조정을 계속한다.
REDO 삭제
ALTER DATABASE DROP LOGFILE GROUP 1;
REDO 상태 확인
SELECT
A.GROUP#
, A.STATUS
, B.MEMBER
, A.BYTES/1024/1024 AS "SIZE(MB)"
FROM
V$LOG A
, V$LOGFILE B
WHERE A.GROUP# = B.GROUP#
ORDER BY 1;
리두 로그 그룹 삭제할 때 제일 중요한건 리두 로그 상태가 INACTIVE 상태 이어야 한다. 그냥 삭제하면 큰일 난다.
REDO LOG 변경 정보 전체 조회
SELECT
TO_CHAR(FIRST_TIME,'YYYY/MM/DD HH24'),
COUNT(*)
FROM V$LOG_HISTORY
GROUP BY TO_CHAR(FIRST_TIME,'YYYY/MM/DD HH24')
ORDER BY 1;
REDO LOG 변경 정보 시간, 분 단위로 분석
-- 2022/11/13 ~ 2022/11/18 기간 조회
-- 추가로 일자를 변경하려면 TO_DATE(년월일)을 바꿔주세요.
-- 예) 2022년 7월 20일 부터 2022년 7월 21일 조회
-- WHERE FIRST_TIME >= TO_DATE('20221113')
-- AND FIRST_TIME < TO_DATE('20221118')
SELECT
TO_CHAR(FIRST_TIME,'HH24:MI'),
COUNT(*)
FROM V$LOG_HISTORY
WHERE FIRST_TIME >= TO_DATE('20221113')
AND FIRST_TIME < TO_DATE('20221118')
GROUP BY TO_CHAR(FIRST_TIME,'HH24:MI')
ORDER BY 1;
최근 10일 동안 REDO LOG 변경 정보 조회
SELECT
DECODE(TO_CHAR(FIRST_TIME,'HH24'),NULL,'TOTAL',TO_CHAR(FIRST_TIME,'HH24')) AS "REDO_TIME",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE)-10 AND FIRST_TIME < TRUNC(SYSDATE) -9 THEN 1 ELSE 0 END) AS "TODAY-10",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -9 AND FIRST_TIME < TRUNC(SYSDATE) -8 THEN 1 ELSE 0 END) AS "TODAY-9",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -8 AND FIRST_TIME < TRUNC(SYSDATE) -7 THEN 1 ELSE 0 END) AS "TODAY-8",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -7 AND FIRST_TIME < TRUNC(SYSDATE) -6 THEN 1 ELSE 0 END) AS "TODAY-7",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -6 AND FIRST_TIME < TRUNC(SYSDATE) -5 THEN 1 ELSE 0 END) AS "TODAY-6",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -5 AND FIRST_TIME < TRUNC(SYSDATE) -4 THEN 1 ELSE 0 END) AS "TODAY-5",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -4 AND FIRST_TIME < TRUNC(SYSDATE) -3 THEN 1 ELSE 0 END) AS "TODAY-4",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -3 AND FIRST_TIME < TRUNC(SYSDATE) -2 THEN 1 ELSE 0 END) AS "TODAY-3",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -2 AND FIRST_TIME < TRUNC(SYSDATE) -1 THEN 1 ELSE 0 END) AS "TODAY-2",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) -1 AND FIRST_TIME < TRUNC(SYSDATE) -0 THEN 1 ELSE 0 END) AS "TODAY-1",
SUM(CASE WHEN FIRST_TIME >= TRUNC(SYSDATE) AND FIRST_TIME < TRUNC(SYSDATE) +1 THEN 1 ELSE 0 END) AS "TODAY",
SUM(DECODE(TO_CHAR(FIRST_TIME,'HH24'),NULL,0,1)) AS "SUM"
FROM V$LOG_HISTORY
WHERE FIRST_TIME >= TRUNC(SYSDATE) - 10
GROUP BY ROLLUP(TO_CHAR(FIRST_TIME,'HH24'))
ORDER BY 1;
디스크 사용량을 보는 명령어
df -h
REDO 그룹 4 추가
ALTER DATABASE
ADD LOGFILE GROUP 4
(
'/opt/oracle/oradata/XE/REDO4_1.LOG',
'/opt/oracle/oradata/XE/REDO4_2.LOG'
) SIZE 500M;
리두 로그를 만들 때 소문자가 아닌 대문자로 만드는 이유는 SqlPlus에서 drop으로 삭제해도 실제 파일은 남아있기 때문에 소문자 대문자를 구분해서 혹시 모를 사고를 방지하기 위해서이다.
초기 설정된 REDO 로그 삭제
-- (REDO 로그 스위치하면서 INACTIVE 상태에서 DROP 명령어 실행)
alter system switch logfile
ALTER DATABASE DROP LOGFILE GROUP 1;
ALTER DATABASE DROP LOGFILE GROUP 2;
ALTER DATABASE DROP LOGFILE GROUP 3;
REDO 로그 파일 삭제
/***************************************************************************
REDO 로그 파일 삭제
초기 설정된 REDO 로그 그룹 1,2,3 삭제
리눅스에서 작업한다. (oracle 계정 확인)
***************************************************************************/
cd /opt/oracle/oradata/DB이름
cd $ORACLE_BASE/oradata/DB이름
rm /opt/oracle/oradata/XE/redo01.log
rm /opt/oracle/oradata/XE/redo02.log
rm /opt/oracle/oradata/XE/redo03.log
데이터베이스를 사용하지 않는 애플리케이션의 프로그래밍은 개개의 프로세스가 자신이 가진 변수(데이터)를 처리하는 것이 일반적이다. 같은 프로그램이 여러 개 실행되었다 하더라도 실제로 변수는 개개의 프로그램마다 가지고 있으므로 신경 쓸 필요가 없다.
하지만 데이터베이스에서는 다르다. 여러 프로세스나 사용자가 하나의 데이터베이스(데이터 집합)에 접근하기 때문이다. 기본적으로 데이터베이스에서는 데이터를 중복으로 보관하지 않으며 보관해서도 안 된다. 기본적으로 여러 사용자나 프로그램이 데이터베이스의 데이터를 공유한다.라고 생각하는 것이 중요하다.
프로세스와 스레드란?
프로세스는 실행 상태에 있는 프로그램을 의미한다. 실행 상태이기 때문에 메모리나 자원을 가지고 있다. 다르게 표현하면 실체화했다고 할 수 있다.
프로세스를 다르게 비유하면 프로그램이라는 거리에서 일하는 사람과 같다. 유닉스 위에서 프로그램이 여러 개 실행되어있어도 각각 다른 사람이므로 CPU가 여러개 있다면 동시에 처리한다.
이에 비에 스레드는 프로세스 내에 존재하는 실행 단위이다. 하나의 프로세스 안에서 처리를 병렬로 하고 싶을 때 사용한다. 어느 쪽이든 병렬로 처리하기 위한 구조이지만 가장 다른 점은 부하의 크기와 메모리 공유 여부이다. 스레드는 부하가 적지만 메모리를 스레드끼리 공유하기 때문에 주의해서 사용해야 한다.
오라클이 여러 개의 프로세스로 구성된 이유
그 이유 중 하나는 다중 처리를 하고 싶기 때문이다. SQL 처리는 길게는 몇 시간 넘게 걸리는 경우도 있다. 그동안 다른 사용자를 기다리게 할 수는 없다. 또한 디스크는 메모리 액세스에 비해서 속도가 매우 느리기 때문이다.(메모리에서는 나노초 단위, 디스크는 밀리초 단위) 속도가 느린 I/O가 반복되고 있는 동안 CPU와 같은 자원을 쉬게 하는 것은 아깝기 때문에 가능하다면 다른 SQL 처리를 해야 한다.
오라클에서 여러 개의 프로세스를 실행하는 것으로 병렬 처리를 한다. 단 오라클은 같은 프로세스가 여러 개 작동하는 것은 아니다.
서버 프로세스와 백그라운드 프로세스의 역할
백그라운드 프로세스의 일
오라클은 서버 프로세스라고 불리는 SQL문을 처리하는 프로세스와 백그라운드 프로세스라고 불리는 주로 서버 프로세스를 도와주는 프로세스가 있다. 고객에 해당하는 오라클 클라이언트와 오라클 클라이언트의 요청(SQL)을 처리하는 서버 프로세스가 존재한다. 오라클 클라이언트는 서버 프로세스와 통신한다.
각 프로세스가 수행하는 처리
DB에서 수행하는 주요 처리
SQL문 수신
SQL문 파싱(어떤 테이블에 접근해야 하는지를 생각하는 처리)
데이터 읽기(디스크에서 읽어온다.)
데이터 기록(디스크에 기록한다.)
SQL 문의 결과 회신
로그 기록(데이터 갱신 로그를 디스크에 기록)
각종 정리(프로세스의 비정상 종료로 인한 아무도 사용하지 않는 락의 해제 등)
로그 보관(아카이브)
서버 프로세스는 서비스를 수행하는 프로세스이다. 오라클은 SQL문을 빠르게 처리하지 않으면 안 되므로 고객을 최우선으로 생각하는 영업 담당자와 거의 같은 업무를 한다.
SQL문의 처리에 필요한 작업
1, 2, 3, 5번이다.
SQL문을 수신하지 않으면 처리는 진행되지 않으며 파싱이라고 불리는 작업을 하지 않으면 어떤 테이블에 접근해야 하는지 조차 알 수 없다. 마지막에 데이터나 결과를 오라클 클라이언트에게 전달하지 않으면 종료할 수도 없다.
SQL 문의 결과를 회신하기 위해서 불필요한 처리는 다른 프로세스에게 맡겨 버리면 된다.
디스크에 데이터를 기록하는 일은 DBWR(데이터베이스 라이터)이 수행한다. 서버 프로세스는 디스크에서 데이터를 읽어오기는 하지만 기록은 하지 않는 이유가 그런 부분에 있다. 원칙적으로 각 프로세스의 역할 분담을 나누어 보면 SQL의 결과를 회신하는 데 필요한 것은 서버 프로세스가 수행하고, 그 이외의 것은 백그라운드 프로세서가 수행한다는 것을 알 수 있다.
Tips.
출처
그림으로 공부하는 오라클 구조
문제가 될 시 ks12b9189@naver.com으로 메일 주시면 게시글 바로 삭제하겠습니다.
오라클은 디스크에서 데이터를 읽어 오고 처리를 한 후 다시 디스크에 집어넣습니다. 즉 오라클이 다루는 데이터는 디스크에서 꺼내 오고 디스크로 돌아가는 것입니다. 그래서 오라클과 디스크는 떼려야 땔 수 없는 관계이다.
디스크 동작 방법
디스크를 LP판이라고 생각하고 디스크를 떠올리자
디스크는 계속 회전하고 그 위를 헤드가 움직여서 데이터를 읽거나 기록한다.
디스크는 1분에 1만 회정도 회전하고 있다. 엑추에이터는 초당 100회 정도 움직일 수 있다.
디스크의 동작
I/O 처리에 필요한 디스크의 동작
데이터를 읽기 또는 기록하기 위해서는 원하는 트랙을 찾지 않으면 안 된다. 이를 디스크 용어로 시크(Seek)라고 한다. 그 후에 원하는 정보를 읽어 낼 수 있는 위치가 회전해서 다가올 때까지 기다리고 있다. 기다리는 시간을 회전을 기다리는 시간이라고 부른다. 그리고서야 데이터를 읽고 쓴다. 디스크의 I/O는 DBMS에서 필요하지만 가능한 줄여야 하는 부분이다.
어떻게 I/O의 대기 시간을 줄일까?
시퀀셜 액세스에 관한 설명이 필요하다. 시퀀셜은 순서를 따라서라는 의미의 순차를 의미한다.
시작점부터 마지막까지 중간 부분을 빠뜨리지 않고 전부 엑세스(읽기/ 쓰기)하는 것이다.
풀 스캔(테이블의 모든 데이터를 읽어 오는 것) 할 때 메모리에 데이터가 없다면 시퀀셜 액세스가 발생한다.
테이블의 크기가 있고 시퀀셜 엑세스로 모든 데이터를 가지고 오려고 할 때 시간이 오래 걸려 인덱스라는 발생이 나오게 되었다.
책에서 무언가를 찾을 때 우리는 책의 모든 내용을 보지 않는다. 대부분 인덱스를 사용한다. 이미 알고 있듯이 인덱스에는 키워드가 순서대로 나열되어 있고 해당 페이지 번호도 쓰여있다. 데이터베이스의 인덱스도 마찬가지이다. 데이터베이스의 인덱스에는 색인할 때 사용하는 키값(SQL의 where절에 적는 조건 값)과 그 키가 존재하고 있는 위치가 기록되어있다.
인덱스 사용의 예
예를 들어 인덱스에서 해당 내용에 대한 정보를 확인 후 해당 페이지를 펼쳐 원하는 정보를 얻는다. SQL문도 마찬가지로 WHERE절에 찾고 싶은 정보를 기술하고 SELECT 뒤에 알고 싶은 항목을 적는다.
SELECT "소속회사" FROM "개인 데이터" WHERE "이름" = "현성";
이 SQL문을 인덱스를 사용해서 처리할 때는 우선 이름이 실려있는 인덱스를 조사한다. 그 결과로 어드레스(ROWID)를 얻을 수 있고, 그 어드레스로 데이터를 읽어 온다. 읽어 온 데이터에 현성이라는 모든 데이터가 있으므로 그 안에서 소속회사의 데이터를 사용자에게 반환한다.
Q&A
Q. 만약 인덱스 자체의 크기가 커지면 크기가 큰 테이블을 조회하는 것과 마찬가지로 처리하는 데 필요한 시간이 늘어날까?
A. 그런 일은 발생하지 않는다. 왜냐하면 오라클은 인덱스를 인덱스의 인덱스를 붙이는 것과 같은 형태를 여러 단계로 구성하기 때문이다.
여러 단계로 구성된 구조를 트리구조라고 부른다. 장점은 인덱스의 필요 없는 부분은 읽지 않고 끝난다는 것이다.
랜덤 인덱스
인덱스를 사용했을 때는 필요한 부분만 읽어 오면 충분하지만 필요한 부분이 디스크 위에 연속적으로 있는 경우는 거의 없습니다. 따라서 헤드를 띄엄띄엄 접근하게 됩니다. 이렇게 접근하는 방식을 랜덤 액세스라고 한다. 시퀀셜 액세스와 반대의 의미를 가진다. 랜덤 액세스를 디스크 시점에서 생각해보면 비효율적이다. 오라클 블록 크기를 4KB라고 가정하면 시크와 회전 시간을 기다리는 것에 시간을 소비하기 때문에 1초 동안 약 400KB(100회의 시크 X 1회 I/O의 크기 4KB) 밖에 읽어 오지 않는다. 실제로 데이터 전송의 효율에서 바라보면 DBMS의 I/O도 같을 수밖에 없다. 시크를 반복하기 때문에 DBMS에서 사용하는 디스크의 지표는 IOPS(초당 수행 가능한 I/O 횟수)가 중요하다고 볼 수 있다. 대부분 디스크는 IOPS가 100회에서 200회 정도이다. 따라서 한 개나 두 개의 디스크를 사용해서 데이터베이스를 만들어 버리면 부하가 집중적으로 발생했을 때 시크가 요청을 따라잡을 수 없으므로 디스크에 병목 현상이 발생하게 된다.
Tip.
Q. 접근하려는 데이터가 전체 데이터의 15% 미만일 경우에만 인덱스 액세스가 유리하다고 하는 이유는 무엇일까?
A. 시퀀셜 엑세스와 랜덤 액세스의 특징 때문이다. 테이블의 데이터가 대량이고 그중 한 개의 행을 꺼내야 한다면 당연히 인덱스 액세스가 빠르게 찾아낼 수 있다. 그에 비해 모든 데이터를 보려고 할 때 매번 인덱스를 찾은 후에 데이터를 찾아가면 오히려 속도가 느려지게 된다.
Q. 만약 데이터가 50%라면? 데이터가 25%라면?
A. 디스크에서의 랜덤 액세스는 데이터를 읽어 오는 효율성이 시퀀셜 액세스보다 떨어진다라는 특성이 중요하다. 예를 들어 테이블에 2만 건의 데이터가 저장되어 있다고 하고 그중 절반인 1만 건을 꺼낸다고 가정할 때 여기서 한 행은 4KB라고 하겠다. 이때 지금까지 사용해 왔던 디스크 성능 값을 사용해 계산해 보면 랜덤 액세스로 100초가 걸린다. 2만 건의 전부를 읽어오는 시퀀셜 액세스로 모든 것을 읽어 온다고 해도 약 4초면 끝난다. 즉 모든 데이터가 아니더라도 일정 크기 이상의 데이터를 읽는다면 디스크 특성상 시퀀셜 액세스를 사용해서 테이블을 풀 스캔 하는 것이 더 빠르다는 것을 알 수 있다.
단, 실제로 캐시에 데이터가 보관되어있는 경우도 있고 한 개의 블록에 여러 행의 데이터가 보관되어있는 경우도 있어서 1회의 I/O로 많은 데이터를 읽어오는 경우도 있다. 그래서 단순히 임계치가 15%라고 말할 수는 없다.
데이터를 보증하기 위한 디스크
오라클의 프로세스가 비정상적 종료되어도 데이터는 무사하다. DBMS는 어떠한 장애에도 견뎌야 한다. 어떻게 커밋한 데이터를 지킬 수 있을까? 데이터를 변경한 후에 커밋이라고 입력하면 오라클은 데이터를 디스크에 기록한다.
그러면 속도가 느리지 않을까? 속도를 빠르게 하기 위한 장치가 있다. DBMS들은 고속화를 위한 장치를 가지고 있어서 그 구조가 복잡한 것이다.
시퀀셜은 어떤 의미인가?
랜덤 엑세스는 db file sequential read라고 표시되고 시퀀셜 액세스는 db file scattered read라고 표시된다.
db file scattered read는 시퀀셜 하게 읽어 오는 것이므로 여러 개의 블록을 읽어온다. 오라클은 데이터 블록 단위로 메모리에 배치한다. 즉 여러 개의 블록이 연속하지 않은 곳에 놓인다. 따라서 scattered라고 표시한다. 그에 반해 db file sequential read는 단일 블록을 읽어 오므로 읽어 온 데이터는 당연히 메모리에서 연속된다. 오라클 메모리에서 연속되어 있으므로 sequential로 표시된다.
출처
그림으로 공부하는 오라클 구조
문제가 될시 ks12b9189@naver.com으로 메일 주시면 게시글 바로 삭제하겠습니다.
ListView는 가로(수평적)로 사용했을 때, 부모의 높이를 전부 사용한다. 근데, 부모가 Column위젯일 경우 에러가 나는데, 그 이유는 Column은 계속해서 수직으로 확장하려고 한다. 따라서 Column이 높이가 정해져 있지 않으니, 본인이 정해져 있는 높이가 없어서 그렇다.
Wrap 위젯
Column 위젯의 단점은, 수직으로 계속 넣기 때문인데, 최대 넓이에 도달함과 관계없이 커지기 때문에, 방지턱(오버플로우)가 일어난다. 그렇다면 최대 넓이가 도달하면 자동으로 다음 줄로 넘기고 싶을 때 사용한다.
Wrap위젯은 children 안에 자식들을 넣어주면 된다. spacing 속성값을 통해 자식들 사이 간격을 정한다. runSpacing 속성 값을 통해 줄단위 사이 간격을 정한다.
제가 디버거 모드를 이용하면서 Step Into, Resume 등 헷갈리는 기능도 있고 디버거를 잘 활용하고 싶어 작성하게 되었습니다. 또한 디버거 이용하는 방법에 잘 모르는 개발자 분들을 위해 작성하게 되었습니다.
1. Break Point
47번 줄, 48번 줄을 보면 빨간색 점이 찍혀있는 것을 볼 수 있다. 이는 Break point로 디버거 모드로 실행시켰을 때 코드 실행이 멈추는 지점
2. 디버거 모드로 실행시키는 방법
프로젝트 화면 상단에 실행시키는 버튼 옆에 벌레같이 생긴 버튼을 누르면 디버거 모드로 실행된다.
3. Step Over
Step Over는 코드 다음 줄로 넘어간다. method call 안으로 들어가지 않는다.
4. Step Into
지금 대기하고 있는 method call 내부로 들어간다.
Step Into를 누르면 아래의 화면과 같이 색이 나오는데 이는 색이 나오는 곳 중 어디로 들어갈 것인지 나타내는 색이다.
어떤 method call 안으로 들어갈 것인지 선택하면 된다.
5. Step Out
Step Into의 반대 개념으로 들어간 스택에서 나오는 기능이다. 근데 나오는데 그 스택을 실행을 시키고 빠져나온다.
6. Drop Frame
Step Out과 유사한 기능으로 그 스택을 실행시키지 않고 빠져나온다.
7. Resume Program
다음 Break Point로 이동한다.
8. Run to Cursor
Break Point를 찍고 Resume 버튼으로 다음 Break Point로 이동을 하였는데 Run to Cursor를 이용하면 Break Point를 찍지 않고 커서를 이용해 커서를 클릭하는 위치로 이동한다.
9. Condition
예를 들어 Step Into를 사용하면 for문이 50번까지 돌 때 49번째의 값을 확인하고 싶으면 Step Into를 49번 실행시켜야 한다. 하지만 Break Point에 우클릭을 해서 Condition에 확인하고 싶은 값의 Boolean 형태로 값을 넣는다. 그러면 해당하는 for문의 값만 실행시킬 수 있다.
10. Evaluate Expression
멈춘 지점에서 이런저런 값을 계산해 볼 수 있다.
Code fragment에서 실행하고 싶은 코드를 작성하고 Evaluate를 누르면 실제 콘솔에서 실행된 것을 확인할 수 있다.
이번에 스크랩 프로젝트 개발을 진행하다가 DTO를 검증해야 하는 경우가 있었습니다. 이를 if문을 이용해서 validation을 진행하였습니다. 이를 별도의 검증 클래스로 만들어 사용할 수 있지만 간단한 검증의 경우에는 JSR 표준을 이용해 간결하게 처리할 수 있습니다.
// 요청한 값에 대한 validation 처리 필요
if(postJoinReq.getUsername() == null || postJoinReq.getUsername().isEmpty()){
return new BaseResponse(JOIN_USERNAME_EMPTY);
}
if(postJoinReq.getPassword() == null || postJoinReq.getPassword().isEmpty()){
return new BaseResponse(JOIN_PASSWORD_EMPTY);
}
if(postJoinReq.getName() == null || postJoinReq.getName().isEmpty()){
return new BaseResponse(JOIN_NAME_EMPTY);
}
// 형식 확인
if(!RegexService.checkUsername(postJoinReq.getUsername())){
return new BaseResponse(JOIN_USERNAME_INVALID);
}
if(!RegexService.checkPw(postJoinReq.getPassword())){
return new BaseResponse(JOIN_PASSWORD_INVALID);
}
if(!RegexService.checkName(postJoinReq.getName())){
return new BaseResponse(JOIN_NAME_INVALID);
}
implementation 추가
Spring에서는 일종의 어댑터인 LocalValidatorFactoryBean가 제약 조건 검증을 처리한다. 이를 이용하려면 LocalValidatorFactoryBean을 빈으로 등록해야 하는데, SpringBoot에서는 아래의 의존성만 추가해주면 해당 기능들이 자동 설정된다.
// validation 검사
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '2.7.1'
@Valid과 @Validated 차이
Spring에서는 메서드 레벨 유효성 검증을 위해 JSR-303의 @Valid 어노테이션을 사용합니다. 또한 유효성 검사를 위해 멤버 속성을 표시하는 데에도 이를 사용합니다. 그러나 @Valid 어노테이션은 그룹 유효성 검사를 지원하지 않습니다.
그룹은 유효성 검사 중에 적용되는 제약 조건을 제한하는 데 도움이 됩니다. 한 가지 특정 사용 사례는 UI 마법사입니다. 여기에서 첫 번째 단계에서 필드의 특정 하위 그룹이 있을 수 있습니다. 다음 단계에서 동일한 Bean에 속하는 다른 그룹이 있을 수 있습니다. 따라서 각 단계에서 이러한 제한된 필드에 제약 조건을 적용해야 하지만 @Valid는 이를 지원하지 않습니다.
이 경우 그룹 수준 의 경우 이 JSR-303의 @Valid 의 변형인 Spring의 @Validated 를 사용해야 합니다. 이것은 메서드 수준에서 사용됩니다. 그리고 멤버 속성을 표시하기 위해 @Valid 어노테이션을 계속 사용합니다.
또한 @Valid 어노테이션은 Controller 단에서만 유효성 검사가 가능하다. 일반적으로 파라미터에 대한 유효성 검증은 Controller 단에서 최대한 처리하는 것이 좋지만, 경우에 따라 다른 곳에서도 파라미터에 대한 검증이 필요합니다. 이런 경우에 AOP 기반으로 메서드의 요청을 가로채서 유효성 검증을 진행할 수 있는 것이 바로 @Validated 어노테이션이다.
@Valid 작성
@PostMapping("/join")
public BaseResponse join(@Validated(ValidationSequence.class) @RequestBody PostJoinReq postJoinReq){
userService.join(postJoinReq);
return new BaseResponse("회원가입에 성공했습니다");
}
@RequestBody 어노테이션 옆에 @Valid를 작성하면, RequestBody로 들어오는 객체에 대한 검증을 수행한다. 이 검증의 세부적인 사항은 객체 안에 정의를 해두어야 한다.
Request class 생성
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class PostJoinReq {
@NotEmpty(message = "이메일을 입력해주세요", groups = NotEmptyGroup.class)
@Email(message = "이메일 형식이 일치하지 않습니다", groups = EmailGroup.class)
private String email; // 아이디
@NotEmpty(message = "비밀번호를 입력해주세요", groups = NotEmptyGroup.class)
@Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\\\d)[A-Za-z\\\\d!?@#$%&*]{5,15}$", message = "비밀번호 형식이 일치하지 않습니다", groups = PatternGroup.class)
private String password; // 비밀번호
@NotEmpty(message = "이름을 입력해주세요", groups = NotEmptyGroup.class)
@Pattern(regexp = "^(?=. *[a-zA-Z가-힣])[A-Za-z가-힣]{1,30}$", message = "이름 형식이 일치하지 않습니다", groups = PatternGroup.class)
private String name; // 이름
}
위와 같이 PostJoinReq 객체를 정의한 후 각 필드에 맞는 어노테이션을 사용하면 된다.
ValidationGroup class 생성
public class ValidationGroup {
// 인터페이스 선언. 이 인터페이스를 @NotEmpty같은 어노테이션에 적용시켜주면 됨.
public interface NotEmptyGroup{}
public interface NotNullGroup{}
public interface PatternGroup{}
public interface EmailGroup{}
}
ValidationSequence interface 생성
// 왼 -> 오 순서로 적용됨
@GroupSequence({Default.class, NotNullGroup.class, NotEmptyGroup.class, PatternGroup.class, EmailGroup.class})
public interface ValidationSequence {
}
@GroupSequence 어노테이션에 적용되어있는 왼쪽에서 오른쪽 순서로 적용된다.
validation 어노테이션 종류
@AssertTrue
Boolean, boolean
값이 항상 True 여야 한다.
@DecimalMax
실수 제외 숫자 클래스.
지정된 최대 값보다 작거나 같아야 하는 숫자이다.
String : value (max 값을 지정한다.)
@DecimalMin
실수 제외 숫자 클래스.
지정된 최소 값보다 크거나 같아야하는 숫자이다.
String : value (min 값을 지정한다.)
@Digits
BigDecimalBigIntegerCharSequencebyte, short, int, long, 이에 대응하는 Wrapper 클래스
허용된 범위 내의 숫자이다.
int : integer (이 숫자에 허용되는 최대 정수 자릿수)int : fraction (이 숫자에 허용되는 최대 소수 자릿수)
@Email
null도 valid로 간주된다.
올바른 형식의 이메일 주소여야한다.
@Future
시간 클래스
Now 보다 미래의 날짜, 시간
@FutureOrPresent
시간 클래스
Now의 시간이거나 미래의 날짜, 시간
@Max
실수 제외 숫자 클래스.
지정된 최대 값보다 작거나 같은 숫자이다.
long : value (max 값을 지정한다)
@Min
실수 제외 숫자 클래스.
지정된 최소 값보다 크거나 같은 숫자이다.
long : value (min 값을 지정한다)
@Negative
숫자 클래스
음수인 값이다.
@NegativeOrZero
숫자 클래스
0이거나 음수인 값이다
@NotBlank
null 이 아닌 값이다.공백이 아닌 문자를 하나 이상 포함한다
@NotEmpty
CharSequence,Collection, Map, Array
null이거나 empty(빈 문자열)가 아니어야 한다.
@NotNull
어떤 타입이든 수용한다.
null 이 아닌 값이다.
@Null
어떤 타입이든 수용한다.
null 값이다.
@Past
시간 클래스
Now보다 과거의 날짜, 시간
@PastOrPresent
시간클래스
Now의 시간이거나 과거의 날짜, 시간
@Pattern
문자열
지정한 정규식과 대응되는 문자열이어야한다. Java의 Pattern 패키지의 컨벤션을 따른다
String : regexp (정규식 문자열을 지정한다)
@Positive
숫자 클래스
양수인 값이다
@PositiveOrZero
숫자 클래스
0이거나 양수인 값이다.
@Size
CharSequence,Collection, Map, Array
이 크기가 지정된 경계(포함) 사이에 있어야한다.
int : max (element의 크기가 작거나 같다)int : min (element의 크기가 크거나 같다)
container의 기본 clip behavior를 생각하자. 자식은, 부모의 상황을 전부 알지 못한다. 특히, 부모의 모양을 모르기 때문에, Container의 모양에 맞게 범위에 벗어날 경우를 대비하여 자식이 범위 밖을 지나면 자동으로 잘라주는 clipBehavior를 설정할 수 있다.
ListView 더 알기
우리에게 스크롤 기능을 주는 ListView는 수직으로만 사용했는데, 우리가 주는 속성 값에 따라 수평으로도 사용이 가능하다. 기본값의 스크롤 방향이 수직으로 설정돼있던 것이다.
중심, 회전축의 뜻을 가지고 있는 Axis, ListView에는 scrollDirection 속성을 설정할 수 있는데, 요구되는 데이터 타입이 Axis이다.
ListView의 속성 중 scrollDirection의 기본값은 Axis.vertical 이기 때문에 수직 스크롤이 되는 것이다.
Q. 그러면, Axis.vertical만 Axis.horizontal로 바꿔주면 되는 것 아닌가요?
맞지만, 하나 더 해줘야 할 것이 있습니다. ListView의 특성상, 부모 위젯의 영향을 받기 때문에, 부모를 하나 만들어 사이즈를 정해주어야 합니다. ListView를 Axis.horizontal을 하게 되면, 높이가 무제한이 되기 때문에 사이즈 부모를 꼭 넣어줘야 합니다.