equals 메서드

==연산자는 객체의 주소 값만을 비교하기 때문에 객체의 상태가 동일한지 알 수 없다. 이때 equals 메서드를 오버라이딩 하여 객체의 동등성 비교를 위한 로직을 구현할 수 있다.

다음과 같이 equals 메소드 내부구현에는 두 객체의 동등성을 어떻게 정의할 것인가 에 대한 내용을 담게된다.

@Override
public boolean equals(Object o) {
  if (this == o) {
    return true;
  }
  if (o == null || Hibernate.getClass(this) != Hibernate.getClass(o)) {
    return false;
  }
  Product product = (Product)o;
  return id != null && Objects.equals(id, product.id);
}

위의 코드에서는 id 값을 통해 객체의 동등성을 확인하도록 equals 메서드를 정의하였다.

주의사항

equals 메서드의 설명에는 다음과 같이 equals를 정의하면 hashcode를 정의하라는 구절이 있다.

Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.

만약 equals만 재정의하고 hashcode를 재정의하지 않는다면, HahsMap, HashSet, Hashtable 과 같은 해시 기반의 컬렉션에서 객체가 제대로 동작하지 않을 수 있다.

HashCode를 정의하지 않는다면

다음의 Sample 클래스는 equals만 정의하고 hashCode를 정의하지 않았다.

Sample.class

class Sample {
    String key;
    String value;

    public Sample(String key, String value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Sample sample = (Sample) o;
        return Objects.equals(key, sample.key);
    }
}

이때 Sample 클래스를 활용해 HashSet을 만들고 테스트를 해보면

Main 함수

public static void main(String[] args) {
    HashSet<Sample> samples = new HashSet<>();

    Sample sample1 = new Sample("a", "b");
    Sample sample2 = new Sample("a", "b");
    samples.add(sample1);

    System.out.println(sample1.hashCode());
    System.out.println(sample2.hashCode());
    System.out.println(samples.contains(sample2));
}

위의 코드에서 sample1, sample2 객체는 같은 key필드 값을 가지고 있다.
key 필드를 통해 객체의 동등성을 비교하기 때문에 결과는 true가 나오길 기대하지만, 실제 결과는 false이다.

실행 결과

798154996
681842940
false

Smaple 클래스는 hashCode를 오버라이딩 하지 않아 서로 다른 hashCode를 갖고있기 때문이다.

수정후

hashCode를 오버라이딩하고 실행하면 다음과 같이 기대한 결과가 나오는것을 확인할 수 있다.

class Sample {
    String key;
    String value;

    public Sample(String key, String value) {
        this.key = key;
        this.value = value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Sample sample = (Sample) o;
        return Objects.equals(key, sample.key);
    }

    @Override
    public int hashCode() {
        return Objects.hash(key);
    }
}

실행결과

128
128
true

참고

https://stackoverflow.com/questions/2265503/why-do-i-need-to-override-the-equals-and-hashcode-methods-in-java

로버트 L. 글래스 - 이론 vs 실무

건축처럼 역사가 오래된 여느 다른 공학 분야에 비해 상대적으로 짧은 소프트웨어 분야의 역사를 감안 했을 때, 소프트웨어 분야는 아직 걸음마 단계다. 따라서 이론보다 실무가 더 앞서 있으며, 실무가 더 중요하다.

로버트 마틴 - 소프트웨어 모듈의 세 가지 목적

  1. 실행 중에 제대로 동작해야 한다.
  2. 변경을 위해 존재해야 한다.
  3. 코드를 읽는사람과 의사소통해야 한다.

예상을 빗나가는 코드

    public class Theater {
        private TicketSeller ticketSeller;

        public Theater(TicketSeller ticketSeller) {
            this.ticketSeller = ticketSeller;
        }

        public void enter(Audience audience) {
            if(audience.getBag().hasInvitation()) {
                Ticket ticket = ticketSeller.getTicketOffice().getTicket();
                audience.getBag().setTicket(ticket);
            } else {
                Ticket ticket = ticketSeller.getTicketOffice().getTicket();
                audience.getBag().minusAmount(ticket.getFee());
                ticketSeller.getTicketOffice().plusAmount(ticket.getFee());
                audience.getBag().setTicket(ticket);
            }
        }
    }

이해가능한 코드란 그 동작이 우리의 예상에서 크게 벗어나지 않는 코드다. 현실에서는 관람객이 직접 자신의 가방에서 초대장을 꺼내 판매원에게 건네지만, 앞선 코드에서는 그렇지않다. 이는 우리의 상식과 너무나 다르게 동작하기에, 코드를 읽는 사람과 제대로 의사소통 하지 못한다.

또한 Theater의 enter메서드를 이해하기 위해서는 Audience와 Bag의 내부 구현을 이해하고 있어야 한다. enter 메서드는 다른 도메인의 너무 많은 세부사항을 다루기 때문에 코드를 읽고 이해해야 하는 사람 모두에게 큰 부담을 준다.

변경에 취약한 코드

TheaterAudience와 Bag의 세부사항에 강하게 의존하고 있기에 해당 객체들의 세부사항이 변경되면, Theater도 변경이 되어야 한다.

객체 사이의 의존성을 완전히 없애는 것이 정답은 아니다. 객체 지향 설계는 서로 의존하면서 협력하는 객체들의 공동체를 구축하는것이다.

따라서 우리의 목표는 최소한의 의존성만 유지하고, 불필요한 의존성을 제거하는 것이다.

자율적인 존재로 만들자

TheaterAudienceTicketSeller에 관해 너무 세세한 부분까지 알 필요가 없도록, 관람객이 스스로 가방안의 현금과 초대장을 처리하고, 판매원이 스스로 매표소의 티켓과 판매 요금을 다루게 하면 된다.

즉, 관람객과 판매원을 자율적인 존재로 만들면 되는 것.

티켓을 관람객의 가방에서 꺼내서 확인하고 판매하는 역할은 소극장의 역할이 아니다. 가방에서 티켓을 꺼내는것은 관람객의 역할이며, 티켓을 판매하는 역할은 판매원의 역할이다.

각자의 역할에만 충실하도록 코드를 변경하자.

티켓 판매

소극장 -> 판매원

    public class Theater {
        private TicketSeller ticketSeller;

        public Theater(TicketSeller ticketSeller) {
            this.ticketSeller = ticketSeller;
        }

        public void enter(Audience audience) {
            ticketSeller.toSell(audience);
        }
    }
    public class TicketSeller {
        private TicketOffice ticketOffice;

        public TicketSeller(TicketOffice ticketOffice) {
            this.ticketOffice = ticketOffice;
        }

        public void toSell(Audience audience) {
            if(audience.getBag().hasInvitation()) {
                Ticket ticket = ticketOffice.getTicket();
                audience.getBag().setTicket(ticket);
            } else {
                Ticket ticket = ticketOffice.getTicket();
                audience.getBag().minusAmount(ticket.getFee());
                ticketOffice.plusAmount(ticket.getFee());
                audience.getBag().setTicket(ticket);
            }
        }
    }

TicketSeller 에서 getTicketOffice메서드가 제거되어 외부에서는 TicketOffice에 접근할 수 없게되었다. 따라서 TicketSellerTicketOffice에 대한 작업을 스스로 수행해야한다.

이렇게 객체 내부의 세부사항을 감추는것을 캡슐화 라고 하며, 캡슐화를 통해 객체와 객체 사이의 결합도를 낮추어 변경이 쉬운 객체를 만들 수 있다.

티켓 확인

소극장 -> 관람객

    public class TicketSeller {
        private TicketOffice ticketOffice;

        public TicketSeller(TicketOffice ticketOffice) {
            this.ticketOffice = ticketOffice;
        }

        public void toSell(Audience audience) {
            ticketOffice.plusAmount(audience.buy(ticketOffice.getTicket()));
        }
    }
    public class Audience {
        private Bag bag;

        public Audience(Bag bag) {
            this.bag = bag;
        }

        public Long buy(Ticket ticket) {
            if(bag.hasInvitation()) {
                bag.setTicket(ticket);
                return 0L;
            } else {
                bag.setTicket(ticket);
                bag.minusAmount(ticket.getFee());
                return ticket.getFee();
            }
        }
    }

