feat: add tla config for model
This commit is contained in:
parent
8758541668
commit
57d048661a
7 changed files with 1592 additions and 0 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -51,6 +51,9 @@ bin/
|
||||||
# Debug information files
|
# Debug information files
|
||||||
*.dwo
|
*.dwo
|
||||||
|
|
||||||
|
# TLA+ states
|
||||||
|
states/
|
||||||
|
|
||||||
# CMake
|
# CMake
|
||||||
*cmake*/
|
*cmake*/
|
||||||
/CMakeLists.txt
|
/CMakeLists.txt
|
||||||
|
|
|
||||||
15
validate/PartitionsStorageSets copy.cfg
Normal file
15
validate/PartitionsStorageSets copy.cfg
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
SPECIFICATION Spec
|
||||||
|
|
||||||
|
INVARIANTS
|
||||||
|
TypeInvariant
|
||||||
|
UniqueOwnerForEveryKey
|
||||||
|
DeadPartitionsAreOutsideTree
|
||||||
|
|
||||||
|
CONSTANTS
|
||||||
|
PartitionIds = {p0, p1, p2, p3, p4}
|
||||||
|
Threads = {t0, t1}
|
||||||
|
NoThread = NoThread
|
||||||
|
KeyCount = 8
|
||||||
|
MinPartitionSize = 2
|
||||||
|
MaxPartitionSize = 4
|
||||||
|
MaxLoad = 8
|
||||||
15
validate/PartitionsStorageSets.cfg
Normal file
15
validate/PartitionsStorageSets.cfg
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
SPECIFICATION Spec
|
||||||
|
|
||||||
|
INVARIANTS
|
||||||
|
TypeInvariant
|
||||||
|
UniqueOwnerForEveryKey
|
||||||
|
DeadPartitionsAreOutsideTree
|
||||||
|
|
||||||
|
CONSTANTS
|
||||||
|
PartitionIds = {p0, p1, p2, p3, p4, p5, p6, p7}
|
||||||
|
Threads = {t0, t1, t2, t3}
|
||||||
|
NoThread = NoThread
|
||||||
|
KeyCount = 1024
|
||||||
|
MinPartitionSize = 2
|
||||||
|
MaxPartitionSize = 8
|
||||||
|
MaxLoad = 9
|
||||||
191
validate/PartitionsStorageSets.tla
Normal file
191
validate/PartitionsStorageSets.tla
Normal file
|
|
@ -0,0 +1,191 @@
|
||||||
|
---- MODULE PartitionsStorageSets ----
|
||||||
|
EXTENDS Naturals, FiniteSets
|
||||||
|
|
||||||
|
(*
|
||||||
|
This model validates only the partition/set logic.
|
||||||
|
AVL tree lookup and QSBR are abstracted away as the variable `tree`,
|
||||||
|
which contains ids of currently reachable live partitions.
|
||||||
|
*)
|
||||||
|
|
||||||
|
CONSTANTS
|
||||||
|
PartitionIds,
|
||||||
|
Threads,
|
||||||
|
NoThread,
|
||||||
|
KeyCount,
|
||||||
|
MinPartitionSize,
|
||||||
|
MaxPartitionSize,
|
||||||
|
MaxLoad
|
||||||
|
|
||||||
|
ASSUME NoThread \notin Threads
|
||||||
|
ASSUME KeyCount > 0
|
||||||
|
ASSUME MinPartitionSize > 0
|
||||||
|
ASSUME MaxPartitionSize >= MinPartitionSize
|
||||||
|
ASSUME MaxLoad > MaxPartitionSize
|
||||||
|
ASSUME Cardinality(PartitionIds) >= 3
|
||||||
|
|
||||||
|
VARIABLES
|
||||||
|
partitions,
|
||||||
|
tree,
|
||||||
|
locks
|
||||||
|
|
||||||
|
vars == <<partitions, tree, locks>>
|
||||||
|
|
||||||
|
Keys == 0..(KeyCount - 1)
|
||||||
|
|
||||||
|
PartitionRecord ==
|
||||||
|
[ lo : 0..KeyCount,
|
||||||
|
hi : 0..KeyCount,
|
||||||
|
dead : BOOLEAN,
|
||||||
|
load : 0..MaxLoad ]
|
||||||
|
|
||||||
|
KeyInPartition(k, p) ==
|
||||||
|
/\ partitions[p].lo <= k
|
||||||
|
/\ k < partitions[p].hi
|
||||||
|
|
||||||
|
Status(p) ==
|
||||||
|
IF partitions[p].dead THEN "DEAD"
|
||||||
|
ELSE IF partitions[p].load > MaxPartitionSize THEN "NEED_SPLIT"
|
||||||
|
ELSE IF partitions[p].load < MinPartitionSize /\ Cardinality(tree) > 1 THEN "NEED_SHRINK"
|
||||||
|
ELSE "ALIVE"
|
||||||
|
|
||||||
|
Init ==
|
||||||
|
\E p \in PartitionIds:
|
||||||
|
/\ tree = {p}
|
||||||
|
/\ partitions =
|
||||||
|
[ q \in PartitionIds |->
|
||||||
|
IF q = p THEN
|
||||||
|
[ lo |-> 0,
|
||||||
|
hi |-> KeyCount,
|
||||||
|
dead |-> FALSE,
|
||||||
|
load |-> MinPartitionSize ]
|
||||||
|
ELSE
|
||||||
|
[ lo |-> 0,
|
||||||
|
hi |-> 0,
|
||||||
|
dead |-> TRUE,
|
||||||
|
load |-> 0 ] ]
|
||||||
|
/\ locks = [q \in PartitionIds |-> NoThread]
|
||||||
|
|
||||||
|
TypeInvariant ==
|
||||||
|
/\ partitions \in [PartitionIds -> PartitionRecord]
|
||||||
|
/\ tree \subseteq PartitionIds
|
||||||
|
/\ locks \in [PartitionIds -> (Threads \cup {NoThread})]
|
||||||
|
/\ \A p \in tree:
|
||||||
|
/\ partitions[p].dead = FALSE
|
||||||
|
/\ partitions[p].lo < partitions[p].hi
|
||||||
|
/\ partitions[p].hi <= KeyCount
|
||||||
|
/\ \A p \in PartitionIds \ tree:
|
||||||
|
partitions[p].dead = TRUE
|
||||||
|
|
||||||
|
UniqueOwnerForEveryKey ==
|
||||||
|
\A k \in Keys:
|
||||||
|
\E p \in tree:
|
||||||
|
/\ KeyInPartition(k, p)
|
||||||
|
/\ \A q \in tree:
|
||||||
|
KeyInPartition(k, q) => q = p
|
||||||
|
|
||||||
|
DeadPartitionsAreOutsideTree ==
|
||||||
|
\A p \in PartitionIds:
|
||||||
|
partitions[p].dead => p \notin tree
|
||||||
|
|
||||||
|
Lock(t, p) ==
|
||||||
|
/\ p \in tree
|
||||||
|
/\ locks[p] = NoThread
|
||||||
|
/\ locks' = [locks EXCEPT ![p] = t]
|
||||||
|
/\ UNCHANGED <<partitions, tree>>
|
||||||
|
|
||||||
|
Unlock(t, p) ==
|
||||||
|
/\ locks[p] = t
|
||||||
|
/\ locks' = [locks EXCEPT ![p] = NoThread]
|
||||||
|
/\ UNCHANGED <<partitions, tree>>
|
||||||
|
|
||||||
|
\* Abstract user mutation of the set stored inside a locked partition.
|
||||||
|
GrowPartition(t, p) ==
|
||||||
|
/\ p \in tree
|
||||||
|
/\ locks[p] = t
|
||||||
|
/\ partitions[p].load < MaxLoad
|
||||||
|
/\ partitions' = [partitions EXCEPT ![p].load = @ + 1]
|
||||||
|
/\ UNCHANGED <<tree, locks>>
|
||||||
|
|
||||||
|
ShrinkPartitionLoad(t, p) ==
|
||||||
|
/\ p \in tree
|
||||||
|
/\ locks[p] = t
|
||||||
|
/\ partitions[p].load > 0
|
||||||
|
/\ partitions' = [partitions EXCEPT ![p].load = @ - 1]
|
||||||
|
/\ UNCHANGED <<tree, locks>>
|
||||||
|
|
||||||
|
SplitPartition(t, p) ==
|
||||||
|
/\ p \in tree
|
||||||
|
/\ locks[p] = t
|
||||||
|
/\ Status(p) = "NEED_SPLIT"
|
||||||
|
/\ partitions[p].lo + 1 < partitions[p].hi
|
||||||
|
/\ \E left \in PartitionIds \ tree:
|
||||||
|
\E right \in (PartitionIds \ tree) \ {left}:
|
||||||
|
\E splitKey \in (partitions[p].lo + 1)..(partitions[p].hi - 1):
|
||||||
|
\E leftLoad \in 0..partitions[p].load:
|
||||||
|
LET rightLoad == partitions[p].load - leftLoad IN
|
||||||
|
/\ rightLoad \in 0..MaxLoad
|
||||||
|
/\ leftLoad \in 0..MaxLoad
|
||||||
|
/\ partitions' =
|
||||||
|
[ partitions EXCEPT
|
||||||
|
![p].dead = TRUE,
|
||||||
|
![left] =
|
||||||
|
[ lo |-> partitions[p].lo,
|
||||||
|
hi |-> splitKey,
|
||||||
|
dead |-> FALSE,
|
||||||
|
load |-> leftLoad ],
|
||||||
|
![right] =
|
||||||
|
[ lo |-> splitKey,
|
||||||
|
hi |-> partitions[p].hi,
|
||||||
|
dead |-> FALSE,
|
||||||
|
load |-> rightLoad ] ]
|
||||||
|
/\ tree' = (tree \ {p}) \cup {left, right}
|
||||||
|
/\ locks' =
|
||||||
|
[ locks EXCEPT
|
||||||
|
![p] = NoThread,
|
||||||
|
![left] = NoThread,
|
||||||
|
![right] = NoThread ]
|
||||||
|
|
||||||
|
ShrinkToPrev(t, p) ==
|
||||||
|
/\ p \in tree
|
||||||
|
/\ locks[p] = t
|
||||||
|
/\ Status(p) = "NEED_SHRINK"
|
||||||
|
/\ \E prev \in tree \ {p}:
|
||||||
|
/\ locks[prev] = t
|
||||||
|
/\ partitions[prev].hi = partitions[p].lo
|
||||||
|
/\ partitions[prev].load + partitions[p].load <= MaxLoad
|
||||||
|
/\ partitions' =
|
||||||
|
[ partitions EXCEPT
|
||||||
|
![prev].hi = partitions[p].hi,
|
||||||
|
![prev].load = partitions[prev].load + partitions[p].load,
|
||||||
|
![p].dead = TRUE ]
|
||||||
|
/\ tree' = tree \ {p}
|
||||||
|
/\ locks' = [locks EXCEPT ![p] = NoThread, ![prev] = NoThread]
|
||||||
|
|
||||||
|
ShrinkToNext(t, p) ==
|
||||||
|
/\ p \in tree
|
||||||
|
/\ locks[p] = t
|
||||||
|
/\ Status(p) = "NEED_SHRINK"
|
||||||
|
/\ \E next \in tree \ {p}:
|
||||||
|
/\ locks[next] = t
|
||||||
|
/\ partitions[p].hi = partitions[next].lo
|
||||||
|
/\ partitions[next].load + partitions[p].load <= MaxLoad
|
||||||
|
/\ partitions' =
|
||||||
|
[ partitions EXCEPT
|
||||||
|
![next].lo = partitions[p].lo,
|
||||||
|
![next].load = partitions[next].load + partitions[p].load,
|
||||||
|
![p].dead = TRUE ]
|
||||||
|
/\ tree' = tree \ {p}
|
||||||
|
/\ locks' = [locks EXCEPT ![p] = NoThread, ![next] = NoThread]
|
||||||
|
|
||||||
|
Next ==
|
||||||
|
\/ \E t \in Threads, p \in PartitionIds: Lock(t, p)
|
||||||
|
\/ \E t \in Threads, p \in PartitionIds: Unlock(t, p)
|
||||||
|
\/ \E t \in Threads, p \in PartitionIds: GrowPartition(t, p)
|
||||||
|
\/ \E t \in Threads, p \in PartitionIds: ShrinkPartitionLoad(t, p)
|
||||||
|
\/ \E t \in Threads, p \in PartitionIds: SplitPartition(t, p)
|
||||||
|
\/ \E t \in Threads, p \in PartitionIds: ShrinkToPrev(t, p)
|
||||||
|
\/ \E t \in Threads, p \in PartitionIds: ShrinkToNext(t, p)
|
||||||
|
|
||||||
|
Spec == Init /\ [][Next]_vars
|
||||||
|
|
||||||
|
====
|
||||||
15
validate/PartitionsStorageSetsSmoke.cfg
Normal file
15
validate/PartitionsStorageSetsSmoke.cfg
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
SPECIFICATION Spec
|
||||||
|
|
||||||
|
INVARIANTS
|
||||||
|
TypeInvariant
|
||||||
|
UniqueOwnerForEveryKey
|
||||||
|
DeadPartitionsAreOutsideTree
|
||||||
|
|
||||||
|
CONSTANTS
|
||||||
|
PartitionIds = {p0, p1, p2}
|
||||||
|
Threads = {t0}
|
||||||
|
NoThread = NoThread
|
||||||
|
KeyCount = 3
|
||||||
|
MinPartitionSize = 1
|
||||||
|
MaxPartitionSize = 2
|
||||||
|
MaxLoad = 3
|
||||||
1320
validate/PartitionsStorageSets_explained.md
Normal file
1320
validate/PartitionsStorageSets_explained.md
Normal file
File diff suppressed because it is too large
Load diff
33
validate/README.md
Normal file
33
validate/README.md
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Валидация
|
||||||
|
|
||||||
|
## Описание структуры данных (partitions_storage)
|
||||||
|
1) Внутри данной структуры данных хранятся множества в виде полуоткрытых интервалов ([X; Y), [Y; Z), ... )
|
||||||
|
2) В процессе работы множество может быть разбито на два более маленьких для увелечения производительности доступа
|
||||||
|
3) В процессе работы множество может быть присаеденено к другому если оно стало слишком маленьким
|
||||||
|
4) Запросы, которые может поддерживать:
|
||||||
|
- `get(key)` - отдает множество, в которому принадлежит данный ключ, множество находится в данный момент под mutex_lock
|
||||||
|
|
||||||
|
## Описание работы структуры данных
|
||||||
|
1) Внутри используется персистентное AVL дерево, которое является lock-free и в узлах, хранит как раз так называемые множества
|
||||||
|
2) У партиции может быть несколько состояний в момент когда пользователь взяль mutex lock и прочитал состояние:
|
||||||
|
- ALIVE ( 16 <= `partition.size()` <= 128)
|
||||||
|
- NEED_SPLIT ( `partition.size()` > 128)
|
||||||
|
- NEED_SHRINK ( 16 > `partition.size()` && partitions_count > 1)
|
||||||
|
- DEAD (особое состояние)
|
||||||
|
3) Как происходит get:
|
||||||
|
- сначала получаем из AVL дерева ноду на партицию (она защищена QSBR) и берем у нее mutex lock
|
||||||
|
- проверяем состояние, если DEAD, то возвращаемся к предыдущему шагу. Если ALIVE то возвращаем, иначе выполняем соответствующие команды
|
||||||
|
4) Как происходит split:
|
||||||
|
- так как над портицией держится lock то мы пемечяем ее как DEAD
|
||||||
|
- разделяем на две по некоторому ключу
|
||||||
|
- вставляем в AVL дерево за место ноды связку из таких node (left, split_key, right)
|
||||||
|
5) Как происходит shrink (shrink происходит когда множеств больше чем одно):
|
||||||
|
- отпускаем lock
|
||||||
|
- находим предыдущею до нас множество и берем у него lock, проверяя не является ли она DEAD
|
||||||
|
- берем lock у нашей партиции и проверяем что она не DEAD
|
||||||
|
- находим следующую за нами множество и берем у него lock, проверяя не является ли она DEAD
|
||||||
|
- помечаем нашу партицию как DEAD
|
||||||
|
- "убираем" нашу партицию из дерева и запоминаем каким ребенком мы были для предка (left, right)
|
||||||
|
- если мы были правым ребенком то передаем все множество в следующее множество
|
||||||
|
- если мы были левым ребенком то передаем все множество в прошлое множество
|
||||||
|
6) В начальный момент времени существует либо только одно множество, либо несколько их, которые сбалансированы
|
||||||
Loading…
Add table
Add a link
Reference in a new issue