snetcat (Secure NetCat) 사용법

Reference

nc (netcat) 사용법 (윈도우용 nc 포함)

Secure netcat – http://snetcat.sourceforge.net/ 

옵션

usage: snetcat [OPTIONS] [<host> <port>]

-u              use UDP instead of TCP

-g              bind to INADDR_ANY

-o file         hexdump passed traffic to a file

-d Act as listening server, forking childs

-s certif Wrap the connection into SSL, using given certificate file

-l localport    listen on localport

-e prog         redirect stdin/stdout to program

 

사용

Fetch web page

% echo -e “GET /index.html HTTP/1.0\n\n” | snetcat www.yahoo.com 80

Fetch web page via HTTPS

% echo -e “GET /index.html HTTP/1.0\n\n” | snetcat -s “” www.yahoo.com 80

Run port forwarder

% snetcat -l local_port remote_host remote_port

Turn web server on your machine to be HTTPS web server

% snetcat -l 443 -s CERTIFICATE.PEM -d 127.0.0.1 80

Mail client (sh script)

sendmail

Simple web server (sh script)

httpd

 

 

>>>>> HTTP를 통해 페이지 가져오기

– 기본 페이지
echo -e “GET / HTTP/1.0\n\n” | snetcat www.stearns.org 80

HTTP/1.1 200 OK

Date: Wed, 14 May 2014 07:35:12 GMT

Server: Apache/2.2.3 (CentOS)

Last-Modified: Wed, 19 Mar 2008 20:10:46 GMT

ETag: “23c1a8-64ce-448cfd92edd80”

Accept-Ranges: bytes

Content-Length: 25806

Connection: close

Content-Type: text/html; charset=UTF-8

<html>

<head>

<title>Bill Stearns’ web site</title>

</head>

 

<body>

</body>

</html>

– 특정 페이지

echo -e “GET http://www.stearns.org/doc/nc-intro.v0.9.html HTTP/1.0\n\n” | snetcat www.stearns.org 80

HTTP/1.1 200 OK

Date: Wed, 14 May 2014 07:36:46 GMT

Server: Apache/2.2.3 (CentOS)

Last-Modified: Thu, 18 Dec 2003 20:31:26 GMT

ETag: “23c369-2ecf-3cec71b8a6380”

Accept-Ranges: bytes

Content-Length: 11983

Connection: close

Content-Type: text/html; charset=UTF-8

 

<html>

<head>

<title>Netcat – network connections made easy</title>

</head>

<body>

</body>

</html>

출처:  http://blog.naver.com/PostView.nhn?blogId=kiros33&logNo=130191150448

넷플러스 스핀에이커

http://netflix.github.io/#repo

https://github.com/spinnaker/spinnaker

넷플릭스가 아마존웹서비스(AWS), 구글 클라우드 플랫폼, 마이크로소프트(MS) 애저 등의 퍼블릭 클라우드 서비스를 통합해 관리할 수 있는 데브옵스(DevOps) 소프트웨어를 오픈소스로 공개했다.

16일(현지시간) 미국 지디넷에 따르면, 넷플릭스는 구글, MS, 피보탈 등과 공동 개발한 클라우드 자원 관리도구 ‘스핀에이커(Spinnaker)’를 오픈소스로 내놨다.

스핀에이커는 그동안 넷플릭스가 수년간 개발해 AWS 자원 관리에 사용해온 아스가르드를 대체한 소프트웨어다. 넷플릭스는 아스가르드 운영을 지난 9월 완전히 중단하고, 스핀에이커로 전면 전환했다.

넷플러스 SURO

https://medium.com/netflix-techblog/announcing-suro-backbone-of-netflixs-data-pipeline-5c660ca917b6

https://github.com/Netflix/suro

Announcing Suro: Backbone of Netflix’s Data Pipeline

by Jae Hyeon Bae, Danny Yuan, and Sudhir Tonse

To make the best business and technical decisions, it is critical for Netflix to reliably collect application specific data in a timely fashion. At Netflix we deploy a fairly large number of AWS EC2instances that host our web services and applications. They collectively emit more than 1.5 million events per second during peak hours, or around 80 billion events per day. The events could be log messages, user activity records, system operational data, or any arbitrary data that our systems need to collect for business, product, and operational analysis.

Given that data is critical to our operations and yet we allow applications to generate arbitrary events, our data pipeline infrastructure needs to be highly scalable, always available, and deliver events with minimal latency, which is measured as elapsed time between the moment when an event is emitted and when the event is available for consumption by its consumers. And yes, the data pipeline needs to be resilient to our own Simian Army, particularly the Chaos Monkeys.

While various web services and applications produce events to Suro, many kinds of consumers may process such data differently. For example, our Hadoop clusters run MapReduce jobs on the collected events to generate offline business reports. Our event stream clusters generate operational reports to reflect real-time trends. Since we may dispatch events to different consumers based on changing needs, our data pipeline also needs to be dynamically configurable.

Suro, which we are proud to announce as our latest offering as part of theNetflixOSS family, serves as the backbone of our data pipeline. It consists of a producer client, a collector server, and plugin framework that allows events to be dynamically filtered and dispatched to multiple consumers.

History of Suro

Suro has its roots in Apache Chukwa, which was initially adopted by Netflix. The current incarnation grew out of what we learned from meeting the operational requirements of running in production over the past few years. The following are notable modifications compared to Apache Chukwa:

  • Suro supports arbitrary data formats. Users can plug in their own serialization and deserialization code
  • Suro instruments many tagged monitoring metrics to make itself operations friendly
  • Suro integrates with NetflixOSS to be cloud friendly
  • Suro supports dispatching events to multiple destinations with dynamic configuration
  • Suro supports configurable store-and-forward on both client and collector

Overall Architecture

The figure below illustrates the overall architecture of Suro. It is the single data pipeline that collects events generated by Netflix applications running in either AWS cloud or Netflix data centers. Suro also dispatches events to multiple destinations for further processing.

Such arrangement supports two typical use cases: batched processing, and real-time computation.

Batch Processing

Many analytical reports are generated by Hadoop jobs. In fact, Suro was initially deployed just to collect data for our Big Data Platform team’s Hadoop clusters. For this, Suro aggregates data into Hadoop sequence files, and uploads them into designated S3 buckets. A distributed demuxing cluster demuxes the events in the S3 buckets to prepare them for further processing by Hadoop jobs. We hope to open source the demuxer in the next few months. Our Big Data Platform team has already open sourced pieces of our data infrastructure, such as Lipstick and Genie. Others will be coming soon. A previous post titled Hadoop Platform as a Service in the Cloudcovers this in detail:

Real-Time Computation

While offline/batch processing the events still form the bulk of our consumer use cases, the more recent trend has been in the area of real-time stream processing. Stream consumers are typically employed to generate instant feedback, exploratory analysis, and operational insights. Log Summaries of application-generated log data is an example that falls under this bucket. The following graph summarizes how log events flow from applications to two different classes of consumers.

  1. Applications emits events to Suro. The events include log lines.
  2. Suro dispatches all the events to S3 by default. Hadoop jobs will process these events.
  3. Based on a dynamically configurable routing rule, Suro also dispatches these log events to a designated Kafka cluster under a mapped topic.
  4. Druid cluster indexes the log lines on the fly, making them immediately available for querying. For example, our service automatically detects error surges for each application within a 10 minute window and sends out alerts to application owners:

5. Application owners can then go to Druid’s UI to explore such errors:

6. A customized ElasticSearch cluster also ingests the same sets of log lines, de-duplicates them, and makes them immediately available for querying. Users are able to jump from an aggregated view on the Druid UI to individual records on ElasticSearch’s Kibana UI to see exactly what went wrong.

Of course, this is just one example of making use of real-time analysis. We are also actively looking into various technologies such as Storm and Apache Samza to apply iterative machine learning algorithms on application events.

Suro Collector In Detail

The figure below zooms into the design of the Suro Collector service. The design is similar to SEDA. Events are processed asynchronously in stages. Events are offered to a queue first in each stage, and a pool of threads consume the events asynchronously from the queue, process them, and sends them off to the next stage.

