Repository files navigation

An experiment on persistent data-streaming

Status

Currently, working in the foundation of the storage layer.

Checkout the Roadmap for feature-specific details.

Project Goals

  • Learn and have fun™️
  • Implement a Kinesis-like streaming-service
  • Single binary
  • Easy to Host, Run & Operate (have you tried to run Kafka yourself?)
    • Kubernetes friendly

Internals

CommitLog

The main component of the whole system is the commit-log, an abstraction manages reads and writes to the log by implementing an immutable, append-only, file-backed sequence of "records", or chunks of data/events that are transmited from producers to consumers.

Records can be written to the log, always appending the last record over and over.

e.g.:

 current cursor
segment 0 ^
|-------------------------------|
| record 0 | record 1 | ... | --> time
|-------------------------------|

In order to manage and scale read and writes, the commit-log split groups of records into Segments, managing to write to a single segment until it reaches a certain, specified size.

Each time a record is written, the segment is trusted to have enough space for the given buffer, then the record is written to the current segment, and the pointer is updated.

More info in the commit_log/src/lib.rs file.

Segment

A Segment is a tuple abstraction to manage the Index and Log files.

Every Segment is composed of a log-file and an index, e.g.:

00000000000011812312.log
00000000000011812312.idx

The role of the segment is to manage writes to the logfile and ensure the entries can be read later on by doing lookups in the index.

On every write, the segment writes an entry to the index with the record's position and size, in the log-file, for later use.

The segment also manages the size of the log file, preventing it from being written once it reaches the specified.

When a segment is full, the commit log makes sure to rotate to a new one, closing the old one.

See how it looks like on disk (on a high-level):

 current cursor
segment 0 ^
|-------------------------------| |
| record 0 | record 1 | ... | segment 1 (current) |
|-------------------------------|-----------------------------| --> time
| record 2 | record 3 | ... |
|-----------------------------|

Under the hood is a bit more complex, the management of writing to the file to disk is of the Segments', as well as managing the Index file.

More info in the commit_log/src/segment.rs and commit_log/src/segment/index.rs and log.rs files.

Log file

The log file is a varied-size sequence of bytes that is storing the content of the records produced by the producers. However, the log itself doesn't have any mechanism for recovery of such records. That's responsibility of the index.

Once initialized, the log-file is truncated to reach the desired value and reserve both memory and disk space, the same for the index.

 current cursor
^
|-------------------------------|
| record 0 | record 1 | ... |----> time
|-------------------------------|

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/log.rs file.

Index file

The role of the index is to provide pointers to records in the log file. Each entry of the index is 20 bytes long, 10 bytes are used for the offset address of the record in the log file, the other 10 bytes for the size of the record.

e.g.:

 current cursor
^
|-------------------------------|
| offset-size | offset-size |...|----> time
|-------------------------------|

There is no separator, it's position-based.

e.g.:

00000001000000000020
---------------------
offset | size
* 000000010 -> offset
* 000000020 -> size

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/index.rs file.

Performance

These are preliminar and poorly collected results, yet it looks interesting:

Storage (Tests are completely offline, no network¹ ...)

Segment size: 20MB
Index size: 10MB
5 GB worth records written in 37.667706s
5 GB worth cold records read in 1.384433s
5 GB worth warm records read in 1.266053s

Per-segment²:

  • ~130 MB/s on write
  • ~3.7 GB/s on cold read (while loading into memory pages)
  • ~4.2 GB/s on warm read (already loaded into memory pages)

Notes:

  • ¹ - Offline - no network overhead taken into account, network will be a big player on the overhead. However, the focus now is storage.
  • ² - Per-segment performance, in a comparinson with kinesis/kafka that would be the per-shard value. If you were to have 10 shards, you could achieve 10x that, limited by external factors, HD/CPU/...

Setup:

OS: macOS Mojave 10.14.4 (18E226)
CPU: 2,5 GHz Intel Core i7
RAM: 16 GB 2133 MHz LPDDR3
HD: 256 GB SSD Storage

Roadmap

  • Settle for a name
  • Storage
    • Core CommitLog
      • Segments
        • Write to segments
        • Write across segments
        • Read from segment
        • Read across segments
          • Index
          • Better API for reading
        • Extract logfile from segment
        • Memory Mapped IO (Performance test before/after)
          • Write
            • Index
            • Segment
            • Refactor segment split to take index into account
          • Read
            • Index
            • Segment
        • Restore state from disk - Ref: (https://github.com/zowens/commitlog/blob/master/src/file_set.rs#L17-L98)
      • Record Struct
        • End-to-end use of records
    • Topics/Streams (probably should come up with a better name)
      • Partitions/Shards
        • ?
  • Networking/API
    • Decide simple inicial protocol
    • Implement Basic Socket Communication
    • Implement TCP/HTTP?
    • Write to streams over the network
    • zero-cost copy (OS sendfile)
  • Replication
    • More to come here...
  • Operational
    • Configuration
      • CLI Basics
  • CI/Tooling
    • Setup Travis
      • Build with Linux/macOS
    • Docker
      • Alpine
  • Testing
    • Benchmarks
      • Write
      • Read
    • Setup Integration Tests
    • Abstract Test Code
  • Documentation
    • Document decisions
      • Document data structures / types
    • Improve README
      • How to Run

Commands

Available make commands

  • make build - Builds the application with cargo
  • make build_release - Builds the application with cargo, with release optimizations
  • make docker_test_watcher - Runs funzzy on linux over docker-compose
  • make docs - Generate the GitHub Markdown docs (At the moment only mermaid)
  • make format - Formats the code according to cargo
  • make help - Lists the available commands. Add a comment with '##' to describe a command.
  • make install - Builds a release version and installs to your cago bin path
  • make run - Runs the newly built
  • make test - Tests all features

References

About

♒︎ An experiment on persistent data-streaming

Resources

Stars

0 stars

Watchers

1 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

Repository files navigation

An experiment on persistent data-streaming

Status

Currently, working in the foundation of the storage layer.

Checkout the Roadmap for feature-specific details.

Project Goals

  • Learn and have fun™️
  • Implement a Kinesis-like streaming-service
  • Single binary
  • Easy to Host, Run & Operate (have you tried to run Kafka yourself?)
    • Kubernetes friendly

Internals

CommitLog

The main component of the whole system is the commit-log, an abstraction manages reads and writes to the log by implementing an immutable, append-only, file-backed sequence of "records", or chunks of data/events that are transmited from producers to consumers.

Records can be written to the log, always appending the last record over and over.

e.g.:

 current cursor
segment 0 ^
|-------------------------------|
| record 0 | record 1 | ... | --> time
|-------------------------------|

In order to manage and scale read and writes, the commit-log split groups of records into Segments, managing to write to a single segment until it reaches a certain, specified size.

Each time a record is written, the segment is trusted to have enough space for the given buffer, then the record is written to the current segment, and the pointer is updated.

More info in the commit_log/src/lib.rs file.

Segment

A Segment is a tuple abstraction to manage the Index and Log files.

Every Segment is composed of a log-file and an index, e.g.:

00000000000011812312.log
00000000000011812312.idx

The role of the segment is to manage writes to the logfile and ensure the entries can be read later on by doing lookups in the index.

On every write, the segment writes an entry to the index with the record's position and size, in the log-file, for later use.

The segment also manages the size of the log file, preventing it from being written once it reaches the specified.

When a segment is full, the commit log makes sure to rotate to a new one, closing the old one.

See how it looks like on disk (on a high-level):

 current cursor
segment 0 ^
|-------------------------------| |
| record 0 | record 1 | ... | segment 1 (current) |
|-------------------------------|-----------------------------| --> time
| record 2 | record 3 | ... |
|-----------------------------|

Under the hood is a bit more complex, the management of writing to the file to disk is of the Segments', as well as managing the Index file.

More info in the commit_log/src/segment.rs and commit_log/src/segment/index.rs and log.rs files.

Log file

The log file is a varied-size sequence of bytes that is storing the content of the records produced by the producers. However, the log itself doesn't have any mechanism for recovery of such records. That's responsibility of the index.

Once initialized, the log-file is truncated to reach the desired value and reserve both memory and disk space, the same for the index.

 current cursor
^
|-------------------------------|
| record 0 | record 1 | ... |----> time
|-------------------------------|

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/log.rs file.

Index file

The role of the index is to provide pointers to records in the log file. Each entry of the index is 20 bytes long, 10 bytes are used for the offset address of the record in the log file, the other 10 bytes for the size of the record.

e.g.:

 current cursor
^
|-------------------------------|
| offset-size | offset-size |...|----> time
|-------------------------------|

There is no separator, it's position-based.

e.g.:

00000001000000000020
---------------------
offset | size
* 000000010 -> offset
* 000000020 -> size

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/index.rs file.

Performance

These are preliminar and poorly collected results, yet it looks interesting:

Storage (Tests are completely offline, no network¹ ...)

Segment size: 20MB
Index size: 10MB
5 GB worth records written in 37.667706s
5 GB worth cold records read in 1.384433s
5 GB worth warm records read in 1.266053s

Per-segment²:

  • ~130 MB/s on write
  • ~3.7 GB/s on cold read (while loading into memory pages)
  • ~4.2 GB/s on warm read (already loaded into memory pages)

Notes:

  • ¹ - Offline - no network overhead taken into account, network will be a big player on the overhead. However, the focus now is storage.
  • ² - Per-segment performance, in a comparinson with kinesis/kafka that would be the per-shard value. If you were to have 10 shards, you could achieve 10x that, limited by external factors, HD/CPU/...

Setup:

OS: macOS Mojave 10.14.4 (18E226)
CPU: 2,5 GHz Intel Core i7
RAM: 16 GB 2133 MHz LPDDR3
HD: 256 GB SSD Storage

Roadmap

  • Settle for a name
  • Storage
    • Core CommitLog
      • Segments
        • Write to segments
        • Write across segments
        • Read from segment
        • Read across segments
          • Index
          • Better API for reading
        • Extract logfile from segment
        • Memory Mapped IO (Performance test before/after)
          • Write
            • Index
            • Segment
            • Refactor segment split to take index into account
          • Read
            • Index
            • Segment
        • Restore state from disk - Ref: (https://github.com/zowens/commitlog/blob/master/src/file_set.rs#L17-L98)
      • Record Struct
        • End-to-end use of records
    • Topics/Streams (probably should come up with a better name)
      • Partitions/Shards
        • ?
  • Networking/API
    • Decide simple inicial protocol
    • Implement Basic Socket Communication
    • Implement TCP/HTTP?
    • Write to streams over the network
    • zero-cost copy (OS sendfile)
  • Replication
    • More to come here...
  • Operational
    • Configuration
      • CLI Basics
  • CI/Tooling
    • Setup Travis
      • Build with Linux/macOS
    • Docker
      • Alpine
  • Testing
    • Benchmarks
      • Write
      • Read
    • Setup Integration Tests
    • Abstract Test Code
  • Documentation
    • Document decisions
      • Document data structures / types
    • Improve README
      • How to Run

Commands

Available make commands

  • make build - Builds the application with cargo
  • make build_release - Builds the application with cargo, with release optimizations
  • make docker_test_watcher - Runs funzzy on linux over docker-compose
  • make docs - Generate the GitHub Markdown docs (At the moment only mermaid)
  • make format - Formats the code according to cargo
  • make help - Lists the available commands. Add a comment with '##' to describe a command.
  • make install - Builds a release version and installs to your cago bin path
  • make run - Runs the newly built
  • make test - Tests all features

References

About

♒︎ An experiment on persistent data-streaming

Resources

Stars

0 stars

Watchers

1 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

Repository files navigation

An experiment on persistent data-streaming

Status

Currently, working in the foundation of the storage layer.

Checkout the Roadmap for feature-specific details.

Project Goals

  • Learn and have fun™️
  • Implement a Kinesis-like streaming-service
  • Single binary
  • Easy to Host, Run & Operate (have you tried to run Kafka yourself?)
    • Kubernetes friendly

Internals

CommitLog

The main component of the whole system is the commit-log, an abstraction manages reads and writes to the log by implementing an immutable, append-only, file-backed sequence of "records", or chunks of data/events that are transmited from producers to consumers.

Records can be written to the log, always appending the last record over and over.

e.g.:

 current cursor
segment 0 ^
|-------------------------------|
| record 0 | record 1 | ... | --> time
|-------------------------------|

In order to manage and scale read and writes, the commit-log split groups of records into Segments, managing to write to a single segment until it reaches a certain, specified size.

Each time a record is written, the segment is trusted to have enough space for the given buffer, then the record is written to the current segment, and the pointer is updated.

More info in the commit_log/src/lib.rs file.

Segment

A Segment is a tuple abstraction to manage the Index and Log files.

Every Segment is composed of a log-file and an index, e.g.:

00000000000011812312.log
00000000000011812312.idx

The role of the segment is to manage writes to the logfile and ensure the entries can be read later on by doing lookups in the index.

On every write, the segment writes an entry to the index with the record's position and size, in the log-file, for later use.

The segment also manages the size of the log file, preventing it from being written once it reaches the specified.

When a segment is full, the commit log makes sure to rotate to a new one, closing the old one.

See how it looks like on disk (on a high-level):

 current cursor
segment 0 ^
|-------------------------------| |
| record 0 | record 1 | ... | segment 1 (current) |
|-------------------------------|-----------------------------| --> time
| record 2 | record 3 | ... |
|-----------------------------|

Under the hood is a bit more complex, the management of writing to the file to disk is of the Segments', as well as managing the Index file.

More info in the commit_log/src/segment.rs and commit_log/src/segment/index.rs and log.rs files.

Log file

The log file is a varied-size sequence of bytes that is storing the content of the records produced by the producers. However, the log itself doesn't have any mechanism for recovery of such records. That's responsibility of the index.

Once initialized, the log-file is truncated to reach the desired value and reserve both memory and disk space, the same for the index.

 current cursor
^
|-------------------------------|
| record 0 | record 1 | ... |----> time
|-------------------------------|

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/log.rs file.

Index file

The role of the index is to provide pointers to records in the log file. Each entry of the index is 20 bytes long, 10 bytes are used for the offset address of the record in the log file, the other 10 bytes for the size of the record.

e.g.:

 current cursor
^
|-------------------------------|
| offset-size | offset-size |...|----> time
|-------------------------------|

There is no separator, it's position-based.

e.g.:

00000001000000000020
---------------------
offset | size
* 000000010 -> offset
* 000000020 -> size

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/index.rs file.

Performance

These are preliminar and poorly collected results, yet it looks interesting:

Storage (Tests are completely offline, no network¹ ...)

Segment size: 20MB
Index size: 10MB
5 GB worth records written in 37.667706s
5 GB worth cold records read in 1.384433s
5 GB worth warm records read in 1.266053s

Per-segment²:

  • ~130 MB/s on write
  • ~3.7 GB/s on cold read (while loading into memory pages)
  • ~4.2 GB/s on warm read (already loaded into memory pages)

Notes:

  • ¹ - Offline - no network overhead taken into account, network will be a big player on the overhead. However, the focus now is storage.
  • ² - Per-segment performance, in a comparinson with kinesis/kafka that would be the per-shard value. If you were to have 10 shards, you could achieve 10x that, limited by external factors, HD/CPU/...

Setup:

OS: macOS Mojave 10.14.4 (18E226)
CPU: 2,5 GHz Intel Core i7
RAM: 16 GB 2133 MHz LPDDR3
HD: 256 GB SSD Storage

Roadmap

  • Settle for a name
  • Storage
    • Core CommitLog
      • Segments
        • Write to segments
        • Write across segments
        • Read from segment
        • Read across segments
          • Index
          • Better API for reading
        • Extract logfile from segment
        • Memory Mapped IO (Performance test before/after)
          • Write
            • Index
            • Segment
            • Refactor segment split to take index into account
          • Read
            • Index
            • Segment
        • Restore state from disk - Ref: (https://github.com/zowens/commitlog/blob/master/src/file_set.rs#L17-L98)
      • Record Struct
        • End-to-end use of records
    • Topics/Streams (probably should come up with a better name)
      • Partitions/Shards
        • ?
  • Networking/API
    • Decide simple inicial protocol
    • Implement Basic Socket Communication
    • Implement TCP/HTTP?
    • Write to streams over the network
    • zero-cost copy (OS sendfile)
  • Replication
    • More to come here...
  • Operational
    • Configuration
      • CLI Basics
  • CI/Tooling
    • Setup Travis
      • Build with Linux/macOS
    • Docker
      • Alpine
  • Testing
    • Benchmarks
      • Write
      • Read
    • Setup Integration Tests
    • Abstract Test Code
  • Documentation
    • Document decisions
      • Document data structures / types
    • Improve README
      • How to Run

Commands

Available make commands

  • make build - Builds the application with cargo
  • make build_release - Builds the application with cargo, with release optimizations
  • make docker_test_watcher - Runs funzzy on linux over docker-compose
  • make docs - Generate the GitHub Markdown docs (At the moment only mermaid)
  • make format - Formats the code according to cargo
  • make help - Lists the available commands. Add a comment with '##' to describe a command.
  • make install - Builds a release version and installs to your cago bin path
  • make run - Runs the newly built
  • make test - Tests all features

References

About

♒︎ An experiment on persistent data-streaming

Resources

Stars

0 stars

Watchers

1 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

Repository files navigation

An experiment on persistent data-streaming

Status

Currently, working in the foundation of the storage layer.

Checkout the Roadmap for feature-specific details.

Project Goals

  • Learn and have fun™️
  • Implement a Kinesis-like streaming-service
  • Single binary
  • Easy to Host, Run & Operate (have you tried to run Kafka yourself?)
    • Kubernetes friendly

Internals

CommitLog

The main component of the whole system is the commit-log, an abstraction manages reads and writes to the log by implementing an immutable, append-only, file-backed sequence of "records", or chunks of data/events that are transmited from producers to consumers.

Records can be written to the log, always appending the last record over and over.

e.g.:

 current cursor
segment 0 ^
|-------------------------------|
| record 0 | record 1 | ... | --> time
|-------------------------------|

In order to manage and scale read and writes, the commit-log split groups of records into Segments, managing to write to a single segment until it reaches a certain, specified size.

Each time a record is written, the segment is trusted to have enough space for the given buffer, then the record is written to the current segment, and the pointer is updated.

More info in the commit_log/src/lib.rs file.

Segment

A Segment is a tuple abstraction to manage the Index and Log files.

Every Segment is composed of a log-file and an index, e.g.:

00000000000011812312.log
00000000000011812312.idx

The role of the segment is to manage writes to the logfile and ensure the entries can be read later on by doing lookups in the index.

On every write, the segment writes an entry to the index with the record's position and size, in the log-file, for later use.

The segment also manages the size of the log file, preventing it from being written once it reaches the specified.

When a segment is full, the commit log makes sure to rotate to a new one, closing the old one.

See how it looks like on disk (on a high-level):

 current cursor
segment 0 ^
|-------------------------------| |
| record 0 | record 1 | ... | segment 1 (current) |
|-------------------------------|-----------------------------| --> time
| record 2 | record 3 | ... |
|-----------------------------|

Under the hood is a bit more complex, the management of writing to the file to disk is of the Segments', as well as managing the Index file.

More info in the commit_log/src/segment.rs and commit_log/src/segment/index.rs and log.rs files.

Log file

The log file is a varied-size sequence of bytes that is storing the content of the records produced by the producers. However, the log itself doesn't have any mechanism for recovery of such records. That's responsibility of the index.

Once initialized, the log-file is truncated to reach the desired value and reserve both memory and disk space, the same for the index.

 current cursor
^
|-------------------------------|
| record 0 | record 1 | ... |----> time
|-------------------------------|

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/log.rs file.

Index file

The role of the index is to provide pointers to records in the log file. Each entry of the index is 20 bytes long, 10 bytes are used for the offset address of the record in the log file, the other 10 bytes for the size of the record.

e.g.:

 current cursor
^
|-------------------------------|
| offset-size | offset-size |...|----> time
|-------------------------------|

There is no separator, it's position-based.

e.g.:

00000001000000000020
---------------------
offset | size
* 000000010 -> offset
* 000000020 -> size

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/index.rs file.

Performance

These are preliminar and poorly collected results, yet it looks interesting:

Storage (Tests are completely offline, no network¹ ...)

Segment size: 20MB
Index size: 10MB
5 GB worth records written in 37.667706s
5 GB worth cold records read in 1.384433s
5 GB worth warm records read in 1.266053s

Per-segment²:

  • ~130 MB/s on write
  • ~3.7 GB/s on cold read (while loading into memory pages)
  • ~4.2 GB/s on warm read (already loaded into memory pages)

Notes:

  • ¹ - Offline - no network overhead taken into account, network will be a big player on the overhead. However, the focus now is storage.
  • ² - Per-segment performance, in a comparinson with kinesis/kafka that would be the per-shard value. If you were to have 10 shards, you could achieve 10x that, limited by external factors, HD/CPU/...

Setup:

OS: macOS Mojave 10.14.4 (18E226)
CPU: 2,5 GHz Intel Core i7
RAM: 16 GB 2133 MHz LPDDR3
HD: 256 GB SSD Storage

Roadmap

  • Settle for a name
  • Storage
    • Core CommitLog
      • Segments
        • Write to segments
        • Write across segments
        • Read from segment
        • Read across segments
          • Index
          • Better API for reading
        • Extract logfile from segment
        • Memory Mapped IO (Performance test before/after)
          • Write
            • Index
            • Segment
            • Refactor segment split to take index into account
          • Read
            • Index
            • Segment
        • Restore state from disk - Ref: (https://github.com/zowens/commitlog/blob/master/src/file_set.rs#L17-L98)
      • Record Struct
        • End-to-end use of records
    • Topics/Streams (probably should come up with a better name)
      • Partitions/Shards
        • ?
  • Networking/API
    • Decide simple inicial protocol
    • Implement Basic Socket Communication
    • Implement TCP/HTTP?
    • Write to streams over the network
    • zero-cost copy (OS sendfile)
  • Replication
    • More to come here...
  • Operational
    • Configuration
      • CLI Basics
  • CI/Tooling
    • Setup Travis
      • Build with Linux/macOS
    • Docker
      • Alpine
  • Testing
    • Benchmarks
      • Write
      • Read
    • Setup Integration Tests
    • Abstract Test Code
  • Documentation
    • Document decisions
      • Document data structures / types
    • Improve README
      • How to Run

Commands

Available make commands

  • make build - Builds the application with cargo
  • make build_release - Builds the application with cargo, with release optimizations
  • make docker_test_watcher - Runs funzzy on linux over docker-compose
  • make docs - Generate the GitHub Markdown docs (At the moment only mermaid)
  • make format - Formats the code according to cargo
  • make help - Lists the available commands. Add a comment with '##' to describe a command.
  • make install - Builds a release version and installs to your cago bin path
  • make run - Runs the newly built
  • make test - Tests all features

References

About

♒︎ An experiment on persistent data-streaming

Resources

Stars

0 stars

Watchers

1 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

Repository files navigation

An experiment on persistent data-streaming

Status

Currently, working in the foundation of the storage layer.

Checkout the Roadmap for feature-specific details.

Project Goals

  • Learn and have fun™️
  • Implement a Kinesis-like streaming-service
  • Single binary
  • Easy to Host, Run & Operate (have you tried to run Kafka yourself?)
    • Kubernetes friendly

Internals

CommitLog

The main component of the whole system is the commit-log, an abstraction manages reads and writes to the log by implementing an immutable, append-only, file-backed sequence of "records", or chunks of data/events that are transmited from producers to consumers.

Records can be written to the log, always appending the last record over and over.

e.g.:

 current cursor
segment 0 ^
|-------------------------------|
| record 0 | record 1 | ... | --> time
|-------------------------------|

In order to manage and scale read and writes, the commit-log split groups of records into Segments, managing to write to a single segment until it reaches a certain, specified size.

Each time a record is written, the segment is trusted to have enough space for the given buffer, then the record is written to the current segment, and the pointer is updated.

More info in the commit_log/src/lib.rs file.

Segment

A Segment is a tuple abstraction to manage the Index and Log files.

Every Segment is composed of a log-file and an index, e.g.:

00000000000011812312.log
00000000000011812312.idx

The role of the segment is to manage writes to the logfile and ensure the entries can be read later on by doing lookups in the index.

On every write, the segment writes an entry to the index with the record's position and size, in the log-file, for later use.

The segment also manages the size of the log file, preventing it from being written once it reaches the specified.

When a segment is full, the commit log makes sure to rotate to a new one, closing the old one.

See how it looks like on disk (on a high-level):

 current cursor
segment 0 ^
|-------------------------------| |
| record 0 | record 1 | ... | segment 1 (current) |
|-------------------------------|-----------------------------| --> time
| record 2 | record 3 | ... |
|-----------------------------|

Under the hood is a bit more complex, the management of writing to the file to disk is of the Segments', as well as managing the Index file.

More info in the commit_log/src/segment.rs and commit_log/src/segment/index.rs and log.rs files.

Log file

The log file is a varied-size sequence of bytes that is storing the content of the records produced by the producers. However, the log itself doesn't have any mechanism for recovery of such records. That's responsibility of the index.

Once initialized, the log-file is truncated to reach the desired value and reserve both memory and disk space, the same for the index.

 current cursor
^
|-------------------------------|
| record 0 | record 1 | ... |----> time
|-------------------------------|

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/log.rs file.

Index file

The role of the index is to provide pointers to records in the log file. Each entry of the index is 20 bytes long, 10 bytes are used for the offset address of the record in the log file, the other 10 bytes for the size of the record.

e.g.:

 current cursor
^
|-------------------------------|
| offset-size | offset-size |...|----> time
|-------------------------------|

There is no separator, it's position-based.

e.g.:

00000001000000000020
---------------------
offset | size
* 000000010 -> offset
* 000000020 -> size

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/index.rs file.

Performance

These are preliminar and poorly collected results, yet it looks interesting:

Storage (Tests are completely offline, no network¹ ...)

Segment size: 20MB
Index size: 10MB
5 GB worth records written in 37.667706s
5 GB worth cold records read in 1.384433s
5 GB worth warm records read in 1.266053s

Per-segment²:

  • ~130 MB/s on write
  • ~3.7 GB/s on cold read (while loading into memory pages)
  • ~4.2 GB/s on warm read (already loaded into memory pages)

Notes:

  • ¹ - Offline - no network overhead taken into account, network will be a big player on the overhead. However, the focus now is storage.
  • ² - Per-segment performance, in a comparinson with kinesis/kafka that would be the per-shard value. If you were to have 10 shards, you could achieve 10x that, limited by external factors, HD/CPU/...

Setup:

OS: macOS Mojave 10.14.4 (18E226)
CPU: 2,5 GHz Intel Core i7
RAM: 16 GB 2133 MHz LPDDR3
HD: 256 GB SSD Storage

Roadmap

  • Settle for a name
  • Storage
    • Core CommitLog
      • Segments
        • Write to segments
        • Write across segments
        • Read from segment
        • Read across segments
          • Index
          • Better API for reading
        • Extract logfile from segment
        • Memory Mapped IO (Performance test before/after)
          • Write
            • Index
            • Segment
            • Refactor segment split to take index into account
          • Read
            • Index
            • Segment
        • Restore state from disk - Ref: (https://github.com/zowens/commitlog/blob/master/src/file_set.rs#L17-L98)
      • Record Struct
        • End-to-end use of records
    • Topics/Streams (probably should come up with a better name)
      • Partitions/Shards
        • ?
  • Networking/API
    • Decide simple inicial protocol
    • Implement Basic Socket Communication
    • Implement TCP/HTTP?
    • Write to streams over the network
    • zero-cost copy (OS sendfile)
  • Replication
    • More to come here...
  • Operational
    • Configuration
      • CLI Basics
  • CI/Tooling
    • Setup Travis
      • Build with Linux/macOS
    • Docker
      • Alpine
  • Testing
    • Benchmarks
      • Write
      • Read
    • Setup Integration Tests
    • Abstract Test Code
  • Documentation
    • Document decisions
      • Document data structures / types
    • Improve README
      • How to Run

Commands

Available make commands

  • make build - Builds the application with cargo
  • make build_release - Builds the application with cargo, with release optimizations
  • make docker_test_watcher - Runs funzzy on linux over docker-compose
  • make docs - Generate the GitHub Markdown docs (At the moment only mermaid)
  • make format - Formats the code according to cargo
  • make help - Lists the available commands. Add a comment with '##' to describe a command.
  • make install - Builds a release version and installs to your cago bin path
  • make run - Runs the newly built
  • make test - Tests all features

References

About

♒︎ An experiment on persistent data-streaming

Resources

Stars

0 stars

Watchers

1 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

Repository files navigation

An experiment on persistent data-streaming

Status

Currently, working in the foundation of the storage layer.

Checkout the Roadmap for feature-specific details.

Project Goals

  • Learn and have fun™️
  • Implement a Kinesis-like streaming-service
  • Single binary
  • Easy to Host, Run & Operate (have you tried to run Kafka yourself?)
    • Kubernetes friendly

Internals

CommitLog

The main component of the whole system is the commit-log, an abstraction manages reads and writes to the log by implementing an immutable, append-only, file-backed sequence of "records", or chunks of data/events that are transmited from producers to consumers.

Records can be written to the log, always appending the last record over and over.

e.g.:

 current cursor
segment 0 ^
|-------------------------------|
| record 0 | record 1 | ... | --> time
|-------------------------------|

In order to manage and scale read and writes, the commit-log split groups of records into Segments, managing to write to a single segment until it reaches a certain, specified size.

Each time a record is written, the segment is trusted to have enough space for the given buffer, then the record is written to the current segment, and the pointer is updated.

More info in the commit_log/src/lib.rs file.

Segment

A Segment is a tuple abstraction to manage the Index and Log files.

Every Segment is composed of a log-file and an index, e.g.:

00000000000011812312.log
00000000000011812312.idx

The role of the segment is to manage writes to the logfile and ensure the entries can be read later on by doing lookups in the index.

On every write, the segment writes an entry to the index with the record's position and size, in the log-file, for later use.

The segment also manages the size of the log file, preventing it from being written once it reaches the specified.

When a segment is full, the commit log makes sure to rotate to a new one, closing the old one.

See how it looks like on disk (on a high-level):

 current cursor
segment 0 ^
|-------------------------------| |
| record 0 | record 1 | ... | segment 1 (current) |
|-------------------------------|-----------------------------| --> time
| record 2 | record 3 | ... |
|-----------------------------|

Under the hood is a bit more complex, the management of writing to the file to disk is of the Segments', as well as managing the Index file.

More info in the commit_log/src/segment.rs and commit_log/src/segment/index.rs and log.rs files.

Log file

The log file is a varied-size sequence of bytes that is storing the content of the records produced by the producers. However, the log itself doesn't have any mechanism for recovery of such records. That's responsibility of the index.

Once initialized, the log-file is truncated to reach the desired value and reserve both memory and disk space, the same for the index.

 current cursor
^
|-------------------------------|
| record 0 | record 1 | ... |----> time
|-------------------------------|

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/log.rs file.

Index file

The role of the index is to provide pointers to records in the log file. Each entry of the index is 20 bytes long, 10 bytes are used for the offset address of the record in the log file, the other 10 bytes for the size of the record.

e.g.:

 current cursor
^
|-------------------------------|
| offset-size | offset-size |...|----> time
|-------------------------------|

There is no separator, it's position-based.

e.g.:

00000001000000000020
---------------------
offset | size
* 000000010 -> offset
* 000000020 -> size

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/index.rs file.

Performance

These are preliminar and poorly collected results, yet it looks interesting:

Storage (Tests are completely offline, no network¹ ...)

Segment size: 20MB
Index size: 10MB
5 GB worth records written in 37.667706s
5 GB worth cold records read in 1.384433s
5 GB worth warm records read in 1.266053s

Per-segment²:

  • ~130 MB/s on write
  • ~3.7 GB/s on cold read (while loading into memory pages)
  • ~4.2 GB/s on warm read (already loaded into memory pages)

Notes:

  • ¹ - Offline - no network overhead taken into account, network will be a big player on the overhead. However, the focus now is storage.
  • ² - Per-segment performance, in a comparinson with kinesis/kafka that would be the per-shard value. If you were to have 10 shards, you could achieve 10x that, limited by external factors, HD/CPU/...

Setup:

OS: macOS Mojave 10.14.4 (18E226)
CPU: 2,5 GHz Intel Core i7
RAM: 16 GB 2133 MHz LPDDR3
HD: 256 GB SSD Storage

Roadmap

  • Settle for a name
  • Storage
    • Core CommitLog
      • Segments
        • Write to segments
        • Write across segments
        • Read from segment
        • Read across segments
          • Index
          • Better API for reading
        • Extract logfile from segment
        • Memory Mapped IO (Performance test before/after)
          • Write
            • Index
            • Segment
            • Refactor segment split to take index into account
          • Read
            • Index
            • Segment
        • Restore state from disk - Ref: (https://github.com/zowens/commitlog/blob/master/src/file_set.rs#L17-L98)
      • Record Struct
        • End-to-end use of records
    • Topics/Streams (probably should come up with a better name)
      • Partitions/Shards
        • ?
  • Networking/API
    • Decide simple inicial protocol
    • Implement Basic Socket Communication
    • Implement TCP/HTTP?
    • Write to streams over the network
    • zero-cost copy (OS sendfile)
  • Replication
    • More to come here...
  • Operational
    • Configuration
      • CLI Basics
  • CI/Tooling
    • Setup Travis
      • Build with Linux/macOS
    • Docker
      • Alpine
  • Testing
    • Benchmarks
      • Write
      • Read
    • Setup Integration Tests
    • Abstract Test Code
  • Documentation
    • Document decisions
      • Document data structures / types
    • Improve README
      • How to Run

Commands

Available make commands

  • make build - Builds the application with cargo
  • make build_release - Builds the application with cargo, with release optimizations
  • make docker_test_watcher - Runs funzzy on linux over docker-compose
  • make docs - Generate the GitHub Markdown docs (At the moment only mermaid)
  • make format - Formats the code according to cargo
  • make help - Lists the available commands. Add a comment with '##' to describe a command.
  • make install - Builds a release version and installs to your cago bin path
  • make run - Runs the newly built
  • make test - Tests all features

References

About

♒︎ An experiment on persistent data-streaming

Resources

Stars

0 stars

Watchers

1 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

Repository files navigation

An experiment on persistent data-streaming

Status

Currently, working in the foundation of the storage layer.

Checkout the Roadmap for feature-specific details.

Project Goals

  • Learn and have fun™️
  • Implement a Kinesis-like streaming-service
  • Single binary
  • Easy to Host, Run & Operate (have you tried to run Kafka yourself?)
    • Kubernetes friendly

Internals

CommitLog

The main component of the whole system is the commit-log, an abstraction manages reads and writes to the log by implementing an immutable, append-only, file-backed sequence of "records", or chunks of data/events that are transmited from producers to consumers.

Records can be written to the log, always appending the last record over and over.

e.g.:

 current cursor
segment 0 ^
|-------------------------------|
| record 0 | record 1 | ... | --> time
|-------------------------------|

In order to manage and scale read and writes, the commit-log split groups of records into Segments, managing to write to a single segment until it reaches a certain, specified size.

Each time a record is written, the segment is trusted to have enough space for the given buffer, then the record is written to the current segment, and the pointer is updated.

More info in the commit_log/src/lib.rs file.

Segment

A Segment is a tuple abstraction to manage the Index and Log files.

Every Segment is composed of a log-file and an index, e.g.:

00000000000011812312.log
00000000000011812312.idx

The role of the segment is to manage writes to the logfile and ensure the entries can be read later on by doing lookups in the index.

On every write, the segment writes an entry to the index with the record's position and size, in the log-file, for later use.

The segment also manages the size of the log file, preventing it from being written once it reaches the specified.

When a segment is full, the commit log makes sure to rotate to a new one, closing the old one.

See how it looks like on disk (on a high-level):

 current cursor
segment 0 ^
|-------------------------------| |
| record 0 | record 1 | ... | segment 1 (current) |
|-------------------------------|-----------------------------| --> time
| record 2 | record 3 | ... |
|-----------------------------|

Under the hood is a bit more complex, the management of writing to the file to disk is of the Segments', as well as managing the Index file.

More info in the commit_log/src/segment.rs and commit_log/src/segment/index.rs and log.rs files.

Log file

The log file is a varied-size sequence of bytes that is storing the content of the records produced by the producers. However, the log itself doesn't have any mechanism for recovery of such records. That's responsibility of the index.

Once initialized, the log-file is truncated to reach the desired value and reserve both memory and disk space, the same for the index.

 current cursor
^
|-------------------------------|
| record 0 | record 1 | ... |----> time
|-------------------------------|

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/log.rs file.

Index file

The role of the index is to provide pointers to records in the log file. Each entry of the index is 20 bytes long, 10 bytes are used for the offset address of the record in the log file, the other 10 bytes for the size of the record.

e.g.:

 current cursor
^
|-------------------------------|
| offset-size | offset-size |...|----> time
|-------------------------------|

There is no separator, it's position-based.

e.g.:

00000001000000000020
---------------------
offset | size
* 000000010 -> offset
* 000000020 -> size

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/index.rs file.

Performance

These are preliminar and poorly collected results, yet it looks interesting:

Storage (Tests are completely offline, no network¹ ...)

Segment size: 20MB
Index size: 10MB
5 GB worth records written in 37.667706s
5 GB worth cold records read in 1.384433s
5 GB worth warm records read in 1.266053s

Per-segment²:

  • ~130 MB/s on write
  • ~3.7 GB/s on cold read (while loading into memory pages)
  • ~4.2 GB/s on warm read (already loaded into memory pages)

Notes:

  • ¹ - Offline - no network overhead taken into account, network will be a big player on the overhead. However, the focus now is storage.
  • ² - Per-segment performance, in a comparinson with kinesis/kafka that would be the per-shard value. If you were to have 10 shards, you could achieve 10x that, limited by external factors, HD/CPU/...

Setup:

OS: macOS Mojave 10.14.4 (18E226)
CPU: 2,5 GHz Intel Core i7
RAM: 16 GB 2133 MHz LPDDR3
HD: 256 GB SSD Storage

Roadmap

  • Settle for a name
  • Storage
    • Core CommitLog
      • Segments
        • Write to segments
        • Write across segments
        • Read from segment
        • Read across segments
          • Index
          • Better API for reading
        • Extract logfile from segment
        • Memory Mapped IO (Performance test before/after)
          • Write
            • Index
            • Segment
            • Refactor segment split to take index into account
          • Read
            • Index
            • Segment
        • Restore state from disk - Ref: (https://github.com/zowens/commitlog/blob/master/src/file_set.rs#L17-L98)
      • Record Struct
        • End-to-end use of records
    • Topics/Streams (probably should come up with a better name)
      • Partitions/Shards
        • ?
  • Networking/API
    • Decide simple inicial protocol
    • Implement Basic Socket Communication
    • Implement TCP/HTTP?
    • Write to streams over the network
    • zero-cost copy (OS sendfile)
  • Replication
    • More to come here...
  • Operational
    • Configuration
      • CLI Basics
  • CI/Tooling
    • Setup Travis
      • Build with Linux/macOS
    • Docker
      • Alpine
  • Testing
    • Benchmarks
      • Write
      • Read
    • Setup Integration Tests
    • Abstract Test Code
  • Documentation
    • Document decisions
      • Document data structures / types
    • Improve README
      • How to Run

Commands

Available make commands

  • make build - Builds the application with cargo
  • make build_release - Builds the application with cargo, with release optimizations
  • make docker_test_watcher - Runs funzzy on linux over docker-compose
  • make docs - Generate the GitHub Markdown docs (At the moment only mermaid)
  • make format - Formats the code according to cargo
  • make help - Lists the available commands. Add a comment with '##' to describe a command.
  • make install - Builds a release version and installs to your cago bin path
  • make run - Runs the newly built
  • make test - Tests all features

References

About

♒︎ An experiment on persistent data-streaming

Resources

Stars

0 stars

Watchers

1 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

Repository files navigation

An experiment on persistent data-streaming

Status

Currently, working in the foundation of the storage layer.

Checkout the Roadmap for feature-specific details.

Project Goals

  • Learn and have fun™️
  • Implement a Kinesis-like streaming-service
  • Single binary
  • Easy to Host, Run & Operate (have you tried to run Kafka yourself?)
    • Kubernetes friendly

Internals

CommitLog

The main component of the whole system is the commit-log, an abstraction manages reads and writes to the log by implementing an immutable, append-only, file-backed sequence of "records", or chunks of data/events that are transmited from producers to consumers.

Records can be written to the log, always appending the last record over and over.

e.g.:

 current cursor
segment 0 ^
|-------------------------------|
| record 0 | record 1 | ... | --> time
|-------------------------------|

In order to manage and scale read and writes, the commit-log split groups of records into Segments, managing to write to a single segment until it reaches a certain, specified size.

Each time a record is written, the segment is trusted to have enough space for the given buffer, then the record is written to the current segment, and the pointer is updated.

More info in the commit_log/src/lib.rs file.

Segment

A Segment is a tuple abstraction to manage the Index and Log files.

Every Segment is composed of a log-file and an index, e.g.:

00000000000011812312.log
00000000000011812312.idx

The role of the segment is to manage writes to the logfile and ensure the entries can be read later on by doing lookups in the index.

On every write, the segment writes an entry to the index with the record's position and size, in the log-file, for later use.

The segment also manages the size of the log file, preventing it from being written once it reaches the specified.

When a segment is full, the commit log makes sure to rotate to a new one, closing the old one.

See how it looks like on disk (on a high-level):

 current cursor
segment 0 ^
|-------------------------------| |
| record 0 | record 1 | ... | segment 1 (current) |
|-------------------------------|-----------------------------| --> time
| record 2 | record 3 | ... |
|-----------------------------|

Under the hood is a bit more complex, the management of writing to the file to disk is of the Segments', as well as managing the Index file.

More info in the commit_log/src/segment.rs and commit_log/src/segment/index.rs and log.rs files.

Log file

The log file is a varied-size sequence of bytes that is storing the content of the records produced by the producers. However, the log itself doesn't have any mechanism for recovery of such records. That's responsibility of the index.

Once initialized, the log-file is truncated to reach the desired value and reserve both memory and disk space, the same for the index.

 current cursor
^
|-------------------------------|
| record 0 | record 1 | ... |----> time
|-------------------------------|

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/log.rs file.

Index file

The role of the index is to provide pointers to records in the log file. Each entry of the index is 20 bytes long, 10 bytes are used for the offset address of the record in the log file, the other 10 bytes for the size of the record.

e.g.:

 current cursor
^
|-------------------------------|
| offset-size | offset-size |...|----> time
|-------------------------------|

There is no separator, it's position-based.

e.g.:

00000001000000000020
---------------------
offset | size
* 000000010 -> offset
* 000000020 -> size

Neither reads nor writes to the index are directly triggering disk-level actions.

Both operations are being intermediated by a memory-mapping buffers, managed by the OS.

More info in the commit_log/src/segment/index.rs file.

Performance

These are preliminar and poorly collected results, yet it looks interesting:

Storage (Tests are completely offline, no network¹ ...)

Segment size: 20MB
Index size: 10MB
5 GB worth records written in 37.667706s
5 GB worth cold records read in 1.384433s
5 GB worth warm records read in 1.266053s

Per-segment²:

  • ~130 MB/s on write
  • ~3.7 GB/s on cold read (while loading into memory pages)
  • ~4.2 GB/s on warm read (already loaded into memory pages)

Notes:

  • ¹ - Offline - no network overhead taken into account, network will be a big player on the overhead. However, the focus now is storage.
  • ² - Per-segment performance, in a comparinson with kinesis/kafka that would be the per-shard value. If you were to have 10 shards, you could achieve 10x that, limited by external factors, HD/CPU/...

Setup:

OS: macOS Mojave 10.14.4 (18E226)
CPU: 2,5 GHz Intel Core i7
RAM: 16 GB 2133 MHz LPDDR3
HD: 256 GB SSD Storage

Roadmap

  • Settle for a name
  • Storage
    • Core CommitLog
      • Segments
        • Write to segments
        • Write across segments
        • Read from segment
        • Read across segments
          • Index
          • Better API for reading
        • Extract logfile from segment
        • Memory Mapped IO (Performance test before/after)
          • Write
            • Index
            • Segment
            • Refactor segment split to take index into account
          • Read
            • Index
            • Segment
        • Restore state from disk - Ref: (https://github.com/zowens/commitlog/blob/master/src/file_set.rs#L17-L98)
      • Record Struct
        • End-to-end use of records
    • Topics/Streams (probably should come up with a better name)
      • Partitions/Shards
        • ?
  • Networking/API
    • Decide simple inicial protocol
    • Implement Basic Socket Communication
    • Implement TCP/HTTP?
    • Write to streams over the network
    • zero-cost copy (OS sendfile)
  • Replication
    • More to come here...
  • Operational
    • Configuration
      • CLI Basics
  • CI/Tooling
    • Setup Travis
      • Build with Linux/macOS
    • Docker
      • Alpine
  • Testing
    • Benchmarks
      • Write
      • Read
    • Setup Integration Tests
    • Abstract Test Code
  • Documentation
    • Document decisions
      • Document data structures / types
    • Improve README
      • How to Run

Commands

Available make commands

  • make build - Builds the application with cargo
  • make build_release - Builds the application with cargo, with release optimizations
  • make docker_test_watcher - Runs funzzy on linux over docker-compose
  • make docs - Generate the GitHub Markdown docs (At the moment only mermaid)
  • make format - Formats the code according to cargo
  • make help - Lists the available commands. Add a comment with '##' to describe a command.
  • make install - Builds a release version and installs to your cago bin path
  • make run - Runs the newly built
  • make test - Tests all features

References

About

♒︎ An experiment on persistent data-streaming

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages