레거시를 옮기며 (2) 쿼리를 두 벌로 나눴습니다

데이터베이스를 하나 더 지원하면서 쿼리를 바꿔 쓸 생각이었습니다. 문법은 통과하는데 결과가 다른 것들이 나왔고, 결국 쿼리를 두 벌로 나눠 들고 가기로 했습니다.

쿼리를 두 벌로 나눴습니다 — 오픈소프트랩 기술블로그
데이터베이스를 하나 더 지원하면서 겪은 일입니다. 시리즈의 다른 글 보기

안녕하세요. 오픈소프트랩 개발팀입니다.

지난 편에서 기반을 한꺼번에 바꾼 이야기를 했습니다. 그 작업에서 가장 오래 걸린 게 데이터 접근 쪽이었습니다.

바꿔 쓰면 될 줄 알았습니다

상황은 이랬습니다. 기존 제품은 한 종류의 데이터베이스를 전제로 만들어져 있었는데, 다른 종류를 쓰는 고객이 생겼습니다.

처음 계획은 단순했습니다.

쿼리 대부분은 표준에 가깝다. 몇 개만 고치면 된다.

실제로 세어보니 그럴듯했습니다. 단순 조회와 입력은 그대로 동작했습니다. 문제가 되는 건 소수였고, 변환 도구를 돌리면 그중 상당수가 자동으로 처리됐습니다.

여기까지는 계획대로였습니다.

문법은 통과하는데 결과가 달랐습니다

문제는 오류가 나는 쿼리가 아니었습니다. 오류가 나면 바로 알고 고칩니다. 어려운 건 정상적으로 실행되는데 값이 다른 쿼리였습니다.

나온 것들을 정리하면 이렇습니다.

빈 문자열과 빈 값

한쪽에서는 길이 0인 문자열을 저장하면 빈 값으로 취급합니다. 다른 쪽은 길이 0인 문자열 그대로 둡니다. 그래서 값이 비었는가를 묻는 조건이 양쪽에서 다르게 걸립니다.

이게 무서운 건 대부분의 경우 티가 안 난다는 겁니다. 실제로 빈 문자열이 들어간 행이 있을 때만 결과가 갈립니다. 테스트 데이터에는 그런 행이 없었습니다.

정렬 순서

정렬 기준을 명시하지 않은 조회가 꽤 있었습니다. 한쪽에서는 늘 같은 순서로 나오길래 정렬이 된다고 믿고 쓴 화면들입니다. 데이터베이스가 바뀌자 순서가 달라졌고, 목록 첫 줄을 기준으로 동작하던 기능이 다른 값을 집었습니다.

이건 사실 원래부터 잘못된 코드였는데, 한쪽에서만 돌려보는 동안에는 드러나지 않았습니다.

날짜와 시간

날짜를 다루는 함수 이름과 형식이 달랐습니다. 이건 눈에 보여서 금방 고쳤는데, 시간대 처리에서 한 번 더 걸렸습니다. 같은 값을 넣어도 저장되는 시각이 몇 시간 어긋나는 경우가 있었습니다.

페이징

목록을 잘라 가져오는 방식이 달랐습니다. 문법만 바꾸면 되는 줄 알았는데, 정렬이 불안정한 상태에서 페이지를 나누니 같은 행이 1페이지와 2페이지에 동시에 나오는 경우가 생겼습니다. 위의 정렬 문제와 겹쳐서 원인을 찾는 데 오래 걸렸습니다.

변환하는 대신 나누기로 했습니다

몇 건 겪고 나서 접근을 바꿨습니다.

원래는 쿼리 하나를 양쪽 다 되게 고치려 했습니다. 조건 분기를 넣거나, 양쪽에서 다 통하는 표현으로 바꾸는 식입니다. 이걸 계속하니 쿼리가 읽기 어려워졌습니다. 그리고 한쪽을 고칠 때마다 다른 쪽을 다시 확인해야 했습니다.

그래서 쿼리를 통째로 두 벌로 나눴습니다. 기존 쿼리는 그대로 두고, 새 데이터베이스용 쿼리를 따로 만들어 8만 줄 넘게 추가했습니다. 실행할 때 설정에 따라 한쪽을 읽습니다.

  mapper/
    oracle/      <- 기존 그대로
    mariadb/     <- 새로 추가

업무 로직은 어느 쪽인지 모릅니다. 목록을 가져온다까지만 알고, 그게 어떤 문장으로 나가는지는 아래에서 정해집니다.

얻은 것과 포기한 것

얻은 것 — 각 데이터베이스에 맞는 문장을 쓸 수 있게 됐습니다. 양쪽에서 다 통하는 표현을 찾느라 비효율적인 쿼리를 쓰는 일이 없어졌습니다. 그리고 한쪽을 고칠 때 다른 쪽이 깨질 걱정을 안 합니다.

포기한 것 — 같은 쿼리를 두 번 씁니다. 기능을 추가하면 양쪽에 다 넣어야 하고, 한쪽만 고치고 끝내는 실수가 실제로 나왔습니다. 조회 화면 하나가 한쪽 고객에게만 옛 결과를 보여준 적이 있습니다.

이때 검사 장치를 따로 두지는 않았습니다. 양쪽을 같이 여는 건 사람이 기억해야 하는 일이었고, 기억이 안 나는 날에 위의 일이 생겼습니다. 지금 보면 쿼리 이름 목록을 맞춰보는 정도라도 자동으로 돌렸어야 했습니다. 이름만 대조해도 한쪽에만 새로 생긴 쿼리는 잡혔을 겁니다.

두 벌은 설계로 정리되지 않았습니다

이 글을 여기서 끝내면 "두 벌을 잘 관리했다"는 이야기가 되는데, 사실이 아닙니다.

전환 이후 오라클을 쓰는 고객사가 눈에 띄게 줄었습니다. 그래서 두 벌을 계속 들고 갈 일 자체가 사라졌습니다. 저희가 구조로 푼 게 아니라 환경이 바뀐 겁니다.

대신 다른 게 왔습니다. 오라클 자리에 티베로나 큐브리드가 들어오는 사업이 생겼고, 그때마다 오라클용 쿼리를 다시 정리해서 그 데이터베이스에 맞췄습니다. 비슷한 계열이라 통째로 다시 쓰지는 않았지만, 위에 적은 네 가지 증상은 매번 다시 확인해야 했습니다. 한 번 겪었다고 다음이 빨라지지는 않았습니다.

지금은 거의 전부 마리아DB를 쓰거나 데이터베이스를 특정하지 않는 조건으로 들어갑니다. 쿼리 세트를 여러 벌 들고 다니던 시기는 지나갔습니다.

다음에 또 오면

정리하면 저희는 이 문제를 푼 게 아니라 지나온 것에 가깝습니다. 새 데이터베이스를 요구하는 사업이 하나만 들어와도 같은 자리로 돌아갑니다.

그래서 지금 하고 있는 건 두 가지입니다.

하나는 쓰는 문법의 범위를 좁히는 것입니다. 새로 쓰는 쿼리는 특정 데이터베이스에만 있는 기능을 되도록 안 씁니다. 그러면 옮길 때 손대야 할 자리가 줄어듭니다. 다만 이건 성능과 맞바꾸는 면이 있어서, 한 번에 끝날 일을 나눠서 해야 할 때가 생깁니다. 어느 선까지 양보할지는 지금 화면별로 판단하고 있고, 사례가 쌓이는 대로 기준을 문장으로 정리할 생각입니다.

다른 하나는 위의 네 가지를 점검 목록으로 만들어 둔 것입니다. 빈 값, 정렬, 날짜, 페이징. 새 환경에 처음 올릴 때 이 네 가지부터 봅니다. 특히 정렬은 원래부터 잘못돼 있었지만 한 환경에서만 돌려서 안 보이던 것이라, 같은 종류가 더 있을 거라고 보고 있습니다.

자동으로 잡는 일은 지금 준비하고 있습니다. 두 환경에 같은 데이터를 넣고 결과를 맞춰보는 방식인데, 비교할 데이터를 처음부터 만들면 본 작업만큼 커집니다. 그래서 실제 조회에서 나온 결과를 그대로 기준 삼는 쪽으로 방향을 잡았습니다. 목록 화면부터 하나씩 늘려가고 있습니다.


여기까지가 AI 이전의 이야기입니다. 다음 시리즈에서는 이 시스템들 위에 생성형 AI가 올라왔을 때 무엇이 달라졌는지 다룹니다. — AI 정보보호 설계

오픈소프트랩 개발팀이 작성합니다. 보안 운영 포탈과 생성형 AI 사용 통제를 만들고 있습니다.