업무에서 MySQL을 쓰기 때문에 쿼리 처리를 위해 knex를 사용하고 있다. knex는 SQL query builder 라이브러리인데 사용이 쉽고 직관적이기 때문에 업무에서 사용해도 무리가 없다. 자세한 내용은 https://knexjs.org 에서 찾아볼 수 있다. 


설치 


우선 다음 명령으로 설치한다. 

npm install knex --save


우선 DB 정보를 이용해 knex 객체를 생성한다. 

1
2
3
4
5
6
7
8
9
const knex = require('knex')({
  client: 'mysql',
  connection: {
    host : HOST_ADDR,
    user : DB_USER,
    password : DB_PASSWORD,
    database : DB_NAME
  }
})
cs


CRUD


이 때 생성한 knex 객체를 이용해서 쿼리를 생성하면 되는데, 유저 테이블에서 유저 정보를 읽는 쿼리는 아래 코드처럼 간단하다.

1
knex.select('id''name''age').from('users')
cs


유저 테이블에 새로운 유저를 추가하는 코드는 다음과 같다.
1
knex('users').insert({ name:'James Kook', age: 20 })
cs

이 때 생성된 유저의 ID가 2일 때 나이를 변경하는 코드는 다음과 같다. 
1
knex('users').update({ age: 21 }).where('id'2)
cs


유저를 삭제하는 코드는 다음과 같다.

1
knex('users').del().where('id'2)
cs

JOIN


유저 테이블과 문서 테이블을 조인해 아이디 2 유저의 문서를 변경 시간 역순으로 나열하는 쿼리는 다음과 같다. 이 때 리턴되는 값은 JSON 배열이며 map() 함수를 이용해 각 레코드를 대상으로 함수를 호출할 수 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const { userId } = params
const userDocs = await knex('users AS u')
    .select('u.id''u.name''u.age''d.id AS doc_id''d.name AS doc_name''d.created_at''d.updated_at')
    .join('documents as d''u.id''d.user_id')
    .where('u.id', userId)
    .orderBy('d.updated_at''DESC')
    .map(r => ({
        userId: r.id,
        userName: r.name,
        userAge: r.age,
        docId: r.doc_id,
        docName: r.doc_name,
        docCreatedAt: r.created_at,
        docUpdatedAt: r.updated_at
    }))
cs


Migration


스키마를 변경하거나 데이터베이스를 업그레이드하려고 할 때에는 knex migration 명령을 이용할 수 있다. 우선 knex를 글로벌로 설치하고 knex init 명령을 실행하여  knexfile.js 파일을 생성한다. 이후 knexfile.js 파일을 열어 데이터베이스 정보를 입력한다.

npm install knex -g

knex init


아래 예제는 development 환경에서 사용할 목적으로 만든 예제이다. 서버 환경에 따라 staging, production의 데이터베이스 정보를 입력해야 할 수 있다. 

1
2
3
4
5
6
7
8
9
10
11
12
module.exports = {
  development: {    
    client: 'mysql',
    connection: {
      timezone: 'UTC',
      host:     HOST_ADDR,
      database: DATABASE_NAME,
      user:     DB_USER,
      password: DB_PASSWORD
    }
  }
}
cs


다음은 migration을 통해 새로운 테이블을 추가하는 예제이다.

knex migrate:make add-attractions-table


이 결과 [timestamp]_add-attractions-table.js 파일이 생성되는데, 아래는 테이블을 생성하고 레코드를 추가하는 예제이다.
1
2
3
4
5
6
7
8
9
10
const table = 'attractions'
 
exports.up = async knex => knex.schema.createTable(table, t => {
  t.increments('id').primary()
  t.string('name')
  t.string('address')
})
.then(() => knex(table).insert({ id: 1name'Big Ben', address: 'Westminster, London SW1A 0AA'}))
 
exports.down = async knex => knex.schema.dropTableIfExists(table)
cs

다음 명령은 최근에 추가된 migration 파일들을 찾아 실행한다. 명령이 정상적으로 완료되면 해당 데이터베이스에 attractions 테이블이 생성되는 것을 볼 수 있다. 
knex migrate:latest


knex migration이 정상적으로 완료되면 해당 데이터베이스의 knex_migrations 테이블에 실행된 migration이 추가되는 것을 확인할 수 있다. 이 테이블을 참고하면 보유한 migration 파일들 중에서 실행되지 않은 migration을 찾을 수 있다.


만일 문제가 발생하여 migration을 취소해야 할 경우 migration 파일의 down() 함수를 실행하여 취소할 수 있는데 다음은 이 때 실행하는 명령이다.

knex migrate:rollback


좀 복잡해 보이는 서브 쿼리도 조금만 익숙해지면 쉽게 만들 수 있고, raw query도 실행할 수 있다. Response 시 호출될 함수를 등록할 수 있는데, 이 기능을 이용해 긴 response time을 갖는 쿼리를 찾아 최적화할 수도 있다. 2년 넘게 사용해왔는데 딱히 불만이 없는 라이브러리다. 다음 사용하게 될 데이터베이스가 MySQL이라면 또 이 라이브러리를 최우선으로 고려할 생각이다.



Posted by 코딩새싹
,

지금 추천 엔진을 만들고 있는데 주기적으로 사용자의 데이터를 가공해서 어떤 데이터를 만들어내야 하는 상황이다. 지금으로서는 하루에 한 번 정도 처리하면 되는 정도로 보이고 데이터의 양이 많지 않아서 scheduled event를 만들어 procedure를 한번씩 콜 하는 걸로 구현했다. 추출한 데이터를 저장할 때에는 json 오브젝트를 텍스트로 저장한다. Procedure 내에서 GROUP_CONCAT()과 CONCAT()을 이용해 json 오브젝트를 만들어 컬럼 값으로 입력하니 잘 된다. 그런데 문제는 유저 데이터의 크기가 클 때 group_concat_max_len 조건으로 인해 제대로 된 json 오브젝트가 잘려 나간 채 저장이 된다는 거였다. 찾아보니 group_concat_max_len의 defaut 값은 1024로 작았다. 


값을 확인하는 방법

SHOW VARIABLES LIKE 'group_concat_max_len';


그래서 이 값을 변경하기 위해 아래 명령을 실행했다. 

SET SESSION group_concat_max_len = 102400;


일단 procedure 내에서 이렇게 처리를 하면 문제 없이 데이터를 텍스트로 저장할 수 있다. MySQL 설정에서 group_concat_max_len의 값을 변경해 데이터베이스를 재실행하는 것이 가장 깔끔한 방식이다. 그래서 DevOps 통해서 group_concat_max_len 값을 영구적으로 값을 변경하려고 했는데 8시간 차이가 나는 원격 근무 중이고 티켓 만들어 요청하기도 귀찮아서 procedure 내에서 매 번 세션의 값을 바꿔 실행하는 것으로 만족하기로 했다. 프로덕션 가기 전에는 제대로 바꾸는 게 좋겠다. 





Posted by 코딩새싹
,

매일 프로그래밍을 하고 있다. 일 때문이기도 하고 좋아서 하기도 하고. 좀 더 개발을 잘 해보고 싶어서 프로그래밍 언어도 하나씩 배우고 설계와 관련된 것들도 찾아보고 이런 저런 고민들을 많이 한다. 지금은 Node.js를 써서 백앤드를 개발하고 있고 추천엔진 개발을 위해서 서툰 Python을 쓰면서 필요한 라이브러리들을 찾아가며 공부 중이다. 조금 익숙해지면 Spark으로 옮겨갈 생각이고 Python 대신 Scala를 써볼까 생각 중이다. 할 수만 있다면 Clojure를 써서 개발하고 싶은데 같이 해줄 사람이 없어서 시작을 못한다. Scala보다는 Clojure가 훨씬 낫다고 생각하는데 이걸로 구직도 어렵고 업무에서 사용하기도 쉽지 않다. 


이 블로그에는 아무거나 개발에 도움 될 만한 것들을 써 넣을 생각이다. 얍!


Posted by 코딩새싹
,