Skip to content

Commit 13c547f

Browse files
committed
Add bank workload
Closes #67
1 parent f411d3b commit 13c547f

File tree

2 files changed

+180
-1
lines changed

2 files changed

+180
-1
lines changed

src/tarantool/bank.clj

+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
(ns tarantool.bank
2+
"Simulates transfers between bank accounts."
3+
(:require [clojure.tools.logging :refer [info warn]]
4+
[clojure.string :as str]
5+
[clojure.core.reducers :as r]
6+
[jepsen [cli :as cli]
7+
[client :as client]
8+
[checker :as checker]
9+
[core :as jepsen]
10+
[control :as c]
11+
[independent :as independent]
12+
[generator :as gen]
13+
[util :refer [timeout meh]]]
14+
[jepsen.tests.bank :as bank]
15+
[next.jdbc :as j]
16+
[next.jdbc.sql :as sql]
17+
[knossos.op :as op]
18+
[jepsen.checker.timeline :as timeline]
19+
[tarantool [client :as cl]
20+
[db :as db]]))
21+
22+
(def table-name "accounts")
23+
24+
(defrecord BankClient [conn]
25+
client/Client
26+
27+
(open! [this test node]
28+
(let [conn (cl/open node test)]
29+
(assoc this :conn conn :node node)))
30+
31+
(setup! [this test node]
32+
(locking BankClient
33+
(let [conn (cl/open node test)]
34+
(Thread/sleep 10000) ; wait for leader election and joining to a cluster
35+
(when (= node (first (db/primaries test)))
36+
(cl/with-conn-failure-retry conn
37+
(info (str "Creating table " table-name))
38+
(j/execute! conn [(str "CREATE TABLE IF NOT EXISTS " table-name
39+
"(id INT NOT NULL PRIMARY KEY,
40+
balance INT NOT NULL)")])
41+
(doseq [a (:accounts test)]
42+
(info "Populating account")
43+
(sql/insert! conn table-name {:id a
44+
:balance (if (= a (first (:accounts test)))
45+
100
46+
0)}))))
47+
(assoc this :conn conn :node node))))
48+
49+
(invoke! [this test op]
50+
;(with-txn op [c conn]
51+
(try
52+
(case (:f op)
53+
:read (->> (sql/query conn [(str "SELECT * FROM " table-name)])
54+
(map (juxt :ID :BALANCE))
55+
(into (sorted-map))
56+
(assoc op :type :ok, :value))
57+
58+
:transfer
59+
(let [{:keys [from to amount]} (:value op)
60+
con (cl/open (first (db/primaries test)) test)
61+
b1 (-> con
62+
(sql/query [(str "SELECT * FROM " table-name " WHERE id = ? ") from])
63+
first
64+
:BALANCE
65+
(- amount))
66+
b2 (-> con
67+
(sql/query [(str "SELECT * FROM " table-name " WHERE id = ? ") to])
68+
first
69+
:BALANCE
70+
(+ amount))]
71+
(cond (neg? b1)
72+
(assoc op :type :fail, :value [from b1])
73+
(neg? b2)
74+
(assoc op :type :fail, :value [to b2])
75+
true
76+
(do (j/execute! con ["UPDATE " table-name " SET balance = balance - ? WHERE id = ?" amount from])
77+
(j/execute! con ["UPDATE " table-name " SET balance = balance + ? WHERE id = ?" amount to])
78+
(assoc op :type :ok)))))))
79+
80+
(teardown! [_ test]
81+
(when-not (:leave-db-running? test)
82+
(info (str "Drop table" table-name))
83+
(cl/with-conn-failure-retry conn
84+
(j/execute! conn [(str "DROP TABLE IF EXISTS " table-name)]))))
85+
86+
(close! [_ test]))
87+
88+
(defn workload
89+
[opts]
90+
(assoc (bank/test opts)
91+
:client (BankClient. nil)))
92+
93+
; One bank account per table
94+
(defrecord MultiBankClient [conn tbl-created?]
95+
client/Client
96+
(open! [this test node]
97+
(assoc this :conn (cl/open node test)))
98+
99+
(setup! [this test]
100+
(locking tbl-created?
101+
;(let [conn (cl/open node test)]
102+
; (Thread/sleep 10000) ; wait for leader election and joining to a cluster
103+
; (when (= node (first (db/primaries test)))
104+
; (cl/with-conn-failure-retry conn
105+
(when (compare-and-set! tbl-created? false true)
106+
;(with-txn-retries conn
107+
(cl/with-conn-failure-retry conn
108+
(doseq [a (:accounts test)]
109+
(info "Creating table" table-name a)
110+
(j/execute! conn [(str "CREATE TABLE IF NOT EXISTS " table-name a
111+
"(id INT NOT NULL PRIMARY KEY,"
112+
"balance INT NOT NULL)")])
113+
(try
114+
(info "Populating account" a)
115+
(sql/insert! conn (str table-name a)
116+
{:id 0
117+
:balance (if (= a (first (:accounts test)))
118+
(:total-amount test)
119+
0)})
120+
(catch java.sql.SQLIntegrityConstraintViolationException e
121+
nil)))))))
122+
123+
(invoke! [this test op]
124+
;(with-txn op [c conn]
125+
(try
126+
(case (:f op)
127+
:read
128+
(->> (:accounts test)
129+
(map (fn [x]
130+
[x (->> (sql/query conn [(str "SELECT balance FROM " table-name
131+
x)]
132+
{:row-fn :BALANCE})
133+
first)]))
134+
(into (sorted-map))
135+
(assoc op :type :ok, :value))
136+
137+
:transfer
138+
(let [{:keys [from to amount]} (:value op)
139+
from (str table-name from)
140+
to (str table-name to)
141+
con (cl/open (first (db/primaries test)) test)
142+
b1 (-> con
143+
(sql/query [(str "SELECT balance FROM " from)])
144+
first
145+
:BALANCE
146+
(- amount))
147+
b2 (-> con
148+
(sql/query [(str "SELECT balance FROM " to)])
149+
first
150+
:BALANCE
151+
(+ amount))]
152+
(cond (neg? b1)
153+
(assoc op :type :fail, :error [:negative from b1])
154+
(neg? b2)
155+
(assoc op :type :fail, :error [:negative to b2])
156+
true
157+
(do (j/execute! con [(str "UPDATE " from " SET balance = balance - ? WHERE id = 0") amount])
158+
(j/execute! con [(str "UPDATE " to " SET balance = balance + ? WHERE id = 0") amount])
159+
(assoc op :type :ok)))))))
160+
161+
(teardown! [_ test]
162+
(when-not (:leave-db-running? test)
163+
(cl/with-conn-failure-retry conn
164+
(doseq [a (:accounts test)]
165+
(info "Drop table" table-name a)
166+
(j/execute! conn [(str "DROP TABLE IF EXISTS " table-name a)])))))
167+
168+
(close! [_ test]))
169+
170+
(defn multitable-workload
171+
[opts]
172+
(assoc (workload opts)
173+
:client (MultiBankClient. nil (atom false))))

src/tarantool/runner.clj

+7-1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
[jepsen.checker.timeline :as timeline]
1818
[jepsen.os.ubuntu :as ubuntu]
1919
[tarantool [db :as db]
20+
[bank :as bank]
2021
[errcode :as err]
2122
[nemesis :as nemesis]
2223
[register :as register]
@@ -39,7 +40,9 @@
3940
4041
Or, for some special cases where nemeses and workloads are coupled, we return
4142
a keyword here instead."
42-
{:set sets/workload
43+
{:bank bank/workload
44+
:bank-multitable bank/multitable-workload
45+
:set sets/workload
4346
:counter-inc counter/workload-inc
4447
:register register/workload})
4548

@@ -180,6 +183,9 @@
180183
:db (db/db (:version opts))
181184
:engine (:engine opts)
182185
:mvcc (:mvcc opts)
186+
:accounts (vec (range 10))
187+
:max-transfer 10
188+
:total-amount 100
183189
:pure-generators true
184190
:concurrency (if (and (< (:concurrency opts) minimal-concurrency)
185191
(= (:workload opts) :register))

0 commit comments

Comments
 (0)