Elasticsearch를 올려놓은 서버가 swap 메모리를 풀로 다 쓰는 문제가 발생했다. 메모리가 부족할 경우 디스크의 일정 부분을 메모리처럼 사용하는 Swap 메모리는 성능저하를 일으킬 수 있다. 찾아보니 설정없이 Elasticsearch를 실행하면 swap 메모리를 사용하므로 swap off 설정을 진행하며 발생한 이슈 해결과정을 정리한다.


  1. bootstrap.memory_lock 설정 활성화

    $ vi {elasticsearch_path}/config/elasticsearch.yml 
    
    # 아래와 같이 수정한다.
    # ----------------------------------- Memory -----------------------------------
    #
    # Lock the memory on startup:
    #
    bootstrap.memory_lock: true
    #
    # Make sure that the heap size is set to about half the memory available
    # on the system and that the owner of the process is allowed to use this
    # limit.
    #
    # Elasticsearch performs poorly when the system is swapping the memory.
    #

bootstrap.memory_lock 설정을 활성화한 후 elasticsearch를 실행하였는데 아래와 같은 로그를 남기며 elasticsearch가 정상적으로 구동하지 않는 문제가 발생했다.

[2020-05-11T13:51:29,683][WARN ][o.e.b.JNANatives         ] [server] This can result in part of the JVM being swapped out.
[2020-05-11T13:51:29,683][WARN ][o.e.b.JNANatives         ] [server] Increase RLIMIT_MEMLOCK, soft limit: 65536, hard limit: 65536
[2020-05-11T13:51:29,683][WARN ][o.e.b.JNANatives         ] [server] These can be adjusted by modifying /etc/security/limits.conf, for example:
    # allow user 'user' mlockall
    user soft memlock unlimited
    user hard memlock unlimited
...
...
[2020-05-11T15:00:31,219][ERROR][o.e.b.Bootstrap          ] [server] node validation exception
[1] bootstrap checks failed
[1]: memory locking requested for elasticsearch process but memory is not locked
[2020-05-11T15:00:31,221][INFO ][o.e.n.Node               ] [server] stopping ...
[2020-05-11T15:00:31,272][INFO ][o.e.n.Node               ] [server] stopped
[2020-05-11T15:00:31,272][INFO ][o.e.n.Node               ] [server] closing ...
[2020-05-11T15:00:31,287][INFO ][o.e.n.Node               ] [server] closed

위와 같은 로그를 남기며 정상적으로 실행이 안된다. 이때 아래의 절차를 수행한다.

  1. user의 메모리 락 제한 없애기
    위의 로그에서 나온 allow user 'user' mlockall 에서 user 부분의 사용자 이름에 대해 메모리락 제한을 없애준다.

    $ sudo vi /etc/security/limits.conf
    
    #<domain>      <type>  <item>         <value>
    
    #ftp             hard    nproc           0
    #@student        -       maxlogins       4
    
    user            soft     memlock         unlimited
    user            hard     memlock         unlimited
    # 아래처럼 한 줄로 변경해도 가능하다.
    # user            -        memlock         unlimited 
    # End of file

해당 설정 후 elasticsearch를 실행했을 때 똑같은 에러가 발생하였다.
이러한 문제는 현재 로그인 세션에서는 limits.conf 설정값이 정상적으로 반영되지 않아서 똑같은 이슈가 발생하는 것이다.

$ ulimit -l
unlimited

위와 같은 결과가 나오지 않으면 해당 세션을 종료 후 다시 서버에 접속한다. 이후에 다시 ulimit 설정값을 확인 후 unlimited 값이 정상적으로 나오면 elasticsearch를 실행시킨다.

반응형

'Dev > ElasticSearch' 카테고리의 다른 글

[ElasticSearch] ElasticSearch 설치 및 config 확인  (0) 2017.08.16

목차

  1. 작게 만들어라!
  2. 한 가지만 해라!
  3. 함수 당 추상화 수준은 하나로!
  4. Switch 문
  5. 서술적인 이름을 사용하라
  6. 함수 인수
  7. 부수 효과를 일으키지 마라!
  8. 명령과 조회를 분리하라!
  9. 오류 코드보다 예외를 사용하라!
  10. 반복하지 마라!
  11. 구조적 프로그래밍
  12. 함수를 어떻게 짜죠?
  13. 결론

작게 만들어라!

작게의 기준은 무엇일까?

  • 블록과 들여쓰기
    if/else/switch 문 등에 들어가는 블록은 한 줄이며 indent는 2단을 넘어서면 안된다.
    적절한 메서드명을 가지는 메서드를 호출하는 방식을 통해 이를 충족시킨다.

한 가지만 해라!

함수는 한 가지를 해야 한다. 그 한 가지를 잘 해야 한다. 그 한 가지만을 해야 한다.

함수는 추상화 수준이 하나인 단계만 수행한다면 그 함수는 한 가지 작업만 한다.
한 함수에서 섹션을 나눌 수 없다면 함수가 한 가지 작업을 한다고 할 수 있다.

함수 당 추상화 수준은 하나로!

함수 내에 추상화 수준이 섞이면 특정 표현이 근본 개념인지 세부사항인지 구분하기 어려워진다.

  • 위에서 아래로 코드 읽기: 내려가기 규칙
    한 함수 다음에는 추상화 수준이 한 단계 낮은 함수가 오면 함수 추상화 수준이 한번에 한 단계씩 낮아진다. 코드는 위에서 아래로 이야기처럼 읽혀야 좋다.

Switch 문

Bad :

public Money calculatePay(Employee e) throws InvalidEmployeeType {
  switch (e.type) {
    case COMMISSIONED:
      return calculateCommissionedPay(e);
    case HOURLY:
      return calculateHourlyPay(e);
    case SALARIED:
      return calculateSalariedPay(e);
    default:
      throw new InvalidEmployeeType(e.type);
  }
}

Switch문의 문제점

  1. 함수가 길어진다
  2. '한 가지' 작업만 수행하지 않는다.
  3. Single Response Principle를 위반한다.
  4. Open Closed Principle을 위반한다.

Switch문은 다형적 객체를 생성하는 코드안에서만 사용하도록 해야 한다.

서술적인 이름을 사용하라

일관적이고 서술적인 이름을 사용하여 함수 이름만으로 해당 함수가 하는 일을 파악할 수 있도록 해야 한다.

함수 인수

테스트 코드 관점에서도 인자가 많을수록 테스트를 위한 유효한 인자를 만들어야 하는 문제가 생긴다.
가장 이상적인 인수 개수는 0개, 최대 3개. 하지만 적을수록 좋다. 줄이자 줄이자!

  • 많이 쓰는 단항 형식
    • 인수에 질문을 던지는 경우
    • 인수를 뭔가로 변환해 결과를 반환하는 경우
    • 이벤트 (출력은 없는 경우, 다소 드물다) - 이름과 문맥을 통해 이벤트 함수임을 명확히 드러내야 한다.
  • 플래그 인수
    플래그 인수 값에 따라 메서드가 다른 기능을 하게 된다는 의미이므로 플래그 인수는 지양해야 한다.
  • 이항 함수
    무시해도 되는 인자가 있는 순간 버그 확률이 늘어난다.
    이항 함수가 적절한 경우는 인수 2개가 한 값을 표현하는 두 요소인 경우, 자연적인 순서가 있는 경우이다.
    같은 클래스인 2개의 인자를 받는 경우 순서를 헷갈릴 수 있으므로 지양해야 한다.
  • 삼항 함수
    순서, 주춤, 무시로 야기되는 문제는 두 배 이상으로 늘어난다.
    인자의 의미가 명확한 경우가 아니면 지양한다.
  • 인수 객체
    결국 변수에 이름을 붙여야 하므로 개념을 표현하게 되는 것이다.
    e.g.,
    Circle makeCircle(double x, double y, double radius);
    Circle makeCircle(Point center, double radius);
  • 인수 목록
    가변 인수 전부가 동등하게 취급될 경우 List 형 인수 하나로 취급할 수 있다.
    e.g., public String format(String format, Object... args)
  • 동사와 키워드
    함수의 의도나 인수의 순서와 의도를 함수 이름에 담아야 한다.
    함수 이름에 인자의 키워드를 추가하여 인자의 의미와 순서를 보장 해 준다.

부수 효과를 일으키지 마라!

함수는 꼭 한 가지 기능만을 한다.
부수 효과로 발생 가능한 문제?

  • Temporal Coupling
  • Order Dependency

명령과 조회를 분리하라!

함수의 역할

  • 뭔가를 수행한다.
  • 뭔가에 답한다.

Bad :

// attribute인 속성을 찾아 값을 value로 설정 후 성공시 true, 실패시 false
public boolean set(String attribute, String value);

if (set("username", "unclebob")) ...

Good :

if (attributeExist("username")) {
  setAttribute("username", "unclebob");
}

오류 코드보다 예외를 사용하라!

오류코드의 문제점

  • 호출자는 오류 코드를 바로 처리해야 한다.
  • 의존성 자석 : 오류 코드의 수정/추가/삭제가 일어날 경우 처리 코드도 수정해야 한다

Try/Catch 블록 뽑아내기
오류처리도 한 가지 작업임을 인지하고 오류 처리 함수를 분리하도록 한다.

Bad : try/catch 블록은 정상 동작과 오류 처리 동작을 섞으며 코드 구조에 혼란을 일으킨다.

try {
  deletePage(page);
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name.makeKey());
} catch (Exception e) {
  logger.log(e.getMessage());
}

Good :

public void delete(Page page) {
  try {
    deletePageAndAllReferences(page);
  } catch (Exception e) {
    logError(e);
  }
}

// 정상 동작만을 처리한다.
private void deletePageAndAllReferences(Page page) throws Exception {
  deletePage(page);
  registry.deleteReference(page.name);
  configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) {
  logger.log(e.getMessage());
}

반복하지 마라!

반복되는 코드가 다른 코드와 섞이면 모양이 달라지면서 중복이 잘 드러나지 않게 되어 수정이 일어날 때 오류가 발생할 확률이 높아진다.

중복을 없애거나 제어 할 목적의 원칙과 기법들

  • 관계형 데이터베이스에 정규 형식을 만듦
  • 객체지향 프로그래밍 - 코드를 부모 클래스로 몰아 중복을 없앰
  • 구조적 프로그래밍
  • AOP(Aspect Oriented Programming) - Spring의 핵심 개념 중 하나로 부가기능의 모듈화 (하단 링크들 참고)
  • COP(Component Oriented Programming) - 기존 시스템이나 소프트웨어를 구성하는 컴포넌트를 조립해서 하나의 새로운 응용프로그램을 만드는 소프트웨어 개발 방법론

구조적 프로그래밍

모든 함수와 함수 내 모든 블록에 entry와 exit이 하나만 존재해야 한다. 즉, return 문이 하나여야 한다. 루프 안에서 break, continue를 사용해서는 안 되며 goto는 절대로 안된다.

함수가 클 때만 이익을 제공하는 기법이다.
함수가 작을때는 return, break, continue를 여러차례 사용해도 괜찮지만 goto는 작은 함수에서도 피해야 한다.

함수를 어떻게 짜죠?

TDD
테스트 코드 -> 중복, 네이밍, 들여쓰기 등 상관없이 우선 기능을 개발 -> 리팩토링 (무조건 단위테스트는 계속 통과)
위 과정을 반복한다.

결론

위의 규칙들을 잘 따라 시스템을 잘 풀어가야한다!


참고하면 좋을 문서

AOP

반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15
[CleanCode] 5장. 형식 맞추기  (0) 2020.05.12
[CleanCode] 4장. 주석  (0) 2020.05.12
[CleanCode] 2장. 의미있는 이름  (0) 2020.05.02

 최근 공부한 내용을 Markdown문서로 정리하고 Github에 저장해서 정리하다보니 내용이 길어질 때 회사에서 사용하는 WIKI처럼 문서 상단에 링크가 걸린 목차를 만들고 싶어졌다.

 

TOC(Table Of Content)는 헤딩 태그를 기준으로 생성 되므로 문서 작성 시 TOC에 표기하고자 하는 문장들은 헤딩 태그로 명시 해 주어야 한다.

 헤딩 태그로 명시된 문장을 목차에 링크 걸기 위해서는 아래와 같은 포맷으로 작성 하면 된다. 이때 주의 할 점은 링크가 걸리는 텍스트의 띄어쓰기는 "-"로 명시해야 하거나 글자수+띄어쓰기 수 만큼의 "-"를 써준다. 하지만 명확하게 표기하기 위해 해당 문장을 그대로 쓰는 것을 추천한다 :)

[목차 텍스트1](#링크가-걸리는-텍스트1)
[목차 텍스트2](#------------)
...
# 링크가 걸리는 텍스트1
# 링크가 걸리는 텍스트2

 

 

 실제 작성한 예시와 결과는 아래와 같다.

## 목차

1. [깨끗한 코드](#1장.-깨끗한-코드)
2. [의미 있는 이름](#2장.-의미-있는-이름)

  

---

## 1장. 깨끗한 코드

### 보이스카우트 규칙

* 코드는 잘 짜기만 했을 때 끝나는 것이 아닌, 시간이 지나도 언제나 깨끗하게 유지해야 한다.
* 보이스카우트 규칙  
  ` 캠프장은 처음 왔을 때보다 더 깨끗하게 해놓고 떠나라.`

---

## 2장. 의미 있는 이름

### 의도를 분명히 밝혀라

이름을 지을 때 아래의 질문들을 고려해야 한다.

* 변수(혹은 함수나 클래스)의 존재 이유는?
* 수행 기능은?
* 사용 방법은?

  위의 예시와 같이 작성하였을 때 깃헙에 커밋을 해 보면 아래와 같이 목차에 있는 깨끗한 코드, 의미 있는 이름에 링크가 걸림을 알 수 있다. 그리고 링크가 걸린 문장에 커서를 가져가면 1장. 깨끗한 코드앞에 링크 표식이 보이는 것 처럼 링크 표식이 나타난다.

 

 


* 위의 내용을 직접 만들지 않도록 도와주는 사이트가 있다. 

https://ecotrust-canada.github.io/markdown-toc/

 

Generate TOC Table of Contents from GitHub Markdown or Wiki Online

This page uses markdown-toc library to generate your MarkDown TOC online. paste markdown here # Paste Your Document In Here ## And a table of contents will be generated ## On the right side of this page. TOC generated here

ecotrust-canada.github.io

사이트에서 아래와 같이 직접 작성한 마크다운 문서를 빨간칸에 붙여넣으면 헤딩 태그를 기준으로 TOC(Table Of Content)를 생성 해 준다.

반응형

의미있는 이름

목차


의도를 분명히 밝혀라

이름을 지을 때 아래의 질문들을 고려해야 한다.

  • 변수(혹은 함수나 클래스)의 존재 이유는?
  • 수행 기능은?
  • 사용 방법은?

Bad

public List<int[]> getThem() {
  List<int[]> list1 = new ArrayList<int[]>();
  for (int[] x : theList)
    if (x[0] == 4)
        list1.add(x);
  return list1;
}

복잡한 코드는 아니지만 해당 메서드가 무엇을(them) 가져오는 것인지, List에 담기는 int[] 배열은 무슨 데이터인지, 4는 무슨 의미를 가지는지를 알 수 없다. 이 말은 곧 각 변수들의 정보를 모르는 경우 코드의 맥락을 완전히 이해 할 수 없다는 뜻이다.
각 변수들의 정보를 모르더라도 이해할 수 있도록 아래와 같이 수정할 수 있다.

Good

public List<Cell> getFlaggedCells() {
  List<Cell> flaggedCells = new ArrayList<Cell>();
  for (Cell cell : gameBoard)
    if (cell.isFlagged())
      flaggedCells.add(cell);
  return flaggedCells;
}

 

그릇된 정보를 피하라

  • 약어를 지양하자
  • 여러 계정을 그룹으로 묶을 때 List와 같은 특정 컨테이너의 사용을 지양하라 (e.g., accountList -> Accounts)
  • 네이밍은 서로 비슷한 이름을 사용하지 말자
  • 유사한 개념은 유사한 표기법을 사용하자, 일관성이 떨어지는 표기법은 그릇된 정보이다.
  • 다른 글자와 혼동되는 단일 알파벳을 피하자 (e.g., 소문자 L <-> 1, 대문자 I)

 

의미 있게 구분하라

한 scope에서 동일 이름을 사용할 수 없으니 컴파일러를 통과하기 위한 무의미한 철자 변경의 오류를 범하지 않도록 해야 한다. 무의미한 철자 변경의 대표적인 두가지 예시는 아래와 같다.

  • 연속된 숫자를 덧붙이기
    Bad :

    public static void copyChars(char a1[], char a2[])

    Good :

    public static void copyChars(char souce[], char destination[])
  • 불용어(noise word) 추가
    불용어란? 없어도 의미 전달에 영향이 없는 단어

    • 접두어 사용을 조심하자
      a, an, the와 같은 접두어는 의미가 분명히 다를 때만 사용하도록 한다. (e.g., 모든 지역변수는 a 접두사 사용, 모든 함수 인수는 the 접두사 사용)
    • 불용어를 사용하여 중복을 발생시키지 말자
      product라는 클래스가 존재 할 때 ProductInfo, ProductData와 같은 개념이 구분되지 않는 클래스를 생성하여 중복을 발생시키지 않도록 한다. (e.g., Money & MoneyAccaount, CustomerInfo & Customer, AccountData & Account)

 

발음하기 쉬운 이름을 사용하라

발음하기 어려운 단어는 커뮤니케이션에도 영향을 끼친다. 이는 실제 존재하는 단어를 사용하는 것만으로도 해결이 된다.

Bad :

아 래의 변수는 generate date, year, month, day, hour, minute, second 의 줄임말이다.

Date genymdhms

Good :

Date generationTimetamp

 

검색하기 쉬운 이름을 사용하라

  • 상수
    상수값에 버그가 있을 경우 검색으로 찾아낼 수 없다. 그러나 상수값의 의미를 나타내는 변수로 정의하여 사용하면 검색에 용의하다.

  • 변수
    알파벳 하나를 변수로 사용하면 검색이 어렵다.

Bad :

int s = 0;
for (int i = 0; i < 30; i++) 
  s += i * 4;

Good :

int maxIndex = 30;
const int MULTIPLICATION_CONDITION = 4;
int sum = 0;
for (int i = 0; i < maxIndex; i++) // 하나의 메서드에서만 쓰이며, 다시 쓰이지 않는다면 한 문자 변수도 나쁘지 않다
  sum += i * MULTIPLICATION_CONDITION;

 

인코딩을 피하라

  • 헝가리식 표기법을 지양하라
    과거 프로그래밍 언어는 변수 및 함수의 인자 이름 앞에 데이터 타입을 명시하는 헝가리식 표기법을 사용하였으나 현대의 프로그래밍 언어는 많은 컴파일러가 타입을 기억하고 강제하므로 네이밍 시 타입을 직접 명시하는 것을 피하는 것이 좋다. 오히려 아래와 같은 문제가 발생 할 수 있다.

    PhoneNumber phoneString; // 이러한 경우 타입이 바뀌게 되었을 때 변수명도 바꿔주어야 하는 문제가 생긴다.
  • 멤버 변수 접두어
    멤버 변수임을 명시하기 위해 "m_" 접두어를 붙이는 것을 지양하고 클래승와 함수는 접두어가 필요없을 정도로 작게 구현해야 한다.

  • 인코딩이 필요한 경우?
    Abstract Factory를 구현하는 경우, 인터페이스 클래스 이름보다는 구현 클래스의 이름을 인코딩 하는 것이 좋다.
    Bad :

    public interface IShapeFactory;  // 인터페이스 클래스
    public class ShapeFactory;  // 구현 클래스

    Good :

    public interface ShapeFactory;  // 인터페이스 클래스
    public class ShapeFactoryImp;  // 구현 클래스
    public class CShapeFactory;  // 구현 클래스

 

자신의 기억력을 자랑하지 마라

  • 하나의 문자만 사용하는 것을 지양하라
    • 루프에서 반복 횟수 변수는 전통적으로 한 글자만을 사용
      보통 i, j, k를 사용 (소문자 l은 대문자 I와 헷갈리므로 절대 안됨!)
    • 이외에는 대부분 적절하지 않다
  • 나만 이해할 수 있는 변수명이 아닌 모두가 이해할 수 있는 명료한 네이밍을 지향하자

 

클래스 이름

  • 동사가 들어가지 않는 명사/명사구가 적합하다.
  • 불용어를 지양하자 (e.g., Manager, Processor, Data, Info)

Good :

public class Customer
public class WikiPage
public class AddressParser

 

메서드 이름

  • 동사/동사구가 적합하다.

  • 접근자(Accessor), 변경자(Mutator), 조건자(Predicate)는 javabean 표준에 따라 get, set, is를 붙인다.
    e.g.,

    String name = employee.getName();
    customer.setName("mike");
    if (paycheck.isPosted());
  • 생성자의 중복정의 시 정적 팩토리 메서드를 사용한다. (이때, 생성자 사용을 제한하려면 해당 생성자를 private으로 사용한다.)

    • 해당 내용의 상세한 특징은 Effective Java 3/E 기준 Item1을 참고한다.

    Bad : 

    Complex fulcrumPoint = new Complex(23.0);

    Good : 

    Complex fulcrumPoint = Complex.FromRealNumber(23.0);

 

기발한 이름은 피하라

  • 특정 문화, 배경지식이 있어야 이해할 수 있는 이름이 아닌 명료한 이름을 선택하여 의도를 분명하고 솔직하게 표현하라

 

한 개념에 한 단어를 사용하라

  • 똑같은 기능을 하는 메서드는 모든 클래스에서 하나의 이름으로 통일한다.
    (e.g., 각 클래스마다 fetch, retrieve, get으로 다르게 구현하지 않도록 한다.)
  • 주석이 필요하지 않도록 메서드 이름은 독자적이고, 일관적이어야 한다.
  • 동일 layer의 클래스는 하나의 이름으로 통일한다.
    (e.g., 하나의 프로젝트에서 DeviceManager, ProtocolController와 같이 같은 의미지만 다른 단어의 혼용을 피한다.)

 

말장난을 하지마라

  • 한 개념에 한 단어를 사용하라 규칙을 따르되, 일관성을 고려한다.
  • 같은 맥락일때만 같은 이름을 사용하여 코드 내용을 확인하지 않아도 기능이 보장되도록 해야 한다.

 

해법 영역에서 가져온 이름을 사용하라

  • 모든 이름에 도메인 영역의 단어를 사용하지 않는다. 기술 개념에는 기술 이름이 가장 적합한 선택이다. (e.g., JobQueue, AccountVisitor)

 

문제 영역에서 가져온 이름을 사용하라

  • 적절한 기술 이름이 없는 경우 또는 도메인 영역과 관련이 깊은 코드에 도메인 영역의 이름을 사용한다.
  • 우수한 프로그래머라면, 해법(기술) 영역과 문제(도메인) 영역을 구분할 줄 알고 각 영역에 알맞은 네이밍을 정해야 한다.

 

의미 있는 맥락을 추가하라

  • 네이밍은 독자적으로 문맥을 가지기 보단 클래스, 메서드, name space의 위치에 따라 맥락을 부여한다.
  • 접두어는 최후의 수단으로 붙인다.
  • 특정 문맥을 가지는 클래스를 생성하면 컴파일러에게도 문맥이 분명해진다.

Bad : 

  • 메서드의 멤버 변수로 사용될 때 어디에 어떻게 이용될지는 길고 복잡한 메서드를 다 읽어야만 이해할 수 있다.
  • 조건문으로 분기되어 변수의 값을 세팅해주지만 해당 값의 의미는 알 수 없다.
private void printGuessStatistics(char candidate, int count) {
  String number;
  String verb;
  String pluralModifier;
  if (count == 0) {
    number = "no";
    verb = "are";
    pluralModifier = "s";
  }
  else if (count == 1) {
    number = "1";
    verb = "is";
    pluralModifier = "";
  } else {
    number = Integer.toString(count);
    verb = "are";
    pluralModifier = "s";
  }
  String guessMessage = String.format("There %s %s %s%s", verb, number, candidate, pluralModifier);
  print(guessMessage);
}

Good : 

  • 클래스의 멤버 변수가 됨으로써 통계 추측 메세지에 사용되는 값임을 클래스의 이름만으로 알 수 있다.
  • 메서드로 분리함으로서 값을 세팅할 때 어떠한 의미를 가지는 값을 세팅하는지 명확해진다.
public class GuessStatisticsMessage {
  private String number;
  private String verb;
  private String pluralModifier;

  public String maek(char candidate, int count) {
    createPluralDependentMessageParts(count);
    return String.format(
      "There %s %s %s%s",
      verb, number, candidate, pluralModifier);
  }

  private void createPluralDependentMessageParts(int count) {
    if (count == 0) {
      thereAreNoLetters();
    } else if (count == 1) {
      thereIsOneLetter();
    } else {
      thereAreManyLetters(count);
    }
  }

  private void thereAreManyLetters() {
    number = Integer.toString(count);
    verb = "are";
    pluralModifier = "s";
  }

  private void thereIsOneLetter() {
    number = "1";
    verb = "is";
    pluralModifier = "";
  }

  private void thereAreNoLetters() {
    number = "no";
    verb = "are";
    pluralModifier = "s";
  }
}

 

불필요한 맥락을 없애라

  • 모든 클래스를 아우르는 맥락을 굳이 접두어로 사용하지 않는다.

    • IDE에서 해당 접두어를 입력하는 순간 수많은 클래스가 열거되어 정말 필요한 클래스를 찾기 어려워진다.
    • 굳이 접두어를 붙임으로서 이름이 불필요하게 너무 길어진다. 긴 이름이 짧은 이름보다 좋지만 의미가 분명한 경우에 한해서이다.

    Bad : 

    package com.gsd;
    
    public class GsdAccountAddress

    Good : 

    package com.gsd;
    
    public class AccountAddress
  • 클래스의 이름으로 좋은 것과 인스턴스의 이름으로 좋은 것은 다르다.

 

마치면서

좋은 이름을 선택한다는 것은 결국 가독성을 높이기 위한 것


참고하면 좋을 문서

반응형

'Dev > Books' 카테고리의 다른 글

[CleanCode] 7장. 예외 처리  (0) 2020.05.16
[CleanCode] 6장. 객체와 자료 구조  (0) 2020.05.15
[CleanCode] 5장. 형식 맞추기  (0) 2020.05.12
[CleanCode] 4장. 주석  (0) 2020.05.12
[CleanCode] 3장. 함수  (0) 2020.05.06

코드리뷰 중 아래와 같은 질문을 받았다.

클래스가 서비스 형태로 사용되는 경우, 상태 정보를 가지고 있지 않은 무상태(stateless) 방식으로 만들면 어떤 장점이 있을까요?

클래스 내에 인스턴스 변수를 사용하지 않는다라는 생각을 해 본적이 없어서 살짝 당황했었다. 나름대로 고민을 해 보았지만 그렇다할 답이 생각이 나지 않아 구글링을 하다보니 관련된 stackOverflow 질문글이 있었다.


질문에 대한 답변은 아래와 같다.

Stateless object is an instance of a class without instance fields (instance variables). The class may have fields, but they are compile-time constants (static final).

A very much related term is immutable. Immutable objects may have state, but it does not change when a method is invoked (method invocations do not assign new values to fields). These objects are also thread-safe.

간단히 정리해보자면, Stateless Object란 인스턴스 변수가 없는 객체를 말한다.

 

코드로 예시를 들어보면 더욱 이해가 쉬울 것 같다.

아래와 같은 코드는 Stateless Object이다.

public class Car {
    void Car() {
    System.out.println("I'm car!");
    }
}

말그대로 인스턴스 변수가 없는 객채인데, 아래와 같은 컴파일 타임에 정의되어있고 변경되지 않는 상수를 가지는 경우도 Stateless Object라고 지칭할 수 있다.

public class Car {
  static final String CAR_MESSAGE = "I'm car!";

  void Car() {
    System.out.println(CAR_MESSAGE);
  }
}

Stateless Object와 연관된 헷갈릴 수 있는 개념으로는 Immutable Object가 있다.
객체지향 프로그래밍에서 Immutable Object는 상태를 바꿀 수 없는 객체이다.

public class Car {
  private static String message;

  void Car(String message) {
    this.message = message;
  }

  public void printMessage() {
    System.out.println(message);
  }
}

이와 같이 불변 객체는 상태가 한번 지정되면 바뀔 수 없지만 컴파일 시점에 값이 정의되는 것이 아니어서 Stateless Object라고 할 수는 없다.

 

완전한 정답은 없겠지만 내가 생각해본 stateless 객채의 장점은 아래와 같지 않을까 싶다.

  • SOLID 법칙중 확장에 열리고 수정에 닫힘을 보장
  • 쓰레드에 안전
  • 인터페이스화 하기 용이
반응형
local에서 작업을 하고 서버에서 코드를 받아서 테스트를 하다가 수정사항이 생기면 양쪽에서 커밋을 마구잡이로 할 때도 있다.
아니면 자잘하게 놓친 한 두줄을 위해 커밋을 또 날리게 되는 경우들이 있다.
그러다보면 커밋 히스토리는 지저분해지기 쉽상.
이런 경우에 rebase를 사용해 커밋 히스토리를 정리할 수 있다.

local에서 작업하는 기준으로 remote에 commit 한 history를 정리하고 싶을 때

# n : 합치고 싶은 커밋의 갯수

$ git rebase -i HEAD~n

위의 명령어를 실행하면 아래와 같이 커밋을 수정할 수 있는 화면이 나온다.

나타난 커밋들 중에 합치고 싶은 커밋의 pick을 squash로 변경한다.

이 예제의 경우 aa8f244 커밋에 5e5dc41 커밋의 내용을 반영하고, 커밋을 합치기 위해 5e5dc41 커밋을 squash로 변경 해 준다.

변경을 완료하면 esc + :x

이제 커밋메세지를 새로 쓸 수 있는 화면이 나오게 된다.

지우고 싶은 커밋메세지의 앞에 # 을 붙여 주석처리를 해준다.

첫번째 커밋 메세지의 위치에 새로 쓰고자 하는 커밋메세지를 작성해주고 :x로 종료해준다.

 

Successfully rebased and updated ~ 로그가 나오게 되면 local에서 rebase가 잘 된 것!

이제 원격에 수정사항을 적용하기 위해 git push --force origin branchName 명령어를 사용한다.

이전 커밋에 히스토리를 합친 것이기 때문에 HEAD보다 현재의 커밋이 뒤에 있기 때문에 그냥 push하면 오류가 나므로 아래와 같이 --force 옵션을 붙여서 force push를 해준다.

--force 옵션을 붙이게 되면 local의 git 정보가 remote에 덮어씌워 지므로 조심해서 써야 한다.

git push --force origin 브랜치명

 

반응형

Git 로컬 정리



git init

- 저장소 생성

mkdir 폴더명

cd 폴더명

git init

-> 성공시 Initialized empty Git repository in /path/폴더명/.git



git status

- 저장소 상태를 확인

git에서 추적하지 않는 파일이 존재하는 경우



git add 파일명

- git 파일을 추적하도록 추가

아무런 메세지가 없으면 성공적으로 추가된

git status 명령어를 실행하면 커밋해야 수정내역 확인 가능



git commit

- git 수정내역을 추가

첫줄에 커밋 메세지를 작성한 vim 종료하면 커밋 완료([Esc]키를 누르고 :wq 입력한 다음 [Enter]키를 눌러 저장 종료)

git commit -m “커밋 메세지

- vim 진입 없이 메세지를 입력하여 커밋할 있음

git commit -a

- 저장소에 변경된 모든 파일을 커밋 (처음 생성된 파일X, 변경된 내역 O)




모든 변경내역을 commit git status 명령어를 실행하면 아래와 같은 결과가 나옴



——————————————————————————————————————————


Git branch


기존 프로젝트에 영향을 끼치지 않고 새로운 기능을 추가/변경하여 테스트를 해야하는 경우

branch 통해 기본 코드를 복사하여 추가적인 코딩을 하면


git branch

현재 존재하는 브랜치 확인


 (현재 작업중인 브랜치 이름앞에는 * 표시 )


git branch 브랜치명

- 브랜치 생성


git checkout 브랜치명

- 작업하고자 하는 브랜치로 이동하는 명령어

git checkout -b 브랜치명

- 브랜치를 만들면서 바로 체크아웃


브랜치에서 변경한 내역을 master 병합

git checkout master

git merge 브랜치명


——————————————————————————————————————————


불필요한 파일 폴더 무시


touch .gitignore

- .gitignore라는 이름의 빈파일 생성

(touch 명령어는 파일의 타임스탬프를 변경하는 용도로 사용하거나 파일이 없는 경우 파일을 만드는 명령어)


www.gitignore.io

위의 사이트에서 검색창에

현재 사용중인 운영체제, IDE, 프로그래밍 언어 이름

조건들을 입력하고 generate 클릭하면 gitignore 파일을 생성해줌 -> .gitignore 파일에 복사하여 저장


——————————————————————————————————————————


충돌 방지


여러 브랜치의 같은 파일의 같은 행에 서로 다른 변경 사항이 있으면 merge 과정에서 충돌이 발생


-> 수작업으로 해결 줘야


——————————————————————————————————————————






반응형

Table에서 checkbox column이 들어갔을 때 checkbox에서 체크된 row의 데이터를 가져오는 과정에서 애를 먹어서 정리를 해보았다.

 

 

html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<table>
    <tbody>
        <tr>
            <th><input type="checkbox" id="checkAll"></th>  <!--테이블의 최상단 column의 check박스는 전체 체크박스에 체크/해제 역할 - 자바 스크립트-->
                <th>col1</th>
                <th>col2</th>
            <th>col3</th>
        </tr>
 
        <tr>
            <td><input type="checkbox" name="checkboxName"></td>
            <td>col1 text</td>
            <td>col2 text</td>
            <td>col3 text</td>
        </tr>
    </tbody>
</table>
cs

 

 

 

JavaScript

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var checkbox = $("input[name=checkboxName]:checked");
 
var col1 = "";
var col2 = "";
var col3 = "";
var col4 = "";
 
checkbox.each(function (i) {
    var tr = checkbox.parent().parent().eq(i);  // checkbox 태그의 두 단계 상위 태그가 tr이기 때문에
    var td = tr.children();  // td 태그는 tr 태그의 하위에 있으므로
 
    col1 = td.eq(1).text(); // 1번째 column(eq(0))은 체크박스 이므로 eq(1)부터 데이터를 받아줌
    col2 = td.eq(2).text();
    col3 = td.eq(3).text();
    col4 = td.eq(4).text();
});
cs

 

반응형

DI(Dependency Injection : 의존성 주입)


클래스 사이의 의존 관계를 자동으로 구성 (의존 관계란 ? http://natana1992.tistory.com/20)

DI 컨테이너가 인스턴스를 관리 (인스턴스 생성하고 과정에서 필요한 인스턴스를 설정하여 애플리케이션에 반환)

 

이점

  인스턴스의 스코프를 제어할 있음(ex. 인스턴스 - 싱글톤 객체 or 매번 새로 생성)

  인스턴스의 라이프 사이클을 이벤트로 제어할 있음

  트랜잭션 관리나 로깅 처리와 같은 공통 처리를 포함할 있음

  객체 사이의 의존 관계가 느슨해지므로 유닛 테스트를 하기 쉬워짐(인터페이스에 관해서만 의존 관계를 만들어 )




Bean 정의 파일


인터페이스에 어떤 실제 기능(Bean) 제공할지를 DI 컨테이너가 관리하도록 구현한다. DI 컨테이너가 bean 관리할 있도록 Bean 정의 파일(XML 정의, JavaConfig 정의) 만든다. 

JavaConfig 정의

@Configuration 애너테이션을 클래스 정의 앞에 붙여 컴파일 JavaConfig 클래스임을 알려야

@Bean 애너테이션을 DI 컨테이너가 관리할 Bean 생성하는 메서드 앞에 붙여야


-> 부분에서 다른 프로젝트의 config파일에 Bean 정의가 되어있는 것을 착각해서 주입이 안되는지 삽질을 했다.....




Auto-wiring


DI 컨테이너에서 명시적으로 Bean 가져오도록 하는 것이 아닌 DI컨테이너가 클래스에 Bean 주입하도록 하는


@Autowired 애너테이션을 통해 DI 컨테이너가 주입해야 필드를 명시함

@Autowired 애너테이션을 붙인 필드를 포함한 클래스를 관리하고 관리하는 객체 중에 애너테이션이 붙은 필드에 맞는 객체를 자동으로 찾아내어 주입




ComponentScan


DI컨테이너에 등록할 Bean 하나하나 정의하기가 번거로움

-> 스프링 프레임워크는 Component Scan 기능을 통해 Bean DI 컨테이너에 자동을 등록하여 문제를 해결


클래스 앞에 @ComponentScan 애너테이션을 붙이면 클래스의 패키지 내부의 모든 클래스를 검색하여 @Component 같은 특정 애너테이션이 붙은 자바 클래스를 찾아내서 DI 컨테이너에 등록


과정을 통해 Bean 정의 파일을 만드는 과정을 생략할 있음


@Component 외에도 컴포넌트 스캔의 대상이 되는 애너테이션

- @Controller : MVC 컨트롤러를 나타내는 애너테이션 (스프링4에서는 REST 서비스용으로 @RestController 있음)

- @Service : 주로 비즈니스 로직을 처리하는 역할을 담당

- @Repository : 도메인 객체를 보존하거나 얻어오고 검색하는 조작을 캡슐화하며 컬렉션 객체와 동일한 역할을 담당 (Entity 객체에 대한 정보를 담아서 같이 사용)




반응형

Design and Implementation of a High-Performance Distributed Web

웹 크롤러란?

방대한 웹 페이지를 방문하여 각종 정보를 자동적으로 수집하는 일을 하는 프로그램으로서 검색엔진의 근간이 됨

방대한 페이지를 고성능으로 방문하기 위한 이슈

  • 좋은 크롤링 전략
  • 고도화 된 시스템 아키텍쳐


크롤링 전략

Breadth-First Crawler : 크롤러는 작은 페이지 집합에서 시작하여 BF 방식을 기반으로 탐색

Crawling Pages for Updates : 최신 검색 인덱스를 유지하기 위해서 페이지 업데이트 이력에 대한 관찰이 중요

Focused Crawling : 전문화된 검색 엔진을 위해 크롤링 하므로 특정 종류의 페이지에 집중. 많은 대역폭을 사용하지 않고 최신의 많은 페이지를 찾는 것이 목적

Random Walking and Sampling : 웹 그래프에서 랜덤 워크를 사용하여 샘플 페이지 검색을 하거나 검색엔진 크기와 품질 평가에 사용

Crawling the “Hidden Web’” : 실제로 데이터베이스에 있지만 쿼리나 웹페이지에서 양식으로 접근이 가능한 데이터. 이러한 데이터에 자동으로 접근하려는 크롤러는 프론트 엔드 시스템으로 확장될 수 있는 것이 특징.

 

크롤러의 필요 요소

Flexibility (유연성
:
가감을 통해 다른 시스템에 사용되거나 다른 시스템을 사용할 수 있어야 한다.

Low Cost and High Performance (저비용 고성능)
:
수백만 페이지를 다운로드 한 뒤의 성능에 효율적인 디스크 접근이 필요하다

Robustess (견고성)
:
많은 어플리케이션에서 받은 페이지 중 이상한 HTML을 견뎌야 함, 크롤링은 오래걸리므로 충돌과네트워크 중단을 버텨야 함.

Etiquette and Speed Control (에티켓과 속도조절
:
로봇룰을 따라야 한다, 하나의 서버에 많은 유입이 발생하지 않도록 각 사이트에 일정시간을 두고연결한다. 전체 다운로드 비율을 시간에 따라 나눠줘야 한다.(이 프로젝트는 캠퍼스에서 진행되기때문에 학생들의 사용에 영향을 끼치지 않기 위해서)

Manageability and Reconfigurability (관리성과 재구성 가능성
:
크롤러의 속도, 통계 등과 관련된 모니터링이 필요하고 속도, 요소 가감, 시스템 차단 등의 매니저가필요

 

크롤러의 구조

크게 두가지 구성요소로 나뉘게 된다

1. 크롤링 어플리케이션

주어진 현 상태와 이전에 크롤링 된 페이지를 통해 무슨 페이지를 요청할 지 결정을 하고 요청을 하는 역할

2. 크롤링 시스템

요청된 페이지를 다운받고 제공해 주는 역할

각각의 크롤러의 시나리오가 모두 다르지만 크롤러는 기본적으로 중복제거, 페이지의 적절성 검사, 로봇 차단 등의 공통적인 기능들이 있으므로 시스템과 전략을 디자인 할 때 유연하게 해야 한다.


 

시스템 아키텍쳐

크롤러는 크게 크롤링 시스템과 크롤링 어플리케이션으로 나뉜다. 그리고 크롤링 시스템은 크롤링 매니저, 다운로더, DNS리졸버의 요소들로 분화된다.

1.Crawl Manager

크롤링 시스템의 중요 요소
어플리케이션으로 부터 URL 요청과 NFS를 통해 접근할 수 있는 URL 정보를 가진 파일에 대한 포인터를 받는다. 높은 성능을 유지하기 위해 대략적으로 어플리케이션이 지정한 순서대로 페이지를 다운 받으면서 특정웹에 너무 부하가 가지 않도록 요청을 재정렬 해주는 목적을 가진다.
URL
들을 로드
->
캐시되지 않은 주소는 DNS resolver IP 주소를 요청 
->
이와 분리되어 우선순위에 따라 로봇 파일을 요청
의 과정을 걸쳐 제외된 URL을 빼고 최종적으로 남은 URL을 일괄적으로 다운로더에게 전송하고 같은 서버는 일정 간격으로 요청되는지를 학인하는 역할을 한다.
또한 매니저는 모니터링을 통해 다운로더와 DNS Resolver의 속도를 맞추고 제한한다. 그리고 데이터 구조의 주기적인 스냅샷을 수행하고 충돌 후 제한된 수의 페이지를 다시 크롤링해야 한다.

2. Downloaders

다운로더는 높은 퍼포먼스의 비동기식 HTTP 클라이언트다.
파이썬으로 구현 된 다운로더는

다른 최대 1000개의 연결을 열고 도착하는 데이터에 대해 연결을 폴링(충돌회피, 동기화 목적으로 다른 장치 상태 검사
->
웹에서 파일을 가져 옴 
-> NFS
를 통해 액세스 할 수 있는 파일로 데이터를 마샬링(한 객체의 메모리에서 표현방식을 저장 또는 전송에 적합한 다른 데이터 형식으로 변환하는 과정)
의 과정을 책임진다. 이 과정에서 매니저는 현재 동시 연결된 수를 조절하여 다운로더의 속도만 조절한다.

3. DNS Resolver

DNS의 클라이언트로 호스트의 정보를 구하는 프로그랭믜 요청을 서버에 대한 질의 형태로 번역해 주고, 그 응답을 프로그램에 만들어 변형시켜주는 역할을 한다.
기존의 크롤러 디자인들은 동기화 특성 때문에 심각한 보틀넥이 있었지만 해당 논문에서는 GNU adns 비동기 DNS 클라이언트 라이브러리로 하나의 컴퓨터에 함께 배치된 DNS 서버에 접근하도록 하여 해결 하였다. 그러나 DNS조회는 대량의 트레픽 프레임을 만들기 때문에 제한된 라우터 용량으로는 크롤링 속도가 제한되었다.

4. Crawling Application

크롤링 어플리케이션은 주어진 URL 시드에서 부터 너비우선 크롤링을 하여 각 페이지에서 파싱된 URL의 이전 발생 여부가 없으면 배치 관리자에게 넘긴다. 다운로드 된 파일은 압축하여 저장소 관리자로 전달된다. 두 가지 유의할 점으로는 첫 번째로 각 페이지의 다수의 하이퍼링크 때문에 중복을 제거한 후에 발생하는 URL 크기는 빠르게 증가하여 메인 메모리 크기를 초과할 수 있다. 두 번째로 이 과정에서 한 페이지에서 파싱되어 관리자에게 전송된 페이지는 오래 후에 다운로드 되므로 어플리케이션이 새로운 요청을 동적 데이터 구조에 즉시 삽입 할 이유가 없다.

5. Scaling the System

해당 논문에서의 주요 목표 중 하나는 적은 비용의 워크 스테이션을 추가하여 추가 구성요소를 확장할 수 있는 시스템을 설계하는 것이다. 하나의 매니저에게 가장 적절한 갯수인 8개의 다운로더와 2~3개의 DNS Resolver의 수를 넘어가게 되면 두 번째 크롤 관리자를 만들고 어플리케이션은 두 가지 관리자 간의 요청을 분리해야 한다.

 인터넷 아카이브 크롤러와 같이 너비우선 크롤러를 4개의 구성요소로 분할하는 것은 간단하다. URL을 해시함수를 사용하여 4가지 하위 집합으로 분리한다. 각 하위 구성요소는 하나의 하위 집합을 처리하고 요청하는 역할을 한다. 매니저는 각자의 어플리케이션에서 다운받은 페이지들을 분리된 디렉토리에 저장하였는지 확인한다. 파싱하는 동안 구성요소가 다른 하위 집합에 속한 URL은 해시값대로 구성요소가 옮겨간다. 크롤링 관리자는 완전히 다르게 표시된 두 개의 어플리케이션 구성요소에서 사용할 수 있다.

 특정한 서버에 대한 과도한 부하를 각 호스트가 최대 하나의 하위 집합에 매핑되도록 하여 피할 수 있다. 일부 집중형 크롤러는 병렬화되기 어렵지만 피할 수 없고 디자인에 국한될 수 없다. 상당 양의 데이터가 포함된 유일한 통신은 다운로드 한 파일의 전송이다. 따라서

다운로드된 파일들이 원격 위치에서 끝날 수 있다는 사실을 감안할 떄 광역분산 환경에서도 시스템을 사용할 수 있다. )

네트워크 성능

다운로더가 NFS를 통해 크롤링 어플리케이션에 페이지들을 저장한 후 어플리케이션이 파일을 읽어 파싱을 하고 스토리지 매니저가 NFS를 통해 영구적인 레파지토리에 복사한다. 이러한 데이터의 이동 과정들이 네트워크를 통해 이루어지게 되며 병목현상이 일어날 수 있게 되자 네트워크의 성능이 중요해졌다. 이를 위한 솔루션으로 병목 현상이 발생하자 마자 어플리케이션을 여러 구성요소로 분할하여 시스템을 확장한다. 다른 방법으로는 기가 비트 이터넷으로 업데이트 하고 rcp를 사용하도록 전환하면 병목현상을 크게 제거할 수 있다.

 

URL Handling

하이퍼 링크는 구문 분석만 하고 색인을 하지 않기 때문에 속도가 훨씬 느려진다. 이를 해결하기 위하여 하이퍼 링크들을 연관된 링크들로 정규화를 한다. 정규화된 하이퍼 링크들은 지금까지 다운로드 되고 접해본 모든 URL에 대하여 검사한다. 이때 URL은 빠르게 증가하며 메모리 크기를 초과하게 된다.
몇가지 해결책 중에 Bloom Filter(원소가 집합에 속하는지 여부만 확인하는 자료구조)를 사용하는 방법이 있는데 어떠한 페이지는 다운로드되지 않는 문제가 있고 무손실 압축으로 URL의 크기를 줄이는 방법은 크롤링에 문제가 생긴다. 다른 방법으로 어플리케이션을 분할하여 여러 기계에 데이터를 저장해도 주 메모리에서 병목 현상이 생긴다.
이보다 확장성있는 솔루션으로 디스크 상주 구조를 사용하는 방법이 있다. URL들을 메인 메모리에 Red-Black 트리 구조로 저장을 하고 새로 URL을 발견하였을 때 메인 메모리에 버퍼링하고 메모리 상주데이터를 디스크 상주데이터로 간단한 스캔 및 복사 작업을 통해 주기적으로 합병한다. 합병을 하는 동안 모든 조회와 삽입을 수행한다. 병합은 새로운 스레드를 생성하여 수행하므로 어플리케이션은 계속해서 새로운 파일을 파싱할 수 있다. 이 방법을 통해서 디스크 랜덤 접근을 완전히 피하거나 적어도 URL 접근으로 선형적인 확장을 하는 것을 목표로 한다.

 

Domain-Based Throttling (도메인 기반 조절)

일부 도메인들은 웹 서버 수가 많지만 네트워크 속도가 느려서 크롤러에 영향을 받을 수 있고 대규모 조직의 많은 서버에 단시간에 많은 접촉이 있으면 경보가 울리기도 한다. 마지막으로 크롤러는 IP 주소가 아닌 호스트 이름에 기반한 접근에 타임아웃이 발생하고 같은 시스템에 웹 서버들이 배치되어 있는지를 감지하지 못한다. 이러한 문제의 해결책으로 크롤 어플리케이션에서 도메인 기반 제한을 해결하기로 하였다. URL을 랜덤한 순서로 섞이 같은 도메인의 URL들이 균등하게 분산되도록 한다.

 

Crawl Manager Data Structures

크롤링 매니저는 N초룰과 로봇룰을 지키면서 다운로더에 요청들을 스케줄링하기 위해 여러 데이터 구조를 유지한다.
요청 파일들을 리스트에 포함하기 위한 FIFO 요청 큐와 호스트 이름으로 구성되고 여러 호스트 데이터 구조로 구성된 URL을 포함하는 FIFO 호스트 큐가 여러개 있다.
호스트 구조로는
1.
현재 매니저에 있는 각 호스트들의 목록과 호스트 큐에 대한 포인터를 가진 호스트 dictionary
2.
다운로드 준비가 되어 있는 포인터를 가진 우선순위 큐
3.
최근에 액세스되어 다시 접촉되기 위해 30초를 기다리는 호스트들에 대한 포인터가 있는 우선순위 큐
이러한 세가지 구조가 있다.
매니저는 요청 번호가 있는 URL을 전송받으면서 목표는 요청간의 간격을 관찰하고 고성능을 달성하면서 많이 보관하는 것이다. 준비 큐의 호스트 포인터는 해당 호스트 큐의 첫번째 URL의 요청 번호를 키로 가져 최소 키값 추출로 다운로드 준비가 완료된 전체 URL 중에 가장 작은 요청 번호를 가진 URL을 선택하여 다운로더에 보낸다. 페이지가 다운로드 된 후에 호스트에 대한 포인터는 대기 큐에 삽입되고 키 값은 호스트에 다시 액세스 할 수 있는 시간과 같다. 우선 순위 큐의 키 값의 최소 인자의 대기시간을 확인하고 대기시간이 지났을 때 추출하여 호스트 대기 큐로 다시 전송한다.
새 호스트에 직면하면 우리는 먼저 호스트 구조를 생성하고 호스트 dictionary에 넣는다. DNS 분석과 로봇 배제가 끝나면 호스트에 대한 포인터를 대기 큐에 삽입한다. 모든 URL들이 큐에 다운로드 되었을 때 호스트는 구조에서 삭제 된다. 그러나 로봇 파일의 특정 정보는 다른 URL이 들어왔을 때 재인증을 할 필요가 없도록 유지된다. 마지막으로 만약 호스트가 응답하지 않으면 호스트를 대기 큐에 잠시 넣고 두 번째 시도에도 대기 상태이면 더 오래 기다린다. 어플리케이션은 특정 호스트의 시간 초과를 결정하여 서버의 중요도에 따라 더 빨리 혹은 더 느리게 크롤링하도록 결정할 수 있다.

 

Scheduling Policy and Manager Performance

URL을 바로 DB에 계속 삽입을 하면 빠르게 메모리 사이즈를 초과하게 되므로 크롤링 된 페이지수와 타임아웃 간격, 대기 큐의 상태 등을 고려하여 삽입을 해야 한다. I/O 성능에 대하여도 고민을 해야 한다. 다운로드 되길 기다리는 URL들과 호스트의 수와 계속 자라는 큐로 인해 메인 메모리가 부족해지게 된다. 그러므로 가장 많은 액티브 데이터 페이지를 저장하기 충분한 메인 메모리를 가지고 있다면 합리적인 캐싱 정책이 잘 작동 될 것으로 기대 된다
여러 요청이 들어오는 경우에는 같은 호스트의 페이지에 두개의 요청이 들어오면 어플리케이션에서 요청된 순서대로 다운로드 될 것이다. 다른 호스트의 페이지에 대한 두 요청은 하나의 호스트 대기열이 제한 시간 규칙 또는 호스트가 다운되거나 요청이 없거나 로봇룰, DNS 확인 중 지연 과 같은 과정이 일어나지 않는 한 발급된 순서로 다운로드 된다.

 

결과와 경험

- 실험을 하는 환경이 학교여서 학교 외부의 공격 등으로 인해 원활하게 진행이 되지 않았다. 그리고 다른 사용자에게 미치는 영향을 최소화 하기 위해 크롤러의 속도를 제어해야 하는 한계가 있었다. 속도를 조절하기 위해서는 크롤링 매니저에게 연결 되는 다운로더의 수를 변경하기도 하였다. 상황에 따라 변경이 어렵기 때문에 각 속도 설정에 대한 연결 수에 대한 절대적인 상한으로 연결 수를 수정하는 솔루션으로 끝맺었다.
-
실험에 사용 된 하드웨어도 영향을 많이 끼쳤다. 하드웨어의 양에 따라서 CPU, 메모리, 디스크의 배치에 대한 이해가 필요하다. 다운로더는 CPU의 대부분을 차지하지만 메모리는 거의 차지하지 않는다. 매니저는 몇 명의 다운로더가 연결되어 있는지에 따라 CPU 사용 시간이 거의 필요하지 않으며 DB용으로 적당한 양의 버퍼 공간이 필요하다. 어플리케이션은 CPU와 메모리를 모두 필요로 하므로 다운로더와 함께 배치하면 안된다.











논문을 읽으면서


* 스스로에게 칭찬할만한 점

느리더라도 읽는 부분을 이해하려고 노력하며 몇번이고 다시 읽은 점

보면서 몰랐던 주변 지식들(NFS)등도 같이 찾아보며 공부한 점


* 부족했던 점

무언가를 할 때 그것에 대한 목적이 뚜렷할 때 더 효율이 높아지는데 중요한 부분이 무엇일지에 대한 생각없이 읽은 점

영어 해석 능력...😂

속도!


* 기타 소감

학습에 있어서 why에 대한 고민의 중요성을 한번 더 깨달았다

지금 모르는 것에 다급해하지 말고 발표 끝나고 말씀 해 주셨듯이 부족한 부분은 아직 생소해서 그런 것이라는 생각을 가지고 차분하게 꾸준히 노력하면 좋겠다.



논문을 정리하면서


* 스스로에게 칭찬할만한 점

읽은 대로 정리했을 때 정리한 글이 너무 긴 줄글이어서 줄이기 위한 노력들을 한 것


* 부족했던 점

정리를 했음에도 불구하고 문장이 너무 장황한 줄글이었음

나만 이해할 것 같이 정리를 했음


* 기타 소감

논문을 정리하는 과정에서 단락단위로 읽고 정리하는 방식으로 진행을 했는데 일단 쭉 읽어보고 다시 읽으면서 머릿속에 정리가 되면 글로 정리하는게 좋을 것 같다

아직은.... 감이 안잡히지만 정리의 방식을 바꿔야 할 것 같다 (키워드 중심의 트리식 정리 후에 문장으로 만들기 처럼...) -> 충분히 고민해 보아야 할 부분



발표하면서


* 스스로에게 칭찬할만한 점

스스로는 이해를 하고 발표를 했다는 점 (과연...?)


* 부족했던 점

발표의 중요한 부분은 상대방이 이해할 수 있도록 준비를 해야 한다는 점을 놓친 것

혼자서 볼 때는 그냥 슥 읽어서 놓친 오타, 글의 구조(? 구성?)들이 있었음. 혼자서도 입밖으로 읽으면서 준비하는게 중요한데 이번에도 안한것~~~


* 기타 소감

글만으로는 상대방을 이해시키기가 어렵다. 특히 나처럼 줄글은!

그림, 표, 등등... 을 같이 사용해 주면 다음번은 더 나아지지 않을까!

반응형

+ Recent posts