rubydb 0.1.4 → 0.1.5

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.
Files changed (420) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +15 -15
  3. data/.github/workflows/benchmark.yml +26 -26
  4. data/.github/workflows/compatibility.yml +63 -63
  5. data/.github/workflows/fuzz.yml +33 -33
  6. data/.github/workflows/lint.yml +21 -21
  7. data/.github/workflows/operations.yml +24 -24
  8. data/.github/workflows/production-validation.yml +111 -111
  9. data/.github/workflows/release.yml +77 -77
  10. data/.github/workflows/security.yml +39 -37
  11. data/.github/workflows/test.yml +26 -26
  12. data/.github/workflows/workload.yml +58 -58
  13. data/.gitignore +12 -5
  14. data/.rubocop.yml +50 -44
  15. data/.standard.yml +9 -14
  16. data/ARCHITECTURE.md +21 -21
  17. data/CHANGELOG.md +43 -27
  18. data/CODE_OF_CONDUCT.md +13 -13
  19. data/CONTRIBUTING.md +29 -29
  20. data/GOVERNANCE.md +16 -16
  21. data/Gemfile +18 -17
  22. data/Gemfile.lock +125 -71
  23. data/README.md +249 -162
  24. data/ROADMAP.md +27 -27
  25. data/Rakefile +71 -71
  26. data/SECURITY.md +54 -54
  27. data/SUPPORT.md +14 -14
  28. data/adapters/activerecord/Gemfile +11 -11
  29. data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +873 -879
  30. data/adapters/activerecord/rubydb-activerecord.gemspec +20 -20
  31. data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +143 -143
  32. data/adapters/ruby/README.md +18 -18
  33. data/adapters/sequel/README.md +11 -11
  34. data/config/monitoring/prometheus-alerts.yml +39 -39
  35. data/config/production.yml +36 -36
  36. data/docs/README.md +75 -71
  37. data/docs/architecture/concurrency.md +14 -14
  38. data/docs/architecture/current-state.md +125 -125
  39. data/docs/architecture/execution-engine.md +25 -25
  40. data/docs/architecture/indexes.md +19 -19
  41. data/docs/architecture/mvcc.md +19 -19
  42. data/docs/architecture/overview.md +13 -13
  43. data/docs/architecture/pages.md +11 -11
  44. data/docs/architecture/production-roadmap.md +82 -82
  45. data/docs/architecture/query-planner.md +20 -20
  46. data/docs/architecture/recovery.md +18 -18
  47. data/docs/architecture/sql-engine.md +12 -12
  48. data/docs/architecture/storage-engine.md +14 -14
  49. data/docs/architecture/transactions.md +10 -10
  50. data/docs/architecture/wal.md +28 -28
  51. data/docs/cli-cheatsheet.md +98 -98
  52. data/docs/cli.md +275 -275
  53. data/docs/contributing/architecture.md +9 -9
  54. data/docs/contributing/benchmarking.md +14 -14
  55. data/docs/contributing/development.md +16 -16
  56. data/docs/contributing/release-process.md +49 -49
  57. data/docs/contributing/testing.md +16 -16
  58. data/docs/debugging.md +229 -229
  59. data/docs/developer/branching.md +10 -10
  60. data/docs/developer/database-diff.md +10 -10
  61. data/docs/developer/local-development.md +17 -17
  62. data/docs/developer/snapshots.md +9 -9
  63. data/docs/developer/temporal-data.md +10 -10
  64. data/docs/developer-guide.md +297 -297
  65. data/docs/getting-started/first-database.md +16 -16
  66. data/docs/getting-started/first-query.md +13 -13
  67. data/docs/getting-started/installation.md +19 -19
  68. data/docs/getting-started/local-to-production.md +300 -300
  69. data/docs/getting-started/quickstart.md +17 -17
  70. data/docs/getting-started/rails.md +16 -16
  71. data/docs/hardening_backlog.md +93 -93
  72. data/docs/lessons-learned.md +112 -112
  73. data/docs/operations/backups.md +33 -33
  74. data/docs/operations/disaster-recovery.md +31 -31
  75. data/docs/operations/failover.md +30 -30
  76. data/docs/operations/monitoring.md +25 -25
  77. data/docs/operations/production-guide.md +295 -295
  78. data/docs/operations/production-runbook.md +45 -45
  79. data/docs/operations/replication.md +33 -33
  80. data/docs/operations/restore.md +6 -6
  81. data/docs/operations/runbook.md +34 -34
  82. data/docs/operations/upgrades.md +14 -14
  83. data/docs/operations/workload-testing.md +17 -17
  84. data/docs/production-readiness.md +118 -118
  85. data/docs/production_validation.md +150 -150
  86. data/docs/rails/active-record.md +11 -11
  87. data/docs/rails/compatibility-guide.md +90 -90
  88. data/docs/rails/database-yml.md +92 -92
  89. data/docs/rails/installation.md +17 -17
  90. data/docs/rails/migrations.md +17 -17
  91. data/docs/rails/production.md +82 -82
  92. data/docs/rails/troubleshooting.md +18 -18
  93. data/docs/release.md +25 -25
  94. data/docs/server/architecture.md +10 -10
  95. data/docs/server/authentication.md +10 -10
  96. data/docs/server/configuration.md +16 -16
  97. data/docs/server/connection-pooling.md +10 -10
  98. data/docs/server/deployment.md +10 -10
  99. data/docs/server/protocol.md +12 -12
  100. data/docs/sql/compatibility-guide.md +82 -82
  101. data/docs/sql/compatibility.md +39 -39
  102. data/docs/sql/data-types.md +10 -10
  103. data/docs/sql/functions.md +9 -9
  104. data/docs/sql/joins.md +9 -9
  105. data/docs/sql/operators.md +9 -9
  106. data/docs/sql/sqlite-compatibility.md +21 -21
  107. data/docs/sql/syntax.md +10 -10
  108. data/docs/sql/transactions.md +10 -10
  109. data/docs/troubleshooting.md +244 -244
  110. data/lessons/01-foundations.md +73 -0
  111. data/lessons/02-local-development.md +121 -0
  112. data/lessons/03-embedded-rubydb.md +99 -0
  113. data/lessons/04-rails-complex-apps.md +138 -0
  114. data/lessons/05-rubydb-production-server.md +237 -0
  115. data/lessons/06-postgresql-massive-apps.md +96 -0
  116. data/lessons/07-hybrid-microservices.md +94 -0
  117. data/lessons/08-migrations-backups-recovery.md +86 -0
  118. data/lessons/09-observability-security-scale.md +87 -0
  119. data/lessons/10-release-readiness.md +125 -0
  120. data/lib/rubydb/backup/archive.rb +332 -334
  121. data/lib/rubydb/backup/backup.rb +400 -401
  122. data/lib/rubydb/backup/incremental.rb +349 -353
  123. data/lib/rubydb/backup/restore.rb +289 -290
  124. data/lib/rubydb/backup/snapshot.rb +265 -267
  125. data/lib/rubydb/backup/verification.rb +276 -279
  126. data/lib/rubydb/branching/branch.rb +181 -181
  127. data/lib/rubydb/branching/branch_manager.rb +307 -311
  128. data/lib/rubydb/branching/branch_metadata.rb +140 -140
  129. data/lib/rubydb/branching/checkout.rb +165 -166
  130. data/lib/rubydb/branching/copy_on_write.rb +272 -272
  131. data/lib/rubydb/branching/diff.rb +137 -138
  132. data/lib/rubydb/branching/merge.rb +282 -285
  133. data/lib/rubydb/build_info.rb +15 -15
  134. data/lib/rubydb/catalog/catalog.rb +391 -391
  135. data/lib/rubydb/catalog/column.rb +112 -112
  136. data/lib/rubydb/catalog/constraint.rb +180 -180
  137. data/lib/rubydb/catalog/database.rb +184 -184
  138. data/lib/rubydb/catalog/index.rb +97 -97
  139. data/lib/rubydb/catalog/schema.rb +103 -103
  140. data/lib/rubydb/catalog/sequence.rb +90 -90
  141. data/lib/rubydb/catalog/system_catalog.rb +698 -698
  142. data/lib/rubydb/catalog/table.rb +178 -178
  143. data/lib/rubydb/catalog/trigger.rb +102 -102
  144. data/lib/rubydb/catalog/view.rb +66 -66
  145. data/lib/rubydb/cli/application.rb +163 -163
  146. data/lib/rubydb/cli/commands/backup.rb +80 -81
  147. data/lib/rubydb/cli/commands/branch.rb +72 -72
  148. data/lib/rubydb/cli/commands/checkout.rb +54 -54
  149. data/lib/rubydb/cli/commands/create.rb +58 -58
  150. data/lib/rubydb/cli/commands/diff.rb +76 -77
  151. data/lib/rubydb/cli/commands/doctor.rb +74 -74
  152. data/lib/rubydb/cli/commands/drop.rb +57 -57
  153. data/lib/rubydb/cli/commands/init.rb +101 -102
  154. data/lib/rubydb/cli/commands/inspect.rb +95 -95
  155. data/lib/rubydb/cli/commands/merge.rb +63 -63
  156. data/lib/rubydb/cli/commands/migrate.rb +62 -62
  157. data/lib/rubydb/cli/commands/restart.rb +42 -39
  158. data/lib/rubydb/cli/commands/restore.rb +121 -121
  159. data/lib/rubydb/cli/commands/shell.rb +365 -365
  160. data/lib/rubydb/cli/commands/snapshot.rb +79 -79
  161. data/lib/rubydb/cli/commands/start.rb +88 -82
  162. data/lib/rubydb/cli/commands/status.rb +96 -92
  163. data/lib/rubydb/cli/commands/stop.rb +47 -47
  164. data/lib/rubydb/cli/commands/vacuum.rb +58 -58
  165. data/lib/rubydb/cli/formatter.rb +221 -221
  166. data/lib/rubydb/cli/output.rb +168 -168
  167. data/lib/rubydb/client/client.rb +302 -304
  168. data/lib/rubydb/client/connection.rb +414 -415
  169. data/lib/rubydb/client/connection_pool.rb +168 -168
  170. data/lib/rubydb/client/connection_url.rb +96 -96
  171. data/lib/rubydb/client/prepared_statement.rb +60 -60
  172. data/lib/rubydb/client/result.rb +123 -123
  173. data/lib/rubydb/client/statement.rb +52 -52
  174. data/lib/rubydb/client/transaction.rb +130 -130
  175. data/lib/rubydb/concurrency/concurrency.rb +19 -19
  176. data/lib/rubydb/concurrency/deadlock_detector.rb +148 -150
  177. data/lib/rubydb/concurrency/latch.rb +101 -101
  178. data/lib/rubydb/concurrency/lock_graph.rb +163 -165
  179. data/lib/rubydb/concurrency/mutex.rb +181 -183
  180. data/lib/rubydb/concurrency/rw_lock.rb +180 -180
  181. data/lib/rubydb/concurrency/scheduler.rb +248 -250
  182. data/lib/rubydb/concurrency/worker_pool.rb +145 -143
  183. data/lib/rubydb/configuration/config.rb +170 -170
  184. data/lib/rubydb/configuration/defaults.rb +179 -179
  185. data/lib/rubydb/configuration/environment.rb +152 -152
  186. data/lib/rubydb/configuration/parser.rb +185 -185
  187. data/lib/rubydb/configuration/validation.rb +221 -221
  188. data/lib/rubydb/constants.rb +74 -74
  189. data/lib/rubydb/constraints/check.rb +181 -181
  190. data/lib/rubydb/constraints/constraint.rb +101 -101
  191. data/lib/rubydb/constraints/foreign_key.rb +130 -130
  192. data/lib/rubydb/constraints/not_null.rb +64 -64
  193. data/lib/rubydb/constraints/primary_key.rb +99 -99
  194. data/lib/rubydb/constraints/unique.rb +106 -108
  195. data/lib/rubydb/constraints/validator.rb +349 -350
  196. data/lib/rubydb/errors/authentication_error.rb +10 -10
  197. data/lib/rubydb/errors/authorization_error.rb +23 -23
  198. data/lib/rubydb/errors/client_error.rb +10 -10
  199. data/lib/rubydb/errors/configuration_error.rb +10 -10
  200. data/lib/rubydb/errors/connection_error.rb +10 -10
  201. data/lib/rubydb/errors/constraint_error.rb +23 -23
  202. data/lib/rubydb/errors/corruption_error.rb +10 -10
  203. data/lib/rubydb/errors/database_error.rb +10 -10
  204. data/lib/rubydb/errors/error.rb +20 -20
  205. data/lib/rubydb/errors/execution_error.rb +10 -10
  206. data/lib/rubydb/errors/parser_error.rb +10 -10
  207. data/lib/rubydb/errors/recovery_error.rb +10 -10
  208. data/lib/rubydb/errors/replication_error.rb +10 -10
  209. data/lib/rubydb/errors/server_error.rb +6 -6
  210. data/lib/rubydb/errors/storage_error.rb +10 -10
  211. data/lib/rubydb/errors/transaction_error.rb +10 -10
  212. data/lib/rubydb/execution/aggregate_executor.rb +134 -138
  213. data/lib/rubydb/execution/delete_executor.rb +110 -112
  214. data/lib/rubydb/execution/distinct_executor.rb +131 -135
  215. data/lib/rubydb/execution/executor.rb +1182 -1188
  216. data/lib/rubydb/execution/expression.rb +191 -193
  217. data/lib/rubydb/execution/index_scan.rb +142 -142
  218. data/lib/rubydb/execution/insert_executor.rb +215 -217
  219. data/lib/rubydb/execution/join_executor.rb +243 -249
  220. data/lib/rubydb/execution/limit_executor.rb +83 -85
  221. data/lib/rubydb/execution/optimizer.rb +227 -215
  222. data/lib/rubydb/execution/plan.rb +355 -353
  223. data/lib/rubydb/execution/planner.rb +542 -536
  224. data/lib/rubydb/execution/predicate.rb +235 -235
  225. data/lib/rubydb/execution/scan.rb +49 -49
  226. data/lib/rubydb/execution/sequential_scan.rb +63 -63
  227. data/lib/rubydb/execution/sort_executor.rb +180 -185
  228. data/lib/rubydb/execution/update_executor.rb +160 -162
  229. data/lib/rubydb/functions/aggregate.rb +70 -70
  230. data/lib/rubydb/functions/date_functions.rb +274 -278
  231. data/lib/rubydb/functions/function.rb +85 -85
  232. data/lib/rubydb/functions/json_functions.rb +231 -215
  233. data/lib/rubydb/functions/numeric_functions.rb +346 -346
  234. data/lib/rubydb/functions/scalar.rb +52 -52
  235. data/lib/rubydb/functions/string_functions.rb +383 -383
  236. data/lib/rubydb/functions/system_functions.rb +258 -246
  237. data/lib/rubydb/history/as_of.rb +238 -238
  238. data/lib/rubydb/history/change.rb +105 -105
  239. data/lib/rubydb/history/history.rb +131 -131
  240. data/lib/rubydb/history/history_manager.rb +228 -229
  241. data/lib/rubydb/history/temporal_query.rb +202 -202
  242. data/lib/rubydb/history/timeline.rb +144 -144
  243. data/lib/rubydb/indexes/btree.rb +186 -186
  244. data/lib/rubydb/indexes/btree_cursor.rb +258 -258
  245. data/lib/rubydb/indexes/btree_node.rb +384 -385
  246. data/lib/rubydb/indexes/hash_index.rb +150 -150
  247. data/lib/rubydb/indexes/index.rb +71 -71
  248. data/lib/rubydb/indexes/index_manager.rb +408 -406
  249. data/lib/rubydb/indexes/index_scan.rb +466 -470
  250. data/lib/rubydb/migrations/migration.rb +253 -254
  251. data/lib/rubydb/migrations/migration_lock.rb +146 -146
  252. data/lib/rubydb/migrations/migration_manager.rb +187 -176
  253. data/lib/rubydb/migrations/migration_version.rb +71 -71
  254. data/lib/rubydb/migrations/schema_diff.rb +211 -211
  255. data/lib/rubydb/migrations/schema_version.rb +64 -64
  256. data/lib/rubydb/monitoring/events.rb +155 -160
  257. data/lib/rubydb/monitoring/health.rb +216 -222
  258. data/lib/rubydb/monitoring/logger.rb +188 -193
  259. data/lib/rubydb/monitoring/metrics.rb +363 -359
  260. data/lib/rubydb/monitoring/performance.rb +176 -176
  261. data/lib/rubydb/monitoring/statistics.rb +168 -170
  262. data/lib/rubydb/mvcc/garbage_collector.rb +199 -199
  263. data/lib/rubydb/mvcc/mvcc.rb +16 -16
  264. data/lib/rubydb/mvcc/snapshot.rb +146 -147
  265. data/lib/rubydb/mvcc/vacuum.rb +180 -180
  266. data/lib/rubydb/mvcc/version.rb +106 -106
  267. data/lib/rubydb/mvcc/version_store.rb +396 -398
  268. data/lib/rubydb/mvcc/visibility.rb +107 -109
  269. data/lib/rubydb/protocol/capabilities.rb +125 -125
  270. data/lib/rubydb/protocol/decoder.rb +142 -145
  271. data/lib/rubydb/protocol/encoder.rb +131 -136
  272. data/lib/rubydb/protocol/handshake.rb +306 -305
  273. data/lib/rubydb/protocol/message.rb +121 -121
  274. data/lib/rubydb/protocol/parameter_binder.rb +101 -0
  275. data/lib/rubydb/protocol/protocol.rb +276 -277
  276. data/lib/rubydb/protocol/version.rb +54 -54
  277. data/lib/rubydb/rails/adapter.rb +245 -239
  278. data/lib/rubydb/rails/connection.rb +312 -314
  279. data/lib/rubydb/rails/database_statements.rb +122 -122
  280. data/lib/rubydb/rails/migration.rb +131 -131
  281. data/lib/rubydb/rails/quoting.rb +109 -109
  282. data/lib/rubydb/rails/result.rb +117 -117
  283. data/lib/rubydb/rails/schema_statements.rb +339 -339
  284. data/lib/rubydb/rails/transaction.rb +105 -105
  285. data/lib/rubydb/rails/type.rb +126 -126
  286. data/lib/rubydb/recovery/checkpoint.rb +261 -257
  287. data/lib/rubydb/recovery/consistency.rb +457 -467
  288. data/lib/rubydb/recovery/corruption_detector.rb +5 -5
  289. data/lib/rubydb/recovery/crash_recovery.rb +381 -387
  290. data/lib/rubydb/recovery/recovery_manager.rb +204 -206
  291. data/lib/rubydb/recovery/redo.rb +235 -237
  292. data/lib/rubydb/recovery/undo.rb +204 -206
  293. data/lib/rubydb/replication/failover.rb +5 -5
  294. data/lib/rubydb/replication/fencing.rb +63 -63
  295. data/lib/rubydb/replication/primary.rb +461 -450
  296. data/lib/rubydb/replication/replica.rb +382 -384
  297. data/lib/rubydb/replication/replication_log.rb +194 -200
  298. data/lib/rubydb/replication/replication_manager.rb +307 -308
  299. data/lib/rubydb/replication/replication_slot.rb +293 -295
  300. data/lib/rubydb/replication/replication_stream.rb +198 -201
  301. data/lib/rubydb/rubydb.rb +564 -560
  302. data/lib/rubydb/security/access_control.rb +252 -254
  303. data/lib/rubydb/security/audit_log.rb +209 -213
  304. data/lib/rubydb/security/authentication.rb +302 -302
  305. data/lib/rubydb/security/authorization.rb +282 -282
  306. data/lib/rubydb/security/credentials.rb +192 -196
  307. data/lib/rubydb/security/password.rb +205 -215
  308. data/lib/rubydb/security/permissions.rb +74 -74
  309. data/lib/rubydb/security/role.rb +99 -101
  310. data/lib/rubydb/security/user.rb +86 -86
  311. data/lib/rubydb/server/connection.rb +383 -366
  312. data/lib/rubydb/server/connection_pool.rb +193 -193
  313. data/lib/rubydb/server/lifecycle.rb +227 -228
  314. data/lib/rubydb/server/listener.rb +139 -136
  315. data/lib/rubydb/server/request_handler.rb +277 -276
  316. data/lib/rubydb/server/server.rb +363 -364
  317. data/lib/rubydb/server/session.rb +371 -369
  318. data/lib/rubydb/server/worker.rb +206 -210
  319. data/lib/rubydb/server/worker_pool.rb +168 -168
  320. data/lib/rubydb/sql/ast/alter_table.rb +169 -169
  321. data/lib/rubydb/sql/ast/begin_transaction.rb +47 -47
  322. data/lib/rubydb/sql/ast/commit.rb +37 -37
  323. data/lib/rubydb/sql/ast/constraint.rb +92 -83
  324. data/lib/rubydb/sql/ast/create_database.rb +41 -41
  325. data/lib/rubydb/sql/ast/create_index.rb +61 -61
  326. data/lib/rubydb/sql/ast/create_schema.rb +52 -52
  327. data/lib/rubydb/sql/ast/create_table.rb +187 -187
  328. data/lib/rubydb/sql/ast/delete.rb +54 -54
  329. data/lib/rubydb/sql/ast/drop_database.rb +41 -41
  330. data/lib/rubydb/sql/ast/drop_index.rb +41 -41
  331. data/lib/rubydb/sql/ast/drop_schema.rb +49 -49
  332. data/lib/rubydb/sql/ast/drop_table.rb +49 -49
  333. data/lib/rubydb/sql/ast/explain.rb +64 -64
  334. data/lib/rubydb/sql/ast/expression.rb +617 -604
  335. data/lib/rubydb/sql/ast/insert.rb +66 -66
  336. data/lib/rubydb/sql/ast/node.rb +42 -42
  337. data/lib/rubydb/sql/ast/rollback.rb +63 -63
  338. data/lib/rubydb/sql/ast/savepoint.rb +59 -59
  339. data/lib/rubydb/sql/ast/select.rb +88 -88
  340. data/lib/rubydb/sql/ast/set_operation.rb +22 -20
  341. data/lib/rubydb/sql/ast/trigger.rb +35 -29
  342. data/lib/rubydb/sql/ast/update.rb +88 -88
  343. data/lib/rubydb/sql/ast/vacuum.rb +19 -19
  344. data/lib/rubydb/sql/ast/view.rb +38 -32
  345. data/lib/rubydb/sql/ast/with.rb +32 -32
  346. data/lib/rubydb/sql/grammar.rb +86 -86
  347. data/lib/rubydb/sql/keywords.rb +156 -156
  348. data/lib/rubydb/sql/lexer.rb +209 -214
  349. data/lib/rubydb/sql/operators.rb +100 -100
  350. data/lib/rubydb/sql/parser.rb +1167 -1170
  351. data/lib/rubydb/sql/planner/analyzer.rb +283 -302
  352. data/lib/rubydb/sql/planner/binder.rb +537 -550
  353. data/lib/rubydb/sql/planner/type_checker.rb +427 -431
  354. data/lib/rubydb/sql/token.rb +210 -210
  355. data/lib/rubydb/storage/buffer_frame.rb +44 -44
  356. data/lib/rubydb/storage/buffer_pool.rb +155 -155
  357. data/lib/rubydb/storage/database_lock.rb +74 -74
  358. data/lib/rubydb/storage/deserializer.rb +332 -342
  359. data/lib/rubydb/storage/engine.rb +2347 -2330
  360. data/lib/rubydb/storage/file_manager.rb +191 -187
  361. data/lib/rubydb/storage/free_space_map.rb +79 -81
  362. data/lib/rubydb/storage/page.rb +92 -94
  363. data/lib/rubydb/storage/page_allocator.rb +852 -855
  364. data/lib/rubydb/storage/page_header.rb +63 -67
  365. data/lib/rubydb/storage/page_manager.rb +127 -131
  366. data/lib/rubydb/storage/record.rb +58 -58
  367. data/lib/rubydb/storage/row.rb +78 -78
  368. data/lib/rubydb/storage/serializer.rb +51 -51
  369. data/lib/rubydb/storage/storage_layout.rb +151 -151
  370. data/lib/rubydb/storage/storage_manager.rb +114 -114
  371. data/lib/rubydb/storage/tuple.rb +458 -461
  372. data/lib/rubydb/storage/visibility_map.rb +964 -973
  373. data/lib/rubydb/transactions/commit_manager.rb +219 -220
  374. data/lib/rubydb/transactions/isolation.rb +98 -98
  375. data/lib/rubydb/transactions/lock.rb +76 -76
  376. data/lib/rubydb/transactions/lock_manager.rb +359 -362
  377. data/lib/rubydb/transactions/savepoint.rb +142 -143
  378. data/lib/rubydb/transactions/transaction.rb +214 -215
  379. data/lib/rubydb/transactions/transaction_id.rb +84 -84
  380. data/lib/rubydb/transactions/transaction_log.rb +256 -257
  381. data/lib/rubydb/transactions/transaction_manager.rb +434 -435
  382. data/lib/rubydb/types/bigint.rb +36 -36
  383. data/lib/rubydb/types/blob.rb +37 -37
  384. data/lib/rubydb/types/boolean.rb +34 -34
  385. data/lib/rubydb/types/date.rb +39 -39
  386. data/lib/rubydb/types/decimal.rb +48 -48
  387. data/lib/rubydb/types/float.rb +34 -34
  388. data/lib/rubydb/types/integer.rb +36 -36
  389. data/lib/rubydb/types/json.rb +41 -41
  390. data/lib/rubydb/types/null.rb +34 -34
  391. data/lib/rubydb/types/smallint.rb +36 -36
  392. data/lib/rubydb/types/text.rb +37 -37
  393. data/lib/rubydb/types/time.rb +46 -46
  394. data/lib/rubydb/types/timestamp.rb +39 -39
  395. data/lib/rubydb/types/type.rb +119 -119
  396. data/lib/rubydb/types/uuid.rb +47 -47
  397. data/lib/rubydb/types/varchar.rb +37 -37
  398. data/lib/rubydb/version.rb +32 -32
  399. data/lib/rubydb/wal/archive.rb +190 -193
  400. data/lib/rubydb/wal/checkpoint.rb +181 -183
  401. data/lib/rubydb/wal/lsn.rb +94 -94
  402. data/lib/rubydb/wal/reader.rb +259 -260
  403. data/lib/rubydb/wal/record.rb +105 -105
  404. data/lib/rubydb/wal/segment.rb +193 -193
  405. data/lib/rubydb/wal/wal.rb +480 -452
  406. data/lib/rubydb/wal/writer.rb +236 -236
  407. data/lib/rubydb.rb +7 -7
  408. data/packaging/docker/docker-compose.failover.yml +43 -43
  409. data/packaging/homebrew/rubydb.rb +19 -19
  410. data/rubydb.gemspec +60 -57
  411. data/scripts/benchmark +7 -7
  412. data/scripts/durability_drill +37 -37
  413. data/scripts/fuzz +63 -63
  414. data/scripts/release +45 -40
  415. data/scripts/release_check +43 -43
  416. data/scripts/replication_failover_drill +268 -250
  417. data/scripts/replication_network_failover_drill +287 -255
  418. data/scripts/restore_drill +45 -45
  419. data/scripts/security +45 -0
  420. metadata +54 -1
