더미 데이터 생성기 - Mock2CSV

Mock Data 생성기의 필요성

  • dummy data 를 대량으로 생산하기 위해서는 몇 가지 방법이 있었다.
    • mock generator (web) 이용
    • procedure 작성
    • 직접 코드 작성
  • mock generator 의 경우 간편하게 만들기는 하지만, 백 만 건이 초과하는 경우에 해당 web 에서 응답을 하지 않거나, 유료 결제가 필요했다. 특히, csv 로 export 해서 LOAD DATA 를 통해서 적재할 경우 백 만 건에 30초 미만으로 아주 빠르게 적재할 수 있었으나, 천 만 건 이상의 데이터를 획득 불가하여 아쉽게도 제외했다.
  • procedure 의 경우 insert into 구문을 loop 돌면서 주입한다. 꽤나 편리한 방법이고, BULK INSERT 를 활용한다면 더 빠른 적재를 할 수 있다. 다만, 생성할 수 있는 데이터의 형태가 한정되어있다.
  • 직접 코드를 작성하는 방법의 경우, json-schema-faker 라는 라이브러리를 결합하여 활용하는 방법이 있었다. 그러나, json-schema-faker 를 이용하려면, json-schema 를 작성해야하고, faker 를 통해서만 데이터 생성(물론 피하는 방법이 있기는 함.)해야해서, 다소 불편함을 느꼈다.
  • 결과적으로, Programmable 하게 코드로 간단하게 작성하여 원하는 형태의 데이터를 빠르게 csv 파일 만들어내는 라이브러리를 하나 만드는 것이 더 낫겠다는 판단을 했고, mock2csv 라이브러리를 제작했다.

Mock2CSV

  • 언어는 typescript 를 선택했다.
    • npm 에서 활용할 수 있는 라이브러리가 많았으며, 하나 모듈 작성하는데 코드 작성 시간이 짧다.
    • Web GUI 를 구성하고자 하는 욕심이 있었고, Web GUI 를 만들기 편한 언어기도 하다.
    • Java 를 선택했다면, Entity 를 끌어다와서 convert 할 수 있는 여지가 있었을 것 같다. 다만, 개발 / 테스트 / 사용에서는 오히려 툴로서 사용하기에 불편하다고 판단했다.
  • 기존 라이브러리 최대한 활용할 수 있으며, programmable 하게 코드를 작성하여 csv 로 생성할 수 있도록 하였다.
  • csv 파일을 만드는 부분은 최대한 라이브러리에 맡기고, 어떤 데이터를 생성해야하는지에 대한 부분은 programmable 하게 적을 수 있도록 하고 싶었다. json-schema-faker 의 경우 정적인 json-schema 를 통해서 생성해야하기에, 불편함을 느꼈기 때문이다.
  • 따라서, 간단한 interface 를 schema 로 정했다. (schema 가 적절한 단어 선택인지는 모르겠지만...)

Schema

export interface Schema {
	[key: string]: () => string;
}

let uid = 1;
const User: Schema = {
	user_id : () => (uid++).toString(),
	nickname: () => faker.internet.userName(),
	...
}

Schema interface 는 아주 단순하다. Objectkey 는 각 column 을 의미하며, 각 column 의 생산 방법을 lambda function 으로 정의한다. 이렇게하면, faker 에서 처리하지 못하는 다양한 타입도 다른 library 를 끌어와서 대체하거나 직접 작성할 수 있다. 또한, id 와 같은sequence 또한 간단한 코드로 처리가 가능하다.

Mock2CSV

class Mock2CSV {
	// member variables..
	constructor(options?: Options) {
      ...
    }

    generate(schema: Schema, recordNum: number, filePath: string) {
      ...
    }
    
    generateToStream(schema: Schema, recordNum: number, stream: WriteStream) {
      ...
    }
}

csv 를 만드는 class 로 Mock2CSV 라는 클래스를 만들었다. 해당 클래스는 csv 형식의 데이터를 schema를 바탕으로 제작하는 책임을 가지고 있다. web 버전의 mock generator 를 참고하여, quote, escape, delimiter, newline 을 configuration 할 수 있도록 해두었다.

초기 버전에서는 Mock2CSV 객체 생성 과정에서 schema, recordNum, filePath 등을 주입했다. 그러나, 그렇게 사용할 경우 새로운 schema 에 대해서 생산을 할 때마다 객체를 새로 생성해야한다. 따라서, csv 를 어떻게 포메팅할 지에 대한 설정만을 하고 generate(schema, recordNum, filePath) 를 통해서 여러 번 재사용할 수 있도록 변경하였다.

generateToStream(schema, recordNum, stream) 또한 추가했다. 이는 stream 을 통해서 외부에 전송하는 경우를 염두에 둔 것이다. web GUI 를 간략하게 붙여서 Mock Generator 와 같은 환경을 보다 쉽게 만들어내기 위함이다. 이후 작업이 있다면 Schema 를 외부 GUI 를 통해서 선택하고 이를 객체로 만든 후 generation 할 수 있는 형태로 만들어내는 과정이 될 것이다. (혹은 예외처리?)

generateAsyncv0.0.7 에서 추가하였다. LOAD DATA 실험 중에 나눠진 데이터를 주입하기 위해 한 번에 20개의 csv 파일을 생성해야했다. 단일 파일을 처리하는 경우에는 괜찮았으나, 현재 코드의 경우에는 모든 stream 이 종료되고 난 이후에야 파일이 한번에 생성되는데 적절한 동작은 아니라고 생각되었다. 때문에, Async 가 지원되는 API 를 추가하였다.

개발하면서 ...

v0.0.6 까지는 테스트 없이 코드를 작성하였는데, 안정성이 너무 떨어졌다. 특히, async api 하나 들어오기 시작하니, 테스트 프레임워크의 도움이 절실해졌다. vitest 가 깔끔하니 괜찮아보여 도입하였고, 덕분에 escape 를 처리하고 있지 않았던 문제로 함께 수정할 수 있었다. (역시 테스트는 선택이 아닌 필수이다) 다만, api 에 대한 e2e 테스트만 수행하고 내부 private method 에 대한 검증은 아직 하지 못했다. (아마 방법이 있을 것 같은데, 조금 찾아봐야할 듯 싶다.)

option 관련해서도 고민이 많았었다. (물론 나만 쓰지만) 다른 사람이 사용한다면 어떻게 처리하는 것이 조금 더 편리할까? 라는 고민에 대해서 많이 하게 되었다. 초기에는 fn(options: Options = new Option()) 형태를 취했는데, 유연성이 부족했다. 이후에 fn(options?: Options | Object) 형태로 변경하였고, this.options = { new DefaultOption(), ...options}; 로 변경하며 Option 객체를 모두 받지 않아도 설정이 가능하도록 변경했다. 아마 다른 방법이 더 있을 것 같기는 하나, 현재는 만족.

아무튼, 만든 이후 이전 작성글에서 LOAD DATA 를 테스트하는데 굉장히 유용하게 활용했다. 데이터 생성 로직 자체가 눈에 잘 들어와서 편리했고, 나름 잘 만든 것 같다는 생각이 든다.

example

사용 방법은 어렵지 않다. schema 를 정의하고, Mock2CSV instance 에서 .generate(schema, recordNum, filePath); 를 호출하면 된다.

import { bcrypt, Schema, faker, moment, Mock2CSV } from "mock2csv";

let user_id = 1;
const pw = bcrypt.hashSync("password123", 10); // encrypt password (encryption is time-consuming)

const User: Schema = {
	userId: () => (user_id++).toString(),
	nickname: () => faker.internet.userName(),
	email: () => faker.internet.email(),
	password: () => pw,
	createdAt: () => moment().tz("Asia/Seoul").format("YYYY-MM-DDTHH:mm:ss.SSSSSSZ"),
	updatedAt: () => moment().tz("Asia/Seoul").format("YYYY-MM-DDTHH:mm:ss.SSSSSSZ"),
};

const m2c = new Mock2CSV();  

m2c.generate(User, 1e3, "user.csv");

배포

  • npm 에 배포하는 것은 처음이었다. typescript 를 만진지 꽤 되어 권장 설정으로 대충 만들어냈다.
  • 사소한 변경 사항(README.md 수정이나 package.json 수정)에도 새로운 버전을 publish 해야했다. 버전을 의미없이 많이 올렸는데, 조금 더 신중하게 수정 / 배포가 필요함을 느꼈다.
  • 단순한 코드이기는 하나, 이렇게 배포하는 것은 처음이라 색다른 경험(?) 이었다. 나 말고 사용할 사람이 더 있을지는 모르겠으나, 업데이트시에 이것저것 주의해야할 것들이 많이 보인다. (만들자마자 deprecated 되어버린 경우도 있었는데, 실제 누가 사용하는 라이브러리라면 곤란했을 것이다.)
  • typescript 에서 손 뗀지 시간이 좀 됐는데, 관련 configuration 잡는 것에 대해서도 다시 짚고 넘어갈 필요가 있었다.
  • 배포한 라이브러리는 아래에서 볼 수 있다.


Read more

대량의 더미 데이터 DB에 주입하기

대량의 더미 데이터 DB에 주입하기

대량의 더미 데이터 DB에 주입하기 현재 만들고 있는 토이 프로젝트에서는 대량의 데이터가 존재하는 DB 를 대상으로 여러가지 테스트를 수행한다. 테스트를 수행하기 위해서 더미 데이터를 주입할 방법에 대해서 모색해야했다. SQL Procedure * SQL 문으로 직접 테이블에 데이터를 주입한다. * 직접 Insert into () values (); 와 같은 구문을 작성할 수도 있지만, Procedure 혹은 function 을

By Sungjun Park
Lombok 에 대한 짧은 생각

Lombok 에 대한 짧은 생각

최근 Java / Spring 으로 스택을 변경하면서 작은 프로젝트를 하고 있다. 팀원의 추천으로 lombok 이라는 라이브러리를 사용하게 되었다. Lombok 을 쓰면 정말 간단하다. Getter, Setter, Constructor, equals, hashCode 과 같이 작성해야하지만 작성에 피로를 느끼는 코드들을 annotation을 통해서 너무 쉽게 만들 수 있다. 그런데, 이 라이브러리 사용할수록 필요성에 대한 의문이 생긴다. 생각을

By Sungjun Park
인증 인가 정보는 어디에 담아야할까? (cookie? authentication header?)

인증 인가 정보는 어디에 담아야할까? (cookie? authentication header?)

인증 인가 정보 전달 방법의 결정 요구사항 * HTTP 표준에 부합해야한다. * 보안 이슈를 최소화해야한다. * 클라이언트에서도 처리가 간편해야한다. * 모든 HTTP request 에서 처리 가능해야한다. * JWT, Session 모두 고려해야한다. 고려대상 Request Body * Request Body 에 auth 정보를 싣어 보내는 방법의 경우 GET HEAD DELETE TRACE 와 같은 HTTP Method 에서 사용할 수 없다.

By Sungjun Park