Inertia

mysql on ec2

EC2 에 mysql을 설치하는 가장 간단한 방법.

install docker, docker-compose

1
2
3
4
5
6
7
8
9
sudo amazon-linux-extras install docker
sudo service docker start
sudo usermod -a -G docker ec2-user
# test docker command
docker info

#install docker-compose
sudo curl -L https://github.com/docker/compose/releases/download/1.23.2/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

install mysql

아래와 같은 docker-compose.yml 파일을 만들고

1
2
3
4
5
6
7
8
9
10
11
mysql:
image: mysql:8
restart: always
container_name: mysql
ports:
- "3306:3306"
environment:
- MYSQL_USER=darren
- MYSQL_PASSWORD=darren
- MYSQL_DATABASE=darren
- MYSQL_ROOT_PASSWORD=product

아래 명령을 실행하면 로컬의 3306포트로 mysql 서버가 뜬다.

docker-compose up -d

mysql shell은 아래처럼 실행할 수 있다.

docker exec -it mysql mysql -uroot -pdarren product

아니면 아래처럼 bash로 들어간 다음

docker exec -it mysql bash

mysql shell을 실행할 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
mysql -uroot -p 
Enter password:
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| darren |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+

dynamodb

RDBMS에 비해 nosql이 가지는 이점은

  • denormalized되지 않았다는 것. -> join이 없으니 쿼리가 간단해 짐.
  • ACID 제약사항) 을 가지지 않는 것. -> locking이 필요 없으니 병렬화 할 수 있고, 성능 이점을 가짐.

nosql의 대표 주자인 dynamo 디자인할때 생각해야 할 사항들을 정리해 보자.

dynamo의 primary key = partition key + sort key 임.

design consideraton

  1. RDB와는 다르게 컬럼명이 스토리지에 중복해서 저장되므로 컬럼명이 짧을 수록 스토리지 비용을 아낄 수 있다.
  2. 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 속석에 의해서 판단이 가능했다.

즉, 아래처럼 판단이 가능하겠다.

1
2
3
4
5
6
7
8
9
10
11
exports.handle = (event, context, callback) => {
console.log('Stream record v1: ', JSON.stringify(event, null, 2));

event.Records.forEach((record) => {
if (record.eventName === 'REMOVE' &&
record.userIdentity // only ttl triggered item has userIdentity property
) {
// send push
}
}
}

이 속성을 기반으로 ttl에 의한 이벤트이면 푸쉬를 보내고, 사용자의 삭제 요청에 의한 것이면 푸쉬를 보내지 않게 판단할 수 있게 되었음.

dockerize angular + spring api

docker로 angular app을 deploy하는 방법. front와 backend의 url이 달라지기 때문에 cors 문제가 생기는데 이를 회피하는 방법은 여러가지가 있다. CloudFront를 사용할 수도 있고 여기서는 ngnix proxy기능을 사용해서 회피하려고 한다.

docker-compose

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
version: '3'
services:
ng:
container_name: ng
image: web-nginx:0.1
ports:
- "80:80"
networks:
- app-network
api:
container_name: api
image: api-backend:0.1
ports:
- "8088:8088"
networks:
- app-network
networks:
app-network:
driver: bridge

docker image 빌드한후 aws ec2 instance 에서 위와 같은 docker-compose.yml을 생성한후 docker-compose up을 하면 nginx와 api docker image가 실행된다.

  • bridge network을 사용한 것은 ng와 api 간의 네트웍을 가능하게 하기 위해서 이다.
  • nginx를 사용한 이유는 static file은 nginx로 서빙을 하고, api는 스프링으로 실행을 하기때문에 cors이슈가 발생하는데 이것을 nginx의 proxy기능을 이용해서 cors문제를 회피하기 위해서 이다. nginx conf는 아래에 설명할 것임

nginx conf

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
server {
listen 80;
server_name localhost;
charset utf-8;

#charset koi8-r;
#access_log /var/log/nginx/host.access.log main;

location /backend {
rewrite ^/backend/(.*) /$1 break;
proxy_pass http://api:8088;
#proxy_pass_request_headers on;
#proxy_set_header Authorization $http_authorization;
#proxy_pass_header Authorization;
break;
}

location / {
root /usr/share/nginx/html;
index index.html index.htm;
}

#error_page 404 /404.html;

# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

위는 일반적인 nginx conf 인데, api:8080으로 proxy가 설정된 것만 유의해서 보면 될듯 하다.

  • rewrite로 /backend/xxx로 시작하는 url을 /xxx로 바꿔서 api:8080으로 라우팅 해준다. 여기서 api는 docker-compose에서 설정된 api docker instance를 의미한다.

geometry

기울기

p1(x1,y1), p2(x2,y2) 두점이 있을때 두점을 잊는 직선의 기울기는 아래처럼 표현된다.

$$
기울기 = \frac{y 변화값}{x변화값} = \frac{y2-y1}{x2-x1}
$$

orientation

P, Q, R 세점이 있다. 이 세점의 방향성은 어떻게 찰을 수 있을까.
(P,Q)의 기울기와 (Q,R)의 기울기를 비교해보면 방향을 알 수 있다.
그렇다면 (p,q) 의 기울기가 더 크다면 pqr은 시계방향으로 굽어 있는 것이고, 그 반대라면 반시계방향으로 굽어 있는 것이다. 두 기울기가 같다면 평행한 것이고.

이것을 수식으로 나타내면

$$
기울기 \overrightarrow{PQ} = \frac{Q_y-P_y}{Q_x-P_x}
$$

$$
기울기 \overrightarrow{QR} = \frac{R_y-Q_y}{R_x-Q_x}
$$

위의 두 값을 비교하면 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
int orientation(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) return 0; // colinear
return (val > 0)? 1: 2; // clock or counterclock wise
}

convex hull

앞서 말한 orientation 은 convex hull알고리즘으로 발전할 수 있다.

https://www.geeksforgeeks.org/convex-hull-set-2-graham-scan/

spirng-web

Building spring web application

Getting started with spring MVC

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를 랜더링 한후 사용자에게 돌려준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package spittr.config;
import org.springframework.web.servlet.support.
AbstractAnnotationConfigDispatcherServletInitializer;
public class SpittrWebAppInitializer
extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected String[] getServletMappings() {
return new String[] { "/" }; //
}
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebConfig.class };
}
}

