ERWin 에서 논리 모델링의 속성 순서를 변경할 경우, 물리 모델의 컬럼 순서는 변경되지 않는다.

 

이를 맞춰주기 위해서는 물리 모델의 컬럼 순서도 직접 변경해야 하나, 상당히 번거로울 수 있다.

 

한번에 논리모델 기준으로 컬럼 순서를 맞추는 방법을 기술한다.

아래와 같은 논리 엔터티와 이에 대응하는 물리 테이블이 있다고 가정한다.

사원부서배치 엔터티의 PK 속성 위치를 부서번호,사원번호 순서에서 사원번호, 부서번호 순서를 바꾸는 경우 물리 ERD의 컬럼 순서를 확인해 보면 순서가 변경되어 있지 않을 것을 확인할 수 있다.

이럴 경우에 논리/물리 속성의 순서를 동일하게 하는 작업을 해주면 된다.

 

1. Physical Model 에서 대상 테이블을 선택한 후 마우스 오른쪽을 클릭한 다음 "COlumn Porperties"를 클릭한다. 

2. 아이콘 중에서 "Reset Order" 아이콘을 클릭한다.

3. 아래와 같이 메뉴가 뜬다.

  • Reset order to match attribute order : 선택한 테이블의 컬럼 순서를 논리 모델의 속성 순서와 정렬을 일치시킨다.
  • Reset all... : 물리 모델 전체 컬럼 순서를 논리 모델 속성 순서와 일치시킬 수 있는 화면이 나타난다.

위와 같은 화면이 나타나면 

Reset all Tables in modelReset column order to match attribute order 를 선택한 후 [OK] 버튼을 클릭한다.

 

위의 모델에 적용하면 아래와 같이 논리모델의 속성 순서와 동일하게 정렬된 것을 볼 수 있다.

 

VScode extension 으로 MySQL DB 연결하기

VSCode에서 MySQL용 Extensions을 설치하고 연결하고자 하는 MySQL DB의 정보로 접속합니다.

 

1단계: Visual Studio Code를 엽니다.

2단계: Extensions로 이동하여 MySQL 확장을 검색하고 "MySQL"을 입력합니다.

             - 아래 화면의 Extensions(①)을 클릭한 후 검색란(②)에 "MySQL"을 입력한다.

3단계: "MySQL" 확장 프로그램을 설치합니다.

            - 검색된 Extension 중 "MySQL(Database manager for MySQL/MariaDB, PostgreSQL, SQLite, Redis and ElasticSearch)" 을 [Install] 버튼을 클릭하여 설치합니다.

 

4단계: 좌측 메뉴발를 보면 "Database"와 "NoSQL" 메뉴가 추가된 것을 볼 수 있습니다.

5단계: "Database" 아이콘을 클릭하면 "Create Connection" 버튼이 보여집니다.버튼을 클릭합니다.

6단계: 접속할 DB의 정보를 입력합니다. 본 문서에서는 Local 컴퓨터에 MySQL 서버가 설치되어 있어 localhost(127.0.0.1)를 입력했습니다.

 
  • 호스트: localhost(127.0.0.1) 또는 접속하고자 하는 서버의 IP Address 또는 endpoint
  • 사용자: db user
  •  비밀번호: 비밀번호
  • 포트번호 : 3306

7단계: " + Connect" 버튼을 클릭하여 DB에 접속합니다.

8단계: 촤측 데이터베이스 메뉴에 localhost(127.0.0.1) 연결이 추가된 것을 볼 수 있습니다. 

 

9단계: 터미널을 이용하려고 아래 그림과 같이 "Open Terminal"을 클릭하면 "Free account not support open terminal!" 이라는 메시지가 출력된다. Free 버전에서는 지원이 안되고 년간 20$를 지불하는 Premium은 지원된다. 20$를 지불하면서까지 사용하고 싶은 생각은 없다.

 

 

 

-> 2023.09.26 현재 기준 Terminal 기능이 무료로 지원된다. "Open Termial" 을 클릭하면 아래와 같은 MySQL DB command line 화면이 보여진다.

10단계: MySQL의 각 스키마를 클릭한 후 아래 화면의 "Open "을 클릭하여 SQL 편집기를 연다.

11단계: SQL 편집기에 원하는 SQL을 입력하고 "Execute" 버튼을 클릭하거나 "Ctlr + Enter"를 친다. 입력한 SQL 문장에 마우스 오른쪽을 클릭하면 SQL 실행을 할 수 있는 여러 메뉴들이 뜬다.

 

[마우스 오른쪽 클릭 시 사용할 수 있는 메뉴들]

  12단계: 이 MySQL 쿼리를 실행하면 아래창에 결과가 출력된다.

이러한 방식으로 VSCode를 통해 데이터베이스에 액세스하고 쿼리를 실행할 수 있습니다. VSCode로 개발 시 간단하게 DB를 조회하는 정도로 활용할 수 있을 정도이다.

 

VSCode를 사용하여 MySQL 서버에 연결하기

VSCode에서 MySQL DB에 연결하기 위해 MySQL용 Extensions을 설치하고 연결하고자 하는 DB의 정보를 입력하여 접속합니다.

 

1단계: Visual Studio Code를 엽니다.

2단계: Extensions로 이동하여 MySQL 확장을 검색하고 "MySQL"을 입력합니다.

             - 아래 화면의 Extensions(①)을 클릭한 후 검색란(②)에 "MySQL"을 입력한다.

3단계: "MySQL" 확장 프로그램을 설치합니다.

            - 검색된 Extension 중 "MySQL(MySQL management tool)" 을 [Install] 버튼을 클릭하여 설치한다.

 

4단계: 탐색기 옵션을 클릭하거나 ( Ctrl + Shift + E )를 누르면 MySQL이 이 섹션에 추가된 것을 볼 수 있습니다.

5단계: "MYSQL" 우측의 "+"를 클릭하면 database 의 host 정보를 입력하라는 창이 보여집니다. host, user, password, port, SSL certificate path 를 입력하는 창이 순서대로 나타납니다. 접속하고자 하는 DB의 정보를 각각 이력합니다. 

6단계: 서버 주소를 입력합니다. 본 문서에서는 컴퓨터에 MySQL 서버가 설치되어 있으므로 localhost를 입력했습니다.

7단계: 그런 다음 DB계정, 암호, user, password, port, SSL certificate path 를 차례를 입력합니다. SSL certificate path 가 없으면 입력하지 않습니다.

  • 호스트: localhost
  • 사용자: db user
  •  비밀번호: 비밀번호
  • 포트번호 : 3306
  • SSL certificate path : 해당사항 없으면 입력하지 않음

8단계: 데이터베이스에 액세스할 수 있는 MySQL 섹션에 localhost 연결이 추가된 것을 볼 수 있습니다. 

 

9단계: 데이터베이스에 연결하려면 localhost를 마우스 오른쪽 버튼으로 클릭하고 새 쿼리를 클릭합니다.     

10단계: 다음 명령을 입력하여 쿼리를 실행합니다. 쿼리 실행은 마우스 오른쪽을 클릭한 후 Run MySQL Query를 클릭하거나 단축어(ctrl + alt + E)를 입력합니다.

CREATE DATABASE myrestaurant ;
CREATE TABLE IF NOT EXISTS myrestaurant.users(
    id INT(10) NOT NULL PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(200) NOT NULL,
    phone VARCHAR(200),
    address VARCHAR(200),
    password VARCHAR(200) NOT NULL
);
INSERT INTO myrestaurant.users(name,phone,address,password)
VALUES
('Gaurav','123456789','Mumbai,India','pass134'),
('Sakshi','987654321','Chennai,India','pass456');

 11단계: 이 데이터베이스에 추가된 'users'라는 새 테이블을 보려면 연결을 새로 고칩니다. 'users'를 마우스 오른쪽 버튼으로 클릭하고 '상위 1000개 선택'을 클릭합니다.

 12단계: 이 MySQL 쿼리를 실행하고 결과를 확인합니다.

이러한 방식으로 VSCode를 통해 데이터베이스에 액세스하고 쿼리를 실행할 수 있습니다. 

 

flyway migrate 수행 시 아래와 같은 오류가 발생하는 경우

jdbc Url 뒤에 ‘allowPublicKeyRetrieval=true&useSSL=false 를 붙여주면 됩니다.

 

SQL State  : S1009
Error Code : 0
Message    : Could not connect to address=(host=127.0.0.1)(port=3306)(type=master) : RSA public key is not available client side (option serverRsaPublicKeyFile not set)

Caused by: java.sql.SQLTransientConnectionException: Could not connect to address=(host=127.0.0.1)(port=3306)(type=master) : RSA public key is not available client side (option serverRsaPublicKeyFile not set)
Caused by: java.sql.SQLException: RSA public key is not available client side (option serverRsaPublicKeyFile not set)

 

flyway.url=jdbc:mysql://127.0.0.1:3306/database_name?allowPublicKeyRetrieval=true&useSSL=false

Stored Object Access Control

CREATE PROCEDURE 및 CREATE FUNCTION에는 CREATE ROUTINE 권한이 필요합니다.

DEFINER 절이 있는 경우 필요한 권한은 사용자(user) 값에 따라 다릅니다.

DEFINER 및 SQL SECURITY 절은 루틴(procedure or function, trigger, view) 실행 시에 액세스 권한을 확인할 때 사용할 보안 컨텍스트를 지정합니다.

 

저장 프로그램(프로시저, 함수, 트리거 및 이벤트) 및 뷰는 사용하기 전에 정의되며 참조 시 해당 권한을 결정하는 보안 컨텍스트 내에서 실행됩니다. 저장 객체의 실행에 적용 가능한 권한은 DEFINER 속성 및 SQL SECURITY 특성에 의해 제어됩니다.

  • The DEFINER Attribute
  • The SQL SECURITY Characteristic
  • Examples
  • Orphan Stored Objects
  • Risk-Minimization Guidelines

The DEFINER Attribute

저장 객체에는 MySQL 계정을 지정하는 DEFINER 속성을 포함할 수 있습니다. DEFINER 속성이 생략된 경우 객체 정의자는 객체를 만든 사용자입니다.
다음 규칙은 저장된 객체에 대한 DEFINER 속성으로 지정할 수 있는 계정을 결정합니다.

  • SET_USER_ID 권한(더 이상 사용되지 않는 SUPER 권한)이 있으면, 모든 계정을 DEFINER 속성으로 지정할 수 있습니다. 계정이 존재하지 않으면 경고가 뜹니다. 저장 객체 DEFINER 속성을 SYSTEM_USER 권한이 있는 계정으로 설정하려면 SYSTEM_USER 권한이 있어야 합니다.
  • 허용되는 계정은 CURRENT_USER()로 지정된 자신의 계정입니다. DEFINER를 다른 계정으로 설정할 수 없습니다.

존재하지 않는 DEFINER 계정으로 저장 객체를 생성하면 고아 객체가 생성되어 잘못된 결과를 초래할 수 있습니다.

 

The SQL SECURITY Characteristic

저장 루틴(프로시저 및 함수) 및 뷰의 경우 정의에는 개체가 정의자 컨텍스트에서 실행되는지 호출자 컨텍스트에서 실행되는지 지정하기 위해 DEFINER 또는 INVOKER 값이 있는 SQL SECURITY 특성이 포함될 수 있습니다. 정의에서 SQL SECURITY 특성을 생략하는 경우 기본값은 정의자(DEFINER) 컨텍스트입니다.

트리거 및 이벤트에는 SQL SECURITY 특성이 없으며 항상 정의자 컨텍스트에서 실행됩니다. 
정의자(DEFINER) 및 호출자(INVOKER) 보안 컨텍스트는 다음과 같은 차이가 있습니다.

  • 정의자 보안 컨텍스트에서 실행되는 저장된 객체는 DEFINER 속성으로 명명된 계정의 권한으로 실행됩니다. 이러한 권한은 호출하는 사용자의 권한과 완전히 다를 수 있습니다. 호출자는 개체를 참조할 수 있는 권한(예: 저장 프로시저를 호출하기 위한 EXECUTE 또는 뷰에서 선택하기 위한 SELECT)이 있어야 하지만 개체 실행 중에는 호출자의 권한이 무시되고 DEFINER 계정 권한만 중요합니다. DEFINER 계정에 권한이 거의 없는 경우 개체가 수행할 수 있는 작업이 이에 따라 제한됩니다. DEFINER 계정에 높은 권한이 있는 경우(예: 관리 계정) 개체는 누가 호출하든 상관없이 강력한 작업을 수행할 수 있습니다.
  • 호출자 보안 컨텍스트에서 실행되는 저장된 루틴 또는 뷰는 호출자에게 권한이 있는 작업만 수행할 수 있습니다. 

Examples

아래 프로시저는 정의자(DEFINER) 보안 컨텍스트에서 실행하기 위해 SQL SECURITY DEFINER로 선언된 프로시저입니다.

CREATE DEFINER = 'definer_user'@'%' PROCEDURE p1()
SQL SECURITY DEFINER
BEGIN
  UPDATE t1 SET counter = counter + 1;
END;

