목차

  1. 소개
  2. 설명
  3. Reference

소개

이번엔 Tibero, Oracle Database의 Stored Procedure, Function에 대해 알아보고자 합니다.

기본적인 Stored Procedure, Function 에 대한 기본 개념과 정의, 활용에 대한 내용을 포함하고 있습니다.

Stored Procedure 를 설명하기 이전 PL/SQL 에 대한 설명을 먼저 진행하고자 합니다.


PL/SQL

PL/SQL은 Oracle's Procedural Language extension to SQL의 약자로 SQL의 확장된 개념으로 ORACLE에서 제공하는 프로그래밍 언어의 특성을 수용한 SQL 확장 문법입니다. PL/SQL Block 내에서 SQL의 DML(데이터 조작어)문과 Query(검색어) 문, 절차형 언어(if, loop) 등을 사용하여 절차적 프로그래밍을 가능하게 한 강력한 트랜잭션 언어입니다.

이러한 PL/SQL 의 종류는 다음과 같습니다.

  1. 익명 procedure
    • 이름 없이 사용되는 PL/SQL 블록
    • DB에 저장되지 않고 사용자가 필요할 때마다 반복적으로 작성, 실행
  2. Stored Procedure
    • 생성 이후 DB에 정보가 저장됨
    • 실행하려는 로직을 처리하고 PL/SQL 블록의 흐름 제어
    • 인자를 받아서 호출되고 실행
  3. Stored Function
    • 프로시저와 동일한 개념, 기능이나 처리 결과를 사용자에게 리턴
  4. Package
    • 특정 업무에 사용되는 프로시저 또는 함수를 묶어 생성하여 관리
  5. Trigger
    • 테이블 생성 시 지정하며, 지정된 이벤트 발생 시 자동적으로 호출되어 실행되는 특수한 형태의 프로시저
    • DB의 감시, 보안, 연속적 오퍼레이션의 자동처리 구현

이후 다룰 주제인 Stored Procedure도 PL/SQL의 한 종류 입니다.

이어서 PL/SQL의 특징을 확인해 보겠습니다.

PL/SQL의 기본 구조

Oracle의 PL/SQL은 기본적으로 블록(BLOCK) 구조로 되어 있으며, 블록의 기본적인 구성은 선언부(DECLARE), 실행부(BEGIN), 예외 처리부(EXCEPTION)로 구성되어 있습니다.

DECLARE -- 선언부
    v_param NUMBER;
    v_param2 VARCHAR(30);
BEGIN -- 실행부
    -- Business Logic
    ..
EXCEPTION -- 예외 처리부
    WHEN OTHERS THEN
        DBMS_OUTPUT.PUT_LINE('ERROR');
END;

-- DECLARE(선언부) 는 블록에서 사용될 변수, 상수, 커서, 예외를 선언하는 섹션으로 필요하지 않으면 생략할 수 있습니다.
-- 1. 변수 선언 : identifier [constant] datatype [not null] [:= | Default expr];
-- 2. 변수에 값 지정하기 : idenfier := expr;
-- 3. %TYPE을 이용한 변수 선언 : idenfier tablename.columnname%Type;
-- 4. %ROWTYPE을 이용한 변수 선언 : idenfier tablename%ROWTYPE;
-- 5. 바인드 변수 : VAR[IABLE] identifier datatype
-- DECLARE 사용은 아래와 같이 나타낼 수 있습니다.


-- DECLARE --
DECLARE
    v_age number(3); -- number(3)형 변수 선언
    v_name varchar2(30); -- varchar2(30)형 변수 선언
    v_member_row member_tbl%ROWTYPE; -- member_tbl 테이블에 컬럼 속성들을 그대로 가진 변수 선언
    v_member_name member_tbl.name%Type; -- member_tbl 테이블에 name 컬럼과 동일한 데이터형 변수 선언
    v_member_age member_tbl.age%Type; -- member_tbl 테이블에 age 컬럼과 동일한 데이터형 변수 선언
BEGIN
    -- 변수에 값 할당
    v_age := 20;
    v_name := '로그';

    -- MEMBER_TBL 테이블에 ID가 '1'인 구성원의 모든 정보를 v_member_row 변수에 할당
    SELECT *
      INTO v_member_row
    FROM MEMBER_TBL
    WHERE ID = '1';

    -- MEMBER_TBL 테이블에 ID가 '2'인 구성원의 이름과 나이를 변수에 할당
    SELECT NAME, AGE
      INTO v_member_name, v_member_age
    FROM MEMBER_TBL
    WHERE ID = '2';
END;

