Introducing Experimental Support for Stored Programs in JS in Percona Server for MySQL

 

mysql

TL;DR

Percona Server for MySQL 에서 이제 JS 언어로 stored programs 을 실험적으로 지원합니다.
Oracle 엔터프라이즈/클라우드 전용 기능에 대한 무료 오픈소스 대안으로 사용자들은 보다 현대적이고 편리하며, 종종 더 친숙한 언어로 stored programs 을 작성할 수 있도록 합니다.
이 기능은 아직 활발히 개발 중이며, 이에 대한 여러분의 피드백을 매우 환영합니다!
바이너리 패키지는 Percona 의 실험적 저장소에서, 소스 코드는 GitHub 에서 확인할 수 있습니다.

 

Why we’re adding JS stored program support

작년에 Oracle 은 MySQL 서버에 저장된 JavaScript(JS) 프로그램에 대한 지원을 도입했습니다(이 기능은 2024년 여름에 MySQL Server 9.0 Innovation Release 에 출시된 이후 사용 가능했습니다).
이 기능은 사용자들이 기본 MySQL Stored Routine language(버전 5.0부터 사용 가능) 보다 더 현대적이고 편리하며, 아마도 더 익숙한 언어로 stored programs 을 작성할 수 있도록 합니다.
CPU 바운드 코드의 경우, JavaScript 프로그램은 더 나은 성능을 제공할 것입니다.

안타깝게도, Oracle 의 새로운 기능에는 한 가지 큰 문제가 있습니다.
이는 무료 오픈소스 MySQL 커뮤니티 버전에서는 사용할 수 없으며, MySQL 엔터프라이즈 제품 또는 Oracle 클라우드 서비스의 일부로만 제공됩니다.
이는 예를 들어 production(운영) 환경에서 무료로 사용할 수 없다는 것을 의미합니다.

Percona 는 오픈소스에 전념하고 있기 때문에, 우리는 JavaScript Stored Programs 에 대한 무료 오픈소스 대안을 제공하는 것이 좋은 생각이라고 결정했습니다.
따라서 우리는 Percona Server for MySQL 에서 JS Stored Programs 지원 작업을 시작했습니다.

Oracle의 구현 방식과 마찬가지로, Percona Server for MySQL 은 JS 로 작성된 Stored Programs 지원을 로드 가능한 컴포넌트로 구현합니다.
그러나 Oracle이 JavaScript 코드 실행을 위해 GraalVM 엔진을 사용하는 것과 달리, Percona 는 V8 엔진을 사용하기로 결정했습니다.

또한, Oracle 과는 달리 Percona 는 Percona Server for MySQL 의 8.4 시리즈(즉, LTS 시리즈)를 목표로 하고 있습니다.

현재 시점에서 Percona Server for MySQL 에서 이 기능은 아직 본격적인 실사용 단계에는 이르지 않았을 수 있지만, 이제 충분히 살펴보고 실험해볼 만한 단계에 도달했다고 생각합니다!
여러분의 피드백을 진심으로 기다리고 있습니다!

 

Where to get it: Binaries and source

이 기능이 활성화된 Percona Server for MySQL 의 바이너리 패키지를 Percona 의 실험용 저장소에서 제공합니다.
해당 저장소에 접근하는 방법은 다음 링크를 참고하세요: https://docs.percona.com/percona-software-repositories/index.html

특히, JS stored programs 지원이 포함된 서버 패키지를 설치하려면 percona-release 도구를 사용하여 “ps-84-lts” 저장소의 “experimental” 컴포넌트를 활성화해야 합니다.

$ sudo percona-release enable ps-84-lts experimental

그 후에 표준 패키지 관리자를 사용하여 Percona Server for MySQL 버전 8.4.5-5 패키지를 설치할 수 있습니다.