The main processing flow is as follows:

  1. Client buffers events in a buffer called message set, and sends buffered messages to Suro Collector.
  2. Suro Collector takes each incoming message set, deflates it if possible, and immediately returns after handing the message set to Message Set Processor.
  3. The Message Set Processor puts the message set into a queue, and has a pool of Message Router threads that routes messages asynchronously.
  4. A Message Router determines which sink a message should go to. If there’s a filter configured for a message, the message payload will be deserialized.
  5. Each sink maintains its own queue, and sends messages to designated configurations asynchronously.

Performance Measurement

Here are some results from simple stress tests.

Test Setup

Test Results

The following table summarizes the test result after warm-up:

Suro Roadmap

The version open sourced today has the following components.

  • Suro Client
  • Suro Server
  • Kafka Sink and S3 Sink
  • Three Types of Message Filters

In the coming months, we will describe and open source other parts of the pipeline. We would love to collaborate with other solutions in the community in this domain and hope that Suro, Genie, Lipstick etc. provide some of the answers in this highly evolving and popular technology space.

Other Event Pipelines

Suro evolved over the past few years alongside many other powerful data pipeline solutions such as Apache Flume and Facebook Scribe. Suro has overlapping features with these systems. The strength of Suro is that it is well integrated into AWS and especially the ecosystem of NetflixOSS, to support Amazon Auto Scaling, Netflix Chaos Monkey, and dynamic dispatching of events based on user defined rules. In particular,

  • Suro client has built-in load balancer that is aware of Netflix Eureka, while Suro server also integrates with Netflix Eureka. Therefore, both Suro servers and applications that use Suro client can be auto scaled.
  • Suro server uses EBS and file-backed queues to minimize message loss during unexpected EC2 termination.
  • Suro server is able to push messages to arbitrary consumers at runtime with the help of Netflix Archaius. Users can declaratively configure Suro server at runtime to dispatch events to multiple destinations, such asApache Kafka, SQS, S3, and any HTTP endpoint. The dispatching can be done in either batches or real time.

Summary

Suro has been the backbone of the data pipeline at Netflix for a few years and has evolved to handle many of the typical use cases that any Big Data infrastructure aims to solve.

In this article, we have described the top level architecture, the use cases, and some of the components that form the overall data pipeline infrastructure at Netflix. We are happy to open source Suro and welcome inputs, comments and involvement from the open source community.

If building critical big data infrastructure is your interest and passion, please take a look at http://jobs.netflix.com.

Docker

출처:http://pyrasis.com/docker.html

목차

  1. Docker
    1. 가상 머신과 Docker
      1. 가상 머신
      2. Docker
      3. 리눅스 컨테이너
    2. Docker 이미지와 컨테이너
  2. Docker 설치하기
    1. 리눅스
      1. 자동 설치 스크립트
      2. 우분투
      3. RedHat Enterprise Linux, CentOS
      4. 최신 바이너리 사용하기
    2. Mac OS X
    3. Windows
  3. Docker 사용해보기
    1. search 명령으로 이미지 검색하기
    2. pull 명령으로 이미지 받기
    3. images 명령으로 이미지 목록 출력하기
    4. run 명령으로 컨테이너 생성하기
    5. ps 명령으로 컨테이너 목록 확인하기
    6. start 명령으로 컨테이너 시작하기
    7. restart 명령으로 컨테이너 재시작하기
    8. attach 명령으로 컨테이너에 접속하기
    9. exec 명령으로 외부에서 컨테이너 안의 명령 실행하기
    10. stop 명령으로 컨테이너 정지하기
    11. rm 명령으로 컨테이너 삭제하기
    12. rmi 명령으로 이미지 삭제하기
  4. Docker 이미지 생성하기
    1. Bash 익히기
    2. Dockerfile 작성하기
    3. build 명령으로 이미지 생성하기
  5. Docker 살펴보기
    1. history 명령으로 이미지 히스토리 살펴보기
    2. cp 명령으로 파일 꺼내기
    3. commit 명령으로 컨테이너의 변경사항을 이미지로 생성하기
    4. diff 명령으로 컨테이너에서 변경된 파일 확인하기
    5. inspect 명령으로 세부 정보 확인하기
  6. Docker 좀더 활용하기
    1. Docker 개인 저장소 구축하기
      1. 로컬에 이미지 데이터 저장
      2. push 명령으로 이미지 올리기
      3. Amazon S3에 이미지 데이터 저장
      4. 기본 인증 사용하기
    2. Docker 컨테이너 연결하기
    3. 다른 서버의 Docker 컨테이너에 연결하기
    4. Docker 데이터 볼륨 사용하기
    5. Docker 데이터 볼륨 컨테이너 사용하기
    6. Docker 베이스 이미지 생성하기
      1. 우분투 베이스 이미지 생성하기
      2. CentOS 베이스 이미지 생성하기
      3. 빈 베이스 이미지 생성하기
    7. Docker 안에서 Docker 실행하기
  7. Dockerfile 자세히 알아보기
    1. .dockerignore
    2. FROM
    3. MAINTAINER
    4. RUN
    5. CMD
    6. ENTRYPOINT
    7. EXPOSE
    8. ENV
    9. ADD
    10. COPY
    11. VOLUME
    12. USER
    13. WORKDIR
    14. ONBUILD
  8. Docker로 애플리케이션 배포하기
    1. 서버 한 대에 애플리케이션 배포하기
      1. 개발자 PC에서 Git 설치 및 저장소 생성하기
      2. 개발자 PC에서 Node.js로 웹 서버 작성하기
      3. 개발자 PC에서 Dockerfile 작성하기
      4. 개발자 PC에서 SSH키 생성하기
      5. 서버에 Git 설치 및 저장소 생성하기
      6. 서버에 Docker 설치하기
      7. 서버에 SSH 키 설정하기
      8. 서버에 Git Hook 설정하기
      9. 개발자 PC에서 소스 Push하기
    2. 서버 여러 대에 애플리케이션 배포하기
      1. 개발자 PC에서 Git 설치 및 저장소 생성하기
      2. 개발자 PC에서 Node.js로 웹 서버 작성하기
      3. 개발자 PC에서 Dockerfile 작성하기
      4. 개발자 PC에서 SSH키 생성하기
      5. 배포 서버에 Git 설치 및 저장소 생성하기
      6. 배포 서버에서 SSH 키 생성하기
      7. 배포 서버에 Docker 설치하기
      8. 배포 서버에 Docker 레지스트리 서버 설정하기
      9. 배포 서버에 SSH 키 설정하기
      10. 배포 서버에 Git Hook 설정하기
      11. 애플리케이션 서버에 Docker 설치하기
      12. 애플리케이션 서버에 SSH 키 설정하기
      13. 개발자 PC에서 소스 Push하기
  9. Docker 모니터링하기
    1. 모니터링 서버 Dockerfile 작성하기
    2. 애플리케이션 서버 Dockerfile 작성
    3. 웹 브라우저에서 그래프 확인
  10. Amazon Web Services에서 Docker 사용하기
    1. Amazon EC2에서 Docker 사용하기
    2. AWS Elastic Beanstalk에서 Docker 사용하기
      1. AWS 콘솔에서 Docker 애플리케이션 배포하기
      2. Docker Hub 공개 저장소 이미지 사용하기
      3. Docker Hub 개인 저장소 이미지 사용하기
      4. Git으로 Elastic Beanstalk Docker 애플리케이션 배포하기
  11. Google Cloud Platform에서 Docker 사용하기
    1. Google Cloud SDK 설정하기
    2. Compute Engine에서 Docker 사용하기
    3. Container Engine에서 Docker 사용하기
  12. Microsoft Azure에서 Docker 사용하기
  13. Docker Hub 사용하기
    1. Docker Hub 가입하기
    2. push 명령으로 이미지 올리기
    3. Docker Hub 개인 저장소 생성하기
    4. Docker Hub Automated Build 활용하기
  14. Docker Remote API 사용하기
    1. Docker Remote API Python 라이브러리 사용하기
      1. 컨테이너 생성 및 시작하기
      2. 이미지 생성하기
      3. 컨테이너 목록 출력하기
      4. 이미지 목록 출력하기
      5. 기타 예제 및 함수
    2. Docker Remote API Python 라이브러리로 HTTPS 통신하기
      1. 인증서 생성하기
      2. Python 라이브러리 사용하기
  15. CoreOS 사용하기
    1. VirtualBox에 CoreOS 설치하기
      1. systemd로 서비스 실행하기
    2. Vagrant로 CoreOS 설치하기
    3. etcd 사용하기
      1. etcd 키, 디렉터리 생성하기
      2. etcd 키, 디렉터리 목록 출력하기
      3. etcd 키, 디렉터리 자동 삭제 설정하기
      4. etcd 키 감시하기
      5. etcd 기타 명령
    4. fleet 사용하기
      1. fleet 머신 목록 출력하기
      2. fleet으로 유닛 실행하기
      3. fleet 유닛 목록 출력하기
      4. fleet 유닛 상태 확인하기
      5. fleet 자동 복구 확인하기
      6. fleet 전용 옵션 사용하기
      7. fleet 유닛 파일 템플릿 활용하기
      8. fleet 사이드킥 모델 활용하기
      9. fleet 기타 명령
    5. 클라우드 서비스에서 CoreOS 사용하기
      1. Amazon EC2에서 CoreOS 사용하기
      2. Google Compute Engine에서 CoreOS 사용하기
  16. Docker로 워드프레스 블로그 구축하기
    1. 워드프레스 Dockerfile 작성하기
    2. MySQL 데이터베이스 Dockerfile 작성하기
    3. 워드프레스와 데이터베이스 컨테이너 생성하기
  17. Docker로 Ruby on Rails 애플리케이션 구축하기
    1. Ruby와 Rails 설치하기
    2. Rails Dockerfile 작성하기
    3. PostgreSQL 데이터베이스 Dockerfile 작성하기
    4. Rails와 데이터베이스 컨테이너 생성하기
  18. Docker로 Django 애플리케이션 구축하기
    1. Django 설치하기
    2. Django Dockerfile 작성하기
    3. Oracle 데이터베이스 Dockerfile 작성하기
    4. Django와 데이터베이스 컨테이너 생성하기
  19. Docker 활용 시나리오
    1. 로드 밸런서와 연계한 확장 전개
    2. 개발, 테스트, 운영을 통합
    3. 손쉬운 서비스 이전
    4. 테스트 용도
  20. Docker 명령어 및 옵션 목록
    1. attach
    2. build
    3. commit
    4. cp
    5. create
    6. diff
    7. events
    8. exec
    9. export
    10. history
    11. images
    12. import
    13. info
    14. inspect
    15. kill
    16. load
    17. login
    18. logout
    19. logs
    20. port
    21. pause
    22. ps
    23. pull
    24. push
    25. restart
    26. rm
    27. rmi
    28. run
    29. save
    30. search
    31. start
    32. stop
    33. tag
    34. top
    35. unpause
    36. version
    37. wait
  21. 부록
    1. Docker 컴파일하기
    2. 우분투 한국 미러 사용하기
    3. 참고 사이트

예제 소스

저작권 정보

  • 가장 빨리 만나는 Docker(이하 ‘책’)의 저작권은 이재홍에게 있습니다.
  • 책의 출판권 및 배타적발행권과 전자책의 배타적전송권은 (주)도서출판 길벗에게 있습니다.
  • 책의 내용을 복제하여 블로그, 웹사이트 등에 게시할 수 없습니다.
    • 링크 및 SNS 공유는 허용합니다.
  • 책의 내용을 변경할 수 없습니다.
  • 책의 내용을 상업적으로 사용할 수 없습니다.
  • 책의 내용을 어떠한 형태로든 재배포할 수 없습니다.

[Apache Kafka] 1. 소개및 아키텍처 정리

Apache Kafka(아파치 카프카)는 LinkedIn에서 개발된 분산 메시징 시스템으로써 2011년에 오픈소스로 공개되었다. 대용량의 실시간 로그처리에 특화된 아키텍처 설계를 통하여 기존 메시징 시스템보다 우수한 TPS를 보여주고 있다.

이 글은 Apache Kafka 공식페이지의 0.8.1 문서와 2011년에 NetDB에 출판된 논문(Kafka: A distributed messaging system for log processing)을 기반으로 작성하였다. (글 작성 시점인 2015.03.09를 기준으로 0.8.2.0이 최신 버전이지만 아직 출시된 지 한 달 남짓 밖에 되지 않으므로 0.8.1.1을 기준으로 작성하였다.)

Kafka의 기본 구성 요소와 동작

Kafka는 발행-구독(publish-subscribe) 모델을 기반으로 동작하며 크게 producer, consumer, broker로 구성된다.

(이미지 출처: Apache Kafka 0.8.1 Documentation)

Kafka의 broker는 topic을 기준으로 메시지를 관리한다. Producer는 특정 topic의 메시지를 생성한 뒤 해당 메시지를 broker에 전달한다. Broker가 전달받은 메시지를 topic별로 분류하여 쌓아놓으면, 해당 topic을 구독하는 consumer들이 메시지를 가져가서 처리하게 된다.

Kafka는 확장성(scale-out)과 고가용성(high availability)을 위하여 broker들이 클러스터로 구성되어 동작하도록 설계되어있다. 심지어 broker가 1개 밖에 없을 때에도 클러스터로써 동작한다. 클러스터 내의 broker에 대한 분산 처리는 아래의 그림과 같이 Apache ZooKeeper가 담당한다.

출처: http://epicdevs.com/17 [Epic Developer]

Apache Solr vs Elasticsearch

http://solr-vs-elasticsearch.com/

Apache Solr vs Elasticsearch

The Feature Smackdown


API

Feature Solr 6.2.1 ElasticSearch 5.0
Format XML, CSV, JSON JSON
HTTP REST API
Binary API SolrJ TransportClient, Thrift (through a plugin)
JMX support ES specific stats are exposed through the REST API
Official client libraries Java Java, Groovy, PHP, Ruby, Perl, Python, .NET, JavascriptOfficial list of clients
Community client libraries PHP, Ruby, Perl, Scala, Python, .NET, Javascript, Go, Erlang, Clojure Clojure, Cold Fusion, Erlang, Go, Groovy, Haskell, Java, JavaScript, .NET, OCaml, Perl, PHP, Python, R, Ruby, Scala, Smalltalk, Vert.x Complete list
3rd-party product integration (open-source) Drupal, Magento, Django, ColdFusion, WordPress, OpenCMS, Plone, Typo3, ez Publish, Symfony2, Riak (via Yokozuna) Drupal, Django, Symfony2, WordPress, CouchBase
3rd-party product integration (commercial) DataStax Enterprise Search, Cloudera Search, Hortonworks Data Platform, MapR SearchBlox, Hortonworks Data Platform, MapR etcComplete list
Output JSON, XML, PHP, Python, Ruby, CSV, Velocity, XSLT, native Java JSON, XML/HTML (via plugin)

Infrastructure

Feature Solr 6.2.1 ElasticSearch 5.0
Master-slave replication Only in non-SolrCloud. In SolrCloud, behaves identically to ES. Not an issue because shards are replicated across nodes.
Integrated snapshot and restore Filesystem Filesystem, AWS Cloud Plugin for S3 repositories, HDFS Plugin for Hadoop environments, Azure Cloud Plugin for Azure storage repositories

Scaling Logstash

http://devquixote.com/devops/2014/10/20/scaling-logstash/

 

로그 축소 비율 조정
나는 최근에 운송 집계 (shippingEasy) 에서 개발 집 (dev ops) 모자를 써야만 로그 집계와 운영 분석을 제공하는 ELK 스택을 설치해야했습니다. 즉 Elasticsearch , Logstash &키바 . 우리는 인프라 스트럭처에 상당히 의존하게되었습니다. 이는 에스컬레이션 지원을 통해 개발자가 참여하도록 지시 할 때 상황이 어떻게 진행되고 있는지 파악하고 프로덕션 문제를 파악할 수 있기 때문입니다. 다음은 평균 응답 시간, 유니콘 작업자 및 대기열 크기와 같은 측정 항목을 보여주는 제작 웹 대시 보드의보기입니다.

