Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheCore

Overview

CacheCore is a Redis-inspired key-value server that I built in C to understand how networking, concurrency, persistence, recovery, and performance interact inside a stateful server.

The project began as an in-memory hashmap and command parser. I developed it incrementally into a persistent multi-client TCP service with stream-aware command framing, a fixed worker pool, a bounded connection queue, synchronized shared state, append-only persistence, incomplete-tail recovery, log compaction, coordinated shutdown, layered tests, and a synchronized throughput benchmark.

Each major feature was introduced in response to a limitation exposed by the previous design. The goal was not to reproduce Redis or create a production-ready database, but to learn the mechanisms and trade-offs behind reliable systems software by implementing and testing them directly.

Verified Features

  • A custom, case-sensitive, newline-delimited TCP protocol with PING, SET, GET, DEL, and QUIT.
  • Persistent connections with buffering for fragmented reads and multiple commands received together.
  • A dynamically resized, separate-chaining hashmap for in-memory storage.
  • Four fixed worker threads and a bounded 16-entry connection queue.
  • Mutex-protected shared database state across concurrent clients.
  • Append-only logging for SET and DEL, with fsync before the in-memory mutation.
  • Startup replay, truncation of an incomplete trailing record, and automatic AOF compaction.
  • Coordinated SIGINT shutdown that closes active client sockets and joins workers.
  • Unit, integration, concurrent stress, and synchronized benchmark coverage.

Design Evolution and Lessons

  • I first separated command parsing, storage, and server responsibilities so each layer could evolve independently.
  • TCP networking exposed the difference between messages and byte streams, leading to explicit newline framing, buffering, and persistent connections.
  • Moving from one client to concurrent clients introduced shared-state ownership, synchronization, bounded work queues, and shutdown coordination.
  • Adding persistence showed me that durability depends on write ordering and clearly defined recovery limits, not merely writing data to a file.
  • Layered tests and a synchronized benchmark turned observed behavior into evidence I could reproduce and reason about.

Architecture at a Glance

+------------+
| TCP client |
+-----+------+
|
| newline-delimited commands (port 6379)
v
+--------------+
| TCP listener |
+------+-------+
|
v
+--------------------------------+
| Bounded connection queue |
| Capacity: 16 |
+---------------+----------------+
|
v
+--------------------------------+
| Worker pool |
| 4 threads |
+---------------+----------------+
|
| one worker owns each connection
v
+--------------------------------+
| Persistent connection handler |
| Buffering, framing, dispatch |
+-----------+--------------------+
|
+---- PING / QUIT ------> Direct response
|
+---- SET / GET / DEL
|
v
+----------------------+
| Database API |
| Single mutex |
+----------+-----------+
|
+-----------+-----------+
| |
GET path SET / DEL path
| |
| v
| +------------------+
| | Append to AOF |
| | and fsync |
| +--------+---------+
| |
| | then mutate
v v
+----------------------+
| In-memory hashmap |
+----------------------+
Startup: AOF -------- replay --------> hashmap
Compaction: hashmap ---- snapshot ------> AOF
  • The listener accepts connections and rejects a new client if the queue is full.
  • A worker owns a persistent connection until that client disconnects or sends QUIT.
  • One database mutex serializes SET, GET, and DEL operations.
  • Successful mutations are persisted before the in-memory hashmap is changed.

Build and Run

Requirements

  • A C11 compiler
  • POSIX sockets and pthreads
  • make
  • nc/netcat for manual protocol testing (optional)

CacheCore was developed and tested on macOS.

Build

make

Start the Server

make run

The server listens on port 6379. From another terminal:

nc 127.0.0.1 6379

The AOF file is created in the server's working directory.

Protocol Commands

CommandNormal responseDescription
PINGPONGCheck that the server is responsive.
SET <key> <value>OKStore or replace a value.
GET <key><value> or NOT_FOUNDRetrieve a value.
DEL <key>OK or NOT_FOUNDDelete a key.
QUITBYEClose the connection.

Commands are case-sensitive and newline-terminated. Keys and values cannot contain whitespace.

Example session:

PING
PONG
SET language c
OK
GET language
c
DEL language
OK
QUIT
BYE

Testing

Run the automated unit and database/AOF integration tests:

make test

This covers the hashmap, command parser, database operations, AOF replay, incomplete-tail recovery, and compaction.

To run the concurrent TCP stress test, start the server and use a second terminal:

make stress-client

The stress client opens eight persistent connections. Each performs 1,000 validated SET/GET cycles. The final regression pass also verified the protocol over one persistent connection, persistence across restart, coordinated shutdown with an active client, and port reuse after shutdown.

Benchmark

With the server running, execute:

make benchmark

The benchmark measures steady-state synchronous GET throughput over the local loopback interface. Connection setup, the initial SET, worker readiness, QUIT, and cleanup are outside the timed interval. Each client keeps one request outstanding at a time; the benchmark does not use pipelining.

Three trials were run for each client count, with 10,000 requests per client:

ClientsTotal GET requestsMedian throughput
110,00053,439 requests/s
220,00094,493 requests/s
440,000146,014 requests/s

These are local macOS measurements and are intended to show scaling within this design, not production or distributed performance. See Testing and Benchmarking for the methodology and interpretation.

Limitations and Security Scope

  • CacheCore uses a custom protocol and is not Redis-compatible.
  • The server binds to all interfaces and provides no authentication, authorization, or encryption.
  • Persistent clients occupy workers for the lifetime of their connections, and a full queue causes new connections to be rejected.
  • One database mutex serializes all data operations, favoring simple correctness over maximum parallelism.
  • AOF recovery repairs only an incomplete trailing record; a malformed complete record causes startup failure.
  • Compaction uses fsync and atomic rename for the AOF file but does not fsync the containing directory.
  • Coordinated graceful shutdown is implemented for SIGINT.

CacheCore is an educational systems project and should not be exposed as a production service.

Documentation

Project Status

The implementation is feature-complete for its intended learning scope. Repository hygiene and the final regression pass are complete, with no functional regressions found across automated tests, TCP behavior, concurrent stress, restart persistence, coordinated shutdown, and a benchmark smoke run.

The most valuable result for me was learning to treat system behavior as a chain of explicit guarantees: frame the byte stream, bound concurrent work, define shared-state ownership, persist mutations in the correct order, recover only what the format can safely identify, and measure performance with controlled timing boundaries.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheCore

Overview

CacheCore is a Redis-inspired key-value server that I built in C to understand how networking, concurrency, persistence, recovery, and performance interact inside a stateful server.

The project began as an in-memory hashmap and command parser. I developed it incrementally into a persistent multi-client TCP service with stream-aware command framing, a fixed worker pool, a bounded connection queue, synchronized shared state, append-only persistence, incomplete-tail recovery, log compaction, coordinated shutdown, layered tests, and a synchronized throughput benchmark.

Each major feature was introduced in response to a limitation exposed by the previous design. The goal was not to reproduce Redis or create a production-ready database, but to learn the mechanisms and trade-offs behind reliable systems software by implementing and testing them directly.

Verified Features

  • A custom, case-sensitive, newline-delimited TCP protocol with PING, SET, GET, DEL, and QUIT.
  • Persistent connections with buffering for fragmented reads and multiple commands received together.
  • A dynamically resized, separate-chaining hashmap for in-memory storage.
  • Four fixed worker threads and a bounded 16-entry connection queue.
  • Mutex-protected shared database state across concurrent clients.
  • Append-only logging for SET and DEL, with fsync before the in-memory mutation.
  • Startup replay, truncation of an incomplete trailing record, and automatic AOF compaction.
  • Coordinated SIGINT shutdown that closes active client sockets and joins workers.
  • Unit, integration, concurrent stress, and synchronized benchmark coverage.

Design Evolution and Lessons

  • I first separated command parsing, storage, and server responsibilities so each layer could evolve independently.
  • TCP networking exposed the difference between messages and byte streams, leading to explicit newline framing, buffering, and persistent connections.
  • Moving from one client to concurrent clients introduced shared-state ownership, synchronization, bounded work queues, and shutdown coordination.
  • Adding persistence showed me that durability depends on write ordering and clearly defined recovery limits, not merely writing data to a file.
  • Layered tests and a synchronized benchmark turned observed behavior into evidence I could reproduce and reason about.

Architecture at a Glance

+------------+
| TCP client |
+-----+------+
|
| newline-delimited commands (port 6379)
v
+--------------+
| TCP listener |
+------+-------+
|
v
+--------------------------------+
| Bounded connection queue |
| Capacity: 16 |
+---------------+----------------+
|
v
+--------------------------------+
| Worker pool |
| 4 threads |
+---------------+----------------+
|
| one worker owns each connection
v
+--------------------------------+
| Persistent connection handler |
| Buffering, framing, dispatch |
+-----------+--------------------+
|
+---- PING / QUIT ------> Direct response
|
+---- SET / GET / DEL
|
v
+----------------------+
| Database API |
| Single mutex |
+----------+-----------+
|
+-----------+-----------+
| |
GET path SET / DEL path
| |
| v
| +------------------+
| | Append to AOF |
| | and fsync |
| +--------+---------+
| |
| | then mutate
v v
+----------------------+
| In-memory hashmap |
+----------------------+
Startup: AOF -------- replay --------> hashmap
Compaction: hashmap ---- snapshot ------> AOF
  • The listener accepts connections and rejects a new client if the queue is full.
  • A worker owns a persistent connection until that client disconnects or sends QUIT.
  • One database mutex serializes SET, GET, and DEL operations.
  • Successful mutations are persisted before the in-memory hashmap is changed.

Build and Run

Requirements

  • A C11 compiler
  • POSIX sockets and pthreads
  • make
  • nc/netcat for manual protocol testing (optional)

CacheCore was developed and tested on macOS.

Build

make

Start the Server

make run

The server listens on port 6379. From another terminal:

nc 127.0.0.1 6379

The AOF file is created in the server's working directory.

Protocol Commands

CommandNormal responseDescription
PINGPONGCheck that the server is responsive.
SET <key> <value>OKStore or replace a value.
GET <key><value> or NOT_FOUNDRetrieve a value.
DEL <key>OK or NOT_FOUNDDelete a key.
QUITBYEClose the connection.

Commands are case-sensitive and newline-terminated. Keys and values cannot contain whitespace.

Example session:

PING
PONG
SET language c
OK
GET language
c
DEL language
OK
QUIT
BYE

Testing

Run the automated unit and database/AOF integration tests:

make test

This covers the hashmap, command parser, database operations, AOF replay, incomplete-tail recovery, and compaction.

To run the concurrent TCP stress test, start the server and use a second terminal:

make stress-client

The stress client opens eight persistent connections. Each performs 1,000 validated SET/GET cycles. The final regression pass also verified the protocol over one persistent connection, persistence across restart, coordinated shutdown with an active client, and port reuse after shutdown.

Benchmark

With the server running, execute:

make benchmark

The benchmark measures steady-state synchronous GET throughput over the local loopback interface. Connection setup, the initial SET, worker readiness, QUIT, and cleanup are outside the timed interval. Each client keeps one request outstanding at a time; the benchmark does not use pipelining.

Three trials were run for each client count, with 10,000 requests per client:

ClientsTotal GET requestsMedian throughput
110,00053,439 requests/s
220,00094,493 requests/s
440,000146,014 requests/s

These are local macOS measurements and are intended to show scaling within this design, not production or distributed performance. See Testing and Benchmarking for the methodology and interpretation.

Limitations and Security Scope

  • CacheCore uses a custom protocol and is not Redis-compatible.
  • The server binds to all interfaces and provides no authentication, authorization, or encryption.
  • Persistent clients occupy workers for the lifetime of their connections, and a full queue causes new connections to be rejected.
  • One database mutex serializes all data operations, favoring simple correctness over maximum parallelism.
  • AOF recovery repairs only an incomplete trailing record; a malformed complete record causes startup failure.
  • Compaction uses fsync and atomic rename for the AOF file but does not fsync the containing directory.
  • Coordinated graceful shutdown is implemented for SIGINT.

CacheCore is an educational systems project and should not be exposed as a production service.

Documentation

Project Status

The implementation is feature-complete for its intended learning scope. Repository hygiene and the final regression pass are complete, with no functional regressions found across automated tests, TCP behavior, concurrent stress, restart persistence, coordinated shutdown, and a benchmark smoke run.

The most valuable result for me was learning to treat system behavior as a chain of explicit guarantees: frame the byte stream, bound concurrent work, define shared-state ownership, persist mutations in the correct order, recover only what the format can safely identify, and measure performance with controlled timing boundaries.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheCore

Overview

CacheCore is a Redis-inspired key-value server that I built in C to understand how networking, concurrency, persistence, recovery, and performance interact inside a stateful server.

The project began as an in-memory hashmap and command parser. I developed it incrementally into a persistent multi-client TCP service with stream-aware command framing, a fixed worker pool, a bounded connection queue, synchronized shared state, append-only persistence, incomplete-tail recovery, log compaction, coordinated shutdown, layered tests, and a synchronized throughput benchmark.

Each major feature was introduced in response to a limitation exposed by the previous design. The goal was not to reproduce Redis or create a production-ready database, but to learn the mechanisms and trade-offs behind reliable systems software by implementing and testing them directly.

Verified Features

  • A custom, case-sensitive, newline-delimited TCP protocol with PING, SET, GET, DEL, and QUIT.
  • Persistent connections with buffering for fragmented reads and multiple commands received together.
  • A dynamically resized, separate-chaining hashmap for in-memory storage.
  • Four fixed worker threads and a bounded 16-entry connection queue.
  • Mutex-protected shared database state across concurrent clients.
  • Append-only logging for SET and DEL, with fsync before the in-memory mutation.
  • Startup replay, truncation of an incomplete trailing record, and automatic AOF compaction.
  • Coordinated SIGINT shutdown that closes active client sockets and joins workers.
  • Unit, integration, concurrent stress, and synchronized benchmark coverage.

Design Evolution and Lessons

  • I first separated command parsing, storage, and server responsibilities so each layer could evolve independently.
  • TCP networking exposed the difference between messages and byte streams, leading to explicit newline framing, buffering, and persistent connections.
  • Moving from one client to concurrent clients introduced shared-state ownership, synchronization, bounded work queues, and shutdown coordination.
  • Adding persistence showed me that durability depends on write ordering and clearly defined recovery limits, not merely writing data to a file.
  • Layered tests and a synchronized benchmark turned observed behavior into evidence I could reproduce and reason about.

Architecture at a Glance

+------------+
| TCP client |
+-----+------+
|
| newline-delimited commands (port 6379)
v
+--------------+
| TCP listener |
+------+-------+
|
v
+--------------------------------+
| Bounded connection queue |
| Capacity: 16 |
+---------------+----------------+
|
v
+--------------------------------+
| Worker pool |
| 4 threads |
+---------------+----------------+
|
| one worker owns each connection
v
+--------------------------------+
| Persistent connection handler |
| Buffering, framing, dispatch |
+-----------+--------------------+
|
+---- PING / QUIT ------> Direct response
|
+---- SET / GET / DEL
|
v
+----------------------+
| Database API |
| Single mutex |
+----------+-----------+
|
+-----------+-----------+
| |
GET path SET / DEL path
| |
| v
| +------------------+
| | Append to AOF |
| | and fsync |
| +--------+---------+
| |
| | then mutate
v v
+----------------------+
| In-memory hashmap |
+----------------------+
Startup: AOF -------- replay --------> hashmap
Compaction: hashmap ---- snapshot ------> AOF
  • The listener accepts connections and rejects a new client if the queue is full.
  • A worker owns a persistent connection until that client disconnects or sends QUIT.
  • One database mutex serializes SET, GET, and DEL operations.
  • Successful mutations are persisted before the in-memory hashmap is changed.

Build and Run

Requirements

  • A C11 compiler
  • POSIX sockets and pthreads
  • make
  • nc/netcat for manual protocol testing (optional)

CacheCore was developed and tested on macOS.

Build

make

Start the Server

make run

The server listens on port 6379. From another terminal:

nc 127.0.0.1 6379

The AOF file is created in the server's working directory.

Protocol Commands

CommandNormal responseDescription
PINGPONGCheck that the server is responsive.
SET <key> <value>OKStore or replace a value.
GET <key><value> or NOT_FOUNDRetrieve a value.
DEL <key>OK or NOT_FOUNDDelete a key.
QUITBYEClose the connection.

Commands are case-sensitive and newline-terminated. Keys and values cannot contain whitespace.

Example session:

PING
PONG
SET language c
OK
GET language
c
DEL language
OK
QUIT
BYE

Testing

Run the automated unit and database/AOF integration tests:

make test

This covers the hashmap, command parser, database operations, AOF replay, incomplete-tail recovery, and compaction.

To run the concurrent TCP stress test, start the server and use a second terminal:

make stress-client

The stress client opens eight persistent connections. Each performs 1,000 validated SET/GET cycles. The final regression pass also verified the protocol over one persistent connection, persistence across restart, coordinated shutdown with an active client, and port reuse after shutdown.

Benchmark

With the server running, execute:

make benchmark

The benchmark measures steady-state synchronous GET throughput over the local loopback interface. Connection setup, the initial SET, worker readiness, QUIT, and cleanup are outside the timed interval. Each client keeps one request outstanding at a time; the benchmark does not use pipelining.

Three trials were run for each client count, with 10,000 requests per client:

ClientsTotal GET requestsMedian throughput
110,00053,439 requests/s
220,00094,493 requests/s
440,000146,014 requests/s

These are local macOS measurements and are intended to show scaling within this design, not production or distributed performance. See Testing and Benchmarking for the methodology and interpretation.

Limitations and Security Scope

  • CacheCore uses a custom protocol and is not Redis-compatible.
  • The server binds to all interfaces and provides no authentication, authorization, or encryption.
  • Persistent clients occupy workers for the lifetime of their connections, and a full queue causes new connections to be rejected.
  • One database mutex serializes all data operations, favoring simple correctness over maximum parallelism.
  • AOF recovery repairs only an incomplete trailing record; a malformed complete record causes startup failure.
  • Compaction uses fsync and atomic rename for the AOF file but does not fsync the containing directory.
  • Coordinated graceful shutdown is implemented for SIGINT.

CacheCore is an educational systems project and should not be exposed as a production service.

Documentation

Project Status

The implementation is feature-complete for its intended learning scope. Repository hygiene and the final regression pass are complete, with no functional regressions found across automated tests, TCP behavior, concurrent stress, restart persistence, coordinated shutdown, and a benchmark smoke run.

The most valuable result for me was learning to treat system behavior as a chain of explicit guarantees: frame the byte stream, bound concurrent work, define shared-state ownership, persist mutations in the correct order, recover only what the format can safely identify, and measure performance with controlled timing boundaries.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheCore

Overview

CacheCore is a Redis-inspired key-value server that I built in C to understand how networking, concurrency, persistence, recovery, and performance interact inside a stateful server.

The project began as an in-memory hashmap and command parser. I developed it incrementally into a persistent multi-client TCP service with stream-aware command framing, a fixed worker pool, a bounded connection queue, synchronized shared state, append-only persistence, incomplete-tail recovery, log compaction, coordinated shutdown, layered tests, and a synchronized throughput benchmark.

Each major feature was introduced in response to a limitation exposed by the previous design. The goal was not to reproduce Redis or create a production-ready database, but to learn the mechanisms and trade-offs behind reliable systems software by implementing and testing them directly.

Verified Features

  • A custom, case-sensitive, newline-delimited TCP protocol with PING, SET, GET, DEL, and QUIT.
  • Persistent connections with buffering for fragmented reads and multiple commands received together.
  • A dynamically resized, separate-chaining hashmap for in-memory storage.
  • Four fixed worker threads and a bounded 16-entry connection queue.
  • Mutex-protected shared database state across concurrent clients.
  • Append-only logging for SET and DEL, with fsync before the in-memory mutation.
  • Startup replay, truncation of an incomplete trailing record, and automatic AOF compaction.
  • Coordinated SIGINT shutdown that closes active client sockets and joins workers.
  • Unit, integration, concurrent stress, and synchronized benchmark coverage.

Design Evolution and Lessons

  • I first separated command parsing, storage, and server responsibilities so each layer could evolve independently.
  • TCP networking exposed the difference between messages and byte streams, leading to explicit newline framing, buffering, and persistent connections.
  • Moving from one client to concurrent clients introduced shared-state ownership, synchronization, bounded work queues, and shutdown coordination.
  • Adding persistence showed me that durability depends on write ordering and clearly defined recovery limits, not merely writing data to a file.
  • Layered tests and a synchronized benchmark turned observed behavior into evidence I could reproduce and reason about.

Architecture at a Glance

+------------+
| TCP client |
+-----+------+
|
| newline-delimited commands (port 6379)
v
+--------------+
| TCP listener |
+------+-------+
|
v
+--------------------------------+
| Bounded connection queue |
| Capacity: 16 |
+---------------+----------------+
|
v
+--------------------------------+
| Worker pool |
| 4 threads |
+---------------+----------------+
|
| one worker owns each connection
v
+--------------------------------+
| Persistent connection handler |
| Buffering, framing, dispatch |
+-----------+--------------------+
|
+---- PING / QUIT ------> Direct response
|
+---- SET / GET / DEL
|
v
+----------------------+
| Database API |
| Single mutex |
+----------+-----------+
|
+-----------+-----------+
| |
GET path SET / DEL path
| |
| v
| +------------------+
| | Append to AOF |
| | and fsync |
| +--------+---------+
| |
| | then mutate
v v
+----------------------+
| In-memory hashmap |
+----------------------+
Startup: AOF -------- replay --------> hashmap
Compaction: hashmap ---- snapshot ------> AOF
  • The listener accepts connections and rejects a new client if the queue is full.
  • A worker owns a persistent connection until that client disconnects or sends QUIT.
  • One database mutex serializes SET, GET, and DEL operations.
  • Successful mutations are persisted before the in-memory hashmap is changed.

Build and Run

Requirements

  • A C11 compiler
  • POSIX sockets and pthreads
  • make
  • nc/netcat for manual protocol testing (optional)

CacheCore was developed and tested on macOS.

Build

make

Start the Server

make run

The server listens on port 6379. From another terminal:

nc 127.0.0.1 6379

The AOF file is created in the server's working directory.

Protocol Commands

CommandNormal responseDescription
PINGPONGCheck that the server is responsive.
SET <key> <value>OKStore or replace a value.
GET <key><value> or NOT_FOUNDRetrieve a value.
DEL <key>OK or NOT_FOUNDDelete a key.
QUITBYEClose the connection.

Commands are case-sensitive and newline-terminated. Keys and values cannot contain whitespace.

Example session:

PING
PONG
SET language c
OK
GET language
c
DEL language
OK
QUIT
BYE

Testing

Run the automated unit and database/AOF integration tests:

make test

This covers the hashmap, command parser, database operations, AOF replay, incomplete-tail recovery, and compaction.

To run the concurrent TCP stress test, start the server and use a second terminal:

make stress-client

The stress client opens eight persistent connections. Each performs 1,000 validated SET/GET cycles. The final regression pass also verified the protocol over one persistent connection, persistence across restart, coordinated shutdown with an active client, and port reuse after shutdown.

Benchmark

With the server running, execute:

make benchmark

The benchmark measures steady-state synchronous GET throughput over the local loopback interface. Connection setup, the initial SET, worker readiness, QUIT, and cleanup are outside the timed interval. Each client keeps one request outstanding at a time; the benchmark does not use pipelining.

Three trials were run for each client count, with 10,000 requests per client:

ClientsTotal GET requestsMedian throughput
110,00053,439 requests/s
220,00094,493 requests/s
440,000146,014 requests/s

These are local macOS measurements and are intended to show scaling within this design, not production or distributed performance. See Testing and Benchmarking for the methodology and interpretation.

Limitations and Security Scope

  • CacheCore uses a custom protocol and is not Redis-compatible.
  • The server binds to all interfaces and provides no authentication, authorization, or encryption.
  • Persistent clients occupy workers for the lifetime of their connections, and a full queue causes new connections to be rejected.
  • One database mutex serializes all data operations, favoring simple correctness over maximum parallelism.
  • AOF recovery repairs only an incomplete trailing record; a malformed complete record causes startup failure.
  • Compaction uses fsync and atomic rename for the AOF file but does not fsync the containing directory.
  • Coordinated graceful shutdown is implemented for SIGINT.

CacheCore is an educational systems project and should not be exposed as a production service.

Documentation

Project Status

The implementation is feature-complete for its intended learning scope. Repository hygiene and the final regression pass are complete, with no functional regressions found across automated tests, TCP behavior, concurrent stress, restart persistence, coordinated shutdown, and a benchmark smoke run.

The most valuable result for me was learning to treat system behavior as a chain of explicit guarantees: frame the byte stream, bound concurrent work, define shared-state ownership, persist mutations in the correct order, recover only what the format can safely identify, and measure performance with controlled timing boundaries.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheCore

Overview

CacheCore is a Redis-inspired key-value server that I built in C to understand how networking, concurrency, persistence, recovery, and performance interact inside a stateful server.

The project began as an in-memory hashmap and command parser. I developed it incrementally into a persistent multi-client TCP service with stream-aware command framing, a fixed worker pool, a bounded connection queue, synchronized shared state, append-only persistence, incomplete-tail recovery, log compaction, coordinated shutdown, layered tests, and a synchronized throughput benchmark.

Each major feature was introduced in response to a limitation exposed by the previous design. The goal was not to reproduce Redis or create a production-ready database, but to learn the mechanisms and trade-offs behind reliable systems software by implementing and testing them directly.

Verified Features

  • A custom, case-sensitive, newline-delimited TCP protocol with PING, SET, GET, DEL, and QUIT.
  • Persistent connections with buffering for fragmented reads and multiple commands received together.
  • A dynamically resized, separate-chaining hashmap for in-memory storage.
  • Four fixed worker threads and a bounded 16-entry connection queue.
  • Mutex-protected shared database state across concurrent clients.
  • Append-only logging for SET and DEL, with fsync before the in-memory mutation.
  • Startup replay, truncation of an incomplete trailing record, and automatic AOF compaction.
  • Coordinated SIGINT shutdown that closes active client sockets and joins workers.
  • Unit, integration, concurrent stress, and synchronized benchmark coverage.

Design Evolution and Lessons

  • I first separated command parsing, storage, and server responsibilities so each layer could evolve independently.
  • TCP networking exposed the difference between messages and byte streams, leading to explicit newline framing, buffering, and persistent connections.
  • Moving from one client to concurrent clients introduced shared-state ownership, synchronization, bounded work queues, and shutdown coordination.
  • Adding persistence showed me that durability depends on write ordering and clearly defined recovery limits, not merely writing data to a file.
  • Layered tests and a synchronized benchmark turned observed behavior into evidence I could reproduce and reason about.

Architecture at a Glance

+------------+
| TCP client |
+-----+------+
|
| newline-delimited commands (port 6379)
v
+--------------+
| TCP listener |
+------+-------+
|
v
+--------------------------------+
| Bounded connection queue |
| Capacity: 16 |
+---------------+----------------+
|
v
+--------------------------------+
| Worker pool |
| 4 threads |
+---------------+----------------+
|
| one worker owns each connection
v
+--------------------------------+
| Persistent connection handler |
| Buffering, framing, dispatch |
+-----------+--------------------+
|
+---- PING / QUIT ------> Direct response
|
+---- SET / GET / DEL
|
v
+----------------------+
| Database API |
| Single mutex |
+----------+-----------+
|
+-----------+-----------+
| |
GET path SET / DEL path
| |
| v
| +------------------+
| | Append to AOF |
| | and fsync |
| +--------+---------+
| |
| | then mutate
v v
+----------------------+
| In-memory hashmap |
+----------------------+
Startup: AOF -------- replay --------> hashmap
Compaction: hashmap ---- snapshot ------> AOF
  • The listener accepts connections and rejects a new client if the queue is full.
  • A worker owns a persistent connection until that client disconnects or sends QUIT.
  • One database mutex serializes SET, GET, and DEL operations.
  • Successful mutations are persisted before the in-memory hashmap is changed.

Build and Run

Requirements

  • A C11 compiler
  • POSIX sockets and pthreads
  • make
  • nc/netcat for manual protocol testing (optional)

CacheCore was developed and tested on macOS.

Build

make

Start the Server

make run

The server listens on port 6379. From another terminal:

nc 127.0.0.1 6379

The AOF file is created in the server's working directory.

Protocol Commands

CommandNormal responseDescription
PINGPONGCheck that the server is responsive.
SET <key> <value>OKStore or replace a value.
GET <key><value> or NOT_FOUNDRetrieve a value.
DEL <key>OK or NOT_FOUNDDelete a key.
QUITBYEClose the connection.

Commands are case-sensitive and newline-terminated. Keys and values cannot contain whitespace.

Example session:

PING
PONG
SET language c
OK
GET language
c
DEL language
OK
QUIT
BYE

Testing

Run the automated unit and database/AOF integration tests:

make test

This covers the hashmap, command parser, database operations, AOF replay, incomplete-tail recovery, and compaction.

To run the concurrent TCP stress test, start the server and use a second terminal:

make stress-client

The stress client opens eight persistent connections. Each performs 1,000 validated SET/GET cycles. The final regression pass also verified the protocol over one persistent connection, persistence across restart, coordinated shutdown with an active client, and port reuse after shutdown.

Benchmark

With the server running, execute:

make benchmark

The benchmark measures steady-state synchronous GET throughput over the local loopback interface. Connection setup, the initial SET, worker readiness, QUIT, and cleanup are outside the timed interval. Each client keeps one request outstanding at a time; the benchmark does not use pipelining.

Three trials were run for each client count, with 10,000 requests per client:

ClientsTotal GET requestsMedian throughput
110,00053,439 requests/s
220,00094,493 requests/s
440,000146,014 requests/s

These are local macOS measurements and are intended to show scaling within this design, not production or distributed performance. See Testing and Benchmarking for the methodology and interpretation.

Limitations and Security Scope

  • CacheCore uses a custom protocol and is not Redis-compatible.
  • The server binds to all interfaces and provides no authentication, authorization, or encryption.
  • Persistent clients occupy workers for the lifetime of their connections, and a full queue causes new connections to be rejected.
  • One database mutex serializes all data operations, favoring simple correctness over maximum parallelism.
  • AOF recovery repairs only an incomplete trailing record; a malformed complete record causes startup failure.
  • Compaction uses fsync and atomic rename for the AOF file but does not fsync the containing directory.
  • Coordinated graceful shutdown is implemented for SIGINT.

CacheCore is an educational systems project and should not be exposed as a production service.

Documentation

Project Status

The implementation is feature-complete for its intended learning scope. Repository hygiene and the final regression pass are complete, with no functional regressions found across automated tests, TCP behavior, concurrent stress, restart persistence, coordinated shutdown, and a benchmark smoke run.

The most valuable result for me was learning to treat system behavior as a chain of explicit guarantees: frame the byte stream, bound concurrent work, define shared-state ownership, persist mutations in the correct order, recover only what the format can safely identify, and measure performance with controlled timing boundaries.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheCore

Overview

CacheCore is a Redis-inspired key-value server that I built in C to understand how networking, concurrency, persistence, recovery, and performance interact inside a stateful server.

The project began as an in-memory hashmap and command parser. I developed it incrementally into a persistent multi-client TCP service with stream-aware command framing, a fixed worker pool, a bounded connection queue, synchronized shared state, append-only persistence, incomplete-tail recovery, log compaction, coordinated shutdown, layered tests, and a synchronized throughput benchmark.

Each major feature was introduced in response to a limitation exposed by the previous design. The goal was not to reproduce Redis or create a production-ready database, but to learn the mechanisms and trade-offs behind reliable systems software by implementing and testing them directly.

Verified Features

  • A custom, case-sensitive, newline-delimited TCP protocol with PING, SET, GET, DEL, and QUIT.
  • Persistent connections with buffering for fragmented reads and multiple commands received together.
  • A dynamically resized, separate-chaining hashmap for in-memory storage.
  • Four fixed worker threads and a bounded 16-entry connection queue.
  • Mutex-protected shared database state across concurrent clients.
  • Append-only logging for SET and DEL, with fsync before the in-memory mutation.
  • Startup replay, truncation of an incomplete trailing record, and automatic AOF compaction.
  • Coordinated SIGINT shutdown that closes active client sockets and joins workers.
  • Unit, integration, concurrent stress, and synchronized benchmark coverage.

Design Evolution and Lessons

  • I first separated command parsing, storage, and server responsibilities so each layer could evolve independently.
  • TCP networking exposed the difference between messages and byte streams, leading to explicit newline framing, buffering, and persistent connections.
  • Moving from one client to concurrent clients introduced shared-state ownership, synchronization, bounded work queues, and shutdown coordination.
  • Adding persistence showed me that durability depends on write ordering and clearly defined recovery limits, not merely writing data to a file.
  • Layered tests and a synchronized benchmark turned observed behavior into evidence I could reproduce and reason about.

Architecture at a Glance

+------------+
| TCP client |
+-----+------+
|
| newline-delimited commands (port 6379)
v
+--------------+
| TCP listener |
+------+-------+
|
v
+--------------------------------+
| Bounded connection queue |
| Capacity: 16 |
+---------------+----------------+
|
v
+--------------------------------+
| Worker pool |
| 4 threads |
+---------------+----------------+
|
| one worker owns each connection
v
+--------------------------------+
| Persistent connection handler |
| Buffering, framing, dispatch |
+-----------+--------------------+
|
+---- PING / QUIT ------> Direct response
|
+---- SET / GET / DEL
|
v
+----------------------+
| Database API |
| Single mutex |
+----------+-----------+
|
+-----------+-----------+
| |
GET path SET / DEL path
| |
| v
| +------------------+
| | Append to AOF |
| | and fsync |
| +--------+---------+
| |
| | then mutate
v v
+----------------------+
| In-memory hashmap |
+----------------------+
Startup: AOF -------- replay --------> hashmap
Compaction: hashmap ---- snapshot ------> AOF
  • The listener accepts connections and rejects a new client if the queue is full.
  • A worker owns a persistent connection until that client disconnects or sends QUIT.
  • One database mutex serializes SET, GET, and DEL operations.
  • Successful mutations are persisted before the in-memory hashmap is changed.

Build and Run

Requirements

  • A C11 compiler
  • POSIX sockets and pthreads
  • make
  • nc/netcat for manual protocol testing (optional)

CacheCore was developed and tested on macOS.

Build

make

Start the Server

make run

The server listens on port 6379. From another terminal:

nc 127.0.0.1 6379

The AOF file is created in the server's working directory.

Protocol Commands

CommandNormal responseDescription
PINGPONGCheck that the server is responsive.
SET <key> <value>OKStore or replace a value.
GET <key><value> or NOT_FOUNDRetrieve a value.
DEL <key>OK or NOT_FOUNDDelete a key.
QUITBYEClose the connection.

Commands are case-sensitive and newline-terminated. Keys and values cannot contain whitespace.

Example session:

PING
PONG
SET language c
OK
GET language
c
DEL language
OK
QUIT
BYE

Testing

Run the automated unit and database/AOF integration tests:

make test

This covers the hashmap, command parser, database operations, AOF replay, incomplete-tail recovery, and compaction.

To run the concurrent TCP stress test, start the server and use a second terminal:

make stress-client

The stress client opens eight persistent connections. Each performs 1,000 validated SET/GET cycles. The final regression pass also verified the protocol over one persistent connection, persistence across restart, coordinated shutdown with an active client, and port reuse after shutdown.

Benchmark

With the server running, execute:

make benchmark

The benchmark measures steady-state synchronous GET throughput over the local loopback interface. Connection setup, the initial SET, worker readiness, QUIT, and cleanup are outside the timed interval. Each client keeps one request outstanding at a time; the benchmark does not use pipelining.

Three trials were run for each client count, with 10,000 requests per client:

ClientsTotal GET requestsMedian throughput
110,00053,439 requests/s
220,00094,493 requests/s
440,000146,014 requests/s

These are local macOS measurements and are intended to show scaling within this design, not production or distributed performance. See Testing and Benchmarking for the methodology and interpretation.

Limitations and Security Scope

  • CacheCore uses a custom protocol and is not Redis-compatible.
  • The server binds to all interfaces and provides no authentication, authorization, or encryption.
  • Persistent clients occupy workers for the lifetime of their connections, and a full queue causes new connections to be rejected.
  • One database mutex serializes all data operations, favoring simple correctness over maximum parallelism.
  • AOF recovery repairs only an incomplete trailing record; a malformed complete record causes startup failure.
  • Compaction uses fsync and atomic rename for the AOF file but does not fsync the containing directory.
  • Coordinated graceful shutdown is implemented for SIGINT.

CacheCore is an educational systems project and should not be exposed as a production service.

Documentation

Project Status

The implementation is feature-complete for its intended learning scope. Repository hygiene and the final regression pass are complete, with no functional regressions found across automated tests, TCP behavior, concurrent stress, restart persistence, coordinated shutdown, and a benchmark smoke run.

The most valuable result for me was learning to treat system behavior as a chain of explicit guarantees: frame the byte stream, bound concurrent work, define shared-state ownership, persist mutations in the correct order, recover only what the format can safely identify, and measure performance with controlled timing boundaries.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheCore

Overview

CacheCore is a Redis-inspired key-value server that I built in C to understand how networking, concurrency, persistence, recovery, and performance interact inside a stateful server.

The project began as an in-memory hashmap and command parser. I developed it incrementally into a persistent multi-client TCP service with stream-aware command framing, a fixed worker pool, a bounded connection queue, synchronized shared state, append-only persistence, incomplete-tail recovery, log compaction, coordinated shutdown, layered tests, and a synchronized throughput benchmark.

Each major feature was introduced in response to a limitation exposed by the previous design. The goal was not to reproduce Redis or create a production-ready database, but to learn the mechanisms and trade-offs behind reliable systems software by implementing and testing them directly.

Verified Features

  • A custom, case-sensitive, newline-delimited TCP protocol with PING, SET, GET, DEL, and QUIT.
  • Persistent connections with buffering for fragmented reads and multiple commands received together.
  • A dynamically resized, separate-chaining hashmap for in-memory storage.
  • Four fixed worker threads and a bounded 16-entry connection queue.
  • Mutex-protected shared database state across concurrent clients.
  • Append-only logging for SET and DEL, with fsync before the in-memory mutation.
  • Startup replay, truncation of an incomplete trailing record, and automatic AOF compaction.
  • Coordinated SIGINT shutdown that closes active client sockets and joins workers.
  • Unit, integration, concurrent stress, and synchronized benchmark coverage.

Design Evolution and Lessons

  • I first separated command parsing, storage, and server responsibilities so each layer could evolve independently.
  • TCP networking exposed the difference between messages and byte streams, leading to explicit newline framing, buffering, and persistent connections.
  • Moving from one client to concurrent clients introduced shared-state ownership, synchronization, bounded work queues, and shutdown coordination.
  • Adding persistence showed me that durability depends on write ordering and clearly defined recovery limits, not merely writing data to a file.
  • Layered tests and a synchronized benchmark turned observed behavior into evidence I could reproduce and reason about.

Architecture at a Glance

+------------+
| TCP client |
+-----+------+
|
| newline-delimited commands (port 6379)
v
+--------------+
| TCP listener |
+------+-------+
|
v
+--------------------------------+
| Bounded connection queue |
| Capacity: 16 |
+---------------+----------------+
|
v
+--------------------------------+
| Worker pool |
| 4 threads |
+---------------+----------------+
|
| one worker owns each connection
v
+--------------------------------+
| Persistent connection handler |
| Buffering, framing, dispatch |
+-----------+--------------------+
|
+---- PING / QUIT ------> Direct response
|
+---- SET / GET / DEL
|
v
+----------------------+
| Database API |
| Single mutex |
+----------+-----------+
|
+-----------+-----------+
| |
GET path SET / DEL path
| |
| v
| +------------------+
| | Append to AOF |
| | and fsync |
| +--------+---------+
| |
| | then mutate
v v
+----------------------+
| In-memory hashmap |
+----------------------+
Startup: AOF -------- replay --------> hashmap
Compaction: hashmap ---- snapshot ------> AOF
  • The listener accepts connections and rejects a new client if the queue is full.
  • A worker owns a persistent connection until that client disconnects or sends QUIT.
  • One database mutex serializes SET, GET, and DEL operations.
  • Successful mutations are persisted before the in-memory hashmap is changed.

Build and Run

Requirements

  • A C11 compiler
  • POSIX sockets and pthreads
  • make
  • nc/netcat for manual protocol testing (optional)

CacheCore was developed and tested on macOS.

Build

make

Start the Server

make run

The server listens on port 6379. From another terminal:

nc 127.0.0.1 6379

The AOF file is created in the server's working directory.

Protocol Commands

CommandNormal responseDescription
PINGPONGCheck that the server is responsive.
SET <key> <value>OKStore or replace a value.
GET <key><value> or NOT_FOUNDRetrieve a value.
DEL <key>OK or NOT_FOUNDDelete a key.
QUITBYEClose the connection.

Commands are case-sensitive and newline-terminated. Keys and values cannot contain whitespace.

Example session:

PING
PONG
SET language c
OK
GET language
c
DEL language
OK
QUIT
BYE

Testing

Run the automated unit and database/AOF integration tests:

make test

This covers the hashmap, command parser, database operations, AOF replay, incomplete-tail recovery, and compaction.

To run the concurrent TCP stress test, start the server and use a second terminal:

make stress-client

The stress client opens eight persistent connections. Each performs 1,000 validated SET/GET cycles. The final regression pass also verified the protocol over one persistent connection, persistence across restart, coordinated shutdown with an active client, and port reuse after shutdown.

Benchmark

With the server running, execute:

make benchmark

The benchmark measures steady-state synchronous GET throughput over the local loopback interface. Connection setup, the initial SET, worker readiness, QUIT, and cleanup are outside the timed interval. Each client keeps one request outstanding at a time; the benchmark does not use pipelining.

Three trials were run for each client count, with 10,000 requests per client:

ClientsTotal GET requestsMedian throughput
110,00053,439 requests/s
220,00094,493 requests/s
440,000146,014 requests/s

These are local macOS measurements and are intended to show scaling within this design, not production or distributed performance. See Testing and Benchmarking for the methodology and interpretation.

Limitations and Security Scope

  • CacheCore uses a custom protocol and is not Redis-compatible.
  • The server binds to all interfaces and provides no authentication, authorization, or encryption.
  • Persistent clients occupy workers for the lifetime of their connections, and a full queue causes new connections to be rejected.
  • One database mutex serializes all data operations, favoring simple correctness over maximum parallelism.
  • AOF recovery repairs only an incomplete trailing record; a malformed complete record causes startup failure.
  • Compaction uses fsync and atomic rename for the AOF file but does not fsync the containing directory.
  • Coordinated graceful shutdown is implemented for SIGINT.

CacheCore is an educational systems project and should not be exposed as a production service.

Documentation

Project Status

The implementation is feature-complete for its intended learning scope. Repository hygiene and the final regression pass are complete, with no functional regressions found across automated tests, TCP behavior, concurrent stress, restart persistence, coordinated shutdown, and a benchmark smoke run.

The most valuable result for me was learning to treat system behavior as a chain of explicit guarantees: frame the byte stream, bound concurrent work, define shared-state ownership, persist mutations in the correct order, recover only what the format can safely identify, and measure performance with controlled timing boundaries.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

CacheCore

