WARN[org.jboss.jca.adapters.jdbc.local.LocalManagedConnectionFactory] (default task-10) IJ030027: Destroying connection that is not valid, due to the following exception: …
…중략
java.sql.SQLException: JDBC-6003: Sequence "AUDIT_REQUEST_ID_SEQ" has not been accessed in this session: no CURRVAL available.
위의 오류 상황은 다음과 같다.
API 요청에 대해 감사 로깅을 진행한다.
API 요청은 3개의 작업을 1개 트랜잭션 으로 작업을 진행한다.
감사로그는 3개의 작업에 대해 각각 감사로그를 진행한다. 즉, 총 3개의 감사로깅을 진행한다.
첫 번째 감사로그는 NEXTVAL을 사용한다.
두 번째, 세 번째 감사로그는 CURRVAL을 사용한다.
Tibero-JDBC-6003오류의 경우, 현재 접속 세션(Connection)에 SEQUENCE.NEXTVAL 없이 SEQUENCE.CURRVAL을 사용했을 때 발생한다.
암호키 등록에 대한 감사로그 입력은 총 3회 실시한다. 첫 번째 감사로그를 입력할 때 NEXTVAL을 호출한다. 두 번째부터는 CURRVAL을 사용한다. 두 번째 감사로그 입력 후, DB Connection Reset이 발생하였다. 해당 사항으로 인해 CURRVAL의 값을 보관했던 커넥션(Session)은 소멸되었다. 그 후, 세 번째 감사로그 입력할 때 CURRVAL을 찾지 못한다.
그러나 위의 상황 분석을 통해 다음과 같이 통찰할 수 있다(다음의 통찰을 통해 쪽팔림 1을 획득할 수 있다.).
CURRVAL은 NEXTVAL을 호출한 세션에서 호출해야 한다.
CURRVAL의 값은 Thread-Safe하지 않다.
(은연중에 CURRVAL이 Thread-Safe하지 않은 것을 알고 있었지만, 연계해서 생각을 하지 못 했다......)
그림으로 표현하면 아래와 같다.
1개 트랜잭션 내부에서의 NEXTVAL->CURRVAL->CURRVAL은 상관없다고 생각한다(트랜잭션 과정 중 DB 세션이 안죽는다는 가정 하에(트랜잭션 중 DB 세션이 죽으면 그건 또 다른 문제이니까 상관 없을 듯...)).
그러나, 위와 같이 별개의 트랜잭션에서의 NEXTVAL->CURRVAL->CURRVAL은 문제가 상당하다. 심각하다. 자세한 사항은 아래 "위의 사항으로 인한 추가 위험"을 참고한다.
위의 사항으로 인한 추가 위험
위의 원인 분석을 토대로 추가 위험사항을 분석한다. 해당 분석은 시나리오 정의와 검증을 토대로 분석을 진행한다. 대표적인 시나리오는 총 두 개,아래와 같다.
현재의 오류가 시나리오2와 같다. 위의 오류는 DBCP 커넥션(세션)의 소멸로 인해 발생하였다. 그러나 많은 동접이 쇄도할 때 아래 그림과 같이 오류가 발생할 수 있다. 모든 요청을 Session1에서 처리하는 것으로 예상하였지만 두 번째 CURRVAL 요청은 Session2에서 처리할 경우 오류가 발생할 수 있다.
부록
현재 DBMS는 Tibero이다. 그러나, Oracle 또한 위와 같은 상황이 있을 수 있다 [1].
오라클 오류는 "ORA-08002: sequence string. CURRVAL is not yet defined in this session"이다.
(2018년 ~ 2019년 초반 과거 메모, 논문 쓰려다 망해서 던진 것, LoRa 1.1에 있는 내용 ㅠㅠ)
Abstract
사물인터넷은 4차 산업을 주도하는 대표적인 기술 중 하나이다. 사물인터넷은 다양한 산업분야에 적용이 가능하다. 사물인터넷을 여러 분야, 환경, 상황에 적용하기 위해서는 저전력, 광대역, 저비용 등의 특징이 필요하다. 이러한 조건의 적합한 기술은 LPWAN(Low Power Wide Area Network)이 적합하며, 대표적인 기술은 LoRaWAN(LongRangeWAN)이 있다. 이와 같은 기술을 적용하여, 사물인터넷 기술은 농업, 제조업, 서비스업 등 다양하게 사용할 수 있다. 그러나 사물인터넷은 많은 보안 위협에 노출되어 있으며, 실로 외부로부터 많은 공격을 받았고, 지속적으로 받고 있다. 또한 LoRaWAN의 경우, 기본적인 암호키 관리 체계가 상당히 부족하며, 위와 같은 현상은 절대적인 보안 취약점의 요소이다. 본 논문은 LoRaWAN의 암호키의 사용, 관리 및 유지에 대한 취약점 분석을 실시하며, 개선 모델을 제안한다.
1. 서론
4차 산업혁명을 바라보는 오늘날,, 사물인터넷(Internet-of-Things, IoT)은 4차 산업혁명을 주도하는 핵심기술 중 하나이다. 이에 부응하여 IoT와 관련된 다양한 기술과 서비스들이 등장하고 있다.
IoT 기기는 제한된 배터리와 전원이 공급되지 않는 곳에 장기간 설치하여 안정적으로 동작하며, 데이터 송/수신을 하는 경우가 많다. 대부분의 데이터 통신은 단순한 유선 통신이 아닌, 무선 환경을 이용한 무선통신이다. IoT에 적합한(광대역, 저전력, 저비용, 적절한 통신 속도, 소량의 데이터 전달) 통신 규격은 LPWAN이다. LPWAN 기술은 대표적으로 총 4가지가 있으며 다음과 같다. LoRaWAN, Sigfox와 같은 비면허 대역과 LTE-M, NB-IOT와 같은 면혀대역의 LPWAN이 있다.
각각의 LPWAN은 장단점이 존재하며, 특정 기술로 모든 IoT에 동일하게 적용할 수 없으며, 사용 환경과 서비스에 적절하게 각각의 LPWAN을 적용해야 한다. 예를 들어, 높은 데이터 통신 품질, 높은 데이터 통신 빈도 및 양을 우선시하는IoT 기기는 NB-IOT, LTE-M이 적합하며, 저렴하고 넓은 데이터 통신 커버리지와 데이터 통신 품질에 의존하지 않는 경우, LoRaWAN, Sigfox가 적합하다.[2]
그러나 LPWAN 기술에서의 보안은 부차적인 요소로 되어있다. 각각의 LPWAN의 보안 적용 방법도 가지각색이며, 보안에 취약한 사항이 존재한다. IoT의 서비스는 하나의 개인과 점점 밀접해지며, 이러한 데이터는 기밀성, 무결성은 보호되어야 한다.
본 논문은 최근 IoT에서 많이 사용하는 비면허 대역 LoRaWAN[3]의 보안 취약점 중 암호키 관리 방안에 대한 취약점을 중점적으로 분석했다.
본 논문의 구성은 다음과 같다. 2장은 LoRaWAN의 암호키 배포 과정(Join 과정)과 취약점 분석 선행 연구 분석을 다루고 이를 토대로 3장에서 LoRaWAN 암호키 관리 방안 취약점 분석을 하여, 문제점을 열거한다. 그 후, 4장은 결론을 맺는다.
2. 관련 연구
2장 관련 연구는 다음과 같다. LoRaWAN 암호키 라이프 사이클은 LoRaWAN의 암호키의 주입과 배포, 세션키 유도, payload의 암/복호화, payload의 무결성 검증, 디바이스 식별 및 인증 절차 등 암호키와 관련된 보안 구조를 분석한다. 그리고 위의 과정의 취약점 분석에 대한 선행 연구를 분석한다.
2.1 LoRaWAN 암호키 Join 과정
LoRaWAN은 아래 [그림 1]과 같이 두 개의 계층 별 암호화를 제공한다. MAC 계층의 암호화는 디바이스의 식별 및 인증 부분이며, Application 계층의 암호화는 디바이스에서 실질적으로 송신하고자 하는 데이터 즉 payload의 암호화를 담당한다. 두 개의 계층은 모두 AES128 대칭키를 사용하며, 상이한 유도식을 통해 산출된 다른 암호키를 사용한다.
[그림 1] LoRaWAN Security Architecture
암호키를 위의 [그림 1]과 사용하기 위해서는 기기 정보 및 암호키의 사전 주입과 활성화 절차가 필요하다. 활성화 절차는 OTAA(Over- The-Air Activation), ABP(Activation by Personalization) 두 가지의 방법이 있으며, 위의 두 가지 방법마다 상이한 사전 주입 절차를 갖고 있다. OTAA은 기기 출하 전, 사전에 DevEUI, AppEUI, AppKey(PSK, Pre-Shared Key)를 기기에 주입을 한다. 기기는 위의 세 가지 정보를 통해 NS(Network Server)와의 Join 과정을 거쳐, 두 개의 Session Key(NwkSKey, AppSKey)를 얻는다. ABP은 갱신되지 않는 두 개의 Session Key를 통해 NS와 송/수신을 한다. 그러나 더 나은 보안성을 제공하기 위해서는 OTAA 방식을 권고한다[4]. 또한 본 논문은 OTAA 방식에서의 암호키 관리 방안에 대한 취약점 분석을 진행한다.
OTAA은 DevEUI, AppEUI, AppKey를 기기에 주입 후, 기기 최초 개통 또는 초기화를 할 때 Join 과정을 진행한다. Join 과정은 다음 [그림 2]와 같다.
[그림 2] LoRaWAN Join procedure[5]
첫 번째 LoRa Mote는 NS에 AppEUI, DevEUI, DevNonce 값과 MIC(Message Integrity Code) 값을 보낸다. MIC를 제외한 세 개의 값은 평문으로 전송된다. AppKey는 기기 출하 전, 사전에 공유된 키이다. DevNonce는 재전송 공격을 방지하기 위한 Mote에서 임의로 생성된 2옥텟 값이며, MIC는 기기의 인증을 위한 값이다. MIC 값 계산식은 다음과 같다.
두 번째 NS는 먼저 DevNonce 값을 통해 재전송 공격을 방지 절차를 진행한다. DevNonce 값이 일전에 사용되었을 경우, 요청은 거절된다. 그 후, Join요청을 한 DevEUI의 AppKey, Mac Header 등 위의 MIC 계산 식을 통해 MIC 인증 절차를 진행한다. 인증을 성공했을 경우, 두 개의 Session key인 Network Session Key(NwkSKey), Application Session Key (AppSKey)를 생성한다. NwkSKey와 AppSKey의 유도석은 다음과 같다.
위와 같은 Join 과정이 완료되면 Mote, NS는 양단간 UpLink 또는 DownLink 준비가 끝난다. Mote의 주기 보고(UpLink)가 있을 경우, [그림 3]과 같이 암호화되어NS에 송신한다. Mote에서 실질적으로 송신하고자 하는 데이터 payload인 FRMPayload(MAC Frame Payload)를 AppSKey를 통해 AES128 CMAC, CTR 모드를 활용하여 암호화한다. 메시지 무결성을 위해 Mac Header(MHDR), Frame Header (FHDR), FPort, 암호화된 FRMPayload의 MIC를 [그림 3]과 같이 계산한다[7]. MIC 계산식은 다음과 같다.
cmac = aes128_cmac(NwkSKey, B0| msg)
MIC = cmac[0..3]
위의 식 은 UpLink 또는 DownLink의 구분자 역할을 한다.
[그림 3] LoRaWAN PHY payload structure of MAC Message Format
2.2 LoRaWAN 암호키 관리 방안 취약점 분석 선행 연구
현재 LoRaWAN의 암호키 사용, 관리 및 유지함에 있어 취약점이 존재한다. 이런 취약점은 보안 사고와 개인(민감) 정보의 유출로 이어진다. 본 논문의 선행 연구 분석은 LoRaWAN의 암호키 관리에 대해 취약점을 언급한 선행 연구만 분석한다.
선행 연구에서 언급한 LoRaWAN의 암호키 관리 측면에서의 취약점은 [표 1]과 같다.
첫째 암호키의 관리 주체는 NS 또는 AS이다. LoRaWAN의 암호키의 보관, 사용, 유지, 분배, 폐기 등 모든 프로세스는 NS와 AS에서 이루어진다. 이와 같이 관리를 할 경우, 인가되지 않은 내부자의 접근을 허용하여, 암호키의 누수와 오용을 범할 여지가 있다.
두 번째 AppKey의 갱신 프로세스가 존재하지 않는다. AppKey를 통해, 두 개의 세션키를 유도하지만, 본질적으로 사용하는 AppKey의 수명은 Mote의 수명과 같다. 즉, AppKey가 노출될 경우, 그간 사용했던 Session Key가 복구될 위험성이 있다.
세 번째 Mote의 OTAA 활성화의 경우, Join 과정이 존재하는데, Join Request(Mote->NS)의 payload는 평문으로 전송된다. Mote의 식별 값과 암호키 유도 재료 중 하나의 노출로 인하여 기밀성을 위배할 수 있다.
[표 1]의 선행 연구 분석 결과와 같이 LoRaWAN은 암호키 관리 측면에서 많은 취약점이 존재한다. III장 취약점 분석은 선행 연구의 취약점 분석에서 언급하지 않은 부분과 추가적인 취약점 분석을 실시한다.
취약 사항
참고문헌
ECB 암호 모드를 사용한다.
[8]
암호키의 관리 주체는 NS와 AS이다.
[8][12]
MACPayload의 FPort 0 일 경우, payload는 NwkSKey를 통해 암호화를 한다.
[8]
AppKey(PSK)의 온라인 갱신 프로세스가 존재하지 않는다.
[5][8]
Mote와 AppKey의 생명주기가 같다.
[8]
AppKey의 합의 및 폐기 메커니즘이 존재하지 않는다.
[8]
낮은 Byte의 Nonce 값으로 인하여, 재전송 공격에 취약하다.
[9][10]
[11]
NS는 AS, NS의 세션키를 만든다. 즉, 계층간의 암호화를 하지 않는다.
[5]
OTAA Join Request의 본문은 암호화되지 않고, NS에 송신된다.
[5][10]
[11]
3. 취약점 분석
III장 취약점 분석은 선행 연구 분석 토대로 추가적으로 LoRaWAN의 암호키에 관련한 취약점을 분석한다. 취약점 분석 관점은 암호키의 관리와 사용 측면을 중점으로 분석한다.
첫 번째 위의 선행 연구 “Mote와 AppKey의 생명주기가 같다.”와 같이 현재 LoRaWAN은 암호키의 생명주기, 상태별 프로세스, 정책이 존재하지 않는다. 암호키의 생명주기는 NIST 키 관리 권고사항의 활성화, 비활성화, 폐기, 손상 등 Mote마다 암호키의 최초 생성부터 유지 및 폐기까지의 암호키의 생명주기와 암호키 상태별 사용 용도를 차등적으로 적용해야 한다[13]. 또한 Mote의 서비스 수준, 처리하는 데이터의 민감도, 용례 등을 통해 위험 평가를 실시하여, 보안 모델을 산정해야 한다[14]. 이와 같은 보안 모델을 통해 암호키의 수명을 측정해야 한다. 암호키의 수명은 암호 메커니즘, 데이터의 트랜잭션의 양, 데이터의 민감도, 키의 노출/사용 정도 보안 모델 요소 등으로 암호키의 수명을 차등적으로 부여해야 한다[15].
두 번째 위의 선행 연구 “ECB 암호 모드를 사용한다.”와 같이 현재 LoRaWAN은 암호키를 통해 암호화를 할 때, IV 값이 존재하지 않는다. 또한 규격에는 IV 값에 대해서 언급을 하지 않았다. IV의 공유 방법이나 어떤 페킷에 적재하는지에 대한 여부가 존재하지 않는다. IV는 같은 평문을 지속적으로 암호화를 하여도 다른 암호문을 출력하기 위한 암호화 재료이다. IV는 재사용되지 않으며, 암호화의 필요성이 없다. LoRaWAN은 16Byte의 암호키 길이를 사용하며, 이와 적합한 IV는 16Byte이므로[18], 이에 적합한 페킷을 적재할 수 있는 공간이 필요하다.
세 번째 세션키 NwkSKey, AppSKey에 대해 갱신 권고 사항이 존재하지 않는다. 세션키는 Mote와 NS 간의 Join 과정에서 확립된다. Join 과정은 Mote의 최초 개통 호 처리 또는 초기화를 하였을 경우만 진행한다. 즉, Mote의 최초 개통 호 작업 후 Mote단에서 초기화 절차를 진행하지 않을 경우, 세션키의 생명주기는 Mote와 같을 수 있다. 세션은 둘 이상의 통신 장비 간, 일정 시간 유효 연결을 확립하여, 요청 및 응답을 처리하는 절차이다[16]. 세션키 또한 한 세션에서 유효한 키를 의미한다[17]. LoRaWAN 환경에서의 지속적인 세션키 갱신은 사실상 어려움이 존재한다. 그러나 일정 주기마다 세션키를 갱신하는 절차를 확립해야 한다.
네 번째 AppKey의 합의 메커니즘을 분석한다. 선행 연구[8]에서는 AppKey의 합의 메커니즘 부재에 대해 문제점으로 지적하였으며, ECDH(Elliptic Curve Diffie-Hellman) 알고리즘을 통해서 해결안을 제시하였다. 현재 LoRaW- AN의 AppKey는 세션키를 유도할 때와 Join Accept Message를 암호화할 때 사용한다. 또한 AppKey에 대해 갱신 권고사항과 규격이 존재하지 않아, AppKey와 Mote의 생명주기가 같다. 위와 같은 문제점은 AppKey의 갱신 절차를 통해 해결할 수 있다. 그러나 AppKey의 암호키 교환 프로토콜(합의 메커니즘)를 적용할 경우 다음의 우려사항이 존재한다.
LoRaWAN에서의 Class A의 Mote의 경우, Mote의 UpLink를 통해서 NS의 DownLink가 가능하다. NS는 주체적으로 DownLink를 하지 못하는 제약이 존재한다. 또한 Mote의 높은 SF(Spreading Factor)는 낮은 Bit Rate로 인하여, 무선 통신은 외부의 잡음에 쉽게 노출된다. 결국 위와 같은 현상에서의 송/수신은 무선 통신의 높은 실패 확률을 초래한다[19].
이러한 통신 환경은 암호키 교환 프로토콜의 적용보다는 PSK가 적합하다. 현재 LoRaWAN에서 사용하는 암호키의 길이 128bit는 적합한 암호 비도를 갖고 있으며, 또한 이미 PSK 기술을 적용한 WPA(Wi-Fi Protected Access)는 다양하게 활용하고 있다. PSK를 안전하게 사용하는 조건은 신뢰된 제3의 서버의 암호키 발급과 갱신이 필요하다.
여기서부터 막장ㅋㅋㅋ
4. LoRaWAN 암호키 보호 모델 제안
선행 연구 분석과 추가적인 취약점 분석을 통해 LoRaWAN은 제3의 신뢰 서버가 필요하며, 안전한 암호키 사용과 유지를 위해서는 암호키 관리 체계와 안전한 암호키 배포 및 접근을 위한 식별 및 인증/인가의 체계 적용이 필요하다. 신뢰 서버의 구성요소는 아래 [그림 4]와 같다.
신뢰 서버는 암호키의 관리, 유지, 배포, 갱신 및 폐기 등의 암호키 사이클 관리를 한다. NS와 AS는 신뢰 서버 API를 통해 값의 암/복호화를 진행하며, 암호키의 관리는 신뢰 서버에 위임을 한다.
[그림 4] Trust Server in LoRaWAN
암호키의 전체적인 관리와 유지 및 갱신 등의 운영을 한다. 용례는 [그림 5]와 같다.
[그림 5] Trust Server Flow in LoRaWAN
HSM 난수 발생기를 통해 임의의 암호키 생성 규격에 맞게 암호키 PSK를 생성한다. 생성된 PSK는 HSM의 KEK(Key Encryp -tion Key)를 통해 포장(Wrapping)하여, 안전하게 보관한다.
PSK는 Mote에 배포 및 주입되며 개통 호 작업을 실시한다.
Join 과정을 할 때 신뢰 서버는 중계 서버 역할을 한다. NS는 Mote의 식별 및 인증을 하며, Join 과정에 필요한 데이터를 신뢰 서버에 송신한다. 신뢰 서버는 NS에서 받은 필요 데이터와 함께 NwkSKey와 AppSKey를 생성한 후, Mote에 송신한다.
Mote에서의 UpLink를 받은 NS는 무결성 검증에 필요한 데이터를 신뢰 서버에 송신한다. 신뢰 서버는 해당 데이터와 NwkSKey를 통해 무결성 검증을 실시한다.
이와 같이 구성할 경우, NS와 AS의 독립된 계층 관리를 할 수 있다. 데이터의 암/복호화는 신뢰 서버에서 진행하며, NS와 AS는 암호키의 값이 필요 없다. 신뢰 서버는 NS와 AS의 식별/인증/인가를 통해 적합한 사용자(NS 또는 AS)를 파악할 수 있다.
신뢰 서버는 Mote의 서비스 보안 수준 진단을 통해 암호키 보호 수준과 암호키의 생명주기를 결정하여, 암호키의 안전성을 지속적으로 유지한다. 또한 해당하는 보호 수준에 상응하는 정책을 적용한다.
Mote의 보안 수준과 정책에 맞게 AppKey, AppSKey, NwkSKey의 지속적인 갱신 절차를 적용한다.
암호키의 생명주기는 NIST 암호키 권고 사항의 권고안을 적용한다. 암호키의 상태 및 상황별 암호키의 생성, 등록, 백업, 복구, 검색, 수정, 폐기, 말소, 검증, 인증 등의 운영체계를 확립해야 한다.
결론
결론 대외비
[참고문헌]
[2] K. Mekki, E. Bajic, F. Chaxel and F. Meyer, "A comparative study of LPWAN technologies for large-scale IoT deployment", ICT Express, Vol. O, no. O, pp.00~11,Sep 2017
[3] N. Sornin, M. Luis, T. Eirich and T. Kramp, "LoRaWAN Specification V1.0.1 Draft 3", LoRa Alliance, Oct 2015
[4] "LoRa Security FAQ in LoRaAlliance", LoRa Alliance, Jul 2016
[5] JH Kim and JS Song, "A Dual Key-Based Activation Scheme for Secure LoRaWAN", Wireless Communication and Mobile Computing, Vol. 2017, Article ID 6590713, Nov 2017
[6] "LoRaWAN Security", LoRa Alliance, Jul 2016
[7] A. Augustin, J. Yi, T. Clausen, and W. M. Townsley, “A Study of LoRa: Long Range & Low Power Networks for the Internet of Things,” Sensors, vol. 16, no. 9, p. 1466, Sep 2016.
[8] van Leent, Marcel. "An improved key distribution and updating mechanism for low power wide area networks (LPWAN).", Cyber Security Academy, Vol. O, no. O, pp.00~11,Jan 2017
[9] Aras, E., Ramachandran, G. S., Lawrence, P., and Hughes, D., "Exploring The Security Vulnerabilities of LoRa." Cybernetics (CYBCONF), 2017 3rd IEEE International Conference on. IEEE, 2017.
[10] Na, S., Hwang, D., Shin, W., and Kim, K. H., "Scenario and countermeasure for replay attack using join request messages in LoRaWAN." Information Networking (ICOIN), 2017 International Conference on. IEEE, 2017.
[11] Tomasin, S., Zulian, S., and Vangelista, L., "Security Analysis of LoRaWAN Join Procedure for Internet of Things Networks." Wireless Communications and Networking Conference Workshops (WCNCW), 2017 IEEE. IEEE, 2017.
디바이스의 활성화 절차는 두가지가 있다. OTAA(Over-The-air-Activation), ABP(Activation By Personalization)이다[1].
6.1 Data Stored in the End-device
6.1.1 Before Activation
6.1.1.1 JoinEUI
JoinEUI는 IEEE EUI64 주소체계에 맞는 글로벌한 어플리케이션 Identifier이다. 해당 Join 서버는 EUI를 유일하게 식별한다(Join 서버는 Join 과정, 세션키 운반 같은 것을 도와줌[2]).
OTAA디바이스의 경우, Join 과정을 하기 전에 JoinEUI는 디바이스에 반드시 저장이되어야 한다. ABP 모드의 경우 JoinEUI 필요 없다.
6.1.1.2 DevEUI
DevEUI는 IEEE EUI64 주소체계에 맞는 글로벌한 디바이스 Identifier이다.
DevEUI는 NS에서 권고되는 유니크 디바이스 Ientifier이다. 활성화 절차를 하거나 네트웤을 통해 로밍을 할 때 식별한다.
OTAA 디바이스의 경우, Join 과정이 되기전에 DevEUI가 디바이스에 반드시 저장되어야 한다. ABP는 DevEUI가 저장될 필요가 없다.
6.1.1.3 Device root keys( AppKey & NwkKey )
NwkKey, AppKey는 AES-128는 디바이스에 할당된 rootKey이다. 디바이스가 OTAA를 통해 네트웤에 Join을 할 때 마다, NwkKey는 FNwkSIntKey, SNwkSIntKey, NwkSEncKey 세션키 3개를 유도한다. AppKey는 AppSKey 세션키 1개를 유도한다.
Secure provisioning, storage, and usage of root keys NwkKey and AppKey on the end- device and the backend are intrinsic to the overall security of the solution. These are left to implementation and out of scope of this document. However, elements of this solution may include SE (Secure Elements) and HSM (Hardware Security Modules). => 키 관리는 본문(로라스팩)에서 안다룸
LoRaWAN 1.0과의 호환성이 필요할 때는 NwkKey만 사용한다(1.0은 AppKey라는 한 개의 키만 사용하는데 그것을 NwkKey로 대체해줌).
The end-device in this case MUST
-Use the NwkKey to derive both the AppSKey and the FNwkSIntKey session keys as in LoRaWAN1.0 specification. èFNwkSIntKey가 1.0의 NwkSKey가 된다는 얘기
-Set the SNwkSIntKey & NwkSEncKey equal to FNwkSIntKey, the same network session key is effectively used for both uplink and downlink MIC calculation and encryption of MAC payloads according to the LoRaWAN1.0 specification. è NwkKey에서 유도한 3개의 세션키를 똑같이 만들고 uplink, downlink 때 사용해라~
NwkKey, AppKey는 OTAA 방식에서는 반드시 저장되지만, ABP 방식은 아니다.
NwkKey와 AppKey는 재사용되지 않게 안전하게 보관해야 한다.
6.1.1.4 JSIntKey and JSEncKey derivation
OTA 디바이스는 NwkKey로 유도되는 키가 아래와 같이 있다.
-JSIntKey: MIC Rejoin-Request type 1 메시지와 Join-Accept 응답에 사용[3]
-JSEncKey: Rejoin-Request에 의해 트리거 된 Join-Accept를 암호화에 사용[4]
6.1.2 After Activation
활성화 후 다음의 정보들이 디바이스에 저장된다.
-DevAddr: 디바이스 어드래스
-NwkSEncKey, SNwkSIntKey, FNwkSIntKey
-AppSKey
6.1.2.1 End-device address(DevAddr)
32bit의 값 DevAddr을 통해 현재 네트웤에서 디바이스를 식별한다.
The LoRaWAN protocol supports various network address types with different network address space sizes. The variable size AddrPrefix field is derived from the Network Server’sunique identifier NetID (see 6.2.3) allocated by the LoRa Alliance with the exception of theAddrPrefix values reserved for experimental/private network. The AddrPrefix field enablesthe discovery of the Network Server currently managing the end-device during roaming.Devices that do not respect this rule cannot roam between two networks because their homeNetwork Server cannot be found. =>여러 네트워크 주소 유형 지원, 네트워크 서버 로밍관련 내용임;
The least significant (32-N) bits, the network address (NwkAddr) of the end-device, can be arbitrarily assigned by the network manager. => NwkAddr은 네트워크 관리자가 임의로 지정
The following AddrPrefix values may be used by any private/experimental network and will not be allocated by the LoRa Aliance. =>주저리주저리
NwkSEncKey는 세션키임, up/down link의 MAC Command를 암/복호화하는데 사용된다. 1.0 NS에 붙으면 MAC payload 암호화와 MIC 계산에 사용된다. NwkSEncKey와 FNwkSIntKey는 같은 값
이것도 안전하게 보관 필요
6.1.2.5 Application session key (AppSKey)
AppSKey는 어플리케이션 세션키임, 어플리케이션 계층에서만 볼 수 있게 payload를 암/복호화 한다. 어플리케이션 페이로드는 단말과의 E2E보안이 됨, .BUT =>but they are integrity protected only in a hop-by-hop fashion: one hop between the end-device and the Network Server, and the other hop between the Network Server and the application server. That means, a malicious Network Server may be able to alter the content of the data messages in transit, which may even help the Network Server to infer some information about the data by observing the reaction of the application end-points to the altered data.
(위의 사항이 취약점이라는데 이해 필요함.)(컨텐츠 이해 못함)
종단간 기밀성 무결성 보호를 하기위해 추가 보안 솔루션을 사용할 수 있음 ..;
6.1.2.6 Session Context
세션 컨텍스트는 NS, AS의 세션을 보관함
NS 세션은 아래와 같이 구성됨
-N/SNwkSIntKey
-NwkSEncKey
-FCntUp
-FCntDwn(LW 1.0) or NFCntDwn(LW 1.1)
-DevAddr
AS 세션은 아래와 같이 구성됨
-AppSKey
-FCntUp
-FCntDown(LW 1.0) or AFCntDwn(LW 1.1)
네트웤 세션 상태는 NS와 디바이스 끼리 관리된다. 어플리케이션 세션 상태는 AS와 디바이스끼리 관리된다.
OTAA 또는 ABP 과정이 끝나면, 새로운 보안 세션이 NS, AS, 디바이스에 수립된다. 키와 디바이스 주소는 세션 기간 동안 고정된다(FNwkSIntKey, SNwkSIntKey, AppSKey, DevAddr). 프레임 카운터는 프레임 트래픽이 세션 기간 동안 교환 할 때 증가함 (FCntUp, FCntDwn, NFCntDwn, AFCntDwn)(이해안감).
It is RECOMMENDED that session state be maintained across power cycling of an end-device. Failure to do so for OTAA devices means the activation procedure will need to be executed on each power cycling of a device.
6.2 Over-the-Air Activation
OTAA의 경우, NS 송신 전 Join을 해야한다. Session context 정보가 없을 경우 매번 Join을 해야한다.
조인을 하기 전에 DevEUI, JoinEUI, NwkKey, AppKey가 있어야한다.
6.2.1 Join procedure
디바이스 관점에서, 조인은 Join/Re-Join Request/accept 메시지로 구성 되어있다.
6.2.2 Join-request message
디바이스가 조인 요청을 하면 조인 과정은 시작한다.
위의 3개 값이 전송된다.
DevNonce는 0부터 시작한다. DevNonce는 조인을 할 때마다 1씩 증가한다(다시 사용하면 안됨). DevNonce는 영구적이다(리붓해도 카운팅은 안사라짐). JoinEUI를 변경하지 않고 DevNonce를 리셋해버리면 NS는 조인 요청을 거절한다(NS는 디바이스의 Nonce를 관리함).
ReJoin일 경우, 위의 그림과 같이 JSEncKey를 생성하여, 복호화 한다(JoinReqType은 헤더에?(MAC 은 검증용으로 포함하고)).
6.2.4 ReJoin-request message
디바이스가 Join을 하여 활성화가 되면, 주기적으로 Re-Join 할 수 있다. Re-Join 요청의 경우, 뒷단 서버와 디바이스끼리 새로운 session context를 확립할 수 있게 해준다. Re-Join은 디바이스 처리 주체를 다른 NS로 이관을 하거나, 주어진 네트워크의 디바이스 devAddr을 변경하거나 re-key를 한다.
RX1/RX2 관련 설명 중
Note: NS는 Re-join Request/Accept Messgae를 처리하는데 디바이스의 성공적인 re-join upLink가 올라오기 전까지 이전 보안 context(session context)를 유지해야한다. 모든 케이스의 re-join 요청 메시지는 Re-Join 요청은 표준 Jorin 요청 메시지와 유사하다.
Rejoin은 Rejoin Type이라는 필드로 3개의 명령을 아래와 같이 구분한다.
Rejoin Req Type
설명
0
NetID와 DevEUI를 포함한다. 라디오 파라미터를 포함한 디바이스의 모든context를 리셋한다(devAddr, session keys, frame counters, radio parameters).
이 메시지는 장치의 JoinServer가 아닌 수신 네트워크 서버에 의해서만 장치의 홈 네트워크 서버로 라우팅 될 수 있다. MIC 도.
위와 같은 Enum들을 자주 봤을 것이다. 일단 얘기하자면 위와 같은 것은 Enum 안티 패턴이다.
아래와 같은 이유로 인해 Int/String Enum 패턴을 사용하면 프로그램이 쉽게 깨질 우려가 존재한다.
컴파일 시점의 상수 -> 상수의 유연한 핸들링이 힘들다.
그룹핑 기능이 없기 때문에 복잡한 기능을 소화하지 못 한다.
Int Enum의 경우, 인쇄 가능한 문자열을 생성하기 피곤하다.
String Enum의 경우, 문자열 비교를 하기 때문에 느리다.
타입 안전성을 보장하지 못 한다.
즉, 정리하자면 타입 세이프하지 못 하다. 때문에, 컴파일 타임에서 오류를 잡을 수 없다. 런타임 테스트를 통해 오류를 확인할 수 있다(int 또는 String형 만 받으면 다 되기 때문에 문제점을 찾기 위한 뎁스가 추가된다). 또한 확장성이 많이 부족하기 때문에 활용하는데 있어, 한계점이 존재할 수 밖에 없다(다만, 컴파일 시점 상수이기 때문에, 어노테이션에 쉽게 적용할 수 있다ㅎㅎ, Enum의 값은 어노테이션에 적용하지 못해 불편하다ㅠㅠㅠ).
그러나 우리는(나 포함) 간단하기 때문에 쓴닼ㅋㅋㅋㅋㅋㅋㅋ. 다만 제품화가 되는 프로그램은 Java Enum 을 쓰는 것을 강력하게 권고 하고싶다(또는 완성도를 더 높이고 싶은 토이 프로젝트).
Enum은 다음의 특징이 존재한다.
Enum은 static final 하다.
계승(상속)이 불가능하다.
컴파일 타임, 타입 세이프하다.
그룹핑이 가능하다(Enum의 Enum을 하여 조합 가능).
Object를 계상받아 Object에서 제공하는 메소드를 활용할 수 있다. 또는 디폴트 메소드를 사용할 수 있다.
serializable, comparable이 가능하다.
메소드를 사용하여 기능 확장이 무궁무진하다(상수 + 관련 데이터의 연계 및 연산을 사용할 수 있다.).
1. Enum은 DB의 코드 값이랑 많이 활용할 수 있다. 추가적으로 코드를 추가하는데도 무리가 없다.
2. 실질적으로 사용할 때 UserAuthority.GREAT_USER 이런식으로 타입 세이프하게 사용할 수 있다.
3. 접근 사용자가 권한의 가능 여부를 판단하는 기능을 메소드 추가를 통해 해소할 수 있다(상수 관련 연계 및 연산 처리).
4. Enum의 Enum을 사용하여, 권한의 권한으로 GREAT_USER, GOOD_USER에 해당하는 권한만 존재하는 Enum을 생성할 수 있다.
Enum 활용
publicenumPlanet{
MERCURY(3.302e+23, 2.439e6),
VENUS (4.869e+24, 6.052e6),
EARTH (5.975e+24, 6.378e6),
MARS (6.419e+23, 3.393e6),
JUPITER(1.899e+27, 7.149e7),
SATURN (5.685e+26, 6.027e7),
URANUS (8.683e+25, 2.556e7),
NEPTUNE(1.024e+26, 2.477e7);
privatefinaldouble mass; // In kilogramsprivatefinaldouble radius; // In metersprivatefinaldouble surfaceGravity; // In m / s^2// Universal gravitational constant in m^3 / kg s^2privatestaticfinaldouble G = 6.67300E-11;
// Constructor
Planet(double mass, double radius) {
this.mass = mass;
this.radius = radius;
surfaceGravity = G * mass / (radius * radius);
}
publicdoublemass(){ return mass; }
publicdoubleradius(){ return radius; }
publicdoublesurfaceGravity(){ return surfaceGravity; }
publicdoublesurfaceWeight(double mass){
return mass * surfaceGravity; // F = ma
}
}
해당 코드는 이펙티브 자바의 Enum 편에 제일 첫장에 나오는 소스 코드이다.
위와 같이 Enum을 활용할 수 있다.
Planet.VENUS.mass() 또는 Planet.VENUS.radius() 를 통해 쉽게 상수 값을 확인할 수 있다.
코드를 보면 딱 이해할 수 있다(가독성 원탑).
해당 상수와 연계된 연산(surfaceWeight, surfaceGravity)을 해당 Enum에서 충분히 소화할 수 있다(SRP)
Enum은 EnumMap, EnumSet 활용하여 더 활용할 수 있으며, 인터페이스를 통해 유연하게 개발할 수 있다.
자세한 사항은 이펙티브 자바 Enum편 또는 Enum 활용에 대한 구글링을 참고한다. 정말 다양하게 Enum을 활용한다.
멋지다.
Spring/MyBatis에서 Enum을 사용할 때 골 때리는 구간이 좀 존재한다. 아래와 같이 해결한다.
Spring에서의 Enum활용(Message Converter)
스프링에서 Jackson Mapper를 통해 JSON 메시지에 대해 자동 Message Converting 기능을 사용할 때 Enum은 별도 처리가 필요하다. 아래와 같이 @JsonValue어노테이션을 지정하면 된다. 좀 아쉬운 점은 모든 Message Converter에 공통적으로 적용하기는 어려운 듯 하다.. 그래도 어노테이션 하나 지정을 통해 개선할 수 있어 다행이다.
스프링에서는 Message Converting 작업과 동시에 Validation을 할 수 있게해준다. 그러면, Enum의 경우, 해당 작업을 어떻게 처리할까? @NotEmpty를 적용했을 경우 아래와 같은 오류를 뱉어 낸다.
Jackson mapper @NotEmpty 적용(실패)
{"status":"fail","errMsg":"UnexpectedTypeException: No validator could be found for type: **.EnumTestVo$UserAuthority","errCode":1}
별도의 처리를 하지 않을 경우, @NotNull @Null 만 적용할 수 있다. @NotNull을 처리할 경우, 아래와 같이 성공적인 벨리데이션 체크를 할 수 있다.
Jackson mapper @NotNull 적용(성공)
{"status":"fail","errMsg":"CustomIllegalArgumentException: Message Validation Error : [target: enumTestVo.userAuthority, errMsg: may not be null ] ","errCode":21013}
만약 NotEmpty와 그 이상의 Validation은 그냥 Custom Validation을 생성하면 된다.
1. @EnumNamePattern(enumCodes={"BAD_USER"})의 경우, BAD_USER만 받는 VO 객체이다.
2. 해당 사항은 Custom Validation과 어노테이션을 추가한 것이다.
3. EnumNamePatternValidator에서 정규 표현식을 통해 참 / 거짓을 판단한다.
4. EnumNamePatternValidator에서 null 값에 대해 는 거짓으로 판단한다.
initialize 메소드에서 최대한 작업할 수 있는 것을 작업하자, isValid 에서 매 Request마다 작업을 진행하기 때문이다.
MyBatis에서의 Enum활용(TypeHandler)
이제 Controller 생성(아래 참고) 및 MyBatis를 연동한다. 우리는 "BAD_USER"가 아닌 DB 코드 값 "USR_004"를 입력하고 싶다. Typehandler를 적용하지 않을 경우, BAD_USER가 입력되는 불상사가 생긴다. MyBatis TypeHandler는 JDBC 프로그래밍에서의 ResultSet, PreparedStatement를 원하는대로 핸들링할 수 있다.
TypeHandler 미적용 감사로그(BAD_USER가 insert 된다)
[***]-2020-07-2022:35:21.741 main DEBUG: **.mapper.enum.insertEnumTest - ==> Preparing: insert into enum_test( user_id, user_name, user_authority )values( ?, ?, ? );
[***]-2020-07-2022:35:21.764 main DEBUG: **.insertEnumTest - ==> Parameters: ehdvudee(String), shin(String), BAD_USER(String)
[***]-2020-07-2022:35:21.770 main DEBUG: **.enum.insertEnumTest - <== Updates: 1
[***]-2020-07-2022:35:21.773 main DEBUG: **.mapper.enum.selectEnumTest - ==> Preparing: select user_id userId, user_name userName, user_authority userAuthority from enum_test
[***]-2020-07-2022:35:21.773 main DEBUG: **.mapper.enum.selectEnumTest - ==> Parameters:
[***]-2020-07-2022:35:21.800 main TRACE: **.mapper.enum.selectEnumTest - <== Columns: userid, username, userauthority
[***]-2020-07-2022:35:21.800 main TRACE: **.mapper.enum.selectEnumTest - <== Row: ehdvudee, shin, BAD_USER
[***]-2020-07-2022:35:21.803 main DEBUG: **.mapper.enum.selectEnumTest - <== Total: 1
-k: (t, n)구성 요소 중 t에 해당하는 값이다. 비밀을 만들기 위한 비밀 조각들의 임계치 값(최소 개수 값)
-n: (t, n)구성 요소 중 n에 해당하는 값이다. 생성하고자 하는 비밀 조각들의 개수이다.
-primeModulus: 비밀을 만들 때 사용하는 소수 값이다.
-Description Info: 기타 정보
모든 메소들은 유틸성이다.
SplitSecretOutput
-
split 메스도를 통해 반환되는 객체이다. 사용자는 해당 객체를 이용해서 시크릿 조각들을 사용할 수 있다.
ShareInfo
-
시크릿 조각을 갖고 있는 객체이다(x, y 값).
CombineOutput
-
시크릿 조각 조합 후의 객체
-
split
시크릿 조각들을 생성한다.
-
combine
시크릿 조각 조합(비밀 생성)
[유틸 클래스(중요사항만 기재)]
내부 클래스
메소드
설명
비고
PolyEquationImpl
-
다항 방정식을 처리하는 유틸클래스이다.
calculateFofX
x 값을 정의하면, 해당 값으로 방정식의 계산을 실시한다. 예는 다음과 같다.
fx = 1234 + 166x + 94x2
위의 값에 x가 1일 경우, 1494를 반환한다.
NumberSimplex
-
선형대수 관련 연산을 할 때 사용.
시크릿 조각을 조립할 때 사용하는 핵심 클래스
BigRational
-
큰 값의 유리수 처리
Numbermatrix
-
Number를 확장한 객체에 대해 행렬을 형성해주는 클래스
코드 분석(중요사항만)
[분석 대상]
- SecretShare.split 메소드
- SecretShare.combine 메소드
[SecretShare.split]
개요
예: PublicInfo객체의 맴버 변수를 다음과 같이 지정한다.
-n = 16
-k = 3
-prime = 1613
위를 해석하면 16개의 조각을 생성하며, 3개의 조각은 임계치를 뜻한다. 즉, 3개의 조각이 존재할 경우 Secret을 생성할 수 있다. k가 3을 뜻하는 것은 이차방정식 그래프를 생성한 다는것이다. 예를 들면 아래와 같다(1234는 시크릿이다.).
코드 분석
if (secret == null)
{
thrownew SecretShareException("Secret cannot be null");
}
if (secret.signum() <= 0)
{
thrownew SecretShareException("Secret cannot be negative");
}
if (publicInfo.getPrimeModulus() != null)
{
checkThatModulusIsAppropriate(publicInfo.getPrimeModulus(), secret);
}
- 유효성 검사를 실시한다.
BigInteger[] coeffs = new BigInteger[publicInfo.getK()];
- k를 조회하여, k개의 계수를 생성한다 coeffs는 coefficient(계수)의 줄임말을 의미한다.
- 계수 1개의 수준은 1개의 Array이다.
randomizeCoeffs(coeffs, random, publicInfo.getPrimeModulus(), secret);
privatevoidrandomizeCoeffs(final BigInteger[] coeffs, final Random random, final BigInteger modulus, final BigInteger secret){
for (int i = 1, n = coeffs.length; i < n; i++)
{
BigInteger big = null;
//big = coeffGenOriginal(random); // see Issue#8
big = coeffGenImproved(random, modulus);
// FIX? TODO:? FIX?
big = big.abs(); // make it positive
coeffs[i] = big;
// Book says "all coefficients are smaller than the modulus"if (modulus != null)
{
coeffs[i] = coeffs[i].mod(modulus);
}
// FIX? TODO: FIX? experiment says "all coefficients are smaller than the secret"
coeffs[i] = coeffs[i].mod(secret);
}
}
- Secret보다 작고, Prime보다 작은 Secret을 제외한 계수들을 생성한다.
- 예를들면 S가 1234이고 k가 3일 때 S를 제외한 1차항, 2차항에 대해 랜덤 계수를 생성한다.
- 0차항의 자리는 S 1234 값이 들어간다.
coeffs[0] = secret;
- 0차 항은 S 값이다.
SplitSecretOutput ret = new SplitSecretOutput(this.publicInfo, equation);
for (int x = 1, n = publicInfo.getNforSplit() + 1; x < n; x++)
{
final BigInteger fofx = equation.calculateFofX(BigInteger.valueOf(x));
BigInteger data = fofx;
if (publicInfo.primeModulus != null)
{
data = data.mod(publicInfo.primeModulus);
}
final ShareInfo share = new ShareInfo(x, data, this.publicInfo);
if (publicInfo.primeModulus != null)
{
if (data.compareTo(publicInfo.getPrimeModulus()) > 0)
{
thrownew RuntimeException("" + data + "\n" + publicInfo.getPrimeModulus() + "\n");
}
}
ret.sharesInfo.add(share);
}
return ret;
- x값은 1부터 시작하기 때문에 1부터 시작하여, n값(16) 까지 반복을 실시한다.
- calculateFofX를 통해 방정식의 셈을 실시한다.
- 해당 결과 값에 대해 mod를 실시한다.
- 생성한 Secret 조각들은 ShareInfo 객체에 적재한다.
- ShareInfo 객체는 SplitSecretOutput에 적재, 사용자는 최종적으로 SplitSecretOutput을 반환 받는다.
- 해당 과정은 다음의 그림을 만드는 과정과 같다.
[SecretShare.combine]
개요
ShareInfo는 다음과 같다.
-(1, 1494)
-(2, 329)
-(3, 965)
위의 ShareInfo 객체는 비밀을 만들기 위해 사용되는 비밀 조각들이다. 해당 조각들을 이용하여 combine 메소드를 실행한다. 해당 메소드는 불필요한 부분이 많기 때문에 해당 부분은 전부 설명에서 제외하였다.
코드 분석
CombineOutput ret = null;
sanityCheckPublicInfos(publicInfo, usetheseshares);
if (true)
{
println(" SOLVING USING THESE SHARES, mod=" + publicInfo.getPrimeModulus());
for (ShareInfo si : usetheseshares)
{
println(" " + si.share);
}
println("end SOLVING USING THESE SHARES");
}
if (publicInfo.getK() > usetheseshares.size())
{
thrownew SecretShareException("Must have " + publicInfo.getK() +
" shares to solve. Only provided " +
usetheseshares.size());
}
checkForDuplicatesOrThrow(usetheseshares);
공공데이터 포털에서 병무청 신체 계측 정보를 제공 받는다. 해당 데이터는 군대를 가기 위한 20대 남성의 신체정보를 계측한 데이터셋이다. 해당 데이터 셋을 학습 시킨 후다음과 같이 체중을 예측한다. 신장, 허리 둘레, 가슴 둘레, 머리 둘레, 발 길이 값을 통해 체중을 예측한다.데이터 셋의 구성은 아래와 같다. 학습방법은 대표적인 기본 방법인 선형 학습법을 사용한다.
데이터셋의 구성 현황
항목
설명
비고
성별
남
연령
20대(대부분 20대 초반)
데이터 개수
135,670
데이터 구성
순번, 날짜, 가슴 둘레, 소매 길이, 신장, 허리 둘레, 샅 높이, 머리 둘레, 발 길이, 몸무게
데이터 단위
cm
데이터 계측 기간
2013 ~ 2016
항목
설명
비고
순번
순번 값
측정 일자
yyyyMMDD 또는 yyyy
가슴 둘레
가슴둘레(단위: cm)
X1 값
소매 길이
소매 길이(단위: cm)
신장
신장(단위: cm)
x2 값
허리 둘레
허리둘레(단위: cm)
X3 값
샅 높이
실제 다리 길이(단위: cm)
머리 둘레
머리둘레(단위: cm)
x4 값
발 길이
발길이(단위: cm)
x5 값
체중
체중(단위: cm)
y 값
학습 전략
해당 데이터셋을 통해 지도학습을 하기위해서는 아래와 같은 절차가 존재한다.
1.가설 정의
2.학습 알고리즘 선정
3.학습을 위한 코스트 함수 정의
4.코스트를 낮추기 위한 옵티마이저 정의
5.학습률 및 횟수 정의
6.학습 및 데이터 검증
“보통, 키가 크면 몸무게가 높다.” 또는 “보통, 허리 둘레가 크면 몸무게가 높다.” 와 같이 가설을 잡는다. 해당 가설을 입증하기 위해, 데이터 학습과 검증을 진행한다. 데이터 학습을 진행하기 위해서는 가설에 상응하는 학습 알고리즘 선정, 코스트 함수 정의, 옵티마이저 정의가 필요하다. 해당 가설은 선형 회귀(Linear Regression)알고리즘을 통해 학습을 진행하며, 해당 학습 Cost를 최소화 하기 위해, 경사하강법(Gradient Descent) 알고리즘을 적용하여, Cost의 저점을 찾는다.경사하강법의 학습 폭은학습률(Learning Rate)을 통해 조절할 수 있다.
전처리
해당 데이터셋은 CSV 형식으로 제공받을 수 있기 때문에전처리 작업이 매우 수월하다.
데이터셋에서 학습에 사용할 데이터는 “가슴 둘레, 신장, 허리 둘레, 머리 둘레, 발 길이, 체중“ 이다. 여기서 체중을 제외한 5개의 값은 입력 값이며, 체중은 출력 값이다.
전처리
데이터 셋의 시각화
- 해당데이터셋에서의 신장, 허리 둘레, 체중을 시각화하였을 경우, 약한 선형 구조를 그릴 수 있다. 즉, 상관관계가 존재한다.
- 허리 둘레와 체중 비교 그래프를 통해 허리 둘레가 크면, 체중도 크다는 사항에 대해 이해할 수 있다.
- 신장과 체중 비교 그래프는 규칙이 존재하지 않는다.
- 허리 둘레와 체중 비교 그래프는 약한 선형 구조이다. 강한 선형구조를 도출하기 위해 “신장”, “가슴 둘레” 등과 같은 값을 통해 강한 선형 구조를 그릴 수 있다. 즉, “허리 둘레(x1) + 신장(x2)+ x… = 체중(y)“이다.
가설 정의
현실 세계에서 납득할 수 있는 가설은 총 3개를 구할 수 있다.
-허리 둘레가 크면 몸무게가 높다.
-가슴 둘레가 크면 몸무게가 높다.
-신장이 크면 몸무게가 높다.
즉, 최종 가설은 “허리 둘레, 가슴 둘레, 신장이 크면 그만큼 몸무게 또한 크다.” 이다.
[학습 알고리즘] – 개요
보통 위의 가설과 같이 선형의 구조를 갖고 있는 경우, 학습 알고리즘은 선형 회귀를 선택한다. 선형 회귀는 선형 예측 함수를 사용해 회귀식을 모델링하며, 알려지지 않은 파라미터는 데이터로부터 예측한다. 즉, 데이터[허리 둘레(x1), 가슴 둘레(x2), 신장(x3)]로 부터 알려지지 않은 파라미터[체중(y)]를 예측한다.
학습 방법 - [학습 알고리즘] – 설명
선형회귀 알고리즘을 시각화하면 다음과 같다.
-선형 회귀는 일차방정식 처럼 W와 b에 의해 가설 H 값이 달라진다.
-만약 허리 둘레, 가슴 둘레, 신장과 같이 값이 2개 이상일때는 선형 회귀(2)와 같다.
-우리는 H(X) = XW를 통해 다중 선형 회귀 또한 구성할 수 있다.
비용 함수
선형 회귀 알고리즘의 학습 정확도를 올리기 위해서는 cost 또는 loss를 줄여야한다. 가설과 실제 데이터 점의 거리가 짧아야 좋은 것이다. 즉, 더 좋은 가설을 판단하기 위해서는 가설의 점과 실제 데이터의 점을 비교한다. 해당 점이 짧은 것이 좋은 가설이며, 해당 거리를 측정해주는 것을 cost/loss function이라 칭한다.
선형 회귀의 Cost Function 식은 보통 (H(x)-y)^2이다. 만약 2개 이상의 값이 있을 경우, Cost Function 식 그림과 같이 시그마 합 공식으로 표현할 수 있다. 해당 값을 작게 구하는 것이 선형 회귀의 학습이다.
옵티마이저
- Cost Function의최소값을 찾는 알고리즘을 옵티마이져라 한다. 해당 프로젝트는 경사 하강법(Gradient Descent) 옵티마이져를 통해 최소값을 구한다.
- 지속적인 반복 학습을 통해 Cost의 최소 값을 도출한다.
- 그러나, 데이터의 선형성이 존재하지 않으면 제대로된 Cost 감소를 실현하지 못한다.
학습
- 해당 가설을 입증하기 위해 선형 회귀(Linear Regression)알고리즘을 통해 학습을 진행
- Cost를 최소화 하기 위해, 경사하강법(Gradient Descent) 알고리즘을 적용
- 경사하강법의 학습 폭은학습률(Learning Rate)을 통해 조절
- 학습률(Learning Rate)은 0.000015이다.
- 135,670개의 데이터를 5만번 학습 시켰으며, 학습률은 0.000015이다. 최종 Cost는 약 32.7382이다. 준수한 Cost를 얻었다.
데이터 검증
제일 중요한 부분이다. 우리가 가설을 정의하고알고리즘을 선정하고 학습 시킨 모델이 제대로된 결과를 예측하는지 확인해야한다.
- 제일 좋은 방법은 데이터셋이 많을 경우, 데이터셋 중 1%만 검증에 사용하는 것이다.
- 또한 현실 세계에서 계측할 수 있으면 해당 값을 검증 데이터로 활용할 수 있다.
- 학습된 모델을 로컬 디스크에 저장하여, 해당 모델을 이용하여, 검증을 실시한다.
항목
순번
신장
허리둘레
가슴둘레
머리둘레
발
길이
실몸무게
예상치
(AI 계산)
오차(kg)
비고
검증 데이터
1
170.0
72.6
83.4
60.8
27.1
50.2
52.099712
+2
2
173.0
75.6
84.8
59.6
29.3
57.4
56.72512
-1
3
177.8
76.0
94.6
59.6
29.5
65.7
64.838196
-1
4
181.1
82.1
91.1
59.1
29.2
67.8
66.06932
-1
5
175.3
90.3
97.3
61.0
29.6
76.5
72.91974
-4
오차 벗어남
6
173.7
89.0
97.1
58.9
26.5
81.3
73.65506
-8
오차 벗어남
7
172.4
101.3
101.7
59.1
27.8
82.4
83.767784
+1
8
186.9
97.9
97.6
60.8
29.9
86.7
78.1594
-8
오차 벗어남
9
170.0
98.4
106.5
61.3
29.0
86.8
83.62165
-3
10
175.2
92.8
108.1
60.2
30.9
88.0
83.75437
-5
오차 벗어남
11
179.0
103.8
106.9
59.5
28.3
91
89.168076
-2
12
175.4
111.7
118.0
60.6
28.2
101.1
100.55304
-1
실세계
검증 데이터
13
183.2
98
102
61
28
87.5
80.801865
-7
오차 벗어남
14
181
115
121
60.5
28
107
105.02866
-2
15
173
85
89.5
56
26.5
68
68.778595
-
16
176
92
107
61
28
82
81.05168
-1
17
172
108
110
59
28
96
93.85968
-3
18
174.1
98
105
58
26.8
83
85.66101
-2
요약
- 사용자의 신장, 허리 둘레, 머리 둘레, 가슴 둘레, 발 길이를 통해 체중 예측
- 가설을 입증하기 위해 선형 회귀(Linear Regression)알고리즘을 통해 학습을 진행
- Cost를 최소화 하기 위해, 경사하강법(Gradient Descent) 알고리즘을 적용
- 경사하강법의 학습 폭은학습률(Learning Rate)을 통해 조절
- 학습률(Learning Rate)은 0.000015이다.
- 135,670개의 데이터를 5만번 학습 시켰으며, 학습률은 0.000015이다. 최종 Cost는 약 32.7382이다. 준수한 Cost를 얻었다
- 데이터의 매칭률은 72.22%이다.
한계
- 체질량 지수(Body Mass Index, BMI)의 문제점은 순전히 키와 몸무게를 통해 비만을 판정한다.
- 해당 사항의 문제점은 체지방 비율, 근육량, 뼈밀도 등의 데이터가 배제되었다는 것이다.
- 현재 데이터 또한 해당 사항을 배제하였다.
- 결국 오차는 근육량, 체지방 비율, 뼈밀도 등의 값이 평균치 보다 많거나 적을 때 생길 수 있다.
- 즉, 추가적으로 필요한 데이터셋은 나이, 근육량, 체지방량, 뼈밀도 이다. 해당 데이터셋을 통해 Cost를 5이하 최소화할 수 있을 것이다.
이론은 SSS의 이론에 대한 설명이며, 구현체 분석은 GitHub의 SSS 구현체 중 스타 지수가 높은 프로젝트의 소스코드를 분석한다.
목차
개요
이론 - 개념
이론 - 기본
이론 - 응용
장점
단점
보안
참고문헌
개요
비밀 공유(secret sharing)는 비밀키의 소유자가 1명이 아닌 다수의 인증된(authorized) 참가자(participant) 들이 되고, 이 때 임의의 참가자는 비밀키의 일부 조각을 소유하게 되는 것이다. 예를 들어, 임의의 그룹 내에 n명의 인증된 참가자가 존재할 경우 비밀키를 n개의 조각으로 분리하여 참가자들에게 각각 비밀 조각 1개씩 나눈다. 이 중 적어 도 n명 이상의 인증된 참가자들 모이면 이들의 비밀 조각들을 이용해 본래의 비밀키(또는 비밀데이터)를 알게 된다.
즉, 비밀키의 조각을 나눠 갖은 3명이 존재한다. 해당 비밀키를 복구하기 위해서는 무조건 3명이 존재해야 한다.
이론 - 개념
선 1줄 요약: 선을 정의하는데 필요한 점의 수
SSS의 이해 – 1
일차 함수 그래프를 통한 비밀번호 공유
참석자: A, B, C
y = x + 3 일차 함수 그래프
A는 위의 그림과 같은 공식(y = x + 3, 일차 함수 그래프)으로 비밀번호를 생성하였다.
A는 비밀번호를 복구를 위해, BC에게 (-2, 1)과 (1, 4)를 각각 공유하였다.
B 또는 C는 혼자서 비밀번호를 구성(완성)하지 못한다.
점에서 선을 정의하기 위해서는 수많은(거의 무한의) 경우의 수가 존재하기 떄문이다.
만약 B, C가 합의를 한다면 비밀번호를 생성할 수 있다.
위와 같이 B, C의 부정행위를 방지할 수 없다.
SSS의 이해 – 2
이차 함수 그래프를 통한 비밀번호 공유
참석자: A, B, C, D
y= x^2 - 3x + 3
A는 위의 그림과 같은 공식(y = x^2 -3x + 3, 이차 함수 그래프)으로 비밀번호를 생성하였다.
A의 베프는 D이다. 이 친구는 절대 배신을 하지 않는다.
A는 비밀번호를 복구를 위해, BCD에게 (1.5, 0.75), (0, 3), (3,3)의 값을 각각 공유하였다. 그리고, 자기 자신은 (2, 1)을 갖고 있는다.
B, C와 공모를 하였지만 비밀번호를 추측하지 못한다.
해당 공식은 3개의 점이 필요하기 때문이다.
만약 A는 비밀번호를 깜박하여, 비밀번호의 재생성이 필요하다.
BCD를 소집하여, 비밀번호를 생성한다.
만약 C가 해당 값을 잃어버리거나 또는 해당 값이 compromised가 되면 ABD는 소집하여, 해당 값을 생성한다.
해당 값으로 다시 그래프를 생성한 후, ABCD는 새로운 인자를 나눠 갖는다.
해당 공식은3개의 점만 필요하지만,보안을 위해 공식을 재구성할 수 있다.예를 들어, 5, 10, 15개 그 이상의 점을 필요로 하는 것이다.위의 예는2차 함수 그래프이며,구성을 위해 최소3개의 점이 필요했다.해당 사항을4,5,6차 함수 그래프를 사용한다.그러면, n차 함수 그래프는n+1의 점으로 그래프를 형성할 수 있다.아래와 같이 방정식으로 표현할 수 있다.
이것을 (t, n) threshold secret sharing이라 칭한다. n개의 비밀 조각들을 제공하고 비밀을 재구성하려면 최소 t조각의 임계 값이 필요하다. 위의 사항은(3, 4)이다. 이게 바로 SSS의 기본 이론이다. t-1차 다항식이 후보로 존재하여 원래의 다항식을 추측할 수 없다. 즉, t개보다 적은 비밀 조각이 모이더라도 원래의 비밀 값은 알 수 없다.
이론 - 기본
Preparation
우리의 Secret은 1234이다(S = 1234).
해당 Secret을 6조각으로 분리한다. 6조각(n = 6) 중 3조각(k = 3)이 모이면 Secret을 생성할 수 있다. k – 1 값은 166, 94이다.
은 Secret이다.
여기서 마지막 k 값 즉, 마지막 공유 조각을 만드는 식은 아래와 같다.
Reconstruction
Secret은 3개의 조각만 있으면 만들 수 있다. 2, 4, 5를 통해 Secret을 생성하면 다음의 식과 같다.
위의 식은 SSS의 기본 식이다. 위의 다항식은 정수 산술을 사용하기 때문에 안전하지가 않다. 공격자는 그래프의 포인트를 얻을 때마다, 포인트 경우의 수가 줄어든다. 예를 들어, 5차 함수 그래프를 그릴 때, SSS의 k는 6이 된다. 즉 임계치는 6이 되며 6조각이 모이면 S를 유도할 수 있다. 그런데 공격자가 k-1까지의 값을 갖고 있으며, 해당 값으로 그래프를 표현해본다. 그러면 얼추 다음의 지점의 경우의 수가 많이 감소한다. 해당사항을 시각적으로 확인할 수 있다(상상해보라).
해당 문제는 다음과 같이 해결할 수 있다.
소수의 모듈러 연산을 통해 그래프를 꼬아 버린다.
모듈러 연산에 사용하는 소수 값의 조건은 아래와 같다. 아래의 조건을 설명하면 다음과 같다. P 즉, prime(소수) p는 n(공유 조각들)보다 크고, S(비밀값)보다 크다. 는 prime의 집합이다. p는 그냥 소수라는 의미이다.
예는 아래와 같다.
k or t = 3
n = 6
k-1 = 166, 94
S = 1234
P =1613
공유 비밀의 값이 1234일 때 우리는 아래와 같은 SSS 공식을 사용하여, 비밀 조각을 생성할 수 있다.
[공유 비밀 생성]
[비밀 재구성 – 1(basic)]
설명
1. 유리수의 modulo 연산의 경우, 정수가 될 때까지 + 또는 – 연산을 통해 정수로 변경한다. 그 후, modulo 연산을 진행한다.
2. 음수의 modulo 연산의 경우, 일반 양수 mod 연산 후, 음수로 변경한 후, m을 더한다(예: -22 mod 5, 22 mod 5는 2이다. -2 + 5 = 3. 즉, -22 mod 5는 3이다.
[비밀 재구성 – 2(optimized)]
공유를 재구성할 때 아래의 식을 적용한다(j, m의 index가 0부터 시작할 때).
식의 풀이는 다음과 같다(i, j의 index가 1부터 시작할 때)
위의식이 최적화 식인 이유는 아래와 같이 필요한 지수에 대해서만 연산을 하기 때문이다(우리가 알고 싶은 것은 0차항 즉, Secret 값이다.). 즉, 1234 + 166x + 94x^2의 값 중에 필요없는 166과 94는 연산에서 제외하고, 비밀 값 1234만 연산한다.
[대입]
위의식이 최적화 식인 이유는 아래와 같이 필요한 지수에 대해서만 연산을 하기 때문이다(우리가 알고 싶은 것은 0차항 즉, Secret 값이다.). 즉, 1234 + 166x + 94x^2의 값 중에 필요없는 166과 94는 연산에서 제외하고, 비밀 값 1234만 연산한다.
장점
Shamir’s secret sharing의 특징은 모든 구성원이 나눠갖는 비밀이 서로 유일(unique)하며 평등하다는 것이다. 또한 threshold 이상의 조각만 있으면 secret를 복구할 수 있기 때문에 일부 비밀 조각이 유실되더라도 안전하고, 소수의 배신자가 있더라도 secret을 복구할 수 없다.
단점
이런 특징들은 한계점이 되기도 한다. 모든 shared secret이 동등하기 때문에 비밀을 나눌 때 보안 등급에 따라 나누기가 힘들고, 비밀 조각을 제공할 때 일부러 틀린 비밀 값을 주는 것을 방지할 수 없으며 비밀을 복구할 때도 누군가 오염된 값을 줘서 비밀이 제대로 복구되었는지 알 수 없다.
그러나, 이와 같은 단점들은 타계층(예: 어플리케이션 계층)에서 보완하면 되는 부분이라 생각한다. 즉, 단점이라기보다는 필요 보완사항이 되겠다.
보안
SSS의 보안 내성은 정보 이론적 보안(Information-theoretic security)이다[1]. 정보 이론으로부터 순수하게 파생된 암호시스템이다. 공격자가 암호를 깨기 위한 충분한 정보가 있지 않은 이상, 공격자의 전사 공격(brute force attack)으로부터 안전하다. 즉, 공격자가 아무리 (t, n) 구성 요소 중 t-1의 값을 갖고 있어도 안전하다. 쉽게 생각해서 “점1개를 이용하여, 선을 예측하라”이다. 가능한가?
그러나, 2차 방정식 이상의 그래프는 그래프의 기울기를 얼추 예상할 수 있다. 때문에, 해당 기울기조차 쪼개어버리는 것이다. 그림 1의 그래프를 modulo 연산을 통해 그림 2처럼 쪼개어 버린다.
우연히 1억건의 DB 데이터를 처리하는 API 서버를 개발하게 되었다. 그러나 해당 API 서버에서의 요청 처리 시간은 5초 ~ 15초, 끔직한 상황이다. REST API의 속도 개선이 절실히 필요하다.
REST API 속도 개선 기술
REST API(HTTP 통신)는 일단 느리다. 그러나, 우리는 웹어플리케이션(REST API)의 속도 개선을 위해 다음과 같은 기술들을 사용할 수 있다.
HTTP 프로토콜의 Cache-Control, ETag를 통한 네트워크 레이턴시 감소
DBMS의 딕셔너리 캐시/라이브러리 캐시/ 버퍼 캐시의 사용으로 인한 디스크 I/O 최소화
DBMS SQL 쿼리 튜닝
어플리케이션의 소스 코드 개선
HTTP 프로토콜의 Cache-Control과 ETag를 동적 컨텐츠 즉, REST API에 적용하고 사용하기가 까다로우며(클라이언트의 수정이 요함), 효과가 미비하다.
DBMS의 엔진 튜닝 및 SQL 쿼리 튜닝도 한계가 있다(나는 전문가가 아니다.) SQL 튜닝 해봤자 실행계획 보고 쿼리 변경 및 인덱스 추가이다. 이번에는 쿼리 소트 부하가 상당하며, 해당사항을 해결하기 위한 SQL 튜닝 지식이 부족했다.
SQL 결과 값을 재가공하기 때문에 최소 O(n2)의 시간복잡도를 갖고 있다. 이런 시간복잡도는 어쩔 수 없다. 결론은 어플리케이션의 소스 코드 개선은 가성비가 좋지 못하다.
결론은 어플리케이션의 캐시 기술을 사용한다. 즉, REST API 결과를 캐싱하는 것이다. 우리는 주변에서 많은 캐싱 기술과 용례를 보고 있었는데 왜 이것을 여지껏 몰랐을까? 정말 의문이다.
스프링 캐시
스프링은 3.1 이상부터 캐시 추상화 기술을 제공한다. 설정과 어노테이션 추가를 통해 바로 쉽게 캐시를 적용할 수 있다(4.1에 확장을 했지만 기능은 잘 모르겠다.). 트랜잭션과 마찬가지로 AOP를 이용해 메소드 실행 과정에 투명하게 적용된다. 캐시 관련 코드를 어플리케이션 로직에 적용하지 않아도 된다.또한 캐시 서비스의 종류가 달라져도 괜찮다(캐시 impl/provider 변경). 캐시 기능과 어플리케이션의 기능이 이렇게 분리 되어있다. 해당 사항은 스프링에 대표적인 기술인 서비스 추상화 기술이다.
스프링 캐시를 사용하기 위해서는 CacheManager 설정과 해당 설정을 적용할 Service 메소드를 지정해야한다. Service 메소드는 트랜잭션의 설정과 같이 어노테이션 지정만으로 깔끔하게 캐시 기능을 구현할 수 있다.
CacheManager 설정은 용례1,2와 같이 설정한다. 간단한 설정은 용례 1과 같이하면된다.
Service 메소드에 적용하는 어노테이션은 다음과 같다.
@Cacheable
해당 어노테이션이 지정된 메소드는 캐시기능을 제공할 수 있다. 해당 어노테이션에서 자주 사용하는 값은 key, value, condition 정도이다.
value 값을 통해 캐시 분류(예: 사용자 조회 캐시/게시글 조회 캐시 분리)
key 값을 통해 인자별 캐시 분류(예: 1번(key) 사용자에 대한 캐싱) SpEL을 활용할 수 있다.
condition을 통해 일정 조건에 대해 충족하는 경우에만 캐싱
sync는 Spring 4.3 이상 지원되는 기능이며, 해당 기능을 통해 쓰레드 세이프하게 캐시를 생성한다(여러 스레드가 한번에 같은 캐시를 생성하는 것을 막는다.).
@CacheEvicit
캐시된 데이터는 갱신되어야 한다. 해당 어노테이션이 지정된 메소드는 캐시 삭제 기능을 제공할 수 있다.
value/key/condition은 @Cacheable 설명과 같다. 다만 캐시 삭제에 초점이 맞춰져있다.
allEntiries 값을 통해 key 별 캐시 삭제가 아닌, 모든 key의 캐시 삭제가 가능하다.
beforeInvocation을 통해 메소드 실행 전 캐시를 삭제할 수 있다.
@CachePut
해당 어노테이션이 지정된 메소드는 캐시하고자 하는 데이터를 저장’만’ 한다. 용례는 다음과 같다. 많은 데이터의 효율적인 캐시 기능 제공을 위해, 미리 저장만 하고 추후에 조회 API는 캐시된 데이터만 반환한다.
아래의 코드 블럭으로 통해 설명이 가능하다.
@CachePut(cacheNames="book", key="#isbn")public Book updateBook(ISBN isbn, BookDescriptor descriptor)
CacheManager
AOP를 이용해 캐시 부가기능을 적용할 수 있게 한다. 동시에 캐시 기술(프로바이더) 상관없이 추상화된 스프링 캐시 API를 이용할 수 잇게 서비스 추상화를 제공한다. 캐시 매니저는 CacheManager 인터페이스를 사용하며, 5가지의 concrete class를 제공한다. Concrete class는 다음과 같다.
ConcurrentMapCacheManager
ConcurrentHashMap을 이용하여 간단한 캐시 기능을 구현한 CacheManager이다. 해당 CahceManager의 경우, 캐시 용량 제한, 캐싱 알고리즘, TTL 등의 부가 기능은 없기 떄문에, 테스트 용도로만 사용을 권고한다(용례 1에 해당한다.).
SimpleCacheManager
기본적으로 제공하는 캐시가 없으며, 직접 등록해줘야 한다. 스프링 Cache 인터페이스를 직접 개발했을 때 테스트할 때 사용할 수 있다(용례 1에 해당한다.).
EhCacheCacheManager
EhCahce는 캐시 용량 제한, 캐싱 알고리즘, TTL, TTI, 파일 캐싱, 분산 서버 캐싱 등의 고급 기능을 제공하는 Java에서 제일 인기 있는 캐시 프레임워크이다. 해당 설정들은 XML 또는 JavaConfig를 통해 설정할 수 있다. 용례 2는 JavaConfig를 통해 설정하였다(XML 설정은 정보가 많으나, JavaConfig는 정보가 정말 부족하다.)(용례2에 해당한다.).
CompositeCacheManager
CompositeCacheManager는 하나 이상의 캐시 매니저를 사용하게 지원해주는 혼합 캐시 매니저이다.
또한 setDiskCache 메소드를 통해 디스크 캐시를 사용할 수 있다. 예를들어 캐시 데이터가 20MB 이며, 해당 데이터가 많을 경우, 메모리를 사용하면 Memory leak이 발생할 것이다. 해당 경우를 상쇄하기 위해 key 값은 메모리가 갖고 있고 value 값은 디스크가 갖고있는다.
또한 암호키 조회의 경우, 암호키 값/기간 무결성 검증 로직이 있기 때문에 TTL은 하루만 설정하였다.
여기서 나오는 아쉬움은 암호키의 값/기간 무결성 검증 로직 또한 AOP로 분류 했으면 해당 암호키 캐싱은 암호키가 폐기할 때 까지 캐시할 수 있을 것이다. 정말 아쉽다.
해당 문서는 실제 프로젝트에 적용하면서 정리한 내용, 보통의 이론적인 내용 SQLite DOC 및 괜찮은 블로그들을 참고 했다.
SQLite 특징
SQLite는 C언어로 개발된 embeded 데이터베이스 엔진이다. SQLite의 특징은 다음과 같다.
경량화: SQLite Library는 1MB ~ 0.6MB 이하이다. 또한 보통의 파일시스템 컨트롤 보다 약 30% 빠르다.
독립적: 표준 C 라이브러리를 통해 개발되었으며, 외부 라이브러리 또는 인터페이스에 종속적인 부분은 없으며, 단순 운영체제 위에서 독립적으로 혼자 잘 돌아간다.
신뢰성: SQLite는 많은 스마트폰, IoT장치, 데스트콥 어플리케이션에서 오래 사용되었다. 수치로 했을 경우, 몇억개의 사용처가 10년 넘게 유지되었으며, 아직까지 업데이트하며 유지보수(지속적인 오픈소스 개발 기여)가 되고 있다.
무서버: 데몬을 띄우는 보통의 RDBMS와 달리 서버없이 디스크 I/O를 통해 DB에 접근한다(Neo-Serverless, Application과의 같은 프로세스가 아닌 별도의 프로세스 지만, 그 별도의 프로세스는 Application이 관리하에 존재한다. 결국 Serverless이다. ).
무설정: 편리하다. 무서버의 특징으로 인해, 서버 프로세스의 설치, 설정, 초기화(가동), 관리, 트러블슈팅 등의 작업이 존재하지 않기 때문에 편리하다.
오픈소스: 오픈소스 라이센스는 public Domain으로 라이센스가 존재하지는 않지만, 지속적인 개발과 매 릴리즈마다 엄격한 테스트 과정이 존재한다. 최신 릴리즈 버전은 2019년 12월이다 ㄷㄷ.., 20주년 된 오픈소스 프로젝트이다.
1개의 데이터 파일: 말마따나 모든 데이터를 1개의 디스크 파일로 표현할 수 있다.
크로스 플랫폼 데이터 파일: 32bit / 64Bit, Big / Little Endian, 운영체제 상관없이 데이터 파일을 사용할 수 있다(VFM을 사용하기 때문에).
SQLite 이럴 때 사용 O
SQLite의 경우, PostgreSQL, MariaDB와 같은 clinet/server SQL DB 엔진과의 적절한 비교대상이 아니다. 해당 RDBMS는 엔터프라이즈 데이터의 공유 저장소 구현이기 때문에 확장성, 동시성, 중앙집중화 및 제어 등이 중요 초점 사항이다.
SQLite 엔진은 개별 프로그램의 로컬 데이터 저장소 구현이기 때문에, 경제성, 효율성, 신뢰성, 독립 및 단순성이 중요 초점 사항이다(보통 스마트폰의 어플리케이션의 DB 사용을 생각하면 상상이 좀 더 쉽다.).
각각 Serverless SQLite와 client/server RDBMS와의 장단점이 존재한다.
SQLite의 사용처는 다음과 같다.
Embedded IoT 디바이스
SQLite DB는 관리자가 필요 없으며, 디바이스에서 혼자 잘 돌아갈 수 있다. 예는 다음과 같다. 폰, TV, 게임 콘솔, 카메라, 시계, 부엌 장비, 자동차 등 모든 IoT 같은 장비
Mini/Toy 프로젝트 또는 소/중규모 웹사이트, 트래픽이 적은 웹사이트에서 무리없이 잘 작동한다.
어플리케이션에서 엔터프라이즈 RDBMS의 데이터를 캐쉬하기 위해 사용될 수 있다.
프로토타입/데모 프로젝트
배보다 배꼽이 더큰 상황을 저지해줄 수 있다. 어플리케이션의 데모 또는 프로토타입을 할 때 임시적으로 사용도 괜찮다.
SQLite 이럴 때 사용 X
다중 클라이언트, Database에 접근하는 많은 클라이언트가 존재할 경우(즉, 많은 동시성을 요구할 때)
엔터프라이즈 환경, 대규모 프로젝트
데이터웨어하우스, 데이터 대용량 처리의 제한, 해쉬조인을 지원하지 않는다. NL-Join(OLTP 적합)밖에 되지 않는다.
빅데이터, SQLite는 사이즈 제한은 140테라바이트 이다.
빈도 높은 동시성, read에 대해 제한은 없으나 write은 딱 1개만 허용하기 때문에 동시성 보장을 못한다(WAL 모드를 통해 Read/Write를 동시에 수용할 수 있지만 결국 Write를 할 수 있는 프로세스는 1개이다.).
SQLite FAQ (5) Can multiple applications or multiple instances of the same application access a single database file at the same time?
여러 프로세스가 동시에 DB를 접근하여 SELECT를 할 수 있다, 그러나, 한개의 프로세스만이 Write(INSERT/UPDATE/DELTE)를 할 수 있다.
SQLite reader/write Lock을 사용하여 DB 접근을 제어한다.
만약 특정 프로세스가 DB Write를 원하면, 파일 Write 하는 동안, Database 파일은 Lock이 걸려있으며, 이 때 Lock은 조회도 할 수 없다.
즉, 매 Write때, DB 접속을 원하는 프로세스/쓰레드는 휴지기를 갖는다(아무것도 못한다.).
SQLite 아키텍쳐
SQLite는 Core와 Backend로 구성이 되어있다. Core는 Interface, Tokenizer, Parser, Code generator, VM을 포함하고 있으며, Backend는 B-Tree, Pager, 파일 시스템에 접근하는 OS Interface를 포함하고 있다. Tokenizer, Parser, Code generator를 VM에 코드를 돌리기 위한 Compiler로 칭하고 있다.
(아키택쳐는 Compiler의 Parser/Code Generator 부분이 많이 어려웠다.)
(해당 내요은 공식홈페이지와 참고문헌을 참고하는게 좀더 좋다)
SQLite DB 사용 흐름도
Interface: 해당 인터페이스를 통해 SQL 쿼리문을 보낸다.
Tokenzer: 요청된 SQL 쿼리를 스캔하여 각각 알맞은 토큰으로 변경한 후, 해당 토큰을 Parser에 전달한다.
Parser: 토큰 스트림을 읽고, 해당 토큰들을 기반으로 하여 Parse Tree를 생성한다(C코드를 생성한다. 이 방식은 YACC/BISON과 같은 맥락이다.).
Code Generator: Parse Tree 분석 후, SQL 작업을 수행할 수 있는 바이트코드를 생성한다.
VDBE(Virtual DataBase Engine): Code Generator가 생성한 바이트코드를 해당 VM에서 실행한다.
BTree: 보통의 RDBMS의 경우, Index를 처리할 때 BTree를 사용한다. SQLite의 경우, 인덱스 뿐만 아니라, 데이터들 또한 BTree구조로 저장을 한다. SQLite에서 데이터를 조회하는 방식은 보통의 RDBMS에서 BTree를 통해 스캔하는 방식과 유사하다.
Pager: SQLite에서의 데이터 처리의 최소 단위를 칭하는 것 같다(Oracle의 데이터 블록과 유사하다 생각한다.) 아래와 같이 Page안에 Data BTree, Index BTree가 존재하는 방식인 것 같다. 트리에 있는 값은 진짜 값이 있는 것 같다(오라클 index BTree의 경우, 데이터 블록이 있고 그 블록안에 여러 값이 존재함). 해당 페이지 단위로 Locking과 Transaction 작업을 진행한다(commit, rollback)
VFS(Virtual File System): 해당 Page의 실제 값을 조회할 때, 즉 Disk의 datafile를 조회할 때 VFS를 사용한다. 해당 VM을 사용하기 때문에 플랫폼에 상관없이 똑 같은 API의 인터페이스를 통해 파일을 조회할 수 있다.
SQLite Lock 개념
현재 설명하고자 하는 Lock 메커니즘은 SQLite3 버전의 메커니즘이다. Lock은 데이터의 무결성을 유지하기 위해 존재하는 개념이다. 1개의 프로세스가 DB에 접근할 때 Lock을 얻는다.
Lock의 종류는 아래와 같다.
UNLOCKED: 파일을 열기만한 상태, DB에 Lock이 잡혀있지 않는 상태이다. 해당 상태에서는 읽기/쓰기가 불가능하다. 아무 프로세스가 접근하여, Locking을 하여, 데이터를 쓰거나 읽을 수 있다. 해당 상태는 기본 상태이다.
SHARED: 파일을 읽고 있는 상태, 동시에 여러 프로세스가 Shared Lock을 설정하여, 동시에 DB 파일을 읽을 수 있다. 그러나, Shared Lock이 1개라도 잡혀있으면 DB 파일은 쓸수 없다.
RESERVED: 파일을 쓸 예정인 상태, 프로세스가 Shared Lock을 잡았으며, DB 파일을 쓸 예정인 상태이다. Reserved Lock은 1개의 DB에 1개의 프로세스만 얻을 수 있다. Reserved Lock이 잡혔다고, Shared Lock을 못 잡는 것은 아니다. 즉, Reserved Lock이 잡혀도 DB 파일 읽기는 가능하다.
PENDING: Exclusive Lock을 잡기 전의 상태, 즉 데이터를 쓰기 전의 상태이다. 해당 Lock은 DB 파일에 데이터를 쓰기 위해 기다리는 상태이다. 해당 Lock은 1개의 DB에 1개의 프로세스만 얻을 수 있으며, Shared Lock이 모두 풀릴 때까지 기다린다(다른 프로세스에서 읽기 완료될 때까지 대기). 해당 상태에 돌입하면, 누구도 해당 DB 파일에 접근하지 못한다. 이 때 접근하면, Database is locked라는 상태메시지가 출력된다. 만약 Shared Lock이 모두 사라지면 바로 Exclusive Lock으로 전이를 한다.
EXCLUSIVE: 프로세스가 파일을 쓰는 상태, Exclusive Lock에 존재는 DB 파일에 데이터를 입력하기 위해서이다. 데이터의 입력은 Insert, Update, Delete가 해당한다. 동시성을 제고하기 위해서는 해당 상태가 최대한 짧아야 한다. 해당 상태 또한 누구도 DB 파일에 접근하지 못하며, 접근할 경우, Database is locked라는 상태메시지가 출력된다.
SQLite 트랜잭션 개념
Sqlite는 Rollback Journal이라는 방식을 통해 트랜잭션을 구현한다.
1개의 프로세스가 Exclusive Lock을 획득하여, 1개의 DB 파일에 데이터를 입력할 때 Journaling 기술을 사용한다. 변경되기 전의 데이터를 rollback journal에 저장한다. 저장 후, rollback journal에 DB 파일의 초기 크기 값도 저장되기 때문에, insert된 데이터도 rollback이 가능하다. 즉, 트랜잭션이 시작할 때 바구니 하나를 만들고, 변경되기 전의 값들을 바구니에 담고, 변경된 값들은 데이터 파일에 기록한다. Rollback상황에 마주치게 되면, 바구니에 값들을 갖고 Rollback을 실현한다. 트랜잭션 작업 중 시스템 크러쉬가 발생될 경우, 해당 journal은 “hot”이 되며, DB 데이터 무결성 복구를 위해 바로 사용된다.
Journal Mode는 총 6가지가 있다.
DELETE: 기본 모드이며, 트랜잭션 종료시 파일을 삭제한다.
TRUNCATE: 트랜잭션 종료시 파일을 0 상태로 만든다. DELETE 보다 더 빠르다.
PERSIST: 매 트랜잭션 마다 파일을 덮어쓰기 한다. TRUNCATE보다 빠르다.
MEMORY: Journal 데이터를 메모리에 기록한다. PERSIST 보다 빠르지만, DB Corrupt에 빠질 수 있다.
WAL: Write-Ahead Logging 방식을 사용한다.
NONE: Journaling을 하지 않음, 트랜잭션을 제어하지 못한다.
Journal WAL mode는 기존 전통적인 Journaling과 반대되는 방식이다. 일반 RDBMS에서 사용하는 WAL 방식을 차용하였다. OracleDBMS의 WAL 방식 설명은 아래를 참고한다. 해당 설명을 참고하고 읽으면 이해에 훨씬 도움이 될 것이다. 해당 WAL 방식을 통해 Read/Write를 동시에 할 수 있게 하여, 동시성을 증대시킨다.
SQLite에 일반 Journaling을 사용하면 파일에 대해 write/reading 상태 밖에 존재하지 않은데, WAL 방식을 사용하면, write/reading/checkpoing 상태가 존재한다. 또한 DB 파일 경로에 journal 임시 파일은 생기지 않고, wal 파일과 shm 파일 생성된다.
wal 파일에는 변경된, 변경하고 있는 데이터들을 저장한다. Commit을 할 경우에는 바로 db 파일에 적용하는 것이 아니라, wal 파일에 commit 구분자를 추가한다. 해당 wal 파일이 일정 크기만큼 커질 경우(default page size=1000) checkpoint가 발동되면서 wal 파일을 data file에 집어넣는 작업이 발생한다. shm 파일은 wal 파일에 접근할 때 공유 메모리 역할 즉, 캐쉬 역할을 한다.
이 때 궁금한 사항은 결국 WAL에 접근해야하면 기존 journal 방식과 똑같이 WAL 파일에 Lock이 걸리지 않냐 그럴 수 있다. 그러나, 다음과 같이 해결한다. 먼저, WAL의 유효한 커밋의 위치 파악을 한다. 해당 위치는 end_mark라 칭한다. WAL 모드에서는 CRUD 작업을 할 때 해당 end_mark를 통해 WAL 파일 복사 여부를 판단한다. 만약, 조회하고자 하는 데이터가 데이터 파일에만 있으면 해당 데이터 파일만 조회하고, 조회하고자 하는 데이터가 데이터 파일 + WAL 파일에도 존재하면 end_mark를 통해 필요 위치까지만 WAL 파일을 복사하여 조회한다. 이렇게 복사해서 사용하기 때문에 1Writer, N reader의 공존, 즉 writer/reader 가 같이 공존할 수 있는 동시성을 제공할 수 있다. 그러나, WAL 파일을 DB 파일에 저장하는 작업이 발생할 때는 Exclusive Lock이 발생한다. 해당 작업을 checkpointing라 한다.
부록) Oracle WAL 처리 방식
위의 사진은 오라클 공홈 DB 구조 사진을 발췌한 것이다. Oracle의 WAL 작업 아래와 같이 간단하게 설명한다.
클라이언트 insert 쿼리 요청
해당 insert문과 관련된 데이터의 undo 세그먼트 생성(rollback을 위한)
해당 insert문과 관련된 데이터의 redo log 생성(트랜잭션 작업 복구를 위한) 및 undo 세그먼트 적재
해당 redo log는 redo log buffer에 저장
변경된 데이터를 buffer cache에 저장(해당 블록은 더티 블록으로 전이)
클라이언트에서 Commit 요청을 할 경우, LGWR 프로세스는 rede log file에 log 파일을 저장한다. 만약 rollback을 하는 경우, redo log에 보관된 undo 세그먼트를 통해 rollback을 한다.
변경된 buffer cache의 더티 블록은 CKPT 프로세스에 의해 때가 되면 데이터 파일과 동기화 작업 실행 명령을 내린다. 데이터 파일 동기화 작업은 DBWR이 진행하며, 더티 블록 데이터를 데이터 파일에 등록한다.
위의 과정에서 데이터 저장 보다 로그 파일을 먼저 적재하는 것을 WAL 원칙 및 작업이라 한다. 이렇게 할 경우, DB corrupt으로부터 데이터를 무결하게 유지할 수 있다.
포팅할 때 겪었던 명령어들
리눅스에서 SQLite 사용할 때
보기 편하게 설정(sqlite db 파일과 같은 경로에 .sqliterc 파일 생성)
vim .sqliterc( 아래 추가, PRAGMA 설정도 될 것 같음 확인은 안해봄 ㅋㅋ )
.header on
.mode colum
SQLite 터미널 안에서
.open $db_name : 데이터베이스 생성(파일이 경우) 그리고 open
.exit, quit : 데이터베이스 종료
.tables: 테이블 목록 조회
.schema $tbl_name : DDL 조회
PRAGMA foreign_keys =ON: 참조 무결설 설정 ON(SQLite 하위 호환 때문에 디폴트 설정은 OFF이다.)
포팅할 때
Right/Full Outer Join을 지원하지 않는다. 그러므로 Right Outer Join의 경우, Left Outer Join으로 변경한다. Full Outer Join 쿼리를 잘 변경하자
now() 함수
datetime( 'now', 'localtime'): yyyy-MM-dd HH:mm:ss 포멧으로 저장
strftime('%s', datetime('now', 'localtime'): 유닉스 타임스탬프로 저장
strftime('%Y-%m-%d', datetime('now', 'localtime')): yyyy-MM-dd 포멧으로 저장
주의사항: 맨위의 처럼 DB insert를 할 경우, 데이터 조회할 때 dateformat 오류가 생길 수 있다. 해당 오류는 다음과 같이 해결할 수 있다. 1)유닉스 타임스탬프로 저장해서 해결 2) PRAGMA date_string_format=yyyy-MM-dd HH:mm:ss 설정
타입
문자형 => varchar(n)
숫자형 => integer
text => text
date => datetime
blob/bytea/varbinary => blob
auto_increment 적용 => auto_increment (DDL에 default를 붙이자)
MyBatis를 사용할 경우, sqlite의 JDBC ResultSet.getBlob()이 impelements가 되지 않았음 때문에, BlobTypeHandler를 확장하거나, TypeHandler를 impelements를 해서 ResultSet.getBytes로 변경해서 한다. 퍼시스턴스 프레임워크(SQL Mapper 또는 ORM)를 쓰지 않는경우 ResultSet에서 getBlob이 아닌 getBytes를 사용한다. 타입 핸들러 코드는 아래와 같다.
맨처음은 파일 DB를 잘 몰라, Database Lock이 걸릴까봐, SqlSession 객체 인스턴스를 한개만 만들었다.
그래서, 멀티 쓰레드라도, 1개의 객체 인스턴스를 쓰면 괜찮은 줄 알았다.
SqlSession 여러개 만드나, 1개 만드나 큰차이가 없는 것 같았다.
2) SQlite 멀티 프로세서 사용(2개의 데몬에서 1개의 DB 파일 사용) - 첫 번째 문제
2개의 서버에서 1개의 DB 파일을 사용할 때 슬슬 문제가 올라왔다.
Database is locked가 떴다...
처음에는 SQLiteException을 통해 catch 슬롯에서 다시 자기 자신을 호출하는 재귀 함수를 통해 개선했음, 근데 문제는 어플리케이션 코드의 수정과 재귀 함수에 스택오버 플로우의 문제가 발생함, 분명, Connection Timeout 같은것이 있을텐데 찾고 계속 찾다 보니 있었다.
busy_timeout=60000
데이터소스 설정할 떄 해당 설정을 하면 다중 프로세스라도 기다렸다가 진행한다.
3) SQlite 멀티 프로세서 사용(2개의 데몬에서 1개의 DB 파일 사용) - 두 번째 문제
busy_timeout 설정을 통해 개선 그러나 특정 API 처리할 때 똑같은 현상 발발
해당 API 처리는 2개의 데몬이 동시에 처리하는 작업임.
문제는 아래와 같다.
B 프로세스가 노란색 지점에서 BEGIN TRANSACTION을 시도하는 순간 A 프로세스의 EXCLUSIVE LOCK에 의해 LOCK 선점을 하지 못해, Database is locked가 생겼다.
4-1) SQlite 멀티 프로세서 사용(2개의 데몬에서 2개의 DB 파일 사용) - 문제 파악
결국 DB 파일을 분리했다ㅡㅡ.
그러나, A 프로세스는 B 프로세스의 DB 파일 참조가 필요하다.
즉, A 프로세스는 1) A DB파일 2) B DB 파일을 사용해야한다.
어플리케이션 코드 수정은 불가피하다.
그러나, 한번의 어플리케이션 수정과 설정 값 제어를 통해 충분히 유연하게 대처가능하다.
4-2) SQlite 멀티 프로세서 사용(2개의 데몬에서 2개의 DB 파일 사용) - Spring 프로젝트 적용
일단 간단하다.
Spring에 2개의 데이터 소스 빈 생성 및 등록을 한다.
그리고 DAO 객체는 해당 빈을 선택적으로 사용한다.
쉽게 얘기해서, 선택적인 빈 주입과 선택적인 빈의 사용으로 어플리케이션 코드의 수정을 최소화 하고 유연하게 대응할 수 있다.
AOP는 Aspect Oriented Programing의 약자로, 관점 지향 프로그래밍을 뜻한다. 공통 관심사(Aspect)의 분리(모듈화)를 통해 소스코드의 중복의 해소 및 어플리케이션의 책임 원칙을 좀덕 획일화한다. 하나의 예는 트랜잭션 잡업과 인증 절차를 예를들 수 있다. 즉, 기능 중에 주 기능에만 집중하고, 부가 기능은 Aspect로 다 분리 시킨다.
AOP 전체 설명도
용어는 아래 용어 항목에서 참고한다.
Cross-Cutting Concern은 위 개념에서 얘기한 부가 기능을 뜻하며, Primary Concern은 주기능을 뜻한다. Existing App과 같이 구성된 것을 NewApp으로 변화 시키고, 개발자는 주기능과 부가기능을 모듈화 개발할 수 있는 행위를 AOP이다.
대략적인 설명은 다음과 같다.
Cross-Cutting Concern, 즉 Aspect들을 Point-Cut을 통해 대상을 적용할 대상(Target)을 지정한 후, Weaving을 시켜 New App을 생성한다(이 문구는 아래의 글을 다 읽은 후, 다시 읽어보자).
AOP 종류
일단 Java 기준, AOP는 대표적으로 SpringAOP와 AspectJ 가 대표적이다.
Spring AOP
IoC/DI/프록시 패턴/자동 프록시 생성 기법/빈 오브젝트의 후처리 조작기법 등을 통해 AOP를 지원하며, 이와 같은 AOP는 스프링과 기본 JDK를 통해 구현 가능하다. 즉 간접적인 방식을 통해 AOP를 지원한다. 해당 Spring AOP는 프록시 방식의 AOP이다. 즉 프록시 디자인 패턴을 사용한다.
스프링 AOP의 프록시 빈 생성 절차는 다음과 같다.
1. 빈 설정파일을 통한 빈 오브젝트 생성
2. (생성된 빈을 이용하여) 빈 후처리기를 통해 컨테이너 초기화 시점에서 자동으로 프록시 생성(DefaultAdvisorAutoProxyCreator.class/BeanPostProcessor.class),
3. 포인트컷을 활용하여, 프록시를 적용할 대상을 지정한다.
4. 프록시 빈 오브젝트 생성 및 IoC 컨테이너에 등록
AspectJ
바이트코드 생성과 조작을 통해 AOP를 실현한다. Target 오브젝트를 뜯어 고쳐서 부가기능을 직접 넣어주는 직접적인 방식이다.
AOP 용어
Target: 부가기능을 부여할 대상을 뜻한다. 즉, 주기능을 얘기한다.
Aspect: AOP의 기본 모듈이다. 한개 또는 그 이상의 포인트컷과 어드바이스의 조합으로 만들어진다.
Advice: 실질적으로 부가 기능의 구현체이다. Advice의 유형은 다음과 같다. Before, After, Around, AfterReturning, AfterThrowing이 있다.
- Before: Target을 실행하기전에 부가 기능 실행
- After: Target실행 후 (해당 Target Exception 또는 정상리턴 여부 상관없이) 실행
- Around: Before + AfterReturning
- AfterReturning: Target 실행 후 성공적인 리턴할 때
- AfterThrowing: Target 실행하다, Exception 던질 때
Join-Point: Advice가 적용될 위치를 표시한다(ex:메소드 실행 단계).
Point-Cut: Advice를 적용할 Target를 선별하는 역할을 한다. Annotation 또는 메소드의 정규식을 통해 표현한다.
Cross-Cutting Concertn: 횡단 공통 관심사이다. 이것은 이미지를 통해 연상하는 것이 더 빠르다.
Cross-Cutting Concerns 설명
AOP Weaving
Aspect(부가기능)와 Application(핵심기능)의 Linking을 하는 과정이다. 해당 객체들을 묶어 새로운 객체를 생성한다.
Spring AOP
이미, 위에서 Weaving 절차를 설명하였다. Spring AOP의 Weaving 절차는 RunTime이다(프록시 빈 생성하는 것, IoC 컨테이너 초기화 작업할 때, 그러니까 WAS 가동할 때).
인터페이스 기준으로 하는 JDK Dynamic Proxy와 Class 기준으로 하는 CGLib Proxy가 존재한다. CGLib Proxy의 경우, AspectJ의 Weaving 처럼 바이트 코드를 조작한다. SpringAOP는 JDK Dynamic Proxy 패턴을 선호한다. 또한 Proxy 패턴 자체가 인터페이스를 끼고하는 페턴이다.
AspectJ
실제, AJC(Apsect Compiler)를 이용하여 Woven System 생성한다. 즉 부가기능과 핵심기능이 합쳐진 클래스 파일을 생성한다. AspectJ의 Weaving 타입은 아래와 같다.
Compile-Time Weaving: Aspect의 클래스와 Aspect를 사용하는 class들을 AJC를 통해 컴파일을 한다. JAR를 이용하여 Weaving을 하는 경우, Post-Compile Weaving(Binary Weaving)을 사용하며, 일반 소스 코드의 경우, 일반 Compile-Time Weaving을 사용한다.
Load-Time Weaving: 클래스로더를 통해 클래스가 JVM에 로딩되는 시점에 클래스의 바이트 코드를 조작한다. 즉, 객체를 메모리에 적재할 때 Weaving을 실현한다. 때문에, 다른 Weaving보다 속도 측면에서는 느리다.
AspectJ Weaving
Spring AOP vs AspectJ
일단 AspectJ 승리
1. 속도: 일단 AspectJ가 8~35배 빠르다. SpringAOP는 Aspect당 기타 AOP 관련 메소드를 추가적으로 호출하기 때문에 바이트코드를 직접적으로 건드린 AspectJ에 비하면 느릴 수 밖에 없다.
2. AspectJ는 강력한 기능을 제공한다.
SpringAOP vs AspectJ for JointPoint
3. 기능 차이점
SpringAOP vs AspectJ
1. Spring AOP는 퓨어 자바를 통해 구현 가능
2. AsepctJ는 Load-Time Weaving을 하지 않는한, AJC를 통한 컴파일 절차가 필요하다.