├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE ├── README.md ├── benchmark ├── tpcc │ ├── constant.go │ ├── def.go │ ├── generator.go │ ├── helper.go │ ├── index.go │ ├── loader.go │ ├── parameter.go │ ├── table.go │ ├── tbl-customer.go │ ├── tbl-district.go │ ├── tbl-history.go │ ├── tbl-item.go │ ├── tbl-neworder.go │ ├── tbl-order.go │ ├── tbl-orderline.go │ ├── tbl-stock.go │ ├── tbl-warehouse.go │ ├── tpcc.go │ ├── tpcc_test.go │ ├── tpcctx.go │ ├── txn-delivery.go │ ├── txn-neworder.go │ ├── txn-orderstatus.go │ ├── txn-payment.go │ ├── txn-stocklevel.go │ └── txn-stockscan.go └── ycsb │ ├── generator.go │ ├── ycsb.go │ └── ycsb_test.go ├── cfmutex ├── cfmutex.go └── cfmutex_test.go ├── common └── common.go ├── config └── config.go ├── examples ├── hello.go ├── strnum │ └── strnum.go └── xfer.go ├── go.mod ├── go.sum ├── index └── index.go ├── osdi23 ├── scripts │ ├── all.sh │ ├── base.diff │ ├── fai.diff │ ├── long-tpcc.sh │ ├── long-ycsb.sh │ ├── nomemalloc.diff │ ├── optimization.sh │ ├── scalability.sh │ ├── sed-mvcc.sh │ ├── sed-tplock.sh │ ├── shardpad.diff │ └── silo.sh └── tplock │ ├── tplock.go │ └── tplock_test.go ├── scripts ├── loc.sh └── test.sh ├── tid └── tid.go ├── trusted_proph └── proph.go ├── tuple └── tuple.go ├── txnsite └── txnsite.go ├── vmvcc ├── db.go └── txn.go └── wrbuf └── wrbuf.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | schedule: 9 | # every week on Monday at 7am UTC 10 | - cron: "0 7 * * MON" 11 | 12 | jobs: 13 | test: 14 | strategy: 15 | matrix: 16 | go-version: ["stable"] 17 | runs-on: "ubuntu-latest" 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Install Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: ${{ matrix.go-version }} 24 | - name: Install dependencies 25 | run: | 26 | go get -t ./... 27 | - name: Check style 28 | run: | 29 | gofmt -w . 30 | git diff --exit-code 31 | - name: Test 32 | run: | 33 | ./scripts/test.sh 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.prof 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 MIT PDOS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vMVCC: Verified transaction library using multi-version concurrency control 2 | 3 | vMVCC is a transaction library aimed at reducing the effort of writing 4 | **concurrent** application code. It is implemented in Go and verified with 5 | [Perennial](https://github.com/mit-pdos/perennial), 6 | [Iris](https://iris-project.org/), and Coq. This repository contains its 7 | implementation. You can find its formal specification and proof 8 | [here](https://github.com/mit-pdos/perennial/tree/master/src/program_proof/mvcc). 9 | 10 | For a high-level overview of its system architecture and proof, please refer to 11 | our [OSDI'23 paper](https://pdos.csail.mit.edu/papers/vmvcc:osdi23.pdf). 12 | 13 | ## Limitations 14 | 15 | 1. Interface limited to `uint64` keys and `string` values 16 | 2. No durability 17 | 3. No range query 18 | 19 | ## Usage 20 | 21 | See [`examples/hello.go`](examples/hello.go) for a minimal example of using 22 | vMVCC. 23 | 24 | ### Import vMVCC 25 | 26 | ```go 27 | import "github.com/mit-pdos/vmvcc/vmvcc" 28 | ``` 29 | 30 | ### Creating a database and activating garbage collection 31 | 32 | ```go 33 | func MkDB() *DB 34 | func (db *DB) ActivateGC() 35 | ``` 36 | 37 | ### Reading and writing the database 38 | 39 | Define your **transaction body** with the following methods: 40 | 41 | ```go 42 | func (txn *Txn) Read(key uint64) (string, bool) 43 | func (txn *Txn) Write(key uint64, val string) 44 | func (txn *Txn) Delete(key uint64) bool 45 | ``` 46 | 47 | ### Executing transactions 48 | 49 | Pick one of the approaches below to execute transactions. 50 | 51 | #### Approach 1: `db.Run` 52 | 53 | Pass your transaction body to `db.Run` to run the transaction atomically: 54 | 55 | ```go 56 | func (db *DB) Run(body func(txn *Txn) bool) bool 57 | ``` 58 | 59 | It is safe to call `db.Run` concurrently on multiple threads. 60 | 61 | #### Approach 2: `txn.Run` 62 | 63 | To reduce memory allocation for transaction objects, and to have more control 64 | over the assignment of transaction sites, another way to run transactions is 65 | with the following approach: (1) create a transaction object `txn` with 66 | `db.NewTxn`, and (2) call `txn.Run` to run the transaction atomically. 67 | 68 | ```go 69 | func (db *DB) NewTxn() *Txn 70 | func (txn *Txn) Run(body func(txn *Txn) bool) bool 71 | ``` 72 | 73 | You can reuse `txn` as many times as you want, and it is safe to call `Run` 74 | concurrently with **different** transaction objects. However, it is **NOT** 75 | safe to call `Run` with the **same** transaction object concurrently. 76 | 77 | #### Transactions with arguments 78 | 79 | Both `Run` methods (on `Txn` and on `DB`) expect a function that takes a single 80 | transaction object; use [function closures](https://go.dev/tour/moretypes/25) to 81 | define your transaction with additional arguments. See 82 | [`examples/xfer.go`](examples/xfer.go) for an example. 83 | 84 | ## Reproducing the results in the OSDI'23 paper 85 | 86 | All the scripts for reproducing the results in the paper can be found in 87 | [`osdi23/scripts`](osdi23/scripts). 88 | 89 | Run all the experiments with `./osdi23/scripts/all.sh`. The results will be 90 | generated in the CSV format under the `./exp` directory. 91 | 92 | ## Developing 93 | 94 | To build the code that should be working, run 95 | 96 | ``` 97 | ./scripts/test.sh 98 | ``` 99 | -------------------------------------------------------------------------------- /benchmark/tpcc/constant.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /** 4 | * Based on: 5 | * https://github.com/apavlo/py-tpcc/blob/7c3ff501bbe98a6a7abd3c13267523c3684b62d6/pytpcc/constants.py 6 | */ 7 | 8 | const H_INIT_AMOUNT float32 = 10.0 9 | const OL_INIT_QUANTITY uint8 = 5 10 | const OL_MAX_QUANTITY uint8 = 10 11 | const OL_MIN_CNT uint8 = 5 12 | const OL_MAX_CNT uint8 = 15 13 | -------------------------------------------------------------------------------- /benchmark/tpcc/def.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /** 4 | * Table definitions. 5 | */ 6 | type Warehouse struct { 7 | /* Primary key: W_ID */ 8 | W_ID uint8 9 | /* Data fields */ 10 | W_NAME [10]byte 11 | W_STREET_1 [20]byte 12 | W_STREET_2 [20]byte 13 | W_CITY [20]byte 14 | W_STATE [2]byte 15 | W_ZIP [9]byte 16 | W_TAX float32 17 | W_YTD float32 18 | } 19 | 20 | const X_W_ID uint64 = 0 21 | const X_W_NAME uint64 = 1 22 | const X_W_STREET_1 uint64 = 11 23 | const X_W_STREET_2 uint64 = 31 24 | const X_W_CITY uint64 = 51 25 | const X_W_STATE uint64 = 71 26 | const X_W_ZIP uint64 = 73 27 | const X_W_TAX uint64 = 82 28 | const X_W_YTD uint64 = 86 29 | const X_W_LEN uint64 = 90 30 | 31 | type District struct { 32 | /* Primary key: (D_W_ID, D_ID) */ 33 | D_ID uint8 34 | D_W_ID uint8 35 | /* Data fields */ 36 | D_NAME [10]byte 37 | D_STREET_1 [20]byte 38 | D_STREET_2 [20]byte 39 | D_CITY [20]byte 40 | D_STATE [2]byte 41 | D_ZIP [9]byte 42 | D_TAX float32 43 | D_YTD float32 44 | D_NEXT_O_ID uint32 45 | /* TPC-C does not have this field, but this makes DELIVERY more efficient */ 46 | D_OLD_O_ID uint32 47 | } 48 | 49 | const X_D_ID uint64 = 0 50 | const X_D_W_ID uint64 = 1 51 | const X_D_NAME uint64 = 2 52 | const X_D_STREET_1 uint64 = 12 53 | const X_D_STREET_2 uint64 = 32 54 | const X_D_CITY uint64 = 52 55 | const X_D_STATE uint64 = 72 56 | const X_D_ZIP uint64 = 74 57 | const X_D_TAX uint64 = 83 58 | const X_D_YTD uint64 = 87 59 | const X_D_NEXT_O_ID uint64 = 91 60 | const X_D_OLD_O_ID uint64 = 95 61 | const X_D_LEN uint64 = 99 62 | 63 | type Customer struct { 64 | /* Primary key: (C_ID, C_D_ID, C_W_ID) */ 65 | C_ID uint32 66 | C_D_ID uint8 67 | C_W_ID uint8 68 | /* Data fields */ 69 | C_FIRST [16]byte 70 | C_MIDDLE [2]byte 71 | C_LAST [16]byte 72 | C_STREET_1 [20]byte 73 | C_STREET_2 [20]byte 74 | C_CITY [20]byte 75 | C_STATE [2]byte 76 | C_ZIP [9]byte 77 | C_PHONE [16]byte 78 | C_SINCE uint32 79 | C_CREDIT [2]byte 80 | C_CREDIT_LIM float32 81 | C_DISCOUNT float32 82 | C_BALANCE float32 83 | C_YTD_PAYMENT float32 84 | C_PAYMENT_CNT uint16 85 | C_DELIVERY_CNT uint16 86 | C_DATA [500]byte 87 | } 88 | 89 | const X_C_ID uint64 = 0 90 | const X_C_D_ID uint64 = 4 91 | const X_C_W_ID uint64 = 5 92 | const X_C_FIRST uint64 = 6 93 | const X_C_MIDDLE uint64 = 22 94 | const X_C_LAST uint64 = 24 95 | const X_C_STREET_1 uint64 = 40 96 | const X_C_STREET_2 uint64 = 60 97 | const X_C_CITY uint64 = 80 98 | const X_C_STATE uint64 = 100 99 | const X_C_ZIP uint64 = 102 100 | const X_C_PHONE uint64 = 111 101 | const X_C_SINCE uint64 = 127 102 | const X_C_CREDIT uint64 = 131 103 | const X_C_CREDIT_LIM uint64 = 133 104 | const X_C_DISCOUNT uint64 = 137 105 | const X_C_BALANCE uint64 = 141 106 | const X_C_YTD_PAYMENT uint64 = 145 107 | const X_C_PAYMENT_CNT uint64 = 149 108 | const X_C_DELIVERY_CNT uint64 = 151 109 | const X_C_DATA uint64 = 153 110 | const X_C_LEN uint64 = 653 111 | 112 | type History struct { 113 | /* Primary key: H_ID (no primary key required in the spec) */ 114 | H_ID uint64 /* the MSB, reserved for table ID, should not be used */ 115 | /* Data fields */ 116 | H_C_ID uint32 117 | H_C_D_ID uint8 118 | H_C_W_ID uint8 119 | H_D_ID uint8 120 | H_W_ID uint8 121 | H_DATE uint32 122 | H_AMOUNT float32 123 | H_DATA [25]byte 124 | } 125 | 126 | const X_H_ID uint64 = 0 127 | const X_H_C_ID uint64 = 8 128 | const X_H_C_D_ID uint64 = 12 129 | const X_H_C_W_ID uint64 = 13 130 | const X_H_D_ID uint64 = 14 131 | const X_H_W_ID uint64 = 15 132 | const X_H_DATE uint64 = 16 133 | const X_H_AMOUNT uint64 = 20 134 | const X_H_DATA uint64 = 24 135 | const X_H_LEN uint64 = 49 136 | 137 | type NewOrder struct { 138 | /* Primary key: (NO_O_ID, NO_D_ID, NO_W_ID) */ 139 | NO_O_ID uint32 140 | NO_D_ID uint8 141 | NO_W_ID uint8 142 | /* No data fields */ 143 | } 144 | 145 | const X_NO_O_ID uint64 = 0 146 | const X_NO_D_ID uint64 = 4 147 | const X_NO_W_ID uint64 = 5 148 | const X_NO_LEN uint64 = 6 149 | 150 | type Order struct { 151 | /* Primary key: (O_W_ID, O_D_ID, O_ID) */ 152 | O_ID uint32 153 | O_D_ID uint8 154 | O_W_ID uint8 155 | /* Data fields */ 156 | O_C_ID uint32 157 | O_ENTRY_D uint32 158 | O_CARRIER_ID uint8 159 | O_OL_CNT uint8 160 | O_ALL_LOCAL bool 161 | } 162 | 163 | const X_O_ID uint64 = 0 164 | const X_O_D_ID uint64 = 4 165 | const X_O_W_ID uint64 = 5 166 | const X_O_C_ID uint64 = 6 167 | const X_O_ENTRY_D uint64 = 10 168 | const X_O_CARRIER_ID uint64 = 14 169 | const X_O_OL_CNT uint64 = 15 170 | const X_O_ALL_LOCAL uint64 = 16 171 | const X_O_LEN uint64 = 17 172 | 173 | type OrderLine struct { 174 | /* Primary key: (OL_W_ID, OL_D_ID, OL_W_ID, OL_NUMBER) */ 175 | OL_O_ID uint32 176 | OL_D_ID uint8 177 | OL_W_ID uint8 178 | OL_NUMBER uint8 179 | /* Data fields */ 180 | OL_I_ID uint32 181 | OL_SUPPLY_W_ID uint8 182 | OL_DELIVERY_D uint32 183 | OL_QUANTITY uint8 184 | OL_AMOUNT float32 185 | } 186 | 187 | const X_OL_O_ID uint64 = 0 188 | const X_OL_D_ID uint64 = 4 189 | const X_OL_W_ID uint64 = 5 190 | const X_OL_NUMBER uint64 = 6 191 | const X_OL_I_ID uint64 = 7 192 | const X_OL_SUPPLY_W_ID uint64 = 11 193 | const X_OL_DELIVERY_D uint64 = 12 194 | const X_OL_QUANTITY uint64 = 16 195 | const X_OL_AMOUNT uint64 = 17 196 | const X_OL_LEN uint64 = 21 197 | 198 | type Item struct { 199 | /* Primary key: I_ID */ 200 | I_ID uint32 201 | /* Data fields */ 202 | I_IM_ID uint32 203 | I_NAME [24]byte 204 | I_PRICE float32 205 | I_DATA [50]byte 206 | } 207 | 208 | const X_I_ID uint64 = 0 209 | const X_I_IM_ID uint64 = 4 210 | const X_I_NAME uint64 = 8 211 | const X_I_PRICE uint64 = 32 212 | const X_I_DATA uint64 = 36 213 | const X_I_LEN uint64 = 86 214 | 215 | type Stock struct { 216 | /* Primary key: (S_W_ID, S_I_ID) */ 217 | S_I_ID uint32 218 | S_W_ID uint8 219 | /* Data fields */ 220 | S_QUANTITY uint16 221 | S_YTD uint32 222 | S_ORDER_CNT uint16 223 | S_REMOTE_CNT uint16 224 | S_DATA [50]byte 225 | } 226 | 227 | const X_S_I_ID uint64 = 0 228 | const X_S_W_ID uint64 = 4 229 | const X_S_QUANTITY uint64 = 5 230 | const X_S_YTD uint64 = 7 231 | const X_S_ORDER_CNT uint64 = 11 232 | const X_S_REMOTE_CNT uint64 = 13 233 | const X_S_DATA uint64 = 15 234 | const X_S_LEN uint64 = 65 235 | 236 | const ( 237 | /* Tables. */ 238 | TBLID_WAREHOUSE uint64 = iota << 56 239 | TBLID_DISTRICT 240 | TBLID_CUSTOMER 241 | TBLID_HISTORY 242 | TBLID_NEWORDER 243 | TBLID_ORDER 244 | TBLID_ORDERLINE 245 | TBLID_ITEM 246 | TBLID_STOCK 247 | /* Index. */ 248 | /* ORDER index on (O_W_ID, O_D_ID, O_C_ID). */ 249 | IDXID_ORDER 250 | ) 251 | 252 | /* NULL values. */ 253 | const O_CARRIER_ID_NULL uint8 = 0xff 254 | const OL_DELIVERY_D_NULL uint32 = 0xffffffff 255 | 256 | /** 257 | * Input definitions. 258 | */ 259 | type NewOrderInput struct { 260 | W_ID uint8 261 | D_ID uint8 262 | C_ID uint32 263 | O_ENTRY_D uint32 264 | I_IDS []uint32 265 | I_W_IDS []uint8 266 | I_QTYS []uint8 267 | } 268 | 269 | type PaymentInput struct { 270 | W_ID uint8 271 | D_ID uint8 272 | H_AMOUNT float32 273 | C_W_ID uint8 274 | C_D_ID uint8 275 | C_ID uint32 276 | H_DATE uint32 277 | } 278 | 279 | type OrderStatusInput struct { 280 | W_ID uint8 281 | D_ID uint8 282 | C_ID uint32 283 | } 284 | 285 | type DeliveryInput struct { 286 | W_ID uint8 287 | O_CARRIER_ID uint8 288 | OL_DELIVERY_D uint32 289 | } 290 | 291 | type StockLevelInput struct { 292 | W_ID uint8 293 | D_ID uint8 294 | THRESHOLD uint16 295 | } 296 | -------------------------------------------------------------------------------- /benchmark/tpcc/generator.go: -------------------------------------------------------------------------------- 1 | /** 2 | * Based on: 3 | * https://github.com/apavlo/py-tpcc/blob/7c3ff501bbe98a6a7abd3c13267523c3684b62d6/pytpcc/runtime/executor.py 4 | */ 5 | 6 | package main 7 | 8 | import ( 9 | "math/rand" 10 | ) 11 | 12 | const ( 13 | TXN_NEWORDER = iota 14 | TXN_PAYMENT 15 | TXN_ORDERSTATUS 16 | TXN_DELIVERY 17 | TXN_STOCKLEVEL 18 | ) 19 | 20 | type Generator struct { 21 | rd *rand.Rand 22 | wvec []uint64 23 | wid uint8 24 | nItems uint32 25 | nWarehouses uint8 26 | nLocalDistricts uint8 27 | nLocalCustomers uint32 28 | } 29 | 30 | func NewGenerator( 31 | wid uint8, 32 | workloads []uint64, 33 | nItems uint32, 34 | nWarehouses uint8, 35 | nLocalDistricts uint8, 36 | nLocalCustomers uint32, 37 | ) *Generator { 38 | wvec := make([]uint64, 5) 39 | var accu uint64 = 0 40 | for i, x := range workloads { 41 | wvec[i] = accu + x 42 | accu += x 43 | } 44 | rd := rand.New(rand.NewSource(int64(wid))) 45 | gen := &Generator{ 46 | rd: rd, 47 | wvec: wvec, 48 | wid: wid, 49 | nItems: nItems, 50 | nWarehouses: nWarehouses, 51 | nLocalDistricts: nLocalDistricts, 52 | nLocalCustomers: nLocalCustomers, 53 | } 54 | 55 | return gen 56 | } 57 | 58 | func (g *Generator) PickTxn() int { 59 | x := g.rd.Uint64() % 100 60 | if x < g.wvec[0] { 61 | return TXN_NEWORDER 62 | } else if x < g.wvec[1] { 63 | return TXN_PAYMENT 64 | } else if x < g.wvec[2] { 65 | return TXN_ORDERSTATUS 66 | } else if x < g.wvec[3] { 67 | return TXN_DELIVERY 68 | } else { 69 | return TXN_STOCKLEVEL 70 | } 71 | } 72 | 73 | func (g *Generator) GetNewOrderInput() *NewOrderInput { 74 | /* Generate item info (including iid, wid, and qty) in the new order. */ 75 | n := pickNOrderLines(g.rd) 76 | iids := make([]uint32, n) 77 | iwids := make([]uint8, n) 78 | iqtys := make([]uint8, n) 79 | for i := range iids { 80 | iids[i] = g.iid() 81 | iqtys[i] = pickQuantity(g.rd) 82 | /* 1% of order lines are remote. */ 83 | if trueWithProb(g.rd, 1) { 84 | iwids[i] = pickWarehouseIdExcept(g.rd, g.nWarehouses, g.wid) 85 | } else { 86 | iwids[i] = g.wid 87 | } 88 | } 89 | 90 | p := &NewOrderInput{ 91 | W_ID: g.wid, 92 | D_ID: g.did(), 93 | C_ID: g.cid(), 94 | O_ENTRY_D: getTime(), 95 | I_IDS: iids, 96 | I_W_IDS: iwids, 97 | I_QTYS: iqtys, 98 | } 99 | return p 100 | } 101 | 102 | func (g *Generator) GetPaymentInput() *PaymentInput { 103 | /* 15% of payments are remote, i.e., W_ID != C_W_ID. */ 104 | did := g.did() 105 | var cwid, cdid uint8 106 | if trueWithProb(g.rd, 15) { 107 | cwid = pickWarehouseIdExcept(g.rd, g.nWarehouses, g.wid) 108 | cdid = g.did() 109 | } else { 110 | cwid = g.wid 111 | cdid = did 112 | } 113 | 114 | p := &PaymentInput{ 115 | W_ID: g.wid, 116 | D_ID: did, 117 | H_AMOUNT: 2.5, /* TODO */ 118 | C_W_ID: cwid, 119 | C_D_ID: cdid, 120 | C_ID: g.cid(), 121 | H_DATE: getTime(), 122 | } 123 | return p 124 | } 125 | 126 | func (g *Generator) GetOrderStatusInput() *OrderStatusInput { 127 | p := &OrderStatusInput{ 128 | W_ID: g.wid, 129 | D_ID: g.did(), 130 | C_ID: g.cid(), 131 | } 132 | return p 133 | } 134 | 135 | func (g *Generator) GetDeliveryInput() *DeliveryInput { 136 | p := &DeliveryInput{ 137 | W_ID: g.wid, 138 | O_CARRIER_ID: uint8(pickBetween(g.rd, 1, 10)), 139 | OL_DELIVERY_D: getTime(), 140 | } 141 | return p 142 | } 143 | 144 | func (g *Generator) GetStockLevelInput() *StockLevelInput { 145 | p := &StockLevelInput{ 146 | W_ID: g.wid, 147 | D_ID: g.did(), 148 | THRESHOLD: uint16(pickBetween(g.rd, 10, 20)), 149 | } 150 | return p 151 | } 152 | 153 | func (g *Generator) did() uint8 { 154 | n := uint8(pickBetween(g.rd, 1, uint32(g.nLocalDistricts))) 155 | return n 156 | } 157 | 158 | func (g *Generator) cid() uint32 { 159 | /* See Silo tpcc.cc:L376. */ 160 | n := uint32(pickBetweenNonUniformly(g.rd, 1023, 259, 1, g.nLocalCustomers)) 161 | return n 162 | } 163 | 164 | func (g *Generator) iid() uint32 { 165 | /* See Silo tpcc.cc:L369. */ 166 | n := uint32(pickBetweenNonUniformly(g.rd, 8191, 7911, 1, g.nItems)) 167 | return n 168 | } 169 | -------------------------------------------------------------------------------- /benchmark/tpcc/helper.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "unsafe" 7 | ) 8 | 9 | func beforeNull(b []byte) []byte { 10 | /* Find the index of the first null character. Assembly in Go. */ 11 | i := bytes.IndexByte(b, 0) 12 | 13 | if i == -1 { 14 | /* Return `b` if no null character is found in `b`. */ 15 | return b 16 | } else { 17 | return b[:i] 18 | } 19 | } 20 | 21 | /* Both `pickBetween` and `pickBetweenNonUniformly` are inclusive. */ 22 | func pickBetween(rd *rand.Rand, min, max uint32) uint32 { 23 | n := rd.Uint32()%(max-min+1) + min 24 | return n 25 | } 26 | 27 | func pickBetweenNonUniformly(rd *rand.Rand, a, c, min, max uint32) uint32 { 28 | /* See Silo tpcc.cc:L360. */ 29 | x := (pickBetween(rd, 0, a) | pickBetween(rd, min, max)) + c 30 | y := x%(max-min+1) + min 31 | return y 32 | } 33 | 34 | func trueWithProb(rd *rand.Rand, prob uint32) bool { 35 | b := rd.Uint32()%100 < prob 36 | return b 37 | } 38 | 39 | /** 40 | * Assume: 41 | * 1. nwh > 0 42 | * 2. wid > 0 43 | * 3. wid <= nwh 44 | */ 45 | func pickWarehouseIdExcept(rd *rand.Rand, nwh, wid uint8) uint8 { 46 | if nwh == 1 { 47 | return 1 48 | } 49 | 50 | widret := wid 51 | for widret == wid { 52 | widret = uint8(rd.Uint32()%uint32(nwh)) + 1 53 | } 54 | 55 | return widret 56 | } 57 | 58 | func pickNOrderLines(rd *rand.Rand) uint8 { 59 | n := uint8(pickBetween(rd, uint32(OL_MIN_CNT), uint32(OL_MAX_CNT))) 60 | return n 61 | } 62 | 63 | func pickQuantity(rd *rand.Rand) uint8 { 64 | n := uint8(pickBetween(rd, 1, uint32(OL_MAX_QUANTITY))) 65 | return n 66 | } 67 | 68 | /* TODO */ 69 | func getTime() uint32 { 70 | return 0 71 | } 72 | 73 | func encodeBool(buf []byte, b bool, offset uint64) { 74 | if b { 75 | buf[offset] = 1 76 | } else { 77 | buf[offset] = 0 78 | } 79 | } 80 | 81 | func encodeU8(buf []byte, n uint8, offset uint64) { 82 | buf[offset] = n 83 | } 84 | 85 | func encodeU16(buf []byte, n uint16, offset uint64) { 86 | copy(buf[offset:], unsafe.Slice((*byte)(unsafe.Pointer(&n)), 2)) 87 | } 88 | 89 | func encodeU32(buf []byte, n uint32, offset uint64) { 90 | copy(buf[offset:], unsafe.Slice((*byte)(unsafe.Pointer(&n)), 4)) 91 | } 92 | 93 | func encodeU64(buf []byte, n uint64, offset uint64) { 94 | copy(buf[offset:], unsafe.Slice((*byte)(unsafe.Pointer(&n)), 8)) 95 | } 96 | 97 | func encodeF32(buf []byte, n float32, offset uint64) { 98 | copy(buf[offset:], unsafe.Slice((*byte)(unsafe.Pointer(&n)), 4)) 99 | } 100 | 101 | func encodeBytes(buf []byte, src []byte, offset uint64) { 102 | copy(buf[offset:], src) 103 | } 104 | 105 | func decodeBool(ptr *bool, s string, offset uint64) { 106 | if s[offset] == 1 { 107 | *ptr = true 108 | } else { 109 | *ptr = false 110 | } 111 | } 112 | 113 | func decodeU8(ptr *uint8, s string, offset uint64) { 114 | copy(unsafe.Slice((*byte)(unsafe.Pointer(ptr)), 1), s[offset:]) 115 | } 116 | 117 | func decodeU16(ptr *uint16, s string, offset uint64) { 118 | copy(unsafe.Slice((*byte)(unsafe.Pointer(ptr)), 2), s[offset:]) 119 | } 120 | 121 | func decodeU32(ptr *uint32, s string, offset uint64) { 122 | copy(unsafe.Slice((*byte)(unsafe.Pointer(ptr)), 4), s[offset:]) 123 | } 124 | 125 | func decodeU64(ptr *uint64, s string, offset uint64) { 126 | copy(unsafe.Slice((*byte)(unsafe.Pointer(ptr)), 8), s[offset:]) 127 | } 128 | 129 | func decodeString(buf []byte, s string, offset uint64) { 130 | copy(buf, s[offset:]) 131 | } 132 | 133 | func decodeF32(ptr *float32, s string, offset uint64) { 134 | copy(unsafe.Slice((*byte)(unsafe.Pointer(ptr)), 4), s[offset:]) 135 | } 136 | 137 | func bytesToString(bs []byte) string { 138 | return *(*string)(unsafe.Pointer(&bs)) 139 | } 140 | -------------------------------------------------------------------------------- /benchmark/tpcc/index.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | "unsafe" 6 | ) 7 | 8 | func readidx(txn *vmvcc.Txn, gkey uint64) ([]uint64, bool) { 9 | opaque, found := txn.Read(gkey) 10 | if !found { 11 | return nil, false 12 | } 13 | ents := decodeidx(opaque) 14 | return ents, true 15 | } 16 | 17 | func writeidx(txn *vmvcc.Txn, gkey uint64, ents []uint64) { 18 | s := encodeidx(ents) 19 | txn.Write(gkey, s) 20 | } 21 | 22 | /** 23 | * Encode a slice of global keys pointing to table records to an opaque string. 24 | */ 25 | func encodeidx(gkeys []uint64) string { 26 | n := uint64(len(gkeys)) 27 | buf := make([]byte, n*8+8) 28 | encodeU64(buf, n, 0) 29 | copy(buf[8:], unsafe.Slice((*byte)(unsafe.Pointer(&gkeys[0])), n*8)) 30 | return string(buf) 31 | } 32 | 33 | /** 34 | * Decode an opaque string to a slice of global keys pointing to table records. 35 | */ 36 | func decodeidx(opaque string) []uint64 { 37 | var n uint64 38 | decodeU64(&n, opaque, 0) 39 | gkeys := make([]uint64, n) 40 | copy(unsafe.Slice((*byte)(unsafe.Pointer(&gkeys[0])), n*8), opaque[8:]) 41 | return gkeys 42 | } 43 | -------------------------------------------------------------------------------- /benchmark/tpcc/loader.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "fmt" 5 | // "time" 6 | "github.com/mit-pdos/vmvcc/vmvcc" 7 | "math/rand" 8 | ) 9 | 10 | /** 11 | * Based on: 12 | * https://github.com/apavlo/py-tpcc/blob/7c3ff501bbe98a6a7abd3c13267523c3684b62d6/pytpcc/runtime/loader.py 13 | * TODO: Initialize other performance-unrelated fields according to TPC-C spec. 14 | */ 15 | 16 | func panicf(ok bool) { 17 | if !ok { 18 | panic("txn.Run should never fail here.") 19 | } 20 | } 21 | 22 | func LoadTPCCItems(txno *vmvcc.Txn, nItems uint32) { 23 | for iid := uint32(1); iid <= nItems; iid++ { 24 | body := func(txni *vmvcc.Txn) bool { 25 | loadItem(txni, iid, true) 26 | return true 27 | } 28 | panicf(txno.Run(body)) 29 | } 30 | } 31 | 32 | func LoadOneTPCCWarehouse( 33 | txno *vmvcc.Txn, wid uint8, 34 | nItems uint32, nWarehouses uint8, 35 | nLocalDistricts uint8, nLocalCustomers uint32, 36 | nInitLocalNewOrders uint32, 37 | ) { 38 | rd := rand.New(rand.NewSource(int64(wid))) 39 | 40 | /* Compute the start of history id of each pair of warehouse and district */ 41 | hid := uint64(wid-1)*uint64(nLocalDistricts)*uint64(nLocalCustomers) + 1 42 | 43 | /* Load warehouse. */ 44 | body := func(txni *vmvcc.Txn) bool { 45 | loadWarehouse(txni, wid) 46 | return true 47 | } 48 | panicf(txno.Run(body)) 49 | 50 | for did := uint8(1); did <= nLocalDistricts; did++ { 51 | // fmt.Printf("Loading (W,D) = (%d,%d).\n", wid, did) 52 | 53 | /* Load district in each warehouse. */ 54 | body = func(txni *vmvcc.Txn) bool { 55 | loadDistrict(txni, did, wid, nInitLocalNewOrders+1) 56 | return true 57 | } 58 | panicf(txno.Run(body)) 59 | 60 | /* Make a permutation of cids to be used for making Order. */ 61 | cids := make([]uint32, nLocalCustomers) 62 | /* Load customer and history. */ 63 | for cid := uint32(1); cid <= nLocalCustomers; cid++ { 64 | body = func(txni *vmvcc.Txn) bool { 65 | /* Load customer in each pair of warehouse and district. */ 66 | bc := rd.Uint32()%10 < 1 67 | loadCustomer(txni, cid, did, wid, bc) 68 | loadHistory(txni, hid, cid, did, wid) 69 | hid++ 70 | cids[cid-1] = cid 71 | return true 72 | } 73 | panicf(txno.Run(body)) 74 | } 75 | 76 | /* Shuffle `cids`. */ 77 | rd.Shuffle(len(cids), func(i, j int) { 78 | cids[i], cids[j] = cids[j], cids[i] 79 | }) 80 | 81 | /* Every customer has one initial order. */ 82 | for oid := uint32(1); oid <= nLocalCustomers; oid++ { 83 | body = func(txni *vmvcc.Txn) bool { 84 | r := uint32(OL_MAX_CNT + 1 - OL_MIN_CNT) 85 | nOrderLines := uint8(rd.Uint32()%r + uint32(OL_MIN_CNT)) 86 | isNewOrder := false 87 | if oid > nLocalCustomers-nInitLocalNewOrders { 88 | /* Load new order for the last `nInitLocalNewOrders`. */ 89 | loadNewOrder(txni, oid, did, wid) 90 | isNewOrder = true 91 | } 92 | /* Load order in each pair of warehouse and district. */ 93 | /* TODO: get current time */ 94 | var entryd uint32 = 0 95 | loadOrder( 96 | txni, oid, did, wid, cids[oid-1], entryd, 97 | nOrderLines, isNewOrder, 98 | ) 99 | 100 | /** 101 | * Load order line. 102 | * The reference implementation starts from 0, but I think it 103 | * should start from 1. 104 | * See our txn-neworder.go:L129, or their drivers/sqlitedriver.py:L276. 105 | */ 106 | for olnum := uint8(1); olnum <= nOrderLines; olnum++ { 107 | /* Load order line in each order. */ 108 | loadOrderLine( 109 | txni, rd, oid, did, wid, olnum, entryd, 110 | nWarehouses, nItems, isNewOrder, 111 | ) 112 | } 113 | return true 114 | } 115 | panicf(txno.Run(body)) 116 | } 117 | } 118 | for iid := uint32(1); iid <= nItems; iid++ { 119 | body = func(txni *vmvcc.Txn) bool { 120 | /* Load stock. */ 121 | loadStock(txni, iid, wid, false) // TODO: original 122 | return true 123 | } 124 | panicf(txno.Run(body)) 125 | } 126 | } 127 | 128 | /** 129 | * Sequential loader, could be very slow. Exists for demo/testing. 130 | */ 131 | func LoadTPCCSeq( 132 | txn *vmvcc.Txn, 133 | nItems uint32, nWarehouses uint8, 134 | nLocalDistricts uint8, nLocalCustomers uint32, nInitLocalNewOrders uint32, 135 | ) { 136 | LoadTPCCItems(txn, nItems) 137 | 138 | for wid := uint8(1); wid <= nWarehouses; wid++ { 139 | LoadOneTPCCWarehouse( 140 | txn, wid, nItems, nWarehouses, 141 | nLocalDistricts, nLocalCustomers, 142 | nInitLocalNewOrders, 143 | ) 144 | } 145 | } 146 | 147 | func loadItem(txn *vmvcc.Txn, iid uint32, original bool) { 148 | // TODO: original data 149 | InsertItem( 150 | txn, iid, 151 | 4, "item", 14.7, "data", 152 | ) 153 | } 154 | 155 | func loadWarehouse(txn *vmvcc.Txn, wid uint8) { 156 | InsertWarehouse( 157 | txn, wid, 158 | "name", "street1", "street2", "city", 159 | [2]byte{'M', 'A'}, [9]byte{'0', '2', '1', '3', '9'}, 160 | 6.25, 80.0, 161 | ) 162 | } 163 | 164 | func loadDistrict(txn *vmvcc.Txn, did uint8, wid uint8, nextoid uint32) { 165 | InsertDistrict( 166 | txn, did, wid, 167 | "name", "street1", "street2", "city", 168 | [2]byte{'M', 'A'}, [9]byte{'0', '2', '1', '3', '9'}, 169 | 6.25, 80.0, nextoid, 1, 170 | ) 171 | } 172 | 173 | func loadCustomer(txn *vmvcc.Txn, cid uint32, did uint8, wid uint8, bc bool) { 174 | var credit [2]byte 175 | if bc { 176 | credit = [2]byte{'B', 'C'} 177 | } else { 178 | credit = [2]byte{'G', 'C'} 179 | } 180 | InsertCustomer( 181 | txn, 182 | cid, did, wid, 183 | "first", [2]byte{'O', 'S'}, "last", "street1", "street2", "city", 184 | [2]byte{'M', 'A'}, [9]byte{'0', '2', '1', '3', '9'}, 185 | [16]byte{'0', '1'}, 1994, credit, 12.3, 43.1, 60.0, 80.0, 186 | 3, 9, "data", 187 | ) 188 | } 189 | 190 | func loadOrder( 191 | txn *vmvcc.Txn, oid uint32, did uint8, wid uint8, 192 | cid uint32, entryd uint32, olcnt uint8, isnew bool, 193 | ) { 194 | InsertOrder( 195 | txn, 196 | oid, did, wid, 197 | cid, entryd, 4, /* TODO: O_CARRIER_D */ 198 | olcnt, true, 199 | ) 200 | } 201 | 202 | func loadNewOrder(txn *vmvcc.Txn, oid uint32, did uint8, wid uint8) { 203 | InsertNewOrder(txn, oid, did, wid) 204 | } 205 | 206 | func loadOrderLine( 207 | txn *vmvcc.Txn, rd *rand.Rand, 208 | oid uint32, did uint8, wid uint8, olnum uint8, 209 | entryd uint32, nwarehouses uint8, nitems uint32, isnew bool, 210 | ) { 211 | /* Randomly pick one item. */ 212 | iid := pickBetween(rd, 1, nitems) 213 | 214 | /* ~1% of items are from remote warehouses. */ 215 | supplywid := wid 216 | if trueWithProb(rd, 1) { 217 | supplywid = pickWarehouseIdExcept(rd, nwarehouses, wid) 218 | } 219 | 220 | var deliveryd uint32 = entryd 221 | var olamount float32 = 0.0 222 | if isnew { 223 | olamount = float32(rd.Uint32()%999999+1) / 100 224 | deliveryd = OL_DELIVERY_D_NULL 225 | } 226 | 227 | InsertOrderLine( 228 | txn, 229 | oid, did, wid, olnum, 230 | iid, supplywid, deliveryd, 231 | OL_INIT_QUANTITY, olamount, 232 | ) 233 | } 234 | 235 | func loadHistory(txn *vmvcc.Txn, hid uint64, cid uint32, did uint8, wid uint8) { 236 | InsertHistory( 237 | txn, 238 | hid, 239 | cid, did, wid, did, wid, /* customer making orders to local district */ 240 | 12, H_INIT_AMOUNT, "", 241 | ) 242 | } 243 | 244 | func loadStock(txn *vmvcc.Txn, iid uint32, wid uint8, original bool) { 245 | var quantity uint16 = 20 // TODO 246 | var data string = "stockdata" // TODO: based on original 247 | InsertStock( 248 | txn, 249 | iid, wid, 250 | quantity, 0, 0, 0, data, 251 | ) 252 | 253 | } 254 | -------------------------------------------------------------------------------- /benchmark/tpcc/parameter.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | /** 4 | * Based on: 5 | * https://github.com/apavlo/py-tpcc/blob/7c3ff501bbe98a6a7abd3c13267523c3684b62d6/pytpcc/constants.py 6 | */ 7 | 8 | /* Item */ 9 | const N_ITEMS uint32 = 100000 10 | 11 | /* District */ 12 | const N_DISTRICTS_PER_WAREHOUSE uint8 = 10 13 | 14 | /* Customer */ 15 | const N_CUSTOMERS_PER_DISTRICT uint32 = 3000 16 | 17 | /* NewOrder */ 18 | const N_INIT_NEW_ORDERS_PER_DISTRICT uint32 = 900 19 | -------------------------------------------------------------------------------- /benchmark/tpcc/table.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | /** 8 | * We could have also added `gkey` as method of `record`. 9 | * However, the problem with this interface is that retrieving records with 10 | * index won't fit this. 11 | */ 12 | type record interface { 13 | encode() string 14 | decode(opaque string) 15 | } 16 | 17 | func readtbl(txn *vmvcc.Txn, gkey uint64, r record) bool { 18 | opaque, found := txn.Read(gkey) 19 | if !found { 20 | return false 21 | } 22 | r.decode(opaque) 23 | return true 24 | } 25 | 26 | func writetbl(txn *vmvcc.Txn, gkey uint64, r record) { 27 | s := r.encode() 28 | txn.Write(gkey, s) 29 | } 30 | 31 | func deletetbl(txn *vmvcc.Txn, gkey uint64) { 32 | txn.Delete(gkey) 33 | } 34 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-customer.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetCustomerX( 8 | txn *vmvcc.Txn, 9 | cid uint32, did uint8, wid uint8, x *Customer, 10 | ) bool { 11 | x.C_ID = cid 12 | x.C_D_ID = did 13 | x.C_W_ID = wid 14 | gkey := x.gkey() 15 | found := readtbl(txn, gkey, x) 16 | return found 17 | } 18 | 19 | func GetCustomer( 20 | txn *vmvcc.Txn, 21 | cid uint32, did uint8, wid uint8, 22 | ) (*Customer, bool) { 23 | x := &Customer{ 24 | C_ID: cid, 25 | C_D_ID: did, 26 | C_W_ID: wid, 27 | } 28 | gkey := x.gkey() 29 | found := readtbl(txn, gkey, x) 30 | return x, found 31 | } 32 | 33 | /** 34 | * Table mutator methods. 35 | */ 36 | func InsertCustomer( 37 | txn *vmvcc.Txn, 38 | cid uint32, did uint8, wid uint8, 39 | first string, middle [2]byte, last, street1, street2, city string, 40 | state [2]byte, zip [9]byte, phone [16]byte, since uint32, 41 | credit [2]byte, creditlim, discount, balance, payment float32, 42 | pcnt, dcnt uint16, data string, 43 | ) { 44 | x := &Customer{ 45 | C_ID: cid, 46 | C_D_ID: did, 47 | C_W_ID: wid, 48 | C_MIDDLE: middle, 49 | C_STATE: state, 50 | C_ZIP: zip, 51 | C_PHONE: phone, 52 | C_SINCE: since, 53 | C_CREDIT: credit, 54 | C_CREDIT_LIM: creditlim, 55 | C_DISCOUNT: discount, 56 | C_BALANCE: balance, 57 | C_YTD_PAYMENT: payment, 58 | C_PAYMENT_CNT: pcnt, 59 | C_DELIVERY_CNT: dcnt, 60 | } 61 | copy(x.C_FIRST[:], first) 62 | copy(x.C_LAST[:], last) 63 | copy(x.C_STREET_1[:], street1) 64 | copy(x.C_STREET_2[:], street2) 65 | copy(x.C_CITY[:], city) 66 | copy(x.C_DATA[:], data) 67 | gkey := x.gkey() 68 | writetbl(txn, gkey, x) 69 | } 70 | 71 | func (x *Customer) UpdateOnBadCredit( 72 | txn *vmvcc.Txn, 73 | hamount float32, cdata string, 74 | ) { 75 | x.C_BALANCE -= hamount 76 | x.C_YTD_PAYMENT += hamount 77 | x.C_PAYMENT_CNT++ 78 | copy(x.C_DATA[:], cdata) 79 | gkey := x.gkey() 80 | writetbl(txn, gkey, x) 81 | } 82 | 83 | func (x *Customer) UpdateOnGoodCredit(txn *vmvcc.Txn, hamount float32) { 84 | x.C_BALANCE -= hamount 85 | x.C_YTD_PAYMENT += hamount 86 | x.C_PAYMENT_CNT++ 87 | gkey := x.gkey() 88 | writetbl(txn, gkey, x) 89 | } 90 | 91 | func (x *Customer) IncreaseBalance(txn *vmvcc.Txn, total float32) { 92 | x.C_BALANCE += total 93 | gkey := x.gkey() 94 | writetbl(txn, gkey, x) 95 | } 96 | 97 | /** 98 | * Convert primary keys of table Customer to a global key. 99 | * Used by all both TableRead and TableWrite. 100 | */ 101 | func (x *Customer) gkey() uint64 { 102 | var gkey uint64 = uint64(x.C_ID) 103 | gkey = gkey<<8 + uint64(x.C_D_ID) 104 | gkey = gkey<<8 + uint64(x.C_W_ID) 105 | gkey += TBLID_CUSTOMER 106 | return gkey 107 | } 108 | 109 | /** 110 | * Encode a Customer record to an opaque string. 111 | * Used by TableWrite. 112 | */ 113 | func (x *Customer) encode() string { 114 | buf := make([]byte, X_C_LEN) 115 | encodeU32(buf, x.C_ID, X_C_ID) 116 | encodeU8(buf, x.C_D_ID, X_C_D_ID) 117 | encodeU8(buf, x.C_W_ID, X_C_W_ID) 118 | encodeBytes(buf, x.C_FIRST[:], X_C_FIRST) 119 | encodeBytes(buf, x.C_MIDDLE[:], X_C_MIDDLE) 120 | encodeBytes(buf, x.C_LAST[:], X_C_LAST) 121 | encodeBytes(buf, x.C_STREET_1[:], X_C_STREET_1) 122 | encodeBytes(buf, x.C_STREET_2[:], X_C_STREET_2) 123 | encodeBytes(buf, x.C_CITY[:], X_C_CITY) 124 | encodeBytes(buf, x.C_STATE[:], X_C_STATE) 125 | encodeBytes(buf, x.C_ZIP[:], X_C_ZIP) 126 | encodeBytes(buf, x.C_PHONE[:], X_C_PHONE) 127 | encodeU32(buf, x.C_SINCE, X_C_SINCE) 128 | encodeBytes(buf, x.C_CREDIT[:], X_C_CREDIT) 129 | encodeF32(buf, x.C_CREDIT_LIM, X_C_CREDIT_LIM) 130 | encodeF32(buf, x.C_DISCOUNT, X_C_DISCOUNT) 131 | encodeF32(buf, x.C_BALANCE, X_C_BALANCE) 132 | encodeF32(buf, x.C_YTD_PAYMENT, X_C_YTD_PAYMENT) 133 | encodeU16(buf, x.C_PAYMENT_CNT, X_C_PAYMENT_CNT) 134 | encodeU16(buf, x.C_DELIVERY_CNT, X_C_DELIVERY_CNT) 135 | encodeBytes(buf, x.C_DATA[:], X_C_DATA) 136 | return bytesToString(buf) 137 | } 138 | 139 | /** 140 | * Decode an opaque string to a Customer record. 141 | * Used by TableRead. 142 | */ 143 | func (x *Customer) decode(opaque string) { 144 | decodeU32(&x.C_ID, opaque, X_C_ID) 145 | decodeU8(&x.C_D_ID, opaque, X_C_D_ID) 146 | decodeU8(&x.C_W_ID, opaque, X_C_W_ID) 147 | decodeString(x.C_FIRST[:], opaque, X_C_FIRST) 148 | decodeString(x.C_MIDDLE[:], opaque, X_C_MIDDLE) 149 | decodeString(x.C_LAST[:], opaque, X_C_LAST) 150 | decodeString(x.C_STREET_1[:], opaque, X_C_STREET_1) 151 | decodeString(x.C_STREET_2[:], opaque, X_C_STREET_2) 152 | decodeString(x.C_CITY[:], opaque, X_C_CITY) 153 | decodeString(x.C_STATE[:], opaque, X_C_STATE) 154 | decodeString(x.C_ZIP[:], opaque, X_C_ZIP) 155 | decodeString(x.C_PHONE[:], opaque, X_C_PHONE) 156 | decodeU32(&x.C_SINCE, opaque, X_C_SINCE) 157 | decodeString(x.C_CREDIT[:], opaque, X_C_CREDIT) 158 | decodeF32(&x.C_CREDIT_LIM, opaque, X_C_CREDIT_LIM) 159 | decodeF32(&x.C_DISCOUNT, opaque, X_C_DISCOUNT) 160 | decodeF32(&x.C_BALANCE, opaque, X_C_BALANCE) 161 | decodeF32(&x.C_YTD_PAYMENT, opaque, X_C_YTD_PAYMENT) 162 | decodeU16(&x.C_PAYMENT_CNT, opaque, X_C_PAYMENT_CNT) 163 | decodeU16(&x.C_DELIVERY_CNT, opaque, X_C_DELIVERY_CNT) 164 | decodeString(x.C_DATA[:], opaque, X_C_DATA) 165 | } 166 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-district.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetDistrict(txn *vmvcc.Txn, did uint8, wid uint8) (*District, bool) { 8 | x := &District{ 9 | D_ID: did, 10 | D_W_ID: wid, 11 | } 12 | gkey := x.gkey() 13 | found := readtbl(txn, gkey, x) 14 | return x, found 15 | } 16 | 17 | func InsertDistrict( 18 | txn *vmvcc.Txn, 19 | did uint8, wid uint8, 20 | name, street1, street2, city string, 21 | state [2]byte, zip [9]byte, tax, ytd float32, 22 | nextoid, oldestoid uint32, 23 | ) { 24 | x := &District{ 25 | D_ID: did, 26 | D_W_ID: wid, 27 | D_STATE: state, 28 | D_ZIP: zip, 29 | D_TAX: tax, 30 | D_YTD: ytd, 31 | D_NEXT_O_ID: nextoid, 32 | D_OLD_O_ID: oldestoid, 33 | } 34 | copy(x.D_NAME[:], name) 35 | copy(x.D_STREET_1[:], street1) 36 | copy(x.D_STREET_2[:], street2) 37 | copy(x.D_CITY[:], city) 38 | gkey := x.gkey() 39 | writetbl(txn, gkey, x) 40 | } 41 | 42 | func (x *District) IncrementNextOrderId(txn *vmvcc.Txn) { 43 | x.D_NEXT_O_ID++ 44 | gkey := x.gkey() 45 | writetbl(txn, gkey, x) 46 | } 47 | 48 | func (x *District) IncrementOldestOrderId(txn *vmvcc.Txn) { 49 | x.D_OLD_O_ID++ 50 | gkey := x.gkey() 51 | writetbl(txn, gkey, x) 52 | } 53 | 54 | func (x *District) UpdateBalance(txn *vmvcc.Txn, hamount float32) { 55 | x.D_YTD += hamount 56 | gkey := x.gkey() 57 | writetbl(txn, gkey, x) 58 | } 59 | 60 | /** 61 | * Convert primary keys of table District to a global key. 62 | * Used by all both TableRead and TableWrite. 63 | */ 64 | func (x *District) gkey() uint64 { 65 | var gkey uint64 = uint64(x.D_ID) 66 | gkey = gkey<<8 + uint64(x.D_W_ID) 67 | gkey += TBLID_DISTRICT 68 | return gkey 69 | } 70 | 71 | /** 72 | * Encode a District record to an opaque string. 73 | * Used by TableWrite. 74 | */ 75 | func (x *District) encode() string { 76 | buf := make([]byte, X_D_LEN) 77 | encodeU8(buf, x.D_ID, X_D_ID) 78 | encodeU8(buf, x.D_W_ID, X_D_W_ID) 79 | encodeBytes(buf, x.D_NAME[:], X_D_NAME) 80 | encodeBytes(buf, x.D_STREET_1[:], X_D_STREET_1) 81 | encodeBytes(buf, x.D_STREET_2[:], X_D_STREET_2) 82 | encodeBytes(buf, x.D_CITY[:], X_D_CITY) 83 | encodeBytes(buf, x.D_STATE[:], X_D_STATE) 84 | encodeBytes(buf, x.D_ZIP[:], X_D_ZIP) 85 | encodeF32(buf, x.D_TAX, X_D_TAX) 86 | encodeF32(buf, x.D_YTD, X_D_YTD) 87 | encodeU32(buf, x.D_NEXT_O_ID, X_D_NEXT_O_ID) 88 | encodeU32(buf, x.D_OLD_O_ID, X_D_OLD_O_ID) 89 | return bytesToString(buf) 90 | } 91 | 92 | /** 93 | * Decode an opaque string to a District record. 94 | * Used by TableRead. 95 | */ 96 | func (x *District) decode(opaque string) { 97 | decodeU8(&x.D_ID, opaque, X_D_ID) 98 | decodeU8(&x.D_W_ID, opaque, X_D_W_ID) 99 | decodeString(x.D_NAME[:], opaque, X_D_NAME) 100 | decodeString(x.D_STREET_1[:], opaque, X_D_STREET_1) 101 | decodeString(x.D_STREET_2[:], opaque, X_D_STREET_2) 102 | decodeString(x.D_CITY[:], opaque, X_D_CITY) 103 | decodeString(x.D_STATE[:], opaque, X_D_STATE) 104 | decodeString(x.D_ZIP[:], opaque, X_D_ZIP) 105 | decodeF32(&x.D_TAX, opaque, X_D_TAX) 106 | decodeF32(&x.D_YTD, opaque, X_D_YTD) 107 | decodeU32(&x.D_NEXT_O_ID, opaque, X_D_NEXT_O_ID) 108 | decodeU32(&x.D_OLD_O_ID, opaque, X_D_OLD_O_ID) 109 | } 110 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-history.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetHistory(txn *vmvcc.Txn, hid uint64) (*History, bool) { 8 | x := &History{H_ID: hid} 9 | gkey := x.gkey() 10 | found := readtbl(txn, gkey, x) 11 | return x, found 12 | } 13 | 14 | func InsertHistory( 15 | txn *vmvcc.Txn, 16 | hid uint64, 17 | cid uint32, cdid uint8, cwid uint8, did uint8, wid uint8, 18 | date uint32, hamount float32, hdata string, 19 | ) { 20 | x := &History{ 21 | H_ID: hid, 22 | H_C_ID: cid, 23 | H_C_D_ID: cdid, 24 | H_C_W_ID: cwid, 25 | H_D_ID: did, 26 | H_W_ID: wid, 27 | H_DATE: date, 28 | H_AMOUNT: hamount, 29 | } 30 | copy(x.H_DATA[:], hdata) 31 | gkey := x.gkey() 32 | writetbl(txn, gkey, x) 33 | } 34 | 35 | /** 36 | * Convert primary keys of table History to a global key. 37 | * Used by all both TableRead and TableWrite. 38 | */ 39 | func (x *History) gkey() uint64 { 40 | var gkey uint64 = x.H_ID 41 | gkey += TBLID_HISTORY 42 | return gkey 43 | } 44 | 45 | /** 46 | * Encode a History record to an opaque string. 47 | * Used by TableWrite. 48 | */ 49 | func (x *History) encode() string { 50 | buf := make([]byte, X_H_LEN) 51 | encodeU64(buf, x.H_ID, X_H_ID) 52 | encodeU32(buf, x.H_C_ID, X_H_C_ID) 53 | encodeU8(buf, x.H_C_D_ID, X_H_C_D_ID) 54 | encodeU8(buf, x.H_C_W_ID, X_H_C_W_ID) 55 | encodeU8(buf, x.H_D_ID, X_H_D_ID) 56 | encodeU8(buf, x.H_W_ID, X_H_W_ID) 57 | encodeU32(buf, x.H_DATE, X_H_DATE) 58 | encodeF32(buf, x.H_AMOUNT, X_H_AMOUNT) 59 | encodeBytes(buf, x.H_DATA[:], X_H_DATA) 60 | return bytesToString(buf) 61 | } 62 | 63 | /** 64 | * Decode an opaque string to a History record. 65 | * Used by TableRead. 66 | */ 67 | func (x *History) decode(opaque string) { 68 | decodeU64(&x.H_ID, opaque, X_H_ID) 69 | decodeU32(&x.H_C_ID, opaque, X_H_C_ID) 70 | decodeU8(&x.H_C_D_ID, opaque, X_H_C_D_ID) 71 | decodeU8(&x.H_C_W_ID, opaque, X_H_C_W_ID) 72 | decodeU8(&x.H_D_ID, opaque, X_H_D_ID) 73 | decodeU8(&x.H_W_ID, opaque, X_H_W_ID) 74 | decodeU32(&x.H_DATE, opaque, X_H_DATE) 75 | decodeF32(&x.H_AMOUNT, opaque, X_H_AMOUNT) 76 | decodeString(x.H_DATA[:], opaque, X_H_DATA) 77 | } 78 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-item.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetItem(txn *vmvcc.Txn, iid uint32) (*Item, bool) { 8 | x := &Item{I_ID: iid} 9 | gkey := x.gkey() 10 | found := readtbl(txn, gkey, x) 11 | return x, found 12 | } 13 | 14 | /** 15 | * Table mutator methods. 16 | */ 17 | func InsertItem( 18 | txn *vmvcc.Txn, 19 | iid uint32, 20 | imid uint32, name string, price float32, data string, 21 | ) { 22 | x := &Item{ 23 | I_ID: iid, 24 | I_IM_ID: imid, 25 | I_PRICE: price, 26 | } 27 | copy(x.I_NAME[:], name) 28 | copy(x.I_DATA[:], data) 29 | gkey := x.gkey() 30 | writetbl(txn, gkey, x) 31 | } 32 | 33 | /** 34 | * Convert primary keys of table Item to a global key. 35 | * Used by all both TableRead and TableWrite. 36 | */ 37 | func (x *Item) gkey() uint64 { 38 | var gkey uint64 = uint64(x.I_ID) 39 | gkey += TBLID_ITEM 40 | return gkey 41 | } 42 | 43 | /** 44 | * Encode a Item record to an opaque string. 45 | * Used by TableWrite. 46 | */ 47 | func (x *Item) encode() string { 48 | buf := make([]byte, X_I_LEN) 49 | encodeU32(buf, x.I_ID, X_I_ID) 50 | encodeU32(buf, x.I_IM_ID, X_I_IM_ID) 51 | encodeBytes(buf, x.I_NAME[:], X_I_NAME) 52 | encodeF32(buf, x.I_PRICE, X_I_PRICE) 53 | encodeBytes(buf, x.I_DATA[:], X_I_DATA) 54 | return bytesToString(buf) 55 | } 56 | 57 | /** 58 | * Decode an opaque string to a Item record. 59 | * Used by TableRead. 60 | */ 61 | func (x *Item) decode(opaque string) { 62 | decodeU32(&x.I_ID, opaque, X_I_ID) 63 | decodeU32(&x.I_IM_ID, opaque, X_I_IM_ID) 64 | decodeString(x.I_NAME[:], opaque, X_I_NAME) 65 | decodeF32(&x.I_PRICE, opaque, X_I_PRICE) 66 | decodeString(x.I_DATA[:], opaque, X_I_DATA) 67 | } 68 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-neworder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetNewOrder(txn *vmvcc.Txn, oid uint32, did uint8, wid uint8) (*NewOrder, bool) { 8 | x := &NewOrder{ 9 | NO_O_ID: oid, 10 | NO_D_ID: did, 11 | NO_W_ID: wid, 12 | } 13 | gkey := x.gkey() 14 | found := readtbl(txn, gkey, x) 15 | return x, found 16 | } 17 | 18 | /** 19 | * Table mutator methods. 20 | */ 21 | func InsertNewOrder(txn *vmvcc.Txn, oid uint32, did uint8, wid uint8) { 22 | x := &NewOrder{ 23 | NO_O_ID: oid, 24 | NO_D_ID: did, 25 | NO_W_ID: wid, 26 | } 27 | gkey := x.gkey() 28 | writetbl(txn, gkey, x) 29 | } 30 | 31 | func DeleteNewOrder(txn *vmvcc.Txn, oid uint32, did uint8, wid uint8) { 32 | x := &NewOrder{ 33 | NO_O_ID: oid, 34 | NO_D_ID: did, 35 | NO_W_ID: wid, 36 | } 37 | gkey := x.gkey() 38 | deletetbl(txn, gkey) 39 | } 40 | 41 | /** 42 | * Convert primary keys of table NewOrder to a global key. 43 | * Used by all both TableRead and TableWrite. 44 | */ 45 | func (x *NewOrder) gkey() uint64 { 46 | var gkey uint64 = uint64(x.NO_O_ID) 47 | gkey = gkey<<8 + uint64(x.NO_D_ID) 48 | gkey = gkey<<8 + uint64(x.NO_W_ID) 49 | gkey += TBLID_NEWORDER 50 | return gkey 51 | } 52 | 53 | /** 54 | * Encode a NewOrder record to an opaque string. 55 | * Used by TableWrite. 56 | */ 57 | func (x *NewOrder) encode() string { 58 | buf := make([]byte, X_NO_LEN) 59 | encodeU32(buf, x.NO_O_ID, X_NO_O_ID) 60 | encodeU8(buf, x.NO_D_ID, X_NO_D_ID) 61 | encodeU8(buf, x.NO_W_ID, X_NO_W_ID) 62 | return bytesToString(buf) 63 | } 64 | 65 | /** 66 | * Decode an opaque string to a NewOrder record. 67 | * Used by TableRead. 68 | */ 69 | func (x *NewOrder) decode(opaque string) { 70 | decodeU32(&x.NO_O_ID, opaque, X_NO_O_ID) 71 | decodeU8(&x.NO_D_ID, opaque, X_NO_D_ID) 72 | decodeU8(&x.NO_W_ID, opaque, X_NO_W_ID) 73 | } 74 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-order.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetOrder(txn *vmvcc.Txn, oid uint32, did uint8, wid uint8) (*Order, bool) { 8 | x := &Order{ 9 | O_ID: oid, 10 | O_D_ID: did, 11 | O_W_ID: wid, 12 | } 13 | gkey := x.gkey() 14 | found := readtbl(txn, gkey, x) 15 | return x, found 16 | } 17 | 18 | func GetOrdersByIndex( 19 | txn *vmvcc.Txn, 20 | cid uint32, did uint8, wid uint8, 21 | ) []*Order { 22 | records := make([]*Order, 0, 10) 23 | 24 | /* Read the index entry. */ 25 | x := &Order{ 26 | O_C_ID: cid, 27 | O_D_ID: did, 28 | O_W_ID: wid, 29 | } 30 | gkeyidx := x.gkeyidx() 31 | gkeys, found := readidx(txn, gkeyidx) 32 | if !found { 33 | return records 34 | } 35 | 36 | /* Read all the records. */ 37 | for _, gkey := range gkeys { 38 | r := new(Order) 39 | readtbl(txn, gkey, r) 40 | records = append(records, r) 41 | } 42 | 43 | return records 44 | } 45 | 46 | /** 47 | * Table mutator methods. 48 | */ 49 | func InsertOrder( 50 | txn *vmvcc.Txn, 51 | oid uint32, did uint8, wid uint8, 52 | cid uint32, oentryd uint32, ocarrierid uint8, olcnt uint8, alllocal bool, 53 | ) { 54 | x := &Order{ 55 | O_ID: oid, 56 | O_D_ID: did, 57 | O_W_ID: wid, 58 | O_C_ID: cid, 59 | O_ENTRY_D: oentryd, 60 | O_CARRIER_ID: ocarrierid, 61 | O_OL_CNT: olcnt, 62 | O_ALL_LOCAL: alllocal, 63 | } 64 | gkey := x.gkey() 65 | writetbl(txn, gkey, x) 66 | 67 | /* Update index. */ 68 | gkeyidx := x.gkeyidx() 69 | ents, found := readidx(txn, gkeyidx) 70 | if !found { 71 | ents = make([]uint64, 0) 72 | } 73 | ents = append(ents, gkey) 74 | writeidx(txn, gkeyidx, ents) 75 | } 76 | 77 | func (x *Order) UpdateCarrier(txn *vmvcc.Txn, ocarrierid uint8) { 78 | x.O_CARRIER_ID = ocarrierid 79 | gkey := x.gkey() 80 | writetbl(txn, gkey, x) 81 | } 82 | 83 | /** 84 | * Convert primary keys of table Order to a global key. 85 | * Used by all both TableRead and TableWrite. 86 | */ 87 | func (x *Order) gkey() uint64 { 88 | var gkey uint64 = uint64(x.O_ID) 89 | gkey = gkey<<8 + uint64(x.O_D_ID) 90 | gkey = gkey<<8 + uint64(x.O_W_ID) 91 | gkey += TBLID_ORDER 92 | return gkey 93 | } 94 | 95 | func (x *Order) gkeyidx() uint64 { 96 | var gkey uint64 = uint64(x.O_C_ID) 97 | gkey = gkey<<8 + uint64(x.O_D_ID) 98 | gkey = gkey<<8 + uint64(x.O_W_ID) 99 | gkey += IDXID_ORDER 100 | return gkey 101 | } 102 | 103 | /** 104 | * Encode a Order record to an opaque string. 105 | * Used by TableWrite. 106 | */ 107 | func (x *Order) encode() string { 108 | buf := make([]byte, X_O_LEN) 109 | encodeU32(buf, x.O_ID, X_O_ID) 110 | encodeU8(buf, x.O_D_ID, X_O_D_ID) 111 | encodeU8(buf, x.O_W_ID, X_O_W_ID) 112 | encodeU32(buf, x.O_C_ID, X_O_C_ID) 113 | encodeU32(buf, x.O_ENTRY_D, X_O_ENTRY_D) 114 | encodeU8(buf, x.O_CARRIER_ID, X_O_CARRIER_ID) 115 | encodeU8(buf, x.O_OL_CNT, X_O_OL_CNT) 116 | encodeBool(buf, x.O_ALL_LOCAL, X_O_ALL_LOCAL) 117 | return bytesToString(buf) 118 | } 119 | 120 | /** 121 | * Decode an opaque string to a Order record. 122 | * Used by TableRead. 123 | */ 124 | func (x *Order) decode(opaque string) { 125 | decodeU32(&x.O_ID, opaque, X_O_ID) 126 | decodeU8(&x.O_D_ID, opaque, X_O_D_ID) 127 | decodeU8(&x.O_W_ID, opaque, X_O_W_ID) 128 | decodeU32(&x.O_C_ID, opaque, X_O_C_ID) 129 | decodeU32(&x.O_ENTRY_D, opaque, X_O_ENTRY_D) 130 | decodeU8(&x.O_CARRIER_ID, opaque, X_O_CARRIER_ID) 131 | decodeU8(&x.O_OL_CNT, opaque, X_O_OL_CNT) 132 | decodeBool(&x.O_ALL_LOCAL, opaque, X_O_ALL_LOCAL) 133 | } 134 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-orderline.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetOrderLineX( 8 | txn *vmvcc.Txn, x *OrderLine, 9 | oid uint32, did uint8, wid uint8, olnum uint8, 10 | ) bool { 11 | x.OL_O_ID = oid 12 | x.OL_D_ID = did 13 | x.OL_W_ID = wid 14 | x.OL_NUMBER = olnum 15 | gkey := x.gkey() 16 | found := readtbl(txn, gkey, x) 17 | return found 18 | } 19 | 20 | func GetOrderLine( 21 | txn *vmvcc.Txn, 22 | oid uint32, did uint8, wid uint8, olnum uint8, 23 | ) (*OrderLine, bool) { 24 | x := &OrderLine{ 25 | OL_O_ID: oid, 26 | OL_D_ID: did, 27 | OL_W_ID: wid, 28 | OL_NUMBER: olnum, 29 | } 30 | gkey := x.gkey() 31 | found := readtbl(txn, gkey, x) 32 | return x, found 33 | } 34 | 35 | /** 36 | * Table mutator methods. 37 | */ 38 | func InsertOrderLine( 39 | txn *vmvcc.Txn, 40 | oid uint32, did uint8, wid uint8, olnum uint8, 41 | iid uint32, iwid uint8, deliveryd uint32, quantity uint8, 42 | amount float32, 43 | ) { 44 | x := &OrderLine{ 45 | OL_O_ID: oid, 46 | OL_D_ID: did, 47 | OL_W_ID: wid, 48 | OL_NUMBER: olnum, 49 | OL_I_ID: iid, 50 | OL_SUPPLY_W_ID: iwid, 51 | OL_DELIVERY_D: deliveryd, 52 | OL_QUANTITY: quantity, 53 | OL_AMOUNT: amount, 54 | } 55 | gkey := x.gkey() 56 | writetbl(txn, gkey, x) 57 | } 58 | 59 | func (x *OrderLine) UpdateDeliveryDate(txn *vmvcc.Txn, deliveryd uint32) { 60 | x.OL_DELIVERY_D = deliveryd 61 | gkey := x.gkey() 62 | writetbl(txn, gkey, x) 63 | } 64 | 65 | /** 66 | * Convert primary keys of table OrderLine to a global key. 67 | * Used by all both TableRead and TableWrite. 68 | */ 69 | func (x *OrderLine) gkey() uint64 { 70 | var gkey uint64 = uint64(x.OL_O_ID) 71 | gkey = gkey<<8 + uint64(x.OL_D_ID) 72 | gkey = gkey<<8 + uint64(x.OL_W_ID) 73 | gkey = gkey<<8 + uint64(x.OL_NUMBER) 74 | gkey += TBLID_ORDERLINE 75 | return gkey 76 | } 77 | 78 | /** 79 | * Encode a OrderLine record to an opaque string. 80 | * Used by TableWrite. 81 | */ 82 | func (x *OrderLine) encode() string { 83 | buf := make([]byte, X_OL_LEN) 84 | encodeU32(buf, x.OL_O_ID, X_OL_O_ID) 85 | encodeU8(buf, x.OL_D_ID, X_OL_D_ID) 86 | encodeU8(buf, x.OL_W_ID, X_OL_W_ID) 87 | encodeU8(buf, x.OL_NUMBER, X_OL_NUMBER) 88 | encodeU32(buf, x.OL_I_ID, X_OL_I_ID) 89 | encodeU8(buf, x.OL_SUPPLY_W_ID, X_OL_SUPPLY_W_ID) 90 | encodeU32(buf, x.OL_DELIVERY_D, X_OL_DELIVERY_D) 91 | encodeU8(buf, x.OL_QUANTITY, X_OL_QUANTITY) 92 | encodeF32(buf, x.OL_AMOUNT, X_OL_AMOUNT) 93 | /* this still creates a copy, but anyway*/ 94 | return bytesToString(buf) 95 | } 96 | 97 | /** 98 | * Decode an opaque string to a OrderLine record. 99 | * Used by TableRead. 100 | */ 101 | func (x *OrderLine) decode(opaque string) { 102 | decodeU32(&x.OL_O_ID, opaque, X_OL_O_ID) 103 | decodeU8(&x.OL_D_ID, opaque, X_OL_D_ID) 104 | decodeU8(&x.OL_W_ID, opaque, X_OL_W_ID) 105 | decodeU8(&x.OL_NUMBER, opaque, X_OL_NUMBER) 106 | decodeU32(&x.OL_I_ID, opaque, X_OL_I_ID) 107 | decodeU8(&x.OL_SUPPLY_W_ID, opaque, X_OL_SUPPLY_W_ID) 108 | decodeU32(&x.OL_DELIVERY_D, opaque, X_OL_DELIVERY_D) 109 | decodeU8(&x.OL_QUANTITY, opaque, X_OL_QUANTITY) 110 | decodeF32(&x.OL_AMOUNT, opaque, X_OL_AMOUNT) 111 | } 112 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-stock.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetStock(txn *vmvcc.Txn, iid uint32, wid uint8) (*Stock, bool) { 8 | x := &Stock{ 9 | S_I_ID: iid, 10 | S_W_ID: wid, 11 | } 12 | gkey := x.gkey() 13 | found := readtbl(txn, gkey, x) 14 | return x, found 15 | } 16 | 17 | func InsertStock( 18 | txn *vmvcc.Txn, 19 | iid uint32, wid uint8, 20 | quantity uint16, ytd uint32, 21 | ordercnt, remotecnt uint16, data string, 22 | ) { 23 | x := &Stock{ 24 | S_I_ID: iid, 25 | S_W_ID: wid, 26 | S_QUANTITY: quantity, 27 | S_YTD: ytd, 28 | S_ORDER_CNT: ordercnt, 29 | S_REMOTE_CNT: remotecnt, 30 | } 31 | copy(x.S_DATA[:], data) 32 | gkey := x.gkey() 33 | writetbl(txn, gkey, x) 34 | } 35 | 36 | func (x *Stock) Update( 37 | txn *vmvcc.Txn, 38 | quantity uint16, ytd uint32, ordercnt uint16, remotecnt uint16, 39 | ) { 40 | x.S_QUANTITY = quantity 41 | x.S_YTD = ytd 42 | x.S_ORDER_CNT = ordercnt 43 | x.S_REMOTE_CNT = remotecnt 44 | gkey := x.gkey() 45 | writetbl(txn, gkey, x) 46 | } 47 | 48 | /** 49 | * Convert primary keys of table Stock to a global key. 50 | * Used by all both TableRead and TableWrite. 51 | */ 52 | func (x *Stock) gkey() uint64 { 53 | var gkey uint64 = uint64(x.S_I_ID) 54 | gkey = gkey<<8 + uint64(x.S_W_ID) 55 | gkey += TBLID_STOCK 56 | return gkey 57 | } 58 | 59 | /** 60 | * Encode a Stock record to an opaque string. 61 | * Used by TableWrite. 62 | */ 63 | func (x *Stock) encode() string { 64 | buf := make([]byte, X_S_LEN) 65 | encodeU32(buf, x.S_I_ID, X_S_I_ID) 66 | encodeU8(buf, x.S_W_ID, X_S_W_ID) 67 | encodeU16(buf, x.S_QUANTITY, X_S_QUANTITY) 68 | encodeU32(buf, x.S_YTD, X_S_YTD) 69 | encodeU16(buf, x.S_ORDER_CNT, X_S_ORDER_CNT) 70 | encodeU16(buf, x.S_REMOTE_CNT, X_S_REMOTE_CNT) 71 | encodeBytes(buf, x.S_DATA[:], X_S_DATA) 72 | return bytesToString(buf) 73 | } 74 | 75 | /** 76 | * Decode an opaque string to a Stock record. 77 | * Used by TableRead. 78 | */ 79 | func (x *Stock) decode(opaque string) { 80 | decodeU32(&x.S_I_ID, opaque, X_S_I_ID) 81 | decodeU8(&x.S_W_ID, opaque, X_S_W_ID) 82 | decodeU16(&x.S_QUANTITY, opaque, X_S_QUANTITY) 83 | decodeU32(&x.S_YTD, opaque, X_S_YTD) 84 | decodeU16(&x.S_ORDER_CNT, opaque, X_S_ORDER_CNT) 85 | decodeU16(&x.S_REMOTE_CNT, opaque, X_S_REMOTE_CNT) 86 | decodeString(x.S_DATA[:], opaque, X_S_DATA) 87 | } 88 | -------------------------------------------------------------------------------- /benchmark/tpcc/tbl-warehouse.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func GetWarehouse(txn *vmvcc.Txn, wid uint8) (*Warehouse, bool) { 8 | x := &Warehouse{W_ID: wid} 9 | gkey := x.gkey() 10 | found := readtbl(txn, gkey, x) 11 | return x, found 12 | } 13 | 14 | func InsertWarehouse( 15 | txn *vmvcc.Txn, 16 | wid uint8, 17 | name, street1, street2, city string, 18 | state [2]byte, zip [9]byte, tax, ytd float32, 19 | ) { 20 | x := &Warehouse{ 21 | W_ID: wid, 22 | W_STATE: state, 23 | W_ZIP: zip, 24 | W_TAX: tax, 25 | W_YTD: ytd, 26 | } 27 | copy(x.W_NAME[:], name) 28 | copy(x.W_STREET_1[:], street1) 29 | copy(x.W_STREET_2[:], street2) 30 | copy(x.W_CITY[:], city) 31 | gkey := x.gkey() 32 | writetbl(txn, gkey, x) 33 | } 34 | 35 | /** 36 | * Table mutator methods. 37 | */ 38 | func (x *Warehouse) UpdateBalance(txn *vmvcc.Txn, hamount float32) { 39 | x.W_YTD += hamount 40 | gkey := x.gkey() 41 | writetbl(txn, gkey, x) 42 | } 43 | 44 | /** 45 | * Convert primary keys of table Warehouse to a global key. 46 | * Used by all both TableRead and TableWrite. 47 | */ 48 | func (x *Warehouse) gkey() uint64 { 49 | var gkey uint64 = uint64(x.W_ID) 50 | gkey += TBLID_WAREHOUSE 51 | return gkey 52 | } 53 | 54 | /** 55 | * Encode a Warehouse record to an opaque string. 56 | * Used by TableWrite. 57 | */ 58 | func (x *Warehouse) encode() string { 59 | buf := make([]byte, X_W_LEN) 60 | encodeU8(buf, x.W_ID, X_W_ID) 61 | encodeBytes(buf, x.W_NAME[:], X_W_NAME) 62 | encodeBytes(buf, x.W_STREET_1[:], X_W_STREET_1) 63 | encodeBytes(buf, x.W_STREET_2[:], X_W_STREET_2) 64 | encodeBytes(buf, x.W_CITY[:], X_W_CITY) 65 | encodeBytes(buf, x.W_STATE[:], X_W_STATE) 66 | encodeBytes(buf, x.W_ZIP[:], X_W_ZIP) 67 | encodeF32(buf, x.W_TAX, X_W_TAX) 68 | encodeF32(buf, x.W_YTD, X_W_YTD) 69 | return bytesToString(buf) 70 | } 71 | 72 | /** 73 | * Decode an opaque string to a Warehouse record. 74 | * Used by TableRead. 75 | */ 76 | func (x *Warehouse) decode(opaque string) { 77 | decodeU8(&x.W_ID, opaque, X_W_ID) 78 | decodeString(x.W_NAME[:], opaque, X_W_NAME) 79 | decodeString(x.W_STREET_1[:], opaque, X_W_STREET_1) 80 | decodeString(x.W_STREET_2[:], opaque, X_W_STREET_2) 81 | decodeString(x.W_CITY[:], opaque, X_W_CITY) 82 | decodeString(x.W_STATE[:], opaque, X_W_STATE) 83 | decodeString(x.W_ZIP[:], opaque, X_W_ZIP) 84 | decodeF32(&x.W_TAX, opaque, X_W_TAX) 85 | decodeF32(&x.W_YTD, opaque, X_W_YTD) 86 | } 87 | -------------------------------------------------------------------------------- /benchmark/tpcc/tpcc.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "flag" 6 | "fmt" 7 | "github.com/mit-pdos/vmvcc/vmvcc" 8 | "log" 9 | "os" 10 | "runtime/pprof" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "time" 15 | ) 16 | 17 | var done bool 18 | 19 | type workloads []uint64 20 | 21 | func (w *workloads) String() string { 22 | return "ratio of each TPC-C transaction" 23 | } 24 | 25 | func (w *workloads) Set(s string) error { 26 | var sum uint64 = 0 27 | ratios := strings.Split(s, ",") 28 | for _, ratio := range ratios { 29 | n, err := strconv.ParseUint(ratio, 10, 64) 30 | if err != nil { 31 | return err 32 | } 33 | *w = append(*w, n) 34 | sum += n 35 | } 36 | 37 | if len(*w) != 5 || sum != 100 { 38 | return errors.New("workload should contain exactly 5 numbers summing up to 100.") 39 | } 40 | 41 | return nil 42 | } 43 | 44 | func dprintf(debug bool, format string, a ...interface{}) (n int, err error) { 45 | if debug { 46 | log.Printf(format, a...) 47 | } 48 | return 49 | } 50 | 51 | func stockscanner( 52 | db *vmvcc.DB, nWarehouses uint8, nItems uint32, 53 | interval time.Duration, 54 | ) { 55 | t := db.NewTxn() 56 | 57 | for !done { 58 | TxnStockScan(t, nWarehouses, nItems) 59 | time.Sleep(interval) 60 | } 61 | } 62 | 63 | func worker( 64 | db *vmvcc.DB, gen *Generator, 65 | chCommitted, chTotal chan []uint64, 66 | ) { 67 | nCommittedTxns := make([]uint64, 5) 68 | nTotalTxns := make([]uint64, 5) 69 | 70 | /* Create a new tranasction object. */ 71 | t := db.NewTxn() 72 | ctx := NewTPCContext() 73 | 74 | /* Start running TPC-C transactions. */ 75 | for !done { 76 | var ok bool = false 77 | x := gen.PickTxn() 78 | switch x { 79 | case TXN_NEWORDER: 80 | p := gen.GetNewOrderInput() 81 | for !ok { 82 | _, _, _, ok = TxnNewOrder(t, p) 83 | } 84 | case TXN_PAYMENT: 85 | p := gen.GetPaymentInput() 86 | for !ok { 87 | ok = TxnPayment(t, p) 88 | } 89 | case TXN_ORDERSTATUS: 90 | p := gen.GetOrderStatusInput() 91 | for !ok { 92 | _, ok = TxnOrderStatus(t, p, ctx) 93 | } 94 | case TXN_DELIVERY: 95 | p := gen.GetDeliveryInput() 96 | for !ok { 97 | _, ok = TxnDelivery(t, p) 98 | } 99 | case TXN_STOCKLEVEL: 100 | p := gen.GetStockLevelInput() 101 | for !ok { 102 | _, ok = TxnStockLevel(t, p) 103 | } 104 | } 105 | 106 | if ok { 107 | nCommittedTxns[x]++ 108 | } 109 | nTotalTxns[x]++ 110 | } 111 | chCommitted <- nCommittedTxns 112 | chTotal <- nTotalTxns 113 | } 114 | 115 | func main() { 116 | var nthrds int 117 | var duration uint64 118 | var stockscan uint64 119 | var cpuprof string 120 | var debug bool 121 | var w workloads = make([]uint64, 0) 122 | flag.IntVar(&nthrds, "nthrds", 1, "number of threads") 123 | flag.Var(&w, "workloads", "ratio of each TPC-C transaction") 124 | flag.Uint64Var(&stockscan, "stockscan", 0, "interval of stock scan transaction (0 to disable)") 125 | flag.Uint64Var(&duration, "duration", 3, "benchmark duration (seconds)") 126 | flag.StringVar(&cpuprof, "cpuprof", "", "write cpu profile to cpuprof") 127 | flag.BoolVar(&debug, "debug", true, "print debug info") 128 | flag.Parse() 129 | 130 | /** 131 | * Initialize the number of warehouses to that of threads. 132 | */ 133 | if nthrds > 255 { 134 | log.Fatalf("nthrds = %d > 255.\n", nthrds) 135 | } 136 | nWarehouses := uint8(nthrds) 137 | dprintf(debug, "Number of threads (also warehouses) = %d", nthrds) 138 | 139 | /** 140 | * Default TPC-C workload distribution: 141 | * NewOrder (45%) 142 | * Payment (43%) 143 | * OrderStatus (4%) 144 | * Delivery (4%) 145 | * StockLevel (4%) 146 | */ 147 | if len(w) != 5 { 148 | w = []uint64{45, 43, 4, 4, 4} 149 | } 150 | dprintf(debug, "Workload distribution (NO, P, OS, D, SL) = %v", w) 151 | dprintf(debug, "") 152 | 153 | chCommitted := make(chan []uint64) 154 | chTotal := make(chan []uint64) 155 | 156 | if cpuprof != "" { 157 | f, err := os.Create(cpuprof) 158 | if err != nil { 159 | log.Fatal("could not create CPU profile: ", err) 160 | } 161 | defer f.Close() // error handling omitted for example 162 | if err := pprof.StartCPUProfile(f); err != nil { 163 | log.Fatal("could not start CPU profile: ", err) 164 | } 165 | defer pprof.StopCPUProfile() 166 | } 167 | 168 | db := vmvcc.MkDB() 169 | db.ActivateGC() 170 | 171 | var nItems uint32 = N_ITEMS 172 | var nLocalDistricts uint8 = N_DISTRICTS_PER_WAREHOUSE 173 | var nLocalCustomers uint32 = N_CUSTOMERS_PER_DISTRICT 174 | var nInitLocalNewOrders uint32 = N_INIT_NEW_ORDERS_PER_DISTRICT 175 | 176 | dprintf(debug, "Loading items...") 177 | start := time.Now() 178 | txnitem := db.NewTxn() 179 | LoadTPCCItems(txnitem, nItems) 180 | elapsed := time.Since(start) 181 | dprintf(debug, "Done (%s).", elapsed) 182 | 183 | var wg sync.WaitGroup 184 | dprintf(debug, "Loading %d warehouses...", nWarehouses) 185 | start = time.Now() 186 | for wid := uint8(1); wid <= nWarehouses; wid++ { 187 | txnwh := db.NewTxn() 188 | wg.Add(1) 189 | go func(wid uint8) { 190 | defer wg.Done() 191 | LoadOneTPCCWarehouse( 192 | txnwh, wid, 193 | nItems, nWarehouses, 194 | nLocalDistricts, nLocalCustomers, nInitLocalNewOrders, 195 | ) 196 | }(wid) 197 | } 198 | wg.Wait() 199 | elapsed = time.Since(start) 200 | dprintf(debug, "Done (%s).", elapsed) 201 | 202 | if stockscan > 0 { 203 | interval := time.Duration(stockscan) * time.Millisecond 204 | go stockscanner(db, nWarehouses, nItems, interval) 205 | } 206 | 207 | dprintf(debug, "Running benchmark...") 208 | start = time.Now() 209 | done = false 210 | for wid := uint8(1); wid <= nWarehouses; wid++ { 211 | gen := NewGenerator(wid, w, nItems, nWarehouses, nLocalDistricts, nLocalCustomers) 212 | go worker(db, gen, chCommitted, chTotal) 213 | } 214 | time.Sleep(time.Duration(duration) * time.Second) 215 | done = true 216 | elapsed = time.Since(start) 217 | dprintf(debug, "Done (%s).", elapsed) 218 | dprintf(debug, "") 219 | 220 | nCommittedTxns := make([]uint64, 5) 221 | nTotalTxns := make([]uint64, 5) 222 | var committed uint64 = 0 223 | var total uint64 = 0 224 | for i := 0; i < nthrds; i++ { 225 | c := <-chCommitted 226 | t := <-chTotal 227 | for x := 0; x < 5; x++ { 228 | nCommittedTxns[x] += c[x] 229 | nTotalTxns[x] += t[x] 230 | committed += c[x] 231 | total += t[x] 232 | } 233 | } 234 | rate := float64(committed) / float64(total) * 100.0 235 | tp := float64(committed) / float64(duration) / 1000.0 236 | 237 | dprintf( 238 | debug, "Commit rate (C / T) = %.2f%% (%d / %d).\n", 239 | rate, committed, total, 240 | ) 241 | dprintf( 242 | debug, "\tNewOrder (C / T) = %.2f%% (%d / %d).\n", 243 | float64(nCommittedTxns[0])/float64(nTotalTxns[0])*100.0, 244 | nCommittedTxns[0], nTotalTxns[0], 245 | ) 246 | dprintf( 247 | debug, "\tPayment (C / T) = %.2f%% (%d / %d).\n", 248 | float64(nCommittedTxns[1])/float64(nTotalTxns[1])*100.0, 249 | nCommittedTxns[1], nTotalTxns[1], 250 | ) 251 | dprintf( 252 | debug, "\tOrderStatus (C / T) = %.2f%% (%d / %d).\n", 253 | float64(nCommittedTxns[2])/float64(nTotalTxns[2])*100.0, 254 | nCommittedTxns[2], nTotalTxns[2], 255 | ) 256 | dprintf( 257 | debug, "\tDelivery (C / T) = %.2f%% (%d / %d).\n", 258 | float64(nCommittedTxns[3])/float64(nTotalTxns[3])*100.0, 259 | nCommittedTxns[3], nTotalTxns[3], 260 | ) 261 | dprintf( 262 | debug, "\tStockLevel (C / T) = %.2f%% (%d / %d).\n", 263 | float64(nCommittedTxns[4])/float64(nTotalTxns[4])*100.0, 264 | nCommittedTxns[4], nTotalTxns[4], 265 | ) 266 | dprintf(debug, "Throughput = %.3f (K txns/s).\n", tp) 267 | 268 | fmt.Printf("%d, %d, %d, %.3f, %.2f\n", 269 | nthrds, stockscan, duration, tp, rate) 270 | } 271 | -------------------------------------------------------------------------------- /benchmark/tpcc/tpcc_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "fmt" 7 | "github.com/mit-pdos/vmvcc/vmvcc" 8 | "github.com/stretchr/testify/assert" 9 | "testing" 10 | ) 11 | 12 | /** 13 | * Tests for basic data layout/size and encoding primitives. 14 | */ 15 | func TestTableId(t *testing.T) { 16 | fmt.Printf("%.15x\n", TBLID_WAREHOUSE) 17 | fmt.Printf("%x\n", TBLID_DISTRICT) 18 | fmt.Printf("%x\n", TBLID_CUSTOMER) 19 | fmt.Printf("%x\n", TBLID_HISTORY) 20 | fmt.Printf("%x\n", TBLID_NEWORDER) 21 | fmt.Printf("%x\n", TBLID_ORDER) 22 | fmt.Printf("%x\n", TBLID_ORDERLINE) 23 | fmt.Printf("%x\n", TBLID_ITEM) 24 | fmt.Printf("%x\n", TBLID_STOCK) 25 | fmt.Printf("%x\n", IDXID_ORDER) 26 | } 27 | 28 | func TestGkey(t *testing.T) { 29 | warehouse := Warehouse{W_ID: 1} 30 | fmt.Printf("%.15x\n", warehouse.gkey()) 31 | district := District{D_ID: 1, D_W_ID: 1} 32 | fmt.Printf("%x\n", district.gkey()) 33 | customer := Customer{C_ID: 1, C_D_ID: 1, C_W_ID: 1} 34 | fmt.Printf("%x\n", customer.gkey()) 35 | history := History{H_ID: 1} 36 | fmt.Printf("%x\n", history.gkey()) 37 | neworder := NewOrder{NO_O_ID: 1, NO_D_ID: 1, NO_W_ID: 1} 38 | fmt.Printf("%x\n", neworder.gkey()) 39 | order := Order{O_ID: 1, O_D_ID: 1, O_W_ID: 1} 40 | fmt.Printf("%x\n", order.gkey()) 41 | orderline := OrderLine{OL_O_ID: 1, OL_D_ID: 1, OL_W_ID: 1, OL_NUMBER: 1} 42 | fmt.Printf("%x\n", orderline.gkey()) 43 | item := Item{I_ID: 1} 44 | fmt.Printf("%x\n", item.gkey()) 45 | stock := Stock{S_I_ID: 1, S_W_ID: 1} 46 | fmt.Printf("%x\n", stock.gkey()) 47 | } 48 | 49 | func encodeSlow(x any) uint64 { 50 | buf := new(bytes.Buffer) 51 | binary.Write(buf, binary.LittleEndian, x) 52 | return uint64(len(buf.String())) 53 | } 54 | 55 | func TestRecordSize(t *testing.T) { 56 | assert := assert.New(t) 57 | warehouse := Warehouse{W_ID: 1} 58 | assert.Equal(encodeSlow(warehouse), X_W_LEN) 59 | district := District{D_ID: 1, D_W_ID: 1} 60 | assert.Equal(encodeSlow(district), X_D_LEN) 61 | customer := Customer{C_ID: 1, C_D_ID: 1, C_W_ID: 1} 62 | assert.Equal(encodeSlow(customer), X_C_LEN) 63 | history := History{H_ID: 1} 64 | assert.Equal(encodeSlow(history), X_H_LEN) 65 | neworder := NewOrder{NO_O_ID: 1, NO_D_ID: 1, NO_W_ID: 1} 66 | assert.Equal(encodeSlow(neworder), X_NO_LEN) 67 | order := Order{O_ID: 1, O_D_ID: 1, O_W_ID: 1} 68 | assert.Equal(encodeSlow(order), X_O_LEN) 69 | orderline := OrderLine{OL_O_ID: 1, OL_D_ID: 1, OL_W_ID: 1, OL_NUMBER: 1} 70 | assert.Equal(encodeSlow(orderline), X_OL_LEN) 71 | item := Item{I_ID: 1} 72 | assert.Equal(encodeSlow(item), X_I_LEN) 73 | stock := Stock{S_I_ID: 1, S_W_ID: 1} 74 | assert.Equal(encodeSlow(stock), X_S_LEN) 75 | } 76 | 77 | func TestEncodeDecodeCustomer(t *testing.T) { 78 | assert := assert.New(t) 79 | c := &Customer{ 80 | C_ID: 14, 81 | C_W_ID: 223, 82 | C_LAST: [16]byte{4, 9}, 83 | } 84 | s := c.encode() 85 | d := new(Customer) 86 | d.decode(s) 87 | assert.Equal(*c, *d) 88 | } 89 | 90 | func TestIndexEncodeDecode(t *testing.T) { 91 | assert := assert.New(t) 92 | gkeys := []uint64{4, 7, 2, 1, 81} 93 | fmt.Printf("len(encodeidx(gkeys)) = %d\n", len(encodeidx(gkeys))) 94 | fmt.Printf("%v\n", decodeidx(encodeidx(gkeys))) 95 | assert.Equal(gkeys, decodeidx(encodeidx(gkeys))) 96 | } 97 | 98 | /** 99 | * Test for table operations. 100 | */ 101 | func TestTableWarehouse(t *testing.T) { 102 | assert := assert.New(t) 103 | db := vmvcc.MkDB() 104 | txno := db.NewTxn() 105 | 106 | /* Insert a Warehouse record. */ 107 | body := func(txn *vmvcc.Txn) bool { 108 | InsertWarehouse( 109 | txn, 110 | 41, 111 | "name", "street1", "street2", "city", 112 | [2]byte{'M', 'A'}, [9]byte{'0', '2', '1', '3', '9'}, 113 | 6.25, 80.0, 114 | ) 115 | return true 116 | } 117 | ok := txno.Run(body) 118 | assert.Equal(true, ok) 119 | 120 | /* Read it, update it, and read it again in one transaction. */ 121 | body = func(txn *vmvcc.Txn) bool { 122 | x, found := GetWarehouse(txn, 41) 123 | assert.Equal(true, found) 124 | assert.Equal(uint8(41), x.W_ID) 125 | assert.Equal(float32(6.25), x.W_TAX) 126 | assert.Equal(float32(80.0), x.W_YTD) 127 | 128 | x.UpdateBalance(txn, 10.0) 129 | 130 | x, _ = GetWarehouse(txn, 41) 131 | assert.Equal(float32(90.0), x.W_YTD) 132 | return true 133 | } 134 | ok = txno.Run(body) 135 | assert.Equal(true, ok) 136 | 137 | /* Read it again. */ 138 | body = func(txn *vmvcc.Txn) bool { 139 | x, found := GetWarehouse(txn, 41) 140 | assert.Equal(true, found) 141 | assert.Equal(uint8(41), x.W_ID) 142 | assert.Equal(float32(6.25), x.W_TAX) 143 | assert.Equal(float32(90.0), x.W_YTD) 144 | return true 145 | } 146 | ok = txno.Run(body) 147 | assert.Equal(true, ok) 148 | } 149 | 150 | func TestTableDistrict(t *testing.T) { 151 | assert := assert.New(t) 152 | db := vmvcc.MkDB() 153 | txno := db.NewTxn() 154 | 155 | /* Insert a District record. */ 156 | body := func(txn *vmvcc.Txn) bool { 157 | InsertDistrict( 158 | txn, 159 | 95, 41, 160 | "name", "street1", "street2", "city", 161 | [2]byte{'M', 'A'}, [9]byte{'0', '2', '1', '3', '9'}, 162 | 6.25, 80.0, 1, 1, 163 | ) 164 | return true 165 | } 166 | ok := txno.Run(body) 167 | assert.Equal(true, ok) 168 | 169 | /* Read it, update it, and read it again in one transaction. */ 170 | body = func(txn *vmvcc.Txn) bool { 171 | x, found := GetDistrict(txn, 95, 41) 172 | assert.Equal(true, found) 173 | assert.Equal(uint8(95), x.D_ID) 174 | assert.Equal(uint8(41), x.D_W_ID) 175 | assert.Equal(float32(6.25), x.D_TAX) 176 | assert.Equal(float32(80.0), x.D_YTD) 177 | assert.Equal(uint32(1), x.D_NEXT_O_ID) 178 | assert.Equal(uint32(1), x.D_OLD_O_ID) 179 | 180 | x.IncrementNextOrderId(txn) 181 | x.IncrementOldestOrderId(txn) 182 | x.UpdateBalance(txn, 10.0) 183 | 184 | x, _ = GetDistrict(txn, 95, 41) 185 | assert.Equal(float32(90.0), x.D_YTD) 186 | assert.Equal(uint32(2), x.D_NEXT_O_ID) 187 | assert.Equal(uint32(2), x.D_OLD_O_ID) 188 | return true 189 | } 190 | ok = txno.Run(body) 191 | assert.Equal(true, ok) 192 | 193 | /* Read it again. */ 194 | body = func(txn *vmvcc.Txn) bool { 195 | x, found := GetDistrict(txn, 95, 41) 196 | assert.Equal(true, found) 197 | assert.Equal(float32(90.0), x.D_YTD) 198 | assert.Equal(uint32(2), x.D_NEXT_O_ID) 199 | assert.Equal(uint32(2), x.D_OLD_O_ID) 200 | return true 201 | } 202 | ok = txno.Run(body) 203 | assert.Equal(true, ok) 204 | } 205 | 206 | func TestTableCustomer(t *testing.T) { 207 | assert := assert.New(t) 208 | db := vmvcc.MkDB() 209 | txno := db.NewTxn() 210 | 211 | /* Insert a Customer record. */ 212 | body := func(txn *vmvcc.Txn) bool { 213 | InsertCustomer( 214 | txn, 215 | 20, 95, 41, 216 | "first", [2]byte{'O', 'S'}, "last", "street1", "street2", "city", 217 | [2]byte{'M', 'A'}, [9]byte{'0', '2', '1', '3', '9'}, 218 | [16]byte{'0', '1'}, 1994, [2]byte{'B', 'C'}, 12.3, 43.1, 60.0, 80.0, 219 | 3, 9, "data", 220 | ) 221 | return true 222 | } 223 | ok := txno.Run(body) 224 | assert.Equal(true, ok) 225 | 226 | /* Read it, update it, and read it again in one transaction. */ 227 | body = func(txn *vmvcc.Txn) bool { 228 | x, found := GetCustomer(txn, 20, 95, 41) 229 | assert.Equal(true, found) 230 | assert.Equal(uint32(20), x.C_ID) 231 | assert.Equal(uint8(95), x.C_D_ID) 232 | assert.Equal(uint8(41), x.C_W_ID) 233 | assert.Equal(float32(60.0), x.C_BALANCE) 234 | assert.Equal(float32(80.0), x.C_YTD_PAYMENT) 235 | assert.Equal(uint16(3), x.C_PAYMENT_CNT) 236 | assert.Equal("data", string(beforeNull(x.C_DATA[:]))) 237 | 238 | x.UpdateOnBadCredit(txn, 10.0, "Hello Customer") 239 | 240 | x, _ = GetCustomer(txn, 20, 95, 41) 241 | assert.Equal(float32(50.0), x.C_BALANCE) 242 | assert.Equal(float32(90.0), x.C_YTD_PAYMENT) 243 | assert.Equal(uint16(4), x.C_PAYMENT_CNT) 244 | assert.Equal("Hello Customer", string(beforeNull(x.C_DATA[:]))) 245 | return true 246 | } 247 | ok = txno.Run(body) 248 | assert.Equal(true, ok) 249 | 250 | /* Read it again. */ 251 | body = func(txn *vmvcc.Txn) bool { 252 | x, found := GetCustomer(txn, 20, 95, 41) 253 | assert.Equal(true, found) 254 | assert.Equal(uint32(20), x.C_ID) 255 | assert.Equal(uint8(95), x.C_D_ID) 256 | assert.Equal(uint8(41), x.C_W_ID) 257 | assert.Equal(float32(50.0), x.C_BALANCE) 258 | assert.Equal(float32(90.0), x.C_YTD_PAYMENT) 259 | assert.Equal(uint16(4), x.C_PAYMENT_CNT) 260 | assert.Equal("Hello Customer", string(beforeNull(x.C_DATA[:]))) 261 | return true 262 | } 263 | ok = txno.Run(body) 264 | assert.Equal(true, ok) 265 | } 266 | 267 | /** 268 | * Tests for TPC-C database loader. 269 | */ 270 | func TestLoader(t *testing.T) { 271 | assert := assert.New(t) 272 | db := vmvcc.MkDB() 273 | txno := db.NewTxn() 274 | 275 | var ok bool 276 | // var nItems uint32 = N_ITEMS 277 | // var nWarehouses uint8 = 10 278 | // var nLocalDistricts uint8 = N_DISTRICTS_PER_WAREHOUSE 279 | // var nLocalCustomers uint32 = N_CUSTOMERS_PER_DISTRICT 280 | // var nInitLocalNewOrders uint32 = N_INIT_NEW_ORDERS_PER_DISTRICT 281 | var nItems uint32 = 10 282 | var nWarehouses uint8 = 2 283 | var nLocalDistricts uint8 = 10 284 | var nLocalCustomers uint32 = 100 285 | var nInitLocalNewOrders uint32 = 30 286 | var nInitLocalOrders = nLocalCustomers 287 | assert.LessOrEqual(nInitLocalNewOrders, nInitLocalOrders) 288 | LoadTPCCSeq( 289 | txno, 290 | nItems, nWarehouses, 291 | nLocalDistricts, nLocalCustomers, 292 | nInitLocalNewOrders, 293 | ) 294 | 295 | /* Testing items. */ 296 | body := func(txni *vmvcc.Txn) bool { 297 | var item *Item 298 | var found bool 299 | item, found = GetItem(txni, 0) 300 | assert.Equal(false, found) 301 | 302 | item, found = GetItem(txni, 1) 303 | assert.Equal(true, found) 304 | assert.Equal(uint32(1), item.I_ID) 305 | assert.Equal(float32(14.7), item.I_PRICE) 306 | 307 | item, found = GetItem(txni, nItems/2) 308 | assert.Equal(true, found) 309 | assert.Equal(nItems/2, item.I_ID) 310 | 311 | item, found = GetItem(txni, nItems) 312 | assert.Equal(true, found) 313 | assert.Equal(nItems, item.I_ID) 314 | 315 | item, found = GetItem(txni, nItems+1) 316 | assert.Equal(false, found) 317 | 318 | /* TODO: Testing whether ~10% of items contain "ORIGINAL" in I_DATA. */ 319 | return true 320 | } 321 | ok = txno.Run(body) 322 | assert.Equal(true, ok) 323 | 324 | /* Testing Warehouse. */ 325 | body = func(txni *vmvcc.Txn) bool { 326 | var warehouse *Warehouse 327 | var found bool 328 | for wid := uint8(0); wid <= nWarehouses+1; wid++ { 329 | warehouse, found = GetWarehouse(txni, wid) 330 | if wid < 1 || wid > nWarehouses { 331 | assert.Equal(false, found) 332 | } else { 333 | assert.Equal(true, found) 334 | assert.Equal(wid, warehouse.W_ID) 335 | } 336 | } 337 | return true 338 | } 339 | ok = txno.Run(body) 340 | assert.Equal(true, ok) 341 | 342 | /* Testing District. */ 343 | body = func(txni *vmvcc.Txn) bool { 344 | var district *District 345 | var found bool 346 | for wid := uint8(0); wid <= nWarehouses+1; wid++ { 347 | for did := uint8(0); did <= nLocalDistricts+1; did++ { 348 | district, found = GetDistrict(txni, did, wid) 349 | if wid < 1 || wid > nWarehouses || did < 1 || did > nLocalDistricts { 350 | assert.Equal(false, found) 351 | } else { 352 | assert.Equal(true, found) 353 | assert.Equal(did, district.D_ID) 354 | assert.Equal(wid, district.D_W_ID) 355 | } 356 | } 357 | } 358 | return true 359 | } 360 | ok = txno.Run(body) 361 | assert.Equal(true, ok) 362 | 363 | /* Testing Customer. */ 364 | body = func(txni *vmvcc.Txn) bool { 365 | /* For testing distribution. */ 366 | var cntBCCustomers uint64 = 0 367 | var cntTotalCustomers uint64 = 0 368 | 369 | var customer *Customer 370 | var found bool 371 | for wid := uint8(0); wid <= nWarehouses+1; wid++ { 372 | for did := uint8(0); did <= nLocalDistricts+1; did++ { 373 | for cid := uint32(0); cid <= nLocalCustomers+1; cid++ { 374 | customer, found = GetCustomer(txni, cid, did, wid) 375 | if wid < 1 || wid > nWarehouses || 376 | did < 1 || did > nLocalDistricts || 377 | cid < 1 || cid > nLocalCustomers { 378 | assert.Equal(false, found) 379 | } else { 380 | assert.Equal(true, found) 381 | assert.Equal(cid, customer.C_ID) 382 | assert.Equal(did, customer.C_D_ID) 383 | assert.Equal(wid, customer.C_W_ID) 384 | cntTotalCustomers++ 385 | if customer.C_CREDIT == [2]byte{'B', 'C'} { 386 | cntBCCustomers++ 387 | } 388 | } 389 | } 390 | } 391 | } 392 | 393 | /* Check that ~10% of customers have a bad credit. */ 394 | ratioBC := float64(cntBCCustomers) / float64(cntTotalCustomers) * 100.0 395 | fmt.Printf("Ratio of customers with bad credits = %f%% (sholud be ~10%%).\n", ratioBC) 396 | return true 397 | } 398 | ok = txno.Run(body) 399 | assert.Equal(true, ok) 400 | 401 | /* Testing History. */ 402 | body = func(txni *vmvcc.Txn) bool { 403 | var history *History 404 | var found bool 405 | var nHistory uint64 = uint64(nWarehouses) * uint64(nLocalDistricts) * uint64(nLocalCustomers) 406 | for hid := uint64(0); hid <= nHistory+1; hid++ { 407 | history, found = GetHistory(txni, hid) 408 | if hid < 1 || hid > nHistory { 409 | assert.Equal(false, found) 410 | } else { 411 | assert.Equal(true, found) 412 | assert.Equal(hid, history.H_ID) 413 | assert.Less(uint8(0), history.H_W_ID) 414 | assert.LessOrEqual(history.H_W_ID, nWarehouses) 415 | assert.Less(uint8(0), history.H_D_ID) 416 | assert.LessOrEqual(history.H_D_ID, nLocalDistricts) 417 | assert.Less(uint32(0), history.H_C_ID) 418 | assert.LessOrEqual(history.H_C_ID, nLocalCustomers) 419 | } 420 | } 421 | return true 422 | } 423 | ok = txno.Run(body) 424 | assert.Equal(true, ok) 425 | 426 | /* Testing Order, NewOrder, and OrderLine. */ 427 | body = func(txni *vmvcc.Txn) bool { 428 | /* For testing distribution. */ 429 | var cntTotalItems uint64 = 0 430 | var cntRemoteItems uint64 = 0 431 | 432 | var order *Order 433 | var neworder *NewOrder 434 | var orderline *OrderLine 435 | var found bool 436 | for wid := uint8(0); wid <= nWarehouses+1; wid++ { 437 | for did := uint8(0); did <= nLocalDistricts+1; did++ { 438 | for oid := uint32(0); oid <= nInitLocalOrders+1; oid++ { 439 | /* Order. */ 440 | order, found = GetOrder(txni, oid, did, wid) 441 | if wid < 1 || wid > nWarehouses || 442 | did < 1 || did > nLocalDistricts || 443 | oid < 1 || oid > nInitLocalOrders { 444 | assert.Equal(false, found) 445 | } else { 446 | assert.Equal(true, found) 447 | assert.Equal(oid, order.O_ID) 448 | assert.Equal(did, order.O_D_ID) 449 | assert.Equal(wid, order.O_W_ID) 450 | assert.LessOrEqual(uint32(1), order.O_C_ID) 451 | assert.LessOrEqual(order.O_C_ID, nLocalCustomers) 452 | assert.LessOrEqual(OL_MIN_CNT, order.O_OL_CNT) 453 | assert.LessOrEqual(order.O_OL_CNT, OL_MAX_CNT) 454 | } 455 | 456 | /* NewOrder, (newoidlb, newoidub] are new orders. */ 457 | newoidlb := nInitLocalOrders - nInitLocalNewOrders 458 | newoidub := nInitLocalOrders 459 | neworder, found = GetNewOrder(txni, oid, did, wid) 460 | if wid < 1 || wid > nWarehouses || 461 | did < 1 || did > nLocalDistricts || 462 | oid <= newoidlb || oid > newoidub { 463 | assert.Equal(false, found) 464 | } else { 465 | assert.Equal(true, found) 466 | assert.Equal(oid, neworder.NO_O_ID) 467 | assert.Equal(did, neworder.NO_D_ID) 468 | assert.Equal(wid, neworder.NO_W_ID) 469 | } 470 | 471 | /* Orderline. */ 472 | olcnt := order.O_OL_CNT 473 | for olnum := uint8(0); olnum <= olcnt+1; olnum++ { 474 | orderline, found = GetOrderLine(txni, oid, did, wid, olnum) 475 | if wid < 1 || wid > nWarehouses || 476 | did < 1 || did > nLocalDistricts || 477 | oid < 1 || oid > nInitLocalOrders || 478 | olnum < 1 || olnum > olcnt { 479 | assert.Equal(false, found) 480 | } else { 481 | assert.Equal(true, found) 482 | assert.Equal(oid, orderline.OL_O_ID) 483 | assert.Equal(did, orderline.OL_D_ID) 484 | assert.Equal(wid, orderline.OL_W_ID) 485 | assert.Equal(olnum, orderline.OL_NUMBER) 486 | olamount := orderline.OL_AMOUNT 487 | if orderline.OL_DELIVERY_D == OL_DELIVERY_D_NULL { 488 | /* This is a new order. */ 489 | assert.LessOrEqual(float32(0.01), olamount) 490 | assert.LessOrEqual(olamount, float32(9999.99)) 491 | } else { 492 | assert.Equal(float32(0.0), olamount) 493 | } 494 | cntTotalItems++ 495 | if orderline.OL_W_ID != orderline.OL_SUPPLY_W_ID { 496 | cntRemoteItems++ 497 | } 498 | } 499 | } 500 | } 501 | } 502 | } 503 | /* Check that the remote orderline is about ~1%. */ 504 | ratioRemoteItems := float64(cntRemoteItems) / float64(cntTotalItems) * 100.0 505 | fmt.Printf("Ratio of remote items = %f%% (sholud be ~1%%).\n", ratioRemoteItems) 506 | return true 507 | } 508 | ok = txno.Run(body) 509 | assert.Equal(true, ok) 510 | 511 | /* Testing Stock. */ 512 | body = func(txni *vmvcc.Txn) bool { 513 | var stock *Stock 514 | var found bool 515 | for wid := uint8(0); wid <= nWarehouses+1; wid++ { 516 | for iid := uint32(0); iid <= nItems+1; iid++ { 517 | stock, found = GetStock(txni, iid, wid) 518 | if wid < 1 || wid > nWarehouses || iid < 1 || iid > nItems { 519 | assert.Equal(false, found) 520 | } else { 521 | assert.Equal(true, found) 522 | assert.Equal(iid, stock.S_I_ID) 523 | assert.Equal(wid, stock.S_W_ID) 524 | assert.Equal(uint16(20), stock.S_QUANTITY) 525 | } 526 | } 527 | } 528 | 529 | return true 530 | } 531 | ok = txno.Run(body) 532 | assert.Equal(true, ok) 533 | } 534 | 535 | /** 536 | * Tests for TPC-C "business" transactions. 537 | */ 538 | func TestPayment(t *testing.T) { 539 | assert := assert.New(t) 540 | db := vmvcc.MkDB() 541 | txno := db.NewTxn() 542 | 543 | // TODO: randomly generating below according to TPC-C spec 544 | cid := uint32(1) 545 | cdid := uint8(2) 546 | cwid := uint8(3) 547 | did := uint8(4) 548 | wid := uint8(5) 549 | hamount := float32(10.0) 550 | 551 | /* Insert a Customer record. */ 552 | var ok bool 553 | body := func(txn *vmvcc.Txn) bool { 554 | InsertCustomer( 555 | txn, 556 | 20, 95, 41, 557 | "first", [2]byte{'O', 'S'}, "last", "street1", "street2", "city", 558 | [2]byte{'M', 'A'}, [9]byte{'0', '2', '1', '3', '9'}, 559 | [16]byte{'0', '1'}, 1994, [2]byte{'B', 'C'}, 12.3, 43.1, 60.0, 80.0, 560 | 3, 9, "data", 561 | ) 562 | return true 563 | } 564 | ok = txno.Run(body) 565 | assert.Equal(true, ok) 566 | 567 | /* Run Payment transaction twice. */ 568 | p := &PaymentInput{ 569 | W_ID: wid, 570 | D_ID: did, 571 | H_AMOUNT: hamount, 572 | C_W_ID: cwid, 573 | C_D_ID: cdid, 574 | C_ID: cid, 575 | } 576 | ok = TxnPayment(txno, p) 577 | assert.Equal(true, ok) 578 | ok = TxnPayment(txno, p) 579 | assert.Equal(true, ok) 580 | } 581 | -------------------------------------------------------------------------------- /benchmark/tpcc/tpcctx.go: -------------------------------------------------------------------------------- 1 | /** 2 | * TPC-C context to reduce memory presure induced by the benchmark itself. 3 | */ 4 | package main 5 | 6 | type TPCContext struct { 7 | warehouse Warehouse 8 | district District 9 | customer Customer 10 | history History 11 | neworder NewOrder 12 | orderline OrderLine 13 | items []Item 14 | stock Stock 15 | noparam NewOrderInput 16 | pparam PaymentInput 17 | osparam OrderStatusInput 18 | dparam DeliveryInput 19 | slparam StockLevelInput 20 | } 21 | 22 | func NewTPCContext() *TPCContext { 23 | ctx := new(TPCContext) 24 | ctx.items = make([]Item, OL_MAX_CNT) 25 | return ctx 26 | } 27 | -------------------------------------------------------------------------------- /benchmark/tpcc/txn-delivery.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "fmt" 5 | "github.com/mit-pdos/vmvcc/vmvcc" 6 | ) 7 | 8 | type DeliveryNewOrderResult struct { 9 | NO_D_ID uint8 10 | NO_O_ID uint32 11 | } 12 | 13 | type DeliveryResult struct { 14 | NO_RES []DeliveryNewOrderResult 15 | } 16 | 17 | func delivery( 18 | txn *vmvcc.Txn, 19 | wid uint8, did uint8, carrierid uint8, deliveryd uint32, 20 | res *DeliveryResult, 21 | ) bool { 22 | /* Find the oldest in-progress order. */ 23 | district, _ := GetDistrict(txn, did, wid) 24 | oid := district.D_OLD_O_ID 25 | district.IncrementOldestOrderId(txn) 26 | 27 | _, found := GetNewOrder(txn, oid, did, wid) 28 | if !found { 29 | return true 30 | } 31 | 32 | /* Append to result. */ 33 | noid := DeliveryNewOrderResult{NO_D_ID: did, NO_O_ID: oid} 34 | res.NO_RES = append(res.NO_RES, noid) 35 | 36 | /* Get the customer id of this order. */ 37 | order, _ := GetOrder(txn, oid, did, wid) 38 | cid := order.O_C_ID 39 | 40 | /* Update the carrier id of this order. */ 41 | order.UpdateCarrier(txn, carrierid) 42 | 43 | /* Sum the total of this order. */ 44 | var total float32 = 0 45 | for olnum := uint8(1); olnum <= 15; olnum++ { 46 | /* Get the order line. */ 47 | ol, found := GetOrderLine(txn, oid, did, wid, olnum) 48 | if !found { 49 | break 50 | } 51 | /* Update the delivery date of each order line. */ 52 | ol.UpdateDeliveryDate(txn, deliveryd) 53 | total += ol.OL_AMOUNT 54 | } 55 | 56 | /* Delete this order from NewOrder. */ 57 | DeleteNewOrder(txn, oid, did, wid) 58 | 59 | /* Update the customer with */ 60 | customer, _ := GetCustomer(txn, cid, did, wid) 61 | customer.IncreaseBalance(txn, total) 62 | 63 | return true 64 | } 65 | 66 | func TxnDelivery(txno *vmvcc.Txn, p *DeliveryInput) ([]*DeliveryResult, bool) { 67 | /* prepare output */ 68 | ress := make([]*DeliveryResult, 10) 69 | /* prepare input */ 70 | wid := p.W_ID 71 | ocarrierid := p.O_CARRIER_ID 72 | oldeliveryd := p.OL_DELIVERY_D 73 | for did := uint8(1); did <= 10; did++ { 74 | res := new(DeliveryResult) 75 | body := func(txni *vmvcc.Txn) bool { 76 | return delivery(txni, wid, did, ocarrierid, oldeliveryd, res) 77 | } 78 | /* Restart this transaction if fails. */ 79 | for !txno.Run(body) { 80 | } 81 | ress[did-1] = res 82 | } 83 | return ress, true 84 | } 85 | -------------------------------------------------------------------------------- /benchmark/tpcc/txn-neworder.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "fmt" 5 | "bytes" 6 | "github.com/mit-pdos/vmvcc/vmvcc" 7 | ) 8 | 9 | type ItemInfo struct { 10 | I_NAME [24]byte 11 | S_QUANTITY uint16 12 | BRAND_GENERIC byte 13 | I_PRICE float32 14 | OL_AMOUNT float32 15 | } 16 | 17 | type NewOrderResult struct { 18 | W_TAX float32 19 | D_TAX float32 20 | D_NEXT_O_ID uint32 21 | TOTAL float32 22 | } 23 | 24 | func neworder( 25 | txn *vmvcc.Txn, 26 | /* input parameter */ 27 | wid uint8, did uint8, cid uint32, oentryd uint32, 28 | iids []uint32, iwids []uint8, iqtys []uint8, 29 | /* return value */ 30 | cret *Customer, res *NewOrderResult, iinfos *[]ItemInfo, 31 | ) bool { 32 | /* Determine whether this is a local transaction. */ 33 | alllocal := true 34 | for _, iwid := range iwids { 35 | if iwid != wid { 36 | alllocal = false 37 | break 38 | } 39 | } 40 | 41 | var olcnt uint8 = uint8(len(iids)) 42 | var ocarrierid uint8 = O_CARRIER_ID_NULL 43 | 44 | /* For each item, read their info. */ 45 | items := make([]*Item, 0, len(iids)) 46 | for _, iid := range iids { 47 | item, found := GetItem(txn, iid) 48 | /* Abort if one of the iids is invalid. (1% as specified by TPC-C.) */ 49 | if !found { 50 | return false 51 | } 52 | items = append(items, item) 53 | } 54 | 55 | /* Read warehouse. */ 56 | warehouse, _ := GetWarehouse(txn, wid) 57 | res.W_TAX = warehouse.W_TAX 58 | 59 | /* Read district. */ 60 | district, _ := GetDistrict(txn, did, wid) 61 | res.D_TAX = district.D_TAX 62 | res.D_NEXT_O_ID = district.D_NEXT_O_ID 63 | oid := district.D_NEXT_O_ID 64 | 65 | /* Read customer. */ 66 | customer, _ := GetCustomer(txn, cid, did, wid) 67 | *cret = *customer 68 | 69 | /* Increment next order id of district. */ 70 | district.IncrementNextOrderId(txn) 71 | 72 | /* Insert an Order record. */ 73 | InsertOrder( 74 | txn, oid, did, wid, 75 | cid, oentryd, ocarrierid, olcnt, alllocal, 76 | ) 77 | 78 | /* Insert a NewOrder record. */ 79 | InsertNewOrder(txn, oid, did, wid) 80 | 81 | /* For each item, read and update stock, create an order line. */ 82 | var total float32 = 0 83 | for i, iid := range iids { 84 | /* Read stock. */ 85 | iwid := iwids[i] 86 | stock, found := GetStock(txn, iid, iwid) 87 | if !found { 88 | continue 89 | } 90 | 91 | /* Retrieve item values to be used later. */ 92 | iname := items[i].I_NAME 93 | iprice := items[i].I_PRICE 94 | idata := items[i].I_DATA 95 | 96 | /* Retrieve current stock values. */ 97 | squantity := stock.S_QUANTITY 98 | sytd := stock.S_YTD 99 | sordercnt := stock.S_ORDER_CNT 100 | sremotecnt := stock.S_REMOTE_CNT 101 | sdata := stock.S_DATA 102 | 103 | /* Compute new stock values. */ 104 | olquantity := iqtys[i] 105 | if squantity < uint16(olquantity+10) { 106 | squantity += 91 107 | } 108 | squantity -= uint16(olquantity) 109 | sytd += uint32(olquantity) 110 | sordercnt += 1 111 | if iwid != wid { 112 | sremotecnt += 1 113 | } 114 | 115 | /* Write stock. */ 116 | stock.Update(txn, squantity, sytd, sordercnt, sremotecnt) 117 | 118 | /* Compute some return values. */ 119 | var brandgeneric byte = 'G' 120 | og := []byte("ORIGINAL") 121 | if bytes.Contains(sdata[:], og) && bytes.Contains(idata[:], og) { 122 | brandgeneric = 'B' 123 | } 124 | olamount := float32(olquantity) * iprice 125 | total += olamount 126 | 127 | /* Insert an OrderLine record. */ 128 | olnum := uint8(i) + 1 129 | InsertOrderLine( 130 | txn, oid, did, wid, olnum, 131 | iid, iwid, oentryd, olquantity, olamount, 132 | ) 133 | 134 | /* TODO: Collect other return values. */ 135 | iinfo := ItemInfo{ 136 | I_NAME: iname, 137 | S_QUANTITY: squantity, 138 | BRAND_GENERIC: brandgeneric, 139 | I_PRICE: iprice, 140 | OL_AMOUNT: olamount, 141 | } 142 | *iinfos = append(*iinfos, iinfo) 143 | } 144 | res.TOTAL = total 145 | 146 | return true 147 | } 148 | 149 | func TxnNewOrder(txno *vmvcc.Txn, p *NewOrderInput) (*Customer, *NewOrderResult, []ItemInfo, bool) { 150 | /* prepare output */ 151 | customer := new(Customer) 152 | res := new(NewOrderResult) 153 | iinfos := make([]ItemInfo, 0) 154 | /* prepare input */ 155 | wid := p.W_ID 156 | did := p.D_ID 157 | cid := p.C_ID 158 | oentryd := p.O_ENTRY_D 159 | iids := p.I_IDS 160 | iwids := p.I_W_IDS 161 | iqtys := p.I_QTYS 162 | body := func(txni *vmvcc.Txn) bool { 163 | return neworder( 164 | txni, wid, did, cid, oentryd, iids, iwids, iqtys, 165 | customer, res, &iinfos, 166 | ) 167 | } 168 | ok := txno.Run(body) 169 | return customer, res, iinfos, ok 170 | } 171 | -------------------------------------------------------------------------------- /benchmark/tpcc/txn-orderstatus.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "fmt" 5 | "github.com/mit-pdos/vmvcc/vmvcc" 6 | ) 7 | 8 | /** 9 | * Simplification: 10 | * 1. No select customer by last name. 11 | */ 12 | 13 | type OrderStatusOrderLineResult struct { 14 | OL_I_ID uint32 15 | OL_SUPPLY_W_ID uint8 16 | OL_QUANTITY uint8 17 | OL_AMOUNT float32 18 | OL_DELIVERY_D uint32 19 | } 20 | 21 | type OrderStatusResult struct { 22 | /* customer */ 23 | C_BALANCE float32 24 | C_FIRST [16]byte 25 | C_MIDDLE [2]byte 26 | C_LAST [16]byte 27 | /* order */ 28 | O_ID uint32 29 | O_ENTRY_D uint32 30 | O_CARRIER_ID uint8 31 | /* order lines */ 32 | OL_RES []OrderStatusOrderLineResult 33 | } 34 | 35 | func orderstatus( 36 | txn *vmvcc.Txn, ctx *TPCContext, 37 | /* input parameters */ 38 | wid uint8, did uint8, cid uint32, 39 | /* return values */ 40 | res *OrderStatusResult, 41 | ) bool { 42 | /* Read customer. */ 43 | customer := &ctx.customer 44 | GetCustomerX(txn, cid, did, wid, customer) 45 | res.C_BALANCE = customer.C_BALANCE 46 | res.C_FIRST = customer.C_FIRST 47 | res.C_MIDDLE = customer.C_MIDDLE 48 | res.C_LAST = customer.C_LAST 49 | 50 | /* Get all orders of this customer. */ 51 | orders := GetOrdersByIndex(txn, cid, did, wid) 52 | if len(orders) == 0 { 53 | return true 54 | } 55 | 56 | /* Pick the one with largest O_ID. */ 57 | oidmax := orders[0].O_ID 58 | imax := 0 59 | for i := range orders { 60 | oid := orders[i].O_ID 61 | if oid > oidmax { 62 | oidmax = oid 63 | imax = i 64 | } 65 | } 66 | order := orders[imax] 67 | oid := order.O_ID 68 | res.O_ID = oid 69 | res.O_ENTRY_D = order.O_ENTRY_D 70 | res.O_CARRIER_ID = order.O_CARRIER_ID 71 | 72 | /* Get all order lines of that order. */ 73 | olres := make([]OrderStatusOrderLineResult, 0, 10) 74 | for olnum := uint8(1); olnum <= 15; olnum++ { 75 | ol := &ctx.orderline 76 | found := GetOrderLineX(txn, ol, oid, did, wid, olnum) 77 | if !found { 78 | break 79 | } 80 | r := OrderStatusOrderLineResult{ 81 | OL_I_ID: ol.OL_I_ID, 82 | OL_SUPPLY_W_ID: ol.OL_SUPPLY_W_ID, 83 | OL_QUANTITY: ol.OL_QUANTITY, 84 | OL_AMOUNT: ol.OL_AMOUNT, 85 | OL_DELIVERY_D: ol.OL_DELIVERY_D, 86 | } 87 | olres = append(olres, r) 88 | } 89 | res.OL_RES = olres 90 | 91 | return true 92 | } 93 | 94 | func TxnOrderStatus(txno *vmvcc.Txn, p *OrderStatusInput, ctx *TPCContext) (*OrderStatusResult, bool) { 95 | /* prepare output */ 96 | res := new(OrderStatusResult) 97 | /* prepare input */ 98 | wid := p.W_ID 99 | did := p.D_ID 100 | cid := p.C_ID 101 | body := func(txni *vmvcc.Txn) bool { 102 | return orderstatus(txni, ctx, wid, did, cid, res) 103 | } 104 | ok := txno.Run(body) 105 | return res, ok 106 | } 107 | -------------------------------------------------------------------------------- /benchmark/tpcc/txn-payment.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/mit-pdos/vmvcc/vmvcc" 6 | "math/rand" 7 | ) 8 | 9 | func payment( 10 | txn *vmvcc.Txn, 11 | wid uint8, did uint8, hamount float32, 12 | cwid uint8, cdid uint8, cid uint32, hdate uint32, 13 | ) bool { 14 | /* Read warehouse. */ 15 | warehouse, _ := GetWarehouse(txn, wid) 16 | 17 | /* Update warehouse balance. */ 18 | warehouse.UpdateBalance(txn, hamount) 19 | 20 | /* Read district. */ 21 | district, _ := GetDistrict(txn, did, wid) 22 | 23 | /* Update district balance. */ 24 | district.UpdateBalance(txn, hamount) 25 | 26 | /* Read customer. */ 27 | customer, _ := GetCustomer(txn, cid, cdid, cwid) 28 | 29 | /* Update customer balance, payment, and payment count. */ 30 | if customer.C_CREDIT == [2]byte{'B', 'C'} { 31 | /* Also update the data field if the customer has bad credit. */ 32 | cdata := fmt.Sprintf("%d %d %d %d %d %.2f|%s", 33 | cid, cdid, cwid, did, wid, hamount, beforeNull(customer.C_DATA[:])) 34 | // fmt.Printf("cdata = %s len(cdata) = %d\n", cdata, len(cdata)) 35 | if len(cdata) > 500 { 36 | cdata = cdata[:500] 37 | } 38 | customer.UpdateOnBadCredit(txn, hamount, cdata) 39 | } else { 40 | customer.UpdateOnGoodCredit(txn, hamount) 41 | } 42 | 43 | /* Randomly generate history record id (not part of TPC-C). */ 44 | exists := true 45 | var hid uint64 46 | for exists { 47 | hid = rand.Uint64() % (1 << 56) /* MSB reserved for table ID */ 48 | _, exists = GetHistory(txn, hid) 49 | if exists { 50 | fmt.Printf("H_ID collides, regenerate a new one.") 51 | } 52 | } 53 | 54 | /* Insert a history record. */ 55 | wname := beforeNull(warehouse.W_NAME[:]) 56 | dname := beforeNull(district.D_NAME[:]) 57 | hdata := fmt.Sprintf("%s %s", wname, dname) 58 | InsertHistory(txn, hid, cid, cdid, cwid, did, wid, hdate, hamount, hdata) 59 | 60 | return true 61 | } 62 | 63 | func TxnPayment(txno *vmvcc.Txn, p *PaymentInput) bool { 64 | wid := p.W_ID 65 | did := p.D_ID 66 | hamount := p.H_AMOUNT 67 | cwid := p.C_W_ID 68 | cdid := p.C_D_ID 69 | cid := p.C_ID 70 | hdate := p.H_DATE 71 | 72 | body := func(txni *vmvcc.Txn) bool { 73 | return payment(txni, wid, did, hamount, cwid, cdid, cid, hdate) 74 | } 75 | ok := txno.Run(body) 76 | return ok 77 | } 78 | -------------------------------------------------------------------------------- /benchmark/tpcc/txn-stocklevel.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "fmt" 5 | "github.com/mit-pdos/vmvcc/vmvcc" 6 | ) 7 | 8 | func stocklevel( 9 | txn *vmvcc.Txn, 10 | /* input parameters */ 11 | wid uint8, did uint8, threshold uint16, 12 | /* return value */ 13 | cnt *uint32, 14 | ) bool { 15 | /* Read district. */ 16 | district, _ := GetDistrict(txn, did, wid) 17 | oidub := district.D_NEXT_O_ID 18 | 19 | /* Computer the order id range. */ 20 | var oidlb uint32 21 | if oidub < 20 { 22 | oidlb = 0 23 | } else { 24 | oidlb = oidub - 20 25 | } 26 | 27 | /** 28 | * Use a map to count distinct items. 29 | */ 30 | iids := make(map[uint32]struct{}, 150) 31 | /* Read the latest 20 orders from OrderLine. */ 32 | for oid := oidlb; oid < oidub; oid++ { 33 | /* Read all the items in this order. */ 34 | for olnum := uint8(1); olnum <= 15; olnum++ { 35 | orderline, found := GetOrderLine(txn, oid, did, wid, olnum) 36 | if !found { 37 | break 38 | } 39 | iid := orderline.OL_I_ID 40 | stock, _ := GetStock(txn, iid, wid) 41 | quantity := stock.S_QUANTITY 42 | if quantity < threshold { 43 | iids[iid] = struct{}{} 44 | } 45 | } 46 | } 47 | 48 | /* Return the number of distinct items below the threshold. */ 49 | *cnt = uint32(len(iids)) 50 | 51 | return true 52 | } 53 | 54 | func TxnStockLevel(txno *vmvcc.Txn, p *StockLevelInput) (uint32, bool) { 55 | /* prepare output */ 56 | var cnt uint32 = 0 57 | /* prepare input */ 58 | wid := p.W_ID 59 | did := p.D_ID 60 | threshold := p.THRESHOLD 61 | body := func(txni *vmvcc.Txn) bool { 62 | return stocklevel(txni, wid, did, threshold, &cnt) 63 | } 64 | ok := txno.Run(body) 65 | return cnt, ok 66 | } 67 | -------------------------------------------------------------------------------- /benchmark/tpcc/txn-stockscan.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | // "fmt" 5 | "github.com/mit-pdos/vmvcc/vmvcc" 6 | ) 7 | 8 | func stockscan( 9 | txn *vmvcc.Txn, 10 | /* input parameters */ 11 | nwhs uint8, nitems uint32, 12 | /* return value */ 13 | cnts []uint32, 14 | ) bool { 15 | /* Read all the stocks in this order. */ 16 | for iid := uint32(1); iid <= nitems; iid++ { 17 | for wid := uint8(1); wid <= nwhs; wid++ { 18 | stock, _ := GetStock(txn, iid, wid) 19 | quantity := stock.S_QUANTITY 20 | cnts[iid-1] += uint32(quantity) 21 | } 22 | } 23 | // fmt.Printf("Done stockscan.\n") 24 | 25 | return true 26 | } 27 | 28 | func TxnStockScan(txno *vmvcc.Txn, nwhs uint8, nitems uint32) ([]uint32, bool) { 29 | /* prepare output */ 30 | cnts := make([]uint32, nitems) 31 | /* prepare input */ 32 | body := func(txni *vmvcc.Txn) bool { 33 | return stockscan(txni, nwhs, nitems, cnts) 34 | } 35 | ok := txno.Run(body) 36 | return cnts, ok 37 | } 38 | -------------------------------------------------------------------------------- /benchmark/ycsb/generator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | /* for Zipfian distribution */ 6 | "github.com/pingcap/go-ycsb/pkg/generator" 7 | ) 8 | 9 | const ( 10 | OP_RD = iota 11 | OP_WR 12 | OP_SCAN 13 | ) 14 | 15 | const ( 16 | DIST_UNIFORM = iota 17 | DIST_ZIPFIAN 18 | ) 19 | 20 | type Generator struct { 21 | rd *rand.Rand 22 | nKeys int 23 | rKeys uint64 24 | rdRatio uint64 25 | zipfian *generator.Zipfian 26 | dist int 27 | } 28 | 29 | func NewGenerator( 30 | seed int, 31 | nKeys int, rKeys, rdRatio uint64, 32 | theta float64, 33 | ) *Generator { 34 | rd := rand.New(rand.NewSource(int64(seed))) 35 | 36 | var zipfian *generator.Zipfian 37 | var dist int 38 | if theta == -1 { 39 | dist = DIST_UNIFORM 40 | } else { 41 | dist = DIST_ZIPFIAN 42 | zipfian = generator.NewZipfianWithItems(int64(rKeys), theta) 43 | } 44 | 45 | gen := &Generator{ 46 | rd: rd, 47 | nKeys: nKeys, 48 | rKeys: rKeys, 49 | rdRatio: rdRatio, 50 | zipfian: zipfian, 51 | dist: dist, 52 | } 53 | 54 | return gen 55 | } 56 | 57 | func (g *Generator) PickOp() int { 58 | x := g.rd.Uint64() % 100 59 | if x < g.rdRatio { 60 | return OP_RD 61 | } else { 62 | return OP_WR 63 | } 64 | } 65 | 66 | func (g *Generator) pickKeyUniform() uint64 { 67 | return g.rd.Uint64() % g.rKeys 68 | } 69 | 70 | func (g *Generator) pickKeyZipfian() uint64 { 71 | return uint64(g.zipfian.Next(g.rd)) 72 | } 73 | 74 | func (g *Generator) PickKey() uint64 { 75 | if g.dist == DIST_ZIPFIAN { 76 | return g.pickKeyZipfian() 77 | } else { 78 | return g.pickKeyUniform() 79 | } 80 | } 81 | 82 | func (g *Generator) NKeys() int { 83 | return g.nKeys 84 | } 85 | -------------------------------------------------------------------------------- /benchmark/ycsb/ycsb.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/mit-pdos/vmvcc/vmvcc" 7 | "log" 8 | "os" 9 | "runtime" 10 | "runtime/pprof" 11 | "time" 12 | ) 13 | 14 | var done, warmup bool 15 | var szrec int = 100 16 | 17 | func populateDataBody(txn *vmvcc.Txn, key uint64) bool { 18 | s := string(make([]byte, szrec)) 19 | txn.Write(key, s) 20 | return true 21 | } 22 | 23 | func populateData(db *vmvcc.DB, rkeys uint64) { 24 | t := db.NewTxn() 25 | for k := uint64(0); k < rkeys; k++ { 26 | body := func(txn *vmvcc.Txn) bool { 27 | return populateDataBody(txn, k) 28 | } 29 | t.Run(body) 30 | } 31 | } 32 | 33 | func longReaderBody(txn *vmvcc.Txn, gen *Generator) bool { 34 | for i := 0; i < 10000; i++ { 35 | key := gen.PickKey() 36 | txn.Read(key) 37 | } 38 | return true 39 | } 40 | 41 | func longReader(db *vmvcc.DB, gen *Generator) { 42 | t := db.NewTxn() 43 | 44 | for !done { 45 | body := func(txn *vmvcc.Txn) bool { 46 | return longReaderBody(txn, gen) 47 | } 48 | t.Run(body) 49 | } 50 | } 51 | 52 | func workerRWBody(txn *vmvcc.Txn, keys []uint64, ops []int, buf []byte) bool { 53 | for i, k := range keys { 54 | if ops[i] == OP_RD { 55 | txn.Read(k) 56 | } else if ops[i] == OP_WR { 57 | for j := range buf { 58 | buf[j] = 'b' 59 | } 60 | s := string(buf) 61 | txn.Write(k, s) 62 | } 63 | } 64 | return true 65 | } 66 | 67 | func workerRW( 68 | db *vmvcc.DB, gen *Generator, 69 | chCommitted, chTotal chan uint64, 70 | ) { 71 | // runtime.LockOSThread() 72 | var committed uint64 = 0 73 | var total uint64 = 0 74 | nKeys := gen.NKeys() 75 | 76 | keys := make([]uint64, nKeys) 77 | ops := make([]int, nKeys) 78 | 79 | t := db.NewTxn() 80 | 81 | buf := make([]byte, szrec) 82 | for !done { 83 | for i := 0; i < nKeys; i++ { 84 | keys[i] = gen.PickKey() 85 | ops[i] = gen.PickOp() 86 | } 87 | body := func(txn *vmvcc.Txn) bool { 88 | return workerRWBody(txn, keys, ops, buf) 89 | } 90 | ok := t.Run(body) 91 | if !warmup { 92 | continue 93 | } 94 | if ok { 95 | committed++ 96 | } 97 | total++ 98 | } 99 | 100 | chCommitted <- committed 101 | chTotal <- total 102 | } 103 | 104 | func workerScanBody(txn *vmvcc.Txn, key uint64) bool { 105 | for offset := uint64(0); offset < 100; offset++ { 106 | txn.Read(key + offset) 107 | } 108 | return true 109 | } 110 | 111 | func workerScan( 112 | db *vmvcc.DB, gen *Generator, 113 | chCommitted, chTotal chan uint64, 114 | ) { 115 | // runtime.LockOSThread() 116 | var committed uint64 = 0 117 | var total uint64 = 0 118 | 119 | t := db.NewTxn() 120 | 121 | for !done { 122 | key := gen.PickKey() 123 | body := func(txn *vmvcc.Txn) bool { 124 | return workerScanBody(txn, key) 125 | } 126 | ok := t.Run(body) 127 | if !warmup { 128 | continue 129 | } 130 | if ok { 131 | committed++ 132 | } 133 | total++ 134 | } 135 | 136 | chCommitted <- committed 137 | chTotal <- total 138 | } 139 | 140 | func main() { 141 | var nthrds int 142 | var nkeys int 143 | var rkeys uint64 144 | var rdratio uint64 145 | var theta float64 146 | var long bool 147 | var duration uint64 148 | var cpuprof string 149 | var heapprof string 150 | var exp bool 151 | flag.IntVar(&nthrds, "nthrds", 1, "number of threads") 152 | flag.IntVar(&nkeys, "nkeys", 1, "number of keys accessed per txn") 153 | flag.Uint64Var(&rkeys, "rkeys", 1000, "access keys within [0:rkeys)") 154 | flag.Uint64Var(&rdratio, "rdratio", 80, "read ratio (200 for scan)") 155 | flag.Float64Var(&theta, "theta", 0.8, "zipfian theta (the higher the more contended; -1 for uniform)") 156 | flag.BoolVar(&long, "long", false, "background long-running RO transactions") 157 | flag.Uint64Var(&duration, "duration", 3, "benchmark duration (seconds)") 158 | flag.StringVar(&cpuprof, "cpuprof", "cpu.prof", "write cpu profile to cpuprof") 159 | flag.StringVar(&heapprof, "heapprof", "heap.prof", "write heap profile to heapprof") 160 | flag.BoolVar(&exp, "exp", false, "print only experimental data") 161 | flag.Parse() 162 | 163 | chCommitted := make(chan uint64) 164 | chTotal := make(chan uint64) 165 | 166 | if cpuprof != "" { 167 | f, err := os.Create(cpuprof) 168 | if err != nil { 169 | log.Fatal("could not create CPU profile: ", err) 170 | } 171 | defer f.Close() // error handling omitted for example 172 | if err := pprof.StartCPUProfile(f); err != nil { 173 | log.Fatal("could not start CPU profile: ", err) 174 | } 175 | defer pprof.StopCPUProfile() 176 | } 177 | 178 | var nthrdsro int = 8 179 | 180 | gens := make([]*Generator, nthrds+nthrdsro) 181 | for i := 0; i < nthrds; i++ { 182 | gens[i] = NewGenerator(i, nkeys, rkeys, rdratio, theta) 183 | } 184 | for i := 0; i < nthrdsro; i++ { 185 | gens[i+nthrds] = NewGenerator(i+nthrds, nkeys, rkeys, rdratio, theta) 186 | } 187 | 188 | db := vmvcc.MkDB() 189 | populateData(db, rkeys) 190 | if !exp { 191 | fmt.Printf("Database populated.\n") 192 | } 193 | 194 | db.ActivateGC() 195 | 196 | /* Start a long-running reader. */ 197 | if long { 198 | for i := 0; i < nthrdsro; i++ { 199 | go longReader(db, gens[nthrds+i]) 200 | } 201 | } 202 | 203 | done = false 204 | warmup = false 205 | for i := 0; i < nthrds; i++ { 206 | if rdratio == 200 { 207 | go workerScan(db, gens[i], chCommitted, chTotal) 208 | } else { 209 | go workerRW(db, gens[i], chCommitted, chTotal) 210 | } 211 | } 212 | // time.Sleep(time.Duration(60) * time.Second) 213 | warmup = true 214 | time.Sleep(time.Duration(duration) * time.Second) 215 | done = true 216 | 217 | var c uint64 = 0 218 | var t uint64 = 0 219 | for i := 0; i < nthrds; i++ { 220 | c += <-chCommitted 221 | t += <-chTotal 222 | } 223 | rate := float64(c) / float64(t) 224 | tp := float64(c) / float64(duration) / 1000000.0 225 | 226 | if exp { 227 | fmt.Printf("%d, %d, %d, %d, %.2f, %v, %d, %f, %f\n", 228 | nthrds, nkeys, rkeys, rdratio, theta, long, duration, tp, rate) 229 | } else { 230 | fmt.Printf("committed / total = %d / %d (%f).\n", c, t, rate) 231 | fmt.Printf("tp = %f (M txns/s).\n", tp) 232 | } 233 | 234 | if heapprof != "" { 235 | f, err := os.Create(heapprof) 236 | if err != nil { 237 | log.Fatal("could not create hea[ profile: ", err) 238 | } 239 | defer f.Close() // error handling omitted for example 240 | runtime.GC() 241 | if err := pprof.WriteHeapProfile(f); err != nil { 242 | log.Fatal("could not start heap profile: ", err) 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /benchmark/ycsb/ycsb_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | ) 8 | 9 | func TestZipfian(t *testing.T) { 10 | assert := assert.New(t) 11 | gen := NewGenerator(0, 1, 1000, 100, 0.9) 12 | for i := 0; i < 100; i++ { 13 | fmt.Printf("%d\n", gen.PickKey()) 14 | } 15 | assert.Equal(true, true) 16 | } 17 | 18 | func TestUniform(t *testing.T) { 19 | assert := assert.New(t) 20 | gen := NewGenerator(0, 1, 1000, 100, -1) 21 | for i := 0; i < 100; i++ { 22 | fmt.Printf("%d\n", gen.PickKey()) 23 | } 24 | assert.Equal(true, true) 25 | } 26 | -------------------------------------------------------------------------------- /cfmutex/cfmutex.go: -------------------------------------------------------------------------------- 1 | package cfmutex 2 | 3 | import ( 4 | "sync" 5 | ) 6 | 7 | type CFMutex struct { 8 | mutex sync.Mutex 9 | padding [7]uint64 10 | } 11 | 12 | func (cfmutex *CFMutex) Lock() { 13 | cfmutex.mutex.Lock() 14 | } 15 | 16 | func (cfmutex *CFMutex) Unlock() { 17 | cfmutex.mutex.Unlock() 18 | } 19 | -------------------------------------------------------------------------------- /cfmutex/cfmutex_test.go: -------------------------------------------------------------------------------- 1 | package cfmutex 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "testing" 7 | "unsafe" 8 | ) 9 | 10 | func TestCacheAligned(t *testing.T) { 11 | assert := assert.New(t) 12 | var cfmutex CFMutex 13 | size := unsafe.Sizeof(cfmutex) 14 | fmt.Printf("size = %d.\n", size) 15 | assert.Equal(uintptr(64), size) 16 | } 17 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | const ( 4 | RET_SUCCESS uint64 = 0 5 | RET_NONEXIST uint64 = 1 6 | RET_RETRY uint64 = 200 7 | RET_UNSERIALIZABLE uint64 = 400 8 | ) 9 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | const ( 4 | N_TXN_SITES uint64 = 32 5 | N_IDX_BUCKET uint64 = 8192 6 | ) 7 | 8 | const ( 9 | TID_SENTINEL uint64 = 18446744073709551615 10 | ) 11 | -------------------------------------------------------------------------------- /examples/hello.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/vmvcc" 5 | ) 6 | 7 | func hello(txn *vmvcc.Txn) bool { 8 | txn.Write(0, "hello") 9 | txn.Read(0) 10 | txn.Delete(0) 11 | 12 | return true 13 | } 14 | 15 | func Hello(txno *vmvcc.Txn) { 16 | body := func(txni *vmvcc.Txn) bool { 17 | return hello(txni) 18 | } 19 | txno.Run(body) 20 | } 21 | 22 | func CallHello() { 23 | db := vmvcc.MkDB() 24 | db.ActivateGC() 25 | 26 | txn := db.NewTxn() 27 | Hello(txn) 28 | } 29 | -------------------------------------------------------------------------------- /examples/strnum/strnum.go: -------------------------------------------------------------------------------- 1 | // / This package converts between strings and numbers. Currently Goose has 2 | // / limited support for manipulating string, so this package is trusted. 3 | package strnum 4 | 5 | import ( 6 | "github.com/goose-lang/primitive" 7 | ) 8 | 9 | func StringToU64(s string) uint64 { 10 | return primitive.UInt64Get([]byte(s)) 11 | } 12 | 13 | func U64ToString(n uint64) string { 14 | buf := make([]byte, 8) 15 | primitive.UInt64Put(buf, n) 16 | return string(buf) 17 | } 18 | -------------------------------------------------------------------------------- /examples/xfer.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/examples/strnum" 5 | "github.com/mit-pdos/vmvcc/vmvcc" 6 | ) 7 | 8 | func xfer(txn *vmvcc.Txn, src, dst, amt uint64) bool { 9 | sbalx, _ := txn.Read(src) 10 | sbal := strnum.StringToU64(sbalx) 11 | 12 | if sbal < amt { 13 | return false 14 | } 15 | 16 | sbaly := strnum.U64ToString(sbal - amt) 17 | txn.Write(src, sbaly) 18 | 19 | dbalx, _ := txn.Read(dst) 20 | dbal := strnum.StringToU64(dbalx) 21 | 22 | if dbal+amt < dbal { 23 | return false 24 | } 25 | 26 | dbaly := strnum.U64ToString(dbal + amt) 27 | txn.Write(dst, dbaly) 28 | 29 | return true 30 | } 31 | 32 | func AtomicXfer(txno *vmvcc.Txn, src, dst, amt uint64) bool { 33 | body := func(txni *vmvcc.Txn) bool { 34 | return xfer(txni, src, dst, amt) 35 | } 36 | return txno.Run(body) 37 | } 38 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mit-pdos/vmvcc 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/goose-lang/primitive v0.1.0 7 | github.com/goose-lang/std v0.6.1 8 | github.com/mit-pdos/gokv v0.1.1 9 | github.com/pingcap/go-ycsb v1.0.1 10 | github.com/stretchr/testify v1.9.0 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/magiconair/properties v1.8.10 // indirect 16 | github.com/mattn/go-runewidth v0.0.16 // indirect 17 | github.com/olekukonko/tablewriter v0.0.5 // indirect 18 | github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect 19 | github.com/pmezard/go-difflib v1.0.0 // indirect 20 | github.com/rivo/uniseg v0.4.7 // indirect 21 | github.com/tchajed/marshal v0.6.5 // indirect 22 | go.uber.org/atomic v1.11.0 // indirect 23 | gopkg.in/yaml.v3 v3.0.1 // indirect 24 | ) 25 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/goose-lang/primitive v0.1.0 h1:JReOdFzUdGD7f8nWTC/uOlNpBdVFU4q8HzMSgn8edaY= 5 | github.com/goose-lang/primitive v0.1.0/go.mod h1:sDE72zVH81ASwPXc1m9OgSfaaY0aZHu4Ty19FgONpOc= 6 | github.com/goose-lang/std v0.4.1 h1:ezoEYbvePtF2TD8vuUUTt5lO893CA6QTgoBX896F8BU= 7 | github.com/goose-lang/std v0.4.1/go.mod h1:bnKHDHwU0lHf99eMI5PVM77UweRyu6qgM/h43qGBRto= 8 | github.com/goose-lang/std v0.6.0 h1:zEJZbRZXIa1mYtdHSeJepA8v2h+Nt4cNgDLEkVQyRHY= 9 | github.com/goose-lang/std v0.6.0/go.mod h1:bnKHDHwU0lHf99eMI5PVM77UweRyu6qgM/h43qGBRto= 10 | github.com/goose-lang/std v0.6.1 h1:fShymy3KEyVkf6Q26Z24W4aBXQD+Qu6XIORuqAdTwK0= 11 | github.com/goose-lang/std v0.6.1/go.mod h1:bnKHDHwU0lHf99eMI5PVM77UweRyu6qgM/h43qGBRto= 12 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 13 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 14 | github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= 15 | github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 16 | github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= 17 | github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= 18 | github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= 19 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 20 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 21 | github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= 22 | github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 23 | github.com/mit-pdos/gokv v0.1.0 h1:0Y7muYhEbJhwO4wGX1gizi/TokeWXK2jvimkv0waNLM= 24 | github.com/mit-pdos/gokv v0.1.0/go.mod h1:cZ/pnhQcotfojoaBEu4rQuYeS8rwsyaiqqIQR3zTkuU= 25 | github.com/mit-pdos/gokv v0.1.1 h1:fNQieoq6yV3I7ebs0Al/u/uxEIM3KYKVyvv1WOn0K1s= 26 | github.com/mit-pdos/gokv v0.1.1/go.mod h1:ZLlOibv49aW6WzNYy/kyew+xwSOLk94LY8qYjheZHJ8= 27 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= 28 | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= 29 | github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= 30 | github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= 31 | github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c h1:xpW9bvK+HuuTmyFqUwr+jcCvpVkK7sumiz+ko5H9eq4= 32 | github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= 33 | github.com/pingcap/go-ycsb v1.0.1 h1:OGIUjQjtC22KDHPCqg4+ScWYFZrHQjJnt3Gmf4N8UOw= 34 | github.com/pingcap/go-ycsb v1.0.1/go.mod h1:VQdVCzhVPTDDfWM8NV7c0zZHtDdN//DHtzifn4uYWVc= 35 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 36 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 37 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 38 | github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= 39 | github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= 40 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 41 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 42 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 43 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 44 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 45 | github.com/tchajed/marshal v0.6.2 h1:DSrylV2Sc47G4RJG2KSOp7HrA76Hd5Bj2WVGtXB/bCw= 46 | github.com/tchajed/marshal v0.6.2/go.mod h1:nY/NmbQidx2CdBY4Y8NdUTnDXgWmhQ6Hg1es+PnxBx8= 47 | github.com/tchajed/marshal v0.6.4 h1:l5oQ4rnxBZT1hYg6wCtiVLU+VpxVMHqAO7X4UvyiPdU= 48 | github.com/tchajed/marshal v0.6.4/go.mod h1:5ZBThIEbCesU7WNnefmLzMqC06/S7am6VqHknoLara0= 49 | github.com/tchajed/marshal v0.6.5 h1:efeXVH7fbUhRtAkAeOn/g8Y9h6+3X+jricoheBodfPQ= 50 | github.com/tchajed/marshal v0.6.5/go.mod h1:d1XBpilY4WRs1EU9Q2QNOgOpSsBaWH/l4eU1sCR7hqk= 51 | go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 52 | go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= 53 | go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= 54 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 55 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 56 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 57 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 58 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 59 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 60 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 61 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 62 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 63 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 64 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 65 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 66 | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 67 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 68 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 69 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 70 | -------------------------------------------------------------------------------- /index/index.go: -------------------------------------------------------------------------------- 1 | package index 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/config" 5 | "github.com/mit-pdos/vmvcc/tuple" 6 | "sync" 7 | ) 8 | 9 | type IndexBucket struct { 10 | latch *sync.Mutex 11 | m map[uint64]*tuple.Tuple 12 | } 13 | 14 | type Index struct { 15 | buckets []*IndexBucket 16 | } 17 | 18 | func MkIndex() *Index { 19 | idx := new(Index) 20 | idx.buckets = make([]*IndexBucket, config.N_IDX_BUCKET) 21 | for i := uint64(0); i < config.N_IDX_BUCKET; i++ { 22 | b := new(IndexBucket) 23 | b.latch = new(sync.Mutex) 24 | b.m = make(map[uint64]*tuple.Tuple) 25 | idx.buckets[i] = b 26 | } 27 | return idx 28 | } 29 | 30 | func getBucket(key uint64) uint64 { 31 | return (key>>52 + key) % config.N_IDX_BUCKET 32 | // return key % config.N_IDX_BUCKET 33 | } 34 | 35 | // @GetTuple returns the tuple pointer associated with the key. 36 | // 37 | // @GetTuple will always create a tuple when there is no entry in @m. This 38 | // design choice seems to wasteful as it will allocate a tuple even with an 39 | // empty @txn.Get, but is actually a must: A @txn.Get should prevent earlier 40 | // txns from creating new versions, even when it fails to retrieve a value. 41 | func (idx *Index) GetTuple(key uint64) *tuple.Tuple { 42 | b := getBucket(key) 43 | bucket := idx.buckets[b] 44 | bucket.latch.Lock() 45 | 46 | // Return the tuple if there exists one. 47 | tupleCur, ok := bucket.m[key] 48 | if ok { 49 | bucket.latch.Unlock() 50 | return tupleCur 51 | } 52 | 53 | // Create a new tuple and associate it with the key. 54 | tupleNew := tuple.MkTuple() 55 | bucket.m[key] = tupleNew 56 | 57 | bucket.latch.Unlock() 58 | return tupleNew 59 | } 60 | 61 | // @getKeys returns keys in the index. Note that the return key set is merely an 62 | // under-approximation of the actual key set. 63 | func (idx *Index) getKeys() []uint64 { 64 | var keys []uint64 65 | keys = make([]uint64, 0, 200) 66 | 67 | for _, bkt := range idx.buckets { 68 | bkt.latch.Lock() 69 | for k := range bkt.m { 70 | keys = append(keys, k) 71 | } 72 | bkt.latch.Unlock() 73 | } 74 | 75 | return keys 76 | } 77 | 78 | // TODO: move this out to a separate GC module. 79 | func (idx *Index) DoGC(tidMin uint64) { 80 | keys := idx.getKeys() 81 | 82 | for _, k := range keys { 83 | tuple := idx.GetTuple(k) 84 | tuple.RemoveVersions(tidMin) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /osdi23/scripts/all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | read -n 1 -p 'This script uses git reset --hard, still run it (y/n)? ' answer 4 | echo '' 5 | if [ "$answer" != 'y' ] 6 | then 7 | exit 1 8 | fi 9 | 10 | nruns=1 11 | 12 | dir=./osdi23/scripts 13 | 14 | # Silo 15 | $dir/silo.sh $nruns 16 | 17 | # Robustness to long-running readers 18 | $dir/long-ycsb.sh $nruns 19 | $dir/long-tpcc.sh $nruns 20 | 21 | # Optimization factor analysis 22 | git reset --hard && git apply $dir/base.diff && $dir/optimization.sh $nruns base 23 | git reset --hard && git apply $dir/shardpad.diff && $dir/optimization.sh $nruns shardpad 24 | git reset --hard && git apply $dir/fai.diff && $dir/optimization.sh $nruns fai 25 | git reset --hard && $dir/optimization.sh $nruns rdtsc 26 | 27 | # Scalability analysis 28 | $dir/scalability.sh $nruns 29 | -------------------------------------------------------------------------------- /osdi23/scripts/base.diff: -------------------------------------------------------------------------------- 1 | diff --git a/config/config.go b/config/config.go 2 | index 516e0c5..3457367 100644 3 | --- a/config/config.go 4 | +++ b/config/config.go 5 | @@ -1,8 +1,8 @@ 6 | package config 7 | 8 | const ( 9 | - N_TXN_SITES uint64 = 32 10 | - N_IDX_BUCKET uint64 = 8192 11 | + N_TXN_SITES uint64 = 1 12 | + N_IDX_BUCKET uint64 = 1 13 | ) 14 | 15 | const ( 16 | diff --git a/tid/tid.go b/tid/tid.go 17 | index c884924..0aa0674 100644 18 | --- a/tid/tid.go 19 | +++ b/tid/tid.go 20 | @@ -1,11 +1,17 @@ 21 | package tid 22 | 23 | import ( 24 | + "sync/atomic" 25 | "github.com/goose-lang/std" 26 | "github.com/mit-pdos/gokv/grove_ffi" 27 | "github.com/mit-pdos/vmvcc/config" 28 | ) 29 | 30 | +func GenTIDWithFAI(gtidref *uint64) uint64 { 31 | + tid := atomic.AddUint64(gtidref, 1) 32 | + return tid 33 | +} 34 | + 35 | func GenTID(sid uint64) uint64 { 36 | var tid uint64 37 | 38 | diff --git a/txnsite/txnsite.go b/txnsite/txnsite.go 39 | index 91e325d..9be4727 100644 40 | --- a/txnsite/txnsite.go 41 | +++ b/txnsite/txnsite.go 42 | @@ -1,7 +1,8 @@ 43 | package txnsite 44 | 45 | import ( 46 | - "github.com/mit-pdos/vmvcc/tid" 47 | + "sync" 48 | + // "github.com/mit-pdos/vmvcc/tid" 49 | "github.com/mit-pdos/vmvcc/cfmutex" 50 | "github.com/tchajed/goose/machine" 51 | ) 52 | @@ -14,13 +15,17 @@ type TxnSite struct { 53 | // Active transaction IDs. 54 | padding [4]uint64 55 | // TODO: should only need 3, change it once performance is tested. 56 | + gtidref *uint64 57 | + gtidlk *sync.Mutex 58 | } 59 | 60 | -func MkTxnSite(sid uint64) *TxnSite { 61 | +func MkTxnSite(sid uint64, gtidref *uint64, gtidlk *sync.Mutex) *TxnSite { 62 | site := new(TxnSite) 63 | site.latch = new(cfmutex.CFMutex) 64 | site.tids = make([]uint64, 0, 8) 65 | site.sid = sid 66 | + site.gtidref = gtidref 67 | + site.gtidlk = gtidlk 68 | 69 | return site 70 | } 71 | @@ -30,7 +35,12 @@ func (site *TxnSite) Activate() uint64 { 72 | site.latch.Lock() 73 | 74 | var t uint64 75 | - t = tid.GenTID(site.sid) 76 | + // t = tid.GenTID(site.sid) 77 | + // t = tid.GenTIDWithFAI(gtid) 78 | + site.gtidlk.Lock() 79 | + t = *site.gtidref 80 | + *site.gtidref++ 81 | + site.gtidlk.Unlock() 82 | // Assume TID never overflow. 83 | primitive.Assume(t < 18446744073709551615) 84 | 85 | @@ -76,7 +86,13 @@ func (site *TxnSite) GetSafeTS() uint64 { 86 | site.latch.Lock() 87 | 88 | var tidnew uint64 89 | - tidnew = tid.GenTID(site.sid) 90 | + // tidnew = tid.GenTID(site.sid) 91 | + // tidnew = tid.GenTIDWithFAI(gtid) 92 | + site.gtidlk.Lock() 93 | + tidnew = *site.gtidref 94 | + *site.gtidref++ 95 | + site.gtidlk.Unlock() 96 | + 97 | primitive.Assume(tidnew < 18446744073709551615) 98 | 99 | var tidmin uint64 = tidnew 100 | diff --git a/vmvcc/db.go b/vmvcc/db.go 101 | index 8da24d6..d2ad962 100644 102 | --- a/vmvcc/db.go 103 | +++ b/vmvcc/db.go 104 | @@ -1,6 +1,7 @@ 105 | package vmvcc 106 | 107 | import ( 108 | + "sync" 109 | "github.com/mit-pdos/vmvcc/config" 110 | "github.com/mit-pdos/vmvcc/index" 111 | "github.com/mit-pdos/vmvcc/wrbuf" 112 | @@ -15,6 +16,7 @@ type DB struct { 113 | latch *cfmutex.CFMutex 114 | // Next site ID to assign. 115 | sid uint64 116 | + gtid uint64 117 | // All transaction sites. 118 | sites []*txnsite.TxnSite 119 | // Index. 120 | @@ -28,11 +30,13 @@ func MkDB() *DB { 121 | db := &DB { proph : proph } 122 | db.latch = new(cfmutex.CFMutex) 123 | db.sites = make([]*txnsite.TxnSite, config.N_TXN_SITES) 124 | + db.gtid = 0 125 | 126 | // Call this once to establish invariants. 127 | + gtidlk := new(sync.Mutex) 128 | tid.GenTID(0) 129 | for i := uint64(0); i < config.N_TXN_SITES; i++ { 130 | - site := txnsite.MkTxnSite(i) 131 | + site := txnsite.MkTxnSite(i, &db.gtid, gtidlk) 132 | db.sites[i] = site 133 | } 134 | db.idx = index.MkIndex() 135 | -------------------------------------------------------------------------------- /osdi23/scripts/fai.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tid/tid.go b/tid/tid.go 2 | index c884924..0aa0674 100644 3 | --- a/tid/tid.go 4 | +++ b/tid/tid.go 5 | @@ -1,11 +1,17 @@ 6 | package tid 7 | 8 | import ( 9 | + "sync/atomic" 10 | "github.com/goose-lang/std" 11 | "github.com/mit-pdos/gokv/grove_ffi" 12 | "github.com/mit-pdos/vmvcc/config" 13 | ) 14 | 15 | +func GenTIDWithFAI(gtidref *uint64) uint64 { 16 | + tid := atomic.AddUint64(gtidref, 1) 17 | + return tid 18 | +} 19 | + 20 | func GenTID(sid uint64) uint64 { 21 | var tid uint64 22 | 23 | diff --git a/txnsite/txnsite.go b/txnsite/txnsite.go 24 | index 91e325d..9c74479 100644 25 | --- a/txnsite/txnsite.go 26 | +++ b/txnsite/txnsite.go 27 | @@ -1,6 +1,7 @@ 28 | package txnsite 29 | 30 | import ( 31 | + "sync" 32 | "github.com/mit-pdos/vmvcc/tid" 33 | "github.com/mit-pdos/vmvcc/cfmutex" 34 | "github.com/tchajed/goose/machine" 35 | @@ -14,13 +15,17 @@ type TxnSite struct { 36 | // Active transaction IDs. 37 | padding [4]uint64 38 | // TODO: should only need 3, change it once performance is tested. 39 | + gtidref *uint64 40 | + gtidlk *sync.Mutex 41 | } 42 | 43 | -func MkTxnSite(sid uint64) *TxnSite { 44 | +func MkTxnSite(sid uint64, gtidref *uint64, gtidlk *sync.Mutex) *TxnSite { 45 | site := new(TxnSite) 46 | site.latch = new(cfmutex.CFMutex) 47 | site.tids = make([]uint64, 0, 8) 48 | site.sid = sid 49 | + site.gtidref = gtidref 50 | + site.gtidlk = gtidlk 51 | 52 | return site 53 | } 54 | @@ -30,7 +35,12 @@ func (site *TxnSite) Activate() uint64 { 55 | site.latch.Lock() 56 | 57 | var t uint64 58 | - t = tid.GenTID(site.sid) 59 | + // t = tid.GenTID(site.sid) 60 | + t = tid.GenTIDWithFAI(site.gtidref) 61 | + // site.gtidlk.Lock() 62 | + // t = *site.gtidref 63 | + // *site.gtidref++ 64 | + // site.gtidlk.Unlock() 65 | // Assume TID never overflow. 66 | primitive.Assume(t < 18446744073709551615) 67 | 68 | @@ -76,7 +86,13 @@ func (site *TxnSite) GetSafeTS() uint64 { 69 | site.latch.Lock() 70 | 71 | var tidnew uint64 72 | - tidnew = tid.GenTID(site.sid) 73 | + // tidnew = tid.GenTID(site.sid) 74 | + tidnew = tid.GenTIDWithFAI(site.gtidref) 75 | + // site.gtidlk.Lock() 76 | + // tidnew = *site.gtidref 77 | + // *site.gtidref++ 78 | + // site.gtidlk.Unlock() 79 | + 80 | primitive.Assume(tidnew < 18446744073709551615) 81 | 82 | var tidmin uint64 = tidnew 83 | diff --git a/vmvcc/db.go b/vmvcc/db.go 84 | index 8da24d6..d2ad962 100644 85 | --- a/vmvcc/db.go 86 | +++ b/vmvcc/db.go 87 | @@ -1,6 +1,7 @@ 88 | package vmvcc 89 | 90 | import ( 91 | + "sync" 92 | "github.com/mit-pdos/vmvcc/config" 93 | "github.com/mit-pdos/vmvcc/index" 94 | "github.com/mit-pdos/vmvcc/wrbuf" 95 | @@ -15,6 +16,7 @@ type DB struct { 96 | latch *cfmutex.CFMutex 97 | // Next site ID to assign. 98 | sid uint64 99 | + gtid uint64 100 | // All transaction sites. 101 | sites []*txnsite.TxnSite 102 | // Index. 103 | @@ -28,11 +30,13 @@ func MkDB() *DB { 104 | db := &DB { proph : proph } 105 | db.latch = new(cfmutex.CFMutex) 106 | db.sites = make([]*txnsite.TxnSite, config.N_TXN_SITES) 107 | + db.gtid = 0 108 | 109 | // Call this once to establish invariants. 110 | + gtidlk := new(sync.Mutex) 111 | tid.GenTID(0) 112 | for i := uint64(0); i < config.N_TXN_SITES; i++ { 113 | - site := txnsite.MkTxnSite(i) 114 | + site := txnsite.MkTxnSite(i, &db.gtid, gtidlk) 115 | db.sites[i] = site 116 | } 117 | db.idx = index.MkIndex() 118 | -------------------------------------------------------------------------------- /osdi23/scripts/long-tpcc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ] 4 | then 5 | nruns=1 6 | else 7 | nruns=$1 8 | fi 9 | 10 | dir=./exp 11 | # rm -rf $dir 12 | mkdir -p $dir 13 | 14 | duration=30 15 | nthrds=32 16 | 17 | ./osdi23/scripts/sed-tplock.sh 18 | 19 | cc="tplock" 20 | 21 | fpath=$dir/long-tpcc-$cc.csv 22 | rm -f $fpath 23 | for i in $(seq $nruns) 24 | do 25 | for interval in 0 10000 5000 1000 500 100 26 | do 27 | stdbuf -o 0 go run ./benchmark/tpcc -nthrds $nthrds -stockscan $interval -duration $duration -debug false | tee -a $fpath 28 | done 29 | done 30 | 31 | ./osdi23/scripts/sed-mvcc.sh 32 | 33 | cc="mvcc" 34 | 35 | fpath=$dir/long-tpcc-$cc.csv 36 | rm -f $fpath 37 | for i in $(seq $nruns) 38 | do 39 | for interval in 0 10000 5000 1000 500 100 40 | do 41 | stdbuf -o 0 go run ./benchmark/tpcc -nthrds $nthrds -stockscan $interval -duration $duration -debug false | tee -a $fpath 42 | done 43 | done 44 | -------------------------------------------------------------------------------- /osdi23/scripts/long-ycsb.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ] 4 | then 5 | nruns=1 6 | else 7 | nruns=$1 8 | fi 9 | 10 | dir=./exp 11 | # rm -rf $dir 12 | mkdir -p $dir 13 | 14 | duration=30 15 | rkeys=1000000 16 | 17 | theta=0.85 18 | nkeys=4 19 | nthrds=24 20 | 21 | ./osdi23/scripts/sed-tplock.sh 22 | 23 | cc="tplock" 24 | 25 | fpath=$dir/long-ycsb-$cc.csv 26 | rm -f $fpath 27 | for i in $(seq $nruns) 28 | do 29 | for rdratio in 100 80 60 40 20 0 30 | do 31 | stdbuf -o 0 go run ./benchmark/ycsb -nthrds $nthrds -duration $duration -rdratio $rdratio -nkeys $nkeys -rkeys $rkeys -theta $theta -exp | tee -a $fpath 32 | done 33 | for rdratio in 100 80 60 40 20 0 34 | do 35 | stdbuf -o 0 go run ./benchmark/ycsb -nthrds $nthrds -duration $duration -rdratio $rdratio -nkeys $nkeys -rkeys $rkeys -theta $theta -long -exp | tee -a $fpath 36 | done 37 | done 38 | 39 | ./osdi23/scripts/sed-mvcc.sh 40 | 41 | cc="mvcc" 42 | 43 | fpath=$dir/long-ycsb-$cc.csv 44 | rm -f $fpath 45 | for i in $(seq $nruns) 46 | do 47 | for rdratio in 100 80 60 40 20 0 48 | do 49 | stdbuf -o 0 go run ./benchmark/ycsb -nthrds $nthrds -duration $duration -rdratio $rdratio -nkeys $nkeys -rkeys $rkeys -theta $theta -exp | tee -a $fpath 50 | done 51 | for rdratio in 100 80 60 40 20 0 52 | do 53 | stdbuf -o 0 go run ./benchmark/ycsb -nthrds $nthrds -duration $duration -rdratio $rdratio -nkeys $nkeys -rkeys $rkeys -theta $theta -long -exp | tee -a $fpath 54 | done 55 | done 56 | 57 | -------------------------------------------------------------------------------- /osdi23/scripts/nomemalloc.diff: -------------------------------------------------------------------------------- 1 | diff --git a/benchmark/ycsb.go b/benchmark/ycsb.go 2 | index f55424d..c16ba79 100644 3 | --- a/benchmark/ycsb.go 4 | +++ b/benchmark/ycsb.go 5 | @@ -55,11 +55,11 @@ func workerRWBody(txn *txn.Txn, keys []uint64, ops []int, buf []byte) bool { 6 | if ops[i] == ycsb.OP_RD { 7 | txn.Get(k) 8 | } else if ops[i] == ycsb.OP_WR { 9 | - for j := range buf { 10 | - buf[j] = 'b' 11 | - } 12 | - s := string(buf) 13 | - txn.Put(k, s) 14 | + // for j := range buf { 15 | + // buf[j] = 'b' 16 | + // } 17 | + // s := string(buf) 18 | + txn.Put(k, "bbbb") 19 | } 20 | } 21 | return true 22 | diff --git a/tuple/tuple.go b/tuple/tuple.go 23 | index 7fa359a..5ebe1e6 100644 24 | --- a/tuple/tuple.go 25 | +++ b/tuple/tuple.go 26 | @@ -91,7 +91,8 @@ func (tuple *Tuple) appendVersion(tid uint64, val string) { 27 | val : val, 28 | deleted : false, 29 | } 30 | - tuple.vers = append(tuple.vers, verNew) 31 | + // tuple.vers = append(tuple.vers, verNew) 32 | + tuple.vers[0] = verNew 33 | 34 | /* Release the permission to update this tuple. */ 35 | tuple.owned = false 36 | -------------------------------------------------------------------------------- /osdi23/scripts/optimization.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ] 4 | then 5 | nruns=1 6 | else 7 | nruns=$1 8 | fi 9 | 10 | if [ -z "$2" ] 11 | then 12 | echo 'Please specify configuration name.' 13 | exit 1 14 | fi 15 | 16 | dir=./exp 17 | # rm -rf $dir 18 | mkdir -p $dir 19 | 20 | duration=30 21 | rkeys=1000000 22 | 23 | theta=0.2 24 | nkeys=1 25 | rdratio=100 26 | 27 | fpath=$dir/optimization-$2.csv 28 | rm -f $fpath 29 | for i in $(seq $nruns) 30 | do 31 | for nthrds in 1 2 4 8 16 32 32 | do 33 | stdbuf -o 0 go run ./benchmark/ycsb -nthrds $nthrds -duration $duration -rdratio $rdratio -nkeys $nkeys -rkeys $rkeys -theta $theta -exp | tee -a $fpath 34 | done 35 | done 36 | -------------------------------------------------------------------------------- /osdi23/scripts/scalability.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ] 4 | then 5 | nruns=1 6 | else 7 | nruns=$1 8 | fi 9 | 10 | dir=./exp 11 | # rm -rf $dir 12 | mkdir -p $dir 13 | 14 | duration=30 15 | rkeys=1000000 16 | 17 | nkeys=4 18 | 19 | for rdratio in 0 100 20 | do 21 | for theta in 0.8 0.85 0.9 0.95 22 | do 23 | fpath=$dir/scalability-$rdratio-$theta.csv 24 | rm -f $fpath 25 | for i in $(seq $nruns) 26 | do 27 | for nthrds in 1 2 4 8 16 32 28 | # for nthrds in 4 29 | do 30 | stdbuf -o 0 go run ./benchmark/ycsb -nthrds $nthrds -duration $duration -rdratio $rdratio -nkeys $nkeys -rkeys $rkeys -theta $theta -exp | tee -a $fpath 31 | done 32 | done 33 | done 34 | done 35 | -------------------------------------------------------------------------------- /osdi23/scripts/sed-mvcc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sed -i 's/vmvcc\/osdi23\/tplock/vmvcc\/vmvcc/' ./benchmark/tpcc/*.go 4 | sed -i 's/vmvcc\/osdi23\/tplock/vmvcc\/vmvcc/' ./benchmark/ycsb/ycsb.go 5 | sed -i '55s/NewROTxn()/NewTxn()/' ./benchmark/tpcc/tpcc.go 6 | -------------------------------------------------------------------------------- /osdi23/scripts/sed-tplock.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sed -i 's/vmvcc\/vmvcc/vmvcc\/osdi23\/tplock/' ./benchmark/tpcc/*.go 4 | sed -i 's/vmvcc\/vmvcc/vmvcc\/osdi23\/tplock/' ./benchmark/ycsb/ycsb.go 5 | sed -i '55s/NewTxn()/NewROTxn()/' ./benchmark/tpcc/tpcc.go 6 | -------------------------------------------------------------------------------- /osdi23/scripts/shardpad.diff: -------------------------------------------------------------------------------- 1 | diff --git a/tid/tid.go b/tid/tid.go 2 | index c884924..0aa0674 100644 3 | --- a/tid/tid.go 4 | +++ b/tid/tid.go 5 | @@ -1,11 +1,17 @@ 6 | package tid 7 | 8 | import ( 9 | + "sync/atomic" 10 | "github.com/goose-lang/std" 11 | "github.com/mit-pdos/gokv/grove_ffi" 12 | "github.com/mit-pdos/vmvcc/config" 13 | ) 14 | 15 | +func GenTIDWithFAI(gtidref *uint64) uint64 { 16 | + tid := atomic.AddUint64(gtidref, 1) 17 | + return tid 18 | +} 19 | + 20 | func GenTID(sid uint64) uint64 { 21 | var tid uint64 22 | 23 | diff --git a/txnsite/txnsite.go b/txnsite/txnsite.go 24 | index 91e325d..9be4727 100644 25 | --- a/txnsite/txnsite.go 26 | +++ b/txnsite/txnsite.go 27 | @@ -1,7 +1,8 @@ 28 | package txnsite 29 | 30 | import ( 31 | - "github.com/mit-pdos/vmvcc/tid" 32 | + "sync" 33 | + // "github.com/mit-pdos/vmvcc/tid" 34 | "github.com/mit-pdos/vmvcc/cfmutex" 35 | "github.com/tchajed/goose/machine" 36 | ) 37 | @@ -14,13 +15,17 @@ type TxnSite struct { 38 | // Active transaction IDs. 39 | padding [4]uint64 40 | // TODO: should only need 3, change it once performance is tested. 41 | + gtidref *uint64 42 | + gtidlk *sync.Mutex 43 | } 44 | 45 | -func MkTxnSite(sid uint64) *TxnSite { 46 | +func MkTxnSite(sid uint64, gtidref *uint64, gtidlk *sync.Mutex) *TxnSite { 47 | site := new(TxnSite) 48 | site.latch = new(cfmutex.CFMutex) 49 | site.tids = make([]uint64, 0, 8) 50 | site.sid = sid 51 | + site.gtidref = gtidref 52 | + site.gtidlk = gtidlk 53 | 54 | return site 55 | } 56 | @@ -30,7 +35,12 @@ func (site *TxnSite) Activate() uint64 { 57 | site.latch.Lock() 58 | 59 | var t uint64 60 | - t = tid.GenTID(site.sid) 61 | + // t = tid.GenTID(site.sid) 62 | + // t = tid.GenTIDWithFAI(gtid) 63 | + site.gtidlk.Lock() 64 | + t = *site.gtidref 65 | + *site.gtidref++ 66 | + site.gtidlk.Unlock() 67 | // Assume TID never overflow. 68 | primitive.Assume(t < 18446744073709551615) 69 | 70 | @@ -76,7 +86,13 @@ func (site *TxnSite) GetSafeTS() uint64 { 71 | site.latch.Lock() 72 | 73 | var tidnew uint64 74 | - tidnew = tid.GenTID(site.sid) 75 | + // tidnew = tid.GenTID(site.sid) 76 | + // tidnew = tid.GenTIDWithFAI(gtid) 77 | + site.gtidlk.Lock() 78 | + tidnew = *site.gtidref 79 | + *site.gtidref++ 80 | + site.gtidlk.Unlock() 81 | + 82 | primitive.Assume(tidnew < 18446744073709551615) 83 | 84 | var tidmin uint64 = tidnew 85 | diff --git a/vmvcc/db.go b/vmvcc/db.go 86 | index 8da24d6..d2ad962 100644 87 | --- a/vmvcc/db.go 88 | +++ b/vmvcc/db.go 89 | @@ -1,6 +1,7 @@ 90 | package vmvcc 91 | 92 | import ( 93 | + "sync" 94 | "github.com/mit-pdos/vmvcc/config" 95 | "github.com/mit-pdos/vmvcc/index" 96 | "github.com/mit-pdos/vmvcc/wrbuf" 97 | @@ -15,6 +16,7 @@ type DB struct { 98 | latch *cfmutex.CFMutex 99 | // Next site ID to assign. 100 | sid uint64 101 | + gtid uint64 102 | // All transaction sites. 103 | sites []*txnsite.TxnSite 104 | // Index. 105 | @@ -28,11 +30,13 @@ func MkDB() *DB { 106 | db := &DB { proph : proph } 107 | db.latch = new(cfmutex.CFMutex) 108 | db.sites = make([]*txnsite.TxnSite, config.N_TXN_SITES) 109 | + db.gtid = 0 110 | 111 | // Call this once to establish invariants. 112 | + gtidlk := new(sync.Mutex) 113 | tid.GenTID(0) 114 | for i := uint64(0); i < config.N_TXN_SITES; i++ { 115 | - site := txnsite.MkTxnSite(i) 116 | + site := txnsite.MkTxnSite(i, &db.gtid, gtidlk) 117 | db.sites[i] = site 118 | } 119 | db.idx = index.MkIndex() 120 | -------------------------------------------------------------------------------- /osdi23/scripts/silo.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z "$1" ] 4 | then 5 | nruns=1 6 | else 7 | nruns=$1 8 | fi 9 | 10 | GO=go 11 | dir=./exp 12 | # rm -rf $dir 13 | mkdir -p $dir 14 | 15 | duration=30 16 | rkeys=1000000 17 | theta=-1 18 | 19 | nkeys=1 20 | 21 | fpath=$dir/silo-ycsb.csv 22 | rm -f $fpath 23 | for i in $(seq $nruns) 24 | do 25 | for rdratio in 100 50 0 200 26 | do 27 | # for nthrds in $(seq 16) 28 | for nthrds in 32 29 | do 30 | stdbuf -o 0 $GO run ./benchmark/ycsb -nthrds $nthrds -duration $duration -rdratio $rdratio -nkeys $nkeys -rkeys $rkeys -theta $theta -exp | tee -a $fpath 31 | done 32 | done 33 | done 34 | 35 | fpath=$dir/silo-tpcc.csv 36 | rm -f $fpath 37 | for i in $(seq $nruns) 38 | do 39 | for workloads in '45,43,4,4,4' 40 | do 41 | # for nthrds in $(seq 8) 42 | for nthrds in 1 32 43 | do 44 | stdbuf -o 0 $GO run ./benchmark/tpcc -nthrds $nthrds -duration $duration -workloads $workloads -debug false | tee -a $fpath 45 | done 46 | done 47 | done 48 | -------------------------------------------------------------------------------- /osdi23/tplock/tplock.go: -------------------------------------------------------------------------------- 1 | package vmvcc 2 | 3 | import ( 4 | //"time" 5 | "github.com/mit-pdos/vmvcc/config" 6 | "sync" 7 | // "github.com/mit-pdos/vmvcc/cfmutex" 8 | ) 9 | 10 | /** 11 | * Tuple. 12 | */ 13 | type Tuple struct { 14 | latch *sync.Mutex 15 | rcond *sync.Cond 16 | lock uint32 17 | del bool 18 | val string 19 | } 20 | 21 | func MkTuple() *Tuple { 22 | tuple := new(Tuple) 23 | tuple.latch = new(sync.Mutex) 24 | tuple.rcond = sync.NewCond(tuple.latch) 25 | tuple.lock = 0 26 | tuple.del = true 27 | 28 | return tuple 29 | } 30 | 31 | func (tuple *Tuple) Own(ownrd bool) bool { 32 | var ok bool 33 | 34 | tuple.latch.Lock() 35 | 36 | if tuple.lock == 0 || (tuple.lock == 1 && ownrd) { 37 | ok = true 38 | tuple.lock = 0xffffffff 39 | } else { 40 | ok = false 41 | } 42 | tuple.latch.Unlock() 43 | 44 | return ok 45 | } 46 | 47 | func (tuple *Tuple) WriteLock() { 48 | tuple.latch.Lock() 49 | } 50 | 51 | func (tuple *Tuple) Write(val string) { 52 | tuple.val = val 53 | tuple.del = false 54 | tuple.lock = 0 55 | tuple.rcond.Broadcast() 56 | 57 | tuple.latch.Unlock() 58 | } 59 | 60 | func (tuple *Tuple) Kill() { 61 | tuple.del = true 62 | tuple.lock = 0 63 | tuple.rcond.Broadcast() 64 | 65 | tuple.latch.Unlock() 66 | } 67 | 68 | func (tuple *Tuple) Free() { 69 | tuple.latch.Lock() 70 | 71 | tuple.lock = 0 72 | tuple.rcond.Broadcast() 73 | 74 | tuple.latch.Unlock() 75 | } 76 | 77 | func (tuple *Tuple) Read() (string, bool) { 78 | tuple.latch.Lock() 79 | 80 | for tuple.lock == 0xffffffff { 81 | tuple.rcond.Wait() 82 | } 83 | 84 | tuple.lock++ 85 | 86 | tuple.latch.Unlock() 87 | return tuple.val, !tuple.del 88 | } 89 | 90 | /** 91 | * Call this only when the transaction already owns the lock of this key. 92 | */ 93 | func (tuple *Tuple) UnconditionalRead() (string, bool) { 94 | tuple.latch.Lock() 95 | 96 | tuple.latch.Unlock() 97 | return tuple.val, !tuple.del 98 | } 99 | 100 | func (tuple *Tuple) ReadRelease() { 101 | tuple.latch.Lock() 102 | 103 | tuple.lock-- 104 | 105 | tuple.latch.Unlock() 106 | } 107 | 108 | /** 109 | * Write buffer. 110 | */ 111 | type WrEnt struct { 112 | key uint64 113 | val string 114 | wr bool 115 | tpl *Tuple 116 | } 117 | 118 | func search(ents []WrEnt, key uint64) (uint64, bool) { 119 | var pos uint64 = 0 120 | for pos < uint64(len(ents)) && key != ents[pos].key { 121 | pos++ 122 | } 123 | 124 | found := pos < uint64(len(ents)) 125 | return pos, found 126 | } 127 | 128 | func swap(ents []WrEnt, i, j uint64) { 129 | tmp := ents[i] 130 | ents[i] = ents[j] 131 | ents[j] = tmp 132 | } 133 | 134 | type WrBuf struct { 135 | ents []WrEnt 136 | } 137 | 138 | func MkWrBuf() *WrBuf { 139 | wrbuf := new(WrBuf) 140 | wrbuf.ents = make([]WrEnt, 0, 128) 141 | return wrbuf 142 | } 143 | 144 | func (wrbuf *WrBuf) sortEntsByKey() { 145 | ents := wrbuf.ents 146 | var i uint64 = 1 147 | for i < uint64(len(ents)) { 148 | var j uint64 = i 149 | for j > 0 { 150 | if ents[j-1].key <= ents[j].key { 151 | break 152 | } 153 | swap(ents, j-1, j) 154 | j-- 155 | } 156 | i++ 157 | } 158 | } 159 | 160 | func (wrbuf *WrBuf) Exists(key uint64) bool { 161 | _, found := search(wrbuf.ents, key) 162 | return found 163 | } 164 | 165 | func (wrbuf *WrBuf) Lookup(key uint64) (string, bool, bool) { 166 | pos, found := search(wrbuf.ents, key) 167 | if found { 168 | ent := wrbuf.ents[pos] 169 | return ent.val, ent.wr, true 170 | } 171 | 172 | return "", false, false 173 | } 174 | 175 | func (wrbuf *WrBuf) Add(key uint64, val string, wr bool, tpl *Tuple) { 176 | ent := WrEnt{ 177 | key: key, 178 | val: val, 179 | wr: wr, 180 | tpl: tpl, 181 | } 182 | wrbuf.ents = append(wrbuf.ents, ent) 183 | } 184 | 185 | func (wrbuf *WrBuf) Write(key uint64, val string) { 186 | pos, found := search(wrbuf.ents, key) 187 | if found { 188 | ent := &wrbuf.ents[pos] 189 | ent.val = val 190 | ent.wr = true 191 | return 192 | } 193 | 194 | ent := WrEnt{ 195 | key: key, 196 | val: val, 197 | wr: true, 198 | } 199 | wrbuf.ents = append(wrbuf.ents, ent) 200 | } 201 | 202 | func (wrbuf *WrBuf) Delete(key uint64) { 203 | pos, found := search(wrbuf.ents, key) 204 | if found { 205 | ent := &wrbuf.ents[pos] 206 | ent.wr = false 207 | return 208 | } 209 | 210 | ent := WrEnt{ 211 | key: key, 212 | wr: false, 213 | } 214 | wrbuf.ents = append(wrbuf.ents, ent) 215 | } 216 | 217 | func (wrbuf *WrBuf) Remove(key uint64) { 218 | pos, found := search(wrbuf.ents, key) 219 | if !found { 220 | return 221 | } 222 | 223 | ent := wrbuf.ents[pos] 224 | wrbuf.ents[pos] = wrbuf.ents[len(wrbuf.ents)-1] 225 | wrbuf.ents[len(wrbuf.ents)-1] = ent 226 | wrbuf.ents = wrbuf.ents[:len(wrbuf.ents)-1] 227 | } 228 | 229 | func (wrbuf *WrBuf) OpenTuples(idx *Index, rdset *WrBuf) bool { 230 | /* Sort entries by key to prevent deadlock. */ 231 | wrbuf.sortEntsByKey() 232 | 233 | /* Start acquiring locks for each key. */ 234 | ents := wrbuf.ents 235 | var pos uint64 = 0 236 | for pos < uint64(len(ents)) { 237 | ent := ents[pos] 238 | tpl := idx.GetTuple(ent.key) 239 | found := rdset.Exists(ent.key) 240 | ret := tpl.Own(found) 241 | if !ret { 242 | break 243 | } 244 | /* Escalate the read lock to write lock. */ 245 | rdset.Remove(ent.key) 246 | // A more efficient way is updating field `tpl`, but not supported by Goose. 247 | ents[pos] = WrEnt{ 248 | key: ent.key, 249 | val: ent.val, 250 | wr: ent.wr, 251 | tpl: tpl, 252 | } 253 | pos++ 254 | } 255 | 256 | /* Release partially acquired locks. */ 257 | if pos < uint64(len(ents)) { 258 | var i uint64 = 0 259 | for i < pos { 260 | tpl := ents[i].tpl 261 | tpl.Free() 262 | i++ 263 | } 264 | return false 265 | } 266 | 267 | for _, ent := range ents { 268 | ent.tpl.WriteLock() 269 | } 270 | return true 271 | } 272 | 273 | func (wrbuf *WrBuf) UpdateTuples() { 274 | ents := wrbuf.ents 275 | for _, ent := range ents { 276 | tpl := ent.tpl 277 | if ent.wr { 278 | tpl.Write(ent.val) 279 | } else { 280 | tpl.Kill() 281 | } 282 | } 283 | } 284 | 285 | func (wrbuf *WrBuf) ReleaseTuples() { 286 | ents := wrbuf.ents 287 | for _, ent := range ents { 288 | tpl := ent.tpl 289 | tpl.ReadRelease() 290 | } 291 | } 292 | 293 | func (wrbuf *WrBuf) Clear() { 294 | wrbuf.ents = wrbuf.ents[:0] 295 | } 296 | 297 | /** 298 | * Index. 299 | */ 300 | type IndexBucket struct { 301 | latch *sync.Mutex 302 | m map[uint64]*Tuple 303 | } 304 | 305 | type Index struct { 306 | buckets []*IndexBucket 307 | } 308 | 309 | func MkIndex() *Index { 310 | idx := new(Index) 311 | idx.buckets = make([]*IndexBucket, config.N_IDX_BUCKET) 312 | for i := uint64(0); i < config.N_IDX_BUCKET; i++ { 313 | b := new(IndexBucket) 314 | b.latch = new(sync.Mutex) 315 | b.m = make(map[uint64]*Tuple) 316 | idx.buckets[i] = b 317 | } 318 | return idx 319 | } 320 | 321 | func getBucket(key uint64) uint64 { 322 | return (key>>52 + key) % config.N_IDX_BUCKET 323 | // return key % config.N_IDX_BUCKET 324 | } 325 | 326 | /** 327 | * Note that `GetTuple` will always create a tuple when there is no entry in 328 | * `m`. This design choice seems to be wasting memory resource as we'll always 329 | * allocate a `Tuple` even with an empty `txn.Read`, but is actually a must: An 330 | * empty `txn.Read` should prevent other transactions from inserting a new one 331 | * during the execution of this transaction. 332 | */ 333 | func (idx *Index) GetTuple(key uint64) *Tuple { 334 | b := getBucket(key) 335 | bucket := idx.buckets[b] 336 | bucket.latch.Lock() 337 | 338 | /* Return the tuple if there exists one. */ 339 | tupleCur, ok := bucket.m[key] 340 | if ok { 341 | bucket.latch.Unlock() 342 | return tupleCur 343 | } 344 | 345 | /* Create a new tuple and associate it with the key. */ 346 | tupleNew := MkTuple() 347 | bucket.m[key] = tupleNew 348 | 349 | bucket.latch.Unlock() 350 | return tupleNew 351 | } 352 | 353 | /** 354 | * Transaction. 355 | */ 356 | type Txn struct { 357 | wrbuf *WrBuf 358 | rdbuf *WrBuf 359 | idx *Index 360 | rdonly bool 361 | rdset map[uint64]*Tuple 362 | } 363 | 364 | type DB struct { 365 | idx *Index 366 | } 367 | 368 | func MkDB() *DB { 369 | db := new(DB) 370 | db.idx = MkIndex() 371 | return db 372 | } 373 | 374 | func (db *DB) NewTxn() *Txn { 375 | /* Make a new txn. */ 376 | txn := new(Txn) 377 | txn.wrbuf = MkWrBuf() 378 | txn.rdbuf = MkWrBuf() 379 | txn.idx = db.idx 380 | txn.rdonly = false 381 | 382 | return txn 383 | } 384 | 385 | func (db *DB) ActivateGC() { 386 | /* Do nothing. Just for compatibility. */ 387 | } 388 | 389 | func (txn *Txn) Write(key uint64, val string) { 390 | wrbuf := txn.wrbuf 391 | wrbuf.Write(key, val) 392 | } 393 | 394 | func (txn *Txn) Delete(key uint64) bool { 395 | wrbuf := txn.wrbuf 396 | wrbuf.Delete(key) 397 | 398 | return true 399 | } 400 | 401 | func (txn *Txn) get(key uint64) (string, bool) { 402 | /* First try to find `key` in the local write set. */ 403 | wrbuf := txn.wrbuf 404 | valb, wr, found := wrbuf.Lookup(key) 405 | if found { 406 | return valb, wr 407 | } 408 | 409 | rdbuf := txn.rdbuf 410 | valb, wr, found = rdbuf.Lookup(key) 411 | if found { 412 | return valb, wr 413 | } 414 | 415 | idx := txn.idx 416 | tuple := idx.GetTuple(key) 417 | val, found := tuple.Read() 418 | rdbuf.Add(key, val, found, tuple) 419 | 420 | return val, found 421 | } 422 | 423 | func (txn *Txn) Read(key uint64) (string, bool) { 424 | if txn.rdonly { 425 | val, found := txn.getRO(key) 426 | return val, found 427 | } 428 | 429 | val, found := txn.get(key) 430 | return val, found 431 | } 432 | 433 | func (txn *Txn) begin() { 434 | txn.wrbuf.Clear() 435 | txn.rdbuf.Clear() 436 | } 437 | 438 | func (txn *Txn) acquire() bool { 439 | ok := txn.wrbuf.OpenTuples(txn.idx, txn.rdbuf) 440 | return ok 441 | } 442 | 443 | func (txn *Txn) commit() { 444 | /* At this point we have all the read and write locks. First release wlocks. */ 445 | txn.wrbuf.UpdateTuples() 446 | txn.rdbuf.ReleaseTuples() 447 | } 448 | 449 | func (txn *Txn) abort() { 450 | txn.rdbuf.ReleaseTuples() 451 | } 452 | 453 | func (txn *Txn) Run(body func(txn *Txn) bool) bool { 454 | if txn.rdonly { 455 | /* Read-only transactions never abort. */ 456 | txn.beginRO() 457 | body(txn) 458 | txn.commitRO() 459 | return true 460 | } 461 | 462 | txn.begin() 463 | cmt := body(txn) 464 | if !cmt { 465 | txn.abort() 466 | return false 467 | } 468 | ok := txn.acquire() 469 | if !ok { 470 | txn.abort() 471 | return false 472 | } 473 | txn.commit() 474 | return true 475 | } 476 | 477 | /** 478 | * Using a slice to keep track of a large read set is too slow, on the other 479 | * hand, using a go map to keep track of a small read set is also too slow. So 480 | * we have this interface for 2PL to create transactions optimized for read-only 481 | * workload. 482 | */ 483 | func (db *DB) NewROTxn() *Txn { 484 | /* Make a new txn. */ 485 | txn := new(Txn) 486 | txn.idx = db.idx 487 | txn.rdonly = true 488 | 489 | return txn 490 | } 491 | 492 | func (txn *Txn) getRO(key uint64) (string, bool) { 493 | rdset := txn.rdset 494 | tuple, locked := rdset[key] 495 | if locked { 496 | val, found := tuple.UnconditionalRead() 497 | return val, found 498 | } 499 | 500 | idx := txn.idx 501 | tuple = idx.GetTuple(key) 502 | val, found := tuple.Read() 503 | rdset[key] = tuple 504 | 505 | return val, found 506 | } 507 | 508 | func (txn *Txn) beginRO() { 509 | txn.rdset = make(map[uint64]*Tuple) 510 | } 511 | 512 | func (txn *Txn) commitRO() { 513 | for _, tpl := range txn.rdset { 514 | tpl.ReadRelease() 515 | } 516 | } 517 | -------------------------------------------------------------------------------- /osdi23/tplock/tplock_test.go: -------------------------------------------------------------------------------- 1 | package vmvcc 2 | 3 | import ( 4 | "fmt" 5 | "github.com/stretchr/testify/assert" 6 | "math/rand" 7 | "sync" 8 | "testing" 9 | ) 10 | 11 | func TestReadRead(t *testing.T) { 12 | assert := assert.New(t) 13 | db := MkDB() 14 | txno := db.NewTxn() 15 | body := func(txni *Txn) bool { 16 | _, found := txni.Read(0) 17 | assert.Equal(false, found) 18 | assert.Equal(uint32(1), db.idx.GetTuple(0).lock) 19 | _, found = txni.Read(0) 20 | assert.Equal(false, found) 21 | assert.Equal(uint32(1), db.idx.GetTuple(0).lock) 22 | return true 23 | } 24 | ok := txno.Run(body) 25 | assert.Equal(true, ok) 26 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 27 | assert.Equal(true, db.idx.GetTuple(0).del) 28 | } 29 | 30 | func TestReadWriteCommit(t *testing.T) { 31 | assert := assert.New(t) 32 | db := MkDB() 33 | txno := db.NewTxn() 34 | body := func(txni *Txn) bool { 35 | _, found := txni.Read(0) 36 | assert.Equal(false, found) 37 | assert.Equal(uint32(1), db.idx.GetTuple(0).lock) 38 | txni.Write(0, "hello") 39 | /* `lock` should still be 1 before commit. */ 40 | assert.Equal(uint32(1), db.idx.GetTuple(0).lock) 41 | return true 42 | } 43 | ok := txno.Run(body) 44 | assert.Equal(true, ok) 45 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 46 | assert.Equal(false, db.idx.GetTuple(0).del) 47 | assert.Equal("hello", db.idx.GetTuple(0).val) 48 | } 49 | 50 | func TestReadWriteAbort(t *testing.T) { 51 | assert := assert.New(t) 52 | db := MkDB() 53 | txno := db.NewTxn() 54 | body := func(txni *Txn) bool { 55 | _, found := txni.Read(0) 56 | assert.Equal(false, found) 57 | assert.Equal(uint32(1), db.idx.GetTuple(0).lock) 58 | txni.Write(0, "hello") 59 | /* `lock` should still be 1 before commit. */ 60 | assert.Equal(uint32(1), db.idx.GetTuple(0).lock) 61 | return false 62 | } 63 | ok := txno.Run(body) 64 | assert.Equal(false, ok) 65 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 66 | assert.Equal(true, db.idx.GetTuple(0).del) 67 | } 68 | 69 | func TestWriteReadCommit(t *testing.T) { 70 | assert := assert.New(t) 71 | db := MkDB() 72 | txno := db.NewTxn() 73 | body := func(txni *Txn) bool { 74 | txni.Write(0, "hello") 75 | /* `lock` should still be 0 before commit. */ 76 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 77 | v, found := txni.Read(0) 78 | assert.Equal(true, found) 79 | assert.Equal("hello", v) 80 | /* write set hit, not even acquire read lock */ 81 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 82 | return true 83 | } 84 | ok := txno.Run(body) 85 | assert.Equal(true, ok) 86 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 87 | assert.Equal(false, db.idx.GetTuple(0).del) 88 | assert.Equal("hello", db.idx.GetTuple(0).val) 89 | } 90 | 91 | func TestWriteReadAbort(t *testing.T) { 92 | assert := assert.New(t) 93 | db := MkDB() 94 | txno := db.NewTxn() 95 | body := func(txni *Txn) bool { 96 | txni.Write(0, "hello") 97 | /* `lock` should still be 0 before commit. */ 98 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 99 | v, found := txni.Read(0) 100 | assert.Equal(true, found) 101 | assert.Equal("hello", v) 102 | /* write set hit, not even acquire read lock */ 103 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 104 | return false 105 | } 106 | ok := txno.Run(body) 107 | assert.Equal(false, ok) 108 | assert.Equal(uint32(0), db.idx.GetTuple(0).lock) 109 | assert.Equal(true, db.idx.GetTuple(0).del) 110 | } 111 | 112 | func worker(i int, txno *Txn) { 113 | t := 0 114 | c := 0 115 | rd := rand.New(rand.NewSource(int64(i))) 116 | body := func(txni *Txn) bool { 117 | for i := 0; i < 5; i++ { 118 | key := rd.Uint64() % 16 119 | if rd.Uint64()%2 == 0 { 120 | txni.Read(key) 121 | } else { 122 | txni.Write(key, "hello") 123 | } 124 | } 125 | return true 126 | } 127 | for j := 0; j < 10000; j++ { 128 | ok := txno.Run(body) 129 | if ok { 130 | c++ 131 | } 132 | t++ 133 | } 134 | fmt.Printf("Thread %d : (%d / %d)\n", i, c, t) 135 | } 136 | 137 | func TestStress(t *testing.T) { 138 | assert := assert.New(t) 139 | db := MkDB() 140 | 141 | /* Initialize each key to 0. */ 142 | var wg sync.WaitGroup 143 | for i := 0; i < 8; i++ { 144 | txno := db.NewTxn() 145 | wg.Add(1) 146 | go func(x int) { 147 | defer wg.Done() 148 | worker(x, txno) 149 | }(i) 150 | } 151 | wg.Wait() 152 | 153 | var key uint64 154 | for key = 0; key < 16; key++ { 155 | assert.Equal(uint32(0), db.idx.GetTuple(key).lock) 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /scripts/loc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DIRS="vmvcc txnsite wrbuf tuple index tid cfmutex config" 4 | FILES_EXEC=$(find ${DIRS} | grep -E ".go$" | grep -vE "_test.go$" | tr '\n' ' ') 5 | FILES_TEST=$(find ${DIRS} | grep -E "_test.go$" | tr '\n' ' ') 6 | 7 | echo "Executable files:" 8 | wc -l $FILES_EXEC 9 | 10 | echo "Testing files:" 11 | wc -l $FILES_TEST 12 | 13 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -eu 4 | 5 | DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | 7 | cd "$DIR/.." 8 | go test ./... 9 | -------------------------------------------------------------------------------- /tid/tid.go: -------------------------------------------------------------------------------- 1 | package tid 2 | 3 | import ( 4 | "github.com/goose-lang/std" 5 | "github.com/mit-pdos/gokv/grove_ffi" 6 | "github.com/mit-pdos/vmvcc/config" 7 | ) 8 | 9 | func GenTID(sid uint64) uint64 { 10 | var tid uint64 11 | 12 | /* Call `GetTSC` and round the result up to site ID boundary. */ 13 | tid = grove_ffi.GetTSC() 14 | // XXX: this would be buggy: 15 | // return tid 16 | 17 | tid = std.SumAssumeNoOverflow(tid, config.N_TXN_SITES)/config.N_TXN_SITES*config.N_TXN_SITES + sid 18 | // Below is the old (and wrong) version where we simply round the result, 19 | // up or down, to site ID boundary. 20 | // tid = (tid & ^(config.N_TXN_SITES - 1)) + sid 21 | 22 | /* Wait until TSC exceeds TID. */ 23 | for grove_ffi.GetTSC() <= tid { 24 | } 25 | 26 | return tid 27 | } 28 | -------------------------------------------------------------------------------- /trusted_proph/proph.go: -------------------------------------------------------------------------------- 1 | package trusted_proph 2 | 3 | import ( 4 | "github.com/goose-lang/primitive" 5 | "github.com/mit-pdos/vmvcc/wrbuf" 6 | ) 7 | 8 | type ProphId = primitive.ProphId 9 | 10 | func NewProph() ProphId { 11 | return primitive.NewProph() 12 | } 13 | 14 | func ResolveRead(p ProphId, tid uint64, key uint64) {} 15 | func ResolveAbort(p ProphId, tid uint64) {} 16 | func ResolveCommit(p ProphId, tid uint64, wrbuf *wrbuf.WrBuf) {} 17 | -------------------------------------------------------------------------------- /tuple/tuple.go: -------------------------------------------------------------------------------- 1 | package tuple 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/common" 5 | "sync" 6 | ) 7 | 8 | // @ts 9 | // Starting timestamp of this version, and also ending timestamp of the next 10 | // version. Lifetime is a half-open interval: (ts of this, ts of next]. 11 | // 12 | // @del 13 | // Tombstone of this version. 14 | // 15 | // @val 16 | // Value of this version. 17 | type Version struct { 18 | ts uint64 19 | del bool 20 | val string 21 | } 22 | 23 | // @owned 24 | // Write lock of this tuple. Acquired before committing. 25 | // 26 | // @rcond 27 | // Condition variable to wake up readers of this tuple. 28 | // 29 | // @tslast 30 | // Timestamp of the last reader or last writer + 1. 31 | // 32 | // @vers 33 | // Versions. 34 | type Tuple struct { 35 | latch *sync.Mutex 36 | rcond *sync.Cond 37 | owned bool 38 | tslast uint64 39 | vers []Version 40 | } 41 | 42 | func findVersion(tid uint64, vers []Version) Version { 43 | var ver Version 44 | length := uint64(len(vers)) 45 | var idx uint64 = 0 46 | for idx < length { 47 | ver = vers[length-idx-1] 48 | if tid > ver.ts { 49 | break 50 | } 51 | idx++ 52 | } 53 | return ver 54 | } 55 | 56 | func (tuple *Tuple) Own(tid uint64) uint64 { 57 | tuple.latch.Lock() 58 | 59 | // Return an error if the caller txn tries to update the tuple that has 60 | // already been read or written by another txn with a higher timestamp. This 61 | // ensures serializability. 62 | if tid < tuple.tslast { 63 | tuple.latch.Unlock() 64 | return common.RET_UNSERIALIZABLE 65 | } 66 | 67 | // Return an error if the latest version is already owned by another txn. 68 | if tuple.owned { 69 | tuple.latch.Unlock() 70 | return common.RET_RETRY 71 | } 72 | 73 | // Acquire the permission to update this tuple (will do on commit). 74 | tuple.owned = true 75 | 76 | tuple.latch.Unlock() 77 | return common.RET_SUCCESS 78 | } 79 | 80 | // Call @WriteOpen before @AppendVersion and @KillVersion. 81 | func (tuple *Tuple) WriteOpen() { 82 | tuple.latch.Lock() 83 | } 84 | 85 | func (tuple *Tuple) appendVersion(tid uint64, val string) { 86 | // Create a new version and add it to the version chain. 87 | verNew := Version{ 88 | ts: tid, 89 | del: false, 90 | val: val, 91 | } 92 | tuple.vers = append(tuple.vers, verNew) 93 | 94 | // Release the permission to update this tuple. 95 | tuple.owned = false 96 | 97 | tuple.tslast = tid + 1 98 | } 99 | 100 | // Append a new version (@tid, @val) to this tuple. 101 | func (tuple *Tuple) AppendVersion(tid uint64, val string) { 102 | tuple.appendVersion(tid, val) 103 | 104 | // Wake up txns waiting on reading this tuple. 105 | tuple.rcond.Broadcast() 106 | 107 | tuple.latch.Unlock() 108 | } 109 | 110 | func (tuple *Tuple) killVersion(tid uint64) bool { 111 | // Create a tombstone and add it to the version chain. 112 | verNew := Version{ 113 | ts: tid, 114 | del: true, 115 | } 116 | tuple.vers = append(tuple.vers, verNew) 117 | 118 | // Release the permission to update this tuple. 119 | tuple.owned = false 120 | 121 | tuple.tslast = tid + 1 122 | 123 | // TODO: Differentiate a successful and a no-op deletion. 124 | return true 125 | } 126 | 127 | // Append a tombstone version of timestamp @tid to this tuple. 128 | func (tuple *Tuple) KillVersion(tid uint64) uint64 { 129 | ok := tuple.killVersion(tid) 130 | var ret uint64 131 | if ok { 132 | ret = common.RET_SUCCESS 133 | } else { 134 | ret = common.RET_NONEXIST 135 | } 136 | 137 | // Wake up txns waiting on reading this tuple. 138 | tuple.rcond.Broadcast() 139 | 140 | tuple.latch.Unlock() 141 | return ret 142 | } 143 | 144 | // Release the write lock without modifying the tuple. 145 | func (tuple *Tuple) Free() { 146 | tuple.latch.Lock() 147 | 148 | // Release the permission to update this tuple. 149 | tuple.owned = false 150 | 151 | // Wake up txns waiting on reading this tuple. 152 | tuple.rcond.Broadcast() 153 | 154 | tuple.latch.Unlock() 155 | } 156 | 157 | // Call @ReadWait before @ReadVersion. 158 | // This design allows us to resolve the prophecy at the txn level. 159 | func (tuple *Tuple) ReadWait(tid uint64) { 160 | tuple.latch.Lock() 161 | 162 | // The only case where a writer would block a reader is when the reader is 163 | // trying to read the latest version (i.e., @tid > @tuple.ts) AND the latest 164 | // version may change in the future (i.e., @tuple.owned = true). 165 | for tid > tuple.tslast && tuple.owned { 166 | tuple.rcond.Wait() 167 | } 168 | 169 | // After the loop, we'll be able to do a case analysis here: 170 | // 171 | // Case 1: [@tid <= @tuple.tslast] 172 | // The linear view has been fixed up to @tid, so we can safely read it. 173 | // 174 | // Case 2: [@tuple.owned = false] 175 | // Follow-up txns trying to modify this tuple will either fail (if the 176 | // writer's timestamp is smaller than @tid), or succeed (if the writer's 177 | // timestamp is greater than @tid) but with the guarantee that the new 178 | // version is going to be end *after* @tid, so we can safely read the latest 179 | // version at this point. 180 | } 181 | 182 | func (tuple *Tuple) ReadVersion(tid uint64) (string, bool) { 183 | ver := findVersion(tid, tuple.vers) 184 | 185 | if tuple.tslast < tid { 186 | // Record the timestamp of the last reader. 187 | tuple.tslast = tid 188 | } 189 | 190 | tuple.latch.Unlock() 191 | return ver.val, !ver.del 192 | } 193 | 194 | func (tuple *Tuple) removeVersions(tid uint64) { 195 | // Require @tuple.vers is not empty. 196 | var idx uint64 197 | idx = uint64(len(tuple.vers)) - 1 198 | for idx != 0 { 199 | ver := tuple.vers[idx] 200 | if ver.ts < tid { 201 | break 202 | } 203 | idx-- 204 | } 205 | 206 | // Ensure @idx points to the first usable version. 207 | tuple.vers = tuple.vers[idx:] 208 | } 209 | 210 | // Remove all versions whose lifetime ends before @tid. 211 | func (tuple *Tuple) RemoveVersions(tid uint64) { 212 | tuple.latch.Lock() 213 | tuple.removeVersions(tid) 214 | tuple.latch.Unlock() 215 | } 216 | 217 | func MkTuple() *Tuple { 218 | tuple := new(Tuple) 219 | tuple.latch = new(sync.Mutex) 220 | tuple.rcond = sync.NewCond(tuple.latch) 221 | tuple.owned = false 222 | tuple.tslast = 1 223 | tuple.vers = make([]Version, 1, 1) 224 | // Not supported by Goose: 225 | // tuple.vers[0].deleted = true 226 | tuple.vers[0] = Version{ 227 | del: true, 228 | } 229 | return tuple 230 | } 231 | -------------------------------------------------------------------------------- /txnsite/txnsite.go: -------------------------------------------------------------------------------- 1 | package txnsite 2 | 3 | import ( 4 | "github.com/goose-lang/primitive" 5 | "github.com/mit-pdos/vmvcc/cfmutex" 6 | "github.com/mit-pdos/vmvcc/tid" 7 | ) 8 | 9 | type TxnSite struct { 10 | latch *cfmutex.CFMutex 11 | sid uint64 12 | // ID of this transaction site 13 | tids []uint64 14 | // Active transaction IDs. 15 | padding [4]uint64 16 | // TODO: should only need 3, change it once performance is tested. 17 | } 18 | 19 | func MkTxnSite(sid uint64) *TxnSite { 20 | site := new(TxnSite) 21 | site.latch = new(cfmutex.CFMutex) 22 | site.tids = make([]uint64, 0, 8) 23 | site.sid = sid 24 | 25 | return site 26 | } 27 | 28 | // @activate adds @tid to the set of active transaction IDs. 29 | func (site *TxnSite) Activate() uint64 { 30 | site.latch.Lock() 31 | 32 | var t uint64 33 | t = tid.GenTID(site.sid) 34 | // Assume TID never overflow. 35 | primitive.Assume(t < 18446744073709551615) 36 | 37 | site.tids = append(site.tids, t) 38 | 39 | site.latch.Unlock() 40 | return t 41 | } 42 | 43 | func findTID(tid uint64, tids []uint64) uint64 { 44 | // Require @tids contains @tid. 45 | var idx uint64 = 0 46 | for tid != tids[idx] { 47 | idx++ 48 | } 49 | 50 | return idx 51 | } 52 | 53 | func swapWithEnd(xs []uint64, i uint64) { 54 | // Require (1) @xs not empty, (2) @i < len(@xs). 55 | tmp := xs[len(xs)-1] 56 | xs[len(xs)-1] = xs[i] 57 | xs[i] = tmp 58 | } 59 | 60 | // @deactivate removes @tid from the set of active transaction IDs. 61 | func (site *TxnSite) Deactivate(tid uint64) { 62 | // Require @site.tids contains @tid. 63 | site.latch.Lock() 64 | 65 | // Remove @tid from the set of active transactions. 66 | idx := findTID(tid, site.tids) 67 | swapWithEnd(site.tids, idx) 68 | site.tids = site.tids[:len(site.tids)-1] 69 | 70 | site.latch.Unlock() 71 | } 72 | 73 | // @GetSafeTS returns a per-site lower bound on the active/future transaction 74 | // IDs. 75 | func (site *TxnSite) GetSafeTS() uint64 { 76 | site.latch.Lock() 77 | 78 | var tidnew uint64 79 | tidnew = tid.GenTID(site.sid) 80 | primitive.Assume(tidnew < 18446744073709551615) 81 | 82 | var tidmin uint64 = tidnew 83 | for _, tid := range site.tids { 84 | if tid < tidmin { 85 | tidmin = tid 86 | } 87 | } 88 | 89 | site.latch.Unlock() 90 | return tidmin 91 | } 92 | -------------------------------------------------------------------------------- /vmvcc/db.go: -------------------------------------------------------------------------------- 1 | package vmvcc 2 | 3 | import ( 4 | "github.com/goose-lang/primitive" 5 | "github.com/mit-pdos/vmvcc/cfmutex" 6 | "github.com/mit-pdos/vmvcc/config" 7 | "github.com/mit-pdos/vmvcc/index" 8 | "github.com/mit-pdos/vmvcc/tid" 9 | "github.com/mit-pdos/vmvcc/txnsite" 10 | "github.com/mit-pdos/vmvcc/wrbuf" 11 | ) 12 | 13 | type DB struct { 14 | // Mutex protecting @sid. 15 | latch *cfmutex.CFMutex 16 | // Next site ID to assign. 17 | sid uint64 18 | // All transaction sites. 19 | sites []*txnsite.TxnSite 20 | // Index. 21 | idx *index.Index 22 | // Global prophecy variable (for verification purpose). 23 | proph primitive.ProphId 24 | } 25 | 26 | func MkDB() *DB { 27 | proph := primitive.NewProph() 28 | db := &DB{proph: proph} 29 | db.latch = new(cfmutex.CFMutex) 30 | db.sites = make([]*txnsite.TxnSite, config.N_TXN_SITES) 31 | 32 | // Call this once to establish invariants. 33 | tid.GenTID(0) 34 | for i := uint64(0); i < config.N_TXN_SITES; i++ { 35 | site := txnsite.MkTxnSite(i) 36 | db.sites[i] = site 37 | } 38 | db.idx = index.MkIndex() 39 | 40 | return db 41 | } 42 | 43 | func (db *DB) NewTxn() *Txn { 44 | db.latch.Lock() 45 | 46 | txn := &Txn{proph: db.proph} 47 | txn.site = db.sites[db.sid] 48 | txn.wrbuf = wrbuf.MkWrBuf() 49 | txn.idx = db.idx 50 | 51 | db.sid = db.sid + 1 52 | if db.sid >= config.N_TXN_SITES { 53 | db.sid = 0 54 | } 55 | 56 | db.latch.Unlock() 57 | return txn 58 | } 59 | 60 | // @GetSafeTS returns a lower bound on the active/future transaction IDs. 61 | func (db *DB) getSafeTS() uint64 { 62 | var min uint64 = config.TID_SENTINEL 63 | // TODO: A more elegant way is to use range 64 | for sid := uint64(0); sid < config.N_TXN_SITES; sid++ { 65 | site := db.sites[sid] 66 | tid := site.GetSafeTS() 67 | if tid < min { 68 | min = tid 69 | } 70 | } 71 | 72 | return min 73 | } 74 | 75 | func (db *DB) gc() { 76 | tidMin := db.getSafeTS() 77 | if tidMin < config.TID_SENTINEL { 78 | db.idx.DoGC(tidMin) 79 | } 80 | } 81 | 82 | func (db *DB) ActivateGC() { 83 | go func() { 84 | for { 85 | db.gc() 86 | primitive.Sleep(1 * 1000000) 87 | } 88 | }() 89 | } 90 | 91 | func (db *DB) Run(body func(txn *Txn) bool) bool { 92 | txn := db.NewTxn() 93 | return txn.Run(body) 94 | } 95 | -------------------------------------------------------------------------------- /vmvcc/txn.go: -------------------------------------------------------------------------------- 1 | package vmvcc 2 | 3 | import ( 4 | "github.com/goose-lang/primitive" 5 | "github.com/mit-pdos/vmvcc/index" 6 | "github.com/mit-pdos/vmvcc/trusted_proph" 7 | "github.com/mit-pdos/vmvcc/txnsite" 8 | "github.com/mit-pdos/vmvcc/wrbuf" 9 | ) 10 | 11 | type Txn struct { 12 | // Transaction ID. 13 | tid uint64 14 | // Transaction site this transaction uses. 15 | site *txnsite.TxnSite 16 | // Write buffer. 17 | wrbuf *wrbuf.WrBuf 18 | // Pointer to the index. 19 | idx *index.Index 20 | // Global prophecy variable (for verification purpose). 21 | proph primitive.ProphId 22 | } 23 | 24 | func (txn *Txn) Write(key uint64, val string) { 25 | wrbuf := txn.wrbuf 26 | wrbuf.Put(key, val) 27 | } 28 | 29 | func (txn *Txn) Delete(key uint64) bool { 30 | wrbuf := txn.wrbuf 31 | wrbuf.Delete(key) 32 | 33 | // To support SQL in the future, @Delete should return false when not found. 34 | return true 35 | } 36 | 37 | func (txn *Txn) Read(key uint64) (string, bool) { 38 | // First try to find @key in the local write set. 39 | wrbuf := txn.wrbuf 40 | valb, wr, found := wrbuf.Lookup(key) 41 | if found { 42 | return valb, wr 43 | } 44 | 45 | idx := txn.idx 46 | tuple := idx.GetTuple(key) 47 | tuple.ReadWait(txn.tid) 48 | trusted_proph.ResolveRead(txn.proph, txn.tid, key) 49 | val, found := tuple.ReadVersion(txn.tid) 50 | 51 | return val, found 52 | } 53 | 54 | func (txn *Txn) begin() { 55 | tid := txn.site.Activate() 56 | txn.tid = tid 57 | txn.wrbuf.Clear() 58 | } 59 | 60 | func (txn *Txn) acquire() bool { 61 | ok := txn.wrbuf.OpenTuples(txn.tid, txn.idx) 62 | return ok 63 | } 64 | 65 | func (txn *Txn) commit() { 66 | trusted_proph.ResolveCommit(txn.proph, txn.tid, txn.wrbuf) 67 | txn.wrbuf.UpdateTuples(txn.tid) 68 | txn.site.Deactivate(txn.tid) 69 | } 70 | 71 | func (txn *Txn) abort() { 72 | trusted_proph.ResolveAbort(txn.proph, txn.tid) 73 | txn.site.Deactivate(txn.tid) 74 | } 75 | 76 | func (txn *Txn) Run(body func(txn *Txn) bool) bool { 77 | txn.begin() 78 | cmt := body(txn) 79 | if !cmt { 80 | txn.abort() 81 | return false 82 | } 83 | ok := txn.acquire() 84 | if !ok { 85 | txn.abort() 86 | return false 87 | } 88 | txn.commit() 89 | return true 90 | } 91 | -------------------------------------------------------------------------------- /wrbuf/wrbuf.go: -------------------------------------------------------------------------------- 1 | package wrbuf 2 | 3 | import ( 4 | "github.com/mit-pdos/vmvcc/common" 5 | "github.com/mit-pdos/vmvcc/index" 6 | "github.com/mit-pdos/vmvcc/tuple" 7 | ) 8 | 9 | // @key and @val 10 | // Key-value pair of the write entry. 11 | // 12 | // @wr 13 | // Write @key with @val, or delete @key. 14 | // 15 | // @tpl 16 | // Tuple pointer. Exists to save one index-lookup per write entry--written by 17 | // @OpenTuples and read by @UpdateTuples. 18 | type WrEnt struct { 19 | key uint64 20 | val string 21 | wr bool 22 | tpl *tuple.Tuple 23 | } 24 | 25 | // Linear search can be quite slow when there are many entries. 26 | func search(ents []WrEnt, key uint64) (uint64, bool) { 27 | var pos uint64 = 0 28 | for pos < uint64(len(ents)) && key != ents[pos].key { 29 | pos++ 30 | } 31 | 32 | found := pos < uint64(len(ents)) 33 | return pos, found 34 | } 35 | 36 | func swap(ents []WrEnt, i, j uint64) { 37 | tmp := ents[i] 38 | ents[i] = ents[j] 39 | ents[j] = tmp 40 | } 41 | 42 | type WrBuf struct { 43 | ents []WrEnt 44 | } 45 | 46 | func MkWrBuf() *WrBuf { 47 | wrbuf := new(WrBuf) 48 | wrbuf.ents = make([]WrEnt, 0, 16) 49 | return wrbuf 50 | } 51 | 52 | func (wrbuf *WrBuf) sortEntsByKey() { 53 | ents := wrbuf.ents 54 | var i uint64 = 1 55 | for i < uint64(len(ents)) { 56 | var j uint64 = i 57 | for j > 0 { 58 | if ents[j-1].key <= ents[j].key { 59 | break 60 | } 61 | swap(ents, j-1, j) 62 | j-- 63 | } 64 | i++ 65 | } 66 | } 67 | 68 | func (wrbuf *WrBuf) Lookup(key uint64) (string, bool, bool) { 69 | pos, found := search(wrbuf.ents, key) 70 | if found { 71 | ent := wrbuf.ents[pos] 72 | return ent.val, ent.wr, true 73 | } 74 | 75 | return "", false, false 76 | } 77 | 78 | func (wrbuf *WrBuf) Put(key uint64, val string) { 79 | pos, found := search(wrbuf.ents, key) 80 | if found { 81 | ent := &wrbuf.ents[pos] 82 | ent.val = val 83 | ent.wr = true 84 | return 85 | } 86 | 87 | ent := WrEnt{ 88 | key: key, 89 | val: val, 90 | wr: true, 91 | } 92 | wrbuf.ents = append(wrbuf.ents, ent) 93 | } 94 | 95 | func (wrbuf *WrBuf) Delete(key uint64) { 96 | pos, found := search(wrbuf.ents, key) 97 | if found { 98 | ent := &wrbuf.ents[pos] 99 | ent.wr = false 100 | return 101 | } 102 | 103 | ent := WrEnt{ 104 | key: key, 105 | wr: false, 106 | } 107 | wrbuf.ents = append(wrbuf.ents, ent) 108 | } 109 | 110 | // @OpenTuples acquires the write locks of tuples to be updated by calling 111 | // @tuple.Own. 112 | // 113 | // This design prevents deadlocks that show up in the design where transactions 114 | // acquire write locks on writes. For instance, consider the following 115 | // scenarios: 116 | // 117 | // Txn A | W(x) R(y) 118 | // Txn B | W(y) R(x) 119 | // 120 | // This causes a deadlock because A's R(y) waits on B's W(y), and B's R(x) waits 121 | // on A's W(x). Acquring write locks at commit time breaks the wait-cycle since 122 | // if one transaction waits on another, this means the latter must have entered 123 | // the commit phase, and therefore never waits on anything. 124 | func (wrbuf *WrBuf) OpenTuples(tid uint64, idx *index.Index) bool { 125 | // Mistakenly think we need to sort the entries by keys to prevent 126 | // deadlocks, but keeping it here since empirically it slightly improves 127 | // performance for some reasons. 128 | wrbuf.sortEntsByKey() 129 | 130 | // Start acquiring locks for each key. 131 | ents := wrbuf.ents 132 | var pos uint64 = 0 133 | for pos < uint64(len(ents)) { 134 | ent := ents[pos] 135 | tpl := idx.GetTuple(ent.key) 136 | ret := tpl.Own(tid) 137 | if ret != common.RET_SUCCESS { 138 | // TODO: can retry a few times for RET_RETRY. 139 | break 140 | } 141 | // A more efficient way is updating field @tpl, but not 142 | // supported by Goose. 143 | ents[pos] = WrEnt{ 144 | key: ent.key, 145 | val: ent.val, 146 | wr: ent.wr, 147 | tpl: tpl, 148 | } 149 | pos++ 150 | } 151 | 152 | // Release partially acquired locks. 153 | if pos < uint64(len(ents)) { 154 | var i uint64 = 0 155 | for i < pos { 156 | tpl := ents[i].tpl 157 | tpl.Free() 158 | i++ 159 | } 160 | return false 161 | } 162 | 163 | for _, ent := range ents { 164 | ent.tpl.WriteOpen() 165 | } 166 | return true 167 | } 168 | 169 | func (wrbuf *WrBuf) UpdateTuples(tid uint64) { 170 | ents := wrbuf.ents 171 | for _, ent := range ents { 172 | tpl := ent.tpl 173 | if ent.wr { 174 | tpl.AppendVersion(tid, ent.val) 175 | } else { 176 | tpl.KillVersion(tid) 177 | } 178 | } 179 | } 180 | 181 | func (wrbuf *WrBuf) Clear() { 182 | wrbuf.ents = wrbuf.ents[:0] 183 | } 184 | --------------------------------------------------------------------------------