Skip to content

Commit 02e90fb

Browse files
authored
Feature - Retry Logic + Universal Logger (#6)
* Retry Wrapper for query() * Internal Logger Impl * Logs for Client and Connection Pool
1 parent 1ff0804 commit 02e90fb

File tree

11 files changed

+779
-30
lines changed

11 files changed

+779
-30
lines changed

CMakeLists.txt

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,30 @@ option(BUILD_EXAMPLES "Build example applications" ON)
1414
option(BUILD_TESTS "Build tests" OFF)
1515
option(BUILD_SHARED_LIBS "Build shared libraries" ON)
1616

17-
# Find ODBC
17+
# Platform-specific configuration
1818
if(WIN32)
1919
# Windows uses odbc32
2020
set(ODBC_LIBRARIES odbc32)
21-
else()
22-
# Unix/Linux/macOS uses unixODBC or iODBC
23-
# Try to find ODBC using standard search paths
21+
elseif(APPLE)
22+
# macOS: Prefer Homebrew paths on Apple Silicon
23+
if(NOT CMAKE_PREFIX_PATH AND CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
24+
list(PREPEND CMAKE_PREFIX_PATH "/opt/homebrew")
25+
endif()
26+
27+
# Find ODBC using macOS paths
2428
find_path(ODBC_INCLUDE_DIR sql.h
2529
PATHS
2630
/opt/homebrew/include
2731
/usr/local/include
28-
/usr/include
2932
/opt/local/include
3033
PATH_SUFFIXES odbc
3134
NO_DEFAULT_PATH)
3235

3336
find_library(ODBC_LIBRARY
3437
NAMES odbc iodbc unixodbc
3538
PATHS
36-
/opt/homebrew/lib
39+
/opt/homebrew/lib
3740
/usr/local/lib
38-
/usr/lib
3941
/opt/local/lib
4042
NO_DEFAULT_PATH)
4143

@@ -48,19 +50,50 @@ else()
4850
endif()
4951
else()
5052
message(FATAL_ERROR
51-
"ODBC library not found. Please install the ODBC driver manager:\n"
52-
" macOS: brew install unixodbc\n"
53-
" Linux: sudo apt-get install unixodbc-dev (Debian/Ubuntu)\n"
54-
" sudo yum install unixODBC-devel (RedHat/CentOS)")
53+
"ODBC library not found. Please install:\n"
54+
" brew install unixodbc")
55+
endif()
56+
else()
57+
# Linux: Use standard paths
58+
find_path(ODBC_INCLUDE_DIR sql.h
59+
PATHS
60+
/usr/include
61+
/usr/local/include
62+
PATH_SUFFIXES odbc
63+
NO_DEFAULT_PATH)
64+
65+
find_library(ODBC_LIBRARY
66+
NAMES odbc unixodbc
67+
PATHS
68+
/usr/lib
69+
/usr/local/lib
70+
NO_DEFAULT_PATH)
71+
72+
if(ODBC_LIBRARY)
73+
set(ODBC_LIBRARIES ${ODBC_LIBRARY})
74+
message(STATUS "Found ODBC library: ${ODBC_LIBRARY}")
75+
if(ODBC_INCLUDE_DIR)
76+
set(ODBC_INCLUDE_DIRS ${ODBC_INCLUDE_DIR})
77+
message(STATUS "Found ODBC include: ${ODBC_INCLUDE_DIR}")
78+
endif()
79+
else()
80+
message(FATAL_ERROR
81+
"ODBC library not found. Please install:\n"
82+
" Debian/Ubuntu: sudo apt-get install unixodbc-dev\n"
83+
" RedHat/CentOS: sudo yum install unixODBC-devel")
5584
endif()
5685
endif()
5786

87+
# External libraries
88+
find_package(spdlog CONFIG REQUIRED)
89+
5890
# Library sources
5991
set(SOURCES
6092
src/client.cpp
6193
src/config.cpp
6294
src/connection_pool.cpp
6395
src/internal/pool_manager.cpp
96+
src/internal/logger.cpp
6497
)
6598

6699
set(HEADERS
@@ -73,6 +106,7 @@ set(HEADERS
73106
# Internal headers (not installed)
74107
set(INTERNAL_HEADERS
75108
src/internal/pool_manager.h
109+
src/internal/logger.h
76110
)
77111

78112
# Create library target
@@ -88,7 +122,10 @@ target_include_directories(databricks_sdk
88122
)
89123

90124
# Link ODBC libraries
91-
target_link_libraries(databricks_sdk PRIVATE ${ODBC_LIBRARIES})
125+
target_link_libraries(databricks_sdk PRIVATE
126+
${ODBC_LIBRARIES}
127+
spdlog::spdlog
128+
)
92129

93130
# Set library properties
94131
set_target_properties(databricks_sdk PROPERTIES

README.md

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,12 @@ The SDK uses a modular configuration system with separate concerns for authentic
113113

114114
#### Configuration Structure
115115

116-
The SDK separates configuration into three distinct concerns:
116+
The SDK separates configuration into four distinct concerns:
117117

118118
- **`AuthConfig`**: Core authentication (host, token, timeout) - shared across all Databricks features
119119
- **`SQLConfig`**: SQL-specific settings (http_path, ODBC driver name)
120120
- **`PoolingConfig`**: Optional connection pooling settings (enabled, min/max connections)
121+
- **`RetryConfig`**: Optional automatic retry settings (enabled, max attempts, backoff strategy)
121122

122123
This modular design allows you to:
123124
- Share `AuthConfig` across different Databricks service clients (SQL, Workspace, Delta, etc.)
@@ -267,6 +268,61 @@ int main() {
267268

268269
**Note**: Multiple Clients with the same config automatically share the same pool!
269270

271+
### Automatic Retry Logic (Reliability)
272+
273+
The SDK includes automatic retry logic with exponential backoff for transient failures:
274+
275+
```cpp
276+
#include <databricks/client.h>
277+
278+
int main() {
279+
// Configure retry behavior
280+
databricks::RetryConfig retry;
281+
retry.enabled = true; // Enable retries (default: true)
282+
retry.max_attempts = 5; // Retry up to 5 times (default: 3)
283+
retry.initial_backoff_ms = 200; // Start with 200ms delay (default: 100ms)
284+
retry.backoff_multiplier = 2.0; // Double delay each retry (default: 2.0)
285+
retry.max_backoff_ms = 10000; // Cap at 10 seconds (default: 10000ms)
286+
retry.retry_on_timeout = true; // Retry timeout errors (default: true)
287+
retry.retry_on_connection_lost = true;// Retry connection errors (default: true)
288+
289+
// Build client with retry configuration
290+
auto client = databricks::Client::Builder()
291+
.with_environment_config()
292+
.with_retry(retry)
293+
.build();
294+
295+
// Queries automatically retry on transient errors
296+
auto results = client.query("SELECT * FROM my_table");
297+
298+
return 0;
299+
}
300+
```
301+
302+
**Retry Features:**
303+
- **Exponential backoff** with jitter to prevent thundering herd
304+
- **Intelligent error classification** - only retries transient errors:
305+
- Connection timeouts and network errors
306+
- Server unavailability (503, 502, 504)
307+
- Rate limiting (429 Too Many Requests)
308+
- **Non-retryable errors** fail immediately:
309+
- Authentication failures
310+
- SQL syntax errors
311+
- Permission denied errors
312+
- **Enabled by default** with sensible defaults
313+
- **Works with connection pooling** for maximum reliability
314+
315+
**Disable Retries (if needed):**
316+
```cpp
317+
databricks::RetryConfig no_retry;
318+
no_retry.enabled = false;
319+
320+
auto client = databricks::Client::Builder()
321+
.with_environment_config()
322+
.with_retry(no_retry)
323+
.build();
324+
```
325+
270326
### Mixing Configuration Approaches
271327

272328
The Builder pattern allows you to mix automatic and explicit configuration:
@@ -493,9 +549,14 @@ Async operations reduce perceived latency by performing work in the background:
493549

494550
1. **Enable pooling** via `PoolingConfig` for applications making multiple queries
495551
2. **Use async operations** when you can do other work while waiting
496-
3. **Combine both** for maximum performance in concurrent scenarios
497-
4. **Size pools appropriately**: min = typical concurrent load, max = peak load
498-
5. **Share configs**: Clients with identical configs automatically share pools
552+
3. **Enable retry logic** (on by default) for production reliability against transient failures
553+
4. **Combine pooling + retries** for maximum reliability and performance
554+
5. **Size pools appropriately**: min = typical concurrent load, max = peak load
555+
6. **Share configs**: Clients with identical configs automatically share pools
556+
7. **Tune retry settings** based on your workload:
557+
- High-throughput: Lower `max_attempts` (2-3) to fail fast
558+
- Critical operations: Higher `max_attempts` (5-7) for maximum reliability
559+
- Rate-limited APIs: Increase `initial_backoff_ms` and `max_backoff_ms`
499560

500561
## Advanced Usage
501562

include/databricks/client.h

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,29 @@ namespace databricks
116116
*/
117117
Builder& with_auto_connect(bool enable = true);
118118

119+
/**
120+
* @brief Set retry configuration for automatic error recovery
121+
*
122+
* Configures how the client handles transient failures like connection
123+
* timeouts and network errors. When enabled, failed operations will be
124+
* automatically retried with exponential backoff.
125+
*
126+
* @param retry Retry configuration
127+
* @return Builder reference for chaining
128+
*
129+
* @code
130+
* databricks::RetryConfig retry;
131+
* retry.max_attempts = 5;
132+
* retry.initial_backoff_ms = 200;
133+
*
134+
* auto client = databricks::Client::Builder()
135+
* .with_environment_config()
136+
* .with_retry(retry)
137+
* .build();
138+
* @endcode
139+
*/
140+
Builder& with_retry(const RetryConfig& retry);
141+
119142
/**
120143
* @brief Build the Client
121144
*
@@ -128,6 +151,7 @@ namespace databricks
128151
std::unique_ptr<AuthConfig> auth_;
129152
std::unique_ptr<SQLConfig> sql_;
130153
std::unique_ptr<PoolingConfig> pooling_;
154+
std::unique_ptr<RetryConfig> retry_;
131155
bool auto_connect_ = false;
132156
};
133157

@@ -239,7 +263,7 @@ namespace databricks
239263

240264
private:
241265
// Private constructor for Builder
242-
Client(const AuthConfig& auth, const SQLConfig& sql, const PoolingConfig& pooling, bool auto_connect);
266+
Client(const AuthConfig& auth, const SQLConfig& sql, const PoolingConfig& pooling, const RetryConfig& retry, bool auto_connect);
243267

244268
class Impl;
245269
std::unique_ptr<Impl> pimpl_;

include/databricks/config.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,4 +101,41 @@ namespace databricks
101101
bool is_valid() const;
102102
};
103103

104+
/**
105+
* @brief Retry configuration for automatic error recovery
106+
*
107+
* Configures automatic retry behavior for transient failures such as
108+
* connection timeouts, network errors, and rate limits. Uses exponential
109+
* backoff to avoid overwhelming the server during retries.
110+
*
111+
* Example usage:
112+
* @code
113+
* databricks::RetryConfig retry;
114+
* retry.enabled = true;
115+
* retry.max_attempts = 5;
116+
* retry.initial_backoff_ms = 200;
117+
*
118+
* auto client = databricks::Client::Builder()
119+
* .with_environment_config()
120+
* .with_retry(retry)
121+
* .build();
122+
* @endcode
123+
*/
124+
struct RetryConfig
125+
{
126+
bool enabled = true; ///< Enable automatic retries (default: true)
127+
size_t max_attempts = 3; ///< Maximum retry attempts (default: 3)
128+
size_t initial_backoff_ms = 100; ///< Initial backoff in milliseconds (default: 100ms)
129+
double backoff_multiplier = 2.0; ///< Exponential backoff multiplier (default: 2x)
130+
size_t max_backoff_ms = 10000; ///< Maximum backoff cap (default: 10s)
131+
bool retry_on_timeout = true; ///< Retry on connection timeout (default: true)
132+
bool retry_on_connection_lost = true; ///< Retry on connection errors (default: true)
133+
134+
/**
135+
* @brief Validate configuration values
136+
* @return true if valid, false otherwise
137+
*/
138+
bool is_valid() const;
139+
};
140+
104141
} // namespace databricks

0 commit comments

Comments
 (0)