더미 데이터 생성기 - Mock2CSV

더미 데이터를 CSV 로 만드는 간단한 라이브러리 만들기

더미 데이터 생성기 - Mock2CSV
Photo by Paul Esch-Laurent / Unsplash

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 잡는 것에 대해서도 다시 짚고 넘어갈 필요가 있었다.
  • 배포한 라이브러리는 아래에서 볼 수 있다.