ACID 제약사항) 을 가지지 않는 것. -> locking이 필요 없으니 병렬화 할 수 있고, 성능 이점을 가짐.
nosql의 대표 주자인 dynamo 디자인할때 생각해야 할 사항들을 정리해 보자.
dynamo의 primary key = partition key + sort key 임.
design consideraton
RDB와는 다르게 컬럼명이 스토리지에 중복해서 저장되므로 컬럼명이 짧을 수록 스토리지 비용을 아낄 수 있다.
LSI(local secondary index)의 경우는 하나의 partition key에 대해서 10G의 사이즈 제한이 있다. 그 이유는 10G가 넘어가게 되면 partitionkey + sort key를 기반으로 해쉬해서 한번더 파티션을 나누기 때문인데, 이때의 인덱스는 더이상 로컬일 수가 없기 때문이다. 이때는 GSI(global secondary index)를 사용해야 한다. GSI는 GSI를 위한 별도의 테이블이 생성되는 것과 같다.
partition key
dynamo는 물리적 파티션을 나눠서 관리하기 때문에 이 파트션을 나눌 키를 정의해야 한다. user id 같은것. date(2018-10-10)같은 것은 좋지 않다. 왜나하면 그 날의 데이터는 모두 하나의 파티션으로 몰릴 것이기 때문에 핫 파티션 문제가 생김.
sort key
파티션 키 하위를 소트할때 사용하는 키. sort key를 사용해서 범위로 쿼리 할 수 있음. 또한 sort key로 정렬이 되므로 순서를 줄 수 있음
LSI(local secondary index)
특정 컬럼명으로 빨리 검색을 하고 싶거나 소트 순서를 주고 싶다면 LSI를 걸 수 있다. KEYS_ONLY로 키만 걸수도 있고, 특정 컬럼을 카피해서 가지고 있을 수도 있다. 현재로서는 10G의 크기 제한이 있기 때문에 설계할때 고려를 해야한다. 크기제한을 벗어나고 싶다면 GSI(Global secondary index)를 걸 수 있다.
ttl
table의 ttl을 enable하고 ttl property를 설정해주면 특정시간이 지난 row를 dynamo가 자동으로 삭제해주는 기능이 있다.
나의 경우 이 기능을 푸쉬를 보낼때 사용했다. ttl에 의해 row가 삭제되면 그 이벤트를 dynamo stream으로 보내게 되고, 그것을 lambda로 받아서 push 처리를 하게 하였다.
이때 한가지 문제가 ttl에 의해서 삭제가 되었는지, 사용자의 DELETE 요청에 의해서 지워졌는지를 어떻게 판단할 수 있는가 였는데 찾아보니 userIdentity 속석에 의해서 판단이 가능했다.
docker로 angular app을 deploy하는 방법. front와 backend의 url이 달라지기 때문에 cors 문제가 생기는데 이를 회피하는 방법은 여러가지가 있다. CloudFront를 사용할 수도 있고 여기서는 ngnix proxy기능을 사용해서 회피하려고 한다.
P, Q, R 세점이 있다. 이 세점의 방향성은 어떻게 찰을 수 있을까. (P,Q)의 기울기와 (Q,R)의 기울기를 비교해보면 방향을 알 수 있다. 그렇다면 (p,q) 의 기울기가 더 크다면 pqr은 시계방향으로 굽어 있는 것이고, 그 반대라면 반시계방향으로 굽어 있는 것이다. 두 기울기가 같다면 평행한 것이고.
위의 두 값을 비교하면 pqr의 상대적인 위치를 알 수 있는 함수를 만들 수 있다. R이 시계방향쪽에 위치하고 있는지 아닌지를..
이것을 orientation이라고 정의하고 함수로 구현하면 아래처럼 구현할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13
// To find orientation of ordered triplet (p, q, r). // The function returns following values // 0 --> p, q and r are colinear // 1 --> Clockwise // 2 --> Counterclockwise intorientation(Point p, Point q, Point r) { int val = (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
if (val == 0) return0; // colinear return (val > 0)? 1: 2; // clock or counterclock wise }
Spring MVC가 동작하는 순서는 이렇다. 사용자의 request가 서버로 오면 가장먼저 만나는 곳이 DispatcherServlet이다. 여러개의 DispatcherServlet을 가질수 있고 각각은 prefix로 구분이 된다. 맵핑되는 DispatherServlet이 정해지면 그 서블릿의 controller중 request url에 맵핑되는 controller가 정해지고 그 컨트롤러가 불리게 된다. 컨트롤러는 request param에 대한 validation을 한후 viewname을 리턴한다. DispatherServlet은 view name을 받아서 view resolver에서 new name을 resolve 한후 해당 View로직을 실행하고 response를 랜더링 한후 사용자에게 돌려준다.
@Configuration @EnableWebMvc//Enable Spring MVC @ComponentScan("spitter.web") publicclassWebConfig extendsWebMvcConfigurerAdapter{ @Bean public ViewResolver viewResolver(){ InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/views/"); resolver.setSuffix(".jsp"); resolver.setExposeContextBeansAsAttributes(true); return resolver; } @Override publicvoidconfigureDefaultServletHandling( DefaultServletHandlerConfigurer configurer){ configurer.enable(); } }
위는 web.xml를 대신에 Javaconfig를 사용해서 설정을 한 것이고 viewResolver및 DispatcherServlet에대한 설정을 하고 있는 부분.
controller
1 2 3 4 5 6
@Controller Declared to be a controller publicclassHomeController{ @RequestMapping(value="/", method=GET) public String home(){ return"home"; //View name is home } }
controller는 위처럼 path mapping과 http method(GET|POST|PUT|DELETE) 맵핑으로 view 이름을 리턴하게 된다.
앞서도 간단히 알아봤지만 aspect는 여러 서비스 모듈에 공통적으로 사용되어지는 것(로깅, security ..)등을 모듈화 한것이고 이런식의 접근을 AOP라고 함.
AOP에서 사용하는 여러 단어들을 알 필요가 있는데
Advice : job of an aspect, @Before @After 등을 말함.
join points : possible oppoortunities
pointcuts : one or more join points applied for an advice, execution, within등을 말함
aspects : advice + pointcuts
introductions : 기존 클래스에 새로운 펑션이나 변수등을 추가할 수 있는 기능
weaving : target object에 aspect를 적용하는 것. spring은 런타임에 함.
spring의 aop는 AOP의 완벽한 구현이 아니라서 method join points만 지원한다. 다른 AOP는 필드나 생성자 join points까지도 지원하는 반면.
Selecting joint points with pointcuts
1 2 3 4
execution(* concert.Performance.perform(..)) execution(* concert.Performance.perform(..)) && within(concert.*)) // limit package concert execution(* concert.Performance.perform()) and bean('woodstock') // with specific bean execution(* concert.Performance.perform()) and !bean('woodstock') // without specific bean
Creating annotated aspects
@Pointcut annotation을 사용하면 이것을 한번 생성한후 참조해서 반복해서 사용할 수 있음. 참조할때는 펑션 이름을 사용하면 됨. 아래 예제 참고.
@Pointcut("execution(** concert.Performance.perform(..))") publicvoidperformance(){} @Before("performance()") // refer by pointcut name publicvoidsilenceCellPhones2(){ System.out.println("Silencing cell phones"); } }
이렇게 어노테이션을 달았다고 aop가 동작하는 것은 아니고 아래처럼 @EnableAspectJAutoProxy와 빈등을 생성해줘야 실제로 aop가 동작하게 됨. 실제로 AspectJ의 annotation을 사용하고 있지만 실제로는 스프링의 aop를 사용하고 있는 것이니 AspectJ의 기능을 풀로 사용할 수 없으니까 주의 해야 함.
DI는 스프링의 근간이고 두 오브젝트간의 결합을 가능하게 해주는 것을 wiring이라고 부른다. 이런 wiring이 어떻게 동작하고 있는지 알아보자.
스프링은 3가지의 wiring을 지원한다. xml과 java config중 type-safe한 java config를 추천하고 있다.
xml
java config
implict bean discovery and automatic wiring
automatic wiring
automatic wiring은 크게 2가지에 의해서 이루어진다.
component scanning
auto wiring
bean은 @Component라는 annotation 의해서 선언되어지고 향후 스캐닝시에 bean으로 생성되어져서 스프링 컨테이너로 초기화 되게 된다. bean에는 이름을 지정해 줄수도있는데, @Component('name')이라고 하면 되고 따로 지정을 해주지 않으면 클래스 이름이 default가 된다.
컴포넌트 스캐닝은 자동으로 이루어지지 않고 @ComponetScan annotation에 의해서 이루어지고 해당 패키지 밑 하위 패키지들이 스캐닝 대상이 된다. xml에서도 다음과 같이 스캐닝을 지정해 줄 수 있다.
@ComponentScan(basePackages="soundsystem") // 명시적으로 패키지를 지정 @ComponentScan(basePackages={"soundsystem", "video"}) // 여러개의 패키지를 지정 할 수도 @ComponentScan(basePackageClasses={CDPlayer.class, DVDPlayer.class}) // 여러개 클래스 지정
앞서 지정한 빈을 사용할때는 @Autowired annotation을 사용하면 되는데 bean의 이름으로 자동으로 매핑을 해준다. 해당 bean을 찾을 수 없을때는 초기화시에 에러가 나게 된다.
java config
java config는 xml에 비해 powerful하고, type-safe하고 refactoring에 우위가 있다. xml의 typo때문에 한번쯤은 삽질한 경험이 있을듯 하다. 이때의 허무함이란..
@Configuration annotation은 컴포넌트 스캐닝 대신에 explicit하게 빈들을 생성할 수 있게 해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@Bean public CompactDisc sgtPeppers(){ returnnew SgtPeppers(); }
@Bean public CDPlayer cdPlayer(){ returnnew CDPlayer(sgtPeppers()); }
@Bean public CDPlayer anotherCDPlayer(){ returnnew CDPlayer(sgtPeppers()); }
위의 예에서 원래의 자바 코드라면 sgtPeppers는 서로 다른 instance를 리턴하기때문에 cdPlayer와 anotherCDPlayer는 서로 다른 CompactDisc 인스턴트여야 하지만 실제로는 같은 인스턴스로 초기화 된다. @Bean때문에 behavior가 달라진 것이다.
mixing java config and xml
아래처럼 다른 configuration을 임포트할 수도 있고, xml config도 import 할 수도 있다.
1 2 3 4 5
@Configuration @Import({CDPlayerConfig.class, CDConfig.class}) // import other java config @ImportResource("classpath:cd-config.xml") // import xml config publicclassSoundSystemConfig{ }