문서화와 테스트 케이스: OpenResty 개발 문제 해결을 위한 강력한 도구
API7.ai
October 23, 2022
OpenResty의 원리와 몇 가지 필수 개념을 배운 후, 이제 API를 배우기 시작할 차례입니다.
개인적인 경험으로는 OpenResty API를 배우는 것은 상대적으로 쉽기 때문에 많은 글을 할애할 필요가 없습니다. 여러분은 궁금해할 수 있습니다: API가 가장 일반적이고 필수적인 부분이 아닌가요? 왜 많은 시간을 들이지 않나요? 여기에는 두 가지 주요 고려 사항이 있습니다.
첫째, OpenResty는 매우 상세한 문서를 제공합니다. 다른 많은 프로그래밍 언어나 플랫폼과 비교했을 때, OpenResty는 API 매개변수와 반환 값 정의뿐만 아니라 완전하고 실행 가능한 코드 예제도 제공하여 API가 다양한 경계 조건을 어떻게 처리하는지 명확하게 보여줍니다.
예제 코드와 주의 사항이 포함된 API 정의를 따르는 것은 OpenResty 문서의 일관된 스타일입니다. 따라서 API 설명을 읽은 후, 즉시 환경에서 샘플 코드를 실행하고 매개변수와 문서를 수정하여 검증하고 이해를 깊이할 수 있습니다.
둘째, OpenResty는 포괄적인 테스트 케이스를 제공합니다. 앞서 언급했듯이, OpenResty 문서는 API의 코드 예제를 보여줍니다. 그러나 공간 제약으로 인해 문서는 다양한 비정상 상황에서의 오류 보고 및 처리 방법과 여러 API를 사용하는 방법을 보여주지 않습니다.
하지만 걱정하지 마세요. 이러한 내용의 대부분은 테스트 케이스 세트에서 찾을 수 있습니다.
OpenResty 개발자에게 가장 좋은 API 학습 자료는 공식 문서와 테스트 케이스입니다. 이들은 전문적이고 독자 친화적입니다.
물고기를 한 마리 주면 하루를 먹여 살릴 수 있지만, 물고기 잡는 법을 가르치면 평생을 먹여 살릴 수 있습니다. OpenResty 개발에서 문서와 테스트 케이스 세트의 힘을 어떻게 발휘할 수 있는지 실제 예제를 통해 경험해 보겠습니다.
shdict의 get
API를 예로 들어
NGINX 공유 메모리 영역을 기반으로 하는 공유 딕셔너리(shared dictionary)는 Lua 딕셔너리 객체로, 여러 작업자 간에 데이터를 접근할 수 있고 속도 제한, 캐시 등의 데이터를 저장할 수 있습니다. 공유 딕셔너리와 관련된 API는 20개 이상이며, OpenResty에서 가장 일반적이고 중요한 API입니다.
가장 간단한 get
작업을 예로 들어보겠습니다. 문서 링크를 클릭하여 비교할 수 있습니다. 다음의 최소화된 코드 예제는 공식 문서에서 수정한 것입니다.
http {
lua_shared_dict dogs 10m;
server {
location /demo {
content_by_lua_block {
local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
local v = dogs:get("Jim")
ngx.say(v)
}
}
}
}
간단히 말해, Lua 코드에서 공유 딕셔너리를 사용하기 전에 nginx.conf
에 lua_shared_dict
지시문을 사용하여 "dogs"라는 이름의 10M 크기의 메모리 블록을 추가해야 합니다. nginx.conf
를 수정한 후, 프로세스를 재시작하고 브라우저나 curl
명령으로 접근하여 결과를 확인할 수 있습니다.
이것이 조금 번거롭게 느껴지나요? 더 간단하게 수정해 보겠습니다. 보시다시피, 이렇게 resty CLI를 사용하는 것은 nginx.conf
에 코드를 삽입하는 것과 같은 효과를 냅니다.
$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
dogs:set("Jim", 8)
local v = dogs:get("Jim")
ngx.say(v)
'
이제 nginx.conf
와 Lua 코드가 어떻게 함께 작동하는지 알게 되었고, 공유 딕셔너리의 set 및 get 메서드를 성공적으로 실행했습니다. 일반적으로 대부분의 개발자는 여기서 멈춥니다. 여기서 주목할 만한 몇 가지 사항이 있습니다.
- 어떤 단계에서 공유 메모리 관련 API를 사용할 수 없나요?
- 샘플 코드에서 get 함수는 하나의 반환 값만 있습니다. 그렇다면 언제 여러 반환 값이 있을까요?
- get 함수의 입력 유형은 무엇인가요? 길이 제한이 있나요?
이 질문들을 과소평가하지 마세요. 이들은 OpenResty를 더 잘 이해하는 데 도움을 줄 수 있으며, 하나씩 살펴보겠습니다.
질문 1: 어떤 단계에서 공유 메모리 관련 API를 사용할 수 없나요?
첫 번째 질문을 살펴보겠습니다. 답은 간단합니다. 문서에는 API를 사용할 수 있는 환경을 나열한 전용 context
(즉, 컨텍스트 섹션)가 있습니다.
context: set_by_lua*, rewrite_by_lua*, access_by_lua*, content_by_lua*, header_filter_by_lua*, body_filter_by_lua*, log_by_lua*, ngx.timer.*, balancer_by_lua*, ssl_certificate_by_lua*, ssl_session_fetch_by_lua*, ssl_session_store_by_lua*
보시다시피, init
및 init_worker
단계는 포함되지 않았습니다. 이는 공유 메모리의 get
API가 이 두 단계에서 사용될 수 없음을 의미합니다. 각 공유 메모리 API는 다른 단계에서 사용될 수 있습니다. 예를 들어, set
API는 init
단계에서 사용될 수 있습니다.
사용할 때 항상 문서를 읽으세요. 물론, OpenResty 문서에는 때때로 오류와 누락이 있으므로 실제 테스트로 검증해야 합니다.
다음으로, 테스트 세트를 수정하여 init
단계에서 공유 딕셔너리의 get
API를 실행할 수 있는지 확인해 보겠습니다.
공유 메모리와 관련된 테스트 케이스 세트를 어떻게 찾을 수 있나요? OpenResty의 테스트 케이스는 모두 /t
디렉토리에 있으며, 규칙적으로 이름이 지정됩니다. 즉, self-incremented-number-function-name.t
입니다. shdict
를 검색하면 043-shdict.t
라는 공유 메모리 테스트 케이스 세트를 찾을 수 있습니다. 이 세트에는 다양한 정상 및 비정상 상황에 대한 테스트가 포함되어 있습니다.
첫 번째 테스트 케이스를 수정해 보겠습니다.
content
단계를 init
단계로 바꾸고 불필요한 코드를 제거하여 get
인터페이스가 작동하는지 확인할 수 있습니다. 이 단계에서는 테스트 케이스가 어떻게 작성되고 조직화되고 실행되는지 이해할 필요는 없습니다. 단지 get
인터페이스를 테스트하고 있다는 것만 알면 됩니다.
=== TEST 1: string key, int value
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
init_by_lua '
local dogs = ngx.shared.dogs
local val = dogs:get("foo")
ngx.say(val)
';
}
--- request
GET /test
--- response_body
32
--- no_error_log
[error]
--- ONLY
테스트 케이스 끝에 --ONLY
플래그를 추가한 것을 눈치채셨을 것입니다. 이는 다른 모든 테스트 케이스를 무시하고 이 테스트 케이스만 실행하도록 하여 실행 속도를 높입니다. 나중에 테스트 섹션에서 다양한 태그에 대해 자세히 설명하겠습니다.
수정 후, prove
명령으로 테스트 케이스를 실행할 수 있습니다.
prove t/043-shdict.t
그러면 문서에 설명된 단계 제한을 확인하는 오류가 발생합니다.
nginx: [emerg] "init_by_lua" directive is not allowed here
질문 2: get
함수가 여러 반환 값을 가지는 경우는 언제인가요?
두 번째 질문을 살펴보겠습니다. 이는 공식 문서에서 요약할 수 있습니다. 문서는 이 인터페이스의 syntax
설명으로 시작합니다.
value, flags = ngx.shared.DICT:get(key)
정상적인 경우.
- 첫 번째 매개변수
value
는 딕셔너리에서key
에 해당하는 값을 반환합니다. 그러나key
가 존재하지 않거나 만료된 경우value
값은nil
입니다. - 두 번째 매개변수
flags
는 조금 더 복잡합니다. set 인터페이스에서 flags를 설정한 경우 이를 반환합니다. 그렇지 않으면 반환하지 않습니다.
API 호출이 실패하면 value
는 nil
을 반환하고 flags
는 특정 오류 메시지를 반환합니다.
문서에서 요약한 정보를 통해 local v = dogs:get("Jim")
과 같이 하나의 수신 매개변수만 있는 코드는 불완전하다는 것을 알 수 있습니다. 이는 일반적인 사용 시나리오만 다루고 두 번째 매개변수를 수신하거나 예외 처리를 하지 않기 때문입니다. 이를 다음과 같이 수정할 수 있습니다.
local data, err = dogs:get("Jim")
if data == nil and err then
ngx.say("get not ok: ", err)
return
end
첫 번째 질문과 마찬가지로, 테스트 케이스 세트를 검색하여 문서에 대한 우리의 이해를 확인할 수 있습니다.
=== TEST 65: get nil key
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
content_by_lua '
local dogs = ngx.shared.dogs
local ok, err = dogs:get(nil)
if not ok then
ngx.say("not ok: ", err)
return
end
ngx.say("ok")
';
}
--- request
GET /test
--- response_body
not ok: nil key
--- no_error_log
[error]
이 테스트 케이스에서 get
인터페이스는 nil
입력을 받고, 반환된 err 메시지는 nil key
입니다. 이는 문서에 대한 우리의 분석이 정확함을 검증하고 세 번째 질문에 대한 부분적인 답을 제공합니다. 적어도 get의 입력은 nil이 될 수 없습니다.
질문 3: get
함수의 입력 유형은 무엇인가요?
세 번째 질문에 대해, get
의 입력 매개변수는 어떤 유형일 수 있나요? 먼저 문서를 확인해 보겠지만, 아쉽게도 문서는 key의 합법적인 유형이 무엇인지 명시하지 않습니다. 어떻게 해야 할까요?
걱정하지 마세요. 적어도 key
가 문자열 유형일 수 있고 nil이 될 수 없다는 것을 알고 있습니다. Lua의 데이터 유형을 기억하시나요? 문자열과 nil 외에도 숫자, 배열, 불리언 유형, 함수가 있습니다. 후자 두 가지는 키로 사용할 필요가 없으므로, 처음 두 가지인 숫자와 배열만 검증하면 됩니다. 먼저 테스트 파일에서 숫자를 key
로 사용하는 경우를 검색해 보겠습니다.
=== TEST 4: number keys, string values
이 테스트 케이스를 통해 숫자도 키로 사용될 수 있으며, 내부적으로 문자열로 변환됨을 알 수 있습니다. 배열은 어떨까요? 아쉽게도 테스트 케이스는 이를 다루지 않으므로 직접 시도해 보아야 합니다.
$ resty --shdict 'dogs 10m' -e 'local dogs = ngx.shared.dogs
dogs:get({})
'
예상대로, 다음과 같은 오류가 보고됩니다.
ERROR: (command line -e):2: bad argument #1 to 'get' (string expected, got table)
요약하면, get
API가 허용하는 key
유형은 문자열과 숫자입니다.
그렇다면 전달된 key의 길이 제한이 있나요? 여기에 해당하는 테스트 케이스가 있습니다.
=== TEST 67: get a too-long key
--- http_config
lua_shared_dict dogs 1m;
--- config
location = /test {
content_by_lua '
local dogs = ngx.shared.dogs
local ok, err = dogs:get(string.rep("a", 65536))
if not ok then
ngx.say("not ok: ", err)
return
end
ngx.say("ok")
';
}
--- request
GET /test
--- response_body
not ok: key too long
--- no_error_log
[error]
문자열 길이가 65536일 때, key가 너무 길다는 메시지가 표시됩니다. 길이를 65535로 변경해 보면, 단 1바이트 줄어들었지만 더 이상 오류가 발생하지 않습니다. 이는 key의 최대 길이가 정확히 65535임을 의미합니다.
요약
마지막으로, OpenResty API에서 오류 메시지가 있는 반환 값은 반드시 변수로 수신하고 오류 처리를 해야 한다는 것을 상기시켜 드립니다. 그렇지 않으면 실수로 연결 풀에 잘못된 연결을 넣거나 API 호출이 실패한 후에도 뒤의 로직을 계속 실행하는 등의 문제가 발생할 수 있습니다.
따라서, OpenResty 코드를 작성할 때 문제가 발생하면 일반적으로 어떻게 해결하시나요? 문서, 메일링 리스트, 또는 다른 채널을 사용하시나요?
이 글을 동료와 친구들과 공유하여 의견을 나누고 개선할 수 있도록 해주세요.