본 포스트는 오라클이나 티베로에서 시퀀스의 cache 및 order 옵션 조합에 따른 성능 개선에 대한 글입니다.

 

운영하고 있는 시스템이 여러가지 사유들로 거래량이 제법 큰 폭으로 증가했었습니다. 증가한 범위가 어느 정도는 용량 산정 시 계산한 여분이 허용하는 범위로 큰 문제는 없어야 하지만, 거래가 집중되는 시간에  간혹 DB의 CPU가 임계치를 조금씩 초과하기 시작했습니다.

 

Maxgauge로 분석해보니 특정 Sequence를 채번하는 nextval Query의 CPU Time이 전체 CPU Time의 많은 비율을 점유하고 있었습니다. 정상적이지 않은 시그널임에는 분명했지만 거래가 몰리는 시점에만 발생하는 현상이라 우선순위를 나중으로 좀 미루어 둔 상태였습니다.

하인리히의 법칙 (1: 29: 300의 법칙)
어떤 대형 사고가 발생하기 전에는 같은 원인으로 수십 차례의 경미한 사고와 수백 번의 징후가 반드시 나타난다는 것을 뜻하는 통계적 법칙

잠시 미루어 둔 사이 여러가지 사유로 다시 제법 거래량이 증가하면서 바로 문제가 수면 위로 노출되었습니다. 거래가 몰리는 시간대에 DB CPU가 100%를 치면서 nextval의 속도가 느려지기 시작했습니다.

 

해당 Sequence는 기간 시스템과 인터페이스하는 전문의 추적번호를 채번하기 위해 사용하는 Sequence로, nextval의 성능이 저하되면서 전체 시스템의 성능이 저하되고 응답 시간이 지연되는 큰 문제로 전개되었습니다.

 

임시 조치

결국 잠시 유량 제어 (일정 비율로만 거래를 통과시킴)를 통해 시스템의 과부하를 방지 했습니다.

 

원인 분석

메타 정보인 dba_sequences의 정보를 조회해보니 해당 sequence의 옵션은 cache + order 옵션으로 구성되어 있었습니다. cache 및 order 옵션에 대한 설명은 아래를 참고합니다.

Cache
설정되어 있는 수만큼 해당 sequence의 NEXT_VAL을 증가시킨 후 cache로 가져오고, nextval 명령을 수행할 때에는 미리 채번해 둔 cache의 값을 사용합니다.

 

Order
nextval 명령의 호출 순서에 따라 채번되는 sequence의 순서를 보장합니다. 단순히 Unique 한 값을 채번하는 것이 목적이 아닌 순서를 보장해야 하는 업무의 경우 Order 옵션을 사용해야 합니다.

 

이중화 환경에서 cache와 order 옵션에 따라 sequence는 아래와 같이 동작합니다. 운영 중인 시스템은 티베로를 사용하므로 TAC 환경에서의 동작이며 Oracle RAC도 동일하게 동작합니다.

Cache + Order
전체 node에서 하나의 sequence cache를 동기화하여 사용하므로 순서가 보장됩니다. 매번 순서 보장을 위해 wait lock을 잡고 순번을 채번합니다.

 

Cache + NoOrder
각자 node에서 따로 sequence cache를 사용하므로 각 node 개별적으로 순서가 보장되지만 전체 node에서는 순서를 보장하지 않았습니다. 각 node에서 미리 채번해 둔 cache를 모두 소진하여 다시 cache로 가져올 때만 wait lock을 잡으며, 다른 경우에는 잡지 않습니다.

 

NoCache
cache를 사용하지 않습니다. 매번 메타 정보를 변경하므로 항상 순서가 보장됩니다.

문제 해결

현재는 Cache + Order 방식으로 동작하므로 매번 wait lock을 잡고 채번하므로 거래가 집중될 경우 CPU 점유율이 증가하는 형태였습니다. 하지만 전문 추적번호라는 업무 성격은 Unique 한 성격만 요구할 뿐 반드시 순서가 보장되어야 하는 형태가 아니므로 Cache + NoOrder 옵션으로 변경하기로 하였습니다.

 

현재 운영 중인 시스템이고 거래가 많은 시스템이라 온라인 중에는 Sequence의 속성을 변경하기고 힘들어서 별도의 Sequence를 생성한 후 애플리케이션에서 사용할 sequence를 변경하는 형태로 작업을 진행했습니다.

 

대용량 시스템은 거래가 많이 발생하기 때문에 언제나 세세한 최적화가 필요해 보입니다.

300x250

본 포스트는 파티션 테이블을 사용할 때 글로벌 인덱스와 로컬 인덱스 구성에 따른 차이를 정리한 글입니다.

 

프로젝트 요건 중 고객별 개인화 마케팅을 위해 전체 고객의 구매나 성향 패턴에 대한 데이터를 Weekly 배치 작업으로 반영해 달라는 요건이 있었습니다. 배치 작업이 수행되는 중에도 온라인 거래에서는 개인화 마케팅을 위한 데이터를 참조한 결과를 화면에 표시해 달라는 요건이었습니다.

 

전체 고객의 데이터를 다루기 때문에 데이터가 수천만 건에 달하는 양이었습니다. 해당 부분에 대해 수행사에서 설계, 개발한 내용을 보니 아래와 같았습니다.

1. 테이블을 List Partition으로 구성한다.
2. 첫 번째 주는 0번째 Partition에 데이터를 적재한다.
3. 두 번째 주는 1번째 Partition에 데이터를 적재하고, 0번째 Partition은 truncate 한다.
4. 2-3번 과정을 매주 반복한다.

수행사에서 오픈 후 해당 내용을 적용하고 첫 번째 주 작업은 무사히 진행되었습니다. 하지만 두 번째 주 작업 때는 작업이 수행되는 시간에 해당 테이블을 사용하는 온라인 거래가 지연이 되는 문제가 발생하였고 결국 그 주 배치 작업은 중단 처리했습니다.

 

Global Partitioned Index

해당 테이블은 List Partition으로 구성한 테이블이었는데 Index를 Global Index로 생성하여 사용하고 있던 부분이 문제였습니다. Partition 테이블에서 Global Index를 사용하면 truncate가 발생할 때 Index 영역이 unusable 상태가 되기 때문에 반드시 Rebuild 가 필요하고, Rebuild를 하는 동안 해당 테이블을 사용하는 온라인 거래가 모두 지연된 것이었습니다.

 

Global Index vs Local Index

설명을 작성해볼까 하고 참고할만한 자료를 찾던 중 굳이 설명을 다시하는 것보다 아래 링크에 아주 설명이 잘되어 있어서 링크를 첨부하겠습니다.

http://dbcafe.co.kr/wiki/index.php/%ED%8C%8C%ED%8B%B0%EC%85%94%EB%8B%9D_%EC%9D%B8%EB%8D%B1%EC%8A%A4

 

파티셔닝 인덱스 - DB CAFE

1 PARTITIONED INDEX의 종류 1.1 partitioned index의 종류 1.1.1 LOCAL INDEX local index란 index를 생성한 table과 partitioned index가 equi-partition된 경우를 나타낸다. 즉, index와 table은 같은 컬럼에 의해 partition 되며, 하

dbcafe.co.kr

일반적으로 위와 같은 Rebuild 이슈나 성능 등을 고려하여, 기본적으로 Local Index 사용을 고려합니다. 어떤 사유로 Local Index를 사용할 수 없을 경우 Global Index 사용을 고려하는 순서로 진행합니다.

 

 

300x250

본 포스트에서는 리액트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다.

 

 

 

개발할 내용을 설계해보았으니 개발에 필요한 환경을 구성하고 프로젝트를 생성해 보도록 하겠습니다.

코드 편집기

코드 편집기는 손에 익은 편한 도구를 사용하면 됩니다. 여기서는 요즘 가장 많이 사용하고 있는 듯 한 VSCode를 사용하겠습니다.

VSCode는 아래의 링크에서 각자의 환경 (Windows, MAC)에 맞는 버전을 다운로드 받아서 설치하시면 됩니다.

https://code.visualstudio.com/

 

Visual Studio Code - Code Editing. Redefined

Visual Studio Code is a code editor redefined and optimized for building and debugging modern web and cloud applications.  Visual Studio Code is free and available on your favorite platform - Linux, macOS, and Windows.

code.visualstudio.com

Node.js

JavaScript 애플리케이션의 Package 의존성을 관리하기 위해 npm이나 yarn을 사용합니다.  npm이나 yarn은 Node.js 기반으로 동작하므로 Node.js 인스톨러를 다운로드하여 설치합니다. 아래의 링크에서 각자의 환경 (Windows, MAC)에 맞는 버전을 다운로드 받아서 설치하시면 됩니다.

https://nodejs.org/ko/download/

 

다운로드 | Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

npm과 yarn의 차이, 장단점에 대해서는 TOSS의 기술 블로그에서 재미나게 설명하고 있으니 참고하시면 될 듯 합니다. 아래 링크 첨부합니다.

https://toss.tech/article/node-modules-and-yarn-berry

 

node_modules로부터 우리를 구원해 줄 Yarn Berry

토스 프론트엔드 레포지토리 대부분에서 사용하고 있는 패키지 매니저 Yarn Berry. 채택하게 된 배경과 사용하면서 좋았던 점을 공유합니다.

toss.tech

Yarn

Node.js까지 설치를 했다면 터미널에서 아래 명령어를 실행하여 Yarn을 설치합니다.

npm install -g yarn

프로젝트 초기화

