검색어 mysql sql file에 대한 글을 관련성을 기준으로 정렬하여 표시합니다. 날짜순 정렬 모든 글 표시
검색어 mysql sql file에 대한 글을 관련성을 기준으로 정렬하여 표시합니다. 날짜순 정렬 모든 글 표시

[컴][db] dbdeployer

 db local test / 테스트 / 테스트 방법 / 구성방법 / 로컬에서 테스트 방법


dbdeployer

dbdeployer 를 이용해서 하나의 host 에서 여러 port 를 이용해서 mysql 를 여러개 띄울 수 있다. 이것을 이용해서 여러 topology 를 테스트 해 볼 수 있다. 이 dbdeployer 의 전신이 MySQL-Sandbox 이다.

환경

  • OS : ubutu 20.04.1 64bit (참고로 dbdeployer 는 windows 용 binary 를 제공하지 않는다.)

시작, dbdeployer init

dbdeployer init 을 하면 directory 들도 만들고, dbdeployer 를 $PATH 에 copy 해 놓게 된다. 그리고 일단 기본적으로 mysql 을 download 해서 SANDBOX_DIRECTORY 에 넣게 된다.

  • ($SANDBOX_BINARY) was created
  • ($SANDBOX_HOME) was created
  • dbdeployer downloads get mysql-8.0.21-linux-glibc2.17-x86_64-minimal.tar.xz
  • dbdeployer unpack mysql-8.0.21-linux-glibc2.17-x86_64-minimal.tar.xz
  • dbdeployer defaults enable-bash-completion --run-it --remote
  • Download of file dbdeployer_completion.sh was successful
na@na-VirtualBox:~/a$ dbdeployer init
SANDBOX_BINARY /home/na/opt/mysql
SANDBOX_HOME   /home/na/sandboxes

--------------------------------------------------------------------------------
Directory /home/na/opt/mysql ($SANDBOX_BINARY) was created
This directory is the destination for expanded tarballs

--------------------------------------------------------------------------------
Directory /home/na/sandboxes ($SANDBOX_HOME) was created
This directory is the destination for deployed sandboxes

--------------------------------------------------------------------------------
# dbdeployer downloads get mysql-8.0.21-linux-glibc2.17-x86_64-minimal.tar.xz
....  48 MB
--------------------------------------------------------------------------------
# dbdeployer unpack mysql-8.0.21-linux-glibc2.17-x86_64-minimal.tar.xz
Unpacking tarball mysql-8.0.21-linux-glibc2.17-x86_64-minimal.tar.xz to $HOME/opt/mysql/8.0.21
Renaming directory /home/na/opt/mysql/mysql-8.0.21-linux-glibc2.17-x86_64-minimal to /home/na/opt/mysql/8.0.21
--------------------------------------------------------------------------------
dbdeployer versions
Basedir: /home/na/opt/mysql
8.0.21
--------------------------------------------------------------------------------
# dbdeployer defaults enable-bash-completion --run-it --remote
  83 kB
Download of file dbdeployer_completion.sh was successful
# completion file: dbdeployer_completion.sh
# Running: sudo cp dbdeployer_completion.sh /etc/bash_completion.d
# File copied to /etc/bash_completion.d/dbdeployer_completion.sh
# Run the command 'source /etc/bash_completion'

main

dpdeployer deploy ... 로 시작하는 명령어들이 main 이다. 이 명령어들은 sandbox-binary directory(보통 $HOME/opt/mysql 로 만들어진다.) 에 있는 db binary 를 이용한다.

  • dpdeployer deploy single
  • dpdeployer deploy replication: replication 관계로 여러 node 를 만든다.
  • dpdeployer deploy multiple: replication 관계가 없는 같은 버전의 sandbox 여러개들을 만든다.
  • 참고로 -h 를 이용하면 자세한 설명을 확인할 수 있다. (ex: dpdeployer deploy multiple -h)

unpack command 를 이용해서 미리 mysql tarball 을 풀어놓으면 된다.(참고로 binary tarball 이다. source tarball 이 아니라.) 만약 다른 db(mariadb) 를 사용하고 싶다면 다른 tarball 을 풀어놓으면 된다. unpack 명령어를 이용하면 되고, unpack 을 하면 /home/na/opt/mysql 에 설치(?) 된다. 대략 저장용량이 3GB 정도 필요하다.

$ wget https://downloads.mariadb.org/interstitial/mariadb-10.4.15/bintar-linux-x86_64/mariadb-10.4.15-linux-x86_64.tar.gz
$  dbdeployer unpack mariadb-10.4.15.tar.gz

mariadb source tarball 은 아래 경로를 가면 된다.

libaio, libnuma

libaio, libnuma 가 필요하다. 설치를 해 놓자.

sudo apt install libaio-dev
sudo apt install libnuma-dev
sudo apt install libtinfo5

deploy replication, deploy multiple, deploy single

deploy replication 은 replication 관계에 있는 node 들을 만들어준다. 다른 방식(multiple, single)도 비슷하게 실행할 수 있다.

10.4.15 version 으로 master-slave 를 만드는 것은 아래처럼 하면 된다. --topology 가 없으면 기본적으로 master-slave 로 만들어준다. (참고: dbdeployer > Replication topologies)

아래처럼 remote-access 를 설정하면, 기본적으로 만들어지는 계정의 값이 바뀐다.(user@%)

참고로, root 의 password 는 기본적으로 msandbox 로 set 된다.

dbdeployer deploy replication 10.4.15 \
--topology=master-slave \
--nodes=3 \
--bind-address=0.0.0.0 \
--remote-access='%'

아래처럼 .sql 을 사용하면, deploy 할 때 .sql 을 실행 한다.

$ dbdeployer deploy --topology=master-slave replication 10.4.15 \
--bind-address=0.0.0.0 \
--remote-access='%' \
--post-grants-sql-file=$PWD/run.sql
-- run.sql
CREATE DATABASE IF NOT EXISTS my_test;
CREATE USER myuser IDENTIFIED BY 'mypassord';
GRANT ALL PRIVILEGES ON my_test.* TO myuser;
GRANT SELECT ON mysql.user TO myuser;
na@na-VirtualBox:~/a$ dbdeployer deploy --topology=master-slave replication 10.4.15
Installing and starting master
. sandbox server started
Installing and starting slave1
. sandbox server started
Installing and starting slave2
. sandbox server started
$HOME/sandboxes/rsandbox_10_4_15/initialize_slaves
initializing slave 1
initializing slave 2
Replication directory installed in $HOME/sandboxes/rsandbox_10_4_15
run 'dbdeployer usage multiple' for basic instructions'

그러면 아래처럼 3개의 process 가 떠있는 것을 확인할 수 있다.

user_na@user_na-VirtualBox:~/a$ ps aux | grep my
user_na        1930  0.0  0.1   2608  1684 pts/0    S    13:50   0:00 /bin/sh bin/mysqld_safe --defaults-file=/home/user_na/sandboxes/rsandbox_10_4_15/master/my.sandbox.cnf
user_na        2042  0.0  8.2 1235432 82544 pts/0   Sl   13:50   0:00 /home/user_na/opt/mysql/10.4.15/bin/mysqld --defaults-file=/home/user_na/sandboxes/rsandbox_10_4_15/master/my.sandbox.cnf --basedir=/home/user_na/opt/mysql/10.4.15 --datadir=/home/user_na/sandboxes/rsandbox_10_4_15/master/data --plugin-dir=/home/user_na/opt/mysql/10.4.15/lib/plugin --log-error=/home/user_na/sandboxes/rsandbox_10_4_15/master/data/msandbox.err --pid-file=/home/user_na/sandboxes/rsandbox_10_4_15/master/data/mysql_sandbox22916.pid --socket=/tmp/mysql_sandbox22916.sock --port=22916
user_na        2164  0.0  0.1   2608  1828 pts/0    S    13:50   0:00 /bin/sh bin/mysqld_safe --defaults-file=/home/user_na/sandboxes/rsandbox_10_4_15/node1/my.sandbox.cnf
user_na        2276  0.1  8.3 1235732 83584 pts/0   Sl   13:50   0:00 /home/user_na/opt/mysql/10.4.15/bin/mysqld --defaults-file=/home/user_na/sandboxes/rsandbox_10_4_15/node1/my.sandbox.cnf --basedir=/home/user_na/opt/mysql/10.4.15 --datadir=/home/user_na/sandboxes/rsandbox_10_4_15/node1/data --plugin-dir=/home/user_na/opt/mysql/10.4.15/lib/plugin --log-error=/home/user_na/sandboxes/rsandbox_10_4_15/node1/data/msandbox.err --pid-file=/home/user_na/sandboxes/rsandbox_10_4_15/node1/data/mysql_sandbox22917.pid --socket=/tmp/mysql_sandbox22917.sock --port=22917
user_na        2384  0.0  0.1   2608  1840 pts/0    S    13:50   0:00 /bin/sh bin/mysqld_safe --defaults-file=/home/user_na/sandboxes/rsandbox_10_4_15/node2/my.sandbox.cnf
user_na        2496  0.1  8.3 1235732 83444 pts/0   Sl   13:50   0:00 /home/user_na/opt/mysql/10.4.15/bin/mysqld --defaults-file=/home/user_na/sandboxes/rsandbox_10_4_15/node2/my.sandbox.cnf --basedir=/home/user_na/opt/mysql/10.4.15 --datadir=/home/user_na/sandboxes/rsandbox_10_4_15/node2/data --plugin-dir=/home/user_na/opt/mysql/10.4.15/lib/plugin --log-error=/home/user_na/sandboxes/rsandbox_10_4_15/node2/data/msandbox.err --pid-file=/home/user_na/sandboxes/rsandbox_10_4_15/node2/data/mysql_sandbox22918.pid --socket=/tmp/mysql_sandbox22918.sock --port=22918
user_na        2557  0.0  0.0  18964   724 pts/0    S+   13:52   0:00 grep --color=auto my

 

client 접근

/home/namh/opt/mysql/10.4.15/bin/mysql -u msandbox --host=127.0.0.1 --port=22918 -p

sandbox 삭제

dbdeployer sandboxes
dbdeployer delete rsandbox_10_4_15
namh@namh-VirtualBox:~/a$ dbdeployer sandboxes
rsandbox_10_4_15 : master-slave 10.4.15 [22916 22917 22918 ]
namh@namh-VirtualBox:~/a$ dbdeployer delete rsandbox_10_4_15
List of deployed sandboxes:
/home/namh/sandboxes/rsandbox_10_4_15
Running /home/namh/sandboxes/rsandbox_10_4_15/send_kill_all destroy
# executing 'send_kill' on /home/namh/sandboxes/rsandbox_10_4_15
executing "send_kill" on slave 1
Terminating the server immediately --- kill -9 5502
executing "send_kill" on slave 2
Terminating the server immediately --- kill -9 5723
executing "send_kill" on master
Terminating the server immediately --- kill -9 5267
Running rm -rf /home/namh/sandboxes/rsandbox_10_4_15
Directory /home/namh/sandboxes/rsandbox_10_4_15 deleted


Reference

  1. Testing Percona XtraDB Cluster 8.0 with DBdeployer - Percona Database Performance Blog

[컴][DB] PostgreSQL 에서 command line 으로 sql 실행하기

파일로 된 sql statement 를 실행하기 / execute the sqls from file on postgresql / cmd tool / mysql command line tool


PostgreSQL 에서 command line 으로 sql 실행하기

file 에 insert sql 이 들어있는 상태이고, 이녀석을 실행해서 db 에 data 를 넣고 싶은 경우에 어떻게 해야 할까?
psql 을 이용하면 된다. windows 에서는 아래 경로에서 찾을 수 있다.
  • ..\PostgreSQL\9.4\bin\psql.exe
  • /usr/bin/psql
psql -U username -d myDataBase -a -f myInsertFile
만약 remote server 에 실행해야 한다면 아래처럼 host 를 지정해 주면 된다.
psql -h 127.0.0.1 -U username -d myDataBase -a -f myInsertFile

encoding

만약 insert file(myInsertfile) 의 encoding 이 console 의 encoding 과 다르지 않은지 확인하자. 기본적으로 client encoding 은 locale setting 을 보고 알아서 setting 한다. 이것을 직접 지정해 주고 싶다면 PGCLIENTENCODING 변수를 이용하면 된다. 자세한 것은 아래 글을 참고하자.

Error 표시

위의 command line 으로 실행하면 error 도 그냥 화면에 보였다가 사라진다. 그래서 error log 만 모으도록 아래처럼 command 를 만들자. 그러면 error 만 나중에 확인할 수 있다.
psql -h 127.0.0.1 -U username -d myDataBase -a -f myInsertFile 2> err.log

pgAdmin III 와의 차이

pgAdmin III 에서는 sql 에 error 가 없는지 확인하고 실행한다. 하지만 이 command line 에서는 바로바로 한줄한줄 실행한다.

See Also

  1. PostgreSQL에서 id sequence reset 하는 방법
  2. pg_hba.conf 설정 ; 여기서 accept 하는 ip address 설정을 해줘야 한다.

mysql command line

mysql -u root -p dbnamel < c:\inquery.sql

Reference

  1. Run a postgresql .sql file using command line args - Stack Overflow
  2. [SOLVED] Finding PostgreSQL installed location

[컴] gradle 에서 flyway 설정하기

migration tool / spring boot / spring migration 툴 / db / database 

gradle 에서 flyway 설정하기

ref. 1 의 방법을 참고하면 된다. 다만 ref. 1 은 구버전의 flyway 에 대한 글이다. 현재 2022-11 기준, 최신버전은 9.7.0 이다.

여기서 이야기하려는 것은 gradle 을 통해서 flyway 를 실행하는 방법이다. 그래서 build.gradle 에 설정해 주면 된다. 다만 몇가지 설정값(pw같은)을 외부로 뺄 것이라서, flyway.conf 파일이 추가로 쓰인다.

디렉토리 구조:

- <proj_root>/
    - conf/
        - flyway.conf
    - build.gradle

build.gradle

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        ...
        // for flyway
        classpath "org.flywaydb:flyway-mysql:9.7.0"
    }
}

plugins {
    id "org.flywaydb.flyway" version "9.7.0"
}

config file 예시

config file 은 아래처럼 작성하면 된다.

# flyway.conf
flyway.url=jdbc:mariadb://127.0.0.1:3306/mydatabase?log=true&sessionVariables=time_zone='+00:00'
flyway.user=testflyway
flyway.password=testflyway
flyway.encoding=UTF-8

config file 을 사용해서 실행하는 방법

<proj>/conf/flyway.conf 를 사용하는 법은 Config file 사용법을 참고하면, 아래처럼 실행을 하면 된다.

아래는 gradlew flywayBaseline 을 실행하는 command 이다.

gradlew -Dflyway.configFiles=./conf/flyway.conf -Dflyway.baselineVersion="1.3.1" -Dflyway.baselineDescription="Existing version of MyDatabase" flywayBaseline

gradlew flywayBaseline -i 를 하면 좀 더 자세한 정보를 확인할 수 있다.

migrate 방법

flywayMigrate 를 하면 다음 경로에 있는 .sql 파일을 실행한다.

  • <proj>/src/main/resources/db/migration/
gradlew -Dflyway.configFiles=./conf/flyway.conf flywayMigrate

예를 들어 <proj>/src/main/resources/db/migration/V1.3.2__testgen.sql 라는 파일이 있는경우, flywayMigrate 를 하면, 아래처럼 history 가 추가되고, sql file 내의 sql statement 가 실행된다.

주의할점

주의할 점은 위의 flywayBaseline 명령을 할 때 flyway.baselineVersion="1.3.1" 를 정했기 때문에, flywayMigrate 는 그 이후부터 실행된다. 그래서 만약 .sql file 의 file 이름이 예를 들어 V1.3.0__testgen.sql 라면, 1.3.1 보다 낮은 버전이기 때문에 실행되지 않는다.

undo

undo 기능은 commecial 에서만 제공한다고 한다.

tip: 그냥 undo 를 하지 말고, 새롭게 버전을 만들고, 그곳에 undo 에서 수행할 내용을 적으면 될 듯 하다.

Gradle Flyway Tasks

See Also

  1. 쿠…sal: [컴] flywayMigrate 를 해서 checksum error 가 나온 경우

Reference

  1. GitHub - iinow/flyway: spring boot + flyway

[컴] RDS 를 local 로 backup

 

복구/ restore / mysql / mysqldump /download /aws database / 백업 /

RDS 를 local 로 backup

여기서는 MySQL 의 경우를 이야기한다.

dump

mysqldump --databases mydb -h myaws-stage-ap-northeast-2c.cru36jme3bsz.ap-northeast-2.rds.amazonaws.com -u myuserid -P 3306 -p > rds-mydb.sql

특정 table 만 dump 를 만들려고 할 때

// mydb.my_table 에 대한 dump 를 뜨려고 한다.
mysqldump -uroot -p -hlocalhost --single-transaction mydb my_table > output.sql

여러 table 을 dump 할 때,

// mydb.table1 mydb.table2 mydb.table3 에 대한 dump
mysqldump.exe -h localhost -u userid -P 7307 -p --single-transaction mydb table1 table2 table3 > mydb-%date%.sql

특정 table 만 제외하고 dump 를 만들려고 할 때

// mydb.my_table 에 대한 dump 를 뜨려고 한다.
mysqldump -uroot -p -hlocalhost --single-transaction --ignore-table=mydb.my_ignore_table mydb > output.sql

insert

mysql -u root -p dbnamel < c:\inquery.sql

S3

ref. 2 를 보면, s3 로 backup 내용을 copy 하고, download 할 수 있는 듯 하다. ref. 2 를 참고하자.

See Also

  1. 쿠...sal: mysql sql file에 대한 검색결과

Reference

  1. How to create a local backup of a remote Amazon RDS MySQL database? - Server Fault
  2. mysql - download RDS snapshot - Stack Overflow

[컴] 하둡, Hive-Metastore

Hive-Metastore

우리가 DB 에서 table 의 정보나, relation 등을 쉽게 하려면 관련정보를 어딘가에 가지고 있어야 한다. HIVE 도 database 이기에 table이나 relation 관련 정보를 어딘가에 저장해 놓을 필요가 있다. 그것을 해주는 것이 Hive-Metastore 라고 보면 된다.

Metastore 에는 database 가 우리의 DB, tables, relation들의 모든 정보들을 Metadata 로 가지고 있다. Hive-Metastore system 도 마찬가지다. table 와 relation 들에 대한 모든 정보를 이 Hive-Metastore system 이 가지고 있다.

  • 모든 Hive 구현체들은 metastore service 들이 필요하다. 이 metastore service 에 metadata 를 저장한다.
  • metastore service는 rdb 의 table 을 사용해서 구현된다.
  • 기본적으로 , Hive는 built-in Derby SQL server 를 사용한다. Derby는 single process storage 를 제공한다. 그래서 Derby 를 사용하면, Hive CLI instance 들을 실행할 수 없다.
  • Hive 를 개인적으로 실행하거나 개발용으로 사용할 때는 Derby 로 좋지만, 이것을 cluster에서 사용하길 원할 때는 MySQL 또는 다른 RDBMS 가 필요하다.
  • hive-site.xml 에서 metastore 위치를 지정할 수 있다.

mysql 사용을 위한 hive-site.xml

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
 
    <property>
        <name>javax.jdo.option.ConnectionURL</name>
        <value>jdbc:mysql://localhost/metastore</value>
        <description>metadata is stored in a MySQL server</description>
    </property>
     
    <property>
        <name>javax.jdo.option.ConnectionURL.metastore_spark_plsql</name>
        <value>jdbc:mysql://localhost/metastore_sql</value>
        <description>user metadata is stored in a MySQL server</description>
    </property>
     
    <property>
        <name>javax.jdo.option.ConnectionDriverName</name>
        <value>com.mysql.jdbc.Driver</value>
        <description>MySQL JDBC driver class</description>
    </property>
     
    <property>
        <name>javax.jdo.option.ConnectionUserName</name>
        <value>user</value>
        <description>user name for connecting to mysql server </description>
    </property>
     
    <property>
        <name>javax.jdo.option.ConnectionPassword</name>
        <value>password</value>
        <description>password for connecting to mysql server </description>
    </property>
     
</configuration>

hive-site.xml

hive-default.xml.template에는 다양한 설정에 대한 기본값이 포함되어 있다. Hive 배포에 미리 패키징된 이 기본값을 재정의하려면 hive-site.xml을 만들고 설정하자.

hive-default.xml.directory<hive_root>/conf 디렉토리에 있으며 hive-site.xml도 같은 디렉토리에 있어야 한다.

템플릿 파일 hive-default.xml.template은 (Hive 0.9.0 기준) Hive에서 전혀 사용되지 않는다. configuration 옵션의 표준 목록(canonical list)은 HiveConf Java class에서만 관리된다.(HiveConf) 템플릿 파일에는 hive-site.xml에 필요한 형식(formatting)이 있으므로 템플릿 파일의 configuration 변수를 hive-site.xml에 붙여넣은 다음 원하는 configuration으로 값을 변경하면 된다.