-- BEGIN(실행부) 는 Begin 문으로 시작하여 End; 문으로 종료하며 수행될 작업의 몸체입니다. SQL문, 제어문, 반복문, 커서 속성 등을 이용하여 블록에서 실행할 몸체를 구성할 수 있으며 생략할 수 없는 필수적 섹션입니다.
-- CRUD, IF문, Loop문, 커서 속성
-- 제어문, 반복문, 함수 정의 등의 로직을 기술함
-- 해당 실행부 내용 중 커서관련 내용은 제외하였습니다. 더 자세한 내용이 궁금하시면 가이드 문서 하단 참조 부분을 참고해주세요.

1. 제어문(IF, CASE)
DECLARE
    V_SCORE NUMBER := 80;
    V_GRADES CHAR(1);
BEGIN
    IF (V_SCORE >= 90) THEN
        V_GRADES := 'A'; 
    ELSIF (V_SCORE >= 80) THEN
        V_GRADES := 'B';
    ELSIF (V_SCORE >= 70) THEN
        V_GRADES := 'C';
    ELSIF (V_SCORE >= 60) THEN
        V_GRADES := 'D';
    ELSE
        V_GRADES := 'F';
    END IF;

    DBMS_OUTPUT.PUT_LINE('학점은 ' || V_GRADES || '입니다.');
END;

2. 반복문(LOOP, WHILE, FOR)
2-1. LOOP 문 : 0 ~ 5 까지 출력
DECLARE
    V_NUM NUMBER := 0;
BEGIN
    LOOP
        DBMS_OUTPUT.PUT_LINE(V_NUM);
        V_NUM := V_NUM + 1;
        EXIT WHEN V_NUM > 5;
END;

2-2. WHILE 문 : 0 ~ 5 까지 출력
DECLARE
    V_NUM NUMBER := 0;
BEGIN
        WHILE V_NUM < 6 LOOP
            DBMS_OUTPUT.PUT_LINE(V_NUM);
            V_NUM := V_NUM + 1;
        END LOOP;
END;

2-3. FOR 문 : 0 ~ 5 까지 출력
DECLARE
    FOR i in 0..5 LOOP
        DBMS_OUTPUT.PUT_LINE(i);
    END LOOP;
END;

-- EXCEPTION(예외처리부)는 End; 문 바로 앞에 위치하며 미리 정의된 예외를 추적하고 명시된 조건이 발생할 경우에 취할 작업을 정의하고 선택적 섹션입니다.
-- 미리정의된 예외, 사용자정의 예외, Exception 함수(sqlcode, sqlerrm)
-- 실행 도중 에러 발생 시 해결하는 문장들을 기술함
-- 해당 예외처리부는 Oracle 과 Tibero 에서 제공하는 시스템예외가 달라보였기 때문에 하단에 tbPSM의 시스템예외사항을 나타냈습니다. 이후 예제를 확인해보겠습니다.

DECLARE
    employee_num NUMBER := 980180;
    employee_grade VARCHAR2(10) := 'S';
BEGIN
    CASE employee_grade
    WHEN 'A' THEN pay_bonus_a(employee_num);
    WHEN 'B' THEN pay_bonus_b(employee_num);
    WHEN 'C' THEN pay_bonus_c(employee_num);
    WHEN 'D' THEN pay_bonus_d(employee_num);
    END CASE;
EXCEPTION
WHEN case_not_found THEN
    pay_bonus_special(employee_num);
END;

 

 

tbPSM(Tibero7)에서 제공하는 시스템 정의 예외 종류

 

 

 

 

Oracle과 Tibero에서 제공하는 PL/SQL, tbPSM에 대한 공식문서는 다음과 같습니다. 참고해 보시면 좋을 것 같습니다.

설명

Stored Procedure란?

DB 내부에 저장된 일련의 SQL 명령문들을 하나의 함수처럼 실행하기 위한 쿼리의 집합. 즉, DB에 대한 작업을 정리한 절차를 RDBMS(관계형 데이터 베이스 관리 시스템)에 저장한 쿼리의 집합이다. 영구저장모듈이라고도 불린다. 여러 쿼리를 하나의 함수로 묶은 것이다.

  • SQL Server에서 제공되는 프로그래밍 기능. 쿼리문의 집합
  • 어떠한 동작을 일괄 처리하기 위한 용도로 사용.
  • 자주 사용되는 일반적인 쿼리를 모듈화 시켜서 필요할 때마다 호출
  • 테이블처럼 각 데이터베이스 내부에 저장

이러한 Procedure는 Tibero7 가이드문서상에는 다음과 같은 형태로 안내되어 있습니다.

[CREATE [OR REPLACE]] PROCEDURE 프러시저_이름 [(파라미터[, 파라미터])] 
    [AUTHID {DEFINER | CURRENT_USER}] {AS | IS}
[PRAGMA AUTONOMOUS_TRANSACTION;]
[선언부]
BEGIN
[실행부]
[예외 처리부]
END;

 

예를 들어 Tibero7에서 Procedure를 작성한다면 다음과 같습니다.

 

