tigerbeetle-node 0.14.153 → 0.14.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/benchmark.ts CHANGED
@@ -11,10 +11,7 @@ import {
11
11
  const MAX_TRANSFERS = 51200
12
12
  const MAX_REQUEST_BATCH_SIZE = 5120
13
13
  const IS_TWO_PHASE_TRANSFER = false
14
- const IS_RAW_REQUEST = false
15
- const PREVIOUS_RAW_REQUEST_RESULT = IS_TWO_PHASE_TRANSFER ? 300000 : 620000
16
- const PREVIOUS_RESULT = IS_TWO_PHASE_TRANSFER ? 150000 : 310000
17
- const PREVIOUS_BENCHMARK = IS_RAW_REQUEST ? PREVIOUS_RAW_REQUEST_RESULT : PREVIOUS_RESULT
14
+ const PREVIOUS_BENCHMARK = IS_TWO_PHASE_TRANSFER ? 150000 : 310000
18
15
  const TOLERANCE = 10 // percent that the benchmark is allowed to deviate from the previous benchmark
19
16
 
20
17
  const client = createClient({
@@ -55,163 +52,6 @@ const accountB: Account = {
55
52
  timestamp: 0n,
56
53
  }
57
54
 
58
- // Helper function to promisify the raw_request:
59
- const rawCreateTransfers = async (batch: Buffer): Promise<CreateTransfersError[]> => {
60
- return new Promise((resolve, reject) => {
61
- const callback = (error: undefined | Error, results: CreateTransfersError[]) => {
62
- if (error) {
63
- reject(error)
64
- }
65
- resolve(results)
66
- }
67
-
68
- try {
69
- client.rawRequest(Operation.create_transfers, batch, callback)
70
- } catch (error) {
71
- reject(error)
72
- }
73
- })
74
- }
75
-
76
- /**
77
- * This encoding function is only for this benchmark script.
78
- *
79
- * ID_OFFSET = 0 (0 -> 16)
80
- * DEBIT_ACCOUNT_ID_OFFSET = 0 + 16 = 16 (16 -> 32)
81
- * CREDIT_ACCOUNT_ID_OFFSET = 16 + 16 = 32 (32 -> 48)
82
- * AMOUNT_OFFSET = 48 + 16 = 64 (48 -> 64)
83
- * PENDING_ID_OFFSET = 64 + 16 = 80 (64 -> 80)
84
- * USER_DATA_128_OFFSET = 80 + 16 = 96 (80 -> 96)
85
- * USER_DATA_64_OFFSET = 96 + 8 = 104 (96 -> 104)
86
- * USER_DATA_32_OFFSET = 104 + 4 = 108 (104 -> 108)
87
- * TIMEOUT_OFFSET = 108 + 4 = 112 (108 -> 112)
88
- * LEDGER_OFFSET = 112 + 4 = 116 (112 -> 116)
89
- * CODE_OFFSET = 116 + 2 = 118 (116 -> 118)
90
- * FLAGS_OFFSET = 118 + 2 = 120 (118 -> 120)
91
- * TIMESTAMP = 120 + 8 = 128 (120 -> 128)
92
- */
93
- const encodeTransfer = (transfer: Transfer, offset: number, output: Buffer): void => {
94
- assert(BigInt((offset + TRANSFER_SIZE)) <= BigInt(output.length), `Transfer ${transfer} exceeds buffer of ${output}!`)
95
-
96
- output.writeBigUInt64LE(transfer.id, offset)
97
- output.writeBigUInt64LE(transfer.debit_account_id, offset + 16)
98
- output.writeBigUInt64LE(transfer.credit_account_id, offset + 32)
99
- output.writeBigUInt64LE(transfer.amount, offset + 48)
100
- output.writeBigUInt64LE(transfer.pending_id, offset + 64)
101
- output.writeBigUInt64LE(transfer.user_data_128, offset + 80)
102
- output.writeBigUInt64LE(transfer.user_data_64, offset + 96)
103
- output.writeInt32LE(transfer.user_data_32, offset + 104)
104
- output.writeInt32LE(transfer.timeout, offset + 108)
105
- output.writeUInt32LE(transfer.ledger, offset + 112)
106
- output.writeUInt32LE(transfer.code, offset + 116)
107
- output.writeUInt32LE(transfer.flags, offset + 118)
108
- output.writeBigUInt64LE(transfer.timestamp, offset + 120)
109
- }
110
-
111
- const runBenchmarkRawRequest = async () => {
112
- assert(
113
- MAX_TRANSFERS % MAX_REQUEST_BATCH_SIZE === 0,
114
- "The raw request benchmark requires MAX_TRANSFERS to be a multiple of MAX_REQUEST_BATCH_SIZE"
115
- )
116
- console.log(`pre-allocating ${MAX_TRANSFERS} transfers and posts...`)
117
- const transfers: Buffer[] = []
118
- const posts: Buffer[] = []
119
-
120
- let count = 0
121
- while (count < MAX_TRANSFERS) {
122
- const transferBatch = Buffer.alloc(MAX_REQUEST_BATCH_SIZE * TRANSFER_SIZE, 0)
123
- const postTransferBatch = Buffer.alloc(MAX_REQUEST_BATCH_SIZE * TRANSFER_SIZE, 0)
124
- for (let i = 0; i < MAX_REQUEST_BATCH_SIZE; i++) {
125
- if (count === MAX_TRANSFERS) break
126
-
127
- count += 1
128
- encodeTransfer(
129
- {
130
- id: BigInt(count),
131
- debit_account_id: accountA.id,
132
- credit_account_id: accountB.id,
133
- amount: 1n,
134
- pending_id: 0n,
135
- user_data_128: 0n,
136
- user_data_64: 0n,
137
- user_data_32: 0,
138
- timeout: IS_TWO_PHASE_TRANSFER ? 2 : 0,
139
- ledger: 1,
140
- code: 1,
141
- flags: IS_TWO_PHASE_TRANSFER ? TransferFlags.pending : 0,
142
- timestamp: 0n,
143
- },
144
- i * TRANSFER_SIZE,
145
- transferBatch
146
- )
147
-
148
- if (IS_TWO_PHASE_TRANSFER) {
149
- encodeTransfer(
150
- {
151
- id: BigInt((MAX_TRANSFERS + count)),
152
- debit_account_id: accountA.id,
153
- credit_account_id: accountB.id,
154
- amount: 1n,
155
- pending_id: BigInt(count),
156
- user_data_128: 0n,
157
- user_data_64: 0n,
158
- user_data_32: 0,
159
- timeout: 0,
160
- ledger: 1,
161
- code: 1,
162
- flags: TransferFlags.post_pending_transfer,
163
- timestamp: 0n,
164
- },
165
- i * TRANSFER_SIZE,
166
- postTransferBatch
167
- )
168
- }
169
- }
170
-
171
- transfers.push(transferBatch)
172
- if (IS_TWO_PHASE_TRANSFER) posts.push(postTransferBatch)
173
- if (count % 100) console.log(`${Number((count / MAX_TRANSFERS) * 100).toFixed(1)}%`)
174
- }
175
- assert(count === MAX_TRANSFERS)
176
-
177
- console.log(`starting benchmark. MAX_TRANSFERS=${MAX_TRANSFERS} REQUEST_BATCH_SIZE=${MAX_REQUEST_BATCH_SIZE} NUMBER_OF_BATCHES=${transfers.length}`)
178
- let maxCreateTransfersLatency = 0
179
- let maxCommitTransfersLatency = 0
180
- const start = Date.now()
181
-
182
- for (let i = 0; i < transfers.length; i++) {
183
- const ms1 = Date.now()
184
-
185
- const transferErrors = await rawCreateTransfers(transfers[i])
186
- assert(transferErrors.length === 0)
187
-
188
- const ms2 = Date.now()
189
- const createTransferLatency = ms2 - ms1
190
- if (createTransferLatency > maxCreateTransfersLatency) {
191
- maxCreateTransfersLatency = createTransferLatency
192
- }
193
-
194
- if (IS_TWO_PHASE_TRANSFER) {
195
- const commitErrors = await rawCreateTransfers(posts[i])
196
- assert(commitErrors.length === 0)
197
-
198
- const ms3 = Date.now()
199
- const commitTransferLatency = ms3 - ms2
200
- if (commitTransferLatency > maxCommitTransfersLatency) {
201
- maxCommitTransfersLatency = commitTransferLatency
202
- }
203
- }
204
- }
205
-
206
- const ms = Date.now() - start
207
-
208
- return {
209
- ms,
210
- maxCommitTransfersLatency,
211
- maxCreateTransfersLatency
212
- }
213
- }
214
-
215
55
  const runBenchmark = async () => {
216
56
  console.log(`pre-allocating ${MAX_TRANSFERS} transfers and posts...`)
217
57
  const transfers: Transfer[][] = []
@@ -311,7 +151,7 @@ const main = async () => {
311
151
  assert(accountResults[0].debits_posted === 0n)
312
152
  assert(accountResults[1].debits_posted === 0n)
313
153
 
314
- const benchmark = IS_RAW_REQUEST ? await runBenchmarkRawRequest() : await runBenchmark()
154
+ const benchmark = await runBenchmark()
315
155
 
316
156
  const accounts = await client.lookupAccounts([accountA.id, accountB.id])
317
157
  const result = Math.floor((1000 * MAX_TRANSFERS)/benchmark.ms)
package/src/index.ts CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  Operation,
8
8
  } from './bindings'
9
9
 
10
- function getBinding (): Binding {
10
+ const binding: Binding = (() => {
11
11
  const { arch, platform } = process
12
12
 
13
13
  const archMap = {
@@ -60,209 +60,72 @@ function getBinding (): Binding {
60
60
 
61
61
  const filename = `./bin/${archMap[arch]}-${platformMap[platform]}${extra}/client.node`
62
62
  return require(filename)
63
- }
64
-
65
- const binding = getBinding()
63
+ })()
66
64
 
67
- interface Binding {
68
- init: (args: BindingInitArgs) => Context
69
- request: (context: Context, operation: Operation, batch: Event[], result: ResultCallback) => void
70
- raw_request: (context: Context, operation: Operation, raw_batch: Buffer, result: ResultCallback) => void
71
- tick: (context: Context) => void,
72
- deinit: (context: Context) => void,
73
- tick_ms: number
74
- }
65
+ export type Context = object // tb_client
66
+ export type AccountID = bigint // u128
67
+ export type TransferID = bigint // u128
68
+ export type Event = Account | Transfer | AccountID | TransferID
69
+ export type Result = CreateAccountsError | CreateTransfersError | Account | Transfer
70
+ export type ResultCallback = (error: Error | null, results: Result[] | null) => void
75
71
 
76
72
  interface BindingInitArgs {
77
73
  cluster_id: number, // u32
74
+ concurrency: number, // u32
78
75
  replica_addresses: Buffer,
79
76
  }
80
77
 
81
- export interface InitArgs {
78
+ interface Binding {
79
+ init: (args: BindingInitArgs) => Context
80
+ submit: (context: Context, operation: Operation, batch: Event[], callback: ResultCallback) => void
81
+ deinit: (context: Context) => void,
82
+ }
83
+
84
+ export interface ClientInitArgs {
82
85
  cluster_id: number, // u32
86
+ concurrency_max?: number, // u32
83
87
  replica_addresses: Array<string | number>,
84
88
  }
85
89
 
86
- export type Context = object
87
-
88
- export type AccountID = bigint // u128
89
- export type TransferID = bigint // u128
90
-
91
- export type Event = Account | Transfer | AccountID | TransferID
92
- export type Result = CreateAccountsError | CreateTransfersError | Account | Transfer
93
- // Note: as of #990, the error is always `undefined` here.
94
- export type ResultCallback = (error: undefined | Error, results: Result[]) => void
95
-
96
90
  export interface Client {
97
91
  createAccounts: (batch: Account[]) => Promise<CreateAccountsError[]>
98
92
  createTransfers: (batch: Transfer[]) => Promise<CreateTransfersError[]>
99
93
  lookupAccounts: (batch: AccountID[]) => Promise<Account[]>
100
94
  lookupTransfers: (batch: TransferID[]) => Promise<Transfer[]>
101
- request: (operation: Operation, batch: Event[], callback: ResultCallback) => void
102
- rawRequest: (operation: Operation, rawBatch: Buffer, callback: ResultCallback) => void
103
95
  destroy: () => void
104
96
  }
105
97
 
106
- let _args: InitArgs | undefined = undefined
107
- const isSameArgs = (args: InitArgs): boolean => {
108
- if (typeof _args === 'undefined') {
109
- return false
110
- }
111
-
112
- if (_args.replica_addresses.length !== args.replica_addresses.length) {
113
- return false
114
- }
115
-
116
- let isSameReplicas = true
117
- args.replica_addresses.forEach((entry, index) => {
118
- if (_args?.replica_addresses[index] !== entry) {
119
- isSameReplicas = false
120
- }
121
- })
122
-
123
- return args.cluster_id === _args.cluster_id && isSameReplicas
124
- }
125
-
126
- let _client: Client | undefined = undefined
127
- let _interval: NodeJS.Timeout | undefined = undefined
128
- // Here to wait until `ping` is sent to server so that connection is registered - temporary till client table and sessions are implemented.
129
- let _pinged = false
130
- // TODO: allow creation of clients if the arguments are different. Will require changes in node.zig as well.
131
- export function createClient (args: InitArgs): Client {
132
- const duplicateArgs = isSameArgs(args)
133
- if (!duplicateArgs && typeof _client !== 'undefined'){
134
- throw new Error('Client has already been initialized with different arguments.')
135
- }
136
-
137
- if (duplicateArgs && typeof _client !== 'undefined'){
138
- throw new Error('Client has already been initialized with the same arguments.')
139
- }
140
-
141
- _args = Object.assign({}, { ...args })
98
+ export function createClient (args: ClientInitArgs): Client {
99
+ const concurrency_max_default = 32 // arbitrary
142
100
  const context = binding.init({
143
- ...args,
144
- replica_addresses: Buffer.from(args.replica_addresses.join(','))
101
+ cluster_id: args.cluster_id,
102
+ concurrency: args.concurrency_max || concurrency_max_default,
103
+ replica_addresses: Buffer.from(args.replica_addresses.join(',')),
145
104
  })
146
105
 
147
- const request = (operation: Operation, batch: Event[], callback: ResultCallback) => {
148
- binding.request(context, operation, batch, callback)
149
- }
150
-
151
- const rawRequest = (operation: Operation, rawBatch: Buffer, callback: ResultCallback) => {
152
- binding.raw_request(context, operation, rawBatch, callback)
153
- }
154
-
155
- const createAccounts = async (batch: Account[]): Promise<CreateAccountsError[]> => {
156
- // Here to wait until `ping` is sent to server so that connection is registered - temporary till client table and sessions are implemented.
157
- if (!_pinged) {
158
- await new Promise<void>(resolve => {
159
- setTimeout(() => {
160
- _pinged = true
161
- resolve()
162
- }, 600)
163
- })
164
- }
165
- return new Promise((resolve, reject) => {
166
- const callback = (error: undefined | Error, results: CreateAccountsError[]) => {
167
- if (error) {
168
- reject(error)
169
- return
170
- }
171
- resolve(results)
172
- }
173
-
174
- try {
175
- binding.request(context, Operation.create_accounts, batch, callback)
176
- } catch (error) {
177
- reject(error)
178
- }
179
- })
180
- }
181
-
182
- const createTransfers = async (batch: Transfer[]): Promise<CreateTransfersError[]> => {
183
- // Here to wait until `ping` is sent to server so that connection is registered - temporary till client table and sessions are implemented.
184
- if (!_pinged) {
185
- await new Promise<void>(resolve => {
186
- setTimeout(() => {
187
- _pinged = true
188
- resolve()
189
- }, 600)
190
- })
191
- }
192
- return new Promise((resolve, reject) => {
193
- const callback = (error: undefined | Error, results: CreateTransfersError[]) => {
194
- if (error) {
195
- reject(error)
196
- return
197
- }
198
- resolve(results)
199
- }
200
-
201
- try {
202
- binding.request(context, Operation.create_transfers, batch, callback)
203
- } catch (error) {
204
- reject(error)
205
- }
206
- })
207
- }
208
-
209
- const lookupAccounts = async (batch: AccountID[]): Promise<Account[]> => {
210
- return new Promise((resolve, reject) => {
211
- const callback = (error: undefined | Error, results: Account[]) => {
212
- if (error) {
213
- reject(error)
214
- return
215
- }
216
- resolve(results)
217
- }
218
-
219
- try {
220
- binding.request(context, Operation.lookup_accounts, batch, callback)
221
- } catch (error) {
222
- reject(error)
223
- }
224
- })
225
- }
226
-
227
- const lookupTransfers = async (batch: TransferID[]): Promise<Transfer[]> => {
106
+ const request = <T extends Result>(operation: Operation, batch: Event[]): Promise<T[]> => {
228
107
  return new Promise((resolve, reject) => {
229
- const callback = (error: undefined | Error, results: Transfer[]) => {
230
- if (error) {
231
- reject(error)
232
- return
233
- }
234
- resolve(results)
235
- }
236
-
237
108
  try {
238
- binding.request(context, Operation.lookup_transfers, batch, callback)
239
- } catch (error) {
240
- reject(error)
109
+ binding.submit(context, operation, batch, (error, result) => {
110
+ if (error) {
111
+ reject(error)
112
+ } else if (result) {
113
+ resolve(result as T[])
114
+ } else {
115
+ throw new Error("UB: Binding invoked callback without error or result")
116
+ }
117
+ })
118
+ } catch (err) {
119
+ reject(err)
241
120
  }
242
121
  })
243
122
  }
244
123
 
245
- const destroy = (): void => {
246
- binding.deinit(context)
247
- if (_interval){
248
- clearInterval(_interval)
249
- }
250
- _client = undefined
124
+ return {
125
+ createAccounts(batch) { return request(Operation.create_accounts, batch) },
126
+ createTransfers(batch) { return request(Operation.create_transfers, batch) },
127
+ lookupAccounts(batch) { return request(Operation.lookup_accounts, batch) },
128
+ lookupTransfers(batch) { return request(Operation.lookup_transfers, batch) },
129
+ destroy() { binding.deinit(context) },
251
130
  }
252
-
253
- _client = {
254
- createAccounts,
255
- createTransfers,
256
- lookupAccounts,
257
- lookupTransfers,
258
- request,
259
- rawRequest,
260
- destroy
261
- }
262
-
263
- _interval = setInterval(() => {
264
- binding.tick(context)
265
- }, binding.tick_ms)
266
-
267
- return _client
268
131
  }