p1 프로시저에 대한 EXECUTE 권한이 있는 모든 사용자는 CALL 문으로 호출할 수 있습니다. 그러나 p1이 실행될 때 정의자 보안 컨텍스트에서 실행되므로 DEFINER 속성으로 명명된 계정인 'admin'@'localhost'의 권한으로 실행됩니다. 이 계정 은 p1에 대한 EXECUTE 권한과 본문 내에서 참조되는 테이블 t1에 대한 UPDATE 권한이 있어야 합니다.

 

아래 프로시저는 SQL SECURITY 특성이 INVOKER라는 점을 제외하면 p1과 동일한 프로시저입니다.

CREATE DEFINER = 'definer_user'@'%' PROCEDURE p2()
SQL SECURITY INVOKER
BEGIN
  UPDATE t1 SET counter = counter + 1;
END;

p1과 달리 p2는 호출자 보안 컨텍스트에서 실행되므로 DEFINER 속성 값에 관계없이 호출한 사용자의 권한으로 실행됩니다. 호출자에게 p2에 대한 EXECUTE 권한이나 테이블 t1에 대한 UPDATE 권한이 없으면 p2가 실패합니다.

 

예제1) DEFINER는 프로시저에 대한 EXECUTE와 테이블에 대한 SELECT 권한만 있고, INVOKER는 프로시저에 대한 EXECUTE와 테이블에 대한 SELECT, UPDATE 권한이 있을 경우 p2프로시져 INVOKER가 프로시저 수행하면 정상수행되지만 DEFINER 가 수행하면 권한 오류가 발생합니다.

CREATE DEFINER = 'definer_user'@'%' PROCEDURE p2()
SQL SECURITY INVOKER
BEGIN
  UPDATE t1 SET counter = counter + 1;
END;

 

DEFINER가 프로시저를 수행했을 경우 UPDATE 권한이 없다는 오류가 발생합니다.

 

ERROR 1142 (42000): UPDATE command denied to user 'definer_user'@'localhost' for table 't1'

INVOKER가 프로시저를 수행했을 경우 프로시저가 정상적으로 수행됩니다.

Query OK, 2 rows affected (0.02 sec)

예제2) 이번에는 위 예제와 반대로 INVOKER는 프로시저에 대한 EXECUTE와 테이블에 대한 SELECT 권한만 있고, DEFINER는 프로시저에 대한 EXECUTE와 테이블에 대한 SELECT, UPDATE 권한이 있을 경우 p1프로시져 수행 시 DEFINER와 INVOKER는 정상 수행합니다. 프로시저를 정의한 DEFINER가 권한이 있기 때문입니다.

CREATE DEFINER = 'definer_user'@'%' PROCEDURE p1()
SQL SECURITY DEFINER
BEGIN
  UPDATE t1 SET counter = counter + 1;
END;

 

DEFINER 또는 INVOKER 가 프로시저를 수행했을 경우 프로시저가 정상적으로 수행됩니다.

Query OK, 2 rows affected (0.02 sec)

[참고사항]

프로시저 수행 권한을 줄 때 주의합니다.

특정 프로시저에 권한을 줄 때는 

GRANT EXECUTE ON PROCEDURE 형식을 사용하고

GRANT EXECUTE ON PROCEDURE db_name.procdure_name TO 'user_name'@'%';

특정 DB의 전체 프로시저에 대한 수행 권한을 줄 때는

GRANT EXECUTE ON db_name.* TO user_name 형식으로 줍니다.

GRANT EXECUTE ON db_name.* TO 'user_name'@'%';

 

DEFINER=CURRENT_USER() 로 설정

DEFINER 절을 생략하면 기본 정의자는 CREATE PROCEDURE 또는 CREATE FUNCTION 문을 실행하는 사용자입니다. 이는 DEFINER = CURRENT_USER를 명시적으로 지정하는 것과 같습니다.

 

*** 테스트 스크립트 ***

-- definer 계정 생성 및 권한 설정
create user 'definer_user'@'%' identified by 'definer_user';
grant select, update on userdb.* to 'definer_user'@'%';
grant execute on userdb.* to 'definer_user'@'%';

-- invoker 계정 생성 및 권한 설정
create user 'invoker_user'@'%' identified by 'invoker_user';
grant select on userdb.* to 'invoker_user'@'%';
grant execute on userdb.* to 'invoker_user'@'%';

-- SQL SECURITY를 DEFINER로 설정
delimiter //
CREATE DEFINER = 'definer_user'@'%' PROCEDURE p1()
SQL SECURITY DEFINER
BEGIN
  UPDATE t1 SET counter = counter + 1;
END;
//

-- SQL SECURITY를 INVOKER로 설정
delimiter //
CREATE DEFINER = 'definer_user'@'%' PROCEDURE p2()
SQL SECURITY INVOKER
BEGIN
  UPDATE t1 SET counter = counter + 1;
END;
//


-- DEFINER를 current_user로 설정
delimiter //
CREATE DEFINER = current_user PROCEDURE p3()
BEGIN
  UPDATE t1 SET counter = counter + 1;
END;
//

 

MySQL DEFINER 정의 관련 정보 조회

SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.EVENTS;
SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.ROUTINES;
SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.TRIGGERS;
SELECT DISTINCT DEFINER FROM INFORMATION_SCHEMA.VIEWS;

 

 

프로시져 (Stored procedure) 권한 문장

CREATE
DEFINER = { user | CURRENT_USER } 
PROCEDURE sp_process_ranking()
SQL SECURITY { DEFINER | INVOKER }
BEGIN
  ...
  ...
END;

 

함수 (Stored function) 생성 문장

CREATE
DEFINER = { user | CURRENT_USER } 
FUNCTION fn_get_usercount() RETURN INT UNSIGNED
SQL SECURITY { DEFINER | INVOKER }
BEGIN
  ...
  ...
END;

 

뷰 권한 문장

CREATE or REPLACE
DEFINER = { user | CURRENT_USER } 
SQL SECURITY { DEFINER | INVOKER }
VIEW view_name
AS SELECT ...

 

동일한 입력 매개변수에 대해 항상 동일한 결과를 생성하는 경우 "DETERMINISTIC"인 것으로 간주되고 그렇지 않은 경우 "NOT DETERMINISTIC" 것으로 간주됩니다. 기본값은 NOT DETERMINISTIC입니다. 함수가 "DETERMINISTIC"임을 선언하려면 DETERMINISTIC을 명시적으로 지정해야 합니다.

 

MySQL은 DETERMINISTIC으로 선언된 루틴에 비결정적(NOT DETERMINISTIC) 결과를 생성하는 문이 없는지 확인하지 않습니다. 루틴을 잘못 선언하면 결과나 성능에 영향을 미칠 수 있습니다. 비결정적 루틴을 DETERMINISTIC으로 선언하면 옵티마이저가 잘못된 실행 계획을 선택하여 예기치 않은 결과가 발생할 수 있습니다. DETERMINISTIC 루틴을 NON DETERMINISTIC으로 선언하면 성능이 저하될 수 있습니다.

 

동일한 입력 매개 변수에 대해 항상 동일한 결과를 생성하는 경우, DETERMINISTIC로 간주되고 그렇지 않으면 NOT DETERMINISTIC으로 간주됩니다.

 

MySQL의 Stored procedure나 Function이 NOT DETERMINISTIC으로 정의되면 Mysql은 이 Stored routine의 결과값이 시시각각 달라진다고 가정하고, 비교가 실행되는 레코드마다 이 Stored routine을 매번 새로 호출해서 비교를 실행하게 됩니다.

즉, 함수 호출의 결과값이 Cache되지 않고, 비교되는 레코드 건수만큼 함수 호출을 발생하는 것입니다.

 

테스트 수행

- 테이블생성

- 테이블 데이터 생성(1백만건), 코드테이블 생성(함수를 수행해서 코드값을 return 할 수 있도록, 1000건)

- 코드별 균등하게 데이터 생성(case1: 코드 별로 1,000건 정도, case2: 코드종류 1건만)

- 펑션 생성(determistic, non-determistic)

- 수행 결과 비교

- 수행시간, profiling, 자원사용량?

 

코드테이블 생성 및 데이터 생성

-- 코드테이블 생성
create table code_tab(code int, code_name varchar(20));
alter table code_tab add constraint pk_code_tab primary key (code);

-- 코드데이터 생성
insert into code_tab(code, code_name)
WITH RECURSIVE cre_code(code, code_name) AS
(
 select 1 code, cast('code-1' as char(50))
 union all
 select code + 1, concat('code-', code) from cre_code where code < 1000
)
select * From cre_code;

transaction 테이블 생성 및 데이터 생성

-- transaction 테이블 생성
create table transaction_tab(tid int, tid_name varchar(20), code int);
alter table transaction_tab add constraint pk_transaction_tab primary key (tid);

-- transaction 데이터 생성
INSERT INTO TRANSACTION_TAB(TID, TID_NAME, CODE)
WITH RECURSIVE cre_tid(tid, tid_name, code) AS
(
 SELECT 1 tid, cast('transaction-1' AS CHAR(50)), 999
 UNION ALL
 SELECt /*+ SET_VAR(cte_max_recursion_depth = 2M) */ tid + 1, CONCAT('transaction-', tid), 999 FROM cre_tid where tid < 1000000
)
SELECT * FROM cre_tid;

 

stored function 생성 - DETERMISTIC, NON-DETERMISTIC

-- 코드값을 return 하는 stored function
DELIMITER $$
CREATE FUNCTION getcodename(v_code varchar(20)) RETURNS VARCHAR(20)
  DETERMINISTIC
BEGIN
declare v_codename varchar(20);
  select code_name into v_codename from code_tab where code = v_code ;
  return v_codename;
END $$
DELIMITER ;

수행시간 검증 

- Stored Function 호출, Scalar Subquery, Join 비교

-- function 호출
explain analyze
select *, getcodename(code) from transaction_tab 
where tid > 10000 and tid < 21000;
--------------------------------------------------------------------------------------
-> Filter: ((a.tid > 10000) and (a.tid < 21000))  (cost=101289.41 rows=110728) (actual time=4.361..475.477 rows=10999 loops=1)
     -> Table scan on A  (cost=101289.41 rows=996748) (actual time=0.022..422.806 rows=1000000 loops=1)
  ==> 수행시간 : 4.891 sec 
     
-- scalar subquery
explain analyze
select *, (select code_name from code_tab where code = A.code) from transaction_tab A 
where tid > 10000 and tid < 21000;
--------------------------------------------------------------------------------------
3	364	16:43:37	explain analyze
 select A.*, (select B.code_name from code_tab B where B.code = A.code) from transaction_tab A 
 where A.tid > 10000 and A.tid < 21000	1 row(s) returned	5.078 sec / 0.000 sec
 ==> 수행시간 : 5.078 sec 
 
-- join query
explain analyze
select *, B.code_name from transaction_tab A, code_tab B 
where A.tid > 10000 and A.tid < 21000
and   A.code = B.code;
--------------------------------------------------------------------------------------
-> Inner hash join (a.`code` = b.`code`)  (cost=1227191.04 rows=123006) (actual time=5.196..444.114 rows=10999 loops=1)
     -> Filter: ((a.tid > 10000) and (a.tid < 21000))  (cost=120.92 rows=11073) (actual time=4.628..442.106 rows=10999 loops=1)
        ...
==> 수행시간 : 0.469 sec

DETERMISTIC Stored Function 호출과 Scalar Subquery 그리고 JOIN 쿼리 비교 시

DETERMISTIC Stored Function 호출과 Scalar Subquery의 소요시간과 실행계획은 동일하였고 미세하게 Strored Function 수행 시간이 빨랐다. 그러나, JOIN 쿼리가 상대적으로 더 좋은 수행 속도를 보여주었다.(HASH JOIN을 수행하기 때문)

JOIN Query를 사용할 수 있으면 최대한 JOIN 쿼리를 사용하도록 한다.

 

code_table의 code 컬럼에 인덱스 생성 시(또는 Primary key 지정 시) 3개의 수행속도는 거의 비슷했다.

 

데이터와 테스트 방법에 따라 결과가 상이할 수 있으니 실제 적용 시에는 더 많은 Case로 테스트 해보는 것이 좋겠다.

 

NON-DETERMISTIC Function은 대부분의 경우 성능이 떨어지기 때문에 테스트하지 않는다. 

 

 

'MySQL' 카테고리의 다른 글

MySQL event란  (0) 2024.12.05
MySQL Lock 발생  (0) 2023.09.22
MySQL - Delimiter  (0) 2023.04.10
MySQL 8.0 connect by  (0) 2023.03.21
GCP-Google Cloud Platform-프로젝트 구축  (0) 2022.03.04

MySQL 버전 : 8.0.32

 

MySQL recursive SQL 에 대해 설명

재귀 공통 테이블 식은 자체 이름을 참조하는 하위 쿼리가 있는 식입니다. 예를 들어 아래와 같은 형식입니다.

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte WHERE n < 5
)
SELECT * FROM cte;

재귀 CTE(Common Table Expressions)의 구조는 다음과 같습니다.

  • WITH 절의 CTE가 자신을 참조하는 경우 WITH 절은 WITH RECURSIVE로 시작해야 합니다.
  • 재귀 CTE 하위 쿼리에는 UNION ALL 또는 UNION [DISTINCT]로 구분되는 두 부분이 있습니다.
SELECT ...      -- return initial row set
UNION ALL
SELECT ...      -- return additional row sets