Yarn까지 설치했다면 프로젝트 루트로 사용할 workspace를 하나 생성합니다. 각자가 편한 경로에 디렉토리 하나를 생성하시면 됩니다. NAVER API를 통해 간단한 검색을 하는 프로그램이므로 searchNaverApi라고 명명하겠습니다.

 

workspace 하위에 아래 디렉토리를 생성하고 이동합니다.

mkdir searchNaverApi
cd searchNaverApi

해당 디렉토리에 프로젝트 설정 파일을 생성합니다. 프로젝트 설정 파일은 package.json이며 yarn init 명령을 수행해서 생성하시면 됩니다. yarn init 명령을 입력하면 아래 정보를 입력하라고 나오는데, 성실하게 입력하셔도 되고, 나중에 파일 생성 후 변경도 가능하니 그냥 모두 Enter로 넘어가셔도 상관 없습니다.

D:\workspace\searchNaverApi> yarn init

name (searchNaverApi) : 
version (1.0.0) :
description :
entry point (index.js) : 
repository url : 
author : 
license (MIT) :
private :

일단 모두 입력 or skip 하게 되면 프로젝트 하위 경로에 package.json 파일이 하나 생성됩니다.

기본 설정으로 만들고 싶을 경우 yarn init -y 명령으로 생성하면 별도 질문 없이 package.json이 기본 설정으로 생성됩니다.

 

현재까지 진행한 프로젝트 파일 구조는 아래와 같습니다.

searchNaverApi
 └── package.json

 

모듈 설치

현재 프로젝트 개발에 필요한 모듈들을 설치합니다. 각 모듈의 버전 별로 호환성 문제나 코드가 상이할 수 있으므로 설치할 버전을 지정하여 설치하겠습니다.

모듈을 설치할 때는 yarn add 명령으로 설치합니다. yarn add 명령의 형식은 아래와 같습니다.

yarn add [packages ...][flags]

yarn help 명령을 입력하면 flags에 대한 상세한 내용을 조회할 수 있지만 이번에는 의존성에 대한 부분만 간략하게 보고 넘어가겠습니다. yarn help add 명령으로 조회한 설명은 아래와 같습니다.

D:\workspace\searchNaverApi> yarn help add
...
-D, --dev         save package to your `devDependencies`
-P, --peer        save package to your `peerDependencies`
-O, --optional    save package to your `optionalDependencies`
...

위 내용 중 --dev (개발 의존성)와 아무것도 입력하지 않았을 경우인 일반 의존성으로만 구분하여 설치를 진행하겠습니다. 간단하게 일반 의존성은 런타임에서 사용하는 모듈을 설치할 때 사용하고, 개발 의존성 (--dev)은 빌드 타임 (babel이나 webpack 등 개발 workflow에서 사용)에서만 사용하는 모듈을 설치할 때 사용합니다.

 

이번 프로젝트에 필요한 모듈을 설치해 보겠습니다.

D:\workspace\searchNaverApi> yarn add react@17.0.2
D:\workspace\searchNaverApi> yarn add react-dom@17.0.2
D:\workspace\searchNaverApi> yarn add --dev babel-core@6.26.3
D:\workspace\searchNaverApi> yarn add --dev babel-loader@8.2.3
D:\workspace\searchNaverApi> yarn add --dev babel-preset-react-app@10.0.1
D:\workspace\searchNaverApi> yarn add --dev webpack@5.66.0
D:\workspace\searchNaverApi> yarn add --dev webpack-dev-server@4.7.3
D:\workspace\searchNaverApi> yarn add --dev webpack-cli@4.10.0

기본적인 React package 및 compile, bundling을 위한 도구를 설치하는 과정입니다. react 및 react-dom을 제외하고는 런타임에 필요한 모듈이 아니므로 개발 의존성으로 설치합니다. 개발 시에 필요한 모듈은 추가적으로 날짜 formatting을 위해 moment 정도만 설치합니다.

(나중에 추가적으로 필요한 모듈이 있으면 그때그때 설치하면 됩니다.)

yarn add moment@2.29.1

설치를 한 모듈은 package.json 파일에서 의존성 관리를 합니다. 설치를 완료한 후 package.json 파일을 확인해 보면 아래와 같이 개발 의존성으로 설치한 모듈들은 devDependencies 하위에, 일반 의존성으로 설치한 모듈들은 dependencies 하위에 관리되고 있음을 알 수 있습니다.

{
  "name": "searchNaverApi",
  "version": "1.0.0",
  "main": "index.js",
  "license": "MIT",
  "dependencies": {
    "moment": "2.29.1",
    "react": "17.0.2",
    "react-dom": "17.0.2",
  },
  "devDependencies": {
    "babel-core": "6.26.3",
    "babel-loader": "8.2.3",
    "babel-preset-react-app": "10.0.1",
    "webpack": "5.66.0",
    "webpack-cli": "4.10.0",
    "webpack-dev-server": "4.7.3"
  }
}

여기까지 작업을 완료했다면 현재 프로젝트 구조는 아래와 같습니다.

searchNaverApi
 ├── node_modules
 │    ├── ...
 ├── package.json
 └── yarn.lock

다음 편에서는 compile 및 bundling 대상인 source 경로와 정적인 resources를 관리하는 경로를 분할하여 프로젝트 구조를 잡아 보도록 하겠습니다.

300x250

본 포스트는 WAS에서 커넥션 풀 부족 현상을 개선하는 방법 및 스프링(Spring Framework)에서 트랜잭션을 설정하는 방법에 대한 글입니다.

 

 

'22년 초부터는 마이데이터 사업자(토스나 뱅크샐러드 등)의 스크래핑이 금지되어 각 금융사의 Open API 서비스로 연결하도록 전환한 상태이지만 '21년까지는 스크래핑에 대한 규정이나 법률이 없었던 관계로 자산 관리 앱들은 각 금융사의 서버를 직접 스크래핑하는 구조였습니다.

 

특히나 해당 자산 관리 서비스들이 나름대로 성공했기 때문에 제법 많은 DAU를 보유하고 있었고, 그래서 해당 서비스들이 무언가 이벤트를 한다던가 PUSH를 발송한다던가 하면 그 트래픽은 고스란히 원천 데이터를 서비스하는 은행, 카드 등의 금융사들로 몰리게 됩니다.

 

사실 각 금융사에서는 시스템을 구축하기 위한 용량 산정을 할 때 이 스크래핑에 대한 부분을 잡지 않고 산정한 금융사가 대부분이므로  예상 TPS보다 150%, 200%의 거래가 유입되면 재미있는(?) 순간들이 많이 발생합니다.

 

그 중 하나였던 WAS Connection Pool 부족 현상에 대한 이야기입니다.

 

WAS Connection Pool 동작

WAS Connection Pool은 인스턴스를 시작할 때 설정한 개수만큼의 Connection Pool을 생성하여 가지고 있습니다. DB를 사용하는 거래가 유입되면 이 때 생성해 둔 Connection을 빌려서 사용하고 (Active), commit이나 rollback을 수행하면 해당 Connection을 Pool에 반납합니다.

 

지금 IDLE이 없고 전체 Connection이 모두 Active라는 이야기는 DB를 사용하는 거래가 유입되어도 Pool에 있는 모든 Connection이 사용 중이니 다른 거래가 완료되어야 현재 대기 중인 거래를 수행할 수 있다는 의미가 됩니다.

 

상태 확인

이런 현상들이 처음 발생했을 때는 특정 Query가 지연되거나 DB 사용률이 높아서 모든 Connection이 Active 상태인가? 라고 생각해서 DB 모니터링 툴을 확인했지만 전체적으로 CPU 도 2-30% 수준에 Running Session이 쌓이는 현상도 없었습니다.

 

일단 조치가 필요했으므로 유입되는 거래랑 특정 비율로만 통과시켜서 지연 상태를 해소시킨 후 Framework의 Transaction 설정을 확인했는데 아래와 같이 설정되어 있었습니다.

<aop:config>
	<aop:pointcut id="requiredTx"
    	expression="execution(* com..impl.*ServiceImpl.*(..))" />
    <aop:advisor advice-ref="txAdvice"
    	pointcut-ref="requiredTx" />
</aop:config>

위 설정은 전체 ServiceImpl 하위의 모든 method를 Transaction 처리한다는 설정입니다. 보통 SI를 하게 되면 특별히 요건으로 요청하지 않는 이상 기본적으로 저런 형태로 초기 설정을 많이들 하게 됩니다.

 

문제점

사실 대고객의 접점에 있는 채널 시스템(홈페이지나 앱 등)은 원천 데이터를 가지고 있지 않습니다. 기본적인 로그인에 필요한 정보나 부가 정보 등은 가지고 있지만 대부분의 데이터는 계정계나 승인 시스템에서 보유하고, 채널 시스템은 해당 시스템들과 연계하여 고객에게 정보를 제공합니다.

 

무슨 이야기인고 하니, 생각보다 자체적으로 DB Transaction 처리를 할 필요가 있는 업무가 많지 않다는 의미입니다. 하지만 타 시스템과 인터페이스를 할 때 DB를 늘 사용하게 되어 해당 거래들이 언제나 하나의 Transaction으로 묶이게 됩니다.

1. 전문 추적번호 채번 및 Logging을 위해 DB를 사용합니다.
2. ServiceImpl class에서 수행합니다.

위와 같은 이유로 타 시스템 인터페이스는 모두 Transaction으로 묶이므로 거래가 급증하여 내부에 있는 타 시스템들이 거래가 지연되어 응답이 늦게 되면 Pool에 있는 모든 Connection들이 Active 상태로 유지되어 전체 채널 거래의 지연으로 이어집니다.

 

해결 방법

해결은 단순히 '분리' 밖에 없습니다. Transaction으로 묶어야 하는 서비스와 Transaction으로 묶지 않아도 되는 서비스를 분리하여 설정해야 합니다. 예를 들어 채널 DB에서 관리하는 로그인이나 회원가입 등, INSERT 및 UPDATE가 있는 서비스는 All or Nothing이 보장되어야 하므로 Transaction 처리가 필요합니다.

 

하지만 타 시스템에서 값을 조회한다거나, 타 시스템에 데이터를 처리 요청하는 서비스의 경우에는 타 시스템에서 Transaction 처리한 후 결과를 응답하므로 채널 시스템에서는 Transaction 처리가 필요하지 않습니다. 이런 부분을 초기 설계에 반영해서 구축했다면 이런 지연 현상들은 많이 방지할 수 있었을테지만 보통 이런 문제는 '운영' 단계에 접어들어야 발생하곤 합니다.

 

운영 중인 시스템은 영향도 검토도 정말 만만치 않은 작업이고 전체 시스템에 영향을 주는 이런 설정을 변경하기는 정말 손 떨리는 작업이 됩니다.

 

분리 방향

분리 방향은 크게 두 가지로 나눌 수 있습니다.

1. 전체 ServiceImpl transaction을 해제 후
   transaction 처리가 필요한 ServiceImpl만 TxServiceImpl로 변경
2. 전체 ServiceImpl transaction 설정을 유지한 상태에서
   transaction이 불필요한 ServiceImpl만 aop 설정에서 제외 처리

먼저 1번 방향을 목표로 하고, 분석을 위해 프로젝트를 최신 상태로 Sync 해보니 현재 사용 중인 ServiceImpl이 500본 정도가 되었습니다. (ㅎㄷㄷ)  ServiceImpl 하나 당 method도 여러 개가 있으니 분석이 가능할까 싶었지만 팀원들과 공유하여 분석해 보기로 하였습니다.

 

각자 담당하고 있는 업무 영역이나 담당하지 않는 부분까지도 다들 바쁜 시간을 쪼개어서 분석에 참여하였기에 생각보다 잘 정리가 되는 듯 했지만 분석된 내용을 샘플링해서 검토해 보니, 잘못된 분석들이 꽤나 있었습니다. 예를 들면 이벤트 응모 같은 서비스는 채널의 DB에서 자체적으로 응모 데이터를 관리하므로 Transaction 처리가 필요하지만, 타 시스템과의 인터페이스도 있어 Transaction 불필요 업무로 분류되어 있었습니다.

 

시스템의 안정성이 위협을 받고 있는 상황에서 전체를 시니어들만으로 재분석 할 수는 없었기에 2번으로 방향을 급선회 했습니다.

 

Default는 Transaction 처리. 일부만 제외.

일단 일부만 제외하는 것으로 방향을 잡았으니 Impact가 있는 대상으로 선정이 필요했습니다. 하여 아래 기준을 적용하여 제외할 대상을 선정하고 진행했습니다.

1. 거래량 순위 TOP 40 안에 드는 거래 중
2. 타 시스템 인터페이스만 사용하고 DB 거래가 불필요한 서비스

Spring 설정은 아래와 같은 식으로 제외할 대상을 선언했습니다.

<aop:pointcut id="requiredTx"
	expression="execution(* com..impl.*ServiceImpl.*(..))
           and !execution(* com.a.AServiceImpl.*(..))
           and !execution(* com.b.BServiceImpl.*(..))" />

 전체 ServiceImpl은 기본적으로 Transaction 처리 후 !execution으로 원하는 ServiceImpl만 제외하는 설정입니다.

 

결과

효과는 정말 드라마틱 했습니다. 전체 3,000개 정도의 서버 인터페이스 중 TOP 40이 차지하는 비중은 80%가 넘었기에, 대부분의 거래에서 Connection을 점유하는 시간 자체를 줄였기 때문에 거래가 집중되어도 Active Connection이 증가하지 않았습니다.

 

집중이 아닌 시간 대에도 평소 Active Connection이 인스턴스 당 20-30 정도였지만 설정 변경 후에는 인스턴스 당 5-7 정도로 아주 안정적인 시스템으로 변신해 버렸습니다.

300x250

'Trouble shooting' 카테고리의 다른 글

Sequence order 옵션에 따른 성능  (0) 2022.06.25
Partition truncate 시 온라인 거래 지연 이슈  (0) 2022.06.24
MAX + 1 채번 이슈  (0) 2022.06.22
중복 로그인 체크 오류  (0) 2021.10.07
JS UI Rendering, Data bind  (0) 2021.10.06

본 포스트에서는 리액트를 이용하여 네이버 API로 데이터를 검색해서 목록 화면을 구성해 보겠습니다.

금융 IT에 종사하다 보니 Front-end 개발을 주로 하지만 Vue나 React 같은 Library 보다는 주로 jQuery 기반으로 개발을 하고 있습니다. 하지만 개발자는 끊임 없이 공부해야 하기에 React 기초를 공부하는 과정을 한 번 정리해 보겠습니다.

 

일단 간단하게 프로젝트를 생성하고 목록 정도만 개발해 볼 수 있도록 NAVER 뉴스 API, 도서 API에서 데이터를 수신해 화면에 목록 형태로 표시하도록 개발해 보겠습니다.

 

목록 구성

일단 NAVER API를 통해 받아온 뉴스 데이터와 도서 데이터는 아래와 같이 구성해 보겠습니다.

(데이터를 어느 정도 길이로 응답할지는 정확히 모르기 때문에 여러 줄로 표시해야 하는 부분은 여러 줄로 표시하도록 구성합니다.)

뉴스, 도서 목록 구성

탭 버튼 구성

목록을 두 가지 형태로 구성하므로 뉴스 데이터를 조회할 지, 도서 데이터를 조회할 지 사용자가 탭으로 선택할 수 있어야 합니다. 화면 하단에 간단하게 탭을 표시하여 선택할 수 있도록 구성합니다.

탭 버튼 구성

위 그림과 같이 간단하게 두 개의 탭을 표시하고 선택한 탭은 반전하여 선택되었음을 표시해 줍니다. 

 

검색 기능 구성

NAVER의 뉴스 및 도서 API는 "검색"을 위한 API 입니다. 사용자가 입력한 Keyword를 기준으로 데이터를 조회하므로 검색어를 입력하고 API를 호출하기 위한 컴포넌트가 필요합니다.

아래 그림과 같이 대강 구성해 보도록 하겠습니다.

검색 기능 구성

그림을 그리다 보니 그렇게 간단하지만은 않을 것 같습니다. 다음 포스트에서는 개발을 하기 위한 프로젝트 환경을 구성해 보도록 하겠습니다.

300x250

본 포스트는 크롬 브라우저의 개발자 도구를 사용하여 네트워크 기록을 분석할 수 있는 HAR 파일 사용법에 대한 설명입니다.

 

 

개발을 하다가 특정 환경이나 사용자에게 오류가 발생하는 경우, 특히나 Back-end가 아닌 Front-end에서 오류가 발생하는 경우 분석을 위해 해당 시점의 네트워크 요청과 결과를 알고 싶을 때가 있습니다.

 

물론 서버에 전체 Request와 Response에 대한 로그를 남긴다면 서버에서 로그를 조회할 수도 있겠지만 너무 대용량이라 전체 로그는 남기기가 쉽지 않습니다. 만약 이럴 경우에 개발자나 테스터에게 동일한 오류가 발생했을 경우 HAR 파일로 저장한 후 네트워크 데이터 분석에 사용할 수 있습니다.

(B2E 같은 내부 사용자를 위한 시스템이면 사용자에게도 로그를 부탁할 수 있겠지만, 대고객 시스템은 이런 경우는 어림 없습니다.)

 

네트워크 기록을 HAR 파일로 저장하기 위해서는 아래와 같이 진행합니다.

 

1. [F12]를 눌러 개발자 도구를 열고 [Network] 탭으로 이동합니다.

2. 도구 중 아래 버튼을 선택합니다.

    또는 바로가기 메뉴에서 [Save all as HAR with content] 를 선택해도 됩니다.

3. 원하는 위치와 파일명을 지정하여 저장합니다.

 

저장한 파일을 에디터에서 열어보면 네트워크 기록이 JSON 형태로 저장되어 있습니다. 해당 내용을 보고 파악하기는 힘들고, 전달받은 HAR 파일을 Google Chrome의 [Network] 탭으로 드래그합니다.

생성하는 시점과 동일한 네트워크 기록이 표시되어 분석할 수 있습니다.

 

HAR 파일은 Chrome이 아닌 다른 브라우저에서도 저장할 수 있습니다.

 

300x250

'Chrome' 카테고리의 다른 글

[Google Chrome] User-Agent 조작하기  (0) 2022.06.22
[Google Chrome] 보안 옵션 해제  (0) 2015.08.31
[Google Chrome] Nework 탭 살펴보기  (0) 2015.08.28
[Google Chrome] Device Emulation  (0) 2015.08.27

본 포스트는 HTTP Response Header 의 값 중 서버 시간을 구해서 사용하는 방법에 대한 설명입니다.

 

 

클라이언트에서 서버의 시간을 참조할 일이 있을 경우, 서버 API에서 응답값에 현재 시간을 보통 포함시켜서 전달합니다. 하지만 정말로 딱! 시간만 필요한 경우는 아래와 같이 빈 페이지나 빈 API의 Response Header의 Date를 참조하면 손쉽게 서버 시간을 구할 수 있습니다.

 

인터페이스를 하면 아래와 같이 Response Header가 응답에 포함되어 있습니다.

아래와 같은 식으로 간단하게 Response Header에 있는 Date 값을 구할 수 있습니다.

fetch(url, {
	method: 'GET'
}).then((resp) => {
	console.log(resp.headers.get('Date'));
});

프로젝트에서 jQuery를 사용한다면 아래 코드를 참고하시면 됩니다.

var options = {};
options.url = window.location.href.toString();
options.method = 'HEAD';
var xhr = $.ajax(options);
xhr.always(function() {
	console.log(xhr.getResponseHeader('date'));
});

 

300x250

본 포스트는 JavaScript의 getTimeZoneOffset() 메소드를 사용하여 UTC 시간을 구한 뒤 서버와 클라이언트의 시간을 동일하게 계산할 수 있는 방법에 대한 설명입니다.

 

요즘 개발하는 서비스들은 대부분 국내에서 뿐만 아니라 해외에서도 사용 가능한 형태로 개발합니다. 해외에서 서비스를 사용하는 경우 뿐 아니라 사용자가 각자 사용하는 Device의 국가, 시간 설정을 대한민국이 아닌 다른 곳으로 해두었을 경우 JavaScript의 new Date() 는 해당 국가 설정을 참조하여 시간을 return 합니다.

 

현재 제가 사용하는 Device는 대한민국 표준시를 사용하도록 설정되어 있습니다.

 이 상태에서 아래 코드의 결과는 다음과 같습니다.

new Date();
//Thu Jun 23 2022 07:36:14 GMT+0900 (한국 표준시)

이번에는 Device의 시간을 미국으로 변경해 보겠습니다.

 

 

그러면 동일한 코드는 아래와 같은 결과를 보여줍니다.

new Date();
//Wed Jun 22 2022 17:35:43 GMT-0500 (북미 중부 하계 표준시)

서비스의 성격에 따라 판단할 문제이지만 만약 서버에서 시간을 체크해야 하는데 화면의 Request에서 시간을 받아서 검증이나 사용을 하게될 경우 서버는 대한민국 표준시를 사용하지만, 화면은 북미 중부 표준시를 사용하게 되므로 대한민국 표준시를 사용하지 않는 사용자는 적절하지 않은 오류를 만나게 될 수도 있습니다.

 

경우에 따라 다르지만 클라이언트 JavaScript 코드에서도 대한민국 표준시로 통일하는 방법이 있습니다.

 

const now = new Date();
const utcDate = now.getTime() + (now.getTimezoneOffset() * 60 * 1000);
const korDate = new Date(utcDate + 9 * 60 * 60 * 1000);

우리나라는 그리니치 표준시 + 9H를 사용하므로, UTC 기준시각에 9시간을 더해주는 로직입니다.

 

위와 같이 코드를 작성하면 클라이언트에서 북미 중부 표준시를 사용하더라도 대한민국 표준시를 화면에서 사용할 수 있습니다. 물론 위와 같은 코드보다는 서버의 시간을 Response Header에서 참조하여 변환하는게 좀 더 일반적인 사용 방법입니다. 

 

300x250

몇 년 전에도 모 프로젝트에서 지원 요청이 와서 한 번 Code Review 하여 해결을 했었고, 얼마 전에도 운영 중인 시스템에서도 동일한 Case가 있었던 것을 보면 아주 기초적인 사항임에도 생각보다 잘 지켜지지 않는 부분일 수 있겠다는 생각이 듭니다.

 

jQuery 사용

대기업에서 SI 프로젝트에 몸 담고 있다보면, 혹은 대기업 SI 프로젝트에서 개발한 시스템을 인수하는 운영 담당자의 역할로 있다보면 서비스 기업들에서 많이들 사용하는 Vue, React 등의 Framework, Library는 구경하기 힘들고 2022년인 현재도 많은 프로젝트들이 jQuery 기반으로 진행되는 것을 알 수 있습니다.

 

대규모 SI 프로젝트의 경우 개발자가 수십~수백 명에 이르는데 Vue나 React를 사용하는 개발자들을 충분히 소싱하기 힘들고, 상용 UI 솔루션들과 연계해야 하는 상황에서 아직 jQuery 기반으로 제공되는 솔루션들이 많아 어쩔 수 없는 선택이 되곤 합니다.

(사실 Zepto 등의 경량 Library도 고려 대상일 수 있겠으나, 이미 검증된 수많은 Plug-in이 있다는 점은 SI 프로젝트에 너무 매력적인 요소로 작용하는 듯 합니다.)

 

jQuery 기반의 프로젝트에서 업무 화면이 복잡해져서 하나의 프로그램에서 수행하는 XHR과 calback이 많아지면 아래와 같은 문제들이 종종 발생하게 됩니다.

 

화면 특정 영역에 데이터가 나오다 말다 해요

A와 B 두 개의 function이 있습니다. A function은 DOM을 초기화하는 function이고, B function은 서버에서 데이터를 받아와 A function이 구성한 DOM에 Data를 Bind하는 역할을 하는 function 입니다.

function A() {
	// 특정 조건에 따라 UI에 특정 영역을 표시하거나 제거합니다.
}

function B() {
	// $.ajax 등의 함수로 서버와 통신하여 A 영역에서 생성한 DOM에 Data를 Bind 합니다.
}

생각보다 많은 개발자들이 아래와 같이 작성하고 (물론 위와 같이 너무 단순한 구조는 아닙니다만), 사실 또 대부분의 경우에 연산은 또 순식간에 일어나므로 올바르게 동작합니다.

A();
B();

하지만 복잡한 Front-end 프로그램을 개발하다 보면 아주 많은 경우의 수가 발생합니다. 사용자의 휴대폰이 구형이라 브라우저의 동작 자체가 느린 환경도 있고, 데이터 음영 구간도 있고, 접속 상태가 좋지 못한 Wi-Fi를 사용하여 Network가 느린 경우도 있어 반드시 작성한 순서대로 실행을 보장하지는 않습니다.

 

Callback function 사용

순서가 반드시 보장되어야 하는 코드들은 정확히 실행해야 하는 타이밍을 callback function을 사용하여 구현합니다. 

 

위와 같은 경우에도 A function에서 생성해야 할 DOM이 생성되지 않은 상태에서 B function의 Data bind가 수행이 되는 경우가 발생하면 해당 영역에는 올바르게 Data를 표시하지 못하는 경우가 발생합니다.

 

순서를 보장하기 위해서는 아래와 같이 작성합니다.

function A(callback) {
	//특정 조건에 따라 UI에 특정 영역을 표시하거나 제거합니다.
    if (callback && typeof callback === 'function') callback();
}

function B() {
	//$.ajax 등의 함수로 서버와 통신하여 A 영역에서 생성한 DOM에 Binding 합니다.
}

A(B);

위와 같은 형태로 작성하면 DOM을 구성하는 작업이 완료된 후 B function을 실행하므로 화면을 올바르게 표시할 수 있습니다. 

 

사실 수백~수천 라인에 달하는 프로그램에서 모든 순서가 정확하게 보장되기가 쉽지가 않습니다. 이 과정에서 callback 지옥 역시 발생합니다. ES6+에서는 async/await, Promise를 사용하여 이런 callback 지옥 구조를 많이 개선했지만 아직 많은 대고객 서비스에서는 I.E를 지원해야 하기 때문에 여전히 callback 지옥을 헤어 나오기는 힘듭니다.

300x250

본 포스트는 크롬의 개발자 도구를 사용하여 useragent 값을 변경해 가면서 테스트 할 수 있는 방법에 대한 설명입니다.

 

 

대고객 서비스를 운영하다 보면 타 업체의 앱과 제휴하여 서비스를 제공하는 경우가 종종 있습니다.

 

이럴 경우 제휴하는 업체에 따라 서비스를 분기해야 할 경우가 종종 생기는데, 이럴 경우 보통 각 업체의 WebView에서 제공하는 User-Agent 값에 따라 분기 처리합니다.

 

물론 최종 확인은 각 업체에서 테스트 앱을 받아 진행하지만, 기본적인 디자인이나 동작 확인은 각 업체에서 테스트 앱을 받아가면서 확인하기에는 시간이 부족한 경우가 많이 있습니다. 이때 Chrome 개발자 도구에서 제공하는 [Network conditions] 탭을 사용하여 간단하게 미리 확인해 볼 수 있습니다.

 

사용법은 아래와 같습니다.

 

먼저 Console 왼쪽에 있는 메뉴 버튼을 누른 후 [Network Conditions] 를 선택하여 탭을 활성화 합니다.

처음 활성화하면 아래와 같이 화면 중간 정도에 있는 User agent 부분이 [Use Browser default] 체크가 활성화되어 있습니다. 체크가 되어 있으면 별도로 User-agent를 조작하지 않고 현재 브라우저의 User-agent를 사용한다는 의미입니다. 우리는 User-agent를 변경할 것이니 체크를 해제하겠습니다.

체크를 해제하면 아래와 같이 Custom User-agent를 입력할 수 있도록 입력상자가 활성화 됩니다.

이때 아래와 같이 확인하고자 하는 WebView에서 제공하기로 약속한 User-agent 문자열을 입력 후 확인할 수 있습니다.

 

300x250

+ Recent posts