Overview

CacheCore is a Redis-inspired key-value server that I built in C to understand how networking, concurrency, persistence, recovery, and performance interact inside a stateful server.

The project began as an in-memory hashmap and command parser. I developed it incrementally into a persistent multi-client TCP service with stream-aware command framing, a fixed worker pool, a bounded connection queue, synchronized shared state, append-only persistence, incomplete-tail recovery, log compaction, coordinated shutdown, layered tests, and a synchronized throughput benchmark.

Each major feature was introduced in response to a limitation exposed by the previous design. The goal was not to reproduce Redis or create a production-ready database, but to learn the mechanisms and trade-offs behind reliable systems software by implementing and testing them directly.

Verified Features

  • A custom, case-sensitive, newline-delimited TCP protocol with PING, SET, GET, DEL, and QUIT.
  • Persistent connections with buffering for fragmented reads and multiple commands received together.
  • A dynamically resized, separate-chaining hashmap for in-memory storage.
  • Four fixed worker threads and a bounded 16-entry connection queue.
  • Mutex-protected shared database state across concurrent clients.
  • Append-only logging for SET and DEL, with fsync before the in-memory mutation.
  • Startup replay, truncation of an incomplete trailing record, and automatic AOF compaction.
  • Coordinated SIGINT shutdown that closes active client sockets and joins workers.
  • Unit, integration, concurrent stress, and synchronized benchmark coverage.

Design Evolution and Lessons

  • I first separated command parsing, storage, and server responsibilities so each layer could evolve independently.
  • TCP networking exposed the difference between messages and byte streams, leading to explicit newline framing, buffering, and persistent connections.
  • Moving from one client to concurrent clients introduced shared-state ownership, synchronization, bounded work queues, and shutdown coordination.
  • Adding persistence showed me that durability depends on write ordering and clearly defined recovery limits, not merely writing data to a file.
  • Layered tests and a synchronized benchmark turned observed behavior into evidence I could reproduce and reason about.

Architecture at a Glance

+------------+
| TCP client |
+-----+------+
|
| newline-delimited commands (port 6379)
v
+--------------+
| TCP listener |
+------+-------+
|
v
+--------------------------------+
| Bounded connection queue |
| Capacity: 16 |
+---------------+----------------+
|
v
+--------------------------------+
| Worker pool |
| 4 threads |
+---------------+----------------+
|
| one worker owns each connection
v
+--------------------------------+
| Persistent connection handler |
| Buffering, framing, dispatch |
+-----------+--------------------+
|
+---- PING / QUIT ------> Direct response
|
+---- SET / GET / DEL
|
v
+----------------------+
| Database API |
| Single mutex |
+----------+-----------+
|
+-----------+-----------+
| |
GET path SET / DEL path
| |
| v
| +------------------+
| | Append to AOF |
| | and fsync |
| +--------+---------+
| |
| | then mutate
v v
+----------------------+
| In-memory hashmap |
+----------------------+
Startup: AOF -------- replay --------> hashmap
Compaction: hashmap ---- snapshot ------> AOF
  • The listener accepts connections and rejects a new client if the queue is full.
  • A worker owns a persistent connection until that client disconnects or sends QUIT.
  • One database mutex serializes SET, GET, and DEL operations.
  • Successful mutations are persisted before the in-memory hashmap is changed.

Build and Run

Requirements

  • A C11 compiler
  • POSIX sockets and pthreads
  • make
  • nc/netcat for manual protocol testing (optional)

CacheCore was developed and tested on macOS.

Build

make

Start the Server

make run

The server listens on port 6379. From another terminal:

nc 127.0.0.1 6379

The AOF file is created in the server's working directory.

Protocol Commands

CommandNormal responseDescription
PINGPONGCheck that the server is responsive.
SET <key> <value>OKStore or replace a value.
GET <key><value> or NOT_FOUNDRetrieve a value.
DEL <key>OK or NOT_FOUNDDelete a key.
QUITBYEClose the connection.

Commands are case-sensitive and newline-terminated. Keys and values cannot contain whitespace.

Example session:

PING
PONG
SET language c
OK
GET language
c
DEL language
OK
QUIT
BYE

Testing

Run the automated unit and database/AOF integration tests:

make test

This covers the hashmap, command parser, database operations, AOF replay, incomplete-tail recovery, and compaction.

To run the concurrent TCP stress test, start the server and use a second terminal:

make stress-client

The stress client opens eight persistent connections. Each performs 1,000 validated SET/GET cycles. The final regression pass also verified the protocol over one persistent connection, persistence across restart, coordinated shutdown with an active client, and port reuse after shutdown.

Benchmark

With the server running, execute:

make benchmark

The benchmark measures steady-state synchronous GET throughput over the local loopback interface. Connection setup, the initial SET, worker readiness, QUIT, and cleanup are outside the timed interval. Each client keeps one request outstanding at a time; the benchmark does not use pipelining.

Three trials were run for each client count, with 10,000 requests per client:

ClientsTotal GET requestsMedian throughput
110,00053,439 requests/s
220,00094,493 requests/s
440,000146,014 requests/s

These are local macOS measurements and are intended to show scaling within this design, not production or distributed performance. See Testing and Benchmarking for the methodology and interpretation.

Limitations and Security Scope

  • CacheCore uses a custom protocol and is not Redis-compatible.
  • The server binds to all interfaces and provides no authentication, authorization, or encryption.
  • Persistent clients occupy workers for the lifetime of their connections, and a full queue causes new connections to be rejected.
  • One database mutex serializes all data operations, favoring simple correctness over maximum parallelism.
  • AOF recovery repairs only an incomplete trailing record; a malformed complete record causes startup failure.
  • Compaction uses fsync and atomic rename for the AOF file but does not fsync the containing directory.
  • Coordinated graceful shutdown is implemented for SIGINT.

CacheCore is an educational systems project and should not be exposed as a production service.

Documentation

Project Status

The implementation is feature-complete for its intended learning scope. Repository hygiene and the final regression pass are complete, with no functional regressions found across automated tests, TCP behavior, concurrent stress, restart persistence, coordinated shutdown, and a benchmark smoke run.

The most valuable result for me was learning to treat system behavior as a chain of explicit guarantees: frame the byte stream, bound concurrent work, define shared-state ownership, persist mutations in the correct order, recover only what the format can safely identify, and measure performance with controlled timing boundaries.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages