feat: add tla config for model
This commit is contained in:
parent
8758541668
commit
57d048661a
7 changed files with 1592 additions and 0 deletions
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