-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Object Pooling for Memory Optimization #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
77109a1
775a16c
c8b60fc
ca550b9
541b4b3
2954106
cdd5d1e
cba5cf3
be07376
aa7afe6
9d5454c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| name: Java Institutional CI | ||
|
|
||
| on: | ||
| push: | ||
| branches: [ "main", "feature/*" ] | ||
| pull_request: | ||
| branches: [ "main" ] | ||
|
|
||
| jobs: | ||
| build: | ||
| name: Build and Test (JDK 21) | ||
| runs-on: ubuntu-latest | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - name: Set up JDK 21 | ||
| uses: actions/setup-java@v4 | ||
| with: | ||
| java-version: '21' | ||
| distribution: 'temurin' | ||
| cache: maven | ||
|
|
||
| - name: Build with Maven | ||
| run: mvn -B package --file pom.xml | ||
|
|
||
| - name: Run Tests | ||
| run: mvn test | ||
|
|
||
| - name: Security Check (Snyk) | ||
| uses: snyk/actions/maven@master | ||
| continue-on-error: true | ||
| env: | ||
| SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} |
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,21 +1,55 @@ | ||
| # Institutional OMS Architecture | ||
| # 🏛️ Institutional OMS Architecture | ||
|
|
||
| ## Conceptual Design | ||
| ## 1. High-Level Design | ||
| The `trade-oms-java-core` follows a **Clean Hexagonal Architecture**, optimized for the **Java 21 Virtual Threads** and **Project Reactor** ecosystems. | ||
|
|
||
| The `trade-oms-java-core` is architected as a cloud-native microservice following the **Hexagonal Architecture (Ports and Adapters)** pattern. This ensures that the core trading logic remains independent of external technologies such as databases, messaging queues, or UI frameworks. | ||
| ```mermaid | ||
| graph TD | ||
| subgraph "External Adapters" | ||
| REST["REST API (WebFlux)"] | ||
| WS["WebSocket Ingest"] | ||
| end | ||
|
|
||
| ### Reactive Execution Pipeline | ||
| subgraph "Application Core" | ||
| direction TB | ||
| PortIn["SubmitOrderUseCase (Port)"] | ||
| Service["OrderService (Domain Service)"] | ||
| Pool["OrderPool (Memory Management)"] | ||
| VThreads["Virtual Threat Executor"] | ||
| end | ||
|
|
||
| 1. **Inbound Port**: Orders enter via REST/WebSocket (WebFlux). | ||
| 2. **Domain Layer**: The `Order` entity undergoes institutional validation. | ||
| 3. **Execution Service**: The `OrderService` handles state transitions. | ||
| 4. **Concurrency Model**: | ||
| - **Non-blocking I/O**: For all external network/DB calls. | ||
| - **Virtual Threads**: For processing-intensive or legacy-blocking components, allowing for linear scalability without thread-pool exhaustion. | ||
| subgraph "Domain Model" | ||
| Order["Order Entity"] | ||
| end | ||
|
|
||
| ### Component Diagram (Conceptual) | ||
| - **API Adapter** (Spring WebFlux) -> **SubmitOrder Port** -> **OrderService** (Domain Logic) -> **Repository Port** (R2DBC Adapter). | ||
| subgraph "Infrastructure Adapters" | ||
| DB["PostgreSQL (R2DBC)"] | ||
| Audit["Audit Log (File/Cloud)"] | ||
| end | ||
|
|
||
| ## Scalability and Resilience | ||
| - **Horizontally Scalable**: Stateless design allows for immediate scaling behind a load balancer. | ||
| - **Resilience**: Integrated circuit breakers and back-pressure handling via Project Reactor. | ||
| REST --> PortIn | ||
| WS --> PortIn | ||
| PortIn --> Service | ||
| Service --> Pool | ||
| Service --> VThreads | ||
| Service --> Order | ||
| VThreads --> DB | ||
| Service --> Audit | ||
| ``` | ||
|
|
||
| ## 2. Low-Latency Memory Strategy | ||
| To maintain sub-millisecond execution times under heavy load, the system utilizes a **Zero-Allocation** strategy for hot-path objects. | ||
|
|
||
| - **Object Pooling**: Instead of relying on the JVM Garbage Collector for every order, we borrow `Order` instances from a pre-allocated `OrderPool`. | ||
| - **Reactive Lifecycle**: Objects are automatically returned to the pool using `.doFinally()` hooks in the Reactor stream. | ||
|
|
||
| ## 3. Concurrency Matrix | ||
| | Component | Engine | Strategy | | ||
| | :--- | :--- | :--- | | ||
| | API Ingest | WebFlux | Event Loop (Non-blocking) | | ||
| | Order Validation | Project Reactor | Functional / Stateless | | ||
| | Execution Logic | Virtual Threads | Thread-per-request (Lightweight) | | ||
| | Database I/O | R2DBC | Asynchronous | | ||
|
|
||
| --- | ||
| *Engineering standard: Castle Trade LLC Institutional Framework.* |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,61 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package com.castletrade.oms.core.domain.model; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.extern.slf4j.Slf4j; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Component; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.ArrayBlockingQueue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import java.util.concurrent.BlockingQueue; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * High-performance object pool for Order objects. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Designed to minimize GC pressure in high-frequency trading scenarios. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Slf4j | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Component | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class OrderPool { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (complexity): Considera introducir una interfaz OrderProvider y una implementación alternativa sencilla para que el pooling quede oculto tras una abstracción y pueda sustituirse sin tocar el código consumidor. Puedes mantener la optimización de pooling pero desacoplarla del dominio y facilitar su sustitución por una implementación más simple. 1. Introduce una interfaz
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private final BlockingQueue<Order> pool; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private static final int DEFAULT_POOL_SIZE = 1000; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public OrderPool() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this(DEFAULT_POOL_SIZE); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public OrderPool(int size) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| this.pool = new ArrayBlockingQueue<>(size); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (int i = 0; i < size; i++) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pool.add(new Order()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.info("Initialized OrderPool with {} pre-allocated objects", size); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Borrows an order from the pool. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public Order borrowOrder() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Order order = pool.poll(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (order == null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.warn("OrderPool exhausted, creating new transient object. Consider increasing pool size."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return new Order(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+31
to
+39
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (performance): Usar WARN para el agotamiento del pool en una ruta caliente puede generar un exceso de ruido en los logs bajo carga. En escenarios de alto rendimiento, esta condición puede producirse con frecuencia, así que
Suggested change
Original comment in Englishsuggestion (performance): Using WARN for pool exhaustion in a hot path may generate excessive log noise under load. In high-throughput scenarios, this condition may be hit frequently, so
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return order; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Returns an order to the pool after resetting its state. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public void returnOrder(Order order) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (order == null) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| order.clear(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| boolean accepted = pool.offer(order); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!accepted) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| log.debug("OrderPool full, discarding order object."); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+51
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Devolver la misma instancia de Dado que Original comment in Englishissue (bug_risk): Returning the same Since |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| * Returns the current number of available objects in the pool. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public int getAvailableCount() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return pool.size(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| spring: | ||
| application: | ||
| name: trade-oms-java-core | ||
| threads: | ||
| virtual: | ||
| enabled: true | ||
| r2dbc: | ||
| url: r2dbc:h2:mem:///testdb;DB_CLOSE_DELAY=-1 | ||
| username: sa | ||
| password: | ||
| sql: | ||
| init: | ||
| mode: always |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.castletrade.oms.core.domain.model; | ||
|
|
||
| import org.junit.jupiter.api.Test; | ||
| import static org.junit.jupiter.api.Assertions.*; | ||
|
|
||
| class OrderPoolTest { | ||
|
sourcery-ai[bot] marked this conversation as resolved.
|
||
|
|
||
| @Test | ||
| void testBorrowAndReturn() { | ||
| OrderPool pool = new OrderPool(10); | ||
| assertEquals(10, pool.getAvailableCount()); | ||
|
|
||
| Order order = pool.borrowOrder(); | ||
| assertNotNull(order); | ||
| assertEquals(9, pool.getAvailableCount()); | ||
|
|
||
| order.setSymbol("BTC/USD"); | ||
|
sourcery-ai[bot] marked this conversation as resolved.
|
||
| pool.returnOrder(order); | ||
|
sourcery-ai[bot] marked this conversation as resolved.
|
||
|
|
||
| assertEquals(10, pool.getAvailableCount()); | ||
|
|
||
| Order reborrowed = pool.borrowOrder(); | ||
| assertNull(reborrowed.getSymbol(), "Order should be cleared when returned to pool"); | ||
| } | ||
|
|
||
| @Test | ||
| void testPoolExhaustion() { | ||
| OrderPool pool = new OrderPool(1); | ||
| pool.borrowOrder(); | ||
|
|
||
| Order exhausted = pool.borrowOrder(); | ||
| assertNotNull(exhausted, "Should create fresh object when pool is empty"); | ||
| assertEquals(0, pool.getAvailableCount()); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion (bug_risk): Considera restringir la visibilidad de
clear()para limitar su mal uso fuera de los contextos de pool.Como método público,
clear()permite que cualquier consumidor reinicie unOrderque puede seguir en uso, lo que puede causar errores sutiles (por ejemplo, entradas en colecciones que de repente pierden sus campos de identidad). Dado que existe para el uso con el pool, considera hacerlo con visibilidad de paquete (package‑private) o limitar su visibilidad de otra forma de modo que soloOrderPoolpueda llamarlo.Implementación sugerida:
OrderPool) que invoqueclear()resida en el mismo paquetecom.castletrade.oms.core.domain.modelo ajusta su paquete en consecuencia.order.clear()desde un paquete diferente, mueve esos tests al mismo paquete o accede a través de la abstracción de pooling en lugar de llamar aclear()directamente.Original comment in English
suggestion (bug_risk): Consider restricting
clear()visibility to limit misuse outside pooling contexts.As a public method,
clear()lets any caller reset anOrderthat may still be in use, which can cause subtle bugs (e.g., entries in collections suddenly losing their identity fields). Since it exists for pooling, consider making it package‑private or otherwise limiting visibility so onlyOrderPoolcan call it.Suggested implementation:
OrderPool) that invokesclear()resides in the same packagecom.castletrade.oms.core.domain.modelor adjust its package accordingly.order.clear()from a different package, either move those tests into the same package or access it via the pooling abstraction instead of directly callingclear().