From 2476d59f3da889e8e2cdb6c3b441e166e5d19a67 Mon Sep 17 00:00:00 2001 From: IgorM27 Date: Wed, 10 Sep 2025 22:18:56 +0300 Subject: [PATCH 01/11] fix pom.xml --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 0e7c0ad2d..a2edb1627 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ Application for resilience and highly-loaded applications course - 2.2.0 + 2.1.0 1.9.0 4.12.0 2.7.5 @@ -175,7 +175,7 @@ spring - 1.8 + 17 From aedf6cc3d9c97e809203ae43d774883972f73acc Mon Sep 17 00:00:00 2001 From: IgorM27 Date: Thu, 11 Sep 2025 21:17:30 +0300 Subject: [PATCH 02/11] change port --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 33d51a58b..2ea662903 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ server.address=0.0.0.0 -server.port=8081 +server.port=8099 server.http2.enabled=true spring.main.allow-bean-definition-overriding=true From be279f3651556fdfe5925bb8141c57e8f0306752 Mon Sep 17 00:00:00 2001 From: IgorM27 Date: Thu, 11 Sep 2025 21:22:03 +0300 Subject: [PATCH 03/11] back port --- src/main/resources/application.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 2ea662903..33d51a58b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,5 +1,5 @@ server.address=0.0.0.0 -server.port=8099 +server.port=8081 server.http2.enabled=true spring.main.allow-bean-definition-overriding=true From 6e97ab819bb92319166a4a61eeeb3544688b19a3 Mon Sep 17 00:00:00 2001 From: danilkasirin <367279@niuitmo.ru> Date: Thu, 18 Sep 2025 00:52:43 +0300 Subject: [PATCH 04/11] test-case-1 --- .gitignore | 4 +++- .../quipy/config/PaymentRateLimiterFactory.kt | 21 +++++++++++++++++++ .../payments/config/PaymentAccountsConfig.kt | 4 +++- .../logic/PaymentExternalServiceImpl.kt | 19 +++++++++++++++++ src/main/resources/application.properties | 2 +- 5 files changed, 47 insertions(+), 3 deletions(-) create mode 100644 src/main/kotlin/ru/quipy/config/PaymentRateLimiterFactory.kt diff --git a/.gitignore b/.gitignore index 259113f73..4beaadeee 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ !**/src/test/**/target/ +prometheus/data/ +grafana/data/ ### STS ### .apt_generated @@ -18,7 +20,7 @@ target/ *.iws *.iml *.ipr -./http-client.env.json +http-client.env.json ### NetBeans ### /nbproject/private/ diff --git a/src/main/kotlin/ru/quipy/config/PaymentRateLimiterFactory.kt b/src/main/kotlin/ru/quipy/config/PaymentRateLimiterFactory.kt new file mode 100644 index 000000000..2b1b5fe63 --- /dev/null +++ b/src/main/kotlin/ru/quipy/config/PaymentRateLimiterFactory.kt @@ -0,0 +1,21 @@ +package ru.quipy.config + +import org.springframework.stereotype.Component +import ru.quipy.common.utils.SlidingWindowRateLimiter +import java.util.concurrent.ConcurrentHashMap +import java.time.Duration + +@Component +class PaymentRateLimiterFactory { + + private val rateLimiters = ConcurrentHashMap() + + fun getRateLimiterForAccount(accountName: String, rateLimitPerSec: Int): SlidingWindowRateLimiter { + return rateLimiters.computeIfAbsent(accountName) { + SlidingWindowRateLimiter( + rate = rateLimitPerSec.toLong(), + window = Duration.ofSeconds(1) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt index eceb90cff..d12a2753e 100644 --- a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt +++ b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt @@ -6,6 +6,7 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import ru.quipy.config.PaymentRateLimiterFactory import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate import ru.quipy.payments.logic.* @@ -57,7 +58,8 @@ class PaymentAccountsConfig { it, paymentService, paymentProviderHostPort, - token + token, + PaymentRateLimiterFactory() ) } } diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index 5cb12106a..c5911a842 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -6,11 +6,13 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import org.slf4j.LoggerFactory +import ru.quipy.config.PaymentRateLimiterFactory import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate import java.net.SocketTimeoutException import java.time.Duration import java.util.* +import java.util.concurrent.Executors // Advice: always treat time as a Duration @@ -19,6 +21,7 @@ class PaymentExternalSystemAdapterImpl( private val paymentESService: EventSourcingService, private val paymentProviderHostPort: String, private val token: String, + private val rateLimiterFactory: PaymentRateLimiterFactory ) : PaymentExternalSystemAdapter { companion object { @@ -33,10 +36,26 @@ class PaymentExternalSystemAdapterImpl( private val requestAverageProcessingTime = properties.averageProcessingTime private val rateLimitPerSec = properties.rateLimitPerSec private val parallelRequests = properties.parallelRequests + private val rateLimiter by lazy { + rateLimiterFactory.getRateLimiterForAccount(accountName, (rateLimitPerSec * 0.9).toInt()) + } + private val paymentExecutor = Executors.newFixedThreadPool(parallelRequests) private val client = OkHttpClient.Builder().build() override fun performPaymentAsync(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { + paymentExecutor.submit { + try { + rateLimiter.tickBlocking() + + executePayment(paymentId, amount, paymentStartedAt, deadline) + } catch (e: Exception) { + logger.error("[$accountName] Failed to process payment $paymentId", e) + } + } + } + + private fun executePayment(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { logger.warn("[$accountName] Submitting payment request for payment $paymentId") val transactionId = UUID.randomUUID() diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 33d51a58b..719cb8de8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,5 +26,5 @@ management.endpoints.web.exposure.include=info,health,prometheus,metrics payment.service-name=${PAYMENT_SERVICE_NAME} payment.token=${PAYMENT_TOKEN} -payment.accounts=${PAYMENT_ACCOUNTS:acc-12,acc-20} +payment.accounts=${PAYMENT_ACCOUNTS:acc-3} payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} \ No newline at end of file From 7425a5f4bce4fba71b90325397ccc857041fa5d6 Mon Sep 17 00:00:00 2001 From: danilkasirin <367279@niuitmo.ru> Date: Thu, 18 Sep 2025 01:19:42 +0300 Subject: [PATCH 05/11] move factory --- .DS_Store | Bin 0 -> 6148 bytes src/.DS_Store | Bin 0 -> 6148 bytes src/main/.DS_Store | Bin 0 -> 6148 bytes src/main/kotlin/.DS_Store | Bin 0 -> 6148 bytes src/main/kotlin/ru/.DS_Store | Bin 0 -> 6148 bytes src/main/kotlin/ru/quipy/.DS_Store | Bin 0 -> 6148 bytes src/main/kotlin/ru/quipy/payments/.DS_Store | Bin 0 -> 6148 bytes .../payments/config/PaymentAccountsConfig.kt | 2 +- .../payments/logic/PaymentExternalServiceImpl.kt | 2 +- .../logic}/PaymentRateLimiterFactory.kt | 4 ++-- 10 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 .DS_Store create mode 100644 src/.DS_Store create mode 100644 src/main/.DS_Store create mode 100644 src/main/kotlin/.DS_Store create mode 100644 src/main/kotlin/ru/.DS_Store create mode 100644 src/main/kotlin/ru/quipy/.DS_Store create mode 100644 src/main/kotlin/ru/quipy/payments/.DS_Store rename src/main/kotlin/ru/quipy/{config => payments/logic}/PaymentRateLimiterFactory.kt (95%) diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..81229e8d26382bd3469cb3d9bb2de3f1954844ac GIT binary patch literal 6148 zcmeH~Jqp4=5QS&dB4Cr!avKle4VIuM@B*S@B?yZB9^E%TjnP_yyn&f-XEsBUS7b9H zqQmpN5$Q#wgBxXSVPuMYE)TiO>2iLYjtb*@FJ*K=2U&T%hcRwa*e@u>x3=Er<$CqZN!+^)bZi z-VT<$t|nVB+C_8t(7dzS6a&*}7cEF&S{)2jfC`Khm`C2*`M-mIoBu~GOsN1B_%j7` zvE6S6yi}g8AFpTiLso6w;GkcQ@b(jc#E#+>+ztE17GO=bASy8a2)GOkRN$uyya2`( B5q1Co literal 0 HcmV?d00001 diff --git a/src/.DS_Store b/src/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..254fc3aa651e4450a614b710682d5cd8457a722c GIT binary patch literal 6148 zcmeH~J8r{33`B>H3IlFbrrah6@C_D*bAntTAH#qFe+t9>9^FGOn<%aW!59MMTXIE6 znnEiAu+43^21Wo&x)X06CT7eBobZJM&imc<=lgOwdXcv1fT#2kll|Nlq<|EV0#ZN< zNP!tCkjMCPHKS+JqeuZMFb@U%`%vi4nrxl%>0pQvfE-v3<2q&uvUq{4$=1mV&2oCM zY_%9eydLdj$?Iyeb@p~x4j-0xHlJc>*4tr)3C(IiK?+ELi2^S@AN~A)r$3tiCoM{) zfE0K#1#H-UY&U$VJX`;~p4UIK>gz@)<8p>yKLJeqD1N1faliP2tjX5N3Qa!(A%lVx H_*Vt)CSMYs literal 0 HcmV?d00001 diff --git a/src/main/.DS_Store b/src/main/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a06cf1a7357e1d25892f7f31bf43bbf84a58aa1f GIT binary patch literal 6148 zcmeHKJ8r`;3?);e2$02NMqME{5Q6jsy+F_!1&SK+&}R3PbM=WRNfE zfAejFMg^z<6`%rCfC?A>JE0I)&W4Rh}$ zfW-p9nm7g`0@I)ZgR0qLXwVTanO76Xz@Up}^PzdOW{0AFJI*hjE?NUQQUNM3Rp23( zt=0c0_#gfMl*APkpaTC&0c{S?hdrK@wRQG5tF;Awgj>!x+zfN4VDNGb^m2@amE)}^ bMP9Kv_G{u8=yb%L4&={(=|ZCdw^rZ>v3?bm literal 0 HcmV?d00001 diff --git a/src/main/kotlin/.DS_Store b/src/main/kotlin/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..f7103b77437c85cd74adcf25347b9bbb3ebcb60b GIT binary patch literal 6148 zcmeH~JqiLr422W55Nx)zoW=uqgF*BJUO;rQ5(LG5j_%8kg{!qJ@&d^>$xhhaSL|#= zL^qFqEz*g|9B!1Ag@GyZiQMFI?(e7L^?I>F+9YWucqfDFuYDvHpaN8Y3Qz$mFdhZ+ zD4#FJ^GJLWDnJEBqJVuL3fx$eE$E*P1Rnvw8Om;0`z!$#D}XiGf~deWTES>lA49C} z?O@4$HQ9pEE}FxK=AG517??)8Xh8zg>R_M(RA8XMH1f{Q{~i3>{6A=6N(HFEpDCcT z?S8w#OXb=6@p@K2WYyLU4*GV4x1Rtcb`&q+Zn$1-0oG&-q5|WOfXl!@1%9f)6X&lG AJ^%m! literal 0 HcmV?d00001 diff --git a/src/main/kotlin/ru/.DS_Store b/src/main/kotlin/ru/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..67775a855c4ba08c030f9e49887774c362fca2b5 GIT binary patch literal 6148 zcmeH~O^O0R4254t2L@*2(q(Ntz}%n^=LB9rR0KhA265r8j_yk;8{1hAjl4kWRZ^Ay z`4(Lr0JgmDzknHl54tNBJ`BtlzvCBI9PxwOVSm|e&*zh;@s@i%pz|7!>$xlu0TB=Z z5fA|p7!iRu#CiUIj_8^6C?X&N;~?PQheCI)sjX{#IygiNK%Fuj#(DG-)Zz(hO>JG7 zp;>khmZcVLi07l6T5?}aZC!ggESnF@JDYbgH0$NC!hmKqM1u&3z<|J{$1^|wzv+MV z|AP{RA|L`UM!=@4^=iqN%Cq(9^*q1Htj`;r8rwNM`~)!Yqj*ja<9hK4wWhYN%+T~B M5EwLwz`qi>0}OW)s{jB1 literal 0 HcmV?d00001 diff --git a/src/main/kotlin/ru/quipy/.DS_Store b/src/main/kotlin/ru/quipy/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..4a9b381bef8f0149aedf7506dec5b7775d779455 GIT binary patch literal 6148 zcmeHKJ5EC}5S)b+Pohal=__ypD+(vb1^5IiAV`5C-K%mgj>hb#Nc2J%nrK#9kG@1siWrg3GhXn4FKl1e+viF4>40+weBu#1ydUzP zHk&u!lt}?8AO)m=6p#XERiMi2aC6pEb(j>80+*nGe;*p%u@{bs@#)|YEdX)Ga2V&& zOAwm}h`n%3WQ1l(B_`FX#jvC^-YTybj)_T!Rr6tWvsH&;aXZg%kq+yL8l`{~I9A{~ zmvgWG_w+yd|6`I?Qa}n^lma$eu9gcvscP%wab9a1{ekW|KXfoiegV2#6|MjP literal 0 HcmV?d00001 diff --git a/src/main/kotlin/ru/quipy/payments/.DS_Store b/src/main/kotlin/ru/quipy/payments/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..79ead5ce553f618001edc929c647391cf69dd803 GIT binary patch literal 6148 zcmeHKOG-mQ5Ue&q0&cQ&IalxoLx?Bv0%}4eAU;9dzg5oV(X9R;36BSE+(1f09T6W(z=?9cf- z40yul3&%u8XqHrBQmtAHOFHAN@_ONzm~>d&%ya5ys}9BDcE($z!+N4d zDIf(d6mri^f33hbdDIwe literal 0 HcmV?d00001 diff --git a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt index d12a2753e..243a454a5 100644 --- a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt +++ b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt @@ -6,7 +6,7 @@ import com.fasterxml.jackson.module.kotlin.registerKotlinModule import org.springframework.beans.factory.annotation.Value import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration -import ru.quipy.config.PaymentRateLimiterFactory +import ru.quipy.payments.logic.PaymentRateLimiterFactory import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate import ru.quipy.payments.logic.* diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index c5911a842..159caed2e 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -6,7 +6,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import org.slf4j.LoggerFactory -import ru.quipy.config.PaymentRateLimiterFactory +import ru.quipy.payments.logic.PaymentRateLimiterFactory import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate import java.net.SocketTimeoutException diff --git a/src/main/kotlin/ru/quipy/config/PaymentRateLimiterFactory.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt similarity index 95% rename from src/main/kotlin/ru/quipy/config/PaymentRateLimiterFactory.kt rename to src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt index 2b1b5fe63..5a6bded01 100644 --- a/src/main/kotlin/ru/quipy/config/PaymentRateLimiterFactory.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt @@ -1,9 +1,9 @@ -package ru.quipy.config +package ru.quipy.payments.logic import org.springframework.stereotype.Component import ru.quipy.common.utils.SlidingWindowRateLimiter -import java.util.concurrent.ConcurrentHashMap import java.time.Duration +import java.util.concurrent.ConcurrentHashMap @Component class PaymentRateLimiterFactory { From c4704bb5e065ca9054e962b8d6dd31fec0ffc88b Mon Sep 17 00:00:00 2001 From: danilkasirin <367279@niuitmo.ru> Date: Fri, 3 Oct 2025 00:10:12 +0300 Subject: [PATCH 06/11] metrics for hw-03 --- .../dashboards/ServicesStatistic.json | 1383 +++++++++-------- .../kotlin/ru/quipy/OnlineShopApplication.kt | 2 + .../ru/quipy/apigateway/APIController.kt | 5 + .../payments/config/PaymentAccountsConfig.kt | 8 +- .../quipy/payments/logic/MetricsReporter.kt | 80 + .../ru/quipy/payments/logic/OrderPayer.kt | 3 + .../logic/PaymentExternalServiceImpl.kt | 17 +- .../payments/logic/PaymentServiceImpl.kt | 4 + src/main/resources/application.properties | 2 +- 9 files changed, 870 insertions(+), 634 deletions(-) create mode 100644 src/main/kotlin/ru/quipy/payments/logic/MetricsReporter.kt diff --git a/grafana/provisioning/dashboards/ServicesStatistic.json b/grafana/provisioning/dashboards/ServicesStatistic.json index 684b97269..0c32b8927 100644 --- a/grafana/provisioning/dashboards/ServicesStatistic.json +++ b/grafana/provisioning/dashboards/ServicesStatistic.json @@ -24,614 +24,760 @@ "editable": true, "fiscalYearStartMonth": 0, "graphTooltip": 1, - "id": 4, "links": [], "liveNow": true, "panels": [ { - "collapsed": true, + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, "gridPos": { - "h": 1, - "w": 24, + "h": 8, + "w": 12, "x": 0, "y": 0 }, - "id": 27, - "panels": [ + "id": 103, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "bars", - "fillOpacity": 23, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "normal" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "noValue": "0", - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 1 - }, - "id": 29, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(rate(payment_finished_total{service=~\"$service\"}[30s])) by (service, outcome, failReason)", - "hide": false, - "instant": false, - "legendFormat": "{{outcome}} - {{failReason}}", - "range": true, - "refId": "B" - } - ], - "title": "Payments outcome rate per second", - "type": "timeseries" + "editorMode": "code", + "expr": "increase(payment_requests_incoming_total[5m])", + "instant": false, + "legendFormat": "incoming", + "range": true, + "refId": "A" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "line", - "fillOpacity": 0, - "gradientMode": "none", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "auto", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - } - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 1 - }, - "id": 26, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "exemplar": true, - "expr": "sum by (type) (rate(events_total{}[30s]))", - "interval": "", - "legendFormat": "{{type}}", - "range": true, - "refId": "A" - } - ], - "title": "Event during testing per second", - "type": "timeseries" + "editorMode": "code", + "expr": "increase(payment_requests_outgoing_total[5m])", + "hide": false, + "instant": false, + "legendFormat": "outgoing", + "range": true, + "refId": "B" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "bars", - "fillOpacity": 23, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "percent" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 265 - }, - "id": 28, - "options": { - "legend": { - "calcs": [], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" - } - }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service))", - "hide": false, - "instant": false, - "legendFormat": "income - {{service}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service))", - "hide": false, - "instant": false, - "legendFormat": "expenses - {{service}} ", - "range": true, - "refId": "A" - } - ], - "title": "Income / Expenses percentage", - "type": "timeseries" + "editorMode": "code", + "expr": "increase(payment_requests_completed_total[5m])", + "hide": false, + "instant": false, + "legendFormat": "completed", + "range": true, + "refId": "C" }, { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-RdYlGr" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "percentunit" - }, - "overrides": [] - }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 265 - }, - "id": 36, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "editorMode": "code", + "expr": "increase(payment_requests_failed_total[5m])", + "hide": false, + "instant": false, + "legendFormat": "failed", + "range": true, + "refId": "D" + } + ], + "title": "Servise request increase", + "type": "timeseries" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 102, + "panels": [], + "title": "Custom", + "type": "row" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 27, + "panels": [], + "title": "General", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 23, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service))", - "hide": false, - "instant": false, - "legendFormat": "income - {{service}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "mappings": [], + "noValue": "0", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "editorMode": "code", - "expr": "sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service))", - "hide": false, - "instant": false, - "legendFormat": "expenses - {{service}} ", - "range": true, - "refId": "A" - } - ], - "title": "Income / Expenses percentage", - "type": "stat" + { + "color": "red", + "value": 80 + } + ] + } }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 29, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "palette-classic" - }, - "custom": { - "axisBorderShow": false, - "axisCenteredZero": false, - "axisColorMode": "text", - "axisLabel": "", - "axisPlacement": "auto", - "barAlignment": 0, - "barWidthFactor": 0.6, - "drawStyle": "bars", - "fillOpacity": 23, - "gradientMode": "hue", - "hideFrom": { - "legend": false, - "tooltip": false, - "viz": false - }, - "insertNulls": false, - "lineInterpolation": "linear", - "lineWidth": 1, - "pointSize": 5, - "scaleDistribution": { - "type": "linear" - }, - "showPoints": "never", - "spanNulls": false, - "stacking": { - "group": "A", - "mode": "none" - }, - "thresholdsStyle": { - "mode": "off" - } - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] - }, - "unit": "short" - }, - "overrides": [] + "editorMode": "code", + "expr": "sum(rate(payment_finished_total{service=~\"$service\"}[30s])) by (service, outcome, failReason)", + "hide": false, + "instant": false, + "legendFormat": "{{outcome}} - {{failReason}}", + "range": true, + "refId": "B" + } + ], + "title": "Payments outcome rate per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } }, - "gridPos": { - "h": 8, - "w": 12, - "x": 0, - "y": 273 + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" }, - "id": 37, - "options": { - "legend": { - "calcs": [ - "lastNotNull" - ], - "displayMode": "table", - "placement": "right", - "showLegend": true - }, - "tooltip": { - "mode": "single", - "sort": "none" + "editorMode": "code", + "exemplar": true, + "expr": "sum by (type) (rate(events_total{}[30s]))", + "interval": "", + "legendFormat": "{{type}}", + "range": true, + "refId": "A" + } + ], + "title": "Event during testing per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 23, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "percent" + }, + "thresholdsStyle": { + "mode": "off" } }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "editorMode": "code", - "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service)", - "hide": false, - "instant": false, - "legendFormat": "income - {{service}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 28, + "options": { + "legend": { + "calcs": [], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service))", + "hide": false, + "instant": false, + "legendFormat": "income - {{service}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service))", + "hide": false, + "instant": false, + "legendFormat": "expenses - {{service}} ", + "range": true, + "refId": "A" + } + ], + "title": "Income / Expenses percentage", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-RdYlGr" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "editorMode": "code", - "expr": "sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service, accountName) ", - "hide": false, - "instant": false, - "legendFormat": "expenses - {{service}} - {{accountName}}", - "range": true, - "refId": "A" - } + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 36, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" ], - "title": "Income / Expenses amount", - "type": "timeseries" + "fields": "", + "values": false }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ { "datasource": { "type": "prometheus", "uid": "PBFA97CFB590B2093" }, - "fieldConfig": { - "defaults": { - "color": { - "mode": "continuous-RdYlGr" - }, - "mappings": [], - "thresholds": { - "mode": "absolute", - "steps": [ - { - "color": "green", - "value": null - }, - { - "color": "red", - "value": 80 - } - ] + "editorMode": "code", + "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service))", + "hide": false, + "instant": false, + "legendFormat": "income - {{service}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service) / (sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service) + sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service))", + "hide": false, + "instant": false, + "legendFormat": "expenses - {{service}} ", + "range": true, + "refId": "A" + } + ], + "title": "Income / Expenses percentage", + "type": "stat" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisBorderShow": false, + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 23, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "insertNulls": false, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "unit": "short" - }, - "overrides": [] + { + "color": "red", + "value": 80 + } + ] }, - "gridPos": { - "h": 8, - "w": 12, - "x": 12, - "y": 273 + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 26 + }, + "id": 37, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "maxHeight": 600, + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "11.4.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" }, - "id": 31, - "options": { - "colorMode": "value", - "graphMode": "area", - "justifyMode": "auto", - "orientation": "auto", - "percentChangeColorMode": "standard", - "reduceOptions": { - "calcs": [ - "lastNotNull" - ], - "fields": "", - "values": false - }, - "showPercentChange": false, - "textMode": "auto", - "wideLayout": true + "editorMode": "code", + "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[1m])) by (service)", + "hide": false, + "instant": false, + "legendFormat": "income - {{service}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" }, - "pluginVersion": "11.4.0", - "targets": [ - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" - }, - "editorMode": "code", - "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service)", - "hide": false, - "instant": false, - "legendFormat": "income - {{service}}", - "range": true, - "refId": "B" - }, - { - "datasource": { - "type": "prometheus", - "uid": "PBFA97CFB590B2093" + "editorMode": "code", + "expr": "sum(increase(external_amount_total{service=~\"$service\"}[1m])) by (service, accountName) ", + "hide": false, + "instant": false, + "legendFormat": "expenses - {{service}} - {{accountName}}", + "range": true, + "refId": "A" + } + ], + "title": "Income / Expenses amount", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-RdYlGr" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null }, - "editorMode": "code", - "expr": "sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service, accountName) ", - "hide": false, - "instant": false, - "legendFormat": "expenses - {{service}} - {{accountName}}", - "range": true, - "refId": "A" - } + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 26 + }, + "id": 31, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "percentChangeColorMode": "standard", + "reduceOptions": { + "calcs": [ + "lastNotNull" ], - "title": "Income / Expenses amount", - "type": "stat" + "fields": "", + "values": false + }, + "showPercentChange": false, + "textMode": "auto", + "wideLayout": true + }, + "pluginVersion": "11.0.0", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(payments_amount_total{service=~\"$service\"}[$__range])) by (service)", + "hide": false, + "instant": false, + "legendFormat": "income - {{service}}", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "PBFA97CFB590B2093" + }, + "editorMode": "code", + "expr": "sum(increase(external_amount_total{service=~\"$service\"}[$__range])) by (service, accountName) ", + "hide": false, + "instant": false, + "legendFormat": "expenses - {{service}} - {{accountName}}", + "range": true, + "refId": "A" } ], - "title": "General", - "type": "row" + "title": "Income / Expenses amount", + "type": "stat" }, { "collapsed": true, @@ -639,7 +785,7 @@ "h": 1, "w": 24, "x": 0, - "y": 1 + "y": 34 }, "id": 10, "panels": [ @@ -658,8 +804,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] } @@ -701,7 +846,7 @@ "h": 8, "w": 12, "x": 0, - "y": 2 + "y": 18 }, "id": 2, "options": { @@ -792,8 +937,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -840,7 +984,7 @@ "h": 8, "w": 12, "x": 12, - "y": 2 + "y": 18 }, "id": 13, "options": { @@ -924,8 +1068,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -941,7 +1084,7 @@ "h": 8, "w": 12, "x": 0, - "y": 226 + "y": 242 }, "id": 25, "options": { @@ -985,7 +1128,7 @@ "h": 1, "w": 24, "x": 0, - "y": 2 + "y": 35 }, "id": 12, "panels": [ @@ -1040,8 +1183,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1087,7 +1229,7 @@ "h": 8, "w": 12, "x": 0, - "y": 3 + "y": 19 }, "id": 7, "options": { @@ -1174,8 +1316,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1222,7 +1363,7 @@ "h": 8, "w": 12, "x": 12, - "y": 3 + "y": 19 }, "id": 8, "options": { @@ -1307,8 +1448,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -1320,7 +1460,7 @@ "h": 8, "w": 12, "x": 0, - "y": 123 + "y": 139 }, "id": 30, "options": { @@ -1378,7 +1518,7 @@ "h": 1, "w": 24, "x": 0, - "y": 3 + "y": 36 }, "id": 17, "panels": [ @@ -1433,8 +1573,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1481,7 +1620,7 @@ "h": 8, "w": 12, "x": 0, - "y": 4 + "y": 20 }, "id": 3, "options": { @@ -1569,8 +1708,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1617,7 +1755,7 @@ "h": 8, "w": 12, "x": 12, - "y": 4 + "y": 20 }, "id": 14, "options": { @@ -1707,8 +1845,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1755,7 +1892,7 @@ "h": 8, "w": 12, "x": 0, - "y": 100 + "y": 116 }, "id": 32, "options": { @@ -1845,8 +1982,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -1893,7 +2029,7 @@ "h": 8, "w": 12, "x": 12, - "y": 100 + "y": 116 }, "id": 34, "options": { @@ -1983,8 +2119,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2055,7 +2190,7 @@ "h": 8, "w": 12, "x": 12, - "y": 108 + "y": 124 }, "id": 43, "options": { @@ -2103,7 +2238,7 @@ "h": 1, "w": 24, "x": 0, - "y": 4 + "y": 37 }, "id": 21, "panels": [ @@ -2155,8 +2290,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2171,7 +2305,7 @@ "h": 8, "w": 12, "x": 0, - "y": 5 + "y": 21 }, "id": 19, "options": { @@ -2253,8 +2387,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2269,7 +2402,7 @@ "h": 8, "w": 12, "x": 12, - "y": 5 + "y": 21 }, "id": 22, "options": { @@ -2351,8 +2484,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2367,7 +2499,7 @@ "h": 8, "w": 12, "x": 12, - "y": 85 + "y": 101 }, "id": 100, "options": { @@ -2411,7 +2543,7 @@ "h": 1, "w": 24, "x": 0, - "y": 5 + "y": 38 }, "id": 38, "panels": [ @@ -2463,8 +2595,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2476,7 +2607,7 @@ "h": 8, "w": 12, "x": 0, - "y": 6 + "y": 22 }, "id": 40, "options": { @@ -2557,8 +2688,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2570,7 +2700,7 @@ "h": 8, "w": 12, "x": 12, - "y": 6 + "y": 22 }, "id": 39, "options": { @@ -2652,8 +2782,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -2669,7 +2798,7 @@ "h": 8, "w": 12, "x": 0, - "y": 38 + "y": 54 }, "id": 95, "options": { @@ -2766,8 +2895,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2779,7 +2907,7 @@ "h": 8, "w": 12, "x": 12, - "y": 38 + "y": 54 }, "id": 42, "options": { @@ -2860,8 +2988,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" } ] }, @@ -2873,7 +3000,7 @@ "h": 8, "w": 12, "x": 0, - "y": 46 + "y": 62 }, "id": 41, "options": { @@ -2916,7 +3043,7 @@ "h": 1, "w": 24, "x": 0, - "y": 6 + "y": 39 }, "id": 97, "panels": [ @@ -2937,7 +3064,6 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -2984,7 +3110,7 @@ "h": 8, "w": 12, "x": 0, - "y": 7 + "y": 47 }, "id": 98, "maxDataPoints": 100, @@ -2998,6 +3124,7 @@ "showLegend": true }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -3039,7 +3166,6 @@ "axisLabel": "", "axisPlacement": "auto", "barAlignment": 0, - "barWidthFactor": 0.6, "drawStyle": "line", "fillOpacity": 0, "gradientMode": "none", @@ -3086,7 +3212,7 @@ "h": 8, "w": 12, "x": 12, - "y": 7 + "y": 47 }, "id": 99, "maxDataPoints": 100, @@ -3100,6 +3226,7 @@ "showLegend": true }, "tooltip": { + "maxHeight": 600, "mode": "single", "sort": "none" } @@ -3173,8 +3300,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3190,7 +3316,7 @@ "h": 8, "w": 24, "x": 0, - "y": 15 + "y": 55 }, "id": 24, "options": { @@ -3312,8 +3438,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3329,7 +3454,7 @@ "h": 8, "w": 12, "x": 0, - "y": 23 + "y": 63 }, "id": 4, "options": { @@ -3412,8 +3537,7 @@ "mode": "absolute", "steps": [ { - "color": "green", - "value": null + "color": "green" }, { "color": "red", @@ -3429,7 +3553,7 @@ "h": 8, "w": 12, "x": 12, - "y": 23 + "y": 63 }, "id": 96, "options": { @@ -3470,15 +3594,15 @@ "type": "row" } ], - "preload": false, "refresh": "5s", - "schemaVersion": 40, + "schemaVersion": 39, "tags": [], "templating": { "list": [ { "allValue": ".*", "current": { + "selected": false, "text": "All", "value": "$__all" }, @@ -3487,7 +3611,9 @@ "uid": "PBFA97CFB590B2093" }, "definition": "label_values(service)", + "hide": 0, "includeAll": true, + "multi": false, "name": "service", "options": [], "query": { @@ -3496,6 +3622,8 @@ }, "refresh": 1, "regex": "", + "skipUrlSync": false, + "sort": 0, "type": "query" } ] @@ -3504,6 +3632,7 @@ "from": "now-15m", "to": "now" }, + "timeRangeUpdatedDuringEditOrView": false, "timepicker": { "refresh_intervals": [ "5s", @@ -3514,6 +3643,6 @@ "timezone": "", "title": "Services statistic", "uid": "KVr-Vmpnz", - "version": 4, + "version": 1, "weekStart": "" } \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/OnlineShopApplication.kt b/src/main/kotlin/ru/quipy/OnlineShopApplication.kt index 9ac22e094..41681afe8 100644 --- a/src/main/kotlin/ru/quipy/OnlineShopApplication.kt +++ b/src/main/kotlin/ru/quipy/OnlineShopApplication.kt @@ -3,12 +3,14 @@ package ru.quipy import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.scheduling.annotation.EnableScheduling import org.springframework.boot.runApplication import ru.quipy.common.utils.NamedThreadFactory import java.util.concurrent.Executors @SpringBootApplication +@EnableScheduling class OnlineShopApplication { val log: Logger = LoggerFactory.getLogger(OnlineShopApplication::class.java) diff --git a/src/main/kotlin/ru/quipy/apigateway/APIController.kt b/src/main/kotlin/ru/quipy/apigateway/APIController.kt index 6f23fa18d..e0ee9874e 100644 --- a/src/main/kotlin/ru/quipy/apigateway/APIController.kt +++ b/src/main/kotlin/ru/quipy/apigateway/APIController.kt @@ -6,6 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired import org.springframework.web.bind.annotation.* import ru.quipy.orders.repository.OrderRepository import ru.quipy.payments.logic.OrderPayer +import ru.quipy.payments.logic.MetricsReporter import java.util.* @RestController @@ -19,6 +20,9 @@ class APIController { @Autowired private lateinit var orderPayer: OrderPayer + @Autowired + private lateinit var metricsReporter: MetricsReporter + @PostMapping("/users") fun createUser(@RequestBody req: CreateUserRequest): User { return User(UUID.randomUUID(), req.name) @@ -56,6 +60,7 @@ class APIController { @PostMapping("/orders/{orderId}/payment") fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): PaymentSubmissionDto { + metricsReporter.incrementIncoming() val paymentId = UUID.randomUUID() val order = orderRepository.findById(orderId)?.let { orderRepository.save(it.copy(status = OrderStatus.PAYMENT_IN_PROGRESS)) diff --git a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt index 243a454a5..0bb510f43 100644 --- a/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt +++ b/src/main/kotlin/ru/quipy/payments/config/PaymentAccountsConfig.kt @@ -37,7 +37,10 @@ class PaymentAccountsConfig { lateinit var allowedAccounts: List @Bean - fun accountAdapters(paymentService: EventSourcingService): List { + fun accountAdapters( + paymentService: ru.quipy.core.EventSourcingService, + metricsReporter: MetricsReporter + ): List { val request = HttpRequest.newBuilder() .uri(URI("http://${paymentProviderHostPort}/external/accounts?serviceName=$serviceName&token=$token")) .GET() @@ -59,7 +62,8 @@ class PaymentAccountsConfig { paymentService, paymentProviderHostPort, token, - PaymentRateLimiterFactory() + PaymentRateLimiterFactory(), + metricsReporter ) } } diff --git a/src/main/kotlin/ru/quipy/payments/logic/MetricsReporter.kt b/src/main/kotlin/ru/quipy/payments/logic/MetricsReporter.kt new file mode 100644 index 000000000..01b62490e --- /dev/null +++ b/src/main/kotlin/ru/quipy/payments/logic/MetricsReporter.kt @@ -0,0 +1,80 @@ +package ru.quipy.payments.logic + +import io.micrometer.core.instrument.Counter +import io.micrometer.core.instrument.MeterRegistry +import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Component +import java.util.concurrent.TimeUnit +import java.util.concurrent.atomic.AtomicLong + +@Component +class MetricsReporter { + + private val logger = LoggerFactory.getLogger(MetricsReporter::class.java) + + private val incomingRequests = AtomicLong(0) + private val outgoingRequests = AtomicLong(0) + private val completedRequests = AtomicLong(0) + private val failedRequests = AtomicLong(0) + + @Autowired + private lateinit var meterRegistry: MeterRegistry + + private lateinit var incomingCounter: Counter + private lateinit var outgoingCounter: Counter + private lateinit var completedCounter: Counter + private lateinit var failedCounter: Counter + + @Autowired + fun initMetrics() { + incomingCounter = Counter.builder("payment.requests.incoming") + .description("Total incoming payment requests") + .register(meterRegistry) + + outgoingCounter = Counter.builder("payment.requests.outgoing") + .description("Total outgoing payment requests to external systems") + .register(meterRegistry) + + completedCounter = Counter.builder("payment.requests.completed") + .description("Total completed payment requests") + .register(meterRegistry) + + failedCounter = Counter.builder("payment.requests.failed") + .description("Total failed payment requests") + .register(meterRegistry) + } + + fun incrementIncoming() { + incomingRequests.incrementAndGet() + incomingCounter.increment() + } + + fun incrementOutgoing() { + outgoingRequests.incrementAndGet() + outgoingCounter.increment() + } + + fun incrementCompleted() { + completedRequests.incrementAndGet() + completedCounter.increment() + } + + fun incrementFailed() { + failedRequests.incrementAndGet() + failedCounter.increment() + } + + @Scheduled(fixedRate = 10, timeUnit = TimeUnit.SECONDS) + fun reportMetrics() { + val incoming = incomingRequests.get() + val outgoing = outgoingRequests.get() + val completed = completedRequests.get() + val failed = failedRequests.get() + + logger.info( + "METRICS | Incoming: $incoming | Outgoing: $outgoing | Completed: $completed | Failed: $failed" + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index a5909b85b..095db057d 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -26,6 +26,9 @@ class OrderPayer { @Autowired private lateinit var paymentService: PaymentService + @Autowired + private lateinit var metricsReporter: MetricsReporter + private val paymentExecutor = ThreadPoolExecutor( 16, 16, diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index 159caed2e..62d4bd1b2 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -6,6 +6,7 @@ import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody import org.slf4j.LoggerFactory +import org.springframework.beans.factory.annotation.Autowired import ru.quipy.payments.logic.PaymentRateLimiterFactory import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate @@ -13,6 +14,7 @@ import java.net.SocketTimeoutException import java.time.Duration import java.util.* import java.util.concurrent.Executors +import java.util.concurrent.atomic.AtomicLong // Advice: always treat time as a Duration @@ -21,12 +23,12 @@ class PaymentExternalSystemAdapterImpl( private val paymentESService: EventSourcingService, private val paymentProviderHostPort: String, private val token: String, - private val rateLimiterFactory: PaymentRateLimiterFactory + private val rateLimiterFactory: PaymentRateLimiterFactory, + private val metricsReporter: MetricsReporter ) : PaymentExternalSystemAdapter { companion object { val logger = LoggerFactory.getLogger(PaymentExternalSystemAdapter::class.java) - val emptyBody = RequestBody.create(null, ByteArray(0)) val mapper = ObjectMapper().registerKotlinModule() } @@ -46,6 +48,7 @@ class PaymentExternalSystemAdapterImpl( override fun performPaymentAsync(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { paymentExecutor.submit { try { + metricsReporter.incrementOutgoing() rateLimiter.tickBlocking() executePayment(paymentId, amount, paymentStartedAt, deadline) @@ -89,6 +92,12 @@ class PaymentExternalSystemAdapterImpl( paymentESService.update(paymentId) { it.logProcessing(body.result, now(), transactionId, reason = body.message) } + + if (body.result) { + metricsReporter.incrementCompleted() + } else { + metricsReporter.incrementFailed() + } } } catch (e: Exception) { when (e) { @@ -107,6 +116,7 @@ class PaymentExternalSystemAdapterImpl( } } } + metricsReporter.incrementFailed() } } @@ -115,7 +125,6 @@ class PaymentExternalSystemAdapterImpl( override fun isEnabled() = properties.enabled override fun name() = properties.accountName - } -public fun now() = System.currentTimeMillis() \ No newline at end of file +fun now() = System.currentTimeMillis() \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt index 1c24e5a72..80d184523 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentServiceImpl.kt @@ -21,7 +21,11 @@ class PaymentSystemImpl( val logger = LoggerFactory.getLogger(PaymentSystemImpl::class.java) } + @Autowired + private lateinit var metricsReporter: MetricsReporter + override fun submitPaymentRequest(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { + metricsReporter.incrementIncoming() for (account in paymentAccounts) { account.performPaymentAsync(paymentId, amount, paymentStartedAt, deadline) } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 719cb8de8..70328e2c4 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,5 +26,5 @@ management.endpoints.web.exposure.include=info,health,prometheus,metrics payment.service-name=${PAYMENT_SERVICE_NAME} payment.token=${PAYMENT_TOKEN} -payment.accounts=${PAYMENT_ACCOUNTS:acc-3} +payment.accounts=${PAYMENT_ACCOUNTS:acc-5} payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} \ No newline at end of file From 1207ca51a414c302be696f68a4128e474b920b11 Mon Sep 17 00:00:00 2001 From: Nazar Vakulenko Date: Fri, 17 Oct 2025 00:56:54 +0300 Subject: [PATCH 07/11] MEGAKNIGNT cooked --- .../ru/quipy/apigateway/APIController.kt | 23 +++++++++++++++---- .../ru/quipy/config/RateConfiguration.kt | 18 +++++++++++++++ .../logic/PaymentExternalServiceImpl.kt | 4 ++++ src/main/resources/application.properties | 2 +- 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/ru/quipy/config/RateConfiguration.kt diff --git a/src/main/kotlin/ru/quipy/apigateway/APIController.kt b/src/main/kotlin/ru/quipy/apigateway/APIController.kt index e0ee9874e..c2d8184ab 100644 --- a/src/main/kotlin/ru/quipy/apigateway/APIController.kt +++ b/src/main/kotlin/ru/quipy/apigateway/APIController.kt @@ -3,14 +3,26 @@ package ru.quipy.apigateway import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpStatus +import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* +import ru.quipy.common.utils.SlidingWindowRateLimiter import ru.quipy.orders.repository.OrderRepository import ru.quipy.payments.logic.OrderPayer import ru.quipy.payments.logic.MetricsReporter import java.util.* +import java.time.Duration @RestController -class APIController { +class APIController( + private val apiRateLimit: Long +) { + + private val rateLimiter by lazy { + SlidingWindowRateLimiter(apiRateLimit, Duration.ofSeconds(1)) + } + val logger: Logger = LoggerFactory.getLogger(APIController::class.java) @@ -59,17 +71,18 @@ class APIController { } @PostMapping("/orders/{orderId}/payment") - fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): PaymentSubmissionDto { - metricsReporter.incrementIncoming() + fun payOrder(@PathVariable orderId: UUID, @RequestParam deadline: Long): ResponseEntity { val paymentId = UUID.randomUUID() val order = orderRepository.findById(orderId)?.let { orderRepository.save(it.copy(status = OrderStatus.PAYMENT_IN_PROGRESS)) it } ?: throw IllegalArgumentException("No such order $orderId") - + if (!rateLimiter.tick()){ + return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).build(); + } val createdAt = orderPayer.processPayment(orderId, order.price, paymentId, deadline) - return PaymentSubmissionDto(createdAt, paymentId) + return ResponseEntity.ok(PaymentSubmissionDto(createdAt, paymentId)) } class PaymentSubmissionDto( diff --git a/src/main/kotlin/ru/quipy/config/RateConfiguration.kt b/src/main/kotlin/ru/quipy/config/RateConfiguration.kt new file mode 100644 index 000000000..432d46c51 --- /dev/null +++ b/src/main/kotlin/ru/quipy/config/RateConfiguration.kt @@ -0,0 +1,18 @@ +package ru.quipy.config + +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import ru.quipy.payments.logic.PaymentExternalSystemAdapter +import ru.quipy.payments.logic.PaymentExternalSystemAdapterImpl + +@Configuration +class RateConfiguration { + + @Bean + fun apiRateLimit(accountAdapters: List): Long { + return accountAdapters + .filterIsInstance() + .sumOf { it.getRateLimitPerSec().toLong() } + } +} + diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index 62d4bd1b2..90bd751e8 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -45,6 +45,10 @@ class PaymentExternalSystemAdapterImpl( private val client = OkHttpClient.Builder().build() + fun getRateLimitPerSec(): Int { + return this.rateLimitPerSec; + } + override fun performPaymentAsync(paymentId: UUID, amount: Int, paymentStartedAt: Long, deadline: Long) { paymentExecutor.submit { try { diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 70328e2c4..16a480e5d 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -26,5 +26,5 @@ management.endpoints.web.exposure.include=info,health,prometheus,metrics payment.service-name=${PAYMENT_SERVICE_NAME} payment.token=${PAYMENT_TOKEN} -payment.accounts=${PAYMENT_ACCOUNTS:acc-5} +payment.accounts=${PAYMENT_ACCOUNTS:acc-23} payment.hostPort=${PAYMENT_HOST:localhost}:${PAYMENT_PORT:1234} \ No newline at end of file From 29bae2cec5865e295e5c7447752236b300aa993d Mon Sep 17 00:00:00 2001 From: danilkasirin <367279@niuitmo.ru> Date: Fri, 24 Oct 2025 19:04:32 +0300 Subject: [PATCH 08/11] test-3 --- src/main/kotlin/ru/quipy/apigateway/.DS_Store | Bin 0 -> 6148 bytes .../ru/quipy/apigateway/APIController.kt | 12 +++++--- .../common/utils/LeakingBucketRateLimiter.kt | 12 +++++--- .../kotlin/ru/quipy/payments/logic/.DS_Store | Bin 0 -> 6148 bytes .../ru/quipy/payments/logic/OrderPayer.kt | 29 ++++++++++-------- .../logic/PaymentExternalServiceImpl.kt | 2 +- 6 files changed, 34 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/ru/quipy/apigateway/.DS_Store create mode 100644 src/main/kotlin/ru/quipy/payments/logic/.DS_Store diff --git a/src/main/kotlin/ru/quipy/apigateway/.DS_Store b/src/main/kotlin/ru/quipy/apigateway/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..9f4c79e3269422f21f93e5912874ecdfece1c952 GIT binary patch literal 6148 zcmeHKOG*Pl5PhXt1l)`}mwkeOHyC4FWFvwXkfAk)sj;wY;_G_|^;72Wx$dXmR=^vx7G8i_N-~fPBm>DnGH^%+ zcxFrHCyv=C1Ia)#@Wp`khr%Xo0|&>tb+FU*S^k+&LS1hOVF_#l2S<7+=BY$awRmEf zr_){{t_>U-P0{EeQ(bucketSize) + private val queue = LinkedBlockingQueue<() -> Unit>(bucketSize) override fun tick(): Boolean { - return queue.offer(1) + return queue.offer {} + } + + fun tick(task: () -> Unit): Boolean { + return queue.offer(task) } private val releaseJob = rateLimiterScope.launch { while (true) { delay(window.toMillis()) - for (i in 0..rate) { - queue.poll() + for (i in 0 until rate) { + queue.poll()?.invoke() } } }.invokeOnCompletion { th -> if (th != null) logger.error("Rate limiter release job completed", th) } diff --git a/src/main/kotlin/ru/quipy/payments/logic/.DS_Store b/src/main/kotlin/ru/quipy/payments/logic/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..384c84c3a646a42bb5e37329d55c803f68deff18 GIT binary patch literal 6148 zcmeHK-AV#M6#mA%kRq@k5K7o55PE~vG!TkHUm*XGNL?}e@ow*XhhCuv>$+?E&dd<& zy4qbxoU_b)^K;I(XTD{3b^w^#Q~w+&0w}Nu=J#1;n9NIAvz#B1Nn<(g(ZeHJ7+|{Q ztplonDzIw`$h&)tYre5_%#eJ4BlhPUpP_;W%$uXA*BwP&`pd_n%2HWcV+%OM4L$qT z{}vrAHbt6<-d`?qxn%nk&VS5^3GZ0Nkk^d8A$cT&3q%<6>Ttf#?@LBLrc9<}KBZq- z+vIlqS$uCPzH*AMfmg52ge)Vp?RuZBf&(;}?UbWg%%5a_Nv!`Y#oY2)U6E_d;-0}J zM|*6`Q(0v3=7y<^k5as=8D?Z=pXqzznVxKA*s?}^Fqb3~yJ|b90QYRM!hu7rRRL8% z75Gs=z7GM5VCXS+Xg?jS>=A&N=dd-l<@Z2h0*|4`*db?V#)T4HsPRV( literal 0 HcmV?d00001 diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index 095db057d..1d8501222 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -5,9 +5,11 @@ import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Service import ru.quipy.common.utils.CallerBlockingRejectedExecutionHandler +import ru.quipy.common.utils.LeakingBucketRateLimiter import ru.quipy.common.utils.NamedThreadFactory import ru.quipy.core.EventSourcingService import ru.quipy.payments.api.PaymentAggregate +import java.time.Duration import java.util.* import java.util.concurrent.LinkedBlockingQueue import java.util.concurrent.ThreadPoolExecutor @@ -39,20 +41,23 @@ class OrderPayer { CallerBlockingRejectedExecutionHandler() ) - fun processPayment(orderId: UUID, amount: Int, paymentId: UUID, deadline: Long): Long { + var limiter = LeakingBucketRateLimiter( + rate = 11, + window = Duration.ofSeconds(1), + bucketSize = 270 + ) + + fun processPayment(orderId: UUID, amount: Int, paymentId: UUID, deadline: Long): Long? { val createdAt = System.currentTimeMillis() - paymentExecutor.submit { - val createdEvent = paymentESService.create { - it.create( - paymentId, - orderId, - amount - ) + val admitted = limiter.tick { + paymentExecutor.submit { + val createdEvent = paymentESService.create { + it.create(paymentId, orderId, amount) + } + logger.trace("Payment ${createdEvent.paymentId} for order $orderId created.") + paymentService.submitPaymentRequest(paymentId, amount, createdAt, deadline) } - logger.trace("Payment ${createdEvent.paymentId} for order $orderId created.") - - paymentService.submitPaymentRequest(paymentId, amount, createdAt, deadline) } - return createdAt + return if (admitted) createdAt else null } } \ No newline at end of file diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index 90bd751e8..32304a2f8 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -39,7 +39,7 @@ class PaymentExternalSystemAdapterImpl( private val rateLimitPerSec = properties.rateLimitPerSec private val parallelRequests = properties.parallelRequests private val rateLimiter by lazy { - rateLimiterFactory.getRateLimiterForAccount(accountName, (rateLimitPerSec * 0.9).toInt()) + rateLimiterFactory.getRateLimiterForAccount(accountName, (rateLimitPerSec * 1.0).toInt()) } private val paymentExecutor = Executors.newFixedThreadPool(parallelRequests) From 406832571f25e433a14faf1041011b0f63e58bc5 Mon Sep 17 00:00:00 2001 From: Nazar Vakulenko Date: Fri, 31 Oct 2025 00:42:47 +0300 Subject: [PATCH 09/11] Just a little bit of magic: change hyper params --- src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index 1d8501222..631dde58a 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -44,7 +44,7 @@ class OrderPayer { var limiter = LeakingBucketRateLimiter( rate = 11, window = Duration.ofSeconds(1), - bucketSize = 270 + bucketSize = 121 ) fun processPayment(orderId: UUID, amount: Int, paymentId: UUID, deadline: Long): Long? { From f687da6c0f2a5fb5855e9ce410ca5a66d635f816 Mon Sep 17 00:00:00 2001 From: Nazar Vakulenko Date: Fri, 31 Oct 2025 00:44:21 +0300 Subject: [PATCH 10/11] Fix --- src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index 631dde58a..1d8501222 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -44,7 +44,7 @@ class OrderPayer { var limiter = LeakingBucketRateLimiter( rate = 11, window = Duration.ofSeconds(1), - bucketSize = 121 + bucketSize = 270 ) fun processPayment(orderId: UUID, amount: Int, paymentId: UUID, deadline: Long): Long? { From 2c35bc1936826c15ed3e1f9aae475d1784d17723 Mon Sep 17 00:00:00 2001 From: Nazar Vakulenko Date: Wed, 5 Nov 2025 22:59:29 +0300 Subject: [PATCH 11/11] feat: Improve LeakingBucket bucket size --- .../kotlin/ru/quipy/payments/logic/OrderPayer.kt | 2 +- .../payments/logic/PaymentExternalServiceImpl.kt | 2 +- .../payments/logic/PaymentRateLimiterFactory.kt | 12 +++++++----- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt index 1d8501222..965ccbb22 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/OrderPayer.kt @@ -44,7 +44,7 @@ class OrderPayer { var limiter = LeakingBucketRateLimiter( rate = 11, window = Duration.ofSeconds(1), - bucketSize = 270 + bucketSize = 275 ) fun processPayment(orderId: UUID, amount: Int, paymentId: UUID, deadline: Long): Long? { diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt index 32304a2f8..cebf53aed 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentExternalServiceImpl.kt @@ -53,7 +53,7 @@ class PaymentExternalSystemAdapterImpl( paymentExecutor.submit { try { metricsReporter.incrementOutgoing() - rateLimiter.tickBlocking() + rateLimiter.tick() executePayment(paymentId, amount, paymentStartedAt, deadline) } catch (e: Exception) { diff --git a/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt b/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt index 5a6bded01..2647e0282 100644 --- a/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt +++ b/src/main/kotlin/ru/quipy/payments/logic/PaymentRateLimiterFactory.kt @@ -1,6 +1,7 @@ package ru.quipy.payments.logic import org.springframework.stereotype.Component +import ru.quipy.common.utils.LeakingBucketRateLimiter import ru.quipy.common.utils.SlidingWindowRateLimiter import java.time.Duration import java.util.concurrent.ConcurrentHashMap @@ -8,13 +9,14 @@ import java.util.concurrent.ConcurrentHashMap @Component class PaymentRateLimiterFactory { - private val rateLimiters = ConcurrentHashMap() + private val rateLimiters = ConcurrentHashMap() - fun getRateLimiterForAccount(accountName: String, rateLimitPerSec: Int): SlidingWindowRateLimiter { + fun getRateLimiterForAccount(accountName: String, rateLimitPerSec: Int): LeakingBucketRateLimiter { return rateLimiters.computeIfAbsent(accountName) { - SlidingWindowRateLimiter( - rate = rateLimitPerSec.toLong(), - window = Duration.ofSeconds(1) + LeakingBucketRateLimiter( + rate = 11, + window = Duration.ofSeconds(1), + bucketSize = 275 ) } }