CREATE OR REPLACE PROCEDURE NEW_EMP (ename VARCHAR2, deptno NUMBER)
IS
    salary NUMBER;
    deptno_not_found EXCEPTION;
BEGIN
    IF deptno = 5 THEN
        salary := 30000;
    ELSIF deptno = 6 THEN
        salary := 35000;
    ELSE
        RAISE deptno_not_found;
    END IF;

    INSERT INTO EMP (ENAME, SALARY, DEPTNO)
        VALUES (ename, salary, deptno);
EXCEPTION
WHEN deptno_not_found THEN
    -- Report Unknown Deptno
    ROLLBACK;
END NEW_EMP;

여기서 OR REPLACE문을 붙일 경우엔 해당 프로시저의 이름이 이미 정의되어 있음에도 프로시저를 업데이트한다는 의미로 사용됩니다.

그렇다면 구체적으로 어떤 차이가 있을지 확인해 보겠습니다.

 

일반 쿼리문 vs Stored Procedure의 차이점

 

 

쉽게 결론부터 말하자면 실행 순서와 속도의 차이가 존재한다

일반 쿼리문의 경우는 다음 흐름으로 실행되며 여러 번 실행되는 것도 매번 같은 순서로 실행된다.

 

 

일반 쿼리문

Stored Procedure

[1] Stored Procedure 정의 단계

  1. 구문분석 : 구문의 오류 파악
  2. 지연된 이름 확인 : 저장 프로시저를 정하는 시점에서 해당 개체(ex. 테이블)가 존재하지 않아도 상관없다. 프로시저 실행 당시에 테이블 존재 여부 확인함(개체이름 확인).
  3. 생성권한 확인 : 현재 사용자가 저장 프로시저를 생성할 권한이 있는지 확인
  4. 시스템 테이블 등록 : 저장 프로시저의 이름 및 코드가 시스템 테이블에 등록.

 

 

[2] 처음 Stored Procedure 실행 단계

구문분석 단계가 빠지는 것만 빼면 일반적인 쿼리문 수행단계와 동일하다. 저장프로시저 정의 단계의 지연된 이름확인에서 미루어두었던 해당 개체 존재 유무를 개체 이름 확인을 통해 수행한다.

 

 

[3] 이후 Stored Procedure 실행 이후에 두 번째 실행부터는 메모리(캐시)에 있는 것을 그대로 가져와 재사용하게 되어 수행시간을 많이 단축한다.

 

 

 

Stored Procedure의 장/단점

  • 장점
    • SQL Server의 성능을 향상할 수 있다 -> 저장프로시저의 두 번째 실행부터는 캐시(메모리)에 있는 것을 가져와서 사용하므로 속도가 빨라진다. 또한, 여러 개의 쿼리를 한 번에 실행할 수 있다.
    • 유지보수 및 재활용 측면에서 좋다 -> 한번 저장 프로시저를 생성해 놓으면, 언제든 실행이 가능하기 때문에 재활용 측면에서 매우 좋다.
    • 보안을 강화할 수 있다(권한체계) -> 저장 프로시저는 사용자들에게 데이터에 대한 제한적인 접근을 허용케 하는 전통적인 수단이다. 사용자별로 테이블에 권한을 주는 게 아닌 저장 프로시저에만 접근 권한을 줌으로써 테이블의 모든 정보를 사용자에게 노출하지 않고 프로시저에서 선택한 정보만 사용자에게 보여줄 수 있다.
    • 네트워크부하를 줄일 수 있다 -> 클라이언트에서 서버로 쿼리의 모든 텍스트가 전송될 경우 네트워크에는 큰 부하가 발생하게 된다. 하지만 저장 프로시저를 이용한다면 저장 프로시저의 이름, 매개변수 등 몇 글자만 전송하면 되기 때문에 부하를 크게 줄일 수 있다. (저장 프로시저를 사용하면, 서버내부에서 이동하는 모든 데이터를 임시 테이블 혹은 변수에 저장할 수 있게 된다.)



  •  단점
    • DB 확장 어려움 -> 서비스 사용자가 많아져 서버의 수를 늘려야 할 때, DB의 수를 늘리는 것이 더 어렵다. 또한, DB 교체는 거의 불가능하다.
    • 데이터 분석의 어려움 -> APP에서 SP를 호출하여 사용하는 경우 문제가 생겨도 해당 이슈에 대한 추적이 힘들다(별도의 에러 테이블 사용),
      개발된 프로시저가 여러 곳에서 사용될 경우 수정했을 때 영향의 분석이 어렵다(별도의 Description 사용),
      배포, 버전 관리 등에 대한 이력 관리가 힘들다.
    • 낮은 처리 성능 -> 문자, 숫자열 연산에 SP를 사용하면 오히려 c, java보다 느린 성능을 보일 수 있다



Function

Function을 설명하기 이전 Procedure와 Function의 차이를 한번 짚고 넘어가려고 합니다.

프로시저(Procedure) 함수(Procedure)
특정 작업을 수행 특정 계산을 수행
리턴값을 가질수도 안가질수도 있음 리턴값을 반드시 가져야 함
리턴값을 여러개 가질 수 있음 리턴값을 오직 하나만 가질 수 있음
서버(DB)단에서 기술 화면(Client)단에서 기술
수식내에서 사용 불가 수식내에서만 사용 가능
단독으로 문장 구성 가능 단독으로 문장 구성 불가

 

 

다음과 같은 차이점이 존재하는데 여기서 개인적으로 중요하게 생각했던 부분은 단독으로 구성이 불가능하다는 점이었습니다.

 

 

이어서 Function에 대하여 설명하자면 기본적인 구조는 Stored Procedure와 큰 차이는 존재하지 않습니다.

 

 

구조의 경우는 다음과 같습니다

[CREATE [OR REPLACE]] FUNCTION 함수_이름 [(파라미터[, 파라미터])] 
RETURN 반환_타입 [AUTHID {DEFINER | CURRENT_USER}] 
[DETERMINISTIC]
[PARALLEL_ENABLE]
[RESULT_CACHE [RELIES_ON (데이터소스이름[,데이터소스이름])]]
[PIPELINED]
{AS | IS}
    [PRAGMA AUTONOMOUS_TRANSACTION;]
    [선언부]
BEGIN
    [실행부]
    RETURN 반환값;
    [예외 처리부]
END;

그리고 해당 구조의 예시를 보면 다음과 같습니다.

CREATE OR REPLACE FUNCTION NEW_EMP (ename VARCHAR, deptno INT) 
RETURN NUMBER IS
    ...
BEGIN
    ...
    RETURN salary;
EXCEPTION
    ...
END NEW_EMP;

 

 

(Option) 캐싱 가능 함수

 

현재 tbPSM의 공식문서상 서브프로그램파트를 확인해 볼 때 위에서 설명한 함수 자체도 캐싱되어 작동될 수 있도록 따로 선언할 수 있게 설명되어 있습니다.

공식문서상 캐싱함수에 대한 설명은 다음과 같습니다.

  • 함수 내에서 패키지 변수를 사용하지 않을 때
  • 함수내에서 시퀀스를 사용하지 않을 때
  • 함수 내의 모든 SQL이 캐싱 가능할 때
    • SQL이 캐싱 가능할 조건 : INSERT, UPDATE, DELETE, MERGE, CURRENT_TIME, CURRENT_TIMESTAMP, LOCALTIMESTAMP, SYSTIMESTAMP, USERENV, SYS_CONTEXT, SYS_CONTEXT, SYS_GUID, CURRENT_USER, ROWNUM, USER, UID, SYSDATE 등이 들어가지 않을 때

캐싱 가능 함수는 SQL에서 실행 시 결괏값을 캐싱하여 실행한다.

함수 선언 시 DETERMINISTIC 절을 명시하면 강제로 캐싱 가능 함수로 정의할 수 있다.

구조는 다음과 같습니다.

CREATE OR REPLACE FUNCTION NEW_EMP (ename VARCHAR, deptno INT)
RETURN NUMBER
[DETERMINISTIC] IS
   ....
BEGIN
   ....
END;

Reference

목차

  1. 소개
  2. 참고 자료

소개

Java에서는 보통 웹 개발을 진행 시 Log관련된 부분들은 어떤 방식으로 사용할까요?

자바 웹 백엔드개발을 하고 있는 주니어 레벨의 개발자분들은 ( 저 포함 ) 제가 느꼈을 때 라이브러리를 단순 사용하기만 하며 로그 라이브러리를 직접 뜯어보고 응용을 해보신 분들은 없을 거라 생각합니다.

하지만 이러한 로그관련된 라이브러리도 자바에 내장된 로그 기능을 이용해서 개발되었다고 이해할 수 있는데요, 오늘은 이 내부 Log관련된 기능들이 어떤 식으로 정의되고 사용되는지에 대하여 깊이 알아보려고 합니다.

우선 Log4j2에 대한 설명으로 시작하려고 합니다.

Log4j2

  • Log4j2 는 자바 로깅 라이브러리입니다. Log4j2 를 설명하기 이전 여러 로깅 라이브러리에 대해 간단하게 소개하고 넘어가겠습니다.
  • 자바의 로깅 라이브러리는 다음과 같이 있습니다.
  • java.util.logging.Logger
  • Log4J, log4j2
  • Logback
  • SLF4j
자바의 로깅 라이브러리는 다음과같은 시간순으로 개발되었습니다.

- Log4j → Logback → Log4j2 

여기서 java.util.logging.Logger는 java언어의 java.util.logging 패키지에 속해있는 로깅용 유틸 클래스입니다.

외부 라이브러리 사용이 필요 없으며 파일이나 콘솔에 로그 내용을 출력할 수 있습니다.

​ 이후 log4j는 가장 오래된 로깅 프레임워크로써 Apache의 Java기반 로깅 프레임 워크입니다.

​ 이런 Log4j는 다음과 같은 구성을 나타내며 구성내의 Appender 부분을 해당 문서에서 Custom하여 적용하는 방법에 대한 설명을 이후 설명드릴 예 정입니다.

요소 설명
Logger 출력할 메시지를 Appender에게 전달
Appender 전달된 로그를 어디에 출력할 것인지 결정(Console, File, JDBC 등)
Layout 로그를 어떤 형식으로 출력할 것인지 결정

 

이런 Log4j 는 다음과같이 사용할 수 있습니다.

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

class Log4jLogger {
        private static Logger logger = LogManager.getLogger(Log4jLogger.class);

        void method() {
            logger.info("info log : {}", 1);
        }
}

 

이러한 Log4j의 버전업이 Log4j2 입니다.

 

여러 차이점이 존재하지만 그중에서도 큰 차이라고 생각되는 부분은 Sl4j의 지원 여부라고 생각됩니다.

이어서 Appender에 대한 자세한 설명을 이어가겠습니다.

 

SAS WIKI에서는 Console Appender와 RollingFileAppender, FileAppender에 대한 설명이 나와있지만 그 밖에도 여러 Appender가 존재하며 제가 설정한 방식은 객체를 만들고 적용하였지만 꼭 객체를 만들어야 하는 것은 아닌 걸로 생각됩니다.

 

Appender는 여러 개가 존재하며 공식문서상 나와있는 부분은 다음과 같습니다. log4j2 공식문서

 

Appenders :: Apache Log4j

Jakarta EE 8 and all Java EE applications servers use the legacy javax package prefix instead of jakarta. If you are using those application servers, you should replace the dependencies above with: We assume you use log4j-bom for dependency management. org

logging.apache.org

 

 

이러한 Appender들 중 이번 글에서는 가장 자주 사용되는 RollingFileAppender에 대한 간단한 설명정도만 하고 넘어가려고 합니다.

 

이후 필요에 의해 다른 로그가 필요할 경우 appender를 공식문서를 참고하여 작성 및 등록하면 적용시킬 수 있을 거라 생각합니다.

RollingFileAppender는 FileAppender를 상속하여 로그 파일을 rollover 합니다.(File Appender는 단순히 파일에 로그를 쓰는 것이라 생각하시면 됩니다.) 여기서 rollover는 타깃 파일을 바꾸는 것으로 이해할 수 있습니다. 예를 들어, RollingFileAppender가 타깃 파일로 log.txt에 로그 메시지를 append 하다가 어느 지정한 조건에 다다르면, 타깃 파일을 다른 파일로 바꿀 수 있습니다.

 

ex. 일자별로 로그 파일을 저장하고 싶은 경우, 파일의 크기별로 로그파일을 저장하고 싶은 경우 등등..

 

RollingFileAppender와 함께 동작하는 두 가지 component가 존재합니다. 첫 번째는 RollingPolicy로 rollover에 필요한 action을 정의합니다. 두 번째는 TriggeringPolicy로 어느 시점에 rollover가 발생할지 정의합니다. 간단히 RollingPolicy는 what, TriggeringPolicy는 when을 담당하고 있다 보시면 될 것 같습니다.

 

RollingFileAppender를 사용하기 위해서는 RollingPolicy와 TriggeringPolicy가 모두 필수적으로 필요합니다. 하지만, RollingPolicy는 TriggeringPolicy 인터페이스의 구현체로, 하나의 RollingPolicy만 지정하여도 사용 가능합니다.

RollingFileAppender는 아래의 parameter를 갖습니다. 자세한 내용은 공식문서를 참고해 주시면 좋겠습니다.

 

이 중에서 제가 작성한 코드의 일부분을 통해 설정한 파라미터들을 보여드리겠습니다.

 

public static void rollingFileAppenderInitialize(CustomFileAppender config) {
        ConfigurationBuilder<BuiltConfiguration> builder = ConfigurationBuilderFactory.newConfigurationBuilder();

        builder.setStatusLevel(Level.ALL);
        builder.setConfigurationName("CustomFileAppenderConfiguration");

        // Adding policies and strategy
        ComponentBuilder<?> triggeringPolicy = builder.newComponent("Policies")
            .addComponent(builder.newComponent("TimeBasedTriggeringPolicy")
                .addAttribute("interval", 1)
                .addAttribute("modulate", true))
            .addComponent(builder.newComponent("SizeBasedTriggeringPolicy")
                .addAttribute("size", "10MB"));

        AppenderComponentBuilder appenderBuilder = builder.newAppender("CustomFileAppender", "RollingFile")
            .addAttribute("fileName", config.setFileName())
            .addAttribute("filePattern", config.setFilePattern())
            .addAttribute("immediateFlush", true)
            .addAttribute("append", true)
            .addComponent(triggeringPolicy);

        appenderBuilder.add(builder.newLayout("PatternLayout")
            .addAttribute("pattern", config.setPattern().getConversionPattern()));

        appenderBuilder.addComponent(builder.newComponent("DefaultRolloverStrategy")
            .addAttribute("max", "30"));

        builder.add(appenderBuilder);

//        // Adding logger with filter if available
        LoggerComponentBuilder loggerBuilder = builder.newLogger("WriteLogger", config.setLogLevel());

//        FilterComponentBuilder filterBuilder = builder.newFilter("RegexFilter", Filter.Result.ACCEPT,
//                Filter.Result.DENY)
//            .addAttribute("pattern", ".*(error|fail).*");
//
//        loggerBuilder.add(filterBuilder);

        loggerBuilder.add(builder.newAppenderRef("CustomFileAppender"));
        builder.add(loggerBuilder);

        // Adding root logger
//        builder.add(builder.newRootLogger(config.setLogLevel())
//            .add(builder.newAppenderRef("CustomFileAppender")));

        Configuration configuration = builder.build();

        ((LoggerContext) LogManager.getContext(false)).start(configuration);
        Configurator.initialize(configuration);
        LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
        Configurator.setRootLevel(Level.ALL);

        // Start the appender manually if necessary
        Appender appender = ctx.getConfiguration().getAppender("CustomFileAppender");
        if (appender != null) {
            appender.start();
        }
        configuration.addAppender(appender);
        configuration.getRootLogger().addAppender(appender, null, null);

        // Update the logger context
        ctx.updateLoggers();
    }

해당 이니셜라이즈 메서드에 파라미터로 넘어오는 것은 SAS WIKI에서 설명하고 있는 CustomFileAppender입니다. 해당 객체를 그대로 넘겨주었을 시 다음과 같이 LoggerContext에 Appender를 추가 및 업데이트합니다.

 

그렇다면 한 가지 의문이 들 수 있을 것 같습니다. 해당 클래스객체를 꼭 안 만들어도 적용시킬 수 있는 것이 아닌가 라는 생각이 들 수 있는데 해당 파라미터의 어팬더를 꼭 안 만들어도 되지만 객체의 구현된 내용 일부만을 조금 수정하는 것이 앞으로 여러 개의 Appender 가 늘어남에 따라 속성값을 관리해야 하는 측면에서는 개발하기 수월해질 것 같습니다.

 

ex) Message 국제화와 같이 반복사용되는 문자내용의 관리적인 측면이라 생각하셔도 좋을 것 같습니다.

 

 

이런 식으로 rollingfileappender에 대한 설명은 넘어가겠습니다.

이어서 Sl4j의 간단한 설명을 진행하겠습니다.

 

Sl4j

SLF4J(Simple Logging Facade for Java)는 java.util.logging, logback 및 log4j와 같은 다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할을 하는 라이브러리입니다.

 

logger의 추상체로써, 인터페이스 이므로 SLF4j 인터페이스를 사용해서 로깅하게 되면 구현체만 갈아 끼우면 logback이나 log4j 등으로

마이그레이션 할 수 있습니다.

 

구현체로는 logback, log4j2 등이 있습니다.

 

디자인 패턴 중 퍼사드패턴처럼 인터페이스만을 제공되게 하여 보다 더 편리하게 사용할 수 있도록 지원되는 라이브러리입니다.

초기의 Spring은 JCL(Jakarta Commons Logging)을 이용하여 로깅하였으나, GC가 제대로 동작하지 않는다는 단점이 있었고

이를 해결하기 위해 도입한 것이 SLF4j이며 JCL의 문제를 해결하기 위해 클래스 로더 대신에 컴파일 시점에 구현체를 선택하도록 하였습니다.

참고 자료

'JAVA' 카테고리의 다른 글

[JAVA & Database] JDBC, MyBatis, JPA 의 차이점  (1) 2022.09.26
[Java] 언어의 특징  (2) 2022.09.09
[JAVA]직렬화 - Serializable란?  (0) 2022.05.16
[JAVA]DAO, DTO, VO 정리.  (0) 2021.12.25

오랜만에 블로그 글을 쓰는 것 같다.

 

마지막 취업글이 파견에 대한 이야기였으니 그로부터 몇 달이 지나 서울 본사로 올라와 근무 중 안 좋은 소식에 접했다.

문득 스타트업에서 처음 입사지원서를 낼 당시에 깊은 고민을 하지 않았던 요인들이 떠올랐다.

"스타트업은 불안정하다", "내가 하고싶은 일만 할 수 있는 게 아니다" 등등 여러 단점이 많았으나, 나는 더 다양한 경험과 마음속에 있던 모험심에 이끌려 지원했던 것 같다.

어떤 일이던 직접 경험해보기 전에는 단점에 대해 와닿지 못하는 것 같다고 생각하며 실제로 내가 이러한 소식을 겪을 줄도 꿈에도 몰랐다.

 

경영상의 이유로 회사를 나왔고, 지난주 다른 회사 면접을 통해 15일자로 입사를 하게 되었다.

 

사실 이직을 할 생각이 없는 것은 아니었으나 애매한 경력이라는 점이 마음에 많이 걸렸었다. 또 경력직으로 면접을 보았으나, 신입입사를 권유받았고 신입으로 입사를 하게 되었다.

 

결과적으로만 본다면 이직을 성공했기에 잘되었다고 느낄 수 있지만, 처음 회사를 급하게 나올 때 심정으로는 정해진 길에 따라 달리고 있는 기차에서 갑자기 튕겨져 나온 느낌이었다. 주변 사람들 역시나 각자의 레일에 따라 나아가고 있는 상황에서 갑자기 취준생으로 돌아온 기분이랄까..

 

재취업 준비를 하며 회사에서 했던 일을 정리하기도 하고.. 짧았던 회사생활에 대한 기억도 많이 해보았다.

 

다음 회사에선 지난 회사생활에서의 내 단점을 보완해야겠다는 마음도 있었고 신입이지만 조금 더 업무를 빠르게 적응하기 위해 자바 강좌도 다시 한번 돌려보고 있다.

 

그리고 회사를 찾을 때는 평판을 보는 것도 중요하지만 매출액, 투자 관련 정보도 그만큼 아주 중요하다는 걸 생각하게 되었다. ㅋㅋㅋ

 

이번 이직한 회사는 다행히 내가 사용하던 기술스택은 그대로이지만 도메인은 사실상 처음 접해보는 핀테크 분야이다.

 

이전 회사에서도 느껴보았지만 도메인에 대한 공부는 제조를 기준으로 했을 때는 1달 정도 걸렸는데.. 핀테크는 양이 많다고 알려져 있어 얼마의 기간이 걸릴지 모르겠지만 다시 신입의 마음으로 열심히 달려야 할 것 같다.

'취업 이야기' 카테고리의 다른 글

스타트업 신입 개발자 후기  (2) 2022.12.16
SI직무 인턴쉽 후기  (0) 2022.09.08

오늘은 ClickHouse Database 의 cluster를 구성해보려고 한다.

해당 글을 읽기 전 나는 혼자힘으로 구성해보고 싶다 하는 사람이 있다면 다음 공식문서를 한번 참고해보자.

 

( https://clickhouse.com/docs/en/intro )

( 사실 공식문서를 직접 읽고 따라해보는것이 제일 좋은 경험이라고 생각한다. )

 

 

 

비교적 ElasticSearch 와 비슷하면서도 특징이 다른 ClickHouse를 사내에서 사용했고 이번 기회에 분산저장구조를 구성해보던 중 ClickHouse와 관련된 정보를 블로그에서는 찾기가 많이 어려웠기 때문에..

(나는 결국 공식문서를 참고해서 하나부터 열까지 만들어보았으나) 비교적 쉽게 설명해보려고 한다.

 

 

이전 Hadoop 의 맵리듀스를 이용해보기위해 분산 저장 구조를 구축해본 경험이 있어 환경구성에서는 비교적 어렵게느꼇던 것은 없었다.

처음은 Virtualbox 에서 가상머신을 3 만들어보자 물론 가상머신을 사용해야하는것은 아니며, 자신이 사용하기 편한 가상환경을 구성해도 좋고 실제 컴퓨터를 3대로 구성해보아도 좋다.

 

해당 글은 가상머신을 기준으로 구성했다.

 

Oracle Virtual Machine 3대

 

이렇게 3개의 머신을 구성하고 OS 는 Rocky Linux 8 버전을 사용했다 Linux파일은 다음 사이트를 참고해서 iso파일을 사용환경에 맞게 다운받자. (https://rockylinux.org/download/)

 

이 후 가상머신을 바로 시작하고싶으나 그 전 한 가지 설정을 더 해주자

여기서 중요한 점은 네트워크 어댑터 관련 설정이다.

 

왜 네트워크 설정이 필요할까 ? 

 

위 3대의 가상머신은 현재 서로의 존재를 모르는 상태이다.

옆집에 누가살고있는지 통성명(?)도 안한 상태이다.

저 3대의 가상머신은 구동시킨 우리PC(로컬)의 ip를 이용하기만 하는 녀석이다.

 

그렇기때문에 우리가 이 가상머신들 사이에 브릿지를 둬서 옆집에는 김xx씨, 이xx씨(host name, host ip)가 살고있다는 것을 알려주자.

 

가상머신의 설정을 보자

 

 

설정 - 네트워크 탭을 확인해보면 기본 네트워크 설정인 NAT으로 되어있을 것이다.

 

 

이 NAT은 현재 우리 PC의 아이피를 이용해 가상머신 속에서 인터넷을 사용하고 있는 상태 라고 보면 된다.

 

우리는 여기서 두번째 어댑터를 만들어보자.

 

여기서 설정해주어야 할 것은 호스트 전용 어댑터 설정과 무작위 모드를 설정해주어야한다

 

무작위 모드는 사실 가상머신에 허용 과 모두 허용 중 원하는것으로 설정하자.

 

설정이 끝났다면 

 

다운받은 리눅스 디스크를 가상머신에 등록하고 실행시키자

 

나는 현재 환경을 따로 추출해둔 것을 사용했기때문에 위치가 

 

 

IDE 가 아닌 SATA에 있지만 따라하는 분들은 저 위에 비어있음에 등록해야 한다.

 

 

이어서 리눅스가 실행되었다면 리눅스에 설정을 또 한번 해주자..

(아직 설정할게 더 남아있다)

 

 

가상머신의 부팅 디스크를 등록해두자

다운받은 리눅스를 등록해주고 실행하자.

 

 

이후 언어를 선택하고 들어가면 다음과 같은 화면이다

 

 

우린 여기서 밑줄친 3개의 비밀번호, 파티션, 네트워크 설정을 꼭 해주자

 

파티션과 비밀번호는 알아서 설정하면 되고 특별한건 네트워크설정을 들어갔을 때 어댑터가 두개 보일 것이다.

여기서 enp0s3어댑터는 초기 1번 네트워크어댑터로 NAT를 의미하고 우린 이걸 켜주자.

 

이어서 설치가 끝났다면 우린 clickhouse 데이터베이스를 설치하기 전 네트워크 환경에대한 구성을 마지막으로 해주어야한다.

 

현재 이웃이 서로 알아볼 수 있는 다리까지는 놓여있으나 아직 이름은 모르는 상태이다. 우린 이제 이웃의 이름과 아이피를 만들어주고 알아볼 수 있도록 각각의 가상머신에 이 정보를 기입해주자.

 

지금부터는 각각의 가상머신에 모두 설정해주어야한다. 

 

 

vim /etc/hosts

해당 경로의 파일을 들어가서 다음과같이 작성해주자

 

중요한건 아래 3줄이다. 특정 ip, hostname 을 정의해서 host(가상머신)끼리 알아볼 수 있도록 하는 것이다.

 

이후 방금 hosts에서 설정한 ip가 이웃들 사이에서 알아볼 수 있는 주소 이기 때문에

저 주소를 설정 해주자.

 

vim /etc/sysconfig/network-scripts 를 들어가보면 우리가 처음 가상머신을 생성할 때

만들어두었던 네트워크 어댑터가 표시되고 이중에서 이웃과 통신할 목적으로 생성한 어댑터는 s8이다.

 

만약 네트워크어댑터를 추가하지않고 저 경로로 접속했을 경우에는 s3어댑터만 존재하는것을 테스트해볼 수 있다.

우리는 이제 저 s8어댑터에 대한 아이피를 고정으로 설정해줄 것이다.

 

 

두번째 어댑터는 이웃과의 통신을 위해 생성했던 것이고 이 어댑터에 이웃들이알아볼 수 있는 주소를 적어두어야 편지를 주고받을 수 있기 떄문에 ..?? 라고 생각하면 조금 더 쉬울 것 같다.

 

 

ifcfg-enp0s8 file 내부

여기서 BOOTPROTO 를 none or static 중 하나로 설정해주고

IPADDR, GATEWAY를 설정해준다

 

고정 IP 를 사용 할 것이기에 직접 명시해주어야 한다.

 

이제 위 방식을 그대로 3개의 가상머신은 또 각각의 ip와 hostname으로 설정을 동일하게 진행하자

 

 

이후 각각의 가상머신의 방화벽을 해제해주면 설정은 끝난다.

 

방화벽 해제하는법은 다음을 따라하자

 

우선 방화벽을 끄고

systemctl stop firewalld.service

 

이후 컴퓨터를 다시 재부팅했을 때 서비스가 실행되지 않도록 disable해주자

 

systemctl disable firewalld.service

 

이후 network를 재부팅해주자

 

systemctl restart network (network service가 없다면 무시하자)

 

마지막으로 방화벽의 상태를 확인하면 환경설정은 끝난다.

 

systemctl status firewalld.service

 

위 순서를 그대로 아래사진으로 표현해보겠다.

 

 

자 이 방화벽도 역시나 3개 다 해주자.. (귀찮겠지만 꼭 필요하다)

 

 

다음 글에서는 이제 clustering 에 필요로 하는 데이터베이스인 클릭하우스 설치부터 작성하겠다.

 

 

 

 

 

 

 

 

 

 

 

 

+ Recent posts