첫 번째 SELECT는 CTE에 대한 초기 행을 생성하며 CTE 이름을 참조하지 않습니다. 두 번째 SELECT는 추가 행을 생성하고 FROM 절에서 CTE 이름을 참조하여 재귀합니다. 두 번째 SELECT가 새 행을 생성하지 않으면 재귀가 종료됩니다. Recursive CTE는 비재귀적 SELECT 부분과 재귀적 SELECT 부분으로 구성됩니다.

 

  • CTE의 컬럼타입은 비재귀적 SELECT 부분의 컬럼타입이며 모두 null을 허용합니다.
  • 비재귀 부분과 재귀 부분이 UNION [DISTINCT]로 구분되면 중복 행이 제거됩니다. 이는 무한 루프를 피하기 위해 전이적 폐쇄를 수행하는 쿼리에 유용합니다.
  • 재귀 부분의 각 반복은 이전 반복에서 생성된 행에 대해서만 작동합니다.

다음 쿼리는 재귀적 CTE 하위 쿼리의 초기 행 집합을 생성하는 비재귀적 부분입니다.

SELECT 1

 

CTE 하위의 아래 쿼리는 재귀 부분입니다.

SELECT n + 1 FROM cte WHERE n < 5

해당 SELECT는 이전 행 집합의 n 값보다 하나 더 큰 새 값을 가진 행을 생성합니다. 첫 번째 반복은 초기 행 세트(1)에 대해서 작동하고 1+1=2를 생성합니다. 두 번째 반복은 첫 번째 반복의 행 집합(2)에서 작동하고 2+1=3을 생성합니다. n이 5보다 작지 않을 때까지 계속됩니다.

CTE의 재귀 부분이 비재귀 부분보다 열에 대해 더 길은 값을 생성하는 경우 데이터 잘림을 방지하기 위해 비재귀 부분의 열을 확장해야 할 수 있습니다.

WITH RECURSIVE cte AS
(
  SELECT 1 AS n, 'abc' AS str
  UNION ALL
  SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3
)
SELECT * FROM cte;

nonstrict SQL 모드에서는 위의 SQL문은 다음을 출력합니다.

비재귀적 SELECT가 열의 길이를 결정하기 때문에 str 열 값은 모두 'abc'입니다. 결과적으로 재귀 SELECT에 의해 생성된 더 길은 str 값이 잘립니다.

+------+------+
| n    | str  |
+------+------+
|    1 | abc  |
|    2 | abc  |
|    3 | abc  |
+------+------+

 

strict SQL모드에서는 이 명령문은 오류를 발생합니다.

ERROR 1406 (22001): Data too long for column 'str' at row 1

이 문제를 해결하려면 명령문이 잘림이나 오류를 생성하지 않도록 비재귀 SELECT에서 CAST()를 사용하여 str 열의 길이를 크게 만들어야 합니다.

WITH RECURSIVE cte AS
(
  SELECT 1 AS n, CAST('abc' AS CHAR(20)) AS str
  UNION ALL
  SELECT n + 1, CONCAT(str, str) FROM cte WHERE n < 3
)
SELECT * FROM cte;

 

이제 명령문은 잘림 없이 다음 결과를 생성합니다.

+------+--------------+
| n    | str          |
+------+--------------+
|    1 | abc          |
|    2 | abcabc       |
|    3 | abcabcabcabc |
+------+--------------+

일부 구문 제약 조건은 재귀 CTE 하위 쿼리 내에서 적용됩니다.

재귀 SELECT 부분은 다음 구문을 포함하지 않아야 합니다.

  • Aggregate functions such as SUM()
  • Window functions
  • GROUP BY
  • ORDER BY
  • DISTINCT

이러한 제약 조건은 재귀 CTE의 비재귀 SELECT 부분에는 적용되지 않습니다.

재귀 SELECT 부분은 sub 쿼리가 아닌 FROM 절에서만 한 번만 CTE를 참조해야 합니다. CTE 이외의 테이블을 참조하고 CTE와 조인할 수 있습니다.

 

Limiting Common Table Expression Recursion

재귀 CTE의 경우 재귀 SELECT 부분에 재귀를 종료하는 조건이 포함되어야 합니다. 런어웨이 재귀 CTE를 방지하기 위한 개발 기법으로 실행 시간을 제한하여 강제 종료할 수 있습니다.

  • cte_max_recursion_depth 시스템 변수는 CTE의 재귀 수준에 대한 제한을 적용합니다. 이 변수 ​​값보다 더 많은 수준을 반복하는 모든 CTE의 실행을 종료합니다.
  • max_execution_time 시스템 변수는 현재 세션 내에서 실행되는 SELECT 문에 대한 실행 제한 시간을 적용합니다.
  • MAX_EXECUTION_TIME 옵티마이저 힌트는 나타나는 SELECT 문에 대해 쿼리당 실행 제한 시간을 적용합니다.

재귀 CTE가 재귀 실행 종료 조건 없이 실수로 작성되었다고 가정합니다.

 

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte
)
SELECT * FROM cte;

-- cte_max_recursion_depth=1000 설정 시 오류 발생 -- 
ERROR 3636 (HY000): Recursive query aborted after 1001 iterations. 
Try increasing @@cte_max_recursion_depth to a larger value.

기본적으로 cte_max_recursion_depth 값은 1000이므로 CTE가 1000 수준을 초과하여 재귀하면 종료됩니다. 

 

MySQL 8.0.19부터 재귀 쿼리 내에서 LIMIT를 사용하여 가장 바깥쪽 SELECT에 반환할 최대 행 수를 지정할 수도 있습니다. 예를 들면 다음과 같습니다.

WITH RECURSIVE cte (n) AS
(
  SELECT 1
  UNION ALL
  SELECT n + 1 FROM cte LIMIT 900
)
SELECT * FROM cte;

 

Recursive Common Table Expression Examples

Hierarchical Data Traversal

재귀 공통 테이블 표현식은 계층 구조에 유용합니다. 아래의 예시를 살펴봅니다. 최상위 직원(CEO)의 관리자 ID는 NULL(관리자 없음)입니다.

CREATE TABLE employees (
  id         INT PRIMARY KEY NOT NULL,
  name       VARCHAR(100) NOT NULL,
  manager_id INT NULL,
  INDEX (manager_id),
FOREIGN KEY (manager_id) REFERENCES employees (id)
);

INSERT INTO employees VALUES
(333, "Yasmina", NULL),  # Yasmina is the CEO (manager_id is NULL)
(198, "John", 333),      # John has ID 198 and reports to 333 (Yasmina)
(692, "Tarek", 333),
(29, "Pedro", 198),
(4610, "Sarah", 29),
(72, "Pierre", 29),
(123, "Adil", 692);

