Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 103 additions & 20 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,110 @@ on:
pull_request:
branches: [ "main" ]

# Cancel in-progress runs for the same branch
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
build-and-test:
# Build job - compiles the project
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
cache: maven

- name: Build project (skip tests)
run: mvn clean compile test-compile -DskipTests -q

- name: Cache build artifacts
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-build-${{ github.sha }}

# Unit tests job
unit-tests:
name: Unit Tests
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
cache: maven

- name: Run unit tests
run: mvn test -q

- name: Upload unit test results
if: always()
uses: actions/upload-artifact@v4
with:
name: unit-test-results
path: target/surefire-reports/
retention-days: 7

# Integration tests job
integration-tests:
name: Integration Tests
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
cache: maven

- name: Run integration tests
run: mvn verify -DskipUnitTests -q

- name: Upload integration test results
if: always()
uses: actions/upload-artifact@v4
with:
name: integration-test-results
path: target/failsafe-reports/
retention-days: 7

# Final status check - this job is used for branch protection
# All tests must pass for this job to succeed
ci-status:
name: CI Status Check
needs: [build, unit-tests, integration-tests]
runs-on: ubuntu-latest
if: always()
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 25
uses: actions/setup-java@v4
with:
java-version: '25'
distribution: 'temurin'
cache: maven

- name: Display Java and Maven version
run: |
java -version
mvn -version

- name: Build and Run All Tests
run: |
chmod +x run_all_tests.sh
./run_all_tests.sh
- name: Check if all jobs passed
run: |
if [[ "${{ needs.build.result }}" != "success" ]]; then
echo "❌ Build failed"
exit 1
fi
if [[ "${{ needs.unit-tests.result }}" != "success" ]]; then
echo "❌ Unit tests failed"
exit 1
fi
if [[ "${{ needs.integration-tests.result }}" != "success" ]]; then
echo "❌ Integration tests failed"
exit 1
fi
echo "✅ All checks passed!"
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ A high-performance, lightweight, in-memory Redis-compatible server built from th
- **💾 In-Memory Storage**: Optimized data structures using `ConcurrentHashMap` for thread-safe, lock-free reads.
- **🔌 Redis Protocol (RESP)**: Implements the Redis Serialization Protocol, compatible with any standard Redis client (`redis-cli`, `jedis`, `redis-py`, etc.).
- **⏳ Advanced Expiration**: Dual-strategy expiration (Lazy + Active background cleanup via `DelayQueue`).
- **🔄 Transaction Support**: Full MULTI/EXEC/DISCARD support with optimized batch execution and zero-contention state management.
- **🎯 Single-Threaded Execution**: Primarily single-threaded command execution for predictable behavior; blocking commands (e.g., `BLPOP`) are handled asynchronously using Netty's event loop to avoid blocking I/O.
- **🏗️ Extensible Command Registry**: Easy to add new commands via a simple interface.

Expand All @@ -38,6 +39,14 @@ Detailed documentation for each command can be found in the [docs/commands](./do
| `SET` | `SET key value [EX s] [PX ms] [NX\|XX]` | [SET.md](./docs/commands/SET.md) |
| `GET` | `GET key` | [GET.md](./docs/commands/GET.md) |
| `DEL` | `DEL key [key ...]` | [DEL.md](./docs/commands/DEL.md) |
| `INCR` | `INCR key` | [INCR.md](./docs/commands/INCR.md) |

### 🔄 Transactions
| Command | Usage | Documentation |
|:---|:---|:---|
| `MULTI` | `MULTI` | [MULTI.md](./docs/commands/MULTI.md) |
| `EXEC` | `EXEC` | [EXEC.md](./docs/commands/EXEC.md) |
| `DISCARD` | `DISCARD` | [DISCARD.md](./docs/commands/DISCARD.md) |
Comment on lines +44 to +49

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdownlint table spacing for the Transactions section.

markdownlint flags missing blank lines around the table (MD058) and compact table pipe spacing (MD060). Adjust spacing to satisfy the style rules.

📝 Proposed fix
-### 🔄 Transactions
-| Command | Usage | Documentation |
-|:---|:---|:---|
-| `MULTI` | `MULTI` | [MULTI.md](./docs/commands/MULTI.md) |
-| `EXEC` | `EXEC` | [EXEC.md](./docs/commands/EXEC.md) |
-| `DISCARD` | `DISCARD` | [DISCARD.md](./docs/commands/DISCARD.md) |
+### 🔄 Transactions
+
+| Command  | Usage   | Documentation                         |
+| :---     | :---    | :---                                  |
+| `MULTI`  | `MULTI` | [MULTI.md](./docs/commands/MULTI.md)  |
+| `EXEC`   | `EXEC`  | [EXEC.md](./docs/commands/EXEC.md)    |
+| `DISCARD`| `DISCARD` | [DISCARD.md](./docs/commands/DISCARD.md) |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
### 🔄 Transactions
| Command | Usage | Documentation |
|:---|:---|:---|
| `MULTI` | `MULTI` | [MULTI.md](./docs/commands/MULTI.md) |
| `EXEC` | `EXEC` | [EXEC.md](./docs/commands/EXEC.md) |
| `DISCARD` | `DISCARD` | [DISCARD.md](./docs/commands/DISCARD.md) |
### 🔄 Transactions
| Command | Usage | Documentation |
| :--- | :--- | :--- |
| `MULTI` | `MULTI` | [MULTI.md](./docs/commands/MULTI.md) |
| `EXEC` | `EXEC` | [EXEC.md](./docs/commands/EXEC.md) |
| `DISCARD`| `DISCARD` | [DISCARD.md](./docs/commands/DISCARD.md) |
🧰 Tools
🪛 markdownlint-cli2 (0.20.0)

[warning] 45-45: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 46-46: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 46-46: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 46-46: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 46-46: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)


[warning] 46-46: Table column style
Table pipe is missing space to the right for style "compact"

(MD060, table-column-style)


[warning] 46-46: Table column style
Table pipe is missing space to the left for style "compact"

(MD060, table-column-style)

🤖 Prompt for AI Agents
In `@README.md` around lines 44 - 49, Add a blank line immediately before and
after the "Transactions" table and normalize pipe spacing so each column cell
and header has a single space on both sides of the pipe (e.g., "| Command |
Usage | Documentation |") and keep the separator row using the existing
alignment tokens (":---") — update the table under the Transactions section
(rows containing the `MULTI`, `EXEC`, `DISCARD` entries) to follow these spacing
rules to satisfy MD058 and MD060.


### 📋 List Operations
| Command | Usage | Documentation |
Expand Down Expand Up @@ -119,10 +128,25 @@ We maintain high confidence through both unit and integration tests.

# Run only unit tests
mvn test

# Run only integration tests
mvn verify -DskipUnitTests
```

---

## 🔒 CI/CD & Branch Protection

This repository enforces **mandatory passing tests** before any PR can be merged:

- ✅ **Build** must compile successfully
- ✅ **Unit Tests** must all pass (469+ tests)
- ✅ **Integration Tests** must all pass (179+ tests)

The CI pipeline runs automatically on every push and PR. See [Branch Protection Setup](./docs/BRANCH_PROTECTION.md) for configuration details.

---

## ⚙️ Configuration

Edit `src/main/resources/application.properties`:
Expand Down
75 changes: 75 additions & 0 deletions docs/commands/DISCARD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# DISCARD

## Syntax

```
DISCARD
```

## Description

Flushes all previously queued commands in a transaction and restores the connection state to normal.

If MULTI was called, DISCARD will abort the transaction. All queued commands are discarded and the client can issue regular commands again.

## Return Value

**Simple string reply:** always `OK`.

## Examples

### Abort a Transaction

```
redis> SET key "original"
OK
redis> MULTI
OK
redis> SET key "modified"
QUEUED
redis> GET key
QUEUED
redis> DISCARD
OK
redis> GET key
"original"
```

### Start New Transaction After DISCARD

```
redis> MULTI
OK
redis> SET foo bar
QUEUED
redis> DISCARD
OK
redis> MULTI
OK
redis> SET foo baz
QUEUED
redis> EXEC
1) OK
redis> GET foo
"baz"
```

## Error Handling

### DISCARD Without MULTI

```
redis> DISCARD
(error) ERR DISCARD without MULTI
```

## Implementation Notes

### Memory Efficiency

The queued commands list is cleared but not deallocated when DISCARD is called. This allows the memory to be reused if another transaction starts on the same connection, reducing allocation overhead and GC pressure.

## Related Commands

- [MULTI](MULTI.md) - Start a transaction
- [EXEC](EXEC.md) - Execute all queued commands
113 changes: 113 additions & 0 deletions docs/commands/EXEC.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# EXEC

## Syntax

```
EXEC
```

## Description

Executes all previously queued commands in a MULTI/EXEC block and restores the connection state to normal.

When EXEC is called, all commands queued since MULTI are executed atomically. This means that either all commands are processed, or none are (in case of errors during queueing).

## Return Value

**Array reply:** Each element is the reply from each command in the transaction, in the order they were queued.

**Nil reply:** If EXEC is called without a prior MULTI or if the transaction was aborted due to errors.

## Examples

### Basic Transaction

```
redis> MULTI
OK
redis> SET key1 "Hello"
QUEUED
redis> SET key2 "World"
QUEUED
redis> GET key1
QUEUED
redis> GET key2
QUEUED
redis> EXEC
1) OK
2) OK
3) "Hello"
4) "World"
```

### Counter Transaction

```
redis> SET counter 0
OK
redis> MULTI
OK
redis> INCR counter
QUEUED
redis> INCR counter
QUEUED
redis> INCR counter
QUEUED
redis> GET counter
QUEUED
redis> EXEC
1) (integer) 1
2) (integer) 2
3) (integer) 3
4) "3"
```

## Error Handling

### EXEC Without MULTI

```
redis> EXEC
(error) ERR EXEC without MULTI
```

### Transaction with Queueing Error

If an error occurs while queueing commands (e.g., syntax error, unknown command), the transaction is aborted:

```
redis> MULTI
OK
redis> SET key value
QUEUED
redis> UNKNOWNCOMMAND
(error) ERR unknown command 'UNKNOWNCOMMAND'
redis> EXEC
(error) EXECABORT Transaction discarded because of previous errors.
```

### Empty Transaction

```
redis> MULTI
OK
redis> EXEC
(empty array)
```

## Implementation Notes

### Optimizations Over Standard Redis

1. **Pre-allocated Response Buffer:** The response StringBuilder is pre-sized based on the number of queued commands, minimizing reallocations during execution.

2. **Zero Command Lookups:** Commands are resolved and stored at queue time, not during EXEC. This eliminates registry lookups during the critical execution phase.

3. **Cache-Friendly Iteration:** Commands are stored in a contiguous ArrayList, providing excellent CPU cache utilization during batch execution.

4. **Batch Execution:** All commands execute in a tight loop without intermediate I/O operations, reducing context switches.

## Related Commands

- [MULTI](MULTI.md) - Start a transaction
- [DISCARD](DISCARD.md) - Abort the transaction
Loading