diff --git a/docs/coordinator.excalidraw b/docs/coordinator.excalidraw new file mode 100644 index 0000000..692c3ae --- /dev/null +++ b/docs/coordinator.excalidraw @@ -0,0 +1,622 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 498, + "versionNonce": 987480120, + "isDeleted": false, + "id": "jPwIU_a9_nxvuDFAcbzxM", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 712.375, + "y": 510.2500000000001, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "width": 307, + "height": 30, + "seed": 1029717964, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "U2-I9a2X4amHnB7NZFWGv" + }, + { + "id": "MJoeQ6ylkFi5Z7UCzD-r-", + "type": "arrow" + }, + { + "id": "KpIRIBeGsT3yzCPp6jbEN", + "type": "arrow" + }, + { + "id": "Qnatw_Uix7cMFwAuW1DkJ", + "type": "arrow" + }, + { + "id": "TLS6mZEI7BXyUdiiYHdrg", + "type": "arrow" + }, + { + "id": "h_hyKP8N7nmD1NiZNa3ez", + "type": "arrow" + }, + { + "id": "CrT6zZ8CKm_MSDw-CmcPG", + "type": "arrow" + } + ], + "updated": 1660733356396, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 389, + "versionNonce": 1321365816, + "isDeleted": false, + "id": "U2-I9a2X4amHnB7NZFWGv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 717.375, + "y": 515.2500000000001, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 297, + "height": 20, + "seed": 1592449524, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660733269433, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "coordinator", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "jPwIU_a9_nxvuDFAcbzxM", + "originalText": "coordinator" + }, + { + "type": "rectangle", + "version": 469, + "versionNonce": 684925752, + "isDeleted": false, + "id": "BY5OdEEKT0Y_DTy9Zgr9C", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 714.375, + "y": 217.41666666666669, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 77, + "height": 192, + "seed": 1621471436, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "MJoeQ6ylkFi5Z7UCzD-r-", + "type": "arrow" + }, + { + "id": "KpIRIBeGsT3yzCPp6jbEN", + "type": "arrow" + } + ], + "updated": 1660733316757, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 113, + "versionNonce": 2140069448, + "isDeleted": false, + "id": "45U617mr0L9ob4mc7Xozt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 724.875, + "y": 171.0865384615385, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 56, + "height": 40, + "seed": 1285924468, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660733195706, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "shard 1\n", + "baseline": 34, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "shard 1\n" + }, + { + "type": "text", + "version": 123, + "versionNonce": 738921016, + "isDeleted": false, + "id": "vY-LnNlhD3qWMEtRPoU0t", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 840.4375, + "y": 171.0865384615385, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 64, + "height": 20, + "seed": 817296972, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660733195706, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "shard 2", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "shard 2" + }, + { + "type": "rectangle", + "version": 499, + "versionNonce": 1256651064, + "isDeleted": false, + "id": "xvkm28eoejETjF3M78jpN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 943.125, + "y": 221.875, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 77, + "height": 187, + "seed": 1482008524, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "h_hyKP8N7nmD1NiZNa3ez", + "type": "arrow" + }, + { + "id": "CrT6zZ8CKm_MSDw-CmcPG", + "type": "arrow" + } + ], + "updated": 1660733356396, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 193, + "versionNonce": 731710264, + "isDeleted": false, + "id": "H72xWL9unzb1mQiLvx7L4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 950.125, + "y": 176.7115384615385, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 63, + "height": 20, + "seed": 1704611020, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1660733195706, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "shard 3", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "shard 3" + }, + { + "type": "rectangle", + "version": 547, + "versionNonce": 1963108408, + "isDeleted": false, + "id": "jj-MVcNrzcH0DbFFo9noF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 833.9375, + "y": 221.16666666666669, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 77, + "height": 193, + "seed": 1374694167, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "Qnatw_Uix7cMFwAuW1DkJ", + "type": "arrow" + }, + { + "id": "TLS6mZEI7BXyUdiiYHdrg", + "type": "arrow" + } + ], + "updated": 1660733333008, + "link": null, + "locked": false + }, + { + "id": "MJoeQ6ylkFi5Z7UCzD-r-", + "type": "arrow", + "x": 717.875, + "y": 501.1682692307693, + "width": 24, + "height": 87, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 6593352, + "version": 99, + "versionNonce": 1021163848, + "isDeleted": false, + "boundElements": null, + "updated": 1660733308793, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -24, + -44 + ], + [ + -3, + -87 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "jPwIU_a9_nxvuDFAcbzxM", + "focus": -0.8341352911917994, + "gap": 9.08173076923083 + }, + "endBinding": { + "elementId": "BY5OdEEKT0Y_DTy9Zgr9C", + "focus": -0.13122256675640864, + "gap": 4.751602564102598 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "KpIRIBeGsT3yzCPp6jbEN", + "type": "arrow", + "x": 752.875, + "y": 419.1682692307693, + "width": 16, + "height": 90, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1407934264, + "version": 74, + "versionNonce": 1205666632, + "isDeleted": false, + "boundElements": null, + "updated": 1660733316764, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + 42 + ], + [ + -9, + 90 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "BY5OdEEKT0Y_DTy9Zgr9C", + "focus": 0.3233993962204972, + "gap": 9.751602564102598 + }, + "endBinding": { + "elementId": "jPwIU_a9_nxvuDFAcbzxM", + "focus": -0.8035367629216211, + "gap": 1.0817307692308304 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "Qnatw_Uix7cMFwAuW1DkJ", + "type": "arrow", + "x": 837.875, + "y": 506.1682692307693, + "width": 7, + "height": 83, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1927132472, + "version": 74, + "versionNonce": 1840565576, + "isDeleted": false, + "boundElements": null, + "updated": 1660733325799, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 7, + -83 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "jPwIU_a9_nxvuDFAcbzxM", + "focus": -0.191317746711659, + "gap": 4.0817307692308304 + }, + "endBinding": { + "elementId": "jj-MVcNrzcH0DbFFo9noF", + "focus": 0.4002005378587657, + "gap": 9.001602564102598 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "TLS6mZEI7BXyUdiiYHdrg", + "type": "arrow", + "x": 872.875, + "y": 423.1682692307693, + "width": 13, + "height": 82, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 247434040, + "version": 76, + "versionNonce": 1827860040, + "isDeleted": false, + "boundElements": null, + "updated": 1660733333013, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 9, + 41 + ], + [ + -4, + 82 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "jj-MVcNrzcH0DbFFo9noF", + "focus": 0.38070164408537926, + "gap": 9.001602564102598 + }, + "endBinding": { + "elementId": "jPwIU_a9_nxvuDFAcbzxM", + "focus": -0.02127803036140877, + "gap": 5.0817307692308304 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "h_hyKP8N7nmD1NiZNa3ez", + "type": "arrow", + "x": 995.875, + "y": 418.1682692307693, + "width": 13, + "height": 90, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 2138692424, + "version": 57, + "versionNonce": 178091592, + "isDeleted": false, + "boundElements": null, + "updated": 1660733348048, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 12, + 47 + ], + [ + -1, + 90 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "xvkm28eoejETjF3M78jpN", + "focus": 0.19231425235177602, + "gap": 9.293269230769283 + }, + "endBinding": { + "elementId": "jPwIU_a9_nxvuDFAcbzxM", + "focus": 0.7835976013538369, + "gap": 2.0817307692308304 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "CrT6zZ8CKm_MSDw-CmcPG", + "type": "arrow", + "x": 957.875, + "y": 502.1682692307693, + "width": 18, + "height": 91, + "angle": 0, + "strokeColor": "#000000", + "backgroundColor": "#15aabf", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "strokeSharpness": "round", + "seed": 1991558200, + "version": 58, + "versionNonce": 1980388936, + "isDeleted": false, + "boundElements": null, + "updated": 1660733356402, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -11, + -39 + ], + [ + 7, + -91 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "jPwIU_a9_nxvuDFAcbzxM", + "focus": 0.6245467021802061, + "gap": 8.08173076923083 + }, + "endBinding": { + "elementId": "xvkm28eoejETjF3M78jpN", + "focus": -0.23155463939046053, + "gap": 2.2932692307692832 + }, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} diff --git a/docs/df-share-nothing.md b/docs/df-share-nothing.md new file mode 100644 index 0000000..c4bd943 --- /dev/null +++ b/docs/df-share-nothing.md @@ -0,0 +1,91 @@ +# Dragonfly Architecture + +Dragonfly is a modern replacement for memory stores like Redis and Memcached. It scales vertically on a single instance to support millions of requests per second. It is more memory efficient, has been designed with reliability in mind, and includes a better caching design. + +## Threading model + +Dragonfly uses a single process with a multiple-thread architecture. Each Dragonfly thread is indirectly assigned several responsibilities via fibers. + +One such responsibility is handling incoming connections. Once a socket listener accepts a client connection, the connection spends its entire lifetime bound to a single thread inside a fiber. Dragonfly is written to be 100% non-blocking; it uses fibers to provide asynchronisity in each thread. One of the essential properties of asynchronisity is that a thread cannot be blocked as long as it has pending CPU tasks. Dragonfly preserves this property by wrapping each unit of execution context in a fiber; we wrap units of execution that can potentially be blocked on I/O. For example, a connection loop runs within a fiber; a function that writes a snapshot runs inside a fiber, and so on. + +As a side comment - asynchronicity and parallelism are different terms. Nodejs, for example, provides asynchronous execution but is single-threaded. Similarly, each Dragonfly thread is asynchronous on its own; therefore, Dragonfly is responsive to incoming events even when it handles long-running commands like saving to disk or running Lua scripts. + + +### Thread actors in DF + +The DF in-memory database is sharded into `N` parts, where `N` is less or equal to the number of threads in the system. Each database shard is owned and accessed by a single thread. +The same thread can handle TCP connections and simultaneously host a database shard. +See the diagram below. + + +
+ + +Here, our DF process spawns 4 threads, where threads 1 through 3 handle I/O (i.e., manage client connections) and threads 2 through 4 manage DB shards. Thread 2, for example, divides its CPU time between handling incoming requests and processing DB operations on the shard it owns. + +So when we say that thread 1 is an I/O thread, we mean that Dragonfly can pin fibers that manage client connections to thread 1. In general, any thread can have many responsibilities that require CPU time; database management and connection handling are only two of those responsibilities. + + +## Fibers + +I suggest reading my [intro post](https://www.romange.com/2018/12/15/introduction-to-fibers-in-c-/) about `Boost.Fibers` to learn more about fibers. + +By the way, I want to compliment `Boost.Fibers` library–it has been exceptionally well designed: +it's unintrusive, lightweight, and efficient. Moreover, its default scheduler can be overidden. In the case of `helio`, the I/O library that powers Dragonfly, we overrode the `Boost.Fibers` scheduler to support shared-nothing architecture and integrate it with the I/O polling loop. + +Importantly, fibers require bottom-up support in the application layer to preserve their asynchronisity. For example, in the snippet below, a blocking write into `fd` won't magically allow a fiber to preempt and switch to another fiber. No, the whole thread will be blocked. + + +```cpp +... +write(fd, buf, 1000000); + +... +pthread_mutex_lock(...); + +``` + +Similarly, with a `pthread_mutex_lock` call, the whole thread might be blocked, wasting precious CPU time.. Therefore, the Dragonfly code uses *fiber-friendly* primitives for I/O, communication, and coordination. These primitives are supplied by the `helio` and `Boost.Fibers` libraries. + +## Life of a command request + +This section explains how Dragonfly handles a command in the context of shared-nothing architecture. In most architectures used today, multi-threaded servers use mutex locks to protect their data structures, but Dragonfly does not. Why is this? + +Inter-thread interactions in Dragonfly occur only via passing messages from thread to thread. For example, consider the following sequence diagram of handling a SET request: + + +```uml +@startuml + +actor User as A1 +boundary connection as B1 +entity "Shard K" as E1 +A1 -> B1 : SET KEY VAL +B1 -> E1 : SET KEY VAL / k = HASH(KEY) % N +E1 -> B1 : OK +B1 -> A1 : Response + +@enduml +``` + + + +Here, a connection fiber resides in a thread different from one that handles the `KEY` entity. We use hashing to decide which shard owns which key. + +Another way to think of this flow is that a connection fiber serves as a coordinator for issuing transactional commands to other threads. In this simple example, the external "SET" command requires a single message passed from the coordinator to the destination shard thread. When we think of the Dragonfly model in the context of a single command request, I prefer to use the following diagram instead of the [one above](#thread-actors-in-df). + +
+ + +Here, a coordinator (or connection fiber) might even reside on one of the threads that coincidently owns one of the shards. However, it iseasier to think of it as a separate entity that never directly accesses any shard data. + +The coordinator serves as a virtualization layer that hides all the complexity of talking to multiple shards. It employs start-of-the-art algorithms to provide atomicity (and strict serializability) semantics for multi-key commands like "mset, mget, and blpop." It also offers strict serializability for Lua scripts and multi-command transactions. + +Hiding such complexity is valuable to the end customer, but it comes with some CPU and latency costs. We believe the trade-off is worthwhile given the value that Dragonfly provides. + +If you want to deep dive into Dragonfly architecture without the complexities of transactional code, it's worth checking [Midi Redis](https://github.com/romange/midi-redis/), +which implements a toy backend supporting `PING`, `SET`, and `GET` [commands](https://github.com/romange/midi-redis/blob/main/server/main_service.cc#L239). + +In fact, Dragonfly grew from that project; they share a common commit history. + +By the way, to learn how to build even simpler TCP backends than `midi-redis`, `helio` library provides sample backends like these: [echo_server](https://github.com/romange/helio/blob/master/examples/echo_server.cc) and [ping_iouring_server.cc](https://github.com/romange/helio/blob/master/examples/pingserver/ping_iouring_server.cc). These backends reach millions of QPS on multi-core servers much like Dragonfly and midi-redis do. diff --git a/docs/thread-per-core.excalidraw b/docs/thread-per-core.excalidraw new file mode 100644 index 0000000..3228495 --- /dev/null +++ b/docs/thread-per-core.excalidraw @@ -0,0 +1,579 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "version": 158, + "versionNonce": 1897755639, + "isDeleted": false, + "id": "N2nJ6OaFNRqcFW23SO0u2", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 714.625, + "y": 507.5390625000001, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 90, + "height": 20, + "seed": 1339600844, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676475959, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "I/O thread", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "I/O thread" + }, + { + "type": "text", + "version": 212, + "versionNonce": 1838113753, + "isDeleted": false, + "id": "pZs66qxoJlWQcWuBsvAxk", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 829.125, + "y": 509.4140625000001, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 90, + "height": 20, + "seed": 1172993740, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676475959, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "I/O thread", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "I/O thread" + }, + { + "type": "text", + "version": 223, + "versionNonce": 1421110391, + "isDeleted": false, + "id": "qhrDskacRkr-tNl2Q3atR", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 948.6875, + "y": 508.02455357142867, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 90, + "height": 20, + "seed": 1936794996, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676504307, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "I/O thread", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "I/O thread" + }, + { + "type": "rectangle", + "version": 344, + "versionNonce": 1641244985, + "isDeleted": false, + "id": "jPwIU_a9_nxvuDFAcbzxM", + "fillStyle": "cross-hatch", + "strokeWidth": 1, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 712.375, + "y": 537.2500000000001, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 431, + "height": 30, + "seed": 1029717964, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "U2-I9a2X4amHnB7NZFWGv" + } + ], + "updated": 1658676541606, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 239, + "versionNonce": 1717412567, + "isDeleted": false, + "id": "U2-I9a2X4amHnB7NZFWGv", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 717.375, + "y": 542.2500000000001, + "strokeColor": "#000000", + "backgroundColor": "transparent", + "width": 421, + "height": 20, + "seed": 1592449524, + "groupIds": [], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676541606, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "message bus", + "baseline": 14, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "jPwIU_a9_nxvuDFAcbzxM", + "originalText": "message bus" + }, + { + "type": "rectangle", + "version": 315, + "versionNonce": 208875257, + "isDeleted": false, + "id": "mBFE2wiT175ZxMSdmWcvQ", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 712.375, + "y": 305.7916666666667, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 77, + "height": 192, + "seed": 352036980, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "type": "text", + "id": "tK1EcrkpG35slJ07z1dTT" + } + ], + "updated": 1658676546251, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 194, + "versionNonce": 181803287, + "isDeleted": false, + "id": "tK1EcrkpG35slJ07z1dTT", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 717.375, + "y": 376.7916666666667, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 67, + "height": 50, + "seed": 1251432308, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676546251, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "thread\n1", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "mBFE2wiT175ZxMSdmWcvQ", + "originalText": "thread\n1" + }, + { + "type": "rectangle", + "version": 430, + "versionNonce": 1426120247, + "isDeleted": false, + "id": "BY5OdEEKT0Y_DTy9Zgr9C", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 833.375, + "y": 306.4166666666667, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 77, + "height": 192, + "seed": 1621471436, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "sIrssFTnnb9f1o26g1j88", + "type": "text" + }, + { + "type": "text", + "id": "sIrssFTnnb9f1o26g1j88" + } + ], + "updated": 1658676546251, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 310, + "versionNonce": 514622649, + "isDeleted": false, + "id": "sIrssFTnnb9f1o26g1j88", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 838.375, + "y": 377.4166666666667, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 67, + "height": 50, + "seed": 711168500, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676546251, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "thread\n2", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "BY5OdEEKT0Y_DTy9Zgr9C", + "originalText": "thread\n2" + }, + { + "type": "text", + "version": 76, + "versionNonce": 1406533463, + "isDeleted": false, + "id": "45U617mr0L9ob4mc7Xozt", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 845.375, + "y": 260.0865384615385, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 53, + "height": 40, + "seed": 1285924468, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676546251, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "shard\nthread", + "baseline": 34, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "shard\nthread" + }, + { + "type": "text", + "version": 85, + "versionNonce": 2081260953, + "isDeleted": false, + "id": "vY-LnNlhD3qWMEtRPoU0t", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 964.9375, + "y": 260.0865384615385, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 53, + "height": 40, + "seed": 817296972, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676546251, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "shard\nthread", + "baseline": 34, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "shard\nthread" + }, + { + "type": "rectangle", + "version": 458, + "versionNonce": 190540409, + "isDeleted": false, + "id": "xvkm28eoejETjF3M78jpN", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1062.125, + "y": 310.875, + "strokeColor": "#000000", + "backgroundColor": "#fa5252", + "width": 77, + "height": 187, + "seed": 1482008524, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "nSQOBHdmN0bLo5OeoOD0P", + "type": "text" + }, + { + "type": "text", + "id": "nSQOBHdmN0bLo5OeoOD0P" + } + ], + "updated": 1658676546251, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 337, + "versionNonce": 2051102103, + "isDeleted": false, + "id": "nSQOBHdmN0bLo5OeoOD0P", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1067.125, + "y": 379.375, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 67, + "height": 50, + "seed": 1058179828, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676546251, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "thread\n4", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "xvkm28eoejETjF3M78jpN", + "originalText": "thread\n4" + }, + { + "type": "text", + "version": 156, + "versionNonce": 1163506521, + "isDeleted": false, + "id": "H72xWL9unzb1mQiLvx7L4", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 1074.125, + "y": 265.7115384615385, + "strokeColor": "#000000", + "backgroundColor": "#fab005", + "width": 53, + "height": 40, + "seed": 1704611020, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676546251, + "link": null, + "locked": false, + "fontSize": 16, + "fontFamily": 1, + "text": "shard\nthread", + "baseline": 34, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "shard\nthread" + }, + { + "type": "rectangle", + "version": 510, + "versionNonce": 1046208569, + "isDeleted": false, + "id": "jj-MVcNrzcH0DbFFo9noF", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 952.9375, + "y": 310.1666666666667, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 77, + "height": 193, + "seed": 1374694167, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [ + { + "id": "NxhycN5eOsL0I52k0H-lh", + "type": "text" + }, + { + "id": "NxhycN5eOsL0I52k0H-lh", + "type": "text" + }, + { + "type": "text", + "id": "NxhycN5eOsL0I52k0H-lh" + } + ], + "updated": 1658676546251, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 391, + "versionNonce": 1308367831, + "isDeleted": false, + "id": "NxhycN5eOsL0I52k0H-lh", + "fillStyle": "hachure", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 957.9375, + "y": 381.6666666666667, + "strokeColor": "#000000", + "backgroundColor": "#fd7e14", + "width": 67, + "height": 50, + "seed": 617412057, + "groupIds": [ + "DYa5vdmfX68EvWPAq2Beo" + ], + "strokeSharpness": "sharp", + "boundElements": [], + "updated": 1658676546251, + "link": null, + "locked": false, + "fontSize": 20, + "fontFamily": 1, + "text": "thread\n3", + "baseline": 43, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "jj-MVcNrzcH0DbFFo9noF", + "originalText": "thread\n3" + } + ], + "appState": { + "gridSize": null, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +}