본문으로 건너뛰기

테이블이 있는데 없다고 했다: Oracle 기동 오류를 좁혀간 기록

· 약 6분
김성연
AI Research Engineer, Braincrew

애플리케이션은 테이블이 없다고 판단했다. 테이블을 만들려고 하자 Oracle은 권한이 없다고 응답했다. 그런데 테이블은 이미 있었다.

Deep Connector를 온프레미스 환경에 옮기던 중 만난 문제다. 오류를 수정해 이미지를 다시 만들었는데도 기동 로그에는 같은 ORA-01031: insufficient privileges가 남았다. 하나를 고칠 때마다 다른 조건이 드러났다. 결국 오류를 처리하는 코드보다 먼저 살펴봐야 했던 것은 “이 계정에서 무엇이 보이는가”였다.

이 글은 당시 개발 기록을 재구성한 회고다. 고객사, 작업 날짜, 실제 계정명과 배포 식별자는 제외했다. 아래 계정과 테이블 이름은 설명용 가명이며 SQL은 진단 예시다. 글 작성 과정에서 운영 환경이나 Oracle 재현 실험을 다시 실행한 것은 아니다.

권한 오류가 아니라 존재 판정부터​

기동 코드의 의도는 단순했다. 필요한 테이블을 확인하고, 없으면 생성한다. 개발 환경에서는 애플리케이션이 테이블을 소유하고 DDL도 실행할 수 있어서 자연스러운 흐름이었다.

테이블 존재 여부 조회
-> 있으면 계속 기동
-> 없으면 CREATE TABLE

다른 환경에서는 DBA가 스키마를 미리 만들고 애플리케이션 계정에는 필요한 데이터 접근 권한만 부여할 수 있다. 이때 존재 판정을 잘못하면, 이미 있는 테이블을 만들려는 코드가 실행된다. 마지막에 보이는 오류는 권한 부족이지만 앞에서 잘못 내린 판단은 테이블 부재다.

권한을 더 주는 방식으로 접근하면 불필요한 생성 시도 자체는 남는다. 먼저 테이블이 없다고 판단한 조회를 확인해야 했다.

대소문자가 만든 첫 번째 오판​

Oracle은 따옴표로 감싸지 않은 식별자를 대문자로 해석한다. 예를 들어 다음 DDL의 테이블 이름은 카탈로그에 APP_SETTINGS로 저장된다.

CREATE TABLE app_settings (
id NUMBER PRIMARY KEY
);

반면 아래 조회에서 'app_settings'는 식별자가 아니라 문자열 리터럴이다. 대문자로 저장된 이름을 이 조건으로 찾으면 일치하지 않는다.

SELECT table_name
FROM user_tables
WHERE table_name = 'app_settings';

당시 기록에는 이 차이 때문에 존재 확인이 실패하고 생성 경로로 넘어간 과정이 남아 있다. DDL의 이름 규칙과 애플리케이션의 식별자 처리를 맞추는 수정이 필요했다.

그렇다고 대소문자만 맞추면 끝나는 문제는 아니었다. 이름을 비교하기 전에, 조회 대상에 그 테이블이 들어오는지부터 따져야 했다.

USER_TABLES는 내가 접근할 수 있는 테이블 목록이 아니다​

설명용 계정으로 구조를 줄이면 다음과 같다.

DATA_OWNER   테이블 소유 계정
APP_CLIENT 애플리케이션 접속 계정

APP_CLIENT가 DATA_OWNER의 테이블을 조회할 권한이 있다고 해서 그 테이블의 소유자가 되는 것은 아니다. Oracle의 USER_TABLES는 현재 사용자가 소유한 테이블을 보여준다. 접근 가능한 테이블을 보여주는 ALL_TABLES와 범위가 다르다.

앱이 APP_CLIENT로 접속한 뒤 USER_TABLES에서 대상 테이블을 찾으면 결과가 없을 수 있다. 테이블이 없어서가 아니라 다른 계정이 소유하고 있기 때문이다.

-- 애플리케이션이 사용하는 계정으로 실행하는 진단 예시
SELECT USER FROM dual;

SELECT owner, table_name
FROM all_tables
WHERE LOWER(table_name) = 'app_settings';

두 조회를 함께 보면 접속 사용자와 접근 가능한 테이블의 소유자를 비교할 수 있다. 같은 이름의 테이블이 여러 스키마에 있을 수 있으므로, 결과를 확인한 뒤에는 의도한 소유자까지 조건에 넣어야 한다.

여기서도 결과가 없다는 이유만으로 테이블이 없다고 단정하면 안 된다. ALL_TABLES 역시 현재 사용자에게 접근 가능한 테이블만 보여준다. 객체 부재와 접근 권한 부족을 구분하려면 DBA의 확인이 필요할 수 있다.

CURRENT_SCHEMA를 바꿔도 소유자가 되지는 않는다​

소유자와 접속 사용자가 다르면 CURRENT_SCHEMA 변경이나 시노님을 떠올리기 쉽다. 둘 다 객체 이름을 해석하는 문제를 풀 때 유용하다.

-- 실제 계정명이 아닌 설명용 예시
ALTER SESSION SET CURRENT_SCHEMA = DATA_OWNER;

하지만 이 설정은 접속 사용자를 바꾸지 않는다. 추가 권한을 부여하지도 않는다. 소유자를 생략한 객체 이름을 어느 스키마에서 찾을지 바꾸는 것과, USER_TABLES가 보여주는 소유 관계는 별개다.

시노님도 마찬가지다. 다른 스키마의 테이블을 편하게 참조할 이름을 만들 수 있지만, 그 테이블이 접속 계정의 소유가 되는 것은 아니다.

당시 존재 판정은 여전히 USER_TABLES를 보고 있었다. 데이터 조회의 이름 해석을 고쳐도 기동 점검은 계속 테이블이 없다는 결론에 도달했다.

예외를 삼키는 수정도 같은 전제에 묶였다​

다음 시도는 생성에 실패하더라도 테이블이 이미 있으면 계속 기동하는 방식이었다. 하지만 “이미 있다”는 재확인에 같은 카탈로그 조회를 쓰면 결과도 같다.

CREATE TABLE 실패
-> 기존 테이블인지 다시 확인
-> USER_TABLES에서는 여전히 찾지 못함
-> 오류 전파

기동 DDL을 실행하는 함수도 하나가 아니었다. 한 경로의 예외 처리를 고쳤다고 다른 스키마 초기화 경로까지 바뀌는 것은 아니었다. 같은 오류 번호만 따라가다 보니, 수정한 함수가 실제 실패 지점인지와 그 함수의 조회 전제가 맞는지를 따로 확인해야 했다.

이 시점부터 질문을 바꿨다. “어떤 오류를 무시할까”가 아니라 “이 배포에서 스키마를 관리하는 주체는 누구인가”를 먼저 정리했다.

스키마 관리와 애플리케이션 기동의 분리​

애플리케이션이 스키마를 관리하는 환경이라면 마이그레이션이나 초기화 DDL이 필요할 수 있다. 반대로 DBA가 미리 준비한 외부 스키마에 접속하는 환경에서는 같은 기동 경로를 그대로 적용하기 어렵다.

당시 수정은 소유자가 분리된 외부 스키마를 명시하고, 연결에서 사용할 스키마를 설정하며, 그 모드에서 기동 시 추가 DDL을 실행하지 않는 방향이었다. 소유자를 인식하도록 카탈로그 조회를 모두 바꾸는 방법도 검토했지만, 이 환경에서는 앱이 스키마를 수정하는 주체가 아니었다.

이것이 모든 스키마 검증을 없애도 된다는 뜻은 아니다. 필요한 테이블과 컬럼이 준비됐는지 확인하는 절차는 별도로 필요하다. DDL을 실행하지 않는 것과 올바른 스키마임을 확인하는 것은 다른 일이다. 명시적으로 스키마 동기화를 켜는 설정도 외부 스키마 운영 방식과 충돌하지 않는지 점검해야 한다.

로컬에 재현해야 했던 것은 데이터보다 권한 구조​

코드만 보면 수정이 맞아 보이는 순간이 여러 번 있었다. 확인이 부족했던 것은 실제 연결에서 그 코드가 어떻게 동작하는지였다.

당시에는 로컬 Oracle 컨테이너에 소유 계정과 접속 계정을 분리해 만들고, 애플리케이션 계정에 필요한 데이터 접근 권한을 부여한 환경을 구성했다. 같은 자리에서 수정 전후의 기동 경로와 엔티티 조회를 비교했다.

테이블을 만들고 조회하는 테스트만으로는 부족했다. 다음 조건이 함께 있어야 원래의 실패를 재현할 수 있었다.

  • 테이블 소유자와 애플리케이션 접속 사용자가 다르다.
  • 애플리케이션 계정은 테이블을 생성할 권한이 없다.
  • 테이블은 미리 존재하고 앱에는 필요한 데이터 접근 권한이 있다.
  • 실제 연결과 기동 코드를 거쳐 존재 확인과 엔티티 조회를 실행한다.

이 재현은 연결 풀의 세션 설정이 ORM을 거쳐 실제 연결에 적용되는지 확인하는 데도 쓰였다. 가짜 카탈로그 응답으로 분기만 통과시키는 테스트와는 확인하는 대상이 달랐다.

이 글에 새 실측값을 싣지 않은 이유도 같다. 당시 재현 기록과 지금 다시 실행한 결과를 섞으면, 독자는 어느 버전과 환경에서 검증했는지 알 수 없게 된다.

코드 수정은 설정 전달까지 이어져야 했다​

식별자 처리처럼 기존 배포에 영향을 주는 변경은 기본 동작을 유지하고 설정으로 켜도록 만들 수 있다. 그런데 새 기능이 이미지에 들어 있어도 설정이 꺼져 있으면 동작은 달라지지 않는다.

당시에는 설정 템플릿의 주석과 배포 전 검사에 빈틈이 있었다. 새 이미지를 넣는 것만으로 수정이 적용됐다고 생각하기 쉬운 상태였다.

그래서 환경변수를 추가할 때 코드뿐 아니라 템플릿과 사전 검사도 함께 바꾸는 것을 재발 방지 항목으로 남겼다. 특히 외부 스키마를 쓰면서 자동 동기화를 켜는 것처럼 충돌하는 설정은 기동 전에 드러나야 한다.

같은 오류가 다시 나오면​

ORA-01031이 다시 보인다면 곧바로 권한을 추가하거나 예외를 무시하는 코드를 넣기보다, 다음 순서로 조사하려 한다.

  1. 애플리케이션이 실제로 접속한 사용자를 확인한다.
  2. 대상 테이블의 소유자와 저장된 식별자 표기를 확인한다.
  3. 존재 판정이 USER_TABLES와 ALL_TABLES 중 무엇을 조회하는지 확인한다.
  4. 실패한 DDL을 실행한 기동 경로와 스키마 관리 주체를 확인한다.
  5. 수정 코드뿐 아니라 필요한 설정이 전달됐는지 확인한다.

테이블이 있는데 없다고 판단한 문제를 CREATE 권한으로 풀려고 하면, 잘못된 존재 판정은 그대로 남는다. 이번 경험에서 바뀐 것은 오류를 처리하는 순서였다. 마지막 오류보다 먼저, 실패한 DDL을 실행하게 만든 판단을 확인한다.

References​