mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-08-20 10:52:25 +00:00
swarm/storage/localstore: add description for gc size counting
This commit is contained in:
parent
f056e86d3f
commit
40432d9690
1 changed files with 73 additions and 0 deletions
|
|
@ -14,6 +14,79 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Counting number of items in garbage collection index
|
||||||
|
|
||||||
|
The number of items in garbage collection index is not the same as the number of
|
||||||
|
chunks in retrieval index (total number of stored chunks). Chunk can be garbage
|
||||||
|
collected only when it is set to a synced state by ModSetSync, and only then can
|
||||||
|
be counted into garbage collection size, which determines whether a number of
|
||||||
|
chunk should be removed from the storage by the garbage collection. This opens a
|
||||||
|
possibility that the storage size exceeds the limit if files are locally
|
||||||
|
uploaded and the node is not connected to other nodes or there is a problem with
|
||||||
|
syncing.
|
||||||
|
|
||||||
|
Tracking of garbage collection size (gcSize) is focused on performance. Key
|
||||||
|
points:
|
||||||
|
|
||||||
|
1. counting the number of key/value pairs in LevelDB takes around 0.7s for 1e6
|
||||||
|
on a very fast ssd (unacceptable long time in reality)
|
||||||
|
2. locking leveldb batch writes with a global mutex (serial batch writes) is
|
||||||
|
not acceptable, we should use locking per chunk address
|
||||||
|
|
||||||
|
Because of point 1. we cannot count the number of items in garbage collection
|
||||||
|
index in New constructor as it could last very long for realistic scenarios
|
||||||
|
where limit is 5e6 and nodes are running on slower hdd disks or cloud providers
|
||||||
|
with low IOPS.
|
||||||
|
|
||||||
|
Point 2. is a performance optimization to allow parallel batch writes with
|
||||||
|
getters, putters and setters. Every single batch that they create contain only
|
||||||
|
information related to a single chunk, no relations with other chunks or shared
|
||||||
|
statistical data (like gcSize). This approach avoids race conditions on writing
|
||||||
|
batches in parallel, but creates a problem of synchronizing statistical data
|
||||||
|
values like gcSize. With global mutex lock, any data could be written by any
|
||||||
|
batch, but would not use utilize the full potential of leveldb parallel writes.
|
||||||
|
|
||||||
|
To mitigate this two problems, the implementation of counting and persisting
|
||||||
|
gcSize is split into two parts. One is the in-memory value (gcSize) that is fast
|
||||||
|
to read and write with a dedicated mutex (gcSizeMu) if the batch which adds or
|
||||||
|
removes items from garbage collection index is successful. The second part is
|
||||||
|
the reliable persistence of this value to leveldb database, as storedGCSize
|
||||||
|
field. This database field is saved by writeGCSizeWorker and writeGCSize
|
||||||
|
functions when in-memory gcSize variable is changed, but no too often to avoid
|
||||||
|
very frequent database writes. This database writes are triggered by
|
||||||
|
writeGCSizeTrigger when a call is made to function incGCSize. Trigger ensures
|
||||||
|
that no database writes are done only when gcSize is changed (contrary to a
|
||||||
|
simpler periodic writes or checks). A backoff of 10s in writeGCSizeWorker
|
||||||
|
ensures that no frequent batch writes are made. Saving the storedGCSize on
|
||||||
|
database Close function ensures that in-memory gcSize is persisted when database
|
||||||
|
is closed.
|
||||||
|
|
||||||
|
This persistence must be resilient to failures like panics. For this purpose, a
|
||||||
|
collection of hashes that are added to the garbage collection index, but still
|
||||||
|
not persisted to storedGCSize, must be tracked to count them in when DB is
|
||||||
|
constructed again with New function after the failure (swarm node restarts). On
|
||||||
|
every batch write that adds a new item to garbage collection index, the same
|
||||||
|
hash is added to gcUncountedHashesIndex. This ensures that there is a persisted
|
||||||
|
information which hashes were added to the garbage collection index. But, when
|
||||||
|
the storedGCSize is saved by writeGCSize function, this values are removed in
|
||||||
|
the same batch in which storedGCSize is changed to ensure consistency. When the
|
||||||
|
panic happen, or database Close method is not saved. The database storage
|
||||||
|
contains all information to reliably and efficiently get the correct number of
|
||||||
|
items in garbage collection index. This is performed in the New function when
|
||||||
|
all hashes in gcUncountedHashesIndex are counted, added to the storedGCSize and
|
||||||
|
saved to the disk before the database is constructed again. Index
|
||||||
|
gcUncountedHashesIndex is acting as dirty bit for recovery that provides
|
||||||
|
information what needs to be corrected. With a simple dirty bit, the whole
|
||||||
|
garbage collection index should me counted on recovery instead only the items in
|
||||||
|
gcUncountedHashesIndex. Because of the triggering mechanizm of writeGCSizeWorker
|
||||||
|
and relatively short backoff time, the number of hashes in
|
||||||
|
gcUncountedHashesIndex should be low and it should take a very short time to
|
||||||
|
recover from the previous failure. If there was no failure and
|
||||||
|
gcUncountedHashesIndex is empty, which is the usual case, New function will take
|
||||||
|
the minimal time to return.
|
||||||
|
*/
|
||||||
|
|
||||||
package localstore
|
package localstore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue