[태그:] 데이터 검증

  • 저장 항목을 바꿀 때, 예전 기록을 무엇으로 확인할까?

    가상의 물품 기록 앱을 생각해 보자. 이름을 담는 저장 항목 label을 display_name으로 바꾸고, 분류를 필수로 받기로 했다. 예전 기록 30건이 그대로 보이면 작업이 끝난 걸까? 같은 이름의 물품이 다른 보관함에 들어 있고, 예전에는 분류를 묻지 않았다면 건수만으로는 답할 수 없다.

    먼저 확인할 것은 같은 ID의 기록이 원래 값과 연결 관계를 유지하는가다. 이 글은 그 확인 방법을 설계하고 준비 과정에서 확인한 범위를 설명한다. 측정 실험은 실행하지 않았다(NOT RUN). 남은 전체 절차가 정한 작업 예산을 넘을 것으로 추정해 측정 전에 멈췄다. 어느 방식이 데이터를 보존했거나 중단 뒤에 안전했다는 결과는 없다.

    이 글의 목차

    코드를 요청하기 전에, 바뀌어도 같아야 할 것을 적는다

    ID는 기록을 구별하는 번호다. 화면에 보이는 이름이 같아도 ID가 다르면 다른 기록이다. 여기서 바꾸려는 것은 저장 칸의 이름이지, 그 안에 든 물품 이름이 아니다. 보관함 이름이 같아도 연결 대상의 ID까지 같다는 뜻은 아니다.

    준비한 자료는 실제 고객 정보가 없는 SQLite 데이터베이스다. 물품 기록 30건과 보관함 기록 3건을 따로 만들었다. 아래는 전체 기대값 표에서 고른 7건, 6가지 사례 묶음이다. 표는 이름과 보관함 ID를 추린 것으로, 변경 전 값과 변경 후의 기대값이지 실행 결과가 아니다. 보관함 ID도 변경 전후 같아야 한다.

    표 1 · 코드를 요청하기 전에, 바뀌어도 같아야 할 것을 적는다
    ID 기존 이름 → 기대 이름 유지할 보관함 ID 확인할 이유
    107
    공용 / Shared → 동일
    10
    같은 이름의 첫 기록
    114
    공용 / Shared → 동일
    20
    이름이 같아도 다른 기록·보관함
    128
    물건 / Item 04 → 동일
    10
    메모의 NULL도 그대로 유지
    135
    NULL → NULL
    20
    값 없음 보존
    142
    "" → ""
    30
    빈 문자열 보존
    149
    물건 / Item 07 → 동일
    NULL
    보관함 미지정 보존
    310
    물건 / Item 30 → 동일
    30
    마지막 기록도 검사

    전체 검사 설계는 30건의 ID 집합, 이름과 메모의 NULL·빈 문자열 구분, 새 분류, 보관함 연결, 보관함 자체의 값까지 비교한다. ID가 빠지거나 새로 생긴 경우도 확인한다. 일곱 예시는 전체 검사를 대신하지 않는다. 이번 자료에서 기대하는 연결은 26건이고 미지정은 4건이다. 존재하는 보관함을 가리키더라도 원래와 다른 보관함이면 의미가 달라진다. SQLite의 외래 키 검사는 관계 제약 위반을 찾는 검사다. 의도한 상대인지 판단하려면 이처럼 별도의 기대값이 필요하다.

    새 분류의 기대값은 30건 모두 unknown이다. 이유는 “이전 버전에서 수집하지 않음”이다. 이 앱의 설계가 그 상태를 허용하기 때문에 가능한 선택이다. 실제 분류가 반드시 필요한 업무라면 권위 있는 자료로 확인하거나 입력을 다시 받아야 하며, 확인할 수 없으면 전환을 멈출 수 있다. 이름이나 메모에서 추측해 채우라는 뜻이 아니다.

    NULL은 여기서 저장된 값이 없음을, ""는 길이 0인 문자열이 저장됐음을 뜻한다. 둘은 기존 이름·메모의 상태다. 새 분류의 unknown은 수집하지 않았다는 명시적 정책이고, 다른 앱에서 선택할 수 있는 ‘기타’와도 다르다. 이 예시의 분류에는 ‘기타’가 없다. SQLite의 제약 설명은 CREATE TABLE 문서에 있다. 제약을 만족해도 과거 사실의 근거가 생기지는 않는다는 것은 이 글의 판단이다.

    무엇을 비교하려 했나

    설계한 두 방식은 일부러 정한 교육용 정책이다. 실제 AI가 흔히 만드는 코드의 표본이 아니다.

    • 같은 열 이름 복사(N): 같은 이름의 칸만 옮기므로 새 display_name에는 NULL을 넣고, 필수 분류에는 모든 기록에 같은 supplies를 넣도록 정했다. 허용된 분류 값이지만 과거 분류의 근거는 없다. ID·메모·보관함 연결은 복사하고 한 건 옮길 때마다 저장을 확정하도록 했다.
    • 명시적 대응(P): label → display_name을 지정하고 ID·메모·연결을 유지하며, 새 분류에 unknown을 넣도록 정했다. 테이블을 다시 만들고 복사하는 변경을 한 단위로 묶는 트랜잭션과 재실행 정책을 함께 설계했다.

    N의 결함은 처음부터 공개된 조건이다. P도 구현과 검사에서 틀릴 수 있다. 두 방식은 대응 규칙, 확정 단위와 재실행 처리 등을 함께 바꾸므로 각 요소의 효과를 따로 측정하는 비교가 아니다. SQLite의 트랜잭션 문서가 설명하는 변경 단위와, 옮길 값이 맞는지는 별개다. 이 설계는 새 테이블에 옮긴 뒤 옛 테이블을 지우는 방식을 택했지만 열 이름 변경이 언제나 그 절차를 요구하는 것은 아니다.

    기대값 표는 변환 코드보다 먼저 고정하고, 별도 검사기가 변경 후 데이터를 읽어 비교하도록 준비했다. 변환 코드가 정답 표를 읽어 결과를 만들거나 통과 여부를 결정해서는 안 된다. 다만 아직 실행하지 않은 코드 생성 환경에는 설정·인증 파일과 일부 실행 환경 접근 등의 예외가 남아 있어 정답을 볼 수 없도록 완전히 분리된 연구라고 부를 수 없다.

    계획은 원본을 보존한 일회용 복사본 네 개에서 N/P 각각 정상 실행 한 번과 중단 실행 한 번을 하는 것이었다. 중단 지점은 새 테이블에 15번째 기록을 넣은 뒤, 옛 테이블을 지우기 전으로 고정했다. 이후 안전하게 검사 가능한 정상 완료 사본과 중단 후 복구 사본 각각에서 같은 코드를 최대 한 번 재실행하도록 했다. 이 실행과 중단·재실행은 모두 하지 않았다. 한 지점의 프로세스 종료 시험을 했더라도 정전, 하드웨어 손상, 옛 테이블 삭제 이후의 실패까지 설명할 수는 없다.

    화면과 저장된 기록은 따로 확인한다

    준비한 화면 검사 설계는 이전 이름으로 읽는 화면, 새 이름으로 읽는 화면, 읽는 코드만 새 이름에 맞게 고친 예전 화면을 구분했다. 화면 조회 오류만으로 데이터가 사라졌다고 단정할 수 없다. 반대로 읽기 코드를 고친 화면이 맞게 보이더라도 수정하지 않은 예전 앱의 호환성을 입증하지 못한다.

    중단 뒤에도 상태와 다음 행동을 나눠 적어야 한다. 온전한 이전 구조로 돌아온 것은 새 구조로의 전환 완료가 아니다. 부분적으로 바뀐 상태나 판독할 수 없는 상태는 그대로 기록한다. 재실행을 거부하고 아무것도 바꾸지 않은 것, 다시 실행해 전환을 마친 것, 이미 완료된 상태를 별도 검사로 확인하고 변경하지 않은 것은 구분해야 한다. 이번 설계는 다른 쓰기 작업을 멈춘 작업 시간을 전제로 했고 읽기 전용 화면을 대상으로 했다. 예전 앱과 새 앱의 동시 쓰기나 무중단 전환은 검증 대상이 아니다.

    준비에서 확인한 것과 측정하지 않은 이유

    2026년 10월 12일 준비 기록에는 원본과 검사 도구를 점검한 오프라인 검사 19개 통과가 남아 있다. 합성 원본 30건이 기대값 표의 입력과 맞는지, 별도 검사기가 일부러 바꾼 이름·분류·관계·메모·NULL/빈 문자열을 잡는지 확인했다. 정상 대조용 새 데이터베이스는 기대값에서 직접 만들었다. 원본을 새 구조로 변환한 결과가 아니며 N/P의 성적도 아니다. 당시 환경은 Python 3.9.6과 Python에 연결된 SQLite 3.54.0이었다.

    10월 26일 독립 검토는 후보 파일 144개의 파일 크기·내용 지문(해시) 일치와 오프라인 검사 실행 34회의 통과를 기록했다. 34회에는 반복이 있어 서로 다른 사례 34개를 뜻하지 않으며, 앞선 19개와 더할 누적 성적도 아니다. 검토 당시에는 새 작업 사본에서 원본 보호용 파일 권한이 쓰기 가능 상태로 나타나 준비 판정이 막혔다. 이후 선택된 기준 사본을 읽기 전용으로 바꾸고 고정한 입력·권한·파일 분리 검사를 다시 통과한 기록을 바탕으로 설계만 고정했다. 이후에도 사용할 때마다 권한과 입력을 다시 확인해야 한다.

    11월 5일 운영 결정은 남은 생성·실행·검증에 약 75분이 필요하지만, 60분 준비·실험 허용량 중 약 29분을 이미 썼다고 정리했다. 이는 작업 예산의 추정이며 데이터 이전 속도의 측정값이 아니다. 예정한 전체 절차가 남은 허용량을 넘을 것으로 추정해 방법론 글로 전환했다. 축소한 측정도 실행하지 않았으며, 더 작은 검사가 모두 불가능하다는 뜻은 아니다. N/P 코드 생성, 실제 변환, 중단·재실행, 변환 결과 검증과 화면 관찰은 모두 NOT RUN이다. 오프라인 준비가 끝났다는 기록으로 이 빈칸을 채울 수 없다.

    본문의 SQLite 문서 설명은 2026년 10월 11일 저장한 확인 기록에 근거하며, 발행 전 재확인이 필요하다.

    독자가 가져갈 것은 승자가 아니라 요청서다. “각 ID의 기존 값과 기대하는 새 값을 표에 나란히 적고, 새 필수 값의 근거와 유지할 연결 대상 ID를 밝혀 주세요. 중단 후 재실행·거부 기준과 화면 확인 방법도 별도로 써 주세요. 시험하지 않는 실패 상황을 밝히고, 준비 검사와 실제 변환 결과를 구분해 주세요.” AI가 돌려준 ID별 대조표부터 검토하자는 것이 이 글의 제안이다. 실제 보존 여부는 그 기준에 따른 실행과 기록이 있어야 답할 수 있다.

    이 글은 AI를 활용해 자료 조사와 초안 작성을 보조했으며, 작성자가 최종 원고를 검토하고 수정 방향을 결정했습니다.