728x90
반응형
SMALL

Part.2를 봤다면, CPU 사용량, 메모리 사용량, 톰캣의 쓰레드, DB 커넥션 풀과 같이 공통으로 사용되는 기술 메트릭은 이미 등록되어 있다. 이런 등록된 메트릭을 사용해서 대시보드를 구성하고 모니터링 하면 된다. 여기서 더 나아가서 비즈니스에 특화된 부분을 모니터링 하고 싶다. 예를 들어, 주문수, 취소수, 재고 수량 같은 메트릭들이 있다. 이 부분은 공통으로 만드는 게 아니라 각각의 비즈니스에 특화된 부분들이다. 

 

이런 메트릭도 시스템을 운영하는데 굉장히 많은 도움이 된다. 예를 들어, 취소수가 갑자기 급증하거나 재고 수량이 임계치 이상으로 쌓이는 부분들은 기술적인 메트릭으로 확인할 수 없는 우리 시스템의 비즈니스 문제를 빠르게 파악하는데 도움을 준다.

 

그래서 이런 자기만의 메트릭을 만들어보자.

 

우선 코드가 필요하다. 간단한 주문 관련 코드를 만들자.

 

OrderService

package hello.order;

import java.util.concurrent.atomic.AtomicInteger;

public interface OrderService {
    void order();

    void cancel();

    AtomicInteger getStock(); // Atomic : 멀티 쓰레드 환경에서 안전하게 값에 쓰기를 할 수 있는 방법
}

 

OrderServiceV0

package hello.order.v0;

import hello.order.OrderService;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class OrderServiceV0 implements OrderService {

    private final AtomicInteger stock = new AtomicInteger(100);

    @Override
    public void order() {
        log.info("주문");
        stock.decrementAndGet();
    }

    @Override
    public void cancel() {
        log.info("취소");
        stock.incrementAndGet();
    }

    @Override
    public AtomicInteger getStock() {
        return stock;
    }
}

 

OrderConfigV0

package hello.order.v0;

import hello.order.OrderService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV0 {
    @Bean
    OrderService orderService() {
        return new OrderServiceV0();
    }
}

 

OrderController

package hello.controller;

import hello.order.OrderService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@Slf4j
@RestController
public class OrderController {

    private final OrderService orderService;

    public OrderController(OrderService orderService) {
        this.orderService = orderService;
    }

    @GetMapping("/order")
    public String order() {
        log.info("order");
        orderService.order();
        return "order";
    }

    @GetMapping("/cancel")
    public String cancel() {
        log.info("cancel");
        orderService.cancel();
        return "cancel";
    }

    @GetMapping("/stock")
    public int stock() {
        log.info("stock");
        return orderService.getStock().get();
    }
}

 

간단한 OrderService 인터페이스와 버전별 구현체가 등록될 것이고 컨트롤러가 있다.

그리고 버전별로 계속 빈으로 등록될 서비스와 설정 클래스가 달라지기 때문에 스캔 경로도 수정해줘야 한다.

ActuatorApplication

package hello;

import hello.order.v0.OrderConfigV0;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

@Import(OrderConfigV0.class)
@SpringBootApplication(scanBasePackages = "hello.controller")
public class ActuatorApplication {

    public static void main(String[] args) {
        SpringApplication.run(ActuatorApplication.class, args);
    }

}

 

이제 주문, 취소, 현재 재고수량에 관련된 컨트롤러에 접속하면 정상적으로 잘 노출되는 것을 확인할 수 있을것이다. 이제 메트릭을 만들어보자.

 

메트릭 등록 V1 - 카운터

우선, 하나씩 증가하는 메트릭을 만들어보자.

메트릭을 등록하려면 먼저 MeterRegistry 라는 클래스를 주입받아야 한다. 액츄에이터를 사용하면 이 MeterRegistry는 자동으로 빈으로 등록된다. 그래서 이 녀석을 통해 카운터나 게이지를 등록해보자.

 

OrderServiceV1

package hello.order.v1;

import hello.order.OrderService;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class OrderServiceV1 implements OrderService {

    private final AtomicInteger stock = new AtomicInteger(100);

    private final MeterRegistry meterRegistry;

    public OrderServiceV1(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @Override
    public void order() {
        log.info("주문");
        stock.decrementAndGet();

        Counter.builder("my.order")
                .tag("class", this.getClass().getName())
                .tag("method", "order")
                .description("order")
                .register(meterRegistry)
                .increment();
    }

    @Override
    public void cancel() {
        log.info("취소");
        stock.incrementAndGet();

        Counter.builder("my.order")
                .tag("class", this.getClass().getName())
                .tag("method", "cancel")
                .description("order")
                .register(meterRegistry)
                .increment();
    }

    @Override
    public AtomicInteger getStock() {
        return stock;
    }
}

위 코드를 보면, 먼저 MeterRegistry를 주입받고, order()cancel()에서 메트릭을 만들고 있다.

그리고 Counter라는 io.micrometer.core.instrument 패키지에 있는 인터페이스를 통해 카운터 메트릭을 등록한다.

 

builder("my.order")는 메트릭의 이름으로 표현될 부분이고, tag는 메트릭에 달린 태그들을 만들어준다. 그리고 register(meterRegistry)로 메트릭으로 등록한 후에 메서드가 호출될 때 한번씩 그 값을 증가시키는 increment()를 호출한다.

 

이렇게 하면 끝이다. 이제 V1 Config 클래스를 만들어서 빈으로 등록하자.

 

OrderConfigV1

package hello.order.v1;

import hello.order.OrderService;
import hello.order.v0.OrderServiceV0;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV1 {
    @Bean
    OrderService orderService(MeterRegistry meterRegistry) {
        return new OrderServiceV1(meterRegistry);
    }
}

 

ActuatorApplication

package hello;

import hello.order.v0.OrderConfigV0;
import hello.order.v1.OrderConfigV1;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Import;

//@Import(OrderConfigV0.class)
@Import(OrderConfigV1.class)
@SpringBootApplication(scanBasePackages = "hello.controller")
public class ActuatorApplication {

    public static void main(String[] args) {
        SpringApplication.run(ActuatorApplication.class, args);
    }

}

 

이렇게 한 후 실행하면 `/actuator/metrics`에 가보면 내가 만든 메트릭이 안 보일건데 그 이유는 한번은 호출이 되어야 메트릭으로 보여진다. 그래서 먼저 주문과 취소를 몇번씩 호출해보자. 호출하고 다시 가보면 이렇게 보인다.

그리고 더 자세한 내용을 보기 위해 `/actuator/metrics/my.order`로 가보자. 아래처럼 내가 만든 메트릭이 보여진다.

 

아주 훌륭하다. 이제 주문이 몇번됐는지 취소는 몇번됐는지를 추적할 수 있게 됐다.

`/actuator/prometheus`로도 가보자. 여기는 카운터이니까 `_total`이 관례상 붙을거다.

 

메트릭이 잘 보여진다. 주문과 취소수를 별개로 데이터를 가지고 있다. 이렇게 프로메테우스에도 잘 보여지니 그라파나로 대시보드를 구성해보자.

 

카운터는 게이지와 달리 오르기만 하기 때문에 주문이 팍 터진 지점이 언제인지 이런것을 확인하기가 어려워서 increase()를 사용했다.

이제 주문수와 취소수도 대시보드로 볼 수 있게 됐다. 이렇게 나만의 메트릭을 만들수가 있다.

 

근데 여기서 한가지 아쉬운 부분이 있는데, 위 코드로 다시 돌아가보면 메트릭을 관리하는 로직은 핵심 비즈니스 개발 로직과 아무런 상관이 없다. 근데 코드가 섞여있다. 이러면? 공통 관심사와 핵심 관심사를 분리해서 더 좋은 코드로 만들수 있다. "AOP!"

 

메트릭 등록 - @Counted

딱 그냥 코드만 봐도 아 공통 관심사는 핵심 비즈니스 로직과 분리하고 싶다! 이런 생각이 들어야 한다. 그리고 그 생각이 들면서 자연스럽게 AOP도 떠올라야 한다. 근데 당연히 다들 이렇게 생각하기 때문에 이미 스프링은 다 대신 만들어 놨다.

 

OrderServiceV2

package hello.order.v2;

import hello.order.OrderService;
import io.micrometer.core.annotation.Counted;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class OrderServiceV2 implements OrderService {

    private final AtomicInteger stock = new AtomicInteger(100);

    @Override
    @Counted("my.order")
    public void order() {
        log.info("주문");
        stock.decrementAndGet();
    }

    @Override
    @Counted("my.order")
    public void cancel() {
        log.info("취소");
        stock.incrementAndGet();
    }

    @Override
    public AtomicInteger getStock() {
        return stock;
    }
}

완전 깔끔해졌다. 그냥 @Counted("my.order")만 붙여주면 끝난다. 이렇게 붙여주면, 메트릭 이름은 "my.order"가 된다. 태그도 클래스와 메서드 명으로 다 알아서 붙여준다. 그리고 한가지만 더 해주면 된다.

 

OrderConfigV2

package hello.order.v2;

import hello.order.OrderService;
import io.micrometer.core.aop.CountedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV2 {

    @Bean
    public OrderService orderService() {
        return new OrderServiceV2();
    }

    /**
     * 이 녀석이 꼭 빈으로 등록되어야 @Counted가 잘 동작한다!
     * */
    @Bean
    public CountedAspect countedAspect(MeterRegistry meterRegistry) {
        return new CountedAspect(meterRegistry);
    }
}

빈으로 CountedAspect를 꼭 등록해줘야 한다. 이렇게 하면 모든게 동일하게 동작한다.

 

태그가 메서드와 클래스 말고도 더 자세하게 추가됐다. 아주 좋다.

여기에 더 나아가서 Timer 라는 메트릭 측정 도구가 있다. 이것도 사용해보자.

 

메트릭 등록 - Timer 

Timer라는 건 카운터와 유사한데 실행 시간도 함께 측정할 수 있다. 다음과 같은 내용을 한번에 측정해준다.

  • seconds_count: 누적 실행 수 - 카운터
  • seconds_sum: 실행 시간의 합 - sum
  • seconds_max: 최대 실행 시간 (가장 오래걸린 시간) - 게이지
참고로, 이 최대 실행 시간은 서버가 띄워진 이래로 모든 데이터의 최대 실행 시간이 아니고 3분 정도 간격으로 계속 체크를 하기 때문에 서버가 최초에 띄워졌을때 걸린 최대 실행 시간이 있더라도 값은 변경될 수 있다.

 

그럼 이제 AOP를 사용하지 않고 직접 등록해 보는것부터 해보자.

OrderServiceV3

package hello.order.v3;

import hello.order.OrderService;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class OrderServiceV3 implements OrderService {

    private final AtomicInteger stock = new AtomicInteger(100);
    private final MeterRegistry meterRegistry;

    public OrderServiceV3(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }

    @Override
    public void order() {
        Timer timer = Timer.builder("my.order")
                .tag("class", this.getClass().getName())
                .tag("method", "order")
                .description("order")
                .register(meterRegistry);

        timer.record(() -> {
            log.info("주문");
            stock.decrementAndGet();

            sleep(500);
        });
    }

    private static void sleep(int l) {
        try {
            Thread.sleep(l + new Random().nextInt(200));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void cancel() {
        Timer timer = Timer.builder("my.order")
                .tag("class", this.getClass().getName())
                .tag("method", "cancel")
                .description("cancel")
                .register(meterRegistry);

        timer.record(() -> {
            log.info("취소");
            stock.incrementAndGet();

            sleep(200);
        });
    }

    @Override
    public AtomicInteger getStock() {
        return stock;
    }
}

이번엔 io.micrometer.core.instrument 패키지에 있는  Timer라는 녀석을 가져온다. 마찬가지로 직접 등록하기 때문에 MeterRegistry도 가져와야 한다. 그래서 아까 카운터를 등록할때와 유사하게 등록을 해주면 된다. 근데 이 Timer는 이 인스턴스로 받아서 record()안에서 실행 로직을 수행해야 한다. 그 점이 좀 다르다는 것 확인하고! 그리고 코드가 너무 짧기 때문에 뭐 실행 시간이나 최대 시간을 계산하는게 매우 의미없을 수 있어서 랜덤하게 sleep()을 적용했다.

 

OrderConfigV3

package hello.order.v3;

import hello.order.OrderService;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV3 {
    @Bean
    OrderService orderService(MeterRegistry meterRegistry) {
        return new OrderServiceV3(meterRegistry);
    }
}

 

 

설정 클래스에 빈으로 잘 등록해준다. 스프링 부트 실행 클래스에 이 설정 클래스로 바꾸는건 생략하겠다. 계속 했던거니까!

그리고 주문과 취소를 여러번 좀 요청한 다음 메트릭을 보면 이렇게 나온다.

카운터도 있는데 총 걸린 시간과 최대 시간이 나온다. 보면 MAX값이 0인 이유는 위에서 말한것처럼 3분 간격으로 계속 체크를 한다. 내가 지난 3분 동안 요청이 없어서 값이 0이다. 그리고 전체 걸린 시간과 카운트가 있으면? 평균시간도 구할 수 있다.

 

그래서 이 또한 그라파나로 이쁘게 가시화해보자.

 

최대 실행 시간에 대한 그래프

 

평균 실행 시간에 대한 그래프

 

이렇게 시간에 관련된 메트릭 데이터도 그라파나로 이쁘게 볼 수 있게 됐다. 이제 이 TimerAOP로 바꿔보자. 이미 스프링이 다 만들어 놓은것을 가져다가 사용만 하면 된다.

 

메트릭 등록 - @Timed

이제 AOP로 간단하게 등록해보자.

OrderServiceV4

package hello.order.v4;

import hello.order.OrderService;
import io.micrometer.core.annotation.Timed;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;

import java.util.Random;
import java.util.concurrent.atomic.AtomicInteger;

@Timed(value = "my.order")
@Slf4j
public class OrderServiceV4 implements OrderService {

    private final AtomicInteger stock = new AtomicInteger(100);

    @Override
    public void order() {
        log.info("주문");
        stock.decrementAndGet();
        sleep(500);
    }

    private static void sleep(int l) {
        try {
            Thread.sleep(l + new Random().nextInt(200));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void cancel() {
        log.info("취소");
        stock.incrementAndGet();
        sleep(200);
    }

    @Override
    public AtomicInteger getStock() {
        return stock;
    }
}

@Timed 애노테이션을 사용하면 된다. 이 애노테이션은 클래스 레벨도 가능하고 메서드 레벨도 다 가능하다.

아까 @Counted랑 다른게 없어서 바로 넘어가자.

 

OrderConfigV4

package hello.order.v4;

import hello.order.OrderService;
import io.micrometer.core.aop.TimedAspect;
import io.micrometer.core.instrument.MeterRegistry;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class OrderConfigV4 {
    @Bean
    OrderService orderService() {
        return new OrderServiceV4();
    }

    @Bean
    public TimedAspect timedAspect(MeterRegistry meterRegistry) {
        return new TimedAspect(meterRegistry);
    }
}

마찬가지로 이 @TimedTimedAspect를 빈으로 꼭! 등록을 해줘야 한다. 이렇게 해주면 끝!

 

메트릭 등록 - 게이지

게이지는 임의로 오르내릴 수 있는 단일 숫자 값을 나타내는 메트릭이다. 예를 들면, 재고 수량이나 주식의 현재가 같은 올랐다가도 내렸다가도 할 수 있는 값을 말한다. 그럼 위에서 계속 작업했던 주문 관련 서비스의 재고 수량을 가지고 게이지 메트릭을 만들어보자.

 

참고로, 게이지로 메트릭을 만들어야하나 카운터로 메트릭을 만들어야하나 고민이 된다면 "값이 줄거나 오르거나 둘 다 가능한가?"를 생각해보면 된다. 떨어지기도 하고 오르기도 하는 경우라면 그냥 게이지로 만들면 된다.

 

StockConfigV1

package hello.order.gauge;

import hello.order.OrderService;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class StockConfigV1 {

    @Bean
    public MyStockMetric myStockMetric(OrderService orderService, MeterRegistry registry) {
        return new MyStockMetric(orderService, registry);
    }

    @Slf4j
    static class MyStockMetric {
        private OrderService orderService;
        private MeterRegistry meterRegistry;

        public MyStockMetric(OrderService orderService, MeterRegistry meterRegistry) {
            this.orderService = orderService;
            this.meterRegistry = meterRegistry;
        }

        // 메트릭을 확인할 때 마다 (`/actuator/*` 요청을 의미) 이 메서드가 호출 된다.
        @PostConstruct
        public void init() {
            Gauge.builder("my.stock", orderService, service -> {
                log.info("stock gauge call");
                return service.getStock().get();
            }).register(meterRegistry);
        }
    }
}

위 코드가 게이지 메트릭을 등록하는 코드이다.

  • 우선 MyStockMetric 이라는 클래스를 만들었다. 이 클래스는 OrderService, MeterRegistry를 주입받는다.
  • @PostConstruct 애노테이션으로 빈으로 등록된 후 실행될 메서드 init()을 만든다.
  • 안에서 Gauge라는 io.micrometer.core.instrument 패키지에 있는 녀석을 가져와서 builder("my.stock") 이라는 메트릭 이름을 준다. 그리고 OrderService를 넘기고 그 서비스의 현재 재고 수량을 반환하는 메서드를 리턴한다.
  • 이렇게 하면 이제 메트릭을 외부에서 확인할 때마다 (예: 누군가가 `/actuator/*`로 요청을 날린다, 프로메테우스가 주기적으로 메트릭 정보를 수집하기 위해 요청을 하는 경우) 이 게이지의 세번째 파라미터인 람다 함수가 호출된다.
  • 그리고 이 MyStockMetric 클래스를 빈으로 등록한다.

이러면 끝이다. 스프링 부트 시작 클래스에 이 설정 클래스 등록하는 것 잊지말고! 실행하면 다음과 같이 계속해서 1초마다 로그가 찍힐것이다. 왜 그럴까? 내가 프로메테우스로 1초마다 `/actuator/prometheus`로 데이터 받아오는 작업을 하게 했기 때문.

 

이렇게 한 후 간단하게 그라파나로 이쁘게 보여줘보자. 게이지에 대한 쿼리는 아주 간단하다. 내가 100개가 있는 상태에서 주문 요청을 여러번 했더니 저렇게 그래프가 꺾였다. 이제 취소도 하면 또 올라오게 된다. 이런게 게이지다.

 

근데, 이거보다 코드를 훨씬 더 간결하게 작성할 수 있다.

StockConfigV2

package hello.order.gauge;


import hello.order.OrderService;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.binder.MeterBinder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Slf4j
@Configuration
public class StockConfigV2 {

    @Bean
    public MeterBinder stockSize(OrderService orderService) {
        return registry -> Gauge.builder("my.stock", orderService, service -> {
            log.info("stock gauge call");
            return service.getStock().get();
        }).register(registry);
    }
}

그냥 이렇게 작성하면 된다. MeterBinder 라는 타입을 반환하는 빈을 등록하면 된다. 나머지 코드는 동일하니 생략. 

 

결론

직접 나만의 메트릭을 만들어 보았다. 실제 프로덕션 서비스 환경에서 유용하게 사용할 수 있을 것 같다. 추적해야 하는 의미있는 데이터를 가지고 나중에 메트릭을 만들어 봐야겠다. 그리고 실무 모니터링 환경에 대해 좀 더 자세히 얘기하고 고민해보자.

 

 

이 강의에서 배운 실무 모니터링 환경에 대해 애기해 보겠다.

 

스프링 부트 - 핵심 원리와 활용 강의 | 김영한 - 인프런

김영한 | 실무에 필요한 스프링 부트는 이 강의 하나로 모두 정리해드립니다., 백엔드 개발자를 위한 스프링 부트 끝판왕! 실무에 필요한 내용을 모두 담았습니다.  [임베딩 영상] 김영한의 스

www.inflearn.com

 

모니터링 3단계

  • 대시보드
  • 애플리케이션 추적 - 핀포인트
  • 로그

대시보드

전체를 한눈에 볼 수 있는 가장 큰 뷰 마이크로미터, 프로메테우스, 그라파나 이렇게해서 사용하면 된다.

 

모니터링 대상

  • 시스템 메트릭 (CPU, 메모리)
  • 애플리케이션 메트릭 (톰캣 쓰레드 풀, DB 커넥션 풀, 애플리케이션 호출 수)
  • 비즈니스 메트릭 (주문수, 취소수)

애플리케이션 추적

주로 각각의 HTTP 요청을 추적, 일부는 마이크로서비스 환경에서 분산 추적. 핀포인트를 사용하면 된다.

 

로그

가장 자세한 추적, 원하는대로 커스텀 가능.

같은 HTTP 요청을 묶어서 확인할 수 있는 방법이 중요하고 그 방법은 MDC를 적용하면 된다.

 

파일로 직접 로그를 남기는 경우

  • 일반 로그와 에러 로그를 구분해서 파일로 남기기

클라우드에 로그를 저장하는 경우

  • 검색이 잘 되도록 구분

 

정리를 하자면, 각각 용도가 다르다. 관찰을 할 땐 전체에서 좁게 가야한다. 핀포인트는 정말 좋다. 핀포인트는 무조건 사용할 것 마이크로서비스 분산 모니터링도 가능하고 대용량 트래픽에도 가능하다.

 

알람

모니터링 툴에서 일정 이상 수치가 넘어가면 슬랙 연동하기

 

알람은 2가지 종류(경고, 심각)로 꼭 구분해서 관리 

왜 그럴까? 경고는 하루 1번 정도 사람이 그냥 들어가서 있나? 하고 보면 된다. 푸시 알림도 필요없다. 근데 심각은 즉시 확인해야 한다. 그래서 푸시 알림도 필요하다. 푸시 알림이 경고까지 적용되면 알림이 와도 느슨한 태도가 될 수 있어서 안된다. 그리고 업무와 삶에 방해가 되지 않아야 한다.

 

예를 들면, 

  • 디스크 사용량 70% - 경고
  • 디스크 사용량 80, 90% - 심각
  • CPU 사용량 40% - 경고
  • CPU 사용량 50% - 심각

 

그리고 알림으로 정해놓은 것 중 알림이 아니어도 될 것 같다싶으면 바로바로 처리해야 한다. 이것도 알림 자체에 느슨한 태도를 유발할 수 있는 원인!

 

 

728x90
반응형
LIST

+ Recent posts