이것이 원래 설정되었을 때 각 어플리케이션 서버의 Logstash-Forwarder 로 로그 이벤트를 전달한 Logstash로 전달하여 Elasticsearch로 색인을 생성했습니다. 그런 다음 Kibana를 사용하여 로그 이벤트를 시각화 할 수 있습니다. 이것은 다음과 같은 전형적인 (아마도 순진) 설정입니다 :

logstash-forwarder
\
logstash-forwarder > logstash > elasticsearch < kibana
/
logstash-forwarder
Rails 스택에 대한 뷰를 얻으려면 Elasticsearch에서 사용자 정의 패턴이있는 다중 행 및 grok 필터를 사용하여 로그 파일을 구문 분석했습니다. 우리는 각각의 유니콘 프로세스가 자신의 번호가 매겨진 로그 파일에 기록되도록함으로써 서로 인터리브하는 로그 이벤트를 얻었습니다. 잠시 동안은 잘 돌아 갔지만 트래픽이 휴가를 사기 시작하면서 문제가 발생하기 시작했습니다. 잠시 동안 일할 것이지만 사건의 틈새가 키바 나에 나타나기 시작할 것이고, 물방울이 늦어지고 결국 멈출 것입니다.

고맙게도, 우리의 응용 프로그램이 죽어 가고있는 것은 아닙니다. Logstash가있었습니다.우리는 두 가지 문제로 악화되고 서로를 가려 냈습니다.

Logstash는 우리가 전송 한 모든 로그 이벤트를 처리해야한다는 요구를 따라갈 수 없었습니다.
Logstash 1.4.1-2에는 TCP 입력에 버그 가있어 연결하는 클라이언트가 이전 문제로 인해 시간 초과되기 시작하면 연결 누출이 발생합니다.
Logstash의 버전을 연결 블룸 문제를 수정하는 최신 코드로 패치하여 2 번째 문제를 수정했습니다. 이를 정리하면 Logstash 내의 병목 현상을 확인할 수 있습니다.

Logstash는 jRuby로 작성되었으며 내부는 파이프 라인 으로 설명됩니다 . 매개 변수는 구성의 입력 / 필터 / 출력 스탠자에 설정된 작업을 수행하는 입력, 필터 (작업자) 및 출력 스레드에 의해 처리됩니다. 각 영역에는 20 개의 요소를 저장할 수있는 대기열이 있습니다. 스레드는 대기열에서 당겨서 작업을하고 다음 대기열로 보내거나 반복합니다.Logstash는 각 입력에 하나의 스레드를 할당하고, 단일 작업자 스레드와 각 출력에 대해 하나의 스레드를 할당합니다. 이것은 다음과 같이 보입니다.

input source –> input thread filter thread output thread –> output destination
\ / \ /
queue queue
/ \ / \
input source –> input thread filter thread output thread –> output destination
Logstash의 이러한 영역 중 하나가 채워진 것보다 빠르게 대기열에서 가져올 수없는 경우 문제가 발생합니다. Logstash는 시스템이 백업 할 때 입력 내용에 따라 다양한 효과를 제공합니다. 우리의 경우 Logstash-Forwarder 연결 시간 초과 및 후속 연결 재 연결 시도가 누출되었습니다. Logstash가 redis 목록에서 대기열로 당겨지면 대기열이 팽창합니다.

상단 및 java 스레드 덤프 조합을 사용하여 병목 현상이 필터 작업자 스레드에 있음을 알 수있었습니다. 입력 스레드와 출력 스레드는 CPU 사용량이 거의 없었으며 빈 대기열에서 항상 차단되는 것으로 보입니다. 그러나 필터 작업자 스레드는 CPU 코어를 페깅하고있었습니다. Logstash 배포에서 작업자 스레드의 수를 늘릴 수 있습니다.

잘못된. 이전에 언급 한 여러 줄 바꿈 필터링을 기억하십니까? Logstash의 다중 라인 필터는 쓰레드에 안전하지 않으며 사용시에는 오직 하나의 작업자 쓰레드로만 제한됩니다.이제 다중 라인 코덱을 사용하여 다중 라인 이벤트 콜렉션을 Logstash의 입력 영역으로 이동하기 만하면됩니다. 아니, 그 중 하나를 작동하지 않습니다. 다중 선 필터를 사용하면 파일 이름별로 이벤트를 구분하는 데 사용할 수있는 stream_identity 속성을 지정할 수 있습니다. 다중 라인 입력 코덱은 그런 것을 제공하지 않습니다. 이는 레일을 서로 분리 된 여러 줄의 로그 메시지를 유지하는 모든 노력이 창 밖으로 나올 수 있음을 의미합니다.

이제 우리는 뒤로 물러나서 인프라를 재평가해야했습니다. 궁극적으로 다음을 수행하기로 결정했습니다.

다중 회선 이벤트가 응용 프로그램 서버 측에서 롤업됩니까? 이것은 로그를 꼬리로 묶어서 Logstash로 보내야하는 책임입니다. 그런 다음 Logstash에서 다중 라인 필터를 척킹하고 단일 Logstash 프로세스 내에서 필터 작업자를 확장 할 수 있습니다.
테일링 데몬 app-server 측과 Logstash 사이의 중개인으로 재발행 목록을 사용하면 몇 가지 이벤트 내구성을 가질 수 있고 로그 데이터를 통해 여러 대의 시스템에서 여러 개의 Logstash 프로세스로 확장 할 수 있습니다.
Logstash 전달자는 다중 회선 이벤트 롤업이나 redis와의 통신을 지원하지 않으므로 다른 꼬리표 디먼을 찾아야하거나 Logstash 자체를 각 응용 프로그램 서버에 배포해야했습니다. 우리는 자바 의존성을 도입하고 완료해야 할 일에 매우 무거워 보였으므로 후자를하고 싶지 않았습니다.

위의 요구 사항을 모두 지원하는 파이썬으로 작성된 로그 테일링 데몬 인 Beaver를 입력하십시오 . 우리는 그것이 작동하는지 확인하기 위한 간단한 개념 증명을 수행하여 하나의 웹 서버에 배포하여 24 시간 동안 수행 한 방법을 확인한 다음 모든 서버에 적용했습니다.서비스 중단없이 며칠 동안 제대로 작동합니다. 이제 우리의 인프라는 다음과 같습니다.

beaver
\
beaver > redis < logstash > elasticsearch < kibana
/
beaver
Logstash에서 여러 스레드 / 코어를 사용하여 필터 처리를 수행 할 수있는 응용 프로그램 서버에서 Beaver에 대한 다중 라인 롤업 작업을 수행 한 후에도 Logstash 인스턴스 하나만 있으면 충분합니다. 그러나 로그 트래픽 / 크기를 다시 늘려 Logstash를 압도하기 시작하면 우리는 여러 인스턴스로 확장하여 데이터가 다시 시작되도록하는 것이 좋습니다.

beaver logstash
\ / \
beaver > redis < > elasticsearch < kibana
/ \ /
beaver logstash
그것은 Logstash-scale 땅에서 3 – 4 일을 보내었다. 그것은 우리의 응용 프로그램 사용자에게 양질의 경험을 제공하는 데 정말로 도움이되는 놀라운 도구입니다. ELK 스택의 일부로 Splunk 의 80 %가 실제로 무료입니다. 그러나 유료 라이센스가 없으면 소매를 감아 서 이와 같은 경우에 일해야합니다. 다행히도 그 뒤에 큰 커뮤니티가 있으며 웹과 #logstash의 freenode에서 많은 도움을 얻을 수 있습니다.

Redis 데이터 모델링

출처:https://www.joinc.co.kr/w/man/12/REDIS/DataModeling#s-1.10.

목차

1. 데이터 모델들

1.1. Message Box

메신저 서비스를 개발하고 있다. 메시지를 보냈는데, 수신 대상이 연결하지 않은 상태일 수도 있다. 이 경우에 메시지는 메시지 함(Message Box)에 저장하기로 했다. 유저가 연결하면, 메시지 함에서 메시지를 읽어온다.메시지 함의 크기는 메시지에 대한 정책에 따라 달라질 수 있다. 나는 “모든 메시지는 중요하다.”는 관점에서 기능을 구현하기로 했다. REDIS는 메모리 기반 데이터베이스이다. 무한정 REDIS에 데이터를 쌓을 수는 없는 노릇이다. 그래서 REDIS에는 최근 도착한 메시지 N개를 저장하고, 그 보다 오랜 메시지는 공간 제약이 없는 다른 영역(예컨데 RDBMS)에 저장하기로 했다.Capped List는 REDIS의 LPUSH와 LTRIM를 이용해서 구현하기로 했다.LPUSH를 수행하면, 리스트의 크기를 반환한다. 반환값이 유저에게 허용한 메시지함 크기 보다 크면, TRIM 연산을 한다.

1
2
3
4
5
6
USER_MAX_MSGBOX_SIZE = 100
list_size = LPUSH message.box.user:1 message
if (n = (list_size – USER_MAX_MSGBOX_SIZE)) > 0
LRANGE message.box.user:1 -n -1 # TRIM .
LTRIM message.box.user:1 0 n
end

작동은 하겠는데, 일단 메시지 박스가 가득차고나면 메시지가 들어올 때마다 “가장 오래된 메시지 저장”->”LTRIM” 연산을 해야 하는 문제점이 있다. 이 문제는 메시지 함 크기에 버퍼를 두는 것으로 해결할 수 있겠다. 메시지 함 의 최대 크기기 100이라면 100 * 2 만큼 메시지를 받는다. 100 * 2가 가득 차면, “가장 오래된 100개의 메시지 저장”->”LTRIM” 하면 효율적으로 운용할 수 있겠다.Capped List 기능을 가진 Message Box의 전체 구성이다.유저가 접근하면, 메시지 함에 있는 메시지를 전부 보여주고 TRIM을 수행한다. 메시지 함을 초과해서 다른 저장소에 저장된 메시지들은 더 보기 명령을 이용해서 네비게이션 할 수 있도록 하면 되겠다. 메시지는 휘발성 이므로 클라이언트에 전달된 메시지는 서버에서 무조건 삭제한다.

1.2. Item 별 방문 카운트

item(상품)별 방문 카운트는 관리자에게는 웹 서비스 최적화를 위한 정보를 제공해 준다. 이 정보들을 꾸준히 저장하면, 고객의 동선과 구매 패턴을 파악하는 기초자료로 사용할 수 있다. 유저별로 page 방문 기록을 저장할 수도 있는데, 이 기록을 이용하면 유저의 기호를 기반으로 하는 추천 시스템 개발에 응용할 수 있을 것이다.

1
> INCR item:item-id

이렇게 하면 item 단위로 count를 할 수 있을 테다. 하지만 이런 류의 데이터는 시계열(time series)이 되어야 쓸만한 정보를 뽑아낼 수 있다. 시계열 데이터를 다룰 때는, 시간 해상도를 결정해야 한다. 하루 단위로 데이터를 저장한다면, 주,월,분기(계절별),년 단위의 유용한 정보를 뽑을 수 있겠지만 “출,퇴근,업무시간,식시시간”등 시간대 별 통계를 얻기는 힘들다. 추천시스템을 만들 경우에도 시간정보가 있어야 정밀한 추천이 가능할 거다. 간단한 예로 통닭을 아침 9시에 추천하는 건 좀 이상하지 않겠는가 ?. 통닭은 좀 너무 예상하기 쉽다면. 음악은 어떤가 ? 아침에 일어나서 듣는 음악과 업무시간에 듣는 음악 저녁시간에 듣는 음악에도 장르별 차이를 예상할 수 있다.일간 카운트 저장 예제다.

1
2
3
4
> INCR item.item-0:20141225
> INCR item.item-1:20141225
> INCR item.item-2:20141225
> INCR item.item-0:20141225

이 key들은 하루동안만 count가 되니, 해당 일이 지난 뒤에서는 파일기반 데이터베이스로 옮기면 되겠다.해상도를 시간단위로 높인다고 가정해도, key 갯수가 * 24가 될 뿐 위의 방식과 달라질 것은 없다.

1
2
3
> INCR item.item-0:2014122511
> INCR item.item-1:2014122511
> INCR item.item-2:2014122512

Item 별 방문 카운트를 약간 응용해보자. A회사는 음악 서비스를 운영하고 있는데, 유저에게 음악을 추천하는 서비스를 만들기로 했다. 유저는 관심있는 음악 페이지를 방문한다고 가정할 수 있다. A사는 각 음악에 장르를 태깅한 다음, 유저가 방문할 때마다 장르에 대해서 카운팅을 하기로 했다. 어느 정도 데이터가 모이면, 이 데이터를 근거로 유저가 좋아할 만한 음악을 추천할 수 있을 거다(정밀한 추천 알고리즘을 돌리려면 다른 데이터들드 좀더 필요하겠지만, 단순화 하기로 했다.). 1부터 10까지 열개의 카테고리로 나눴다. 50은 유저 ID다.

1
2
3
> INCR user.category:50.0
> INCR user.category:50.1
> INCR user.category:50.1

하지만 이 방법이 괜찮을지 확신이 서질 않는다. 천만명의 Active한 유저를 관리해야 하면, key만 1억이다. 샤딩을 해서 키를 분산하는 방법도 있겠는데, 어쨋든 키가 많아지는게 불만이다. REDIS는 separate chainning hash를 사용해서 키가 늘어나더라도 성능을 유지할 수 있다. 메모리를 더 사용해야 한다는 점이 맘에 걸리기는 하지만 큰 문제는 없을 것이다.메모리를 아껴보자는 생각에서 SETRANGE를 사용해 보기로 했다. User 마다 10byte의 고정된 공간을 제공하고, 이 공간에 count 하는 방식이다. 유저 ID는 고정된 integer 값이므로 유저의 카운트 정보가 저장된 위치(offset)을 구할 수 있다.

1
OFFSET = USER_ID * 10

이제 장르별로 key를 만들면 된다. 최대 1000명의 유저가 있다고 가정한다면, 1000 * 10 크기의 key를 만들면 된다.

1
2
3
4
> SETRANGE music.category:0 music.category:0 10000 0
> SETRANGE music.category:1 music.category:0 10000 0
> SETRANGE music.category:2 music.category:0 10000 0
….

Ruby 프로그램을 이용해서 테스트를 해보기로 했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
require redis
# 10 .
# 10 99 ?
FIELD_SIZE = 10
class Counter
@redis = nil
def initialize
@redis = Redis.new(:host=>192.168.57.2)
end
def incr args
offset = args[:user_id] * FIELD_SIZE
key = music.category:#{args[:category]}
count = @redis.getrange(key, offset, offset + FIELD_SIZE 1)
@redis.setrange(key, offset, count.to_i+1)
end
end
user_id = 10
counter = Counter.new
counter.incr :user_id=>user_id, :category=>0

시간 복잡도가 O(1)이다. GET, SET 연산의 복잡도도 O(1)이니, 이 정도면 쓸만하지 싶다. string의 최대 크기는 512M 이고 유저 한명이 차지하는 공간이 10byte라면, key 하나에 5천만 정도의 유저 count를 저장할 수 있다.

1.3. API 호출 제한

OpenAPI 서비스를 하다보면, 일정시간에 호출할 수 있는 최대 API 갯수에 제한을 걸어야 할 때가 있다.간단하게 유저 ID와 날짜를 조합해서 key로 만들고, 이 Key 에 대해서 INCR 하는 방법이 있다. INCR 연산 후에 값(count)을 반환한다. 이 반환 값을 허용한 최대 크기와 비교하면 된다.

1
2
> INCR apicall.user01:20141212
“1812”

일 단위로 카운팅을 하니, 사용하지 않는 키는 배치작업을 이용해서 주기적으로 삭제해야 한다는 귀찮음이 있다.EXPIRE와 조합하는 것으로 이 문제를 해결 할 수 있다. EXPIRE한 key에 대해서 set, getset을 적용하면, EXPIRE 값이 사라진다는 걸 알고 있을 것이다. INCL 연산 대해서는 EXPIRE가 사라지지 않는다.

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
39
40
key = apicall.#{user_id}:counter
#
# expire .
if !@redis.exists key
@redis.incr key
@redis.expire key, 3600 * 24
end
count = @redis.incr key
# , false .
# API .
# TTL Key ,
# api .
if count.to_i > 10000
return false
end
ttl = @redis.ttl(key)
puts key TTL : #{ttl}
return true
end
def call name
puts API CALL : #{name}
end
end
apimgr = OpenAPIManager.new
if apimgr.call? 2
apimgr.call /test
else
puts OpenAPI Call ERROR
end

문제 없이 작동할 거다. 하지만 카운팅 데이터에 따라서 자동으로 삭제할 필요가 있는지는 고민을 할 필요가 있다. 이런 류의 카운팅 정보는 어딘가에 저장해두고 분석할 필요가 있기 때문이다. 분석을 위해서 데이터를 남겨야 한다면, 첫 번째 방법을 사용해야 할 거다.키가 많아지는 경우를 고민해야 할 수도 있겠는데, 일반적으로 “개발자 등록을 마친 유저”에 대해서 API 호출을 허용할 테니, 크게 걱정하지 않아도 될 것 같다.

1.4. Tag 검색

책 판매 사이트에 Tag 기반 검색을 추가하기로 했다. 아래와 같이 책 정보를 만들고, SADD 명령으로 tag 정보를 만들었다.

1
2
3
4
5
6
7
8
9
10
11
> SET book:1 “{‘title’ : ‘Diving into Python’, ‘author’: ‘Mark Pilgrim’}”
> SET book:2 “{‘title’ : ‘Programing Erlang’, ‘author’: ‘Joe Armstrong’}”
> SET book:3 “{‘title’ : ‘Programing in Haskell’, ‘author’: ‘Graham Hutton’}”
> SADD tag:python 1
> SADD tag:erlang 2
> SADD tag:haskell 3
> SADD tag:programming 1 2 3
> SADD tag:computing 1 2 3
> SADD tag:distributedcomputing 2
> SADD tag:FP 2 3

REDIS는 SINTER(교집합), SUNION(합집합), SDIFF(차집합)의 집합연산 명령을 제공한다. 이들 명령을 이용해서 Tag 검색을 수행할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
require redis
redis = Redis.new(:host=>192.168.56.5)
# 0 results
redis.sinter(tag:erlang, tag:haskell) do | book |
end
# 3 results
redis.sinter(tag:programming, tag:computing).each do | book |
puts redis.get(book:#{book})
end
# 2 result
redis.sunion(tag:erlang, tag:haskell).each do | book |
puts redis.get(book:#{book})
end
# 2 result
redis.sdiff(tag:programming, tag:haskell).each do | book |
puts redis.get(book:#{book})
end

1.5. Log aggregation

시스템에서 발생하는 로그들을 중앙에 저장해서 관리하려고 한다.각 노드는 LPUSH로 밀어 넣고, 처리하는 쪽(로컬 파일 혹은 데이터베이스에 쌓는)에서는 BRPOP로 꺼낸다.

1
2
3
4
5
6
7
require ‘redis’
redis = Redis.new(:host=>’192.168.56.5′, :port=>6379)
loop do
item = redis.brpop(‘logging’, 0)
puts item[1]
end

REDIS PUB/SUB로도 구현할 수 있지 않을까 ? 라는 생각을 해봤지만 Subscribe가 뻗어버려서 메시지를 소비할 녀석이 없을 경우 메시지를 버려버리는 문제 때문에 사용하기 힘들 것 같다.

1.6. Pub/Sub Communication

Pub/Sub 시스템으로도 사용할 수 있다.

1
2
> SUBSCRIBE log
> PUBLISH log Hellow

PUB 메시지는 큐등에 쌓이지 않는다. Subscriber가 없다면, 메시지를 잃어 버릴 수 있다는 의미다. 따라서 PUB/SUB 시스템은 분실되도 큰 상관 없는 곳에 사용해야 한다. 예를 들어 서버 점검을 클라이언트에게 알려주기 위한 알람 용도로의 사용이다. WoW(World of warcraft)의 경우 서버 점검 1시간 전 10 분전 5분전에 알람 메시지를 줘서 유저가 미리 대비할 수 있게 한다.

1.7. shopping cart 관리

쇼핑 카트를 관리해 보자. 이 서비스의 기능요소들은 아래와 같다.

  1. 유저는 쇼핑 카트를 만들 수 있다.
  2. 쇼핑카트에는 하나 이상의 상품을 담을 수 있다.
  3. 상품을 빼는 것도 가능하다.
  4. 상품은 하나 이상 담을 수 있다.

카트를 식별할 ID를 가져야 할 건데, 유저 ID로 만들기로 했다. 유저는 하나의 카트만 가질 수 있다는 제한이 걸리는데, 별 상관 없을 것 같다.카트에 담은 상품관리

UserID ProductID Qty
1 28 1
1 372 2
2 15 1
2 160 5
2 201 7

UserID를 Key로 한 값에 ProductID:Qty 형식의 key/value의 리스트를 저장해야 한다. REDIS의 Hash를 이용하면 되겠다.카트 테이블 정보를 Redis에 밀어 넣었다.

1
2
3
4
5
> HSET cart.user:1 28 1
> HSET cart.user:1 372 2
> HSET cart.user:2 15 1
> HSET cart.user:2 160 5
> HSET cart.user:2 201 7

테스트

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
class ShoppingChart
@redis = nil
def initialize
@redis = Redis.new(:host=>192.168.56.5, :port=>6379)
end
def allItem userid
puts HGET ALL ITEM ====
puts %10s : %s % [Product,Qty]
@redis.hgetall(cart.user:1).each do | field, value |
puts %10s : %s % [field, value]
end
end
def removeItem userid, productId
@redis.hdel cart.user:#{userid}, productId
end
def addItem userid, productId, qty
@redis.hset cart.user:#{userid}, productId, qty
end
end
cart = ShoppingChart.new
cart.allItem 1
cart.removeItem 1, 28
cart.allItem 1
cart.addItem 1, 1280, 5
cart.addItem 1, 1312, 2
cart.allItem 1

웹 애플리케이션의 경우 유저 세션이 만료되는 시점을 알 수 없다. 다음번 방문했을 때, 이전 카트가 보인다거나 하는 문제도 있고, 메모리를 낭비하는 문제도 있으니, 카트 만료시간을 정해줘야 한다. 간단하게 EXPIRE를 이용해서 만료시간을 정하자. 카트 메서드를 호출 할 때마다, 만료시간을 재설정 하면 되겠다.

1.8. Atomic Get and Delete

GET과 DELETE를 원자적으로(atomically) 수행하고 싶다면, MULTI-EXEC 명령을 이용하면 되겠다.

1
2
3
4
5
6
7
8
9
> SET toto 1
> MULTI
> GET toto
QUEUED
> DEL toto
QUEUED
> EXEC
1) “1”
2) (integer) 1

1.9. Simple social graph

친구의 친구의 친구의 친구의 친구.. 관계를 그래프로 표현하면 소셜 그래프라고 된다고 하더라. 이 관계는 follows와 followers, block의 리스트로 정의 할 수 있다. 아래와 같은 소셜 그래프를 REDIS에 저장해 보자.유저 “1”을 중심으로 소셜 그래프를 만들었다. 화살표는 팔로잉의 방향이다. 1과 2 관계에서 2는 1의 팔로워다.(2는 1을 팔로잉 하고 있다.) 1과 3은 서로 팔로잉한 “친구” 관계다. 4와 5는 아는 사람 정도가 되겠다. 4는 아는 사람이 둘이고 5는 아는 사람이 한명이다. SET을 이용해서 데이터를 구성했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1 2
> SADD user.follower:1 2
> SADD user.following:2 1
# 1 3
> SADD user.friend:1 3
> SADD user.friend:3 1
# 3 4
> SADD user.following:3 4
> SADD user.follower:4 3
# 2 4
> SADD user.following:2 4
> SADD user.follower:4 2
# 3 5
> SADD user.following:3 5
> SADD user.follower:5 3

테스트 결과

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
# 4
> SMEMBERS user.follower:4
1) “2”
2) “3”
# 1
> SMEMBERS user.follower:1
1) “2”
# 1
> SMEMBERS user.friend:1
1) “3”
# 1 3 , 3
# , , .
> MULTI
> SMEMBERS user.follower:3
QUEUED
> SMEMBERS user.following:3
QUEUED
> SMEMBERS user.friend:3
> EXEC
1) (empty list or set)
2) 1) “4”
2) “5”
3) 1) “1”

이 데이터 모델은 유저간의 친밀도(weight – 가중치)를 알 수 없다는 문제가 있다. 소셜 네트워크 기반의 서비스를 하려면 유저간의 친밀도 값을 함께 가지고 있어야 한다. 가중치를 적용한 관계 그래프는 아래와 같을 것이다.유저 1에게 다른 유저를 추천한다고 가정해 보자. 유력한 대상은 4, 5, 6 이다. 가중치가 없다면 무작위로 추천을 해야 겠지만, 가중치가 있다면 어떤 유저를 우선 추천해야 하는지 계산할 수 있을 거다.유저간 각 관계에는 숫자로 가중치가 있는데, 경로에 있는 가중치를 모두 더해서 값이 큰 녀석을 추천하는 알고리즘을 만들기로 했다. 친구의 경우 쌍방향인데, 이 값을 모두 더하기로 했다.(친한 친구의 아는 사람이라면 당연히 가중치가 더 높을 것이다.)

  • 1 에서 5 : (3 + 3) + 4 = 10
  • 1 에서 4 : 2 + 1 + ( 3 + 3 ) + 2 = 11. 4는 2와 3 모두가 알고 있다. 따라서 모든 경로의 값을 더해서 가중치를 높게 잡았다.
  • 1 에서 6 : 2 + 1 = 3

이 알고리즘에 따르면 4, 5, 6 순서로 추천을 하면 된다는 결과가 나왔다. 실제로는 경로를 구성하는 Hop의 갯수를 포함하는 알고리즘을 개발해야 겠지만, 여기에서는 그냥 단순한 알고리즘을 사용한다. (소셜 그래프에서 추천 알고리즘은 연구해 볼만한 가치가 있겠다.)이제 가중치를 어떻게 저장할 것이냐 하는 이슈가 있다. 가중치를 별도의 키로 저장하는 방법을 생각해 볼 수 있겠다. 유저 1을 중심으로 할 경우 아래와 같이 저장한다.

1
2
3
> SADD route.weight:1.2 2
> SADD route.weight:1.3 6
> SADD route.weight:1.5 5

간단하긴 하지만, 유저가 많아지고 관계가 촘촘해 질 수록 데이터의 양이 기하 급수적으로 늘어난 다는 단점이 있다. 그냥 관계 그래프를 만들 때, 값에 가중치를 포함하면 된다.

1
2
3
4
5
6
7
# 1 2
> SADD user.follower:1 “[2, 2]”
> SADD user.following:2 “[1, 2]”
# 1 3
> SADD user.friend:1 “[3, 6]”
> SADD user.friend:3 “[1, 6]”

소수점을 이용해서 가중치를 저장하는 방법도 있다.

1
2
3
4
5
6
7
# 1 2
> SADD user.follower:1 2.2
> SADD user.following:2 1.2
# 1 3
> SADD user.friend:1 3.6
> SADD user.friend:3 1.6

가중치가 1을 넘지만 않게 조절한다면, 첫 번째 방법보다 효율적으로 동작할 거다. 가중치외에 다른 부가적인 정보를 넣고 싶다면 첫번째 방법을 사용하면 된다.ZADD를 이용해서 구현 할 수도 있다. ZADD는 “정렬을 위한 추가적인 값이 필요” 하다는 것을 제외하면 SADD와 동일하다. ZADD로 데이터를 다시 만들어봤다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1 2
> ZADD user.follower:1 2 2
> ZADD user.following:2 2 1
# 1 3
> ZADD user.friend:1 6 3
> ZADD user.friend:3 6 1
# 3 4
> ZADD user.following:3 2 4
> ZADD user.follower:4 2 3
# 2 4
> ZADD user.following:2 1 4
> ZADD user.follower:4 1 2
# 3 5
> ZADD user.following:3 4 5
> ZADD user.follower:5 4 3

테스트 코드

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
require redis
class Friend
@id = nil
@redis = nil
def initialize id
@redis = Redis.new(:host=>192.168.56.5)
@id = id
end
def follower
@redis.zrange user.follower:#{@id}, 0, -1, :with_scores => true
end
def following
@redis.zrange user.following:#{@id}, 0, -1, :with_scores => true
end
def friend
@redis.zrange user.friend:#{@id}, 0, -1, :with_scores => true
end
end
id = ARGV[0]
my = Friend.new id
my.follower.each do | v |
puts #{v[0]} : #{v[1]}
end
my.following.each do | v |
puts #{v[0]} : #{v[1]}

가중치는 아래의 서비스에 이용할 수 있을 거다.

  • 이웃(친구의 친구) 추천 : 높은 가중치를 가지는 친구의 이웃을 추천하는게 적중도가 높겠다.
  • 컨텐츠 추천 : 가중치가 높은 친구는 컨텐츠에 대한 기호가 비슷할 확률이 높다. 컨텐츠 소비 데이터와 함께 사용한다면, 정교한 추천이 가능할거다.

이 예제에서 가중치는 follower, following 두 개 요소로만 계산하고 있다. Like, 메시지 전송등과 같은 요소들을 이용해서 정교한 가중치 모델을 만들어 보는 것도 재미있겠다.소셜 그래프의 응용은 따로 문서를 만들어서 고민해봐야 겠다.

1.10. FIFO Queue

REDIS는 List 데이터타입을 지원한다. LPUSH와 RPOP를 이용해서 LIST 데이터에 대한 큐를 만들 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
> LPUSH queue1 low
(integer) 1
> LPUSH queue1 medium
(integer) 2
> LPUSH queue1 high
(integer) 3
> RPOP queue1
“low”
> RPOP queue1
“medium”
> RPOP queue1
“high”

REDIS는 RPUSH, LPUSH, LPOP, RPOP를 이용해서 데이터를 넣고 뺄 수 있다. 또한 blocking pop도 지원한다. 이들 연산의 시간 복잡도는 모두 O(1)로, 자료의 크기에 상관없이 동일하게 빠른 성능을 뽑아낼 수 있다.

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
require redis
class Queue
@id
@key
def initialize
@redis = Redis.new(:host=>192.168.56.5)
id = @redis.incr(queue_space)
@key = queue:#{id}
end
def push v
@redis.lpush(@key, v)
end
def pop
@redis.rpop(@key)
end
def key
@key
end
end
queue = Queue.new
queue.push Job 1
queue.push Job 2
queue.push Job 3
puts queue.pop

1.11. Session Storage

HTTP 기반의 모든 웹 애플리케이션들은 session을 이용해서 상태를 관리한다. 세션정보는 데이터베이스 혹은 공유파일 시스템을 이용해서, 모든 웹(혹은 WAS)서버들이 공유해야 한다. REDIS를 이용해서 세션 저장소를 만들어 보려고 한다.세션 저장소는 아래의 기능을 가지고 있어야 한다.

  1. Create : 세션을 만든다.
  2. Destroy : 세션을 삭제한다.
  3. Read : 세션에 저장된 정보를 읽는다.
  4. Write : 세션에 데이터를 쓴다.
  5. Gc : 사용하지 않는 세션은 자동으로 정리해 줘야 한다.

세션이름을 Key로 하고 데이터로 string을 저장한다. Gc는 EXPIRE 명령으로 구현한다.

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
require redis
class Session
@r = nil
@ttl = nil
def initialize ttl
@r = Redis.new(:host=>192.168.56.5)
@ttl = ttl
end
def create
r = Random.new
id = r.rand(0..10000)
session = session:#{id}
@r.expire session, @ttl
@r.set session, {‘id’:’#{id}‘}
return session
end
def read session_id
if @r.exists session_id
@r.expire session_id, @ttl
return @r.get(session_id)
end
end
def write session_id, data
if @r.exists session_id
@r.expire session_id, @ttl
return @r.set(session_id, data)
end

1.12. Auto Complete

영어사전 서비스에 자동완성 기능을 제공하려고 한다. 나는 단어들을 계층(Tree)적으로 구성하기로 했다.단어의 경로를 단지 계층적으로만 구성하면, 수많은 단어들 중에서 적절한 단어를 추천할 수 없을 것이다. 자동완성 서비스의 품질을 높이기 위해서는 적절한 단어를 추천하기 위한 알고리즘이 필요하다. 그래서 각 경로에 가중치(weight)를 주기로 했다. 이 알고리즘은 아래와 같이 작동한다.유저가 단어를 선택했다면, 단어의 경로를 계산할 수 있다. 위 그림에서 유저가 “apple”를 선택했다면, 경로는 a -> ap -> app -> apple 이 된다. 그러면 모든 경로에 대해서 가중치를 +1 한다. 이제 유저가 “a”를 선택하면, 가중치가 높은 “ap”가 높은 우선순위로 추천될 것이다. 유저가 입력하는 모든 단어에 대해서 이 연산을 수행하면 된다. (물론 유저마다 관심분야가 다르기 때문에, 제대로 서비스하려면 개인화 해야 한다. 고수준 응용은 따로 고민해서 정리해야 겠다. 여기에서는 모델을 단순화 한다.)

1
2
3
4
5
6
7
8
9
10
ZADD term.next:a 0 ac
ZADD term.next:a 0 ap
ZADD term.next:a 0 agree
ZADD term.next:ap 0 app
ZADD term.next:ap 0 apoint
ZADD term.next:app 0 apply
ZADD term.next:app 0 apple
ZADD term.next:app 0 application

유저가 apple를 입력하면, a와의 경로에 있는 모든 단어에 대해서 가중치를 높여야 한다. 단어가 선택될 때마다 경로를 찾는 건 너무 비효율적이라고 생각해서, LIST에 정리하기로 했다.

1
2
3
4
5
6
LPUSH term.route:apple apple app ap a
LPUSH term.route:apply apply app ap a
LPUSH term.route:application application app ap a
LPUSH term.route:apoint apoint ap a
LPUSH term.route:ac ac a
LPUSH term.route:agree agree a

app나 ap 등의 경로는 apple, apply, application에서 이미 만들어졌다. 따라서 굳이 경로 정보를 추가할 필요 없이 SCAN 명령으로 찾을 수 있다.

1
2
3
4
5
6
7
8
> SCAN 0 MATCH term.route:app* COUNT 3
1) “0”
2) 1) “term.route:apple”
> LRANGE term.route:apple 0 -1
1) “a”
2) “ap”
3) “app”

테스트 코드

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
# term , .
# zrevrange score .
def next term
@redis.zrevrange term.next:#{term}, 0, -1
end
# term .
def route term
@redis.lrange term.route:#{term}, 0, -1
end
# term submit ,
# +1 .
def addWeight term
i = 0
items = route(term)
items.each do | v |
if items[i+1] != nil
@redis.zincrby term.next:#{v}, 1, items[i+1]
i = i+1
end
end
end
end
ac = AutoComplete.new
ac.addWeight apple
puts ac.next a
  1. 유저가 “apple” 단어를 입력했다.
  2. addWeight “apple” 메서드가 호출 된다.
  3. 이제 a <-> ap <-> app <-> apple 는 모두 +1의 가중치가 적용된다.
  4. 유저가 “a”를 입력하면, ap, ac, agree를 추천한다. 추천은 내림차순으로 정리되므로 ap가 가장 높은 우선순위로 추천된다.
  5. 유저가 “ap”를 입력하면, app이 높은 우선순위로 추천된다.
  6. app를 입력하면 apple를 추천한다

구글 검색 서비스의 검색어 추천을 보면, 두 단계 혹은 세 단계로 확장해서 검색어를 추천하는 걸 볼 수 있다. 예를 들어 “a”를 입력했을 때, 위의 코드는 “ap, ac, agree”에서만 추천하지만, 구글의 경우에는 “a, ac, ap, agree, app, apoint”등으로 범위를 넓혀서 추천한다.2단계까지 범위를 넓히면 a를 입력했을 때 “ap > app > ac > agree == apoint” 순으로 추천이 가능하다. 단어 추천 품질이 더 높아질 거다. (성능도 고려해야 하기 때문에)구현이 복잡해 지는게 단점이다. 고민해 볼만한 재미있는 주제이지만, 여기에서는 다루지 않는다.

1.13. 검색어 추천

Auto Complete와 비슷하지만 다른 점이 있다. 위의 auto complete 데이터 구조는 “한 단어”를 대상으로 한다. 반면 검색어의 경우 두 개 이상의 단어로 이루어지는 경우가 있는데, 여기에는 적용하기가 힘들다. 단순하게 구현하자면 각 단어에 대해서 auto complete 알고리즘을 적용하면 되겠지만, 서비스 품질을 보장하기 힘들 거다. 서비스 품질을 보장하려면 “단어와 단어 사이에도 가중치”를 둬야 한다.예를 들어 “Linux”라는 단어에 대해서는 “Apple” 보다는 “Torvalds”가 더 높은 가중치를 가질 것이니, Torvalds를 추천하는게 합리적이다…. 정리 중

2. 기타 고려해야 할 것들

2.1. 메시지 정리

Log aggregation처럼 중앙에 메시지를 수집하기 위해서 REDIS를 사용하는 경우, REDIS 테이터베이스가 꽉 차는 경우에 대한 대비가 필요하다.REDIS가 꽉 찰 경우 capped list하는 방법이 있겠으나 상당히 위험한 방법이다. REDIS 인스턴스의 메모리 혹은 list/key의 갯수를 모니터링 해서, 임계치를 초과 할 경우 관리자에게 알려줘서 처리하도록 하는게 안전할 것 같다.Info를 이용하면 keyspace에서 각 데이터베이스별 key 크기를 확인할 수 있다.

1
2
3
4
5
6
used_cpu_sys_children:0.00
used_cpu_user_children:0.00
# Keyspace
db0:keys=10,expires=1,avg_ttl=45761
db1:keys=20,expires=0,avg_ttl=45761
  • keys : expires 설정된 키를 포함한 모든 키의 갯수
  • expires : expires 설정된 키.

Expires는 keys중에서 expires가 설정된 키의 갯수를 의미한다. 예를 들어 db0의 keys는 10개다. Expires 설정된 키가 삭제되면 keys는 9가 될 것이다.dbsize로 가져올 수도 있다. info처럼 상세한 정보를 출력하지는 않으며, select한 db의 정보만 가져온다.

1
2
3
4
5
6
> select 1
> dbsize
(integer) 20
> select 0
> dbsize
(integer) 10

리스트 크기는 LLEN으로 가져올 수 있다.이들 정보를 수집했다면, 임계치를 정해서 alert 메시지를 발생하도록 설정한다. 임계치의 70%는 일반 경고 메시지, 90%는 크리티컬 경고 메시지를 발생하게 해서 적절한 조치(POP하는 인스턴스가 제대로 작동하는지 확인 혹은 POP 하는 인스턴스를 늘리는 등의)를 취하면 된다. 나는 zabbix를 이용해서 모니터링 시스템을 구축했다.

3. 클라이언트 연결 테이블 관리

4. 참고

  • http://www.scribd.com/doc/33531219/Redis-Presentation
  • http://oldblog.antirez.com/post/autocomplete-with-redis.html
  • http://www.slideshare.net/jinojjan/node-js-redis
  • http://www.slideshare.net/Byungwook/redis-data-modeling-examples
  • http://openmymind.net/Data-Modeling-In-Redis/