각 직원의 관리 체인(즉, CEO에서 직원으로의 경로)이 포함된 조직도를 생성하려면 재귀 CTE를 사용합니다.

아래 예제는 사번ID의 체인경로를 조회한다. 이름으로 하고 싶은 경우에는 id 대신 name 을 사용하면 된다.

WITH RECURSIVE employee_paths (id, name, path) AS
(
  SELECT id, name, CAST(id AS CHAR(200))
    FROM employees
    WHERE manager_id IS NULL
  UNION ALL
  SELECT e.id, e.name, CONCAT(ep.path, '->', e.id)
    FROM employee_paths AS ep JOIN employees AS e
      ON ep.id = e.manager_id
)
SELECT * FROM employee_paths ORDER BY path;
 
+------+---------+--------------------+
| id   | name    | path               |
+------+---------+--------------------+
|  333 | Yasmina | 333                |
|  198 | John    | 333->198           |
|   29 | Pedro   | 333->198->29       |
| 4610 | Sarah   | 333->198->29->4610 |
|   72 | Pierre  | 333->198->29->72   |
|  692 | Tarek   | 333->692           |
|  123 | Adil    | 333->692->123      |
+------+---------+--------------------+

 

MySQL recursive SQL로 데이터 생성하기

아래와 같은 WITH RECURSIVE SQL로 임시 데이터를 생성할 수 있습니다.

임시 데이터를 입력할 테이블을 생성합니다.

CREATE TABLE cte_populated
(
  id INT NOT NULL PRIMARY KEY,
  val varchar(20)
) ENGINE = InnoDB;

 

INSERT 문장과 WITH RECURSIVE 문장으로 임시 데이터를 입력합니다. 아래 예제는 1부터 2000000까지 2000000건의 데이터를 입력하는 문장입니다. 이를 응용하여 다양하게 데이터를 생성할 수 있습니다.

INSERT cte_populated(id, val)
WITH  RECURSIVE int_seq AS (
SELECT 1 AS id, cast('test1' as char(200)) as pass
UNION ALL
SELECT id + 1, concat('test',id)
FROM int_seq
WHERE id  < 2000000)
SELECT /*+ SET_VAR(cte_max_recursion_depth = 3M) */ id,  pass as id FROM int_seq;

/*+ SET_VAR(cte_max_recursion_depth = 3M) */ 힌트는 서버에 cte_max_recursion_depth 파라미터의 기본값이 1000으로 설정되어 있어 depth가 1000 이상인 경우 오류가 발생합니다. 이럴 경우 1000 건 이상 입력 시 힌트를 사용하여 원하는 만큼의 데이터를 생성할 수 있습니다. 3M에서 'M'은 백만입니다.

 

 

AWS Lambda를 사용하여 Amazon RDS 중지 및 시작

 

일반적인 개발 환경에서 개발 및 테스트 데이터베이스는 일과시간(9:00 ~ 18:00) 동안 사용되며 사용하지 않을 때는 유휴 상태입니다. 그러나 이 유휴 시간 동안에도 컴퓨팅 및 스토리지에 대한 비용이 청구됩니다.

 

비용을 줄이기 위해 Amazon RDS 인스턴스를 일시적으로 중지 할 수 있습니다 . 인스턴스가 중지된 동안에는 스토리지 및 백업에 대한 요금만 부과되며 DB 인스턴스 시간에 대한 요금은 부과되지 않습니다. 중지된 인스턴스는 7일 후에 자동으로 시작됩니다.

본 문서는 컴퓨팅 비용을 절감하기 위해 유휴 데이터베이스를 중지 및 시작하도록 AWS Lambda 및 Amazon EventBridge를 사용하는 방법을 설명합니다.

 

솔루션 개요

AWS Lambda는 서버를 관리하지 않고도 코드를 실행할 수 있는 컴퓨팅 서비스입니다. 

Amazon EventBridge는 이벤트를 생성하고 이에 대한 응답으로 특정 작업을 할당할 수 있는 간단한 규칙을 사용합니다.

Amazon RDS 인스턴스에 대한 컴퓨팅 비용은 시간 단위로 청구됩니다. 

오랜 시간 동안 유휴 상태로 유지되는 개발 또는 테스트 환경에서 프로비저닝된 데이터베이스 인스턴스는 Lambda 함수 및 EventBridge 규칙을 사용하여 야간에 자동으로 중지되고 업무 시간 전에 시작될 수 있습니다.

 

다음 그림은 Lambda함수와 EvnentBridge를 이용한 RDS 자동 중지/기동 아키텍처입니다.

전체적인 구성 단계

  1. 리소스를 프로비저닝
    • RDS 인스턴스용 태그(태그를 이용한 시작/중지 Lambda)
    • Lambda에 대한 AWS Identity and Access Management(IAM) 정책 및 역할
    • 데이터베이스를 중지하고 시작하는 두 개의 Lambda 함수
  2. Lambda 함수를 트리거하는 Amazon EventBridge 규칙을 생성

 

전제 조건

다음이 필요합니다.

  • Amazon RDS에 대한 관리자 액세스 권한이 있는 AWS 계정
  • RDS 인스턴스

 

리소스 프로비저닝

태그, Lambda에 대한 IAM 정책 및 역할, 데이터베이스를 중지하거나 시작하도록 예약하는 Lambda 함수를 생성하는 방법을 설명합니다.

태그 만들기

DB 인스턴스 생성 시 또는 인스턴스 생성 후 인스턴스에 태그를 할당할 수 있습니다. 아래와 같이 태그를 할당합니다.

  1. Amazon RDS 콘솔에서 태그를 추가할 데이터베이스와 해당 데이터베이스 내의 인스턴스를 선택
  2. 인스턴스 세부 정보 아래의 태그 탭에서 태그 추가를 선택
  3. 태그 키 DEV-TEST에 를 입력
  4.  에 Auto-Shutdown를 입력
  5. 추가를 선택

데이터베이스를 중지하는 Lambda 함수 생성

데이터베이스를 중지하고 시작하기 위해 호출할 수 있는 두 개의 Lambda 함수를 생성합니다. 

먼저 중지 기능을 하는 Lambda 함수를 생성합니다.

  1. Lambda 콘솔의 탐색 창에서 함수를 선택합니다.
  2. 함수 만들기를 선택합니다 .
  3. 함수 이름 에 stoprds를 입력합니다 .
  4. 런타임 에서 Python 3.10을 선택합니다 .
  5. 실행 역할 에서 기존 역할 사용을 선택합니다 .
  6. 기존 역할 에서 생성한 역할을 선택합니다( rdsLambda).
  7. 함수 만들기를 선택합니다 .
  8. 함수 세부 정보 페이지에서 함수 코드로 이동합니다.
  9. 샘플 코드를 삭제하고 다음을 입력합니다.
    # this Code will help to schedule stop the RDS databasrs using Lambda
    # Yesh 
    # Version -- 2.0
    
    import boto3
    import os
    import sys
    import time
    from datetime import datetime, timezone
    from time import gmtime, strftime
    
    def shut_rds_all():
        region=os.environ['REGION']
        key=os.environ['KEY']
        value=os.environ['VALUE']
    
        
        client = boto3.client('rds', region_name=region)
        response = client.describe_db_instances()
        v_readReplica=[]
        for i in response['DBInstances']:
            readReplica=i['ReadReplicaDBInstanceIdentifiers']
            v_readReplica.extend(readReplica)
        
        for i in response['DBInstances']:
    #The if condition below filters aurora clusters from single instance databases as boto3 commands defer to stop the aurora clusters.
            if i['Engine'] not in ['aurora-mysql','aurora-postgresql']:
    #The if condition below filters Read replicas.
                if i['DBInstanceIdentifier'] not in v_readReplica and len(i['ReadReplicaDBInstanceIdentifiers']) == 0:
                    arn=i['DBInstanceArn']
                    resp2=client.list_tags_for_resource(ResourceName=arn)
    #check if the RDS instance is part of the Auto-Shutdown group.
                    if 0==len(resp2['TagList']):
                        print('DB Instance {0} is not part of autoshutdown'.format(i['DBInstanceIdentifier']))
                    else:
                        for tag in resp2['TagList']:
    #If the tags match, then stop the instances by validating the current status.
                            if tag['Key']==key and tag['Value']==value:
                                if i['DBInstanceStatus'] == 'available':
                                    client.stop_db_instance(DBInstanceIdentifier = i['DBInstanceIdentifier'])
                                    print('stopping DB instance {0}'.format(i['DBInstanceIdentifier']))
                                elif i['DBInstanceStatus'] == 'stopped':
                                    print('DB Instance {0} is already stopped'.format(i['DBInstanceIdentifier']))
                                elif i['DBInstanceStatus']=='starting':
                                    print('DB Instance {0} is in starting state. Please stop the cluster after starting is complete'.format(i['DBInstanceIdentifier']))
                                elif i['DBInstanceStatus']=='stopping':
                                    print('DB Instance {0} is already in stopping state.'.format(i['DBInstanceIdentifier']))
                            elif tag['Key']!=key and tag['Value']!=value:
                                print('DB instance {0} is not part of autoshutdown'.format(i['DBInstanceIdentifier']))
                            elif len(tag['Key']) == 0 or len(tag['Value']) == 0:
                                print('DB Instance {0} is not part of auroShutdown'.format(i['DBInstanceIdentifier']))
                elif i['DBInstanceIdentifier'] in v_readReplica:
                    print('DB Instance {0} is a Read Replica. Cannot shutdown a Read Replica instance'.format(i['DBInstanceIdentifier']))
                else:
                    print('DB Instance {0} has a read replica. Cannot shutdown a database with Read Replica'.format(i['DBInstanceIdentifier']))
    
        response=client.describe_db_clusters()
        for i in response['DBClusters']:
            cluarn=i['DBClusterArn']
            resp2=client.list_tags_for_resource(ResourceName=cluarn)
            if 0==len(resp2['TagList']):
                print('DB Cluster {0} is not part of autoshutdown'.format(i['DBClusterIdentifier']))
            else:
                for tag in resp2['TagList']:
                    if tag['Key']==key and tag['Value']==value:
                        if i['Status'] == 'available':
                            client.stop_db_cluster(DBClusterIdentifier=i['DBClusterIdentifier'])
                            print('stopping DB cluster {0}'.format(i['DBClusterIdentifier']))
                        elif i['Status'] == 'stopped':
                            print('DB Cluster {0} is already stopped'.format(i['DBClusterIdentifier']))
                        elif i['Status']=='starting':
                            print('DB Cluster {0} is in starting state. Please stop the cluster after starting is complete'.format(i['DBClusterIdentifier']))
                        elif i['Status']=='stopping':
                            print('DB Cluster {0} is already in stopping state.'.format(i['DBClusterIdentifier']))
                    elif tag['Key'] != key and tag['Value'] != value:
                        print('DB Cluster {0} is not part of autoshutdown'.format(i['DBClusterIdentifier']))
                    else:
                        print('DB Instance {0} is not part of auroShutdown'.format(i['DBClusterIdentifier']))
    
    def lambda_handler(event, context):
        shut_rds_all()
  10. 저장을 선택합니다 .
    위의 람다 함수는 환경 변수로 전달되는 3개의 매개변수(REGION, KEY, VALUE)가 필요합니다. REGION은 RDS 인스턴스가 현재 실행 중인 위치이고 KEY 및 VALUE는 이전 단계에서 자동 종료가 필요한 인스턴스에 대해 연결한 태그입니다. RDS 인스턴스에 연결한 값은 환경 변수와 정확히 일치해야 합니다. 아래 단계에 따라 입력하십시오.
  11. '구성' 탭으로 이동하여 '환경 변수'를 선택합니다. EDIT를 클릭하고 아래와 같이 환경 변수를 추가합니다.
  12. 테스트를 선택하여 기능을 테스트합니다.

    테스트 이벤트 구성 페이지가 열립니다.
  13. 이벤트 이름 에 를 입력합니다 stop.
  14. 생성을 선택합니다 .
  15. 테스트를 다시 선택하여 기능을 테스트합니다.

테스트는 함수에 지정된 태그로 데이터베이스를 중지합니다. 함수 세부 정보 페이지에서 함수가 성공한 것을 확인할 수 있습니다.

데이터베이스를 시작하는 Lambda 함수 생성

동일한 단계를 반복하여 라는 시작 함수를 만듭니다 rdsstart. 다음 코드를 사용하십시오.

# this Code will help to schedule start the RDS databasrs using Lambda
# Yesh 
# Version -- 2.0

import boto3
import os
import sys
import time
from datetime import datetime, timezone
from time import gmtime, strftime

def start_rds_all():
    region=os.environ['REGION']
    key=os.environ['KEY']
    value=os.environ['VALUE']
    client = boto3.client('rds', region_name=region)
    response = client.describe_db_instances()

    v_readReplica=[]
    for i in response['DBInstances']:
        readReplica=i['ReadReplicaDBInstanceIdentifiers']
        v_readReplica.extend(readReplica)
    
    for i in response['DBInstances']:
#The if condition below filters aurora clusters from single instance databases as boto3 commands defer to start the aurora clusters.
        if i['Engine'] not in ['aurora-mysql','aurora-postgresql']:
