이거 때문에 일주일 고생했다..
SQLCA?
Object language bindings (SQL/OLB)라는 것이 있다. 다른 이름으론 embedded SQL이란 것인데(나는 Esql이라 부른다.), 일반적으로 디비를 접근하기 위해 개발자들은 ODBC JDBC..뭐 이런 것들을 사용하지만, 핸들을 다루는 커넥터 API기 때문에 사실 쓰기 좀 불편한 면이 있다.
단순히 SELECT 1 FROM DUAL; 하나 보자고 DBC핸들 만들고...CONNECT핸들 만들고...STATEMENT 핸들 만들고..
이런 과정은 좀 과하게 복잡하고, JDBC면 그나마 낫겠지만 ODBC는 저거 한 문장을 위해 수십 줄의 추가 코드가 필요할 수 있다.
이를 보완하기 위해 나온 것이 Embedded SQL으로, 특정 구문을 통해 그냥 SQL문만 나열해도 되는 것처럼 만들어 줄 수 있다. 기반은 c, cpp.
ex)
EXEC SQL BEGIN DECLARE SECTION;
int sEmpNo;
varchar sEName[20 + 1];
long sSalary;
EXEC SQL END DECLARE SECTION;
...
EXEC SQL
SELECT empno, ename, sal
INTO :sEmpNo, :sEName, :sSalary
FROM EMP
ORDER BY SAL DESC
LIMIT 1;
declare section에 Select절을 실행하는데, into 절에 있는 variable에 해당 데이터를 넣는다는 식으로, 매우 간단하게 사용 가능한 개념이다. 보통은 전용 컴파일러가 있어 DB별로 만들어 주기도 하는데..이걸 언어라고 해야 하나? api라고 해야 하나?
대부분의 DB에 있는 개념이고 오라클은 약간 변형하여 Pro*C라는 이름으로 사용하고 있다.(여긴 Esql과 조금 다르긴 함)
이 Esql에서 에러 핸들링을 위한 구조체가 있는데, 바로 SQLCA라는 놈으로 사용하기가 매우 간편하다.
그냥 선언만 해주고, 쿼리를 실행 후에, sqlca에 무슨 값이 들어갔는지 확인해서 에러 핸들링을 하면 된다. 선언도 할 필요 없고 세팅도 할 필요가 없다. 자체 지원이니까.
struct sqlca
{
char sqlcaid[8];
int sqlabc;
struct
{
unsigned short sqlerrml;
char sqlerrmc[SQLERRMC_LEN];
} sqlerrm;
char sqlerrp[8];
int sqlerrd[6];
char sqlwarn[8];
char sqlext[8];
char sqlstate[8];
unsigned short *rowstatus;
}
(대략 뭐 이렇게 생김)
상세 참고 : https://www.ibm.com/docs/en/informix-servers/12.10?topic=structure-fields-sqlca
이번에 내가 달려간 이슈는 바로 이 에러 핸들링에서 시작한다.
이쯤에서 꽤 확신이 들었다. 코드 문제도 커서가 안 닫힌 문제도 아니고 에러 핸들링 시에 에러가 아님에도 에러로 판단한다면 cursor는 open된 채로 정리되지 않고 끝날 수 있다는 점이다. open cursor(성공) -> Ap에선 에러로 판단(return) -> 당연히 close 로직을 타지 않음 -> cursor는 열려 있음.
즉 에러 핸들러를 모든 Thread가 공유하는 문제라면 상기한 문제점들을 설명 가능하다는 것이다. SQL_SUCCESS임에도 시간차로 에러가 발생할 수 있고, 내 에러가 아닌데도 핸들링하므로서 원하지 않는 방식의 루틴이 될 수 있다.
다른 제품은 모르겠으나(아마..thread safe하게 작동하는 옵션 등이 있는 것으로 알고 있다) goldilocks에서는 sqlca가 전역 변수로 취급된다. 선언할 필요도 없고 사용이 편한 대신에 생기는 trade-off인 셈이다.
즉...메인 프로세스에서 thread로 특정 함수, 코드를 호출하는 형식이라면, 해당 코드에 '모두' sqlca를 선언해 줘야 한다.
struct sqlca sqlca;
이 한문장만 추가해 주면 각각 local scope에 구조체가 선언됨으로서 thread-safe하게 사용할 수 있게 된다.
추가해 주는게 좀 일이었지만, 어쨌든 넣고 나니 cursor 등 에러 없이 정상적으로 서비스 됨을 확인하였다.
thread도 많고 로그 정보도 많고 접근 자체를 로직 문제로 접근하다보니 시간이 꽤 걸렸다. 내 경력 중에 제일 풀기 어려운 문제였던 듯.