@@ -1,295 +1,293 @@
1
- # frozen_string_literal: true
2
-
3
- require "time"
4
- require "set"
5
-
6
- module RubyDB
7
- module Replication
8
- # Durable slot state is represented by the primary while the replication
9
- # catalog is still file-backed. A slot retains the last acknowledged LSN
10
- # and prevents accidental reuse of a replica identity.
11
- class ReplicationSlot
12
- attr_reader :name, :primary
13
- attr_accessor :confirmed_lsn
14
-
15
- def initialize(name, primary)
16
- @name = name.to_s
17
- @primary = primary
18
- @confirmed_lsn = 0
19
- end
20
-
21
- def advance(lsn)
22
- @confirmed_lsn = [@confirmed_lsn, lsn.to_i].max
23
- end
24
- end
25
-
26
- # Failover - Handles automatic failover
27
- class Failover
28
- attr_reader :stats
29
-
30
- # Failover states
31
- STATE_NORMAL = :normal
32
- STATE_HEALTH_CHECK = :health_check
33
- STATE_DETECTED = :detected
34
- STATE_FAILING_OVER = :failing_over
35
- STATE_PROMOTING = :promoting
36
- STATE_VERIFYING = :verifying
37
- STATE_COMPLETE = :complete
38
- STATE_FAILED = :failed
39
-
40
- def initialize(replication_manager, config = {})
41
- @replication_manager = replication_manager
42
- @config = config
43
- @state = STATE_NORMAL
44
- @failover_triggered = false
45
- @failover_start_time = nil
46
- @candidate_replicas = []
47
- @chosen_candidate = nil
48
- @health_check_history = []
49
- @stats = {
50
- failovers: 0,
51
- successful_failovers: 0,
52
- failed_failovers: 0,
53
- total_failover_time_ms: 0,
54
- avg_failover_time_ms: 0,
55
- last_failover_time: nil,
56
- last_failover_result: nil,
57
- health_check_failures: 0,
58
- consecutive_failures: 0
59
- }
60
- @lock = Mutex.new
61
- @running = false
62
- @failover_thread = nil
63
- @failover_timeout = config[:timeout] || 30
64
- @check_interval = config[:check_interval] || 5
65
- @max_consecutive_failures = config[:max_consecutive_failures] || 3
66
- @failover_trigger = config[:trigger] || :auto
67
- end
68
-
69
- def start
70
- @lock.synchronize do
71
- return if @running
72
-
73
- @running = true
74
- @state = STATE_NORMAL
75
- @failover_thread = Thread.new { failover_loop }
76
-
77
- puts "Failover handler started"
78
- true
79
- end
80
- end
81
-
82
- def stop
83
- @lock.synchronize do
84
- return unless @running
85
-
86
- @running = false
87
-
88
- @failover_thread&.kill
89
- @failover_thread = nil
90
-
91
- puts "Failover handler stopped"
92
- true
93
- end
94
- end
95
-
96
- def trigger_failover(reason = "manual trigger")
97
- @lock.synchronize do
98
- return false if @failover_triggered
99
-
100
- @failover_triggered = true
101
- @failover_start_time = Time.now
102
- @stats[:failovers] += 1
103
-
104
- Thread.new do
105
- execute_failover(reason)
106
- end
107
-
108
- true
109
- end
110
- end
111
-
112
- def health_check
113
- trigger_reason = nil
114
- result = @lock.synchronize do
115
- result = @replication_manager.health_check
116
-
117
- @health_check_history << {
118
- timestamp: Time.now,
119
- result: result
120
- }
121
-
122
- if @health_check_history.size > 100
123
- @health_check_history.shift
124
- end
125
-
126
- if result[:healthy] == false
127
- @stats[:health_check_failures] += 1
128
- @stats[:consecutive_failures] += 1
129
-
130
- if @stats[:consecutive_failures] >= @max_consecutive_failures &&
131
- @failover_trigger == :auto &&
132
- !@failover_triggered
133
- trigger_reason = "health check failures"
134
- end
135
- else
136
- @stats[:consecutive_failures] = 0
137
- end
138
-
139
- result
140
- end
141
- trigger_failover(trigger_reason) if trigger_reason
142
- result
143
- end
144
-
145
- def stats
146
- @lock.synchronize do
147
- @stats.merge({
148
- state: @state,
149
- running: @running,
150
- failover_triggered: @failover_triggered,
151
- consecutive_failures: @stats[:consecutive_failures],
152
- health_check_history_size: @health_check_history.size
153
- })
154
- end
155
- end
156
-
157
- private
158
-
159
- def failover_loop
160
- while @running
161
- sleep(@check_interval)
162
- begin
163
- health_check
164
- rescue => e
165
- # Log error but continue
166
- end
167
- end
168
- end
169
-
170
- def execute_failover(reason)
171
- @lock.synchronize do
172
- @state = STATE_DETECTED
173
- start_time = Time.now
174
-
175
- begin
176
- # Step 1: Find candidate replicas
177
- @state = STATE_HEALTH_CHECK
178
- candidates = find_candidate_replicas
179
-
180
- if candidates.empty?
181
- @state = STATE_FAILED
182
- @stats[:failed_failovers] += 1
183
- return failover_result(false, "No candidate replicas found")
184
- end
185
-
186
- # Step 2: Choose best candidate
187
- @state = STATE_PROMOTING
188
- @chosen_candidate = choose_best_candidate(candidates)
189
-
190
- unless @chosen_candidate
191
- @state = STATE_FAILED
192
- @stats[:failed_failovers] += 1
193
- return failover_result(false, "No suitable candidate found")
194
- end
195
-
196
- # Step 3: Promote candidate
197
- @state = STATE_FAILING_OVER
198
- promote_result = promote_candidate(@chosen_candidate)
199
-
200
- unless promote_result[:success]
201
- @state = STATE_FAILED
202
- @stats[:failed_failovers] += 1
203
- return failover_result(false, "Promotion failed: #{promote_result[:error]}")
204
- end
205
-
206
- # Step 4: Verify promotion
207
- @state = STATE_VERIFYING
208
- verify_result = verify_promotion(@chosen_candidate)
209
-
210
- unless verify_result[:success]
211
- @state = STATE_FAILED
212
- @stats[:failed_failovers] += 1
213
- return failover_result(false, "Verification failed: #{verify_result[:error]}")
214
- end
215
-
216
- # Step 5: Complete failover
217
- @state = STATE_COMPLETE
218
- @stats[:successful_failovers] += 1
219
-
220
- elapsed_ms = (Time.now - start_time) * 1000
221
- @stats[:total_failover_time_ms] += elapsed_ms
222
- @stats[:avg_failover_time_ms] = @stats[:total_failover_time_ms] / @stats[:successful_failovers]
223
- @stats[:last_failover_time] = Time.now
224
-
225
- failover_result(true, "Failover completed successfully", {
226
- candidate: @chosen_candidate[:node_id],
227
- elapsed_ms: elapsed_ms,
228
- reason: reason
229
- })
230
-
231
- rescue => e
232
- @state = STATE_FAILED
233
- @stats[:failed_failovers] += 1
234
- failover_result(false, "Failover failed: #{e.message}")
235
- ensure
236
- @failover_triggered = false
237
- @chosen_candidate = nil
238
- end
239
- end
240
- end
241
-
242
- def find_candidate_replicas
243
- candidates = []
244
-
245
- replica = @replication_manager.replica
246
- status = replica&.replication_status
247
- if replica&.running? && status && status[:state] == Replica::STATE_SYNCED
248
- candidates << {
249
- node_id: @replication_manager.replica.config[:node_id] || "replica_1",
250
- host: replica.primary_host,
251
- port: replica.primary_port,
252
- status: status[:state].to_s,
253
- lag_ms: status[:lag_ms].to_i,
254
- replayed_lsn: status[:last_replayed_lsn].to_i
255
- }
256
- end
257
-
258
- candidates
259
- end
260
-
261
- def choose_best_candidate(candidates)
262
- candidates.min_by { |c| [c[:lag_ms] || 0, -(c[:replayed_lsn] || 0)] }
263
- end
264
-
265
- def promote_candidate(candidate)
266
- result = @replication_manager.promote_to_primary
267
- return { success: false, error: "Promotion manager returned no result" } unless result.is_a?(Hash)
268
-
269
- result[:success] ? result : { success: false, error: result[:error] || "Promotion failed" }
270
- end
271
-
272
- def verify_promotion(candidate)
273
- mode = @replication_manager.respond_to?(:mode) ? @replication_manager.mode : nil
274
- return { success: false, error: "Promotion did not enter primary mode" } unless mode == ReplicationManager::MODE_PRIMARY
275
-
276
- health = @replication_manager.health_check
277
- health[:healthy] ? { success: true, health: health } : { success: false, error: "Promoted node is unhealthy" }
278
- end
279
-
280
- def failover_result(success, message, extra = {})
281
- started_at = @failover_start_time || Time.now
282
- result = {
283
- success: success,
284
- message: message,
285
- timestamp: Time.now.iso8601,
286
- elapsed_ms: (Time.now - started_at) * 1000,
287
- state: @state
288
- }.merge(extra)
289
-
290
- @stats[:last_failover_result] = result
291
- result
292
- end
293
- end
294
- end
295
- end
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module RubyDB
6
+ module Replication
7
+ # Durable slot state is represented by the primary while the replication
8
+ # catalog is still file-backed. A slot retains the last acknowledged LSN
9
+ # and prevents accidental reuse of a replica identity.
10
+ class ReplicationSlot
11
+ attr_reader :name, :primary
12
+ attr_accessor :confirmed_lsn
13
+
14
+ def initialize(name, primary)
15
+ @name = name.to_s
16
+ @primary = primary
17
+ @confirmed_lsn = 0
18
+ end
19
+
20
+ def advance(lsn)
21
+ @confirmed_lsn = [@confirmed_lsn, lsn.to_i].max
22
+ end
23
+ end
24
+
25
+ # Failover - Handles automatic failover
26
+ class Failover
27
+ attr_reader :stats
28
+
29
+ # Failover states
30
+ STATE_NORMAL = :normal
31
+ STATE_HEALTH_CHECK = :health_check
32
+ STATE_DETECTED = :detected
33
+ STATE_FAILING_OVER = :failing_over
34
+ STATE_PROMOTING = :promoting
35
+ STATE_VERIFYING = :verifying
36
+ STATE_COMPLETE = :complete
37
+ STATE_FAILED = :failed
38
+
39
+ def initialize(replication_manager, config = {})
40
+ @replication_manager = replication_manager
41
+ @config = config
42
+ @state = STATE_NORMAL
43
+ @failover_triggered = false
44
+ @failover_start_time = nil
45
+ @candidate_replicas = []
46
+ @chosen_candidate = nil
47
+ @health_check_history = []
48
+ @stats = {
49
+ failovers: 0,
50
+ successful_failovers: 0,
51
+ failed_failovers: 0,
52
+ total_failover_time_ms: 0,
53
+ avg_failover_time_ms: 0,
54
+ last_failover_time: nil,
55
+ last_failover_result: nil,
56
+ health_check_failures: 0,
57
+ consecutive_failures: 0
58
+ }
59
+ @lock = Mutex.new
60
+ @running = false
61
+ @failover_thread = nil
62
+ @failover_timeout = config[:timeout] || 30
63
+ @check_interval = config[:check_interval] || 5
64
+ @max_consecutive_failures = config[:max_consecutive_failures] || 3
65
+ @failover_trigger = config[:trigger] || :auto
66
+ end
67
+
68
+ def start
69
+ @lock.synchronize do
70
+ return if @running
71
+
72
+ @running = true
73
+ @state = STATE_NORMAL
74
+ @failover_thread = Thread.new { failover_loop }
75
+
76
+ puts "Failover handler started"
77
+ true
78
+ end
79
+ end
80
+
81
+ def stop
82
+ @lock.synchronize do
83
+ return unless @running
84
+
85
+ @running = false
86
+
87
+ @failover_thread&.kill
88
+ @failover_thread = nil
89
+
90
+ puts "Failover handler stopped"
91
+ true
92
+ end
93
+ end
94
+
95
+ def trigger_failover(reason = "manual trigger")
96
+ @lock.synchronize do
97
+ return false if @failover_triggered
98
+
99
+ @failover_triggered = true
100
+ @failover_start_time = Time.now
101
+ @stats[:failovers] += 1
102
+
103
+ Thread.new do
104
+ execute_failover(reason)
105
+ end
106
+
107
+ true
108
+ end
109
+ end
110
+
111
+ def health_check
112
+ trigger_reason = nil
113
+ result = @lock.synchronize do
114
+ result = @replication_manager.health_check
115
+
116
+ @health_check_history << {
117
+ timestamp: Time.now,
118
+ result: result
119
+ }
120
+
121
+ if @health_check_history.size > 100
122
+ @health_check_history.shift
123
+ end
124
+
125
+ if result[:healthy] == false
126
+ @stats[:health_check_failures] += 1
127
+ @stats[:consecutive_failures] += 1
128
+
129
+ if @stats[:consecutive_failures] >= @max_consecutive_failures &&
130
+ @failover_trigger == :auto &&
131
+ !@failover_triggered
132
+ trigger_reason = "health check failures"
133
+ end
134
+ else
135
+ @stats[:consecutive_failures] = 0
136
+ end
137
+
138
+ result
139
+ end
140
+ trigger_failover(trigger_reason) if trigger_reason
141
+ result
142
+ end
143
+
144
+ def stats
145
+ @lock.synchronize do
146
+ @stats.merge({
147
+ state: @state,
148
+ running: @running,
149
+ failover_triggered: @failover_triggered,
150
+ consecutive_failures: @stats[:consecutive_failures],
151
+ health_check_history_size: @health_check_history.size
152
+ })
153
+ end
154
+ end
155
+
156
+ private
157
+
158
+ def failover_loop
159
+ while @running
160
+ sleep(@check_interval)
161
+ begin
162
+ health_check
163
+ rescue
164
+ # Log error but continue
165
+ end
166
+ end
167
+ end
168
+
169
+ def execute_failover(reason)
170
+ @lock.synchronize do
171
+ @state = STATE_DETECTED
172
+ start_time = Time.now
173
+
174
+ begin
175
+ # Step 1: Find candidate replicas
176
+ @state = STATE_HEALTH_CHECK
177
+ candidates = find_candidate_replicas
178
+
179
+ if candidates.empty?
180
+ @state = STATE_FAILED
181
+ @stats[:failed_failovers] += 1
182
+ return failover_result(false, "No candidate replicas found")
183
+ end
184
+
185
+ # Step 2: Choose best candidate
186
+ @state = STATE_PROMOTING
187
+ @chosen_candidate = choose_best_candidate(candidates)
188
+
189
+ unless @chosen_candidate
190
+ @state = STATE_FAILED
191
+ @stats[:failed_failovers] += 1
192
+ return failover_result(false, "No suitable candidate found")
193
+ end
194
+
195
+ # Step 3: Promote candidate
196
+ @state = STATE_FAILING_OVER
197
+ promote_result = promote_candidate(@chosen_candidate)
198
+
199
+ unless promote_result[:success]
200
+ @state = STATE_FAILED
201
+ @stats[:failed_failovers] += 1
202
+ return failover_result(false, "Promotion failed: #{promote_result[:error]}")
203
+ end
204
+
205
+ # Step 4: Verify promotion
206
+ @state = STATE_VERIFYING
207
+ verify_result = verify_promotion(@chosen_candidate)
208
+
209
+ unless verify_result[:success]
210
+ @state = STATE_FAILED
211
+ @stats[:failed_failovers] += 1
212
+ return failover_result(false, "Verification failed: #{verify_result[:error]}")
213
+ end
214
+
215
+ # Step 5: Complete failover
216
+ @state = STATE_COMPLETE
217
+ @stats[:successful_failovers] += 1
218
+
219
+ elapsed_ms = (Time.now - start_time) * 1000
220
+ @stats[:total_failover_time_ms] += elapsed_ms
221
+ @stats[:avg_failover_time_ms] = @stats[:total_failover_time_ms] / @stats[:successful_failovers]
222
+ @stats[:last_failover_time] = Time.now
223
+
224
+ failover_result(true, "Failover completed successfully", {
225
+ candidate: @chosen_candidate[:node_id],
226
+ elapsed_ms: elapsed_ms,
227
+ reason: reason
228
+ })
229
+ rescue => e
230
+ @state = STATE_FAILED
231
+ @stats[:failed_failovers] += 1
232
+ failover_result(false, "Failover failed: #{e.message}")
233
+ ensure
234
+ @failover_triggered = false
235
+ @chosen_candidate = nil
236
+ end
237
+ end
238
+ end
239
+
240
+ def find_candidate_replicas
241
+ candidates = []
242
+
243
+ replica = @replication_manager.replica
244
+ status = replica&.replication_status
245
+ if replica&.running? && status && status[:state] == Replica::STATE_SYNCED
246
+ candidates << {
247
+ node_id: @replication_manager.replica.config[:node_id] || "replica_1",
248
+ host: replica.primary_host,
249
+ port: replica.primary_port,
250
+ status: status[:state].to_s,
251
+ lag_ms: status[:lag_ms].to_i,
252
+ replayed_lsn: status[:last_replayed_lsn].to_i
253
+ }
254
+ end
255
+
256
+ candidates
257
+ end
258
+
259
+ def choose_best_candidate(candidates)
260
+ candidates.min_by { |c| [c[:lag_ms] || 0, -(c[:replayed_lsn] || 0)] }
261
+ end
262
+
263
+ def promote_candidate(candidate)
264
+ result = @replication_manager.promote_to_primary
265
+ return {success: false, error: "Promotion manager returned no result"} unless result.is_a?(Hash)
266
+
267
+ result[:success] ? result : {success: false, error: result[:error] || "Promotion failed"}
268
+ end
269
+
270
+ def verify_promotion(candidate)
271
+ mode = @replication_manager.respond_to?(:mode) ? @replication_manager.mode : nil
272
+ return {success: false, error: "Promotion did not enter primary mode"} unless mode == ReplicationManager::MODE_PRIMARY
273
+
274
+ health = @replication_manager.health_check
275
+ health[:healthy] ? {success: true, health: health} : {success: false, error: "Promoted node is unhealthy"}
276
+ end
277
+
278
+ def failover_result(success, message, extra = {})
279
+ started_at = @failover_start_time || Time.now
280
+ result = {
281
+ success: success,
282
+ message: message,
283
+ timestamp: Time.now.iso8601,
284
+ elapsed_ms: (Time.now - started_at) * 1000,
285
+ state: @state
286
+ }.merge(extra)
287
+
288
+ @stats[:last_failover_result] = result
289
+ result
290
+ end
291
+ end
292
+ end
293
+ end