#The if condition below filters Read replicas.
            if i['DBInstanceIdentifier'] not in v_readReplica and len(i['ReadReplicaDBInstanceIdentifiers']) == 0:
                arn=i['DBInstanceArn']
                resp2=client.list_tags_for_resource(ResourceName=arn)
#check if the RDS instance is part of the Auto-Shutdown group.
                if 0==len(resp2['TagList']):
                    print('DB Instance {0} is not part of autoshutdown'.format(i['DBInstanceIdentifier']))
                else:
                    for tag in resp2['TagList']:
                        if tag['Key']==key and tag['Value']==value:
                            if i['DBInstanceStatus'] == 'available':
                                print('{0} DB instance is already available'.format(i['DBInstanceIdentifier']))
                            elif i['DBInstanceStatus'] == 'stopped':
                                client.start_db_instance(DBInstanceIdentifier = i['DBInstanceIdentifier'])
                                print('Started DB Instance {0}'.format(i['DBInstanceIdentifier']))
                            elif i['DBInstanceStatus']=='starting':
                                print('DB Instance {0} is already in starting state'.format(i['DBInstanceIdentifier']))
                            elif i['DBInstanceStatus']=='stopping':
                                print('DB Instance {0} is in stopping state. Please wait before starting'.format(i['DBInstanceIdentifier']))
                        elif tag['Key']!=key and tag['Value']!=value:
                            print('DB instance {0} is not part of autoshutdown'.format(i['DBInstanceIdentifier']))
                        elif len(tag['Key']) == 0 or len(tag['Value']) == 0:
                            print('DB Instance {0} is not part of autoShutdown'.format(i['DBInstanceIdentifier']))
            elif i['DBInstanceIdentifier'] in v_readReplica:
                print('DB Instance {0} is a Read Replica.'.format(i['DBInstanceIdentifier']))
            else:
                print('DB Instance {0} has a read replica. Cannot shutdown & start a database with Read Replica'.format(i['DBInstanceIdentifier']))

    response=client.describe_db_clusters()
    for i in response['DBClusters']:
        cluarn=i['DBClusterArn']
        resp2=client.list_tags_for_resource(ResourceName=cluarn)
        if 0==len(resp2['TagList']):
            print('DB Cluster {0} is not part of autoshutdown'.format(i['DBClusterIdentifier']))
        else:
            for tag in resp2['TagList']:
                if tag['Key']==key and tag['Value']==value:
                    if i['Status'] == 'available':
                        print('{0} DB Cluster is already available'.format(i['DBClusterIdentifier']))
                    elif i['Status'] == 'stopped':
                        client.start_db_cluster(DBClusterIdentifier=i['DBClusterIdentifier'])
                        print('Started Cluster {0}'.format(i['DBClusterIdentifier']))
                    elif i['Status']=='starting':
                        print('cluster {0} is already in starting state.'.format(i['DBClusterIdentifier']))
                    elif i['Status']=='stopping':
                        print('cluster {0} is in stopping state. Please wait before starting'.format(i['DBClusterIdentifier']))
                elif tag['Key'] != key and tag['Value'] != value:
                    print('DB Cluster {0} is not part of autoshutdown'.format(i['DBClusterIdentifier']))
                else:
                    print('DB Instance {0} is not part of autoShutdown'.format(i['DBClusterIdentifier']))

def lambda_handler(event, context):
    start_rds_all()

위의 람다 함수는 환경 변수로 전달되는 3개의 매개변수(REGION, KEY, VALUE)가 필요합니다. REGION은 RDS 인스턴스가 현재 실행 중인 위치이고 KEY 및 VALUE는 이전 단계에서 자동 종료가 필요한 인스턴스에 대해 연결한 태그입니다. RDS 인스턴스에 연결한 값은 환경 변수와 정확히 일치해야 합니다. 아래 단계에 따라 입력하십시오.

'구성' 탭으로 이동하여 '환경 변수'를 선택합니다. EDIT를 클릭하고 아래와 같이 환경 변수를 추가합니다.

함수 테스트는 다음 출력을 제공해야 합니다.

두 기능을 모두 생성하고 테스트한 후 필요에 따라 이러한 기능을 트리거하는 EventBridge 규칙을 생성할 수 있습니다.

Amazon EventBridge 규칙 생성

Amazon EventBridge 규칙은 태그가 지정된 데이터베이스를 중지하거나 시작하기 위해 생성한 함수를 트리거합니다. 이 게시물에서는 일정에 따라 트리거하도록 구성합니다.

  1. EventBridge 콘솔의 탐색 창에 있는 Events 에서 Rules 를 선택합니다 . 규칙 만들기 섹션 에서 아래와 같이 규칙의 이름을 정의합니다.
  2. Define Pattern 섹션 에서 Schedule을 선택하고 Cron 표현식을 클릭한 후 0 23 ? * MON-FRI *아래와 같이 Enter를 누르십시오(이 cron 표현식은 월요일부터 금요일까지 GMT 오후 11시에 데이터베이스를 중지합니다).
  3. 이벤트 버스 선택 섹션 에서 AWS 기본 이벤트 버스를 선택하고 선택한 이벤트 버스에서 규칙을 활성화합니다(기본적으로 활성화됨).
  4. 대상 선택 섹션 에서 첫 번째 드롭다운 상자에서 Lambda 함수를 선택합니다 .
  5. Function 에서 생성한 정지 함수( stoprds)를 선택합니다.
  6. 입력한 세부 정보를 검토하고 페이지 하단에서 만들기를 선택하여 규칙을 만듭니다.
    EventBridge 규칙은 이제 stoprds예약된 시간에 Lambda 함수를 트리거합니다.
  7. rdsstart이 단계를 반복하여 원하는 예약 시간에 Lambda 함수를 트리거하는 규칙을 생성합니다 .

요약

이 게시물에서는 사용하지 않을 때 개발 및 테스트 환경에서 RDS 데이터베이스를 중지하고 시작하도록 Lambda 함수를 예약하는 방법을 보여주었습니다. Lambda를 사용하여 RDS DB 인스턴스의 시작 및 종료를 자동화함으로써 조직은 컴퓨팅 비용을 더욱 절감하고 지속적으로 실행할 필요가 없는 데이터베이스 환경의 관리를 간소화할 수 있습니다.

이 솔루션을 시도하고 AWS Lambda  및  Amazon Relational Database Service 사용의 모든 이점을 활용하는 것이 좋습니다 . AWS Systems Manager를 사용하여 Amazon RDS 중지 및 시작을 수행할 수도 있습니다. 블로그 게시물 " AWS Systems Manager를 사용하여 Amazon RDS 중지 및 시작 예약 "을 확인하십시오.

질문이나 요청 사항이 있으면 언제든지 댓글로 문의해 주세요.

 

 

+ Recent posts