반응형

1. 페이징스페이스(Pagingspace)

페이징스페이스는 메인 메모리가 부족할 때 사용하는 디스크 공간으로, 스왑 스페이스(Swapspace)라고도 한다. 

1GB 메모리가 탑재된 시스템에서 이미 900MB 메모리를 운영체제와 다른 프로세스가 사용하고 있는데, 300MB 메모리를 사용하는 프로세스를 새로 실행한다고 가정해 보자. 여유 메모리가 100MB밖에 없어 신규 프로세스를 기동할 수 없는 상황으로 보인다. 그러나 운영체제는 현재 사용 중인 900MB 메모리 중 200MB 정도를 디스크에 존재하는 페이징스페이스라는 곳으로 옮김으로써 메인 메모리에서 300MB를 확보해 신규 프로세스를 수행한다.

즉, 페이징스페이스는 운영체제가 메인 메모리 크기 이상의 메모리를 사용할 수 있게 해주는 역할을 한다. 그러나 시스템 성능 관점에서 보면 페이징스페이스는 디스크에 프로세스 메모리를 쓰고, 읽는 스와핑 작업으로 인해 메모리 접근만으로 처리될 때메 비해 큰 성능 저하가 발생한다. 따라서 스와핑이 발생하지 않도록 메모리 여유율을 유지하는 것이 성능에 중요하다.

2. 페이징(Paging)

가상 메모리 체계에서는 운영체제가 인식하는 일정한 크기(4KB, 64KB)의 데이터 기록 단위를 페이지라고 한다. 메인 메모리로부터 한 페이지의 데이터를 보조 기억장치(디스크)로 복사하거나 보조 기억장치로부터 메인 메모리로 로드하는 것을 페이징이라고 한다.

3. 스와핑(Swapping)

메모리에서 페이지 혹은 세그먼트 단위 데이터를 교환하는 것으로 컴퓨터가 메모리보다 큰 프로그램을 실행하거나 메모리보다 큰 데이터 파일을 다룰 수 있게 한다. 가상 메모리 체계에서는 페이징 기법을 이용해 스와핑을 수행한다. 스와핑은 프로세스가 사용하는 메모리 일부를 페이징스페이스로 옮기거나 페이징스페이스로부터 메모리로 로드하는 것을 가리킨다.

4. 페이지 부재(Page Fault)

페이징 방식의 가상메모리에서 CPU가 사용하려는 페이지가 메인 메모리에 없는 경우다. 사용하려는 가상 메모리 주소에 해당 페이지를 매핑 테이블에서 주소 변환할 때 해당 페이지가 메인 메모리에 없다고 표시돼 있으면 페이지 부재가 발생한다. 이 경우 해당 페이지를 디스크에 있는 파일 또는 페이징스페이스에서 가져와야 하고 메인 메모리가 부족한 경우에는 다른 페이지와 교체해야 한다.

5. 페이지 인(Page In)

가상 메모리에서 페이지 부재가 일어났을 때 디스크에 있는 프로그램이나 데이터를 메모리에 로드하는 것

6. 페이지 아웃(Page out)

가상 메모리에서 페이지 부재가 발생했을 때 메모리가 부족하면 페이지 스틸러(Page stealer)는 기존 메모리를 디스크로 기록하는 페이지 아웃이라는 작업을 수행한다. 기록 대상 메모리가 프로세스의 작업 세그먼트(Work segment)영역에 있는 메모리면 페이징스페이스로 기록하고, 파일에서 읽어 들인 내용(Permanent segment)인 경우 페이지의 내용이 변경됐으면 디스크의 해당 파일에 기록하고 그렇지 않으면 소멸시킨다.

7. 페이지 스캔률(Page Scan Rate)

운영체제는 일정량의 여유 페이지(Free page)를 확보해서 필요할 경우 즉시 제공하기 위해 페이지 스캔 (Page scan)이라는 검색을 통해 해제함으로써 여분으로 확보할 페이지를 찾는다. 여유 페이지 목록(Free page list) 임계치보다 여유 페이지 수가 줄어들면 메모리 관리자는 기존의 페이지 인된 것 중에서 LRU 알고리즘 검색을 통해 페이지 아웃이 가능한 것을 찾아 페이지 아웃시킨 후 여유 페이지로 변경한다. 여기서 페이지 아웃이 가능한 페이지를 탐색하는 빈도를 의미하는 페이지 스캔률이 나오는데 빈도가 높다는 것은 해제할 페이지가 넉넉하지 못하기 때문에 자주 메모리를 탐색해야 한다는 의미로 볼 수 있다. 

 

해당 내용은 '실무로 배우는 시스템 성능 최적화' 책의 내용중 일부를 발췌한 것입니다.

 

반응형
반응형

DB에 넣으려는 값의 타입과 자바 모델 객체의 타입이 다를 때 발생

아래 에러로그를 보면 video_id 컬럼에서 에러가 나는데

DB에는 long(bigint) type인데 application에서는 video_id를 string type으로 처리하고 있었고

string type의 video_id를 DB에 insert 하려다 보니 에러 발생

java.sql.SQLException: Data truncated for column 'video_id' at row 1 at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.17.jar:8.0.17] at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.17.jar:8.0.17] at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122) ~[mysql-connector-java-8.0.17.jar:8.0.17] at com.mysql.cj.jdbc.ClientPreparedStatement.executeInternal(ClientPreparedStatement.java:953) ~[mysql-connector-java-8.0.17.jar:8.0.17] at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1092) ~[mysql-connector-java-8.0.17.jar:8.0.17] at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdateInternal(ClientPreparedStatement.java:1040) ~[mysql-connector-java-8.0.17.jar:8.0.17] at com.mysql.cj.jdbc.ClientPreparedStatement.executeLargeUpdate(ClientPreparedStatement.java:1340) ~[mysql-connector-java-8.0.17.jar:8.0.17] at com.mysql.cj.jdbc.ClientPreparedStatement.executeUpdate(ClientPreparedStatement.java:1025) ~[mysql-connector-java-8.0.17.jar:8.0.17] at com.zaxxer.hikari.pool.ProxyPreparedStatement.executeUpdate(ProxyPreparedStatement.java:61) ~[HikariCP-3.2.0.jar:na] at com.zaxxer.hikari.pool.HikariProxyPreparedStatement.executeUpdate(HikariProxyPreparedStatement.java) ~[HikariCP-3.2.0.jar:na] at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:175) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3192) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3706) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:90) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:604) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:478) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:356) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1483) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:512) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3321) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2517) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:447) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:178) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$300(JdbcResourceLocalTransactionCoordinatorImpl.java:39) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:271) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:104) ~[hibernate-core-5.3.12.Final.jar:5.3.12.Final] at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:533) ~[spring-orm-5.1.10.RELEASE.jar:5.1.10.RELEASE] at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746) ~[spr
반응형
반응형

해당 글은 '실무로 배우는 시스템 성능 최적화' 책의 내용을 발췌한 내용입니다. 최근 해당 책을 읽다가 이전에 내가 했던 고민과 비슷한 사례에서 비롯한 성능 저하 사례가 있어 포스팅 해본다. 보통 스프링부트에서 API를 만들 때 RestTemplate을 만들어서 사용하실텐데 매번 RestTemplate객체를 new로 생성해서 사용해야하는지 싱글톤 객체로 만들어 놓고 사용해야 하는지 고민하던 때가 있었다. 처음 멋 모를 때는 API Controller마다 new로 생성해서 사용했었는데 RestTempalate가 스레드 간 공유해도 안전한 클래스라는걸 알고 난 이후에 @Bean으로 싱글톤 객체를 생성해 사용했었다. new 객체를 매번 생성할 때도 나는 아래와 같은 장애상황을 겪진 못했지만 아래와 같이 내부락이 발생할 수 있으므로 싱글톤 객체로 만들어 사용할 수 있도록 하자. 또한 @Bean 애노테이션으로 만든 싱글톤 객체보다 빈 대신 static을 사용한 싱글턴 구조를 만드는게 더 좋다고 하는데 궁금하시다면 조치부분을 참고.

스프링 프레임워크의 객체를 잘못 사용한 성능 저하 사례

현상

오픈 전 6시간 동안 진행하는 안정성 테스트 과정에서 1시간이 경과한 후 응답시간이 급격히 저하되는 현상이 발생했다.

접근 방식

오라클 JVM을 사용하고 있어 응답시간이 느려졌을 때 jstack으로 스택을 5회 수집해서 분석했다. 성능 저하 시 전체 사용자 요청을 처리 중인 스레드 중 90% 이상이 애플리케이션 내부 락 대기 중이었다.

시스템의 애플리케이션 서버는 프론트 서버와 백엔드 서버로 구성돼 있는데, 프론트 서버에 들어온 서비스 요청은 다시 HTTP를 사용해 다시 백엔드 업무 서버를 호출하는 구조다. 내부 락은 프론트 서버 내에서 백엔드 서버의 서비스를 호출할 때 사용하는 RestTemplate 객체를 생성하는 과정(RestTeamplate.init())에서 발생하고 있었다. 이에 RestTemplate 클래스에 대해 인터넷 검색을 한 결과, 애플리케이션에서 RestTemplate을 잘못 사용하고 있음을 알 수 있었다.

원인

스프링 프레임워크에서 제공하는 HTTP 클라이언트 템플릿인 RestTemplate 클래스는 객체 생성 시 내부에 무거운 Charset.availableCharsets 메서드가 실핸된다. 더구나 Charset.availableCharsets 메서드는 여러 스레드가 동시에 사용했을 때 스레드 간에 락 경합이 생겨서 급격한 성능 저하를 유발한다. HTTP로 백엔드 시스템의 서비스를 호출할 때마다 RestTemplate객체를 새로 생성해서 사용함으로써 Charset.availableCharsets 메서드에 의한 성능 저하가 발생한 것이다.

조치

RestTemplate은 스레드 간에 공유해도 안전한 클래스이므로 싱글턴 구조로 객체를 한 개만 생성한 후 스레드 간에 공유해서 사용하도록 수정한다. 그래서 객체 생성이 인스턴스마다 한 번만 이뤄져 락 경합이 제거됐다(RestTemplate 클래스를 스프링 프레임워크의 빈으로 사용할 수도 있으나 RestTemplate 클래스를 사용하는 MobileClient 클래스 또한 프레임워크의 일부라서 빈 대신 static을 사용한 싱글턴 구조를 만들었다.

결과

6시간 안정성 테스트 동안 응답시간이 증가하는 현상 없이 일정하게 유지됐다.

ref : 실무로 배우는 시스템 성능 최적화

반응형
반응형

'실무로 배우는 시스템 성능 최적화' 책을 읽다가 L4설정 오류에 따른 알아두면 좋을 법한 성능 저하 사례가 있어 포스팅 해보려고 한다. 해당내용은 모두 '실무로 배우는 시스템 성능 최적화' 책 내용에 기반한다.

[ 현상 ]

웹 포털 화면을 사용할 때마다 대부분 정적 콘텐츠를 반복해서 내려받아 응답시간이 오래 걸리는 성능 저하 현상이 발생했다.

[ 접근 방식 ]

피들러로 모니터링한 결과, 동일 정적 콘텐츠를 기존에 내려받았음에도 다시 내려받는 현상이 발생하고 있음을 확인하고 HTTP 응답을 분석한 결과, 동일 정적 콘텐츠의 변경일자가 각기 다른 것을 발견했다. 이것은 한 웹 서버에 요청이 들어간다면 발생할 수 없는 상황이므로 웹 서버 액세스 로그를 통해 한 PC에서 들어오는 HTTP 요청이 여러 웹 서버에 골고루 들어온다는 것을 확인했다. 이에 웹 서버 파일시스템 구성과 L4의 분배 방식을 확인해 원인을 찾았다.

[ 원인 ]

-L4에서 단순 라운드 로빈(Round-robin) 형태로 HTTP 요청을 분배시켜 한 사용자의 요청에 대해 4대의 웹 서버에서 서비스가 이루어짐

-웹 서버의 문서 루트는 NAS 같은 공유 파일시스템이 아닌 로컬 파일시스템 내에 개별적으로 존재하고, 같은 콘텐츠임에도 변경일자가 각 서버마다 달랐다.

-위 두 가지 원인으로 한 브라우저에서 이뤄지는 HTTP 요청이 4대의 서버에 무작위로 분배되고, 각 서버 동일 콘텐츠의 변경일자가 모두 달라서 매번 변경된 콘텐츠로 인식됐다.

-이로 인해 브라우저 캐시가 정상적으로 효과를 발휘하지 못하고 매번 콘텐츠 내용을 내려받음으로써 성능 저하가 발생했다.

[ 조치 ]

L4 분배 방식을 해시(HASH)로 변경해 PC의 IP가 동일하면 동일한 웹 서버로 접속이 이뤄지도록 개선했다(NAS 같은 공유 파일시스템을 사용하면 L4의 분배 방식에 상관없이 브라우저 캐시가 동작하게 할 수도 있다).

[ 결과 ]

브라우저 캐시가 정상적으로 동작해 매번 콘텐츠를 내려받지 않음으로써 화면 응답시간이 개선됐다.

반응형
반응형

최근 1월 중순쯤부터 Chrome Samesite이슈에 대응한다고 설연휴전까지 시간이 굉장히 빠르게 지나간 것 같다.

아마 서비스에서 쿠키를 클라이언트들의 브라우저에 심어 무엇인가를 처리하고 있었던 분들이라면 해당 이슈에 대해 이미 파악하고 대처 했을 거라 생각된다. 그럼 구글에서 크롬(Google Chrome)에 어떤 정책을 적용했고 SameSite란 무엇인가? 에 대해 간단히 정리해보려 한다.

SameSite 한 마디로 지금 현재 사이트(도메인, First Party)의 쿠키만 클라이언트 브라우저에 심도록 허용하겠다! 

이 말인 즉슨 기존 크롬 버전 80이전 버전에서는 도메인이 다른 (Third Party)의 서버에서 response.addCookie만 해주면 third party쿠키를 쉽게 심을 수 있었다. 관련한 내용은 이전 포스팅을 참고 바란다.

"다른 도메인 내부에 쿠키 생성하기(이게 가능해?)" https://brocess.tistory.com/146?category=715032

 

[ Spring ] 다른 도메인 내부에 쿠키 생성하기 (이게 가능해???)

다른 도메인 내부에 쿠키 생성하기 (이게 가능해???) 최근 쿠키매칭시스템 설계를 위해 광고 프로세스 전반에 대한 내용을 학습중 우리 도메인 쿠키 내부에 다른 도메인 쿠키값들이 박혀있는 걸 발견하였다. 뭐야..

brocess.tistory.com

그렇다 이전에는 그냥 막 심어도 문제 없었다. 

근데 구글에서 보안 이슈를 명목(사이트간 쿠키기반 요청 위조(CSRF - Cross site request forgery)으로 크롬(Google Chrome) 80버전 부터는 third party(웹서비스와 도메인이 다른 서버)에서 쿠키 심는걸 default로 막겠다는 것이다. 2020년 2월 4일 부터 점차 크롬을 사용하는 사용자들의 브라우저들이 79버전에서 80으로 자동으로 차차 업데이트 되어질거라고 말했었다.

이게 자기들 도메인의 웹서비스에 쿠키를 심어서 사용하는 서비스에는 별문제가 없겠지만 광고서비스와 같이 해당 클라이언트의 쿠키의 특정ID값을 기반으로 유저를 구분해 타겟팅하는 서비스들에서는 큰 이슈거리일 수 밖에 없다. 😱😱😱😱😱😱😱😱

하지만 구글에서는 아예 third party 쿠키를 심지못하도록 원천봉쇄하지는 않았다. 쿠키를 다른 도메인의 서비스에 심을 때 'SameSite=none', 'secure'설정을 쿠키에 하면 기존처럼 허용해주겠다는 것이다!!!! 크롬 버전 80이상부터는 기본적으로 크롬 브라우저의 설정이 'SameSite=Lax'가 되어 버린다. (Lax보다 한 단계 높은 보안 수준인 'strict'도 존재한다.)

 

그럼 어떤식으로 해결을 해주어야 할까?

크롬 Samesite설정을 강제적으로 80버전과 같은 보안수준으로 올리고 테스트를 해보았다. 해당 설정은 크롬 브라우저에 chrome://flags라고  치면 확인할 수 있다.

크롬 80이전 버전에서는 모두 Default로 되어있다.(SameSite=none)이라고 생각하면 된다.

위의 Default값을 Enable로 변경하면 크롬 80버전의 SameSite의 상황을 재현할 수 있다.

크롬80부터는 기본이 SameSite=Lax상태가 되어버린다.

따라서 이렇게 해놓고 테스트를 했을 때 기존에 클라이언트들의 브라우저에 심어놓았던 쿠키를 아예 읽어올 수 없게 되었다.😱😱😱😱

 

그럼 어떻게 해결을 했나?

일단 유저들의 크롬 브라우저가 80으로 업데이트 되기전 클라이언트들의 브라우저에 심긴 우리 서비스의 식별 ID값에 "samesite=none", "secure"설정을 하여 동일한 값으로 쿠키를 덮어씌워줘야 했다. but, "secure"설정이 된 쿠키는 "https"가 설정된 서버에서만 읽을 수 있기 때문에 수 많은 광고주나 매체주에 심겨 우리쪽 서버를 호출하는 url이 http로 되어있을 경우 읽지 못하는 문제가 발생할 수 있었다. 이에 서버 자체적으로 http로 들어오는 요청을 https로 리다이렉트 시키도록 하였다.

추가 검토사항으로는 구글이 크롬관련 작성한 문서에는 다음과 같이 씌어져 있다. 

Versions of Chrome from Chrome 51 to Chrome 66 (inclusive on both ends).
These Chrome versions will reject a cookie with `SameSite=None`

즉, 크롬버전 51~66사이의 버전에서는 우리가 아무리 쿠키에 'SameSite=None', 'secure'설정을 한다고 하더라도 reject시켜 버린다는 것이다....

https://www.chromium.org/updates/same-site/incompatible-clients

 

SameSite=None: Known Incompatible Clients - The Chromium Projects

Home of the Chromium Open Source Project

www.chromium.org

그래서 어떡하지라는 생각이 들어 일단 우리 서비스에 요청오는 request들의 user-agent데이터를 기반으로 해당 버전에 속하는 요청이 얼마나 되는지 확인해보았다. 그랬더니 다행히도 해당 버전으로 부터 오는 요청은 1~2%내외였고 이 1~2%를 위해 무엇인가를 작업하기엔 trade-off가 되는 것들이 너무 많았다. 서버에서 하나 같이 user-agent의 버전을 확인해 해당 버전에서는 별도의 처리를 해주어야 했고 결국에 해당 버전에서 SameSite=None을 reject시켜버리기 때문에 손을 쓰기 힘든 상황이었기에 해당 버전을 위해 별도의 처리는 따로 하지 않기로 결정되었다.

그리고 스프링, 자바 어플리케이션에서 많이 사용하는 javax.servlet.http.cookie class에서는 SameSite관련 API를 지원하지 않기 때문에 SameSite속성을 추가하기 위해서는 HttpServletResponse 객체에 Set-Cookie헤더(header)를 추가해서 처리했어야 했다.

어플리케이션 사이드나 Web Server설정 관련해서 처리한 부분은 잘정리된 포스팅이 있어 해당 블로그를 참고하면 좋을 것 같다.👍(굉장히 잘 정리되어 있다.)

https://ifuwanna.tistory.com/223

 

Cookie SameSite 설정하기 (Chrome 80 쿠키 이슈)

20년 2월 4일 릴리즈된 구글 크롬(Google Chrome)80버전부터 새로운 쿠키 정책이 적용 되어 Cookie의 SameSite 속성의 기본값이 "None"에서 "Lax"로 변경되었습니다. SameSite 를 None 으로 설정할 경우 모든 도..

ifuwanna.tistory.com

작업을 하며 느낀점은....구글이 갑이다....지금도 구글의 영향력이 쌔지만 앞으로는 점점 더 그 파워가 막강해질것이다....그럼 구글 주식을 사야겠다😅......여기서 이만...

반응형
반응형

mac로컬 환경에서 가끔 스프링부트 어플리케이션 실행시 다음과 같은 에러가 발생한다.

Unable to start embedded Tomcat servlet container 
_org.springframework.boot.context.embedded.EmbeddedServletContainerException_: Unable to start embedded Tomcat servlet container
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.start(_TomcatEmbeddedServletContainer.java:165_)
org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.startEmbeddedServletContainer(_EmbeddedWebApplicationContext.java:293_)
.
.
.
org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainer.start(_TomcatEmbeddedServletContainer.java:159_)

... 10 common frames omitted

해결방법

maven clean, build 다시 하고 문제가 발생할 경우 다음과 같이 처리해준다. 

터미널 켜서 명령어 : lsof -n -i4TCP:8080 치고 pid확인해서 (어플리케이션 포트 설정이 8080인 경우)
kill -9 pid 로 해당 프로세스를 강제종료시켜주고 다시 실행해보자. 
반응형
반응형

브라우저에서 특정 사이트를 접속 할 때 브라우져는 어떻게 해당 도메인에 해당하는 IP를 어떤 순서로 찾을까????

 

1. 먼저 브라우저는 Local Cache를 검색한다.

 

2. 그리고 hosts 파일을 검색하여 찾는다. 

 

3. 위의 두 경우에 해당하는 도메인이 없는경우 그 때

DNS(Domanin Name Service)서버에 요청하여 해당 도메인의 IP주소를 받게된다.

 

그래서 보통 테스트 할 때

 

기존 naver.com DNS에 등록되어 있는 도메인의 ip가 323.5.2.1일지라도 

내 로컬 피시, 리눅스 환경에서 hosts파일에 naver.com 127.0.0.1로 등록해 놓게 되면

DNS에 가기전에 hosts파일을 먼저 뒤지기 때문에 127.0.0.1 IP로 요청이 가게 되는 것이다.

(보통 개발하다 보면 특정 도메인 테스트를 위해 이런 방법을 쓰곤 한다.)

반응형
반응형

Mac에서 인텔리제이(IntelliJ)를 사용하시는 분들이라면 무조건 겪어 보셨을거다..(분명해...)

Action검색 : Ctrl + Shift + a (execute maven, execute gradle 등등) 기본 액션을 편하게 해주는 단축키

 

근데 짜증나게도 여러번 단축키를 누르다 보면...

action창이 뜨지 않고 아래와 같은 이상한 노란 메모장같은 화면이 뜬다....(apropos exe - 80 x 48)

경험해보신분들 있죠???

 

해결책

해결법을 알려드리려 한다. MAC에서 제공하는 단축키와 겹치다 보니 아래와 같이 해당 서비스를 disable해주어야 한다.

시스템환경설정 -> 키보드 -> 단축키(탭) -> 서비스 -> 터미널 man페이지 인덱스 검색 => 비활성화(체크 해제)

터미널에서 man 페이지 인덱스 검색 체크를 해제해주어야 한다.

 

체크를 해제 해주게 되면 그 이후 부터는 노란 메모장을 더 이상 마주하지 않을 것이다^_^

반응형
반응형

scala 패턴매칭 관련해서 이전 문서 보다가 스터디하면서 정리했던 내용이 있길래 블로그로 무브

 

패턴 매칭

단순 매치

val bools = Seq(true, false)

for (bool <- bools) {
  bool match {
    case true => println("Got heads")
    case false => println("Got tails")
  }
}

컴파일러는 시퀀스의 타입으로부터 그 원소에 true와 false라는 두가지 경우가 있음을 안다.
따라서 매치가 완전하지 않을 경우 경고를 표시한다.
또한 일치하는 case 절이 없는 경우 MatchError가 발생한다.


매치 내의 값, 변수, 타입

특정 값이나 특정 타입의 모든 값과 매치시킬 수 있다.

{code:scala}

for {
  x <- Seq(1, 2, 2.7, "one", "two", 'four)                           // <1>
} {
  val str = x match {                                                // <2>
    case 1          => "int 1"                                       // <3>
    case i: Int     => "other int: "+i                               // <4>
    case d: Double  => "a double: "+x                                // <5>
    case "one"      => "string one"                                  // <6>
    case s: String  => "other string: "+s                            // <7>
    case unexpected => "unexpected value: " + unexpected             // <8>
  }
  println(str)                                                       // <9>
}

{code}

결과
int 1
  other int: 2
  a double: 2.7
  string one
  string: two
  unexpected value: 'four

- 매치는 앞에서부터 차례대로 처리된다.
- 구체적인 절이 앞으로 나와야 한다.
- 덜 구체적인 절은 뒤로 가야 한다.
- 기본절은 가장 마지막에 와야한다.

 

{code:scala}

def checkY(y: Int) = {
  for {
    x <- Seq(99, 100, 101)
  } {
    val str = x match {
      case y => "found y!" //  `y`(역 작은 따옴표)
      case i: Int => "int: "+i
    }
    println(str)
  }
}
checkY(100)

{code}

결과
int:99
found y!
int: 101
--------'y'일 경우--------
found y!
found y!
found y!

 

- case 절에서 소문자로 시작하는 이름은 뽑아낸 값을 저장할 새로운 변수 이름으로 간주한다
- 정의한 값을 참조하고 싶을 경우 역작은따옴표로 둘러싸야 한다.
- 대문자로 시작하는 이름은 타입 이름으로 간주된다.
- '\|'를 사용해서 *case \: Int \| \:Double => "Int or Double?"* 과 같이도 사용가능하다.

 

시퀀스에 일치시키기


Seq 는 정해진 순서대로 원소를 순회할 수 있는 List나 Vector 등의 모든 구체적인 컬렉션 타입의 부모 타입이다.
(Seq는 시퀀스('sequence 순서가 정해진 열')이라는 뜻에서 온 말이다)

{code:scala}
val nonEmptySeq    = Seq(1, 2, 3, 4, 5)                              // <1>
val emptySeq       = Seq.empty[Int]
val nonEmptyList   = List(1, 2, 3, 4, 5)                             // <2>
val emptyList      = Nil
val nonEmptyVector = Vector(1, 2, 3, 4, 5)                           // <3>
val emptyVector    = Vector.empty[Int]
val nonEmptyMap    = Map("one" -> 1, "two" -> 2, "three" -> 3)       // <4>
val emptyMap       = Map.empty[String,Int]

def seqToString[T](seq: Seq[T]): String = seq match {                // <5>
  case head +: tail => s"$head +: " + seqToString(tail)              // <6>
  case Nil => "Nil"                                                  // <7>
}

for (seq <- Seq(                                                     // <8>
    nonEmptySeq, emptySeq, nonEmptyList, emptyList,
    nonEmptyVector, emptyVector, nonEmptyMap.toSeq, emptyMap.toSeq)) {
  println(seqToString(seq))
}

- Nil : 비어있는 List를 표현하는 객체, 이 객체는 모든 빈 시퀀스와 일치한다 (List가 아닌 컬렉션에도 Nil을 사용할 수 있다)
- tail : Seq에서 머리를 제외한 나머지 부분

결과
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
1 +: 2 +: 3 +: 4 +: 5 +: Nil
Nil
(one,1) +: (two,2) +: (three,3) +: Nil
Nil


- Map은 순회 시 특별한 순서를 보장하지 않기 때문에 Seq의 서브타입이 아님 (따라서 Map.toSeq를 호출해 키-값 튜플의 시퀀스 만들어 주어야함)
- 연산자 +: 는 시퀀스의 '콘즈' 연산자다. 메서드 이름이 콜론(:)으로 끝나면 오른쪽으로 결합되어 Seq의 꼬리에 대한 호출이 됨을 기억 (List :: 연산자와 비슷)

 

h3. 4.4 튜플에 일치시키기

{code:scala}
val langs = Seq(
  ("Scala",   "Martin", "Odersky"),
  ("Clojure", "Rich",   "Hickey"),
  ("Lisp",    "John",   "McCarthy"))

for (tuple <- langs) {
  tuple match {
    case ("Scala", _, _) => println("Found Scala")                   // <1>
    case (lang, first, last) =>                                      // <2>
      println(s"Found other language: $lang ($first, $last)")
  }
}
{code}

결과
Found Scala
Found other language: Clojure (Rich, Hickey)
Found other language: Lisp (John, McCarthy)

 

케이스 클래스에 일치시키기

{code:scala}
case class Address(street: String, city: String, country: String)
case class Person(name: String, age: Int, address: Address)

val alice   = Person("Alice",   25, Address("1 Scala Lane", "Chicago", "USA"))
val bob     = Person("Bob",     29, Address("2 Java Ave.",  "Miami",   "USA"))
val charlie = Person("Charlie", 32, Address("3 Python Ct.", "Boston",  "USA"))

for (person <- Seq(alice, bob, charlie)) {
  person match {
    case Person("Alice", 25, Address(_, "Chicago", _)) => println("Hi Alice!")
    case Person("Bob", 29, Address("2 Java Ave.", "Miami", "USA")) =>
      println("Hi Bob!")
    case Person(name, age, _) =>
      println(s"Who are you, $age year-old person named $name?")
  }
}
{code}

결과
Hi Alice!
Hi Bob!
Who are you, 32 year-old person named Charlie?

 

-zipWithIndex : 튜플을 번호와 함께 출력하고 싶을 경우 사용

{code:scala}
val itemsCosts = Seq(("Pencil", 0.52), ("Paper", 1.35), ("Notebook", 2.43))
val itemsCostsIndices = itemsCosts.zipWithIndex
for (itemCostIndex <- itemsCostsIndices) {
  itemCostIndex match {
    case ((item, cost), index) => println(s"$index: $item costs $cost each")
  }
}
{code}

결과
itemsCostsIndices  : Seq[((String, Double), Int)] = List(((Pencil,0.52),0), ((Paper,1.35),1), ((Notebook,2.43),2))
0: Pencil costs 0.52 each
1: Paper costs 1.35 each
2: Notebook costs 2.43 each

 

apply, unapply 메서드

- 케이스 클래스의 특징 중 하나는 컴파일러가 모든 케이스 클래스에  대해 각 클래스와 이름이 같은 싱글턴 객체인 *동반 객체*를 자동으로 만들어 낸다.
(동반 객체는 직접 정의도 가능하다.)
* Companion Class (동반 클래스) : (객체 관점) 클래스의 이름이 싱글톤 객체의 이름과 같다.

* Companion Object (동반 객체) :  (클래스 관점) 싱글톤 객체의 이름이 클래스의 이름과 같다.

{code:scala}

case class Point(x: Double = 0.0, y: Double = 0.0)

> 만들어지는 동반 객체
object Point {   def apply(x: Double = 0.0, y: Double = 0.0) = new Point(x,y)}

> 동반 객체에는 자동으로 apply라는 메서드가 추가되고 동반 클래스의 생성자와 같은 인자를 받는다.
> 객체 뒤에 인자 목록을 덧붙이면 스칼라는 그 객체의 apply를 호출하기 위해 찾는다.
val p1 = Point.apply(1.0, 2.0)val p2 = Point(1.0, 2.0)둘은 동등한 식이라고 볼 수 있다.
Point.apply 메서드는 사실상 Point를 만드는 팩토리로 단지 동작은 new 키워드 없이 생성자를 호출하는 것과 같다.

{code}

unapply

{code:scala}

object patternMatching2 {
  object Test {
    def unapply(a: Int): Boolean = {
        if (a < 10) { true }
        else { false }
    }
}

object Test2 {
    def unapply(a: Int): Option[(Int, String)] = {
        if (a > 30) { Some(a/10, "from Test2") }
        else { None }
    }
}

class Example {
    def method(target: Int) {
        target match {
            case Test() => println("matched to Test")
            case Test2(n @ Test(), m) => println("result: " + n + " " + m)
            case 120 => println("match to 120")
            case 11 => println("noneMatching")
        }
    }
}

val t = new Example                               //> t  : patternMatching2.Example = patternMatching2$Example@593634ad
t.method(11)                                      //> matched to Test
t.method(40)                                      //> result: 4 from Test2
t.method(120)                                     //> match to 120
}

{code}

- n, m은 Test2로부터 리턴받을 아큐먼트
- 돌려받는 아규먼트에 다른 extractor를 @로 연결하면 해당 값에 대한 패턴매칭을 추가로 적용할 수 있다.

 

케이스 절의 변수 바인딩에 대해 더 살펴보기

객체 자체에도 변수를 대입하고 싶은 경우
p @ ...문법은 전체 Person 인스턴스를 p에 대입하며, a @ ...부분도 비슷하게 Address 객체를 대입한다.

{code:scala}

case class Address(street: String, city: String, country: String)
case class Person(name: String, age: Int, address: Address)

val alice   = Person("Alice",   25, Address("1 Scala Lane", "Chicago", "USA"))
val bob     = Person("Bob",     29, Address("2 Java Ave.",  "Miami",   "USA"))
val charlie = Person("Charlie", 32, Address("3 Python Ct.", "Boston",  "USA"))

for (person <- Seq(alice, bob, charlie)) {
  person match {
    case p @ Person("Alice", 25, address) => println(s"Hi Alice! $p")
    case p @ Person("Bob", 29, a @ Address(street, city, country)) =>
      println(s"Hi ${p.name}! age ${p.age}, in ${a.city}")
    case p @ Person(name, age, _) =>
      println(s"Who are you, $age year-old person named $name? $p")
  }
}
{code}

결과
Hi Alice! Person(Alice,25,Address(1 Scala Lane,Chicago,USA))
Hi Bob! age 29, in Miami
Who are you, 32 year-old person named Charlie? Person(Charlie,32,Address(3 Python Ct.,Boston,USA))

 

패턴 매칭의 다른 사용법

if 식에서도 패턴 매칭을 사용할 수 있다.
{code:scala}
val p = Person("Alice",   25, Address("1 Scala Lane", "Chicago", "USA"))

if ( alice == Person("Alice", 25, Address("1 Scala Lane", "Chicago", "USA")))
     "yes" 
else "no"

 

결과
res0: String = yes

위치지정자 _는 여기서 사용할 수 없다.
== 검사에 사용할 $eq$eq라는 내부 함수가 있다.
JVM 명세에서는 식별자에 알파벳, 숫자, _, $만 사용할 수 있기 때문에, 스칼라는 그 외의 문자를 JVM이 받아들일 수 있는 문자열로 조작한다.
그래서 =은 $eq가 된다.

 

마치며


- 정보를 정제된 방식으로 추출해야 하는 경우 패턴 매칭을 고려하라.
- for 내장과 함께 패턴 매칭은 자주 사용하는 스칼라 코드를 간결하면서 강력하게 만들어준다.
- 스칼라 프로그래머가 1/10 정도의 줄 수로 자바로 만든 프로그램과 비슷한 기능을 작성하는 경우도 드물지 않다.
- 패턴 매칭은 여러 함수형 언어의 보증서로 데이터 구조로부터 데이터를 뽑아내는 유연하고 간결한 기법이다.

반응형

+ Recent posts