DB 데이터를 Hadoop에 저장 시 삽질 두가지.
최근 SQLServer에 저장된 데이터를 HDFS로 저장하는 작업을 하고 있습니다. 최근까지 잘 동작하는 프로그램에 다음 두가지 문제가 발생하여 삽질한 내역을 공유합니다.
- 데이터 건수가 맞지 않는 문제
- 잘못된 일자가 저장되는 문제
DB to HDFS
DB에 저장되고 있는 데이터를 HDFS에 저장할 때 많이 사용하는 방법이 DB의 레코드 중에 생성 일자 또는 수정 일자를 기준으로 데이터를 조회하여 결과를 HDFS에 저장하는 방법입니다. 예를 들어 테이블이 다음과 같은 경우 created_at 또는 updated_at 필드를 이용하여 데이터를 조회합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13
+------------+----------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+----------+------+-----+---------+----------------+ | data | varcahr(255) | YES | MUL | NULL | | | created_at | datetime | NO | | NULL | | | updated_at | datetime | NO | | NULL | | +------------+----------+------+-----+---------+----------------+ String lastFetchDateTime = getLastFetchDate(); String currentDateTime = getCurrentCurrentDateTime(); ResultSet rs = stmt.executeQuery("select * from table where created_at between " + lastFetchDate + " and " + currentDateTime); where (rs.next()) { out.write(rs.getString("data")); }
데이터가 생성된 이후 수정되는 데이터라면 조금 더 복잡하게 처리할 필요가 있지만 여기서는 그런 이슈가 아니기 때문에 논외로 하겠습니다.
데이터 건수가 한두건씩 누락되는 상황이 발생하여 누락이 발생한 데이터의 패턴을 찾아보려고 노력했습니다. 프로그램에서 가장 에러가 많이 발생하는 부분이 경계 값 근처라는 생각을 가지고 일자에 집중해서 확인을 해보니 누락된 대부분의 레코드가 배치 추기 근처에 있는 레코드들이었습니다. 이를 근처로 다음과 같은 가설을 세워봤습니다.
- 첫번째 가설: DB 서버 또는 Application Server와 마이그레이션 프로그램이 실행되는 서버 사이에 시간이 맞지 않는 경우
- 모든 서버 확인 결과 1초 이내 오차만 있음
- 두번째 가설: 다음 표와 같은 타임라인으로 서로 프로그램이 실행된 경우
번호 | 업무 처리 Server | 마이그레이션 프로그램 |
1 | 2017.04.20 23:59:50 에 트렌젝션 시작 | |
2 | 2017.04.20 23:59:51 에 테이블에 레코드 1개 저장 | |
3 | 2017.04.21 00:05:00에 마이그레이션 프로그램 시작
(처리 대상 데이터는 2017.04.20 00:00:00.000 ~ 2017.04.20 23:59:59.999) |
|
4 | 2017.04.21 00:00:10에 트렌젝션 종료
DB에 저장됨 |
|
5 | 2017.04.22 00:05:00에 마이그레이션 프로그램 시작
(처리 대상 데이터는 2017.04.21 00:00:00.000 ~ 2017.04.21 23:59:59.999) |
2번에서 저장된 데이터는 아직 트렌젝션이 종료되지 않았기 때문에 3번 마이그레이션 프로그램에 의해 실행된 select 질의에는 조회되지 않습니다. 따라서 3번에 실행된 마이그레이션 프로그램에서는 2번에서 저장된 레코드는 반영이 안됩니다. 5번 다음 마이그레이션이 실행되어도 2번에 생성된 데이터는 일자가 이미 이전 마이그레이션 프로그램에 의해 처리된 일자이기 때문에 select 문의 대상이 아니기 때문에 마이그레이션 되지 않습니다. 결국 2번에서 생성된 레코드는 마이그레이션이 안되는 문제가 있습니다.
이 가설의 경우 마이그레이션 프로그램 개발 및 스케줄링 시 3번과 같이 정각이 아닌 5분에 시작하도록 하였습니다. 이것때문에 이 가설이 문제를 발생시킨 가설이 될 것이라고 생각하지 않았습니다. 하지만 DB에 데이터를 저장하는 시스템에서도 가끔 대량으로 데이터를 입력하는 상황이 발생하는데 이때에는 5분 이상 트렌젝션을 발생시킬 수 있기 때문에 이런 문제가 발생하였습니다.
필자가 운영 중인 시스템의 경우 요구사항 수준이 높지 않아서 운영 DB와 HDFS에 저장되는 시차가 수시간 또는 하루 단위 이기 때문에 문제 해결이 쉬웠습니다. 마이그레이션 프로그램에서 대상 데이터의 시간을 1시간 이전 데이터를 처리하도록 조정하여 해결하였습니다. 하지만 요구사항이 수분내의 데이터를 옮겨야 하는 상황이라면 해결 방법이 조금 더 복잡해져야 합니다. 대략 이전 시간까지 포함해서 조회하여 HDFS에 저장하고 다시 Uniq한 값을 조회해서 마지막 최종 값으로 저장하는 등의 방법도 한가지 방법이 될 것 같습니다.
String to Date
두번째 문제는 HDFS에 저장된 데이터를 다시 DB에 넣을 때 발생하는 문제입니다. DB에 넣고 원본 데이터와 비교하니 Date 타입의 컬럼으로 저장된 데이터 중에 일부 다른 값이 저장되 데이터가 발견되었습니다. 이 데이터를 원본 데이터와 비교하니 원본에서는 Date 타입이 아니라 문자열 타입으로 지정되어 있었고 "20170432" 와 같이 잘못된 일자 값이 저장된 경우였습니다. 다음 코드와 같이 방어 코드가 삽입되어 있음에도 잘못된 값으로 저장되어 있었습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13
DateFormat yyyymmdd = new SimpleDateFormat("yyyyMMdd"); java.sql.Date birthdayDate = null; String birthday = null; try { birthday = rs.getString("birthday"); if (birthday != null) { birthdayDate = new java.sql.Date(yyyymmdd.parse(birthday).getTime()); } } catch (ParseException e) { LOG.warn("Wrong date format: " + birthday); } // Parse 에러가 발생한 경우 null로 저장 pstmt.setDate(1, birthdayDate);
"20170432"의 경우 마이그레이션 된 데이터에는 "20170502" 가 저장되어 있었습니다. 문제 해결을 위해 약간의 구글링과 DateFormat 관련 API를 확인해보니 "setLenient" 라는 메소드가 있는 것을 발견하였습니다. 이 API 를 확인해보니 DateFormat의 parse() 메소드는 날짜가 잘못 되어 있어도 이를 알아서 보정해주는 기능을 가지고 있습니다. 즉 4월 32일의 의미를 5월 2일로 자동으로 해석하는 기능을 가지고 있습니다. 이것을 enable/disable 시키는 메소드가 setLenient 메소드입니다. 아래 코드와 같이 작성하면 parse() 수행 시 ParseException 발생합니다.
1 2 3 4
DateFormat yyyymmdd = new SimpleDateFormat("yyyyMMdd"); yyyymmdd.setLenient(false); //다음 라인에서 ParseException 발생 System.out.println(yyyymmdd.parse("20170432"));
마치며
많은 데이터를 여기 저기로 옮기다 보면 데이터의 유실, 변환 과정에 발생하는 데이터의 오류 등을 원인을 찾는데 어려움을 겪고 많은 시간을 보내게 됩니다. 이런 경우 필자는 주로 문제가 발생한 데이터가 공통적으로 어떤 특징을 가지고 있는지를 먼저 찾아 봅니다. 공통적인 특징들 중에서 경계 값에 걸치는 특징은 없는지, 범위를 벗어나는 데이터는 없는지 등을 먼저 찾습니다. 데이터 작업의 90%는 데이터를 모으고 변환하는 등의 전처리 작업이라고 할 수 있습니다. 이런 삽질을 줄이는 방법을 찾아봐야 할 것 같은데 현재까지는 미리 삽질을 통해 경험해봐야 다음 번에는 줄일 수 있는