@Configuration
@EnableWebMvc //Enable Spring MVC
@ComponentScan("spitter.web")
public class WebConfig
extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver resolver =
new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
resolver.setExposeContextBeansAsAttributes(true);
return resolver;
}
@Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
} }

위는 web.xml를 대신에 Javaconfig를 사용해서 설정을 한 것이고 viewResolver및 DispatcherServlet에대한 설정을 하고 있는 부분.

controller

1
2
3
4
5
6
@Controller Declared to be a controller public class HomeController {
@RequestMapping(value="/", method=GET)
public String home() {
return "home"; //View name is home
}
}

controller는 위처럼 path mapping과 http method(GET|POST|PUT|DELETE) 맵핑으로 view 이름을 리턴하게 된다.

query/path parameter

1
2
3
4
5
6
@RequestMapping(method=RequestMethod.GET)
public List<Spittle> spittles(
@RequestParam(value="max", defaultValue=MAX_LONG_AS_STRING) long max,
@RequestParam(value="count", defaultValue="20") int count) {
return spittleRepository.findSpittles(max, count);
}

form

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Controller
@RequestMapping("/spitter")
public class SpitterController {
private SpitterRepository spitterRepository;
@Autowired
public SpitterController(
SpitterRepository spitterRepository) {
this.spitterRepository = spitterRepository;
}
Inject SpitterRepository
@RequestMapping(value="/register", method=GET)
public String showRegistrationForm() {
return "registerForm";
}
@RequestMapping(value="/register", method=POST)
public String processRegistration(Spitter spitter) {
spitterRepository.save(spitter);
return "redirect:/spitter/" + spitter.getUsername(); // redirect
} }

form 받는 post request예제.

post의 경우 request로 온 data를 validation이 필요한데. 이것은 java valication annotation을 사용할 수 있다. @null, @notNull, @Min, @Max 같은 것들..

1
2
3
4
5
6
7
8
9
public class Spitter {
private Long id;
@NotNull
@Size(min=5, max=16)
private String username;
@NotNull
@Size(min=5, max=25)
private String password;
}

Spring In Action - Aspect-oriented Spring

Chapter4 - Aspect-oriented Spring

AOP

앞서도 간단히 알아봤지만 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을 사용하면 이것을 한번 생성한후 참조해서 반복해서 사용할 수 있음. 참조할때는 펑션 이름을 사용하면 됨. 아래 예제 참고.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Aspect
public class Audience {
@Before("execution(** concert.Performance.perform(..))")
public void silenceCellPhones() {
System.out.println("Silencing cell phones");
}

@Pointcut("execution(** concert.Performance.perform(..))")
public void performance() {}
@Before("performance()") // refer by pointcut name
public void silenceCellPhones2() {
System.out.println("Silencing cell phones");
}

}

