Skip to content

Commit 0e0fa45

Browse files
committed
Update
1 parent 1a89f1f commit 0e0fa45

5 files changed

Lines changed: 83 additions & 49 deletions

File tree

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,11 @@ handle other errors, for example RESOURCE_EXHAUSTED (HTTP 429).
124124

125125
### Memory Configuration
126126

127-
The `SpiceClient` uses an Arrow `RootAllocator` for managing off-heap memory. By default, it limits memory to 50% of the JVM's maximum heap size or 1GB, whichever is lower. To prevent OOM issues in constrained environments, you can limit the maximum memory:
127+
The `SpiceClient` uses an Arrow `RootAllocator` for managing off-heap memory. By default, it uses all available memory. You can configure the memory limit using megabytes:
128128

129129
```java
130130
SpiceClient client = SpiceClient.builder()
131-
.withMaxMemory(1024 * 1024 * 1024L) // 1GB limit
131+
.withMemoryLimitMB(1024) // 1GB limit
132132
.build();
133133
```
134134

pom.xml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,6 @@
5353
<artifactId>slf4j-simple</artifactId>
5454
<version>2.0.16</version>
5555
</dependency>
56-
<dependency>
57-
<groupId>com.github.oshi</groupId>
58-
<artifactId>oshi-core</artifactId>
59-
<version>6.9.1</version>
60-
</dependency>
6156
<dependency>
6257
<groupId>junit</groupId>
6358
<artifactId>junit</artifactId>

src/main/java/ai/spice/SpiceClient.java

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ of this software and associated documentation files (the "Software"), to deal
6161
*/
6262
public class SpiceClient implements AutoCloseable {
6363

64+
private static final long BYTES_PER_MB = 1024L * 1024L;
65+
6466
private String appId;
6567
private String apiKey;
6668
private URI flightAddress;
@@ -86,15 +88,19 @@ public static SpiceClientBuilder builder() throws URISyntaxException {
8688
* application
8789
* @param apiKey the API key used for authentication with Spice.ai
8890
* services
89-
* @param flightAddress the URI of the flight address for connecting to Spice.ai
91+
* @param flightAddress the URI of the flight address for connecting to
92+
* Spice.ai
9093
* services
9194
* @param httpAddress the URI of the Spice.ai runtime HTTP address
9295
*
93-
* @param maxRetries the maximum number of connection retries for the client
96+
* @param maxRetries the maximum number of connection retries for the
97+
* client
9498
* @param userAgent the user agent string
95-
* @param maxMemory the maximum memory allocation for the Arrow RootAllocator
99+
* @param memoryLimitMB the memory limit in megabytes for the Arrow
100+
* RootAllocator
96101
*/
97-
public SpiceClient(String appId, String apiKey, URI flightAddress, URI httpAddress, int maxRetries, String userAgent, long maxMemory) {
102+
public SpiceClient(String appId, String apiKey, URI flightAddress, URI httpAddress, int maxRetries,
103+
String userAgent, long memoryLimitMB) {
98104
this.appId = appId;
99105
this.apiKey = apiKey;
100106
this.maxRetries = maxRetries;
@@ -110,7 +116,12 @@ public SpiceClient(String appId, String apiKey, URI flightAddress, URI httpAddre
110116
this.flightAddress = flightAddress;
111117
}
112118

113-
Builder builder = FlightClient.builder(new RootAllocator(maxMemory), new Location(this.flightAddress));
119+
// Convert megabytes to bytes for RootAllocator:
120+
// https://arrow.apache.org/java/main/reference/org.apache.arrow.memory.core/org/apache/arrow/memory/RootAllocator.html
121+
long memoryLimitBytes = (memoryLimitMB > Long.MAX_VALUE / BYTES_PER_MB)
122+
? Long.MAX_VALUE
123+
: memoryLimitMB * BYTES_PER_MB;
124+
Builder builder = FlightClient.builder(new RootAllocator(memoryLimitBytes), new Location(this.flightAddress));
114125

115126
if (Strings.isNullOrEmpty(apiKey)) {
116127
this.flightClient = new FlightSqlClient(builder.build());

src/main/java/ai/spice/SpiceClientBuilder.java

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ of this software and associated documentation files (the "Software"), to deal
2626
import java.net.URISyntaxException;
2727

2828
import com.google.common.base.Strings;
29-
import oshi.SystemInfo;
30-
import oshi.hardware.GlobalMemory;
3129

3230
/**
3331
* Builder class for creating instances of SpiceClient.
@@ -40,33 +38,7 @@ public class SpiceClientBuilder {
4038
private URI flightAddress;
4139
private URI httpAddress;
4240
private int maxRetries = 3;
43-
private long maxMemory = Long.MAX_VALUE;
44-
45-
/**
46-
* Calculates the default maximum memory for the Arrow RootAllocator.
47-
* Returns 50% of the system's total physical memory or 1GB, whichever is lower.
48-
* Falls back to JVM heap size if system memory cannot be determined.
49-
*
50-
* @return the default maximum memory in bytes.
51-
*/
52-
private static long calculateDefaultMaxMemory() {
53-
long oneGB = 1024L * 1024 * 1024;
54-
try {
55-
SystemInfo systemInfo = new SystemInfo();
56-
GlobalMemory memory = systemInfo.getHardware().getMemory();
57-
long totalMemory = memory.getTotal();
58-
if (totalMemory > 0) {
59-
long halfMemory = totalMemory / 2;
60-
return Math.min(halfMemory, oneGB);
61-
}
62-
} catch (Exception e) {
63-
// Fallback to JVM heap if OSHI fails
64-
}
65-
66-
long jvmMaxMemory = Runtime.getRuntime().maxMemory();
67-
long halfMemory = jvmMaxMemory / 2;
68-
return Math.min(halfMemory, oneGB);
69-
}
41+
private long memoryLimitMB = Long.MAX_VALUE; // Default is all available memory.
7042

7143
/**
7244
* Constructs a new SpiceClientBuilder instance
@@ -76,7 +48,6 @@ private static long calculateDefaultMaxMemory() {
7648
SpiceClientBuilder() throws URISyntaxException {
7749
this.flightAddress = Config.getLocalFlightAddressUri();
7850
this.httpAddress = Config.getLocalHttpAddressUri();
79-
this.maxMemory = calculateDefaultMaxMemory();
8051
}
8152

8253
/**
@@ -170,16 +141,22 @@ public SpiceClientBuilder withMaxRetries(int maxRetries) {
170141
}
171142

172143
/**
173-
* Sets the maximum memory allocation for the Arrow RootAllocator.
144+
* Sets the memory limit for Apache Arrow allocator in megabytes.
145+
* This controls the maximum amount of off-heap memory that can be allocated
146+
* for Arrow Flight operations. If not set, the allocator will use all available memory.
174147
*
175-
* @param maxMemory The maximum memory in bytes for the RootAllocator (must be > 0)
148+
* @param memoryLimitMB Maximum memory limit in megabytes. Default is all available memory.
149+
* Must be positive.
176150
* @return The current instance of SpiceClientBuilder for method chaining.
151+
* @throws IllegalArgumentException if memoryLimitMB is not positive
152+
*
153+
* @see org.apache.arrow.memory.RootAllocator
177154
*/
178-
public SpiceClientBuilder withMaxMemory(long maxMemory) {
179-
if (maxMemory <= 0) {
180-
throw new IllegalArgumentException("maxMemory must be greater than 0");
155+
public SpiceClientBuilder withMemoryLimitMB(long memoryLimitMB) {
156+
if (memoryLimitMB <= 0) {
157+
throw new IllegalArgumentException("Memory limit must be positive, got: " + memoryLimitMB + " MB");
181158
}
182-
this.maxMemory = maxMemory;
159+
this.memoryLimitMB = memoryLimitMB;
183160
return this;
184161
}
185162

@@ -189,6 +166,6 @@ public SpiceClientBuilder withMaxMemory(long maxMemory) {
189166
* @return The SpiceClient instance
190167
*/
191168
public SpiceClient build() {
192-
return new SpiceClient(appId, apiKey, flightAddress, httpAddress, maxRetries, userAgent, maxMemory);
169+
return new SpiceClient(appId, apiKey, flightAddress, httpAddress, maxRetries, userAgent, memoryLimitMB);
193170
}
194171
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package ai.spice;
2+
3+
import junit.framework.TestCase;
4+
5+
/**
6+
* Test memory configuration functionality
7+
*/
8+
public class MemoryConfigurationTest extends TestCase {
9+
10+
public void testMemoryLimitMBConfiguration() throws Exception {
11+
try {
12+
// Test that memory limit in MB is properly configured
13+
SpiceClient client = SpiceClient.builder()
14+
.withMemoryLimitMB(128) // 128 MB
15+
.build();
16+
17+
// If we reach here without exception, the configuration worked
18+
assertTrue("Memory configuration should not throw exception", true);
19+
20+
client.close();
21+
} catch (Exception e) {
22+
// We expect some exceptions due to no local Spice instance,
23+
// but not IllegalArgumentException from memory configuration
24+
assertFalse("Should not throw IllegalArgumentException for valid memory config",
25+
e instanceof IllegalArgumentException);
26+
}
27+
}
28+
29+
public void testInvalidMemoryLimitMB() throws Exception {
30+
try {
31+
SpiceClient.builder()
32+
.withMemoryLimitMB(0) // Invalid: must be positive
33+
.build();
34+
fail("Should throw IllegalArgumentException for zero memory limit");
35+
} catch (IllegalArgumentException e) {
36+
// Expected exception
37+
assertTrue("Should throw IllegalArgumentException for zero memory limit", true);
38+
}
39+
}
40+
41+
public void testNegativeMemoryLimitMB() throws Exception {
42+
try {
43+
SpiceClient.builder()
44+
.withMemoryLimitMB(-100) // Invalid: negative
45+
.build();
46+
fail("Should throw IllegalArgumentException for negative memory limit");
47+
} catch (IllegalArgumentException e) {
48+
assertTrue("Should throw IllegalArgumentException for negative memory limit", true);
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)