Skip to content

Commit 688dcf3

Browse files
authored
docs(architecture): update architecture docs (#154)
1 parent f39ea50 commit 688dcf3

File tree

5 files changed

+42
-67
lines changed

5 files changed

+42
-67
lines changed

docs/architecture.md

+15-6
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ Having outlined all the steps to execute a single roundtrip Graphsync request, t
6666
- minimize network traffic and not send duplicate data
6767
- not get blocked if any one request or response becomes blocked for whatever reason
6868
- have ways of protecting itself from getting overwhelmed by a malicious peer (i.e. be less vulnerable to Denial Of Service attacks)
69+
- manage memory effectively when executing large queries on either side
6970

7071
To do this, GraphSync maintains several independent threads of execution (i.e. goroutines). Specifically:
7172
- On the requestor side:
@@ -75,8 +76,7 @@ To do this, GraphSync maintains several independent threads of execution (i.e. g
7576
4. Each outgoing request has an independent thread collecting and buffering final responses before they are returned to the caller. Graphsync returns responses to the caller through a channel. If the caller fails to immediately read the response channel, this should not block other requests from being processed.
7677
- On the responder side:
7778
1. We maintain an independent thread to receive incoming requests and track outgoing responses. As each incoming request is received, it's put into a prioritized queue.
78-
2. We maintain fixed number of threads that continuously pull the highest priority request from the queue and perform the selector query for that request
79-
3. Each peer we respond to has an independent thread marshaling and deduplicating outgoing responses and blocks before they are sent back. This minimizes data sent on the wire and allows queries to proceed without getting blocked by the network.
79+
2. We maintain fixed number of threads that continuously pull the highest priority request from the queue and perform the selector query for that request. We marshal and deduplicate outgoing responses and blocks before they are sent back. This minimizes data sent on the wire and allows queries to proceed without getting blocked by the network.
8080
- At the messaging layer:
8181
1. Each peer we send messages to has an independent thread collecting and buffering message data while waiting for the last message to finish sending. This allows higher level operations to execute without getting blocked by a slow network
8282

@@ -143,7 +143,7 @@ In addition, an optimized responder implementation accounts for the following co
143143

144144
* *Preserve Bandwith* - Be efficient with network usage, deduplicate data, and buffer response output so that each new network message contains all response data we have at the time the pipe becomes free.
145145

146-
The responder implementation is managed by the Response Manager. The ResponseManager delegates to PeerTaskQueue to rate limit the number of in progress selector traversals and ensure no one peer is given more priority than others. As data is generated from selector traversals, the ResponseManager uses the PeerResponseManager to aggregate response data for each peer and send compact messages over the network.
146+
The responder implementation is managed by the Response Manager. The ResponseManager delegates to PeerTaskQueue to rate limit the number of in progress selector traversals and ensure no one peer is given more priority than others. As data is generated from selector traversals, the ResponseManager uses the ResponseAssembler to aggregate response data for each peer and send compact messages over the network.
147147

148148
The following diagram outlines in greater detail go-graphsync's responder implementation, covering how it's initialized and how it responds to requests:
149149
![Responding To A Request](responder-sequence.png)
@@ -160,12 +160,21 @@ Meanwhile, the ResponseManager starts a fixed number of workers (currently 6), e
160160

161161
The net here is that no peer can have more than a fixed number of requests in progress at once, and even if a peer sends infinite requests, other peers will still jump ahead of it and get a chance to process their requests.
162162

163-
### Peer Response Sender -- Deduping blocks and data
163+
### ResponseAssembler -- Deduping blocks and data
164164

165165
Once a request is dequeued, we generate an intercepted loader and provide it to go-ipld-prime to execute a traversal. Each call to the loader will generate a block that we either have or don't. We need to transmit that information across the network. However, that information needs to be encoded in the GraphSync message format, and combined with any other responses we may be sent to the same peer at the same time, ideally without sending blocks more times than necessary.
166166

167-
These tasks are generally managed by the PeerResponseManager which spins up one PeerResponseSender for each peer. The PeerResponseSender tracks links with the LinkTracker and aggregates responses with the ResponseBuilder. Every time the PeerResponseSender is called by the intercepted loader, it users the LinkTracker and ResponseBuilder to add block information and metadata to the response. Meanwhile, the PeerResponseSender runs a continuous loop that is synchronized with the message sending layer -- a new response is aggregated until the message sending layer notifies that the last message was sent, at which point the new response is encoded and sent.
167+
These tasks are managed by the ResponseAssembler. The ResponseAssembber creates a LinkTracker for each peer to track what blocks have been sent. Responses are sent by calling Transaction on the ResponseAssembler, which provides a ResponseBuilder interface that can be used to assemble responses. Transaction is named as such because all data added to a response by calling methods on the provided ResponseBuilder is gauranteed to go out in the name network message.
168168

169169
## Message Sending Layer
170170

171-
The message sending layer is the simplest major component, consisting of a PeerManager which tracks peers, and a message queue for each peer. The PeerManager spins up new new message queues on demand. When a new request is received, it spins up a queue as needed and delegates sending to the message queue which collects message data until the network stream is ready for another message. It then encodes and sends the message to the network
171+
The message consists of a PeerManager which tracks peers, and a message queue for each peer. The PeerManager spins up new new message queues on demand. When a new request is received, it spins up a queue as needed and delegates sending to the message queue which collects message data until the network stream is ready for another message. It then encodes and sends the message to the network.
172+
173+
The message queue system contains a mechanism for applying backpressure to a query execution to make sure that a slow network connection doesn't cause us to load all the blocks for the query into memory while we wait for messages to go over the network. Whenever you attempt to queue data into the message queue, you provide an estimated size for the data that will be held in memory till the message goes out. Internally, the message queue uses the Allocator to track memory usage, and the call to queue data will block if there is too much data buffered in memory. When messages are sent out, memory is released, which will unblock requests to queue data for the message queue.
174+
175+
## Hooks And Listeners
176+
177+
go-graphsync provides a variety of points in the request/response lifecycle where one can provide a hook to inspect the current state of the request/response and potentially take action. These hooks provide the core mechanisms for authenticating requests, processing graphsync extensions, pausing and resuming, and generally enabling a higher level consumer of the graphsync to precisely control the request/response lifecycle.
178+
179+
Graphsync also provides listeners that enable a caller to be notified when various asynchronous events happen in the request response lifecycle. Currently graphsync contains an internal pubsub notification system (see [notifications](../notifications)) to escalate low level asynchonous events back to high level modules that pass them to external listeners. A future refactor might look for a way to remove this notification system as it adds additional complexity.
180+

docs/processes.png

-8.91 KB
Loading

docs/processes.puml

-10
Original file line numberDiff line numberDiff line change
@@ -51,16 +51,6 @@ fork again
5151
:ipld.Traverse;
5252
end fork
5353
}
54-
partition "Aggregating Responses" {
55-
:PeerResponseManager;
56-
fork
57-
:PeerResponseSender;
58-
fork again
59-
:PeerResponseSender;
60-
fork again
61-
:PeerResponseSender;
62-
end fork
63-
}
6454
}
6555
endif
6656
partition "Message Sending Layer" {

docs/responder-sequence.png

-43.5 KB
Loading

docs/responder-sequence.puml

+27-51
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
@startuml Responding To A Request
22
participant "GraphSync\nTop Level\nInterface" as TLI
33
participant ResponseManager
4-
participant "Query Workers" as QW
4+
participant "Query Executor" as QW
55
participant PeerTaskQueue
66
participant PeerTracker
7-
participant PeerResponseManager
8-
participant PeerResponseSender
7+
participant Traverser
8+
participant ResponseAssembler
99
participant LinkTracker
1010
participant ResponseBuilder
11-
participant IPLD
1211
participant "Intercepted Loader" as ILoader
1312
participant Loader
1413
participant "Message Sending\nLayer" as Message
@@ -37,63 +36,40 @@ ResponseManager -> ResponseManager : Cancel Request Context
3736
end
3837
end
3938
else
40-
par
4139
loop until shutdown
4240
note over QW: Request Processing Loop
4341
QW -> PeerTaskQueue : Pop Request
4442
PeerTaskQueue -> PeerTracker : Pop Request
4543
PeerTracker -> PeerTaskQueue : Next Request\nTo Process
4644
PeerTaskQueue -> QW : Next Request\nTo Process
47-
QW -> IPLD : DecodeNode
48-
IPLD -> QW : Selector Spec Node
49-
QW -> IPLD : ParseSelector
50-
IPLD -> QW : Root Node, IPLD Selector
51-
QW -> PeerResponseManager : SenderForPeer
52-
PeerResponseManager -> PeerResponseSender ** : Create for peer\nas neccesary
53-
PeerResponseSender -> LinkTracker ** : Create
54-
PeerResponseSender -> QW : PeerResponseSender
55-
activate PeerResponseSender
56-
QW -> ILoader ** : Create w/ RequestID, PeerResponseSender, Loader
57-
QW -> IPLD : Start Traversal Of Selector
45+
QW -> QW : Process incoming request hooks
46+
QW -> ILoader ** : Create w/ Request, Peer, and Loader
47+
QW -> Traverser ** : Create to manage selector traversal
5848
loop until traversal complete or request context cancelled
59-
note over PeerResponseSender: Selector Traversal Loop
60-
IPLD -> ILoader : Request to load blocks\nto perform traversal
49+
note over Traverser: Selector Traversal Loop
50+
Traverser -> ILoader : Request to load blocks\nto perform traversal
6151
ILoader -> Loader : Load blocks\nfrom local storage
6252
Loader -> ILoader : Blocks From\nlocal storage or error
63-
ILoader -> IPLD : Blocks to continue\n traversal or error
64-
ILoader -> PeerResponseSender : Block or error to Send Back
65-
activate PeerResponseSender
66-
PeerResponseSender -> LinkTracker : Notify block or\n error, ask whether\n block is duplicate
67-
LinkTracker -> PeerResponseSender : Whether to\n send block
68-
PeerResponseSender -> ResponseBuilder ** : Create New As Neccesary
69-
PeerResponseSender -> ResponseBuilder : Aggregate Response Metadata & Block
70-
PeerResponseSender -> PeerResponseSender : Signal Work To Do
71-
deactivate PeerResponseSender
72-
end
73-
IPLD -> QW : Traversal Complete
74-
QW -> PeerResponseSender : Request Finished
75-
activate PeerResponseSender
76-
PeerResponseSender -> LinkTracker : Query If Errors\n Were Present
77-
LinkTracker -> PeerResponseSender : True/False\n if errors present
78-
PeerResponseSender -> ResponseBuilder : Aggregate request finishing
79-
PeerResponseSender -> PeerResponseSender : Signal Work To Do
80-
deactivate PeerResponseSender
81-
end
82-
else
83-
loop until shutdown / disconnect
84-
note over PeerResponseSender: Message Sending\nLoop
85-
PeerResponseSender -> PeerResponseSender : Wait For Work Signal
86-
...
87-
PeerResponseSender -> ResponseBuilder : build response
88-
ResponseBuilder -> PeerResponseSender : Response message data to send
89-
PeerResponseSender -> Message : Send response message data
90-
activate Message
91-
Message -> PeerResponseSender : Channel For When Message Processed
92-
...
93-
Message -> PeerResponseSender : Notification on channel
94-
deactivate Message
53+
ILoader -> Traverser : Blocks to continue\n traversal or error
54+
ILoader -> QW : Block or error to Send Back
55+
QW -> QW: Processing outgoing block hooks
56+
QW -> ResponseAssembler: Add outgoing responses
57+
activate ResponseAssembler
58+
ResponseAssembler -> LinkTracker ** : Create for peer if not already present
59+
ResponseAssembler -> LinkTracker : Notify block or\n error, ask whether\n block is duplicate
60+
LinkTracker -> ResponseAssembler : Whether to\n send block
61+
ResponseAssembler -> ResponseBuilder : Aggregate Response Metadata & Block
62+
ResponseAssembler -> Message : Send aggregate response
63+
deactivate ResponseAssembler
9564
end
96-
deactivate PeerResponseSender
65+
Traverser -> QW : Traversal Complete
66+
QW -> ResponseAssembler : Request Finished
67+
activate ResponseAssembler
68+
ResponseAssembler -> LinkTracker : Query If Errors\n Were Present
69+
LinkTracker -> ResponseAssembler : True/False\n if errors present
70+
ResponseAssembler -> ResponseBuilder : Aggregate request finishing
71+
ResponseAssembler -> Message : Send aggregate response
72+
deactivate ResponseAssembler
9773
end
9874
deactivate QW
9975
end

0 commit comments

Comments
 (0)