이렇게 어노테이션을 달았다고 aop가 동작하는 것은 아니고 아래처럼 @EnableAspectJAutoProxy와 빈등을 생성해줘야 실제로 aop가 동작하게 됨. 실제로 AspectJ의 annotation을 사용하고 있지만 실제로는 스프링의 aop를 사용하고 있는 것이니 AspectJ의 기능을 풀로 사용할 수 없으니까 주의 해야 함.

1
2
3
4
5
6
7
8
9
@Configuration
@EnableAspectJAutoProxy //enable auto-proxying
@ComponentScan
public class ConcertConfig {
@Bean
public Audience audience() {
return new Audience();
}
}

@Around는 보다 많은 것을 할수 있는데 jp.proceed를 부름으로서 pointcut의 대상 함수가 실행이 되게 된다. 따라서 원래 대상 함수를 블락킹 할 수도 있어 더 강력하다.

1
2
3
4
5
6
7
8
9
10
11
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Silencing cell phones");
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}

아래를 보면 args로 펑션의 파람을 받아서 advice로 전달할 수 있다.

1
2
3
4
5
6
7
8
9
@Pointcut(
"execution(* soundsystem.CompactDisc.playTrack(int)) " +
"&& args(trackNumber)")
public void trackPlayed(int trackNumber) {}
@Before("trackPlayed(trackNumber)")
public void countTrack(int trackNumber) {
int currentCount = getPlayCount(trackNumber);
trackCounts.put(trackNumber, currentCount + 1);
}

또한 AOP에는 introductions 이라는 새로운 method를 붙일 수 있는 기능도 있다. @DeclareParents라는 어노테이션으로

1
2
3
4
5
6
@Aspect
public class EncoreableIntroducer {
@DeclareParents(value="concert.Performance+",
defaultImpl=DefaultEncoreable.class)
public static Encoreable encoreable;
}

스프링이 지원하지 않는 AspectJ의 기능이 필요하다면 AspectJ의 기능을 사용할 수 도 있다.

Spring In Action - Advanced Wiring

Chapter3 - Advanced Wiring

Env and profiles

환경에 따라 다른 configuration이 필요한 것은 흔한 요구사항임. 예를 들어 local에서는 embedded database를 사용하고 prod에서는 mysql을 사용하는 식으로 말이다.

스프링은 재빌드가 필요없는 runtime 솔루션을 제공한다.

1
@Profile("dev") // dev에서만 유효함.

그렇다면 어떻게 profile을 설정할 수 있지? 아래 property를 통해서 환경변수,web.xml, JVM System property 등으로 설정할 수 있음

spring.profiles.active vs spring.profiles.default (active가 우선순위가 높음)

test시 에는 아래처럼 원하는 프로파일을 복수개 혹은 단수로 지정할 수 있음.

1
@ActiveProfiles("dev") // active profile을 지정할 수 있게 하는 헐퍼 annotation

Conditional beans

1
@Conditional(SpecialCondition.class) // 조건이 맞을때만 bean생성

실제로는 @Profile도 @Conditional을 이용해서 구현되어 있다.

Addressing ambiguity

1
2
3
4
5
6
7
8
9
10
@Component
public class Cake implements Dessert{}

@Component
@Qualifier('icecream') //
public class IceCream implements Dessert{}

@Autowired
@Qualifier('icecream')
Dessert mine; // wiring with IceCream

Scoping beans

bean scope 이라는 개념이 있는데 default는 싱글톤임.

  • singleton
  • prototype : inject될때마다 빈 생성
  • session : session이 만들어질때 빈 생성
  • request : request가 만들어질때 빈 생성

session, request의 경우는 언제 생성을 해야 할지가 모호해지는데, 이것때문에 proxy 하는 객체를 둬서 그것으로 실제 콜이 일어날때 real bean에게 콜을 한다.

Value Injection

value값을 외부에서 받아오고 싶을때는 property를 사용하면 된다.

1
2
3
4
5
6
7
8
@Configuration
@PropertySource("classpath:/app.properties")
public class ExpressiveConfig {
@Autowired
Environment env;

// env.getProperty(key);
}

Spring Boot의 경우에는 이것도 자동으로 구성해주는데 src/main/resources에 있는 application[-env].properties을 찾는다.
fallback 순서는 아래 처럼 된다.

  • application-env.properties
  • application.properties

property를 읽어오는 순서를 여기에 명시하고 있으니 참고.

참고로 Spring boot의 경우가 훨씬 편함.

top K frequent items

hashmap + Heap

가장 간단한 형태로 구현할 수 있음. hashamp으로 key별로 frequency를 저장하고, Heap으로 top K 만 관리. space가 그다지 크지 않을때

top K in data stream

distribute 환경에서 stream으로 데이터가 들어온다고 하면 어떤 방법을 사용할 수 있을까

multi hashmap + heap

앞서 봤던 hashmap + Heap을 N개의 노드에 나누어서 각 노드에서 top K를 계산하고 있다가, 전체 top K를 계산할때는 각 노드의 K를 받아와서 머지를 한 후 최종 top K를 계산한다.

approximate algorithm

앞서 살펴본 것들은 모든 데이터의 frequency를 저장하고 있어야 하기 때문에 저장 공간의 낭비가 심하다. 그래서 근사 알고리즘들이 좀 나왔는데

count min sketch + heap

N개의 hash function을 만들어, data가 들어올때마다 N개의 해쉬펑션의 hash_vale의 frequency를 증가시켜줌.이것을 T[hash_func][hash_value] 라 함.
실제 val를 얻을때는 모든 T를 hash_func만큼 돌면서 얻은 값의 최소값을 반환함.

data의 존재여부를 판단하는 bloom filter와 아주 유사한데 그것에 count가 더해진 형태이다.

lossy counting

데이터 스트림을 특정 기간의 타임 윈도우로 분할을 해서, 그 윈도우에서 얻은 frequency -1를 해준다. -1을 해줌으로서 마이너한 데이터를 제거할 수 있게 된다.
그렇게 해서 전체 space를 줄일 수 있다.

reference

Spring In Action - Wiring Beans

Wiring beans

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에서도 다음과 같이 스캐닝을 지정해 줄 수 있다.

<context:component-scan base-package=”soundsystem” />

아래처럼 복수개의 패키지및 java config를 지정할 수 있음.

1
2
3
@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() {
return new SgtPeppers();
}

@Bean
public CDPlayer cdPlayer() {
return new CDPlayer(sgtPeppers());
}

@Bean
public CDPlayer anotherCDPlayer() {
return new 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
public class SoundSystemConfig {
}

반대로 xml에서도 java config를 import 할 수 있다.

1
2
3
4
<beans>
<bean class="soundsystem.CDConfig" />
<import resource="cdplayer-config.xml" />
</beans>

twitter architecture

http://highscalability.com/blog/2013/7/8/the-architecture-twitter-uses-to-deal-with-150m-active-users.html 여기 있는 글을 정리한 수준.

target TPS, read: 1000 tps write: 100 tps

data model

  • user
    • id
    • name
    • email
  • tweet
    • id
    • content
    • created
    • type: retweet|reply|
  • follower N:M mapping
    • 이것은 Flock이라는 Graph DB를 사용했다고 함. following, follower, block 등의 정보 저장.
    • uid
    • follow_uid

arch

  • db는 nosql(Dynamo DB, mongo DB)를 쓰거나, mysql을 sharding해서.. sharding은 timestamp로 샤딩.
  • 트위터는 write보다는 read요청이 훨씬 많으니 read에 최적화를 해야 함.
    • 대량의 redis cluster로 timeline을 캐쉬해 둔다.
    • 사용자가 트윗을 올리면, follower의 redis cache로 push해 준다. follwer가 많은 유명인의 경우 상당한 시간이 걸릴수 있다. 레이디 가가나 오바마 같은 경우
  • home timeline의 트윗은 최대 800개까지 redis에 저장

ranking

  • 기본적으로는 시간순으로 타임 라인을 구성할 수 있다.
  • 여기에 커멘트가 많거나 공유가 많은 트윗에 가산점을 줘서 점수로 소트하여 타임라인에 표시해 줄 수 있다
  • affinity, time, popularity의 팩터가 중요
  • 이러한 인풋대비 결과(stickyness)와 연동하여 A/B test후 계속 개선해 나가야 함. 이를 위해서는 analytics 역시 필요.

Publishing

  • pull vs push, read가 훨씬 많기 때문에 write시에 push해서 미리 사용자들의 타임라인을 만들어 두는 것이 유리
  • inactive 사용자나 트위터 홈, 서치 결과(query timeline)등은 pull based로 하고 있음.

monitoring

analytics

Reference