TicketSellerAudience의 buy 메서드에만 의존하게 된다. 즉 관객이 스스로 가방을 확인하고 티켓 구매를 진행한다. 외부에는 Bag을 노출하지 않게 되므로 Bag의 존재를 내부로 캡슐화 할 수 있게 된다.

개선점

수정된 AudienceTicketSeller는 자신이 가지고 있는 소지품을 스스로 관리한다. 이것은 우리의 예상과 정확하게 이라하며, 코드를 읽는 사람과의 의사소통이라는 관점에서 개선된 것이다.

또한 AudienceTicketSeller의 내부 구현을 변경하더라도 Theater를 변경 할 필요가 없으므로 변경 용이성 측면에서도 개선이 된것이다.

객체지향의 핵심은 캡슐화를 이용해 의존성을 적절히 관리함으로써 객체 사이의 결합도를 낮추는 것이다. 객체지향 코드는 각 객체의 문제를 스스로 처리한다는 점에서 이해하기 쉽고, 객체 내부의 변경이 외부에 영향을 주지 않으므로 변경하기가 수월하다.

개선 방향

설계를 어렵게 만드는 것은 의존성이다. 다른 객체가 몰라도 되는 불필요한 세부사항을 객체 내부로 캡슐화하여 자율적인 객체들이 낮은 결합도와 높은 응집도를 가지고 협력하도록 최소한의 의존성을 남기는것이 휼륭한 객체지향 설계다.

더 개선해 보기

TicketSellerAudience는 자율적인 객체가 되었다. 하지만 TicketOfficebag은 여전히 수동적인 존재이다. 이 둘도 자율적인 객체로 개선할 수 있다.

   public class Bag {
       private Long amount;
       private Invitation invitation;
       private Ticket ticket;

       public Bag(Long amount) {
           this(null,amount);
       }

       public Bag(Invitation invitation, long amount) {
           this.amount = amount;
           this.invitation = invitation;
       }

       public Long hold(Ticket ticket) {
           if(hasInvitation()) {
               setTicket(ticket);
               return 0L;
           } else {
               setTicket(ticket);
               minusAmount(ticket.getFee());
               return ticket.getFee();
           }
       }

       private boolean hasInvitation() {
           return invitation != null;
       }

       public boolean hasTicket() {
           return ticket != null;
       }

       private void setTicket(Ticket ticket) {
           this.ticket = ticket;
       }

       private void minusAmount(Long amount) {
           this.amount -= amount;
       }

       public void plusAmount(Long amount) {
           this.amount += amount;
       }
   }
    public class Audience {
      public Long buy(Ticket ticket) {
        return bag.hold(ticket);
      }
    }

캡슐화를 통해 Audiencebag의 인터페이스에만 의존하게 된다.

    public class TicketOffice {
        private Long amount;
        private List<Ticket> tickets = new ArrayList<>();

        public TicketOffice(Long amount, Ticket ... tickets) {
            this.amount = amount;
            this.tickets.addAll(Arrays.asList(tickets));
        }

        public void sellTicketTo(Audience audience) {
            plusAmount(audience.buy(getTicket()));
        }

        private Ticket getTicket() {
            return tickets.remove(0);
        }

        public void minusAmount(Long amount) {
            this.amount -= amount;
        }

        private void plusAmount(Long amount) {
            this.amount += amount;
        }

    }
    public class TicketSeller {
      public void sellTo(Audience audience) {
        ticketOffice.sellTicketTo(audience);
      }
    }

캡슐화를 통해 TicketSellerTicketOffice의 인터페이스에만 의존하게 된다. 하지만 TicketOffice는 판매를 위해 Audience에 의존하게 된다. 새로운 의존성이 추가되었으므로, 전체 설계의 관점에서는 결합도가 상승한것이다.

결합도를 낮추는것이 우선일까 각 객체의 자율성을 만족시키는것이 우선일까?
설계는 트레이드오프의 산물이다. 모든 사람을 만족시킬 수는 없다.

의인화

bagTicketOffice, Theater는 현실세계에서는 자율적인 존재가 아니다. 하지만 객체지향 세계에서는 모든 객체들이 자율적으로 행동한다.
이처럼 모든 객체를 능동적이고 자율적인 존재로 객체를 설계하는 원칙을 의인화 라고 한다.

좋은 설계

요구사항이 항상 변경되기에 변경을 수용할 수 있는 설계가 중요하다. 또한 코드를 변경할 때 버그가 추가될 가능성이 높기 때문에 최소한의 변경으로 요구사항을 만족시킬 수 있어야 한다.

변경할 수 있는 코드는 이해하기 쉬운 코드다. 이해하기 쉬운 코드만이 코드를 선뜻 수정하게 만든다.

개인적으로

각 객체의 자율성을 만족시키는 것이 우선일까? 결합하는 낮추는 것이 우선일까? 트레이드오프는 개발 공부를 하면서 매번 듣고 느끼는 단어입니다.

협업하면서 정답이 없는 문제에 대해 논쟁하다 보면 정말 정답은 없지만, 상황에 따라 조금 더 타당한 논리가 지지받습니다. 때문에 코드 작성 만큼 다양한 관점에서의 논리와 해석을 많이 찾아보면서 자신만의 논리를 찾아가는 과정도 중요하다 생각합니다.

오브젝트 책을 통해 논리가 분명한 설계를 할 수 있게 되길 바랍니다.

'Book Review' 카테고리의 다른 글

[오브젝트] 합성과 유연한 설계  (0) 2022.11.18

Spring MVC 

Spring MVC는 웹 애플리케이션을 개발하거나 구축하기 위한 웹 MVC 프레임워크입니다.

 

Spring Boot

Spring Boot는 Spring MVC와 달리 내장 웹 컨테이너에서 애플리케이션을 실행하기 때문에 JAVA만 설치되어 있으면 개발이 가능합니다. 또한 Spring Boot에는 웹 애플리케이션을 개발하기 위한 다양한 의존성들이 있어, 빠르게 Spring 기반 애플리케이션을 개발하거나 구축할 수 있습니다.

 


WebContainer

웹 컨테이너는 자바 서블릿과 상호작용하는 웹서버 컴포넌트로, 서블릿의 생명주기를 관리하고 URL과 서블릿을 매핑하는 역할을 합니다. 그중 대표적인 웹 컨테이너가 톰캣인데, 스프링 부트에는 내장 톰캣이 포함되어 있습니다.

 

때문에 Spring MVC를 사용하면 톰캣을 배포하고 연동하는 과정을 거쳐야 하지만, Spring Boot를 사용하면 이러한 과정이 불필요합니다. 그렇다면 내장 톰캣을 사용하였을 때 단점은 없을까요?

 

EmbeddedTomcat을 사용하였을때 

포트번호

서비스마다 포트 번호가 다르기 때문에 모든 URL을 관리하고 모니터링하는 것이 번거로울 수 있습니다.

하나의 톰캣 서버에 연동된 3개의 스프링 애플리케이션을 동작시키기 위해, 서비스 A의 포트번호는 8081, 모듈 B는 8082, 모듈 C는 8083과 같이 구성해야 합니다.

톰캣 서버수 증가

여러 스프링 부트 애플리케이션을 사용할 경우 각 애플리케이션마다 내장 톰캣 서버가 존재하기 때문에 관리해야 할 톰캣 서버가 늘어난다는 단점이 있습니다. 하지만 이러한 단점은 각 서비스의 메모리, 스레드 및 JVM과 같은 전반적인 상태 및 리소스를 모니터링하는 spring-boot-admin을 사용하여 이 문제를 해결할 수 있습니다. 또한 이러한 문제는 마이크로서비스를 구축할 때 유연성을 제공하기 때문에 장점이 됩니다.

 

 

참고

https://medium.com/javarevisited/spring-boot-with-embedded-tomcat-pros-and-cons-84da418e7af1

https://stackoverflow.com/questions/61006680/spring-boot-microservices-embedded-tomcat-vs-external-tomcat

문제 상황

회원가입 로직을 Optional을 활용하여 리팩토링하다 생긴 문제입니다.

다음과 같이 provider와 providerId를 통해 유저를 조회하여 해당하는 유저가 존재하지 않는다면 새로운 유저를 생성하도록 구현하였습니다.

return userRepository
    .findByProviderAndProviderId(provider, providerId)
    .orElse(createUser(oAuth2User, provider));

이때 중복된 provider, providerId로 인해 SQL integrity violation exception이 발생하였습니다.

디버깅을 해보니 findByProviderAndProviderId 메서드를 동해 유저를 조회하였을때,

유저가 존재하든 존재하지 않든 createUser메서드를 호출하여 생긴 문제였습니다.

 

이는 다음과 같이 orElse를 orElseGet으로 변경하여 해결하였습니다.

return userRepository
        .findByProviderAndProviderId(provider, providerId)
        .orElseGet(() -> createUser(oAuth2User, provider));

Optional 구현체

  public T orElse(T other) {
    return value != null ? value : other;
  }

  public T orElseGet(Supplier<? extends T> supplier) {
    return value != null ? value : supplier.get();
  }

orElse 메서드의 경우 값을 인자로 받는 반면 (Call by Value)

orElseGet의 경우 Supplier로 래핑된 값을 인자로 받는것을 확인할 수 있었습니다. (Call By Name)

 

이렇게 Supplier로 래핑되어있기때문에 createUser 메서드를 즉시 호출하지않고, 값이 존재하지 않을경우에만 값을 호출하게 됩니다.

이렇게 orElseGet을 잘 활용한다면, 불필요한 호출을 줄일 수 있습니다.

함수형 인터페이스 Supplier

Supplier 는 java8에 추가된 함수형 인터페이스로, "call by name" 을 통해 지연 연산(Lazy Evaluation)을 구현할 수 있습니다.

 

import java.util.function.Supplier;

public class Main {
    public static void main(String[] args) {
        // call by value
        String result = expensiveOperation(); 
        System.out.println(result); // expensive operation 수행
 
        // call by name (with Supplier)
        Supplier<String> resultSupplier = () -> expensiveOperation(); // expensive operation 수행 X
        System.out.println(resultSupplier.get()); // expensive operation 수행
    }

    public static String expensiveOperation() {
        // 오래 걸리는 연산
        return "Expensive Result";
    }
}

call by value 방식에서는, 함수 호출을 위해 expensiveOperation 메소드의 실행이 먼저 수행됩니다.
반면에 call by name 방식에서는, Supplier<String>를 사용하여 함수 호출이 실제로 필요한 시점(get() 메소드 호출 시점)까지 연산을 지연시킵니다.

Husky

husky는  .git/hooks 폴더를 건드리지 않고도 git hook 스크립트를 제어할 수 있게 해주는 툴입니다.

설정과정

1. Husky 설치

npm install husky jira-prepare-commit-msg --save-dev && npx husky install

 

2. 훅 생성

npx husky add .husky/prepare-commit-msg 'npx jira-prepare-commit-msg $1'

 

3. 훅 수정

 

.husky/prepare-commit-msg

#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

npx jira-prepare-commit-msg $1

$1이 빠져있을 수 있음

 

4. package.json 파일 수정

{
  "jira-prepare-commit-msg": {
    "messagePattern": "[$J] $M",
    "jiraTicketPattern": "([A-Z]+-\\\\d+)",
    "commentChar": "#",
    "isConventionalCommit": false,
    "allowEmptyCommitMessage": false,
    "gitRoot": ""
  }
}

 

5. 다음과 형태로 커밋 메시지 앞부분에 지라 티켓 번호 작성이 자동화 됩니다.

 

 

YCSB(Yahoo! Cloud Serving Benchmark)는 NoSQL 데이터베이스의 쿼리 테스트를 위한 오픈소스 벤치마킹툴입니다.

Load vs Run

YCSB에서의 load명령어는 데이터베이스에 데이터를 생성하며, run은 데이터베이스에서 가능한 명령어를 수행합니다. 즉, load명령은 insert 연산만을 수행하며, run의경우 YCSB의 workload에 따라 read, inserts, update를 수행합니다.

RocksDB

Load

./bin/ycsb load rocksdb -s -P workloads/workloada -p rocksdb.dir=../data -p recordcount=10000
[OVERALL], RunTime(ms), 343  
[OVERALL], Throughput(ops/sec), 29154.51895043732  
[TOTAL_GCS_G1_Young_Generation], Count, 1  
[TOTAL_GC_TIME_G1_Young_Generation], Time(ms), 8  
[TOTAL_GC_TIME_%_G1_Young_Generation], Time(%), 2.3323615160349855  
[TOTAL_GCS_G1_Old_Generation], Count, 0  
[TOTAL_GC_TIME_G1_Old_Generation], Time(ms), 0  
[TOTAL_GC_TIME_%_G1_Old_Generation], Time(%), 0.0  
[TOTAL_GCs], Count, 1  
[TOTAL_GC_TIME], Time(ms), 8  
[TOTAL_GC_TIME_%], Time(%), 2.3323615160349855  
[CLEANUP], Operations, 1  
[CLEANUP], AverageLatency(us), 1102.0  
[CLEANUP], MinLatency(us), 1102  
[CLEANUP], MaxLatency(us), 1102  
[CLEANUP], 95thPercentileLatency(us), 1102  
[CLEANUP], 99thPercentileLatency(us), 1102  
[INSERT], Operations, 10000  
[INSERT], AverageLatency(us), 15.312  
[INSERT], MinLatency(us), 5  
[INSERT], MaxLatency(us), 36223  
[INSERT], 95thPercentileLatency(us), 19  
[INSERT], 99thPercentileLatency(us), 36  
[INSERT], Return=OK, 10000  

Run

./bin/ycsb run rocksdb -s -P workloads/workloada -p rocksdb.dir=../data -p operationcount=10000
[OVERALL], RunTime(ms), 419  
[OVERALL], Throughput(ops/sec), 23866.34844868735  
[TOTAL_GCS_G1_Young_Generation], Count, 1  
[TOTAL_GC_TIME_G1_Young_Generation], Time(ms), 8  
[TOTAL_GC_TIME_%_G1_Young_Generation], Time(%), 1.9093078758949882  
[TOTAL_GCS_G1_Old_Generation], Count, 0  
[TOTAL_GC_TIME_G1_Old_Generation], Time(ms), 0  
[TOTAL_GC_TIME_%_G1_Old_Generation], Time(%), 0.0  
[TOTAL_GCs], Count, 1  
[TOTAL_GC_TIME], Time(ms), 8  
[TOTAL_GC_TIME_%], Time(%), 1.9093078758949882  
[READ], Operations, 4909  
[READ], AverageLatency(us), 4.262986351599103  
[READ], MinLatency(us), 1  
[READ], MaxLatency(us), 1720  
[READ], 95thPercentileLatency(us), 14  
[READ], 99thPercentileLatency(us), 26  
[READ], Return=OK, 4909  
[CLEANUP], Operations, 1  
[CLEANUP], AverageLatency(us), 1174.0  
[CLEANUP], MinLatency(us), 1174  
[CLEANUP], MaxLatency(us), 1174  
[CLEANUP], 95thPercentileLatency(us), 1174  
[CLEANUP], 99thPercentileLatency(us), 1174  
[UPDATE], Operations, 5091  
[UPDATE], AverageLatency(us), 18.371439795717933  
[UPDATE], MinLatency(us), 7  
[UPDATE], MaxLatency(us), 9679  
[UPDATE], 95thPercentileLatency(us), 37  
[UPDATE], 99thPercentileLatency(us), 80  
[UPDATE], Return=OK, 5091  

MongoDB

Load

./bin/ycsb load mongodb -s -P workloads/workloada -p mongodb.url="mongodb://127.0.0.1:27017/workload" -p recordcount=1000

url은 mongodb의 url을 입력하면 되며 뒤의 workloada는 workloada라는 이름의 데이터베이스를 만들어서 사용한다는 의미입니다.
YCSB의 workload에 대해서는 https://github.com/brianfrankcooper/YCSB/wiki/Core-Workloads

[OVERALL], RunTime(ms), 519
[OVERALL], Throughput(ops/sec), 1926.7822736030828
[TOTAL_GCS_G1_Young_Generation], Count, 1
[TOTAL_GC_TIME_G1_Young_Generation], Time(ms), 11
[TOTAL_GC_TIME_%_G1_Young_Generation], Time(%), 2.119460500963391
[TOTAL_GCS_G1_Old_Generation], Count, 0
[TOTAL_GC_TIME_G1_Old_Generation], Time(ms), 0
[TOTAL_GC_TIME_%_G1_Old_Generation], Time(%), 0.0
[TOTAL_GCs], Count, 1
[TOTAL_GC_TIME], Time(ms), 11
[TOTAL_GC_TIME_%], Time(%), 2.119460500963391
[CLEANUP], Operations, 2
[CLEANUP], AverageLatency(us), 1629.5
[CLEANUP], MinLatency(us), 2
[CLEANUP], MaxLatency(us), 3257
[CLEANUP], 95thPercentileLatency(us), 3257
[CLEANUP], 99thPercentileLatency(us), 3257
[INSERT], Operations, 1000
[INSERT], AverageLatency(us), 525.652
[INSERT], MinLatency(us), 156
[INSERT], MaxLatency(us), 95423
[INSERT], 95thPercentileLatency(us), 616
[INSERT], 99thPercentileLatency(us), 758
[INSERT], Return=OK, 1000

Run

./bin/ycsb run mongodb -s -P workloads/workloada -p mongodb.url="mongodb://127.0.0.1:27017/workload" -p operationcount=1000
[OVERALL], RunTime(ms), 459
[OVERALL], Throughput(ops/sec), 2178.649237472767
[TOTAL_GCS_G1_Young_Generation], Count, 1
[TOTAL_GC_TIME_G1_Young_Generation], Time(ms), 9
[TOTAL_GC_TIME_%_G1_Young_Generation], Time(%), 1.9607843137254901
[TOTAL_GCS_G1_Old_Generation], Count, 0
[TOTAL_GC_TIME_G1_Old_Generation], Time(ms), 0
[TOTAL_GC_TIME_%_G1_Old_Generation], Time(%), 0.0
[TOTAL_GCs], Count, 1
[TOTAL_GC_TIME], Time(ms), 9
[TOTAL_GC_TIME_%], Time(%), 1.9607843137254901
[READ], Operations, 475
[READ], AverageLatency(us), 577.9852631578948
[READ], MinLatency(us), 191
[READ], MaxLatency(us), 54559
[READ], 95thPercentileLatency(us), 658
[READ], 99thPercentileLatency(us), 976
[READ], Return=OK, 475
[CLEANUP], Operations, 2
[CLEANUP], AverageLatency(us), 1631.5
[CLEANUP], MinLatency(us), 2
[CLEANUP], MaxLatency(us), 3261
[CLEANUP], 95thPercentileLatency(us), 3261
[CLEANUP], 99thPercentileLatency(us), 3261
[UPDATE], Operations, 525
[UPDATE], AverageLatency(us), 354.56190476190477
[UPDATE], MinLatency(us), 188
[UPDATE], MaxLatency(us), 7471
[UPDATE], 95thPercentileLatency(us), 561
[UPDATE], 99thPercentileLatency(us), 698
[UPDATE], Return=OK, 525

'Infra' 카테고리의 다른 글

[Nginx] Nginx 도입기(with SSL)  (2) 2022.10.03
[Husky] 커밋메시지 JIRA 티켓번호 자동화  (0) 2022.08.22
[RocksDB] RocksDB 활용사례  (0) 2022.08.22
[RocksDB] Introduction  (0) 2022.08.22
[RocksDB] RocksDB Install  (0) 2022.08.22

+ Recent posts