From 165a27a83d9e41523ec7f45459f94de06a1e3696 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Mon, 16 Jun 2025 12:45:28 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20ConcurrentSkipListSet=EA=B8=B0?= =?UTF-8?q?=EB=B0=98=20=ED=81=90=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 PriorityBlockingQueue 기반의 주문 큐를, ConcurrentSkipListMap, ConcurrentSkipListSet을 사용한 구조로 변경했습니다. --- .../queue/InMemoryWaitingOrdersManager.java | 2 +- .../NestedInMemoryBuyOrderSkipListSet.java | 96 ++++++++++++++++++ .../NestedInMemorySellOrderSkipListSet.java | 97 +++++++++++++++++++ .../queue/NestedInMemoryWaitingOrders.java | 89 +++++++++++++++++ .../NestedInMemoryWaitingOrdersManager.java | 44 +++++++++ 5 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryBuyOrderSkipListSet.java create mode 100644 src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemorySellOrderSkipListSet.java create mode 100644 src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrders.java create mode 100644 src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrdersManager.java diff --git a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/InMemoryWaitingOrdersManager.java b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/InMemoryWaitingOrdersManager.java index 83d29bf6..287dfa04 100644 --- a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/InMemoryWaitingOrdersManager.java +++ b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/InMemoryWaitingOrdersManager.java @@ -29,7 +29,7 @@ public WaitingOrders getWaitingOrders(String ticker) { @Override public void removeWaitingOrders(String ticker) { - + waitingOrdersMap.remove(ticker); } @Override diff --git a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryBuyOrderSkipListSet.java b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryBuyOrderSkipListSet.java new file mode 100644 index 00000000..ef89a263 --- /dev/null +++ b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryBuyOrderSkipListSet.java @@ -0,0 +1,96 @@ +package com.cleanengine.coin.order.adapter.out.persistentce.order.queue; + +import com.cleanengine.coin.common.domain.port.PriorityQueueStore; +import com.cleanengine.coin.order.domain.BuyOrder; + +import java.util.Comparator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicLong; + +public class NestedInMemoryBuyOrderSkipListSet implements PriorityQueueStore { + private final ConcurrentSkipListMap> map = new ConcurrentSkipListMap<>(Comparator.reverseOrder()); + private final AtomicLong size = new AtomicLong(); + + @Override + public void put(BuyOrder item) { + if(item == null) throw new IllegalArgumentException("item cannot be null."); + + map.compute(item.getPrice(), (key, buyOrders) -> { + if(buyOrders == null) { + buyOrders = new ConcurrentSkipListSet<>(Comparator.reverseOrder()); + } + boolean added = buyOrders.add(item); + if(added) size.incrementAndGet(); + return buyOrders; + }); + } + + @Override + public BuyOrder poll() { + while(true) { + Map.Entry> firstEntry = map.firstEntry(); + + if (firstEntry == null) { + return null; + } + + ConcurrentSkipListSet buyOrders = firstEntry.getValue(); + try { + BuyOrder order = buyOrders.first(); + this.remove(order); + return order; + } catch (NoSuchElementException e) { + continue; + } + } + } + + @Override + public BuyOrder peek() { + while(true) { + Map.Entry> firstEntry = map.firstEntry(); + + if (firstEntry == null) { + return null; + } + + ConcurrentSkipListSet buyOrders = firstEntry.getValue(); + try { + return buyOrders.first(); + } catch (NoSuchElementException e) { + continue; + } + } + } + + @Override + public BuyOrder remove(BuyOrder item) { + if (item == null) return null; + + map.computeIfPresent(item.getPrice(), (key, orders) -> { + boolean removed = orders.remove(item); + if (removed) size.decrementAndGet(); + return orders.isEmpty() ? null : orders; + }); + + return item; + } + + @Override + public long size() { + return size.get(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public void clear() { + map.clear(); + } +} diff --git a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemorySellOrderSkipListSet.java b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemorySellOrderSkipListSet.java new file mode 100644 index 00000000..29cd1368 --- /dev/null +++ b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemorySellOrderSkipListSet.java @@ -0,0 +1,97 @@ +package com.cleanengine.coin.order.adapter.out.persistentce.order.queue; + +import com.cleanengine.coin.common.domain.port.PriorityQueueStore; +import com.cleanengine.coin.order.domain.BuyOrder; +import com.cleanengine.coin.order.domain.SellOrder; + +import java.util.Comparator; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.ConcurrentSkipListMap; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.concurrent.atomic.AtomicLong; + +public class NestedInMemorySellOrderSkipListSet implements PriorityQueueStore { + private final ConcurrentSkipListMap> map = new ConcurrentSkipListMap<>(); + private final AtomicLong size = new AtomicLong(); + + @Override + public void put(SellOrder item) { + if(item == null) throw new IllegalArgumentException("item cannot be null."); + + map.compute(item.getPrice(), (key, sellOrders) -> { + if(sellOrders == null) { + sellOrders = new ConcurrentSkipListSet<>(); + } + boolean added = sellOrders.add(item); + if(added) size.incrementAndGet(); + return sellOrders; + }); + } + + @Override + public SellOrder poll() { + while(true) { + Map.Entry> firstEntry = map.firstEntry(); + + if (firstEntry == null) { + return null; + } + + ConcurrentSkipListSet sellOrders = firstEntry.getValue(); + try { + SellOrder order = sellOrders.first(); + this.remove(order); + return order; + } catch (NoSuchElementException e) { + continue; + } + } + } + + @Override + public SellOrder peek() { + while(true) { + Map.Entry> firstEntry = map.firstEntry(); + + if (firstEntry == null) { + return null; + } + + ConcurrentSkipListSet sellOrders = firstEntry.getValue(); + try { + return sellOrders.first(); + } catch (NoSuchElementException e) { + continue; + } + } + } + + @Override + public SellOrder remove(SellOrder item) { + if (item == null) return null; + + map.computeIfPresent(item.getPrice(), (key, orders) -> { + boolean removed = orders.remove(item); + if (removed) size.decrementAndGet(); + return orders.isEmpty() ? null : orders; + }); + + return item; + } + + @Override + public long size() { + return size.get(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public void clear() { + map.clear(); + } +} diff --git a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrders.java b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrders.java new file mode 100644 index 00000000..73c44c11 --- /dev/null +++ b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrders.java @@ -0,0 +1,89 @@ +package com.cleanengine.coin.order.adapter.out.persistentce.order.queue; + +import com.cleanengine.coin.common.adapter.out.store.InMemoryPriorityQueueStore; +import com.cleanengine.coin.common.domain.port.PriorityQueueStore; +import com.cleanengine.coin.order.domain.BuyOrder; +import com.cleanengine.coin.order.domain.Order; +import com.cleanengine.coin.order.domain.OrderType; +import com.cleanengine.coin.order.domain.SellOrder; +import com.cleanengine.coin.order.domain.spi.WaitingOrders; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class NestedInMemoryWaitingOrders implements WaitingOrders { + private final String ticker; + private final PriorityQueueStore limitBuyOrderPriorityQueueStore = new NestedInMemoryBuyOrderSkipListSet(); + private final PriorityQueueStore marketBuyOrderPriorityQueueStore = new InMemoryPriorityQueueStore<>(); + private final PriorityQueueStore limitSellOrderPriorityQueueStore = new NestedInMemorySellOrderSkipListSet(); + private final PriorityQueueStore marketSellOrderPriorityQueueStore = new InMemoryPriorityQueueStore<>(); + + @Override + public String getTicker() { + return ticker; + } + + @Override + public void addOrder(Order order) { + if(order == null) throw new IllegalArgumentException("order cannot be null."); + + if (order instanceof BuyOrder) { + if(order.getIsMarketOrder()) { + marketBuyOrderPriorityQueueStore.put((BuyOrder) order); + } + else{ + limitBuyOrderPriorityQueueStore.put((BuyOrder) order); + } + } else { + if(order.getIsMarketOrder()) { + marketSellOrderPriorityQueueStore.put((SellOrder) order); + } + else{ + limitSellOrderPriorityQueueStore.put((SellOrder) order); + } + } + } + + @Override + public PriorityQueueStore getBuyOrderPriorityQueueStore(OrderType orderType) { + return orderType == OrderType.MARKET ? marketBuyOrderPriorityQueueStore : limitBuyOrderPriorityQueueStore; + } + + @Override + public PriorityQueueStore getSellOrderPriorityQueueStore(OrderType orderType) { + return orderType == OrderType.MARKET ? marketSellOrderPriorityQueueStore : limitSellOrderPriorityQueueStore; + } + + @Override + public void removeOrder(Order order) { + if(order == null) throw new IllegalArgumentException("order cannot be null."); + + if (order instanceof BuyOrder) { + if(order.getIsMarketOrder()) { + marketBuyOrderPriorityQueueStore.remove((BuyOrder) order); + } + else{ + limitBuyOrderPriorityQueueStore.remove((BuyOrder) order); + } + } else { + if(order.getIsMarketOrder()) { + marketSellOrderPriorityQueueStore.remove((SellOrder) order); + } + else{ + limitSellOrderPriorityQueueStore.remove((SellOrder) order); + } + } + } + + @Override + public void clearAllQueues() { + limitBuyOrderPriorityQueueStore.clear(); + marketBuyOrderPriorityQueueStore.clear(); + limitSellOrderPriorityQueueStore.clear(); + marketSellOrderPriorityQueueStore.clear(); + } + + @Override + public void close() { + + } +} diff --git a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrdersManager.java b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrdersManager.java new file mode 100644 index 00000000..acf4f5bf --- /dev/null +++ b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrdersManager.java @@ -0,0 +1,44 @@ +package com.cleanengine.coin.order.adapter.out.persistentce.order.queue; + +import com.cleanengine.coin.order.domain.spi.WaitingOrders; +import com.cleanengine.coin.order.domain.spi.WaitingOrdersManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Optional; + +@Slf4j +//@Component +public class NestedInMemoryWaitingOrdersManager implements WaitingOrdersManager { + private final HashMap waitingOrdersMap = new HashMap<>(); + + @Override + public WaitingOrders getWaitingOrders(String ticker) { + if(!waitingOrdersMap.containsKey(ticker)) { + addWaitingOrders(ticker); + } + + Optional waitingOrdersOpt = Optional.ofNullable(waitingOrdersMap.get(ticker)); + if(waitingOrdersOpt.isEmpty()){ + log.debug("WaitingOrders not found. with " + ticker); + throw new RuntimeException("WaitingOrders not found with " + ticker); + } + return waitingOrdersOpt.get(); } + + @Override + public void removeWaitingOrders(String ticker) { + waitingOrdersMap.remove(ticker); + } + + @Override + public void close() { + + } + + protected synchronized void addWaitingOrders(String ticker){ + if(!waitingOrdersMap.containsKey(ticker)){ + waitingOrdersMap.put(ticker, new NestedInMemoryWaitingOrders(ticker)); + } + } +} From 7bd17edd5c6f35446f751a1892594b57ee50bd37 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Mon, 16 Jun 2025 13:17:32 +0900 Subject: [PATCH 02/12] =?UTF-8?q?fix:=20SkipListSet=EA=B8=B0=EB=B0=98=20?= =?UTF-8?q?=EC=A3=BC=EB=AC=B8=ED=81=90=20=EC=A0=95=EB=A0=AC=20=EB=AC=B8?= =?UTF-8?q?=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 매수 주문이 생성시간과 반대로 정렬되고 있었던 문제를 수정했습니다 --- .../order/queue/NestedInMemoryBuyOrderSkipListSet.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryBuyOrderSkipListSet.java b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryBuyOrderSkipListSet.java index ef89a263..ab35bef3 100644 --- a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryBuyOrderSkipListSet.java +++ b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryBuyOrderSkipListSet.java @@ -20,7 +20,7 @@ public void put(BuyOrder item) { map.compute(item.getPrice(), (key, buyOrders) -> { if(buyOrders == null) { - buyOrders = new ConcurrentSkipListSet<>(Comparator.reverseOrder()); + buyOrders = new ConcurrentSkipListSet<>(); } boolean added = buyOrders.add(item); if(added) size.incrementAndGet(); From 83b9c368cb3fdf466db18892c472c3b6669b6960 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Tue, 17 Jun 2025 13:06:58 +0900 Subject: [PATCH 03/12] =?UTF-8?q?temp:=20=EC=84=A4=EC=A0=95=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=20=EC=9E=84=EC=8B=9C=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit temp --- monitoring/docker-compose.yml | 16 +++++++-- monitoring/otel/otel-collector-config.yml | 42 +++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 monitoring/otel/otel-collector-config.yml diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml index 1337a0b1..5513d491 100644 --- a/monitoring/docker-compose.yml +++ b/monitoring/docker-compose.yml @@ -18,7 +18,7 @@ services: - TZ=Asia/Seoul - OTEL_SERVICE_NAME=my-spring-app - OTEL_TRACES_EXPORTER=otlp - - OTEL_EXPORTER_OTLP_ENDPOINT=http://jaeger:4318 + - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 - OTEL_LOGS_EXPORTER=none - OTEL_METRICS_EXPORTER=none - OTEL_INSTRUMENTATION_METHODS_ENABLED=true @@ -60,6 +60,7 @@ services: command: - '--config.file=/etc/prometheus/prometheus.yml' - '--storage.tsdb.retention.time=30d' + - '--web.enable-remote-write-receiver' ports: - "9090:9090" networks: @@ -85,12 +86,21 @@ services: image: jaegertracing/all-in-one:latest container_name: jaeger ports: - - "16686:16686" + - "16686:16686" # UI + - "14250:14250" # Otel 수신 포트 + networks: + - monitoring-net + + otel-collector: + image: otel/opentelemetry-collector-contrib:latest + command: ["--config=/etc/otel-collector-config.yml"] + volumes: + - ./otel/otel-collector-config.yml:/etc/otel-collector-config.yml + ports: - "4317:4317" - "4318:4318" networks: - monitoring-net - volumes: prometheus_data: {} grafana_data: {} diff --git a/monitoring/otel/otel-collector-config.yml b/monitoring/otel/otel-collector-config.yml new file mode 100644 index 00000000..a4df2ba8 --- /dev/null +++ b/monitoring/otel/otel-collector-config.yml @@ -0,0 +1,42 @@ +receivers: + otlp: + protocols: + grpc: + http: + +processors: + batch: + timeout: 500ms + send_batch_size: 8192 + send_batch_max_size: 16384 + + spanmetrics: + metrics_exporter: prometheus + latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms, 500ms, 1s] + dimensions: + - service.name + - span.name + - span.kind + - status.code + +exporters: + jaeger: + endpoint: jaeger:14250 + tls: + insecure: true + + prometheus: + endpoint: "0.0.0.0:8889" + +service: + pipelines: + traces: + receivers: [otlp] + processors: [batch, spanmetrics] + exporters: [jaeger] + + # 메트릭 데이터 처리 파이프라인 + metrics: + receivers: [otlp] + processors: [batch] + exporters: [prometheus] \ No newline at end of file From 3460fe377468f7597fa57a0c83f4c61887b8fad5 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Wed, 18 Jun 2025 19:12:30 +0900 Subject: [PATCH 04/12] =?UTF-8?q?config:=20spanmetrics=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit span으로부터 나온 trace정보로부터 metric을 추출해내는 설정을 반영했습니다 --- monitoring/docker-compose.yml | 22 +++++++++------ monitoring/otel/otel-collector-config.yml | 33 +++++++++++++---------- monitoring/prometheus/prometheus.yml | 6 ++++- 3 files changed, 38 insertions(+), 23 deletions(-) diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml index f26cad51..0b9817b7 100644 --- a/monitoring/docker-compose.yml +++ b/monitoring/docker-compose.yml @@ -18,7 +18,7 @@ services: - TZ=Asia/Seoul - OTEL_SERVICE_NAME=my-spring-app - OTEL_TRACES_EXPORTER=otlp - - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4317 + - OTEL_EXPORTER_OTLP_ENDPOINT=http://otel-collector:4318 - OTEL_LOGS_EXPORTER=none - OTEL_METRICS_EXPORTER=none - OTEL_INSTRUMENTATION_METHODS_ENABLED=true @@ -26,6 +26,8 @@ services: depends_on: mariadb: condition: service_healthy + otel-collector: + condition: service_started networks: - app-network - monitoring-net @@ -34,7 +36,7 @@ services: image: mariadb:latest container_name: mariadb2 ports: - - "3307:3306" + - "3306:3306" env_file: - ../docker/local.properties environment: @@ -67,7 +69,6 @@ services: - monitoring-net restart: unless-stopped - # --- 추가된 InfluxDB 서비스 --- influxdb: image: influxdb:2.7 container_name: influxdb @@ -97,7 +98,7 @@ services: networks: - monitoring-net restart: unless-stopped - depends_on: # --- 수정된 부분 --- + depends_on: - prometheus - jaeger - influxdb @@ -107,26 +108,31 @@ services: container_name: jaeger ports: - "16686:16686" # UI - - "14250:14250" # Otel 수신 포트 + - "4317:4317" + - "4318:4318" networks: - monitoring-net + otel-collector: image: otel/opentelemetry-collector-contrib:latest command: ["--config=/etc/otel-collector-config.yml"] volumes: - ./otel/otel-collector-config.yml:/etc/otel-collector-config.yml ports: - - "4317:4317" - - "4318:4318" + - "8889:8889" + - "4319:4317" + - "4320:4318" + - "13133:13133" networks: - monitoring-net + volumes: prometheus_data: {} grafana_data: {} mariadb_data: driver: local - influxdb_data: {} # --- 추가된 부분 --- + influxdb_data: {} networks: app-network: diff --git a/monitoring/otel/otel-collector-config.yml b/monitoring/otel/otel-collector-config.yml index a4df2ba8..d6f66fc4 100644 --- a/monitoring/otel/otel-collector-config.yml +++ b/monitoring/otel/otel-collector-config.yml @@ -2,26 +2,27 @@ receivers: otlp: protocols: grpc: + endpoint: 0.0.0.0:4317 http: + endpoint: 0.0.0.0:4318 processors: batch: timeout: 500ms send_batch_size: 8192 send_batch_max_size: 16384 + probabilistic_sampler: + sampling_percentage: 15 +connectors: spanmetrics: - metrics_exporter: prometheus - latency_histogram_buckets: [100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms, 500ms, 1s] - dimensions: - - service.name - - span.name - - span.kind - - status.code + histogram: + explicit: + buckets: [100us, 1ms, 2ms, 6ms, 10ms, 100ms, 250ms, 500ms, 1s] exporters: - jaeger: - endpoint: jaeger:14250 + otlp/jaeger: + endpoint: jaeger:4317 tls: insecure: true @@ -30,13 +31,17 @@ exporters: service: pipelines: - traces: + traces/metrics: receivers: [otlp] - processors: [batch, spanmetrics] - exporters: [jaeger] + processors: [batch] + exporters: [spanmetrics] - # 메트릭 데이터 처리 파이프라인 - metrics: + traces/jaeger: receivers: [otlp] + processors: [probabilistic_sampler,batch] + exporters: [otlp/jaeger] + + metrics: + receivers: [spanmetrics] processors: [batch] exporters: [prometheus] \ No newline at end of file diff --git a/monitoring/prometheus/prometheus.yml b/monitoring/prometheus/prometheus.yml index d1690101..59130832 100644 --- a/monitoring/prometheus/prometheus.yml +++ b/monitoring/prometheus/prometheus.yml @@ -8,4 +8,8 @@ scrape_configs: - job_name: 'my-app' static_configs: - targets: [ 'app:8080' ] - metrics_path: /actuator/prometheus \ No newline at end of file + metrics_path: /actuator/prometheus + - job_name: 'otel-collector' + scrape_interval: 15s + static_configs: + - targets: [ 'otel-collector:8889' ] \ No newline at end of file From 713c19035da909d534abfd0da82ea570519b3325 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Wed, 18 Jun 2025 19:13:25 +0900 Subject: [PATCH 05/12] =?UTF-8?q?config:=20jaeger=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 jaeger가 인메모리 기반으로 동작하여 자주 종료되는 이슈가 있었기에 파일 기반으로 동작하도록 변경했습니다 --- monitoring/docker-compose.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml index 0b9817b7..38c85f60 100644 --- a/monitoring/docker-compose.yml +++ b/monitoring/docker-compose.yml @@ -106,6 +106,15 @@ services: jaeger: image: jaegertracing/all-in-one:latest container_name: jaeger + user: "${UID}:${GID}" + environment: + - SPAN_STORAGE_TYPE=badger + - BADGER_EPHEMERAL=false + - BADGER_DIRECTORY_VALUE=/tmp/jaeger/data + - BADGER_DIRECTORY_KEY=/tmp/jaeger/keys + - COLLECTOR_OTLP_ENABLED=true + volumes: + - jaeger_data:/tmp/jaeger ports: - "16686:16686" # UI - "4317:4317" @@ -133,6 +142,7 @@ volumes: mariadb_data: driver: local influxdb_data: {} + jaeger_data: {} networks: app-network: From ae53d52c9026612faf2b26a69f7e788b0c15a642 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Fri, 20 Jun 2025 15:11:50 +0900 Subject: [PATCH 06/12] =?UTF-8?q?config:=20=EB=B6=80=ED=95=98=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EC=A7=80=ED=91=9C=20=EC=84=A4?= =?UTF-8?q?=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 추가 지표를 제공하도록 actuator yml 설정 jaeger가 터지지 않도록 sampling 비율 수정 jaeger가 셀프 모니터링시 오류가 발생하지 않도록 수정 --- monitoring/docker-compose.yml | 15 ++++++++++----- monitoring/otel/otel-collector-config.yml | 2 +- src/main/resources/application-actuator.yml | 14 +++++++++++++- 3 files changed, 24 insertions(+), 7 deletions(-) diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml index 38c85f60..c7074185 100644 --- a/monitoring/docker-compose.yml +++ b/monitoring/docker-compose.yml @@ -9,7 +9,7 @@ services: - /etc/localtime:/etc/localtime:ro - ./opentelemetry-javaagent.jar:/app/opentelemetry-javaagent.jar working_dir: /app - command: [ "java", "-jar", "coin-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev,mariadb-local,actuator,apm" ] + command: [ "java", "-jar", "coin-0.0.1-SNAPSHOT.jar", "--spring.profiles.active=dev,it,mariadb-local,actuator,apm" ] ports: - "8080:8080" env_file: @@ -113,12 +113,14 @@ services: - BADGER_DIRECTORY_VALUE=/tmp/jaeger/data - BADGER_DIRECTORY_KEY=/tmp/jaeger/keys - COLLECTOR_OTLP_ENABLED=true + - COLLECTOR_OTLP_GRPC_HOST_PORT=0.0.0.0:4317 + - COLLECTOR_OTLP_HTTP_HOST_PORT=0.0.0.0:4318 volumes: - jaeger_data:/tmp/jaeger ports: - "16686:16686" # UI - - "4317:4317" - - "4318:4318" + - "4319:4317" + - "4320:4318" networks: - monitoring-net @@ -130,11 +132,14 @@ services: - ./otel/otel-collector-config.yml:/etc/otel-collector-config.yml ports: - "8889:8889" - - "4319:4317" - - "4320:4318" + - "4317:4317" + - "4318:4318" - "13133:13133" networks: - monitoring-net + depends_on: + jaeger: + condition: service_started volumes: prometheus_data: {} diff --git a/monitoring/otel/otel-collector-config.yml b/monitoring/otel/otel-collector-config.yml index d6f66fc4..c5852c05 100644 --- a/monitoring/otel/otel-collector-config.yml +++ b/monitoring/otel/otel-collector-config.yml @@ -12,7 +12,7 @@ processors: send_batch_size: 8192 send_batch_max_size: 16384 probabilistic_sampler: - sampling_percentage: 15 + sampling_percentage: 1 connectors: spanmetrics: diff --git a/src/main/resources/application-actuator.yml b/src/main/resources/application-actuator.yml index 167d9532..9c9a38b6 100644 --- a/src/main/resources/application-actuator.yml +++ b/src/main/resources/application-actuator.yml @@ -9,4 +9,16 @@ management: prometheus: metrics: export: - enabled: true \ No newline at end of file + enabled: true +server: + tomcat: + mbeanregistry: + enabled: true + +spring: + jmx: + enabled: true + jpa: + properties: + hibernate: + generate_statistics: true From f27a9c3ec92502a7732ace24602246592eb59070 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Fri, 20 Jun 2025 15:12:32 +0900 Subject: [PATCH 07/12] =?UTF-8?q?config:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=9C=20=EC=A7=80=ED=91=9C=20=ED=95=84=ED=84=B0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hibernate가 너무 많은 지표를 전달하기 때문에, 얻고 싶은 지표만을 필터링합니다. --- .../coin/configuration/MicrometerConfig.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/com/cleanengine/coin/configuration/MicrometerConfig.java diff --git a/src/main/java/com/cleanengine/coin/configuration/MicrometerConfig.java b/src/main/java/com/cleanengine/coin/configuration/MicrometerConfig.java new file mode 100644 index 00000000..68cd9103 --- /dev/null +++ b/src/main/java/com/cleanengine/coin/configuration/MicrometerConfig.java @@ -0,0 +1,32 @@ +package com.cleanengine.coin.configuration; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.config.MeterFilter; +import io.micrometer.core.instrument.config.MeterFilterReply; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +@Profile("actuator") +@Configuration +public class MicrometerConfig { + @Bean + public MeterFilter meterFilter() { + final String hibernateFilterPrefix = "hibernate.statements"; + + return new MeterFilter() { + @Override + public MeterFilterReply accept(Meter.Id id) { + if (id.getName().startsWith("hibernate.")) { + if(id.getName().startsWith(hibernateFilterPrefix)) { + return MeterFilterReply.ACCEPT; + } + else{ + return MeterFilterReply.DENY; + } + } + return MeterFilterReply.NEUTRAL; + } + }; + } +} From 683c457a8abb91bb3770d265aa0222084ed9bed9 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Fri, 20 Jun 2025 15:13:09 +0900 Subject: [PATCH 08/12] =?UTF-8?q?test:=20=EB=B6=80=ED=95=98=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=9A=A9=20token=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 테스트 환경에서만 동작하고, local.properties의 secret에 기반해 동작하므로 안전합니다 --- .../cleanengine/coin/tool/TokenGenerator.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/test/java/com/cleanengine/coin/tool/TokenGenerator.java diff --git a/src/test/java/com/cleanengine/coin/tool/TokenGenerator.java b/src/test/java/com/cleanengine/coin/tool/TokenGenerator.java new file mode 100644 index 00000000..a89b282e --- /dev/null +++ b/src/test/java/com/cleanengine/coin/tool/TokenGenerator.java @@ -0,0 +1,60 @@ +package com.cleanengine.coin.tool; + +import com.cleanengine.coin.user.login.application.JWTUtil; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.annotation.Profile; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +@SpringBootTest +@Profile("dev, it") +public class TokenGenerator { + + @Value("${JWT_SECRET}") + private String secret; + + @Test + public void createToken(){ + JWTUtil jwtUtil = new JWTUtil(secret); + System.out.println(jwtUtil.createJwt(1001, 1000 * 60*60*24*365L)); + System.out.println(jwtUtil.createJwt(1002, 1000 * 60*60*24*365L)); + } + + @Test + public void createTokens(){ + int size = 0; + List tokens = new ArrayList<>(); + + JWTUtil jwtUtil = new JWTUtil(secret); + + for (int i = 1; i<=1000; i++){ + tokens.add(jwtUtil.createJwt(i, 1000 * 60*60*24*365L)); + size++; + } + + ObjectMapper objectMapper = new ObjectMapper(); + + try{ + File outputFile = new File("tokens.json"); + objectMapper.writerWithDefaultPrettyPrinter().writeValue(outputFile, new UserTokens(size, tokens)); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static class UserTokens{ + public int size; + public List tokens; + + public UserTokens(int size, List tokens) { + this.size = size; + this.tokens = tokens; + } + } +} From 6f98e8c0a14f22599a9804445680a75c1e690f5b Mon Sep 17 00:00:00 2001 From: Junh-b Date: Fri, 20 Jun 2025 15:13:55 +0900 Subject: [PATCH 09/12] =?UTF-8?q?fix:=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=B4=88=EA=B8=B0=ED=99=94=20=EC=BD=94=EB=93=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit wallet 생성 시점이 바뀐 후, 제대로 동작할 수 있도록 데이터 초기화 코드를 변경했습니다. --- docker/mariadb/init.sql | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/mariadb/init.sql b/docker/mariadb/init.sql index 29b57767..eb9362b2 100644 --- a/docker/mariadb/init.sql +++ b/docker/mariadb/init.sql @@ -110,3 +110,5 @@ INSERT INTO `if`.asset (ticker, name) VALUES ('TRUMP', '오피셜트럼프'); INSERT INTO `if`.wallet (wallet_id, account_id, buy_price, roi, size, ticker) VALUES (1, 1, 0, 0, 500000000, 'BTC'); INSERT INTO `if`.wallet (wallet_id, account_id, buy_price, roi, size, ticker) VALUES (2, 1, 0, 0, 500000000, 'TRUMP'); +INSERT INTO `if`.wallet (wallet_id, account_id, buy_price, roi, size, ticker) VALUES (3, 2, 0, 0, 500000000, 'BTC'); +INSERT INTO `if`.wallet (wallet_id, account_id, buy_price, roi, size, ticker) VALUES (4, 2, 0, 0, 500000000, 'TRUMP'); From bc5e6204f885d58b6e5335d56eadd30bd376dba5 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Fri, 20 Jun 2025 15:15:09 +0900 Subject: [PATCH 10/12] =?UTF-8?q?config:=20=EB=B6=80=ED=95=98=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=9A=A9=20=EC=B4=88=EA=B8=B0=ED=99=94=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application-loadtest.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/main/resources/application-loadtest.yml diff --git a/src/main/resources/application-loadtest.yml b/src/main/resources/application-loadtest.yml new file mode 100644 index 00000000..e5b0bc62 --- /dev/null +++ b/src/main/resources/application-loadtest.yml @@ -0,0 +1,5 @@ +spring: + sql: + init: + mode: always + data-locations: classpath:dataset/`if`.users.sql,classpath:dataset/`if`.account.sql,classpath:dataset/`if`.walletBTC.sql,classpath:dataset/`if`.walletTRUMP.sql From 10c28982e435714da2f55c6c40f921031602aa39 Mon Sep 17 00:00:00 2001 From: Junh-b Date: Mon, 23 Jun 2025 13:41:25 +0900 Subject: [PATCH 11/12] =?UTF-8?q?config:=20=EB=AA=A8=EB=8B=88=ED=84=B0?= =?UTF-8?q?=EB=A7=81=20=EC=84=A4=EC=A0=95=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hibernate metric 지표가 warn 레벨로 찍히도록 수정, docker compose시 intellij 디버그용 에이전트를 포함하지 않도록 변경 --- build.gradle | 1 + monitoring/docker-compose.yml | 2 +- src/main/resources/application-actuator.yml | 3 +++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 83ce59b6..f01680fb 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'io.micrometer:micrometer-registry-prometheus' implementation("io.opentelemetry.instrumentation:opentelemetry-instrumentation-annotations:2.16.0") + implementation 'org.hibernate.orm:hibernate-micrometer' implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation 'com.google.code.gson:gson:2.13.1' diff --git a/monitoring/docker-compose.yml b/monitoring/docker-compose.yml index c7074185..6e6431d4 100644 --- a/monitoring/docker-compose.yml +++ b/monitoring/docker-compose.yml @@ -22,7 +22,7 @@ services: - OTEL_LOGS_EXPORTER=none - OTEL_METRICS_EXPORTER=none - OTEL_INSTRUMENTATION_METHODS_ENABLED=true - - JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005, -javaagent:/app/opentelemetry-javaagent.jar + - JAVA_TOOL_OPTIONS=-javaagent:/app/opentelemetry-javaagent.jar depends_on: mariadb: condition: service_healthy diff --git a/src/main/resources/application-actuator.yml b/src/main/resources/application-actuator.yml index 9c9a38b6..bd3c931b 100644 --- a/src/main/resources/application-actuator.yml +++ b/src/main/resources/application-actuator.yml @@ -22,3 +22,6 @@ spring: properties: hibernate: generate_statistics: true +logging: + level: + org.hibernate.engine.internal.StatisticalLoggingSessionEventListener: WARN From 3f4dd5f38d52ea0805288bab28f15b5af694cc7d Mon Sep 17 00:00:00 2001 From: Junh-b Date: Mon, 23 Jun 2025 13:42:38 +0900 Subject: [PATCH 12/12] =?UTF-8?q?fix:=20=EB=B3=80=EA=B2=BD=EB=90=9C=20?= =?UTF-8?q?=ED=81=90=20=ED=99=9C=EC=84=B1=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 PriorityBlockingQueue에서 ConcurrentSkipListSet 기반으로 적용되도록 변경했습니다 --- .../persistentce/order/queue/InMemoryWaitingOrdersManager.java | 2 +- .../order/queue/NestedInMemoryWaitingOrdersManager.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/InMemoryWaitingOrdersManager.java b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/InMemoryWaitingOrdersManager.java index 287dfa04..3530d960 100644 --- a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/InMemoryWaitingOrdersManager.java +++ b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/InMemoryWaitingOrdersManager.java @@ -9,7 +9,7 @@ import java.util.Optional; @Slf4j -@Component +//@Component public class InMemoryWaitingOrdersManager implements WaitingOrdersManager { private final HashMap waitingOrdersMap = new HashMap<>(); diff --git a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrdersManager.java b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrdersManager.java index acf4f5bf..0fd9b21e 100644 --- a/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrdersManager.java +++ b/src/main/java/com/cleanengine/coin/order/adapter/out/persistentce/order/queue/NestedInMemoryWaitingOrdersManager.java @@ -9,7 +9,7 @@ import java.util.Optional; @Slf4j -//@Component +@Component public class NestedInMemoryWaitingOrdersManager implements WaitingOrdersManager { private final HashMap waitingOrdersMap = new HashMap<>();