Hive 0.9.0 ~ 0.13.1은 HiveConf.java 에서 직접생성되지 않아서 일부 configuration 값들이 없을 수도 있고, 기본값과 설명도 최신이 아닐 수 있다. 그러나 Hive 0.14.0부터는 템플릿 파일이 HiveConf.java에서 직접 생성되므로 configuration 변수 및 기본값에 대한 신뢰할 수 있는 원본이다.

administrative configuration 변수는 다음과 같다. 사용자 변수는 Hive configuration 속성에 나열됩니다. Hive 0.14.0부터는 SHOW CONF 명령을 사용하여 configuration 변수에 대한 정보를 표시할 수 있습니다.

hive-default.xml.template contains the default values for various configuration variables that come prepackaged in a Hive distribution. In order to override any of the values, create hive-site.xml instead and set the value in that file as shown above.

hive-default.xml.template is located in the conf directory in your installation root, and hive-site.xml should also be created in the same directory.

Please note that the template file hive-default.xml.template is not used by Hive at all (as of Hive 0.9.0) – the canonical list of configuration options is only managed in the HiveConf java class. The template file has the formatting needed for hive-site.xml, so you can paste configuration variables from the template file into hive-site.xml and then change their values to the desired configuration.

In Hive releases 0.9.0 through 0.13.1, the template file does not necessarily contain all configuration options found in HiveConf.java and some of its values and descriptions might be out of date or out of sync with the actual values and descriptions. However, as of Hive 0.14.0 the template file is generated directly from HiveConf.java and therefore it is a reliable source for configuration variables and their defaults.

The administrative configuration variables are listed below. User variables are listed in Hive Configuration Properties. As of Hive 0.14.0 you can display information about a configuration variable with the SHOW CONF command.

See Also

  1. AdminManual Configuration - Apache Hive - Apache Software Foundation : hive-site.xml 관련 설정 값들에 대한 설명

Reference

  1. Hive-Metastore: A Basic Introduction - DZone Database

[컴][웹] MyBatis-Spring 사용법

MyBatis 세팅

Spring 에서 MyBatis setting 을 하는데 자꾸 error 가 난다. 계속 급한 마음에 설정을 제대로 잡을 수 없어서 오늘은 제대로 MyBatis 를 알아보려 한다.

여기 나온글은 대부분 아래 글에서 나온 이야기가 될 것이다.

MyBatis-Spring

MyBatis 를 Spring 에서 사용할 수 있도록 해주는 것이 MyBatis-Spring 이다. 이 MyBatis-Spring 을 이용하면,

  1. MyBatis mapper 들과 SqlSession 들을 다루고,
  2. 다른 bean 에 inject 해서 다른 bean 에서 사용할 수 있도록 도와주고,
  3. MyBatis 의 Exception 들에 대해서 Spring 의 DataAccessException 을 발생시켜 준다.

원래 MyBatis 는 xml 을 가지고 sql 을 작성하고 그것을 code 에서 사용할 수 있도록 되어 있다. 그런데 이 것을 spring 에서 사용할 수 있도록 (bean 을 만들고, inject 를 하는 등의)wrapper 를 만든 것이 MyBatis-Spring 이라고 할 수 있겠다.

MyBatis 가 어떤 모습을 띠는 지는 아래 페이지를 참고하자.

Installation

Spring 에서 MyBatis 를 이용할 수 있도록 해주는 것이 MyBatis-Spring 인데, 이 녀석을 사용하려면 아래와 같은 녀석들이 있어야 한다.

  • Java 5 이상
  • MyBatis-Spring 의 버전과 맞는 버전의 MyBatis 와 Spring

버전에 대한 정보는 아래 페이지를 참고하자.

이것을 Spring 에서 사용한다면, project 의 build path 에 아래의 library 들을 추가해야 한다.

  • mybatis-spring-x.x.x.jar
  • mybatis-spring 과 dependency 가 있는 library

이것은 개인적으로 번거럽다. maven 설정은 dependency 와 관련된 library 를 해결해 줘서 좋다.

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>1.1.1</version>
</dependency>

그런데 아래에서 mapper 를 자동으로 scan 하기 위해서 mybatis-spring 1.2.2 를 사용했는데, 이때는 maven 에서 자동으로 mybatis 를 가져오지 못하는 듯 했다. 그래서 아래처럼 mybatis 설정으로 추가해 줬다.

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.2.7</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.2.2</version>
</dependency>

Spring 에서 MyBatis 사용하기

MyBatis 를 Spring 에서 사용하기 위해서는 Spring Application Context 에 2가지를 정의해야 한다.

  • SqlSessionFactory
  • 1개 이상의 mapper interface

SqlSessionFactory 생성

이것을 MyBatis-Spring 에서는 SqlSessionFactoryBean 을 이용해서 SqlSessionFactory 를 만들 수 있다. 이녀석은 Spring 설정 file 인 .xml 에서 정의해 주면 된다.

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

위에서 보다시피 dataSource 가 필요하다. 이 dataSource 는 Spring 에서 사용하던 DataSource 를 이야기 한다. 그리고 원래 Spring 에서 DataSource 를 사용하기 위해서 하는 설정은 해줘야 한다. 예를 들어 내가 사용하던 dataSource 는 아래와 같은 모습이다.

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.springframework.jdbc.datasource.DriverManagerDataSource" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

1개 이상의 mapper interface

아래와 같은 mapper 를 가지고 있다고 해보자.

public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{userId}")
  User getUser(@Param("userId") String userId);
} 

이녀석을 사용하려면 MapperFactoryBean 을 사용하면 된다.

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="my.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

scanning mapper

이 방법대신에 자동으로 mapper 를 scan 할 수 있다. 위의 MapperFactoryBean 설정대신에 간단히 아래 설정을 해주면 된다.

<mybatis:scan/> 와 @MapperScan 은 MyBatis-Spring 1.2.0. 부터 가능하고 @MapperScan 을 사용하려면 Spring 3.1+ 을 사용해야 한다. (참고)

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"

    xsi:schemaLocation="http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://mybatis.org/schema/mybatis-spring
     http://mybatis.org/schema/mybatis-spring.xsd
     ">
<mybatis:scan base-package="my.sample.mapper" />

mapper class

위의 code 에서 보다시피 Mapper class 는 반드시 interface 여야 한다. 그리고 sql 문은 annotation 으로 사용하면 된다. 이것 대신에 MyBatis 의 xml 방법을 사용해도 된다.

위의 <bean> 설정으로 만들어진 bean 은 Spring 의 다른 bean 들 처럼 inject 할 수 있다. MapperFactoryBean 은 SqlSession 을 만드는 것과 closing 을 처리 해 준다. Spring transaction 이 진행중이면, SqlSession 은 transaction 이 끝났을 때 commit 을 해주거나 roll back 을 해준다.

주의할 점

내가 이전에 설정에서 계속 실패했던 이유는 MyBatis 와 MyBatis-Spring 을 같은 것이라고 여겼기 때문이다. 그래서 MyBatis 설정 + MyBatis-Spring 설정이 함께 들어가서 그것들이 엉킨듯 하다. 위에서 이야기한 대로 설정을 다시 잡아줬더니 잘 동작한다.

code examples

dynamic sql 에서 아래처럼 여러줄을 사용해서 사용하려면, <script> 를 사용하면 된다.

@Select({"<script>",
         "SELECT *", 
         "FROM blog",
         "WHERE id IN", 
           "<foreach item='item' index='index' collection='list'",
             "open='(' separator=',' close=')'>",
             "#{item}",
           "</foreach>",
         "</script>"}) 
List<Blog> selectBlogs(@Param("list") int[] ids);

kotlin 으로 작성한 mybatis java api 예제

package com.mycom.repository.product2

import org.apache.ibatis.annotations.Insert
import org.apache.ibatis.annotations.Param
import org.apache.ibatis.annotations.Select
import org.apache.ibatis.annotations.SelectProvider
import org.apache.ibatis.builder.annotation.ProviderMethodResolver
import org.apache.ibatis.jdbc.SQL
import org.springframework.stereotype.Repository
import com.mycom.model.Customer


@Repository
interface PurchaseDataSyncRepository {

    // java api
    // @ref https://mybatis.org/mybatis-3/java-api.html
    //
    // type-handler
    // `mybatis.type-handlers-package=com.mycom.converter.mybatis,young.common.converter.mybatis`
    // @see application.properties
    @Select("SELECT id FROM customer AS t1 ORDER BY t1.id DESC LIMIT 1")
    fun getTheLastCustomerId(): Long?

    @Insert(
        "INSERT INTO customer ",
        "(id,name,business_no,",
        "updated_at,created_at)",
        "VALUES (",
        "#{customer.id}, #{customer.name}, #{customer.businessNo}, ",
        "#{customer.updatedAt}, #{customer.createdAt}",
        ")",
    )
    fun insertCustomer(
        @Param("customer") customer: Customer,
    ): Int?


    // @ref: https://mybatis.org/mybatis-3/java-api.html
    @SelectProvider(
        type = UserSqls::class, method = "getUserIdsFromEmailsQuery"
    )
    fun getUserIdsFromEmails(@Param("emails") emails: List<String>): List<HashMap<String, Long>>?


}

class UserSqls {

    // @ref: https://mybatis.org/mybatis-3/statement-builders.html
    fun getUserIdsFromEmailsQuery(emails: List<String>): String {

        // ["myuser1@mycom.com","myuser2@mycom.com",...]
        // --> ('myuser1@mycom.com','myuser2@mycom.com',...)
        val emailsCond = emails.joinToString(
            prefix = "('",
            separator = "','",
            postfix = "')",
        )
        return object : SQL() {
            init {
                SELECT("t1.email, t1.id")
                FROM("user AS t1")
                WHERE("t1.email IN $emailsCond")
            }
        }.toString()
    }

}

상속(inheretance)이용

아래처럼 SQL 을 상속(extend) 해서 사용할 수 있다.

///
package young.pa.persistence.repository.product2.querybuilder

import org.apache.ibatis.jdbc.SQL

open class MySql : SQL() {
    // your custom functions or overrides here
    fun buildSelect(): SQL {
        return this.SELECT("t1.email, t1.id")
    }
}

///

class UserSqls {

    fun getUserIdsFromEmailsQuery(emails: List<String>): String {

        val emailsCond = emails.joinToString(
            prefix = "('",
            separator = "','",
            postfix = "')",
        )
        return object : MySql() {
            init {
                buildSelect()
                FROM("user AS t1")
                WHERE("t1.email IN $emailsCond")
            }
        }.toString()
    }

}

Source code

위에서 설명한 내용을 직접 적용한 소스는 아래에서 다운로드 할 수 있다.

[컴][db] MariaDB 에서 Spider engine 사용하기

 

Spider with MariaDB

Spider Engine 은 partitioning 과 XA transactions 지원한다. 그리고 다른 MariaDB instanc들의 table 들을 하나의 instance 에 있는 table 처럼 다룰 수 있게 해준다. Spider node 는 MariaDB 서버이다. 그리고 이 서버에서 application 에서 오는 query 를 수신하게 된다. 그리고 실제 data 가 있는 data node에 접속해서 이 query 를 처리하게 된다. data node 도 data 를 가진 mariaDB instance 이다.


간단정리

  1. dbdeployer 로 instance 를 여러개 만든다. : 이러면 같은 host 에 port 가 다르게 해서 여러 instance 가 실행된다.
  2. data-node
    1. table 을 만든다.
    2. spider 계정을 만들어서 접근 권한을 준다.
  3. spider-node
    1. Spider Engine 을 사용할 수 있게 설정한다.
    2. create server 명령어로 data-node 정보를 생성한다.
    3. Spider engine 으로 data-node 에서 만든 table 과 똑같은 table 을 만들고, data-node 와 연결을 한다.


지원하는 version

아래는 stable 만 적어놨다. full list 는 ref.1 을 참고하자.

Spider Version Introduced Maturity
Spider 3.3.14 MariaDB 10.4.3, MariaDB 10.3.13 Stable
Spider 3.3.13 MariaDB 10.3.7 Stable

replication

10.4.5 부터는 아래 configuration 이 기본으로 들어가 있다.

slave_transaction_retry_errors="1158,1159,1160,1161,1429,2013,12701"

이것은 해당 error 가 발생하면, 자동으로 retry 를 하도록 하는 것이다.

10.3.4 에서 만약에 replication 을 위해서 spider 를 사용하는 경우라면 추가해야 한다.

  • 1158: Got an error reading communication packets
  • 1159: Got timeout reading communication packets
  • 1160: Got an error writing communication packets
  • 1161: Got timeout writing communication packets
  • 1429: Unable to connect to foreign data source
  • 2013: Lost connection to MySQL server during query
  • 12701: Remote MySQL server has gone away

dbdeployer

dbdeployer 를 이용해서 하나의 host 에서 여러 port 를 이용해서 mysql 를 여러개 띄울 수 있다. 이것을 이용해서 여러 topology 를 테스트 해 볼 수 있다. 이 dbdeployer 의 전신이 MySQL-Sandbox 이다. 여기서는 이 dbdeployer 를 이용해서 local 에서 mysql 을 구성하고 test 할 것이다.

instance 접근 방법

기본적으로 dbdeployer 를 instance 를 만들면 몇개의 계정이 기본으로 만들어진다.

아래처럼 접근하면 된다. 기본적으로 dbdeployer 의 root password 는 msandbox 로 되어있다.

user_na@user_na-VirtualBox:~$ /home/user_na/opt/mysql/10.4.15/bin/mysql -u root -p --host=127.0.0.1 --port=22918 

설치

data node 에서는 spider node 에서 부터 query 를 받아오게 설정해야 하고, spider node 에서는 remote storage 로 data node 를 사용한다고 설정해야 한다.

data node, spider node 모두의 설정을 건드려야 한다.

+-------------+
| spider node |
|             |
+----+--------+
     |
     |
     |
+----+----+
|         |
|data node|
|         |
+---------+

data node 설정

  1. table 생성
  2. spider@<spider_address> user 계정 생성
  3. 생성한 계정에 해당 table 에 대한 접근 권한 주기(GRANT)
CREATE TABLE test_table.spider_example (
   id INT PRIMARY KEY AUTO_INCREMENT,
   name VARCHAR(50)
) ENGINE=InnoDB;

CREATE USER spider@127.0.0.1;
SET PASSWORD FOR spider@127.0.0.1 = PASSWORD('spiderpw');
GRANT ALL ON test_table.spider_example TO spider@127.0.0.1;

spider node 설정

  1. sudo apt install mariadb-plugin-spider
  2. /home/user_na/opt/mysql/10.4.15/bin/mysql -u root --port=22916 --host=127.0.0.1 -p < /usr/share/mysql/install_spider.sql
  3. Spider engine 을 사용하는 table 생성
  4. create server

spider node 도 mariaDB 이다. 다만 table 이 Spider engine 으로 만들어져 있다.

먼저 spider 로 사용하기로 한 mariaDB 가 Spider engine 을 지원하는지 확인해야 한다. 아래 query 를 실행해서 ENGINE 에 SPIDER 가 보이지 않으면, 지원하지 않는 것이다.

SELECT ENGINE, SUPPORT FROM information_schema.ENGINES;

지원하지 않으면 mariadb-plugin-spider 를 설치한다. 그러면 /usr/share/mysql/install_spider.sql 가 생성되는데, 이 녀석을 실행해 주면 된다. 그러면 이 script 가 몇가지 table 을 자동으로 생성하게 된다.(아래 참조) 자세한 내용은 직접 file 을 확인하자.

  • spider_xa
  • spider_xa_member
  • spider_xa_failed_log
  • spider_tables
  • spider_link_mon_servers
  • spider_link_failed_log
  • spider_table_position_for_recovery
  • spider_table_sts
  • spider_table_crd

create server

각 data node 에 대해서 CREATE SERVER ... 를 해줘야 한다.(참고 : https://mariadb.com/kb/en/create-server/)

CREATE SERVER dataNode1 FOREIGN DATA WRAPPER mysql
OPTIONS (
   -- data node information
   HOST '127.0.0.1',
   DATABASE 'test',
   USER 'spider',
   PASSWORD 'spiderpw',
   PORT 22917);
FLUSH TABLES;

-- 삭제시
DROP SERVER dataNode1

create table

이제 spider 에도 data node 와 똑같은 모양의 table 을 생성하자. 차이는 단지 사용하는 ENGINE 만 다를 뿐이다.

CREATE TABLE test.spider_example (
   id INT PRIMARY KEY AUTO_INCREMENT,
   name VARCHAR(50)
) ENGINE=Spider
COMMENT='wrapper "mysql", srv "dataNode1", table "spider_example"';

See Also

  1. PowerPoint 프레젠테이션 - noc_tr2_se9_choi.pdf
  2. Spider Differences Between SpiderForMySQL and MariaDB - MariaDB Knowledge Base

Reference

  1. Spider - MariaDB Knowledge Base
  2. dbdeployer | DBdeployer is a tool that deploys MySQL database servers easily.
  3. Testing Percona XtraDB Cluster 8.0 with DBdeployer - Percona Database Performance Blog

[컴] espanso, text 확장기

copilot / 코딩 / suggest / candidate / 예상 / text expander

espanso, text 확장기

설치

설명

이것은 bash 의 alias 와 비슷하다. 다만 차이점은 bash 의 alias 는 bash 내에서만 된다. 즉, command line 에서만 쓰인다.

하지만 이것은 gui 에서 쓰인다. 그래서 windows 내의 모든 application 안에 있는 typing 영역에서 사용할 수 있다. (예를 들면, 콘솔창, 텍스트 에디터, 웹브라우저의 주소창등.)

그래서 typing 을 줄여줄 수 있다. coding 에서 쓰는 snippet 과 같은 동작도 지원해주고, shell 에서 실행한 결과를 text 로 가져오고 싶다면 그런 것도 지정해 놓을 수 있다.

base.yml 에 자신이 원하는 command 를 추가해 놓을 수 있다.

  • %appdata%\espanso\match\base.yml

community

package

espanso 는 package를 제공한다.

설치된 package list 를 보여준다

espansod.exe package list

예시

아래처럼 해놓으면, :s2 를 typing 할 때마다, console 에서 dir 해서 나온 결과값이 화면에 typing 이 된다.

matches:
  ...
  - trigger: ":s2"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "dir"

  - trigger: ":ip"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "curl https://api.ipify.org"
          shell: cmd

toggle key

default.yml 에 가서 toogle_key 를 설정하면 된다.

  • %appdata%\espanso\config\default.yml

만약 ‘ALT’ 로 설정했다면, ’ALT’key 를 두번 누르는 경우 espanso 가 동작하지 않게 된다.

search bar key

default.yml 에 가서 search_shortcut 를 설정하면 된다.

search_shortcut: ALT+SHIFT+SPACE

특정 app 에서 disable

특정 app 이 실행될때 espanso 를 disable 시킬 수 도 있다.

include, exclude 규칙

_mymatch.yml 처럼 앞에 _를 붙이면, espanso 가 자동으로 load 하지 않는다.

debugging

log 보기

문제가 발생할 때 log 를 보려면 다음처럼 하면 된다.

espansod.exe log

debug: true

  - trigger: ":localip"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "ip a | grep 'inet 192' | awk '{ print $2 }'"
          debug: true

windows 에서 주의할 점

만약 espanso 를 일반 사용자 계정으로 실행한다면, ‘관리자’(administrator) 계정으로 실행한 app 안에서는 동작하지 않는다.

example

  • %appdata%.yml
# espanso match file
matches:

  - trigger: ":ip"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "curl https://api.ipify.org"
          shell: cmd

  - trigger: ":dbdump"
    replace: "mysqldump --databases mydb -h locahost -u myuserid -P 3306 -p > rds-mydb.sql"
  - trigger: ":dbinsert"
    replace: "chcp 65001 && \"mysql.exe\" -u root -p mydb < rds-mydb.sql"

  - trigger: ":cutf"
    replace: "chcp 65001"

  # markdown
  - trigger: ":img"
    replace: "![]('{{clipboard}}')"
    vars:
      - name: "clipboard"
        type: "clipboard"
  # markdown
  - trigger: ":ref"
    replace: "## References\n"

  # run an app
  - trigger: ":espanso-config"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "c:\\Program Files\\Sublime Text 3\\sublime_text.exe"
          shell: cmd

  - trigger: ":run-sublimetext"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "c:\\Program Files\\Sublime Text 3\\sublime_text.exe"
          shell: cmd

  - trigger: ":run-lnk"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "c:\\ProgramData\\Microsoft\\Windows\\Start Menu\\Programs\\7-Zip\\7-Zip File Manager.lnk"
          shell: cmd
  # message 를 받는 form 을 보여준다.
  - trigger: ":git-commit"
    form: |
      'git commit -m "[[message]]"'
  # cmd /v 가 필요
  - trigger: ":git-bcopy"
    replace: |
              for /f "delims=" %a in ('git --no-pager branch ^| d:\a\apps\fzf\fzf.exe +m') do @set branch=%a && echo !branch:~2,-1! | clip
  - trigger: ":git-cbranch"
    replace: |
              for /f "delims=" %a in ('git --no-pager branch ^| d:\a\apps\fzf\fzf.exe +m') do @set branch=%a && git checkout !branch:~2,-1!

  # 여러파일중 하나를 선택하는 예시
  - trigger: ":file"
    replace: "{{form1.file}}"
    vars:
      - name: files
        type: shell
        params:
          cmd: "dir /b ."
          shell: cmd
      - name: form1
        type: form
        params:
          layout: |
            Select file:
            [[file]]
          fields:
            file:
              type: list
              values: "{{files}}"
  # And much more! For more information, visit the docs: https://espanso.org/docs/

See Also

  1. 쿠…sal: [컴][유틸] fzf - fuzzy finder
  2. Speed up git commands with Autohotkey - DEV Community : autohotkey 로 espanso 와 비슷한 효과를 낼 수 있다. 
  3. 쿠...sal: [컴] espanso, text 확장기, 내설정
  4. Hotkeys & Text Expander | 500+ Productivity Resources : 다른 extender 들을 확인할 수 있다.

[컴] espanso, text 확장기, 내설정

 

copilot / 코딩 / suggest / candidate / 예상 / text expander

espanso, text 확장기, 내설정

  • path: %appdata%\espanso\match\base.yml
# espanso match file

# For a complete introduction, visit the official docs at: https://espanso.org/docs/

# You can use this file to define the base matches (aka snippets)
# that will be available in every application when using espanso.

# Matches are substitution rules: when you type the "trigger" string
# it gets replaced by the "replace" string.
matches:
  # NOTE: espanso uses YAML to define matches, so pay attention to the indentation!

  # But matches can also be dynamic:

  # Print the current date
  - trigger: ":now"
    replace: "{{mydate}}"
    vars:
      - name: mydate
        type: date
        params:
          format: "%Y-%m-%d %H:%M"

  # Print the output of a shell command
  - trigger: ":shell"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "echo 'Hello from your shell'"

  # Print the output of a shell command
  - trigger: ":s2"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "dir"

  - trigger: ":ip"
    replace: "{{output}}"
    vars:
      - name: output
        type: shell
        params:
          cmd: "curl https://api.ipify.org"
          shell: cmd

  - trigger: ":dq"
    replace: "\\x22"

  - trigger: ":dbdump"
    replace: "mysqldump.exe -h localhost -u userid -P 7307 -p --single-transaction mydb table1 table2 table3 > mydb-1212.sql"
  - trigger: ":db_realdb_snapshot"
    replace: "-- created_at은 UTC\n\x22d:\\a\\appss\\MariaDB 10.5\\bin\\mysqldump.exe\x22 -h localhost -u foodpang -P 7317 -p --single-transaction foodpang snapshot_product --where=\x22created_at >= '2023-02-20' AND created_at < '2023-02-21'\x22 --no-create-info > snapshot-20230220.sql"
  - trigger: ":db_testdb_insert"
    replace: "chcp 65001\n\x22d:\\a\\appss\\MariaDB 10.5\\bin\\mysql.exe\x22 -u foodpang -P 7307 -p foodpang < snapshot-20230220.sql"
  - trigger: ":db_delete_all"
    replace: "DELETE FROM product_purchase where id > 0;\nALTER TABLE product_purchase AUTO_INCREMENT=1;"

  - trigger: ":dbinsert"
    replace: "chcp 65001 && \"d:\\a\\appss\\MariaDB 10.5\\bin\\mysql.exe\" -u root -P 3306 -p foodpang_analysis < d:\\a\\prog\\foodpang\\purchase\\rds-mydb.sql"
  - trigger: ":dbtimezone"
    replace: "SET @original_time_zone = @@session.time_zone;SET time_zone = 'Asia/Seoul';"

  - trigger: ":cutf"
    replace: "chcp 65001"

  - trigger: ":git-flow-feature"
    replace: "git flow feature"
  - trigger: ":git-undo-commit-to-stage"
    replace: "git reset --soft HEAD~1"
  - trigger: ":git-checkout"
    replace: "git checkout"
  - trigger: ":git-commit"
    form: |
      'git commit -m "[[message]]"'
  - trigger: ":git-branch-name-copy"
    replace: |
              for /f "delims=" %a in ('git --no-pager branch ^| d:\a\apps\fzf\fzf.exe +m') do @set branch=%a && echo !branch:~2,-1! | clip
  - trigger: ":git-checkout-branch"
    replace: |
              for /f "delims=" %a in ('git --no-pager branch ^| d:\a\apps\fzf\fzf.exe +m') do @set branch=%a && git checkout !branch:~2,-1!
  - trigger: ":git-delete-branch"
    replace: |
              echo need cmd /v && for /f "delims=" %a in ('git --no-pager branch ^| d:\a\apps\fzf\fzf.exe +m') do @set branch=%a && git branch !branch:~2,-1! -d
  - trigger: ":git-cbranch2"
    replace: |
              "d:\a\appss\TortoiseGit\bin\TortoiseGitProc.exe" /command:switch
  # markdown
  - trigger: ":img"
    replace: "![]('{{clipboard}}')"
    vars:
      - name: "clipboard"
        type: "clipboard"
  # markdown
  - trigger: ":ref"
    replace: "## References\n"


  # And much more! For more information, visit the docs: https://espanso.org/docs/

[컴][php] lumen 에서 phpunit




lumen 에서 phpunit

실행

test 는 <proj>/vendor/bin/phpunit.bat 를 실행하면 된다.


phpunit 에서 server 주소나 port 를 변경

  • Laravel\Lumen\Testing\TestCase
에 protected $baseUrl 변수가 있는데, 이것을 TestCase.php 에서 상속받아서 수정해주면 된다.




vscode 에서 phpunit 실행

vscode 에서 phpunit 실행을 위해서는 extension 을 설치하고, workspace setting 을 수정해주면 된다.


debugger

vscode 에서 debugger 를 켠채로 phpunit (extension)을 실행하면 debugging 이 가능하다



Auth

lumen 에서는 아래 파일에 자신의 model 을 넣어줘야 한다.
  • .\database\factories\ModelFactory.php
아래코드에서 $faker 를 사용해서 넣을 수 있는 값들을 넣으면 된다. Generator source 를 보면 가능한 값들이 무엇인지 알 수 있다.


$factory->define(App\Member::class, function (Faker\Generator $faker) {
    return [
        'mb_id' => $faker->email,
        'mb_name' => $faker->name,
         // Any other Fields in your Comments Model
    ];
});


DBUnit

composer 로 설치할 때 phpunit 의 버전이 맞지 않으면 설치가 안된다. (맞으면 상관없다.) 만약 맞지 않으면 현재 설치된 phpunit 을 지우고 그냥 dbunit 을 바로 설치하면 된다.
  • composer require --dev phpunit/dbunit

메모리에 SQLite 으로 임시 DB 를 만들어서 DB 관련 test 하기


sqlite 을 사용하기

extension 활성화

php.ini 에서 pdo 관련 dll(또는 so) 를 enable 해야 한다.

phpunit.xml

phpunit.xml 에서 DB_CONNECTION 을 변경해주면 된다. 당연히 database.php 의 connections 에 있는 값을 넣어줘야 한다.
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
...>
    ...
    <php>
        <env name="APP_ENV" value="testing"/>
        ...
        <env name="DB_CONNECTION" value="sqlite" />
    </php>
</phpunit>





MySQL data --> Sqlite



Testing




phpunit examples

주의할점은 parent::setUp() 을 꼭 써주자.

class ItemRepayCalendarControllerTest extends TestCase
{
    protected $user;

    public function setUp()
    {
        parent::setUp();
        $this->user = factory('App\G5Member')->create();
    }

    /**
     * A basic functional test example.
     *b/api/v1/add-p2p-corp?name=헤라펀딩
     * @return void
     */
    public function testAddP2pCorp()
    {
        // /b/api/v1/calendar/add-p2p-corp?name='.urlencode('헤라펀딩')
        $this->actingAs($this->user)
            ->post('/b/api/v1/calendar/add-p2p-corp', ['name'=>'헤라펀딩'])
             ->seeJson([
                 "status" => "success",
             ]);
    }



dbunit example

class RepayPartlyManagerTest extends TestCase
{
    use TestCaseTrait;
    /**
     * @return PHPUnit_Extensions_Database_DB_IDatabaseConnection
     */
    public function getConnection()
    {
        $id = 'tail';
        $pw = 'tail';
        $pdo = new PDO('mysql:...', $id, $pw);
        $database = 'tail';
        return $this->createDefaultDBConnection($pdo, 'tail');
    }

    /**
     * @return PHPUnit_Extensions_Database_DataSet_IDataSet
     */
    public function getDataSet()
    {
        return $this->createMySQLXMLDataSet(__DIR__.'/_files/dataset.xml');
    }


    public function testGetListOfUsersRepaymentDistribution(){


        $conn = $this->getConnection();
        $datas = $this->getDataSet();
        ...
    }
}


mockup

testItemMoreInterest() 에서는 테스트하려는 method 안의 특정 method 에 대한 mockup 을 만드는 법을 확인할 수 있다.
<?
class RepayManagerTest extends TestCase
{
    ...
    public function testGetListOfUsersRepaymentDistribution(){

        $stubMgrData = $this->createMock(RepayManagerData::class);
        
        // Configure the stub.
        $stubMgrData->method('getUsersInvestedAmount')
            ->willReturn([(object)['rest_amount' => 1000, 'invest_amount_all' => 10000
                                    , 'return_amount_all' => 10000,
                                    'member_idx' => 10000,
                                    'invest_idx' => 10000,
                                    'dstMemGuid' => 'test-guid-1', 'title' => 'test-title'],]);
        $stubMgrData->method('getAdminDeposit')
            ->willReturn(15000);
        $stubMgrData->method('insertIntoPartlyDB');
        ...
    }

    public function testItemMoreInterest()
    {
        // mock inside function

        $mockedObject = $this->getMockBuilder(ItemMoreInterest::class)
            ->setConstructorArgs(['first_arg'])
            ->setMethods(['innerCalledFunc'])  // innerCalledFunc 는 override 가 가능해야 한다.
            ->getMock();
        // https://phpunit.de/manual/6.5/en/test-doubles.html#test-doubles.mock-objects.tables.matchers
        $mockedObject->expects($this->once())
            ->method("innerCalledFunc")
            ->with(
                // parameter
                // https://phpunit.readthedocs.io/en/9.5/assertions.html?highlight=identicalTo#appendixes-assertions-assertthat-tables-constraints
                $this->anything()
             )
            ->willReturn('test-tid');

        $result = $mockedObject->sendMoreInterestBody(
            1,
            'test@test.com',
            86,
            '테스트product',
            1000
        );
        $this->assertNull($result);

    }
}
        

constructor 에서 사용하는 method 의 mock 을 만들때





References

  1. Testing - Laravel - The PHP Framework For Web Artisans

[컴] mariadb 의 .frm .ibd 파일로 data 내용 불러오기

mysql / mariadb / 마리아db/ 마이sql / 파일 복구 / 살리기 / restore / recovery / 어떻게 복구

mariadb 의 .frm .ibd 파일로 data 내용 불러오기

mysql, mariadb 의 저장된 파일인 .frm, .ibd file에서 data 를 다시 가져오는 방법이다. 다만, 이방법은 백업한 것을 다시 살려서 운영버전으로 쓰려는 것보다는, data를 어떻게든 살려보려 할 때 쓰는 방법으로 보는 것이 맞을 듯 하다.

  1. db 생성, 아무이름이나 가능, 여기서는 mydb1 으로 만들었다.
  2. table 생성, table을 같은 DDL 로 생성
    • ddl 을 모르는 경우 위 문서로 가면 mysqlfrm라는 tool을 사용해서 .from에서 ddl 을 얻는 방법을 알려준다.
  3. ALTER TABLE table_name DISCARD TABLESPACE;
  4. <mariadb>\data\mydb1\ 에 예전 ‘ibd’ 복사
  5. primary index 외에 모든 index 를 삭제(처음 table을 만들때 index를 없애고 만들어도 된다.)
  6. ALTER TABLE table_name IMPORT TABLESPACE;

테이블 정의 추출

util 을 설치후 아래처럼 명령어를 실행하면 DDL 이 보인다. port 8080 은 db port 는 아니다. 서버를 띄우는 듯 하다.

"d:\a\MySQL Utilities 1.6\mysqlfrm.exe" --server=root:mypassword@localhost d:\a\mytable.frm --port=8080

[컴] database query 관련 python unittest

 

파이썬 / db test / mysql test / test 방법 / db 쉽게

database query 관련 python unittest

import unittest
from datetime import datetime
import pymysql

class TestInsertPurchaseData(unittest.TestCase):
    def shortDescription(self):
        # 이것을 하지 않으면 첫줄만 print 된다. [ref. 1]
        return self._testMethodDoc

    @classmethod
    def setUpClass(cls):
        cls.conn = pymysql.connect(
            host="localhost",
            user="root",
            password="namhun",
            cursorclass=pymysql.cursors.DictCursor,
        )
        
        cls.db = 'testdb'
        cls._setUpCreateDB(cls.db)
        cursor = cls.conn.cursor()
        cursor.execute(f"use {cls.db}")


        # query from .sql file
        with open('schema.sql', encoding='utf8') as f:
            commands = f.read().split(';')

        for command in commands:
            if command.strip() != "":
                cursor.execute(command)

    @classmethod
    def tearDownClass(cls):
        # drop test database
        try:
            cls.cursor.execute(f"DROP DATABASE {cls.db}")
            cls.conn.commit()
            cls.cursor.close()
        except pymysql.Error as err:
            print(f"Database {cls.db} does not exists. Dropping db failed")
        cls.conn.close()

    @classmethod
    def _setUpCreateDB(cls, db):
        cursor = cls.conn.cursor()
        try:
            cursor.execute(f"DROP DATABASE {db}")
            cursor.close()
            print("DB dropped")
        except pymysql.Error as err:
            print(f"{db}{err}")
            cursor.close()

        # create database
        cls.cursor = cls.conn.cursor()
        try:
            cls.cursor.execute(f"CREATE DATABASE {db} DEFAULT CHARACTER SET 'utf8'")
        except pymysql.Error as err:
            print(f"Failed creating database: {err}")
            exit(1)

    def test_insert_record(self):
        """
        Test whether the inserted row count is correct
        """
        pass

if __name__ == "__main__":
    # 이것은 python -m unittest 로 하면 호출이 되지 않는다. `python -m unittest test.py` 
    unittest(verbosity=2)
CREATE TABLE `product_price` (
    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
    `purchase_date` date NOT NULL COMMENT '',
    `total_amount` int(11) NOT NULL COMMENT '',
    `total_price` bigint(20) NOT NULL COMMENT '',
    PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `price2` (
    `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
    `sales_date` date NOT NULL COMMENT '',
    `price2` int(11) NOT NULL COMMENT '',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
INSERT INTO mapping (id,`sales_date`,price2) VALUES
    (1,'2022-01-01',3558),
    (2,'2022-01-02',40000);

unittest verbose option

unittest(verbosity=2)를 하면, test case 를 실행할때 첫줄을 print 해준다.[ref. 1]

다음 부분은 python -m unittest test.py 를 실행하면 실행되지 않는다. __name__ 값이 '__main__' 이 아니기 때문이다.

python -m unittest -v test.py 를 하면 verbosity=2 와 같은 결과가 나온다.

if __name__ == "__main__":
    unittest(verbosity=2)

Reference

  1. Print a different long description in nose tests along with test name python - Stack Overflow

[컴] NestJS 설치 및 간단한 api 생성

orm 사용 / nestjs 에서 orm 사용 / nestjs example / nestjs simple api / 사용법

NestJS 설치 및 간단한 api 생성

@nestjs/cli를 사용해서 boiler plate 를 만들 수 있다.

npm i -g @nestjs/cli

원하는 project 이름으로 만들어보자. 여기서는 myapp 으로 했다.

nest new myapp

dev server

package.json 을 참고하면 된다.

npm run start:dev

main.ts 를 보면 알겠지만, 기본 port 는 3000 으로 되어 있다. 브라우저에서 http://localhost:3000 로 접속하자. Hello World 가 보이면 된다.

config

config 파일을 사용하려면 @nestjs/config를 설치해야 한다.

npm i --save @nestjs/config

module 추가

각 기능별로 Module 을 만들어서 추가하면 된다. Application 에는 하나의 root module 이 있고, 이 root module 이 모든 module 을 import 하게 하면 된다. root module 은 nest product 를 new 할 때 만들어진다. src/app.module.ts

여기서는 MyFuncModule 을 만들어보자. Module 은 어떤 controller 를 사용하고, 어떤 provider 를 사용하는지 명시하게 된다.

controller 에서 'myfunc' 을 만들어주면 http://localhost:3000/myfunc/ 를 호출할 수 있다.

src/
  |
  + app.module.ts
  + app.controller.ts
  + app.service.ts
  |
  +-- myfunc/
        |
        + myfunc.controller.ts
        + myfunc.module.ts
        + myfunc.service.ts

AppModule :

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MyFuncModule } from './diper/myfunc.module';

@Module({
  imports: [MyFuncModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

MyFuncModule :

import { Module } from '@nestjs/common';
import { MyFuncController } from './myfunc.controller';
import { MyFuncService } from './myfunc.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MyFunEntity } from 'src/model/myfunc.entity';

@Module({
  imports: [TypeOrmModule.forFeature([MyFuncEntity]],
  controllers: [MyFuncController],
  providers: [MyFuncService],
})
export class MyFuncModule {}
import { Controller, Get } from '@nestjs/common';
import { MyFuncService } from './myfunc.service';
import { MyFuncEntity } from 'src/model/myfunc.entity';

@Controller('myfunc')
export class MyFuncController {
  constructor(private readonly myFunc: MyFuncService) {}

  @Get()
  async findAll(): Promise<MyFuncEntity[]> {
    return await this.myFunc.findAll();
  }
}
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { MyFuncEntity } from 'src/model/myfunc.entity';
import { Repository } from 'typeorm';

@Injectable()
export class MyFuncService {
  constructor(
    @InjectRepository(MyFuncEntity)
    private readonly myFuncEntityRepository: Repository<MyFuncEntity>,
  ) {}

  async findAll(): Promise<MyFuncEntity[]> {
    return await this.diperRepository.find();
  }
  
  getHello(): string {
    return 'Hello World!';
  }
}
import { Entity, PrimaryGeneratedColumn, Column } from "typeorm"

@Entity()
export class MyFuncEntity {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    price: number

    @Column()
    date: Date

}

Database ORM

기본으로 TypeORM 을 이용한 TypeORMModule 을 제공한다. module 은 설치해야 한다. 다음은 mysql 과 관련한 설치 command 이다. 다른 db 에 대한 정보는 TypeORM - Installation 를 참고하자.

npm install --save @nestjs/typeorm typeorm mysql2

설치를 하고 나서 TypeOrmModule 을 root module 에 import 하면 된다.

  • entites option: 아래 code 에서 entities 에 만들 table 의 정보가 들어가면 된다. 저곳에 명시하지 않으면 해당 table 을 만들지 않는다.
  • autoLoadEntities : 자동으로 entity 를 불러온다. 기본값은 false 이다.
  • synchronize : 이 값은 기본값이 true이다. 이 값을 true로 하면, entity 가 변경내역이 서버 시작 시점에 반영된다. production 에선 이 값을 false 로 두라고 한다.
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'root',
      database: 'test', 
      entities: [],     // <---- info for tables
      synchronize: true // <---- 
    }),
  ],
})
export class AppModule {}

DataSource 를 사용하는 법

아래처럼 ConfigModule.forRoot 에서 config file 을 불러올지 정해주고, 그값들을 TypeOrmModule.forRootAsync에서 inject 해서 사용하면 된다.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { MyFuncModule } from './diper/myfunc.module';

import { ConfigModule, ConfigService, } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    ConfigModule.forRoot({
      envFilePath: ['.env.dev.local', '.env.dev'],
      isGlobal: true
    }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => {
        return ({
          type: 'mysql',
          host: configService.get('HOST'),
          port: +configService.get('PORT'),
          username: configService.get('DATABASE_USER'),
          password: configService.get('DATABASE_PASSWORD'),
          database: configService.get('DATABASE'),
          // entities: [
          //   __dirname + '/**/*.entity{.ts,.js}',
          // ],
          autoLoadEntities: true,
          synchronize: true,
        })
      }
    }),
    MyFuncModule
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }

TypeOrmModule.forRootAsync

TypeOrmModule.forRootAsync 를 사용하면, 무조건 entity 에 대한 table 을 생성하지 않는다. 각 module 에서 자신들이 사용할 entity 를 TypeOrmModule.forFeature([MyFuncEntity]) 처럼 imports에 적어주게 되는데, 이렇게 적힌 것들에 대해서만 생성해 준다.

data migration

최초의 table 의 생성은 ConfigModule.forRoot 의 option entities를 통해 정보를 가져와서 생성하게 된다.

NestJS 에서 제공하진 않고, TypeORM CLI 를 이용한다.(참고) typeorm package 가 이미 설치되어 있으니, 이걸 이용하면 된다.

migration:create :

아래처럼 하면, ./migration/test01.ts 가 생성된다. 그러면 이 test01.ts에 migration 내용을 적으면 된다.

.\node_modules\.bin\typeorm migration:create ./migration/test01

migration:run :

.ts file 을 js 로 바꿔서 사용하기 위해 ts-node 를 사용해야 한다. 다음 커맨드를 이용하면 된다.

npx typeorm-ts-node-commonjs migration:run -- -d path-to-datasource-config

full codes

Refrence

  1. First steps | NestJS - A progressive Node.js framework

[컴][웹] tomcat 에서 context.xml 에 JNDI 를 이용해 DataSource 설정하기




JNDI 사용해서 DataSource 설정

아래처럼 DataSource 를 설정하는 경우에
<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
    <property name="jndiName" value="java:/comp/env/jdbc/namh"/>
    <property name="resourceRef" value="true" />
</bean>

jdbc/namh 을 어디엔가 정의해 주게 되는데, 그곳이 context.xml 이다.

java:/comp/env


에서 보면, java:/comp/env 는 tomcat 에서 Context 를 구성하게 되면 Context 안의 resource 들을 사용할 때 사용한다.



context.xml


위의 글에서 보듯이 tomcat 의 application directory 안에 /META-INF/context.xml 이 기본적으로 context.xml 로 지정되어 있다.

http://tomcat.apache.org/tomcat-7.0-doc/jndi-datasource-examples-howto.html



<Context path="" docBase="ROOT" distributable="true">
    <Resource name="jdbc/namh"
                auth="Container"
                type="javax.sql.DataSource"
                factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
                initialSize="3"
                maxTotal="30"
                maxIdle="10"
                minIdle="2"
                maxWaitMillis="60000"
                validationQuery="SELECT 1"
                testWhileIdle ="true"
                timeBetweenEvictionRunsMillis = "1800000"
                logAbandoned="true"
                jdbcInterceptors="org.apache.tomcat.jdbc.pool.interceptor.ConnectionState;org.apache.tomcat.jdbc.pool.interceptor.StatementFinalizer"
                username="xxxxxx"
                password="pspspsp"


                loginTimeout="10"
                driverClassName="com.mysql.jdbc.Driver"
                url="jdbc:mysql://127.0.0.1:65001/db_name?useUnicode=true&amp;characterEncoding=utf-8&amp;autoReconnect=true&amp;interactiveClient=true&amp;autoReconnectForPools=true&amp;characterSetResults=UTF-8"/>
</Context>


이 때 $CATALINA_HOME/lib 에 jdbc driver 의 jar libaray 가 들어 있어야 한다.




Database connection pool leaks

Database connection pool(DBCP) 은 connection 에 대한 pool 을 만들어 놓고 그녀석들을 재사용하는 것이다. 그런데 이 녀석에게 한가지 문제가 있다고 한다.
web application 이 ReulstSet, Statement, Connection 에서 사용한 connection 들을 명시적으로 close 해야 한다는 것이다. web application 에서 이녀석들을 close 하지 못하면 close 되지 못한 녀석들은 다시 사용할 수 없게 된다. 이런 녀석들이 database connection pool leak 이다.

Apache Commons DBCP 를 설정해서 이런 버려진 connection 들(abandoned connections) 을 알아서 찾아가지고 다시 사용할 수 있도록 할 수 있다.

이 설정은 <Resource> 설정에 다음 속성을 추가하면 된다. default 값은 false 이다.
removeAbandoned="true"
f
아래의 설정을 통해서 DBCP 가 log 에 database connection resource 들을 abandon 시키는 code 의 stack trace 를 찍게 할 수도 있다.
logAbandoned = "true"




context.xml 설정이 적용되지 않는 경우


server.xml 에 설정이 되어 있으면 META-INF/context.xml 이 먹히지 않는다.
java - javax.naming.NameNotFoundException: Name is not bound in this Context. Unable to find - Stack Overflow


It is NOT recommended to place <Context> elements directly in the server.xml file. This is because it makes modifying the Context configuration more invasive since the main conf/server.xml file cannot be reloaded without restarting Tomcat.(출처 : Apache Tomcat 7 Configuration Reference (7.0.56) - The Context Container)

<apach-tomcat-path>\conf\Catalina\localhost 폴더안에 ROOT.xml 이 있고, 그 안에 Resource 가 있는 경우에도 context.xml 을 사용하지 않는다.

[컴][웹][파이썬] celery beat 에서 schedule task 를 dynamically 추가/삭제

셀러리 비트 / celery beat how to add/remove scheduled task dynamically / 원하는 순간에 추가/삭제 / 다이내믹하게  /


celery beat 에서 schedule task 를 dynamically add/remove


git hub issue comment 에서 dynamic 하게 event 를 추가할 때 celery beat 가 새롭게 populate_heap() 을 하도록 하는 법이 나와 있다. 여기서 이야기하는 바는 아래 2가지이다.
  1. populate_heap() 을 schedule 이 변경될 때마다 해주는 것
  2. 그리고 DatabaseSchduler 를 사용해서 add/remove 를 하는 것(참고: Using custom scheduler classes / Celery 4.1.0 documentation)

이중에 1번째 내용은 이슈의 comment에 의하면 4.1 버전에 반영되었다고 한다.

실제로 확인을 해보니 반영이 되어 있다. Populate heap when periodic tasks are changed · celery/celery@6a24e00 · GitHub


Scheduler

celery.beat.PersistentScheduler

기본적으로 celery.beat.PersistentScheduler를 사용한다.
$celery -A proj beat -s /home/celery/var/run/celerybeat-schedule

celery.beat.PersistentScheduler 는 celerybeat-schedule file을 이용하는데 shelve database 파일이다. 아래 shelve database 의 '제한사항(retrictions)' 에도 나오지만 concurrent access 를 지원하지 않는다.

django-celery-beat

django-celery-beat 은 database 에 "periodic task schedule" 을 저장할 수 있도록 해준다. 하지만 이것은 Django 를 필요로 한다.

Django 없이 SQLAlchemy 를 이용하는 DatabaseScheduler

사용법은 간단한다.  SQLAlchemy 를 설치하고
$pip install sqlalchemy

아래 code 를 실행해서 DB table 을 생성한다.(참고로, 아래코드는 sqlite 을 사용하는 코드다)(다른 DB  urls 과 관련해서는 여기 를 참고하자.)

참고로 추후에 sqlalchemy_scheduler_models.py 의 engine  도 user/password 를 해줘야 한다. 예를 들면 아래와 같은 식으로 말이다.
  • engine = sqlalchemy.create_engine('mysql://root:root_password@localhost/db_name')




## sqlalchemy_scheduler_models.py
...
engine = sqlalchemy.create_engine('sqlite://')
Base = declarative_base(bind=engine)
...

from sqlalchemy import create_engine
from sqlalchemy_scheduler_models import Base


# Create an engine that stores data in the local directory's
# sqlalchemy_example.db file.
engine = create_engine('sqlite:///sqlalchemy_example2.db')
 
# Create all tables in the engine. This is equivalent to "Create Table"
# statements in raw SQL.
Base.metadata.create_all(engine)

그리고 celconfig.py 에 아래를 추가하자.
CELERYBEAT_SCHEDULER = 'yourproject.sqlalchemy_scheduler:DatabaseScheduler'


주의

주의할 점은 scheduler 가 바뀌는 것에 대해서는 새롭게 sync 를 하지만 celery_crontabs 내용이 바뀌는 것에 대해서는 반영되지 않는다. 이것이 왜 중요하냐면, scheduler 에서 시간 설정을 crontab_id 로 하기 때문이다.

즉, crontab 의 내용을 변경하는 경우(시간을 변경하는 것)라면 새롭게 celery beat 를 restart 해야 한다.

etc

수행조건

enabled, last_run_at

table 'celery_schedules' 에 있는 task 들은 해당되는 crontab_id 시간에 수행된다. 이때 "enabled" 가 '1' 이 되어 있어야 하고, 또 하나 "last_run_at" 이 현재 UTC 보다 이전이어야 한다.

참고로 last_run_at 이라고 가정하고, 동작한다. 그러기 때문에 insert 할 때도 UTC 를 insert 해야 한다. 그렇지 않으면 last_run_at 이 현재 UTC 보다 큰 값이 돼서 동작이 실행되지 않을 수 있다.

sync 시점

또는 date_changed 를 보고 이전의 date_changed 시간이후여야 한다. 그래야 task 를 resync 하게 된다.(참고 : sqlalchemy_scheduler.py: DatabaseScheduler.should_sync())

should_sync 가 발생빈도는 Scheduler 의 sync_every 변수를 수정하면 된다.
# beat.py
class Scheduler(object):
     ...
    #: How often to sync the schedule (3 minutes by default)
    sync_every = 3 * 60

그리고 또하나 주의할 점은 session 과 관련돼서, session 이 기본적으로 autoflush 를 하지 않기 때문에, 새롭게 update 된 내용이 session 을 통해 이루어지지 않았다면, DatabaseScheduler 가 새롭게 sync 하지 못한다. (예를 들면, DB client 에서 직접 수정한 경우등)

그렇기 때문에 되도록 schedule 을 추가하는 부분도 동일한 session 을 이용하도록 하는 것이 낫다.


See Also

  1. Don't keep important data in your Celery queue : celery 에 apply_async 를 사용하는 것보다는 주기적으로 task 를 확인하고 실행하도록 하는 것이 낫다는 내용의 글. expires option 에 대한 이야기도 있다.
  2. Celery 4.4.5- task_time_limit task_time_limit 은 default 가 No time limit 이다. 하지만, 주기적으로 task 를 돌릴 때 설정한 주기와 task 를 끝내는데 걸리는 시간은 고려해야 한다.


[컴] Google cloud platform (GCP) 난잡 정리

google cloud platform



Google cloud platform (GCP) 난잡 정리

구글의 가격정책

  • 서버를 오래 띄워놓으면 할인율이 커진다.
  • vcpu 단위로 계약 한다. / vcpu 단위로 commit 을 매입?
  • preemptive use 에 대한 할인??
  • hour 단위보다 낮은 단위 , 분, 초 에 대한 과금도 가능

billing 정보 분석

  • Billing 정보를 BigQuery 로 export 로 받아 놓으면, 쿼리 를 통해서 정보를 자세하게 뽑아낼 수 있다.

BigQuery(빅쿼리) 과금

  • 읽어드리는 데이터 의 용량으로 과금이 된다.
  • select join 등 할 때 사용하는 데이터 의 양이 크면 큰돈
  • 슬롯 정액제 , 특정 슬롯을 구매하고, 그 안에서 계속 사용 가능

과금

  • GCP 는 나가는 traffic 에 대한 비용에 대해 과금한다. (서버에서 나가는)
  • 대륙간 region 을 이동하는 packet 에 대한 과금도 한다.(리전을 넘어가지 않는한 과금을 하지는 않는다.)
과금종류
  • Per-second billing
  • sustained use discounts
  • committed use discounts

Quota 이슈

  • 서비스 릴리즈 전에 quota 관련 test 필요
  • https://bespinglobal.qwiklabs.com/
    • 여기에서 구글 클라우드 관리 할 수 있게 이 베스핀 애들이 만들어 놨다고 함

quota 종류

  1. 확장 불가능한 qutoa
  2. 요청하면 확장을 할 수 있는 quota

google cloud platform 제품 및 서비스

vm instance

  • startup script 를 bucket 에 올려서 instance 를 띄울때마다 원하는 script 을 실행하게 할 수 있다.

GCP 접속 방법

  • Cloud platform Console
  • Cloud Shell and Cloud SDK (shell 은 GCP 접속하면 기본으로 하나 제공됨,CLI)
    • 보안 이슈로 막아야 하는 케이스 들이 있다 고 함...
  • Cloud Console Mobile App
  • Rest-based API

GCP 계층 구조

  • https://cloud.google.com/resource-manager/docs/cloud-platform-resource-hierarchy?hl=ko
  • 베스핀 글로벌 사이트 에서 왼쪽 위 리스트에서 확인가능
  • 모든 리소스는 프로젝트 에 종속된다.
  • organization 아래 organization 은 불가능
  • 권한(IAM, 정책)은 상속된다. 상위 계층에 권한을 주면, 하위 구조에도 영향이 미친다
  • IAM 은 여러개의 permission 을 갖는 개체 , Role 이라 부르는 듯
    • predefined role 들이 존재한다. 이것을 수정해서 자신의 롤을 만들어도 된다.(primitive, predefined, cutom role 이 가능)

서비스 계정 Service Account

  • application 이 gcp 에 접근 하기 위한 계정(apache 의 nobody 계정 같은 느낌)
  • project 내에서 만들어진다.
  • 계층 구조에 있는 상속 이 동작하기 때문에, 프로젝트 위 폴더에서 사용하도록 서비스 계정 을 만들 수 있다???
  • 하지만 편리하게 관리하는 것은 프로젝트 별로 관리하는 것이 낫다?
  • 이건 그냥 auth key 같은 녀석인듯?

VPC

  • VPC 존재 , 아마존이랑 비슷한듯
  • VPC 는 하나의 LAN 이라고 보면 된다.
  • 여러 region 을 하나의 VPC 을 생성할 수 있다.
  • shared VPC 형식으로 네트워크 A 와 네트워크 B 사이에 A와 B 가 공유하는 네트워크 C 를 만들어 쓰기도 한다???

vm instance

리전 Region

HW 하드웨어

  • standard, SSD, local SSD 가 있는데,
  • local SSD 는 cache 로만 제공한다.
  • 동일단가 대비 disk 성능이 좋다
  • 용량을 늘리면 성능이 올라간다.
  • SSD 는 타사 대비 2배 ?

preemptible instance

  • 이녀석은 매우 저렴
  • preemptible instance 는 24시간 이후에 자동으로 삭제
  • 이녀석은 주위에 instance가 필요하면 가져가 버린다.
  • OS 에서 preemption 을 하는 것처럼 instance 가 GCP 에 의해 preemption 이 가능한 듯 하다. 그래서 구글 맘대로 가져가 버리고, 우리가 그것을 계속 차지 하고 있지 못한다.
  • 항상 ready 해야 하지 않고, 빠른 응답이 중요하지 않은 경우 쓰면 좋을 듯

migration

  • instance 는 짧게는 일주일 길게는 한달에 한번 migration 이 구글에 의해 일어난다.
  • gpu instance 를 활용해서 머신러닝 하는게 제일 저렴
  • gpu instance 는 live migration 시점에 서버를 내린다.(하드웨어 종속성이 커서 그렇다)
  • 다른 instance 는 그저 알람 하나를 주는 정도다.

load balancer

proxy

앞단에 google front engine 에 존재(gfe 가 proxy역할), 단일 ip 를 물고 있다.
proxy 기반 load balancer
  • global https
  • global ssl proxy
  • global tcp proxy

regional

도쿄 리전 을 만들고 리전에 위치한 프론트가 그 트래픽을 받아준다. 프록시를 통하지 않는다. 클라이언트 아이피가 직접 도달한다.
DSR 형태로 동작
그래서 리저널은 그냥 라우팅만 해주는 정도이다.(TCP/UDP 단에서 라우팅) 성능 저하가 없다.
특정한 정책을 넣거나, 자동 모니터 등을 제공하진 않는다.
  • regional
  • regional internal : 내부 L4 switch 라고 보면 된다.

DNS

  • 클라우드 DNS
  • internal dns service 도 제공

CDN

  • 대용량 스트리밍 관련 CDN도 제공
  • 백엔드로 붙일 수 있는게, global load balancer 에 대해서만 CDN활성화 가능
  • 버킷에 올라가 있는 것만 가능??
  • 타사대비 가격 경쟁력이 있다고 한다??

interconnect option , vpn

회사 내부 망과 gcp 와의 통신을 위한 여러 interconnect option 을 제공
특정 아이피에서 접속할 때 gcp에 접근할 때 빠르게 해준다.
  • direct peering: 구글이 직접 라우팅
  • carrier peering : 망사업자를 통해 지원 ?
서울 리전 이 들어오고 나야 가능? 하다고 함.
  • dedicated interconncet : 구글 데이터 센터에 직접 전용선을 붙이는 것, 데이터 센터가 인접할 수록 유리
  • partner interconnect : ISP 나 망사업자 를 통하는 방식

GCP web page 에서 instance 에 있는 ssh 버튼 으로 접속시

자세한 내용은 찾아봐야 할 듯
  • 구글의 ssh proxy 를 이용해서
  • 접속 하면서, 그때 키를 받아서 메타데이터를 업데이트 해서 접속?

클라우드 스토리지 Cloud Storage

  • 초당 수만번의 파일을 쓰거나 읽거나, 급격한 요청이 들어오면, 약간의 delay 가 발생할 수 있지만, - 실서버에서 큰 이슈가 된 적은 없다.
  • 퍼포먼스에서 문제는 없다.
  • 스토리지 레벨의 암호화는 되고 있다. 암호화를 할 때 별도의 키 를 세팅할 수 있다.
  • 대량의 데이터를 올릴 때는 그냥 데이터 센터에 보내면 자기내 들이 올리는 서비스 도 제공해 준다.
  • 클라우드 스토리지 파일들은 bucket 으로 구성된다.

Storage class

  • 멀티 리저널(multi regional), 리저널, Nearline, Coldline
  • 리저널은 차이가 없다.
백업용
  • Nearline, Coldline 은 저장용으로 적당
    • Nearline 은 한달에 한번 쓸때
    • Coldline 은 사고날때 쓰는 용도
  • 저장용량당 비용이 싸다. 대신 한번에 읽기 쓰기 할 때의 비용이 비싸다.

클라우드 SQL

  • MySQL, PostgresSQL 만 제공

Cloud BigTable

  • Cloud BigTable 은 NoSQL 에 의해 managed 된다.
  • 대량 처리에 특화된다.
  • 대량의 데이터 insert , select
  • 키 기반으로 빠른 결과 얻을 때
  • HBase API
  • instance 를 많이 늘릴 수록 빨라진다.
  • 클러스터의 노드 수가 많아 질 수록 속도가 올라간다.(반응성이 좋아진단 소린가?)

cloud spanner

  • 국내 게임사는 아직 선택 안했다.
  • 많이 비싸다
  • 자동 복사 가 가능
  • horizontally scalable RDBMS
  • global consistency 를 보장
  • 해외사례는 있지만, 국내 사례는 없다.

BigQuery

  • 대량의 데이터에 대한 query 등, 데이터 사이언스 쪽에서 필요
  • 다루는 data 양에 따라 과금된다. 결과가 아니라 과정에서 사용되는 data 양

Google Container Registry

  • 사설 컨테이너 저장소 를 제공해준다.

cloud source repository

  • 사설 git
  • 사용용량에 따라 가격

google kubernetes engine

  • 쿠버네티스 엔진 을 이용해서 구글에서 만든 컨테이너 만이 아니라, 로컬에 구성된 컨테이너도 같이 관리 할 수 있는 서비스를 제공을 시작했다.
  • gke 에서는 칼리코(Calico) 를 사용 하고 있다.
현재 클라우드의 인스턴스는 가격 이 중요한 차별점이다. 그래서 인스턴스에 대해서 차별화 에 대한 고민을 하지 않는다. 장애율만 낮으면 된다.
그래서 구글은 쿠버네티스 와 빅데이터 쪽을 차별화 하려하고 있다.

google app engine

  1. standard app engine
    • 자동 scaling
    • local file system 못 쓴다.
    • request 의 timeout 이 1분 (무조건)
    • 3rd party library 설치 제약
    • 신속한 auto scaling
    • flexible 보다 이게 더 비쌈
  2. flexible environment
    • instance 를 우리가 정하고, 여기에 node 를 띄울 수 있다.
    • instance 에 ssh 접속을 허용
    • auto scaling 이 빠르게 되지 않는다. (상당히 느리다)

Cloud Functions

  • aws lambda 같은 것
  • 사용된 computing 정도에 따라 가격

가능한 trigger

  • http request
  • 특정파일이 생길때 : log 가 발생할때 등
  • ...

Deployment Manager

  • instance 배포 등, 인프라 매니지먼트 서비스
  • .yaml 하나 만들어놓고, 한번에 deploy 하는 것
  • terraform 비슷한 서비스
  • api 도 제공

Stack Driver

  • 모니터링
  • 로깅
2개 를 위해 주로 사용
  • 디버그
  • 에러 리포팅
  • 트레이스
  • 프로파일링
에도 사용
  • 로그 저장소로 보면 된다.
  • 임계치가 넘으면 알람을 받을 수 있다.
  • 개인 로그등도 stack driver 로 보내서 모니터링을 할 수 있다.
  • 메트릭은 6주, 로그는 400일, 개인이 개인적으로 쌓은 로그는 1개월 보관
  • 특정 로그들을 받아서 필터링 해서 바로 원하는 곳으로 보낼 수 없다.

사용

pub /sub 에서 받아서 dataflow 에서 처리하고 big query 에 저장하는 식으로 사용