이 기능의 소스 코드는 GitHub 에 별도 브랜치(https://github.com/percona/percona-server/tree/js-lang)에서도 제공됩니다.
이를 통해 JS stored programs 기능이 포함된 Percona Server for MySQL 을 직접 빌드할 수도 있습니다.
자세한 내용은 다음 문서를 참조하세요: https://github.com/percona/percona-server/blob/js-lang/components/js_lang/README.md
이 경우 가장 복잡한 부분은, Percona 코드와 호환되는 정확한 파라미터로 V8 엔진의 올바른 버전을 빌드하는 작업입니다.

 

Step-by-step: Getting started with JS stored programs

JS 지원이 가능한 Percona Server for MySQL 버전을 설치하고 시작한 후(예: experimental repository), 가장 먼저 해야 할 일은 다음을 사용하여 JS language component 를 설치하는 것입니다:

INSTALL COMPONENT 'file://component_js_lang';

또한 JS stored programs 을 생성할 사용자에게 (표준 CREATE ROUTINE 권한 외에) 새로운 전역 동적 CREATE_JS_ROUTINE 권한을 부여해야 합니다. 예를 들면 다음과 같습니다:

GRANT CREATE_JS_ROUTINE ON *.* TO root@localhost;

그 후, 해당 사용자 중 한 명으로 접속하여 LANGUAGE JS 절이 포함된 CREATE PROCEDURE 또는 CREATE FUNCTION 문을 사용하여 JS 언어로 stored programs 을 생성할 수 있습니다. 예를 들면 다음과 같습니다:

CREATE FUNCTION fact(n INT) RETURNS INT LANGUAGE JS AS $$
let result = 1;
while (n > 1) {
result *= n;
n--;
}
return result;
$$;

함수 본문을 구분하는 $$ 에 주목하세요.
이는 JS 코드 내에서 일반적으로 SQL 문을 구분하는 데 사용되는 세미콜론(;) 문자가 사용되기 때문에 필요합니다.
최신 버전의 mysql 클라이언트는 이러한 $$ 구문을 별도의 조치 없이 잘 처리할 수 있도록 업데이트되어 있습니다.
그러나 이전 클라이언트 버전에서는 혼동을 피하기 위해 세미콜론(;)이 아닌 다른 문자를 구분자로 할당해야 할 수도 있습니다.
이는 클라이언트 전용 DELIMITER 문을 사용하여 수행할 수 있습니다.

JS 저장 루틴이 생성되면, 일반 SQL 루틴처럼 동일한 방식으로 호출할 수 있습니다.

mysql> SELECT fact(5);
+---------+
| fact(5) |
+---------+
|    120  |
+---------+
1 row in set (0.00 sec)

이는 `INFORMATION_SCHEMA.ROUTINES` 테이블에 표시되며, 필요한 경우 삭제할 수 있습니다:

mysql> SELECT routine_schema AS s, routine_name AS n, routine_definition AS def, external_language AS l FROM INFORMATION_SCHEMA.ROUTINES WHERE routine_name='fact';
+------+------+----------------------------------------------+----+
| s    | n    | def                                          | l  |
+------+------+----------------------------------------------+----+
| test | fact | let result = 1;
while (n > 1) {
result *= n;
n--;
}
return result;
| JS |
+------+------+---------------------------------------------+----+
1 row in set (0.01 sec)mysql> DROP FUNCTION fact;
Query OK, 0 rows affected (0.01 sec)

`CREATE FUNCTION` 또는 `PROCEDURE` 문에서 선언하는 매개변수는 JS 코드에서 접근할 수 있으며 OUT 및 INOUT 매개변수도 지원됩니다.
JS `return` 문으로 반환된 값은 stored function 의 반환 값이 됩니다.

대부분의 SQL 데이터 타입이 매개변수로 지원합니다.
UTF-8 이외의 문자 집합을 사용하는 문자 데이터는 JS 코드 내에서 처리될 수 있도록 자동으로 UTF-8 로 변환됩니다.
이 변환은 반환 값이나 OUT/INOUT 매개변수가 필요한 경우 역으로 수행됩니다.
BLOB, BINARY 및 VARBINARY 는 DataView 객체로 매핑됩니다.

`JSON` SQL 데이터 타입의 매개변수는 JS 객체로 변환됩니다.
반환 값, OUT 및 INOUT 매개변수는 그 반대 방향으로 변환됩니다.
이 기능은 매우 잘 작동하기 때문에 별도의 예시가 필요할 정도입니다.

mysql> CREATE FUNCTION norm_keys(d JSON) RETURNS JSON LANGUAGE JS AS $$
   $>   const processData = (j) => {
   $>     // Handle different possible structures
   $>     if (Array.isArray(j)) {
   $>       return j.map(i => processData(i));
   $>     } else if (typeof j === 'object') {
   $>       const r = {};
   $>       for (const key in j) {
   $>         // Transform keys to lowercase for consistency
   $>         const normKey = key.toLowerCase();
   $>         r[normKey] = processData(j[key]);
   $>       }
   $>       return r;
   $>     }
   $>     return j;
   $>   };
   $>
   $>   return processData(d);
   $> $$;
Query OK, 0 rows affected (0.01 sec)mysql> SELECT norm_keys('{
   '>                     "UserName": "John",
   '>                     "Age": 30,
   '>                     "Contacts": [ { "type": "email",      "value": "john@example.com" },
   '>                                   { "type": "phone", "value": "123-456-7890" } ],
   '>                     "metadata": { "lastLogin": "2023-01-01", "isActive": true }
   '>                   }') AS n;
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| n                                                                                                                                                                                                    |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| {"age": 30, "contacts": [{"type": "email", "value": "john@example.com"}, {"type": "phone", "value": "123-456-7890"}], "metadata": {"isactive": true, "lastlogin": "2023-01-01"}, "username": "John"} |
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

각 connection 및 각 active 사용자마다 자신만의 JS 컨텍스트를 가지며, 이는 다른 connection 과 공유되지 않고 (사용성 측면), 같은 connection 내의 다른 사용자와도 공유되지 않습니다 (보안 측면).

Percona 는 V8 엔진이 제공하는 동일 수준의 JS 언어 지원을 제공합니다.
즉, 표준 연산자, 데이터 타입, 객체(예: Math) 및 ECMA 표준의 함수들은 지원되지만, 브라우저에서 제공되는 DOM 모델과 같은 기능은 지원되지 않음을 의미합니다.
또한, 데이터베이스 보안을 우회할 수 있는 네트워크 또는 파일 I/O 도 지원하지 않습니다.

현재 connection 및 사용자에 대해 발생한 마지막 JS 오류에 대한 정보를 얻으려면 우리 컴포넌트가 제공하는 JS_GET_LAST_ERROR() 및 JS_GET_LAST_ERROR_INFO() UDF를 사용할 수 있습니다.
예를 들면 다음과 같습니다:

mysql> CREATE PROCEDURE p_error() LANGUAGE JS AS $$
$>   function l0() {
$>     return "Ooops!"();
$>   }
$>   function l1() {
$>     return l0();
$>   }
$>   (() => { return l1(); })();
$> $$;
Query OK, 0 rows affected (0.01 sec)mysql> CALL p_error();
ERROR 6000 (HY000): TypeError: "Ooops!" is not a functionmysql> SELECT js_get_last_error_info();
+----------------------------------------------------------+
| js_get_last_error_info()                                 |
+----------------------------------------------------------+| Error: TypeError: "Ooops!" is not a function
  At: SQL PROCEDURE test.p_error:3:19
  Line: return "Ooops!"();
^

Stack:
TypeError: "Ooops!" is not a function
at l0 (SQL PROCEDURE test.p_error:3:20)
at l1 (SQL PROCEDURE test.p_error:6:12)
at SQL PROCEDURE test.p_error:8:19
at SQL PROCEDURE test.p_error:8:27
at SQL PROCEDURE test.p_error:10:3                    |
+----------------------------------------------------------+
1 row in set (0.01 sec)

또한, 표준 JS 콘솔 로그 API(https://console.spec.whatwg.org/)를 사용하여 JS stored programs 을 디버깅할 수 있습니다. 예를 들면 다음과 같습니다:

mysql> CREATE PROCEDURE p1() LANGUAGE JS AS $$ console.log("Test!") $$;
Query OK, 0 rows affected (0.01 sec)mysql> CALL p1();
Query OK, 0 rows affected (0.01 sec)mysql> SELECT JS_GET_CONSOLE_LOG();
+----------------------+
| JS_GET_CONSOLE_LOG() |
+----------------------+
| Test!                |
+----------------------+
1 row in set (0.00 sec)mysql> SELECT JS_GET_CONSOLE_LOG_JSON();
+----------------------------------------------------------+
| JS_GET_CONSOLE_LOG_JSON()                                |
+----------------------------------------------------------+
| [
    {
"timestamp": "2025-05-27 19:13:25.654906",
"level": "Info",
"message": "Test!"
}
]                                                        |
+----------------------------------------------------------+
1 row in set (0.00 sec)

위 예시에서 볼 수 있듯이, 현재 connection 및 사용자에 대한 console log 출력을 확인하기 위해 JS_GET_CONSOLE_LOG() 와 JS_GET_CONSOLE_LOG_JSON() UDF 를 사용할 수 있습니다.
기본 형식과 확장 형식 모두 지원됩니다.
또한, console log 를 초기화하는 JS_CLEAR_CONSOLE_LOG() UDF 도 존재합니다.

JS 로 작성된 stored programs 이 너무 오래 실행되거나 무한 루프에 빠진 경우, KILL QUERY SQL 문을 사용하여 실행을 쉽게 중단할 수 있습니다.
MAX_EXECUTION_TIME hint 및 변수도 작동합니다.

 

Limitations and what’s coming next

우리 단기 계획에 아직 작동하지 않는 몇 가지 사항이 있습니다:

  • JS 안에서 Stored Programs 메모리 사용량 추적 및 제한 (GitHub 버전에서는 작동하지만 아직 제대로 테스트되지 않았습니다).
  • JS 안의 Stored Programs 에서 SQL 실행

 

Help us shape the future: We want your feedback

저희는 특히 JavaScript 로 개발하시는 사용자분들께 이 새로운 실험적 기능을 사용해 보시고, 발견하신 문제나 개선할 점에 대한 의견을 공유해 주시길 부탁드립니다!
의견은 Percona 커뮤니티 포럼이나 저희 JIRA를 통해 전달하실 수 있습니다.
이 초기 단계에서 여러분의 피드백을 반영하는 것이 훨씬 수월하니 많은 참여 부탁드립니다!

 

블로그 원문 : https://www.percona.com/blog/introducing-experimental-support-for-stored-programs-in-js-in-percona-server-for-mysql/

 

 

자유롭게 댓글을 달아주세요! 언제나 환영합니다.
기타 문의:  info@neoclova.co.kr
네오클로바 기술블로그 홈 바로가기: https://neoclova.net
네오클로바 홈페이지: http://neoclova.co.kr

 

Similar Posts

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다