rubydb 0.1.4 → 0.1.6
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.
- checksums.yaml +4 -4
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -15
- data/.github/workflows/benchmark.yml +26 -26
- data/.github/workflows/compatibility.yml +63 -63
- data/.github/workflows/fuzz.yml +33 -33
- data/.github/workflows/lint.yml +21 -21
- data/.github/workflows/operations.yml +24 -24
- data/.github/workflows/production-validation.yml +111 -111
- data/.github/workflows/release.yml +77 -77
- data/.github/workflows/security.yml +39 -37
- data/.github/workflows/test.yml +26 -26
- data/.github/workflows/workload.yml +58 -58
- data/.gitignore +16 -5
- data/.rubocop.yml +50 -44
- data/.standard.yml +9 -14
- data/ARCHITECTURE.md +21 -21
- data/CHANGELOG.md +57 -27
- data/CODE_OF_CONDUCT.md +13 -13
- data/CONTRIBUTING.md +29 -29
- data/GOVERNANCE.md +16 -16
- data/Gemfile +18 -17
- data/Gemfile.lock +125 -71
- data/README.md +168 -12
- data/ROADMAP.md +27 -27
- data/Rakefile +76 -71
- data/SECURITY.md +54 -54
- data/SUPPORT.md +14 -14
- data/accelerator/bin/SHA256SUMS +6 -0
- data/accelerator/bin/rubydb-accelerator-darwin-amd64 +0 -0
- data/accelerator/bin/rubydb-accelerator-darwin-arm64 +0 -0
- data/accelerator/bin/rubydb-accelerator-linux-amd64 +0 -0
- data/accelerator/bin/rubydb-accelerator-linux-arm64 +0 -0
- data/accelerator/bin/rubydb-accelerator-windows-amd64.exe +0 -0
- data/accelerator/bin/rubydb-accelerator-windows-arm64.exe +0 -0
- data/accelerator/cmd/rubydb-accelerator/main.go +11 -0
- data/accelerator/go.mod +3 -0
- data/accelerator/internal/execution/aggregate.go +94 -0
- data/accelerator/internal/execution/distinct.go +22 -0
- data/accelerator/internal/execution/filter.go +73 -0
- data/accelerator/internal/execution/join.go +79 -0
- data/accelerator/internal/execution/operators.go +167 -0
- data/accelerator/internal/execution/scan.go +20 -0
- data/accelerator/internal/execution/sort.go +62 -0
- data/accelerator/internal/execution/types.go +136 -0
- data/accelerator/internal/execution/value.go +67 -0
- data/accelerator/internal/memory/arena.go +47 -0
- data/accelerator/internal/memory/reuse.go +22 -0
- data/accelerator/internal/metrics/registry.go +67 -0
- data/accelerator/internal/parallel/bounded_queue.go +56 -0
- data/accelerator/internal/parallel/scheduler.go +47 -0
- data/accelerator/internal/parallel/worker_pool.go +53 -0
- data/accelerator/internal/protocol/cancellation.go +48 -0
- data/accelerator/internal/protocol/columnar.go +263 -0
- data/accelerator/internal/protocol/frame.go +187 -0
- data/accelerator/internal/runtime/worker.go +521 -0
- data/accelerator/internal/storage/page_reader.go +81 -0
- data/accelerator/internal/storage/snapshot_scan.go +539 -0
- data/accelerator/internal/wal/checksum.go +13 -0
- data/accelerator/internal/wal/compression.go +41 -0
- data/accelerator/internal/wal/group_commit.go +24 -0
- data/accelerator/internal/wal/record_encoder.go +40 -0
- data/adapters/activerecord/Gemfile +11 -11
- data/adapters/activerecord/README.md +8 -3
- data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +881 -879
- data/adapters/activerecord/rubydb-activerecord.gemspec +21 -21
- data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +143 -143
- data/adapters/ruby/README.md +18 -18
- data/adapters/sequel/README.md +11 -11
- data/config/monitoring/prometheus-alerts.yml +39 -39
- data/config/production.yml +36 -36
- data/docs/README.md +77 -71
- data/docs/architecture/concurrency.md +14 -14
- data/docs/architecture/current-state.md +125 -125
- data/docs/architecture/execution-engine.md +25 -25
- data/docs/architecture/go-accelerator.md +179 -0
- data/docs/architecture/indexes.md +19 -19
- data/docs/architecture/mvcc.md +19 -19
- data/docs/architecture/overview.md +13 -13
- data/docs/architecture/pages.md +11 -11
- data/docs/architecture/production-roadmap.md +82 -82
- data/docs/architecture/query-planner.md +20 -20
- data/docs/architecture/recovery.md +18 -18
- data/docs/architecture/sql-engine.md +12 -12
- data/docs/architecture/storage-engine.md +14 -14
- data/docs/architecture/transactions.md +10 -10
- data/docs/architecture/wal.md +28 -28
- data/docs/cli-cheatsheet.md +98 -98
- data/docs/cli.md +299 -275
- data/docs/contributing/architecture.md +9 -9
- data/docs/contributing/benchmarking.md +30 -14
- data/docs/contributing/development.md +16 -16
- data/docs/contributing/release-process.md +49 -49
- data/docs/contributing/testing.md +16 -16
- data/docs/debugging.md +229 -229
- data/docs/developer/branching.md +10 -10
- data/docs/developer/database-diff.md +10 -10
- data/docs/developer/local-development.md +49 -17
- data/docs/developer/snapshots.md +9 -9
- data/docs/developer/temporal-data.md +10 -10
- data/docs/developer-guide.md +297 -297
- data/docs/getting-started/first-database.md +16 -16
- data/docs/getting-started/first-query.md +13 -13
- data/docs/getting-started/installation.md +19 -19
- data/docs/getting-started/local-to-production.md +300 -300
- data/docs/getting-started/quickstart.md +17 -17
- data/docs/getting-started/rails.md +16 -16
- data/docs/hardening_backlog.md +93 -93
- data/docs/lessons-learned.md +112 -112
- data/docs/operations/backups.md +33 -33
- data/docs/operations/disaster-recovery.md +31 -31
- data/docs/operations/failover.md +30 -30
- data/docs/operations/monitoring.md +25 -25
- data/docs/operations/production-guide.md +295 -295
- data/docs/operations/production-runbook.md +45 -45
- data/docs/operations/replication.md +33 -33
- data/docs/operations/restore.md +6 -6
- data/docs/operations/runbook.md +34 -34
- data/docs/operations/upgrades.md +14 -14
- data/docs/operations/workload-testing.md +17 -17
- data/docs/production-readiness.md +118 -118
- data/docs/production_validation.md +150 -150
- data/docs/rails/active-record.md +11 -11
- data/docs/rails/compatibility-guide.md +90 -90
- data/docs/rails/database-yml.md +92 -92
- data/docs/rails/installation.md +17 -17
- data/docs/rails/migrations.md +17 -17
- data/docs/rails/production.md +82 -82
- data/docs/rails/troubleshooting.md +18 -18
- data/docs/release.md +25 -25
- data/docs/server/architecture.md +10 -10
- data/docs/server/authentication.md +10 -10
- data/docs/server/configuration.md +16 -16
- data/docs/server/connection-pooling.md +10 -10
- data/docs/server/deployment.md +10 -10
- data/docs/server/protocol.md +12 -12
- data/docs/sql/compatibility-guide.md +82 -82
- data/docs/sql/compatibility.md +39 -39
- data/docs/sql/data-types.md +10 -10
- data/docs/sql/functions.md +9 -9
- data/docs/sql/joins.md +9 -9
- data/docs/sql/operators.md +9 -9
- data/docs/sql/sqlite-compatibility.md +21 -21
- data/docs/sql/syntax.md +10 -10
- data/docs/sql/transactions.md +10 -10
- data/docs/troubleshooting.md +244 -244
- data/lessons/01-foundations.md +73 -0
- data/lessons/02-local-development.md +121 -0
- data/lessons/03-embedded-rubydb.md +99 -0
- data/lessons/04-rails-complex-apps.md +138 -0
- data/lessons/05-rubydb-production-server.md +237 -0
- data/lessons/06-postgresql-massive-apps.md +96 -0
- data/lessons/07-hybrid-microservices.md +179 -0
- data/lessons/08-migrations-backups-recovery.md +86 -0
- data/lessons/09-observability-security-scale.md +87 -0
- data/lessons/10-release-readiness.md +192 -0
- data/lessons/11-community-adapter.md +323 -0
- data/lessons/12-rails-ecommerce-pressure.md +263 -0
- data/lib/rubydb/accelerator/client.rb +451 -0
- data/lib/rubydb/accelerator/error.rb +22 -0
- data/lib/rubydb/accelerator/manager.rb +606 -0
- data/lib/rubydb/accelerator.rb +13 -0
- data/lib/rubydb/backup/archive.rb +332 -334
- data/lib/rubydb/backup/backup.rb +400 -401
- data/lib/rubydb/backup/incremental.rb +349 -353
- data/lib/rubydb/backup/restore.rb +289 -290
- data/lib/rubydb/backup/snapshot.rb +265 -267
- data/lib/rubydb/backup/verification.rb +276 -279
- data/lib/rubydb/branching/branch.rb +181 -181
- data/lib/rubydb/branching/branch_manager.rb +307 -311
- data/lib/rubydb/branching/branch_metadata.rb +140 -140
- data/lib/rubydb/branching/checkout.rb +165 -166
- data/lib/rubydb/branching/copy_on_write.rb +272 -272
- data/lib/rubydb/branching/diff.rb +137 -138
- data/lib/rubydb/branching/merge.rb +282 -285
- data/lib/rubydb/build_info.rb +15 -15
- data/lib/rubydb/catalog/catalog.rb +391 -391
- data/lib/rubydb/catalog/column.rb +112 -112
- data/lib/rubydb/catalog/constraint.rb +180 -180
- data/lib/rubydb/catalog/database.rb +184 -184
- data/lib/rubydb/catalog/index.rb +97 -97
- data/lib/rubydb/catalog/schema.rb +103 -103
- data/lib/rubydb/catalog/sequence.rb +90 -90
- data/lib/rubydb/catalog/system_catalog.rb +698 -698
- data/lib/rubydb/catalog/table.rb +178 -178
- data/lib/rubydb/catalog/trigger.rb +102 -102
- data/lib/rubydb/catalog/view.rb +66 -66
- data/lib/rubydb/cli/application.rb +168 -163
- data/lib/rubydb/cli/commands/accelerator.rb +72 -0
- data/lib/rubydb/cli/commands/backup.rb +80 -81
- data/lib/rubydb/cli/commands/branch.rb +72 -72
- data/lib/rubydb/cli/commands/checkout.rb +54 -54
- data/lib/rubydb/cli/commands/create.rb +58 -58
- data/lib/rubydb/cli/commands/diff.rb +76 -77
- data/lib/rubydb/cli/commands/doctor.rb +77 -74
- data/lib/rubydb/cli/commands/drop.rb +57 -57
- data/lib/rubydb/cli/commands/init.rb +101 -102
- data/lib/rubydb/cli/commands/inspect.rb +95 -95
- data/lib/rubydb/cli/commands/merge.rb +63 -63
- data/lib/rubydb/cli/commands/migrate.rb +62 -62
- data/lib/rubydb/cli/commands/restart.rb +42 -39
- data/lib/rubydb/cli/commands/restore.rb +121 -121
- data/lib/rubydb/cli/commands/shell.rb +365 -365
- data/lib/rubydb/cli/commands/snapshot.rb +79 -79
- data/lib/rubydb/cli/commands/start.rb +88 -82
- data/lib/rubydb/cli/commands/status.rb +96 -92
- data/lib/rubydb/cli/commands/stop.rb +47 -47
- data/lib/rubydb/cli/commands/vacuum.rb +58 -58
- data/lib/rubydb/cli/formatter.rb +221 -221
- data/lib/rubydb/cli/output.rb +168 -168
- data/lib/rubydb/client/client.rb +309 -304
- data/lib/rubydb/client/connection.rb +429 -415
- data/lib/rubydb/client/connection_pool.rb +168 -168
- data/lib/rubydb/client/connection_url.rb +96 -96
- data/lib/rubydb/client/prepared_statement.rb +60 -60
- data/lib/rubydb/client/result.rb +127 -123
- data/lib/rubydb/client/statement.rb +52 -52
- data/lib/rubydb/client/transaction.rb +130 -130
- data/lib/rubydb/concurrency/concurrency.rb +19 -19
- data/lib/rubydb/concurrency/deadlock_detector.rb +148 -150
- data/lib/rubydb/concurrency/latch.rb +101 -101
- data/lib/rubydb/concurrency/lock_graph.rb +163 -165
- data/lib/rubydb/concurrency/mutex.rb +181 -183
- data/lib/rubydb/concurrency/rw_lock.rb +180 -180
- data/lib/rubydb/concurrency/scheduler.rb +248 -250
- data/lib/rubydb/concurrency/worker_pool.rb +145 -143
- data/lib/rubydb/configuration/config.rb +170 -170
- data/lib/rubydb/configuration/defaults.rb +191 -179
- data/lib/rubydb/configuration/environment.rb +152 -152
- data/lib/rubydb/configuration/parser.rb +185 -185
- data/lib/rubydb/configuration/validation.rb +228 -221
- data/lib/rubydb/constants.rb +74 -74
- data/lib/rubydb/constraints/check.rb +181 -181
- data/lib/rubydb/constraints/constraint.rb +101 -101
- data/lib/rubydb/constraints/foreign_key.rb +130 -130
- data/lib/rubydb/constraints/not_null.rb +64 -64
- data/lib/rubydb/constraints/primary_key.rb +99 -99
- data/lib/rubydb/constraints/unique.rb +106 -108
- data/lib/rubydb/constraints/validator.rb +349 -350
- data/lib/rubydb/errors/authentication_error.rb +10 -10
- data/lib/rubydb/errors/authorization_error.rb +23 -23
- data/lib/rubydb/errors/client_error.rb +10 -10
- data/lib/rubydb/errors/configuration_error.rb +10 -10
- data/lib/rubydb/errors/connection_error.rb +10 -10
- data/lib/rubydb/errors/constraint_error.rb +23 -23
- data/lib/rubydb/errors/corruption_error.rb +10 -10
- data/lib/rubydb/errors/database_error.rb +10 -10
- data/lib/rubydb/errors/error.rb +20 -20
- data/lib/rubydb/errors/execution_error.rb +10 -10
- data/lib/rubydb/errors/parser_error.rb +10 -10
- data/lib/rubydb/errors/recovery_error.rb +10 -10
- data/lib/rubydb/errors/replication_error.rb +10 -10
- data/lib/rubydb/errors/server_error.rb +6 -6
- data/lib/rubydb/errors/storage_error.rb +10 -10
- data/lib/rubydb/errors/transaction_error.rb +10 -10
- data/lib/rubydb/execution/accelerator_dispatch.rb +30 -0
- data/lib/rubydb/execution/aggregate_executor.rb +134 -138
- data/lib/rubydb/execution/cost_model.rb +72 -0
- data/lib/rubydb/execution/delete_executor.rb +110 -112
- data/lib/rubydb/execution/distinct_executor.rb +131 -135
- data/lib/rubydb/execution/executor.rb +1544 -1188
- data/lib/rubydb/execution/expression.rb +191 -193
- data/lib/rubydb/execution/index_scan.rb +142 -142
- data/lib/rubydb/execution/insert_executor.rb +215 -217
- data/lib/rubydb/execution/join_executor.rb +243 -249
- data/lib/rubydb/execution/limit_executor.rb +83 -85
- data/lib/rubydb/execution/operator_selection.rb +57 -0
- data/lib/rubydb/execution/optimizer.rb +227 -215
- data/lib/rubydb/execution/physical_plan.rb +47 -0
- data/lib/rubydb/execution/plan.rb +355 -353
- data/lib/rubydb/execution/planner.rb +508 -536
- data/lib/rubydb/execution/predicate.rb +235 -235
- data/lib/rubydb/execution/scan.rb +49 -49
- data/lib/rubydb/execution/sequential_scan.rb +63 -63
- data/lib/rubydb/execution/sort_executor.rb +194 -185
- data/lib/rubydb/execution/update_executor.rb +160 -162
- data/lib/rubydb/functions/aggregate.rb +70 -70
- data/lib/rubydb/functions/date_functions.rb +274 -278
- data/lib/rubydb/functions/function.rb +85 -85
- data/lib/rubydb/functions/json_functions.rb +231 -215
- data/lib/rubydb/functions/numeric_functions.rb +346 -346
- data/lib/rubydb/functions/scalar.rb +52 -52
- data/lib/rubydb/functions/string_functions.rb +383 -383
- data/lib/rubydb/functions/system_functions.rb +258 -246
- data/lib/rubydb/history/as_of.rb +238 -238
- data/lib/rubydb/history/change.rb +105 -105
- data/lib/rubydb/history/history.rb +131 -131
- data/lib/rubydb/history/history_manager.rb +228 -229
- data/lib/rubydb/history/temporal_query.rb +202 -202
- data/lib/rubydb/history/timeline.rb +144 -144
- data/lib/rubydb/indexes/btree.rb +215 -186
- data/lib/rubydb/indexes/btree_cursor.rb +258 -258
- data/lib/rubydb/indexes/btree_node.rb +384 -385
- data/lib/rubydb/indexes/hash_index.rb +150 -150
- data/lib/rubydb/indexes/index.rb +71 -71
- data/lib/rubydb/indexes/index_manager.rb +408 -406
- data/lib/rubydb/indexes/index_scan.rb +466 -470
- data/lib/rubydb/migrations/migration.rb +253 -254
- data/lib/rubydb/migrations/migration_lock.rb +146 -146
- data/lib/rubydb/migrations/migration_manager.rb +187 -176
- data/lib/rubydb/migrations/migration_version.rb +71 -71
- data/lib/rubydb/migrations/schema_diff.rb +211 -211
- data/lib/rubydb/migrations/schema_version.rb +64 -64
- data/lib/rubydb/monitoring/events.rb +155 -160
- data/lib/rubydb/monitoring/health.rb +216 -222
- data/lib/rubydb/monitoring/logger.rb +188 -193
- data/lib/rubydb/monitoring/metrics.rb +363 -359
- data/lib/rubydb/monitoring/performance.rb +176 -176
- data/lib/rubydb/monitoring/statistics.rb +168 -170
- data/lib/rubydb/mvcc/garbage_collector.rb +199 -199
- data/lib/rubydb/mvcc/mvcc.rb +16 -16
- data/lib/rubydb/mvcc/snapshot.rb +146 -147
- data/lib/rubydb/mvcc/vacuum.rb +180 -180
- data/lib/rubydb/mvcc/version.rb +106 -106
- data/lib/rubydb/mvcc/version_store.rb +396 -398
- data/lib/rubydb/mvcc/visibility.rb +107 -109
- data/lib/rubydb/protocol/capabilities.rb +125 -125
- data/lib/rubydb/protocol/decoder.rb +142 -145
- data/lib/rubydb/protocol/encoder.rb +131 -136
- data/lib/rubydb/protocol/handshake.rb +306 -305
- data/lib/rubydb/protocol/message.rb +121 -121
- data/lib/rubydb/protocol/parameter_binder.rb +101 -0
- data/lib/rubydb/protocol/protocol.rb +276 -277
- data/lib/rubydb/protocol/version.rb +54 -54
- data/lib/rubydb/rails/adapter.rb +245 -239
- data/lib/rubydb/rails/connection.rb +312 -314
- data/lib/rubydb/rails/database_statements.rb +122 -122
- data/lib/rubydb/rails/migration.rb +131 -131
- data/lib/rubydb/rails/quoting.rb +109 -109
- data/lib/rubydb/rails/result.rb +117 -117
- data/lib/rubydb/rails/schema_statements.rb +339 -339
- data/lib/rubydb/rails/transaction.rb +105 -105
- data/lib/rubydb/rails/type.rb +126 -126
- data/lib/rubydb/recovery/checkpoint.rb +261 -257
- data/lib/rubydb/recovery/consistency.rb +457 -467
- data/lib/rubydb/recovery/corruption_detector.rb +5 -5
- data/lib/rubydb/recovery/crash_recovery.rb +381 -387
- data/lib/rubydb/recovery/recovery_manager.rb +204 -206
- data/lib/rubydb/recovery/redo.rb +235 -237
- data/lib/rubydb/recovery/undo.rb +204 -206
- data/lib/rubydb/replication/failover.rb +5 -5
- data/lib/rubydb/replication/fencing.rb +63 -63
- data/lib/rubydb/replication/primary.rb +461 -450
- data/lib/rubydb/replication/replica.rb +382 -384
- data/lib/rubydb/replication/replication_log.rb +194 -200
- data/lib/rubydb/replication/replication_manager.rb +307 -308
- data/lib/rubydb/replication/replication_slot.rb +293 -295
- data/lib/rubydb/replication/replication_stream.rb +198 -201
- data/lib/rubydb/rubydb.rb +570 -560
- data/lib/rubydb/security/access_control.rb +252 -254
- data/lib/rubydb/security/audit_log.rb +209 -213
- data/lib/rubydb/security/authentication.rb +302 -302
- data/lib/rubydb/security/authorization.rb +282 -282
- data/lib/rubydb/security/credentials.rb +192 -196
- data/lib/rubydb/security/password.rb +205 -215
- data/lib/rubydb/security/permissions.rb +74 -74
- data/lib/rubydb/security/role.rb +99 -101
- data/lib/rubydb/security/user.rb +86 -86
- data/lib/rubydb/server/connection.rb +383 -366
- data/lib/rubydb/server/connection_pool.rb +193 -193
- data/lib/rubydb/server/lifecycle.rb +227 -228
- data/lib/rubydb/server/listener.rb +139 -136
- data/lib/rubydb/server/request_handler.rb +277 -276
- data/lib/rubydb/server/server.rb +363 -364
- data/lib/rubydb/server/session.rb +416 -369
- data/lib/rubydb/server/worker.rb +206 -210
- data/lib/rubydb/server/worker_pool.rb +168 -168
- data/lib/rubydb/sql/ast/alter_table.rb +169 -169
- data/lib/rubydb/sql/ast/begin_transaction.rb +47 -47
- data/lib/rubydb/sql/ast/commit.rb +37 -37
- data/lib/rubydb/sql/ast/constraint.rb +92 -83
- data/lib/rubydb/sql/ast/create_database.rb +41 -41
- data/lib/rubydb/sql/ast/create_index.rb +61 -61
- data/lib/rubydb/sql/ast/create_schema.rb +52 -52
- data/lib/rubydb/sql/ast/create_table.rb +187 -187
- data/lib/rubydb/sql/ast/delete.rb +54 -54
- data/lib/rubydb/sql/ast/drop_database.rb +41 -41
- data/lib/rubydb/sql/ast/drop_index.rb +41 -41
- data/lib/rubydb/sql/ast/drop_schema.rb +49 -49
- data/lib/rubydb/sql/ast/drop_table.rb +49 -49
- data/lib/rubydb/sql/ast/explain.rb +64 -64
- data/lib/rubydb/sql/ast/expression.rb +617 -604
- data/lib/rubydb/sql/ast/insert.rb +66 -66
- data/lib/rubydb/sql/ast/node.rb +42 -42
- data/lib/rubydb/sql/ast/rollback.rb +63 -63
- data/lib/rubydb/sql/ast/savepoint.rb +59 -59
- data/lib/rubydb/sql/ast/select.rb +88 -88
- data/lib/rubydb/sql/ast/set_operation.rb +22 -20
- data/lib/rubydb/sql/ast/trigger.rb +35 -29
- data/lib/rubydb/sql/ast/update.rb +88 -88
- data/lib/rubydb/sql/ast/vacuum.rb +19 -19
- data/lib/rubydb/sql/ast/view.rb +38 -32
- data/lib/rubydb/sql/ast/with.rb +32 -32
- data/lib/rubydb/sql/grammar.rb +86 -86
- data/lib/rubydb/sql/keywords.rb +156 -156
- data/lib/rubydb/sql/lexer.rb +209 -214
- data/lib/rubydb/sql/operators.rb +100 -100
- data/lib/rubydb/sql/parser.rb +1167 -1170
- data/lib/rubydb/sql/planner/analyzer.rb +283 -302
- data/lib/rubydb/sql/planner/binder.rb +537 -550
- data/lib/rubydb/sql/planner/type_checker.rb +427 -431
- data/lib/rubydb/sql/token.rb +210 -210
- data/lib/rubydb/storage/buffer_frame.rb +44 -44
- data/lib/rubydb/storage/buffer_pool.rb +155 -155
- data/lib/rubydb/storage/database_lock.rb +74 -74
- data/lib/rubydb/storage/deserializer.rb +332 -342
- data/lib/rubydb/storage/engine.rb +2409 -2330
- data/lib/rubydb/storage/file_manager.rb +191 -187
- data/lib/rubydb/storage/free_space_map.rb +79 -81
- data/lib/rubydb/storage/page.rb +92 -94
- data/lib/rubydb/storage/page_allocator.rb +852 -855
- data/lib/rubydb/storage/page_header.rb +63 -67
- data/lib/rubydb/storage/page_manager.rb +127 -131
- data/lib/rubydb/storage/record.rb +58 -58
- data/lib/rubydb/storage/row.rb +78 -78
- data/lib/rubydb/storage/serializer.rb +51 -51
- data/lib/rubydb/storage/snapshot_reader.rb +167 -0
- data/lib/rubydb/storage/storage_layout.rb +151 -151
- data/lib/rubydb/storage/storage_manager.rb +114 -114
- data/lib/rubydb/storage/tuple.rb +458 -461
- data/lib/rubydb/storage/visibility_map.rb +964 -973
- data/lib/rubydb/transactions/commit_manager.rb +219 -220
- data/lib/rubydb/transactions/isolation.rb +98 -98
- data/lib/rubydb/transactions/lock.rb +76 -76
- data/lib/rubydb/transactions/lock_manager.rb +359 -362
- data/lib/rubydb/transactions/savepoint.rb +142 -143
- data/lib/rubydb/transactions/transaction.rb +214 -215
- data/lib/rubydb/transactions/transaction_id.rb +84 -84
- data/lib/rubydb/transactions/transaction_log.rb +256 -257
- data/lib/rubydb/transactions/transaction_manager.rb +434 -435
- data/lib/rubydb/types/bigint.rb +36 -36
- data/lib/rubydb/types/blob.rb +37 -37
- data/lib/rubydb/types/boolean.rb +34 -34
- data/lib/rubydb/types/date.rb +39 -39
- data/lib/rubydb/types/decimal.rb +48 -48
- data/lib/rubydb/types/float.rb +34 -34
- data/lib/rubydb/types/integer.rb +36 -36
- data/lib/rubydb/types/json.rb +41 -41
- data/lib/rubydb/types/null.rb +34 -34
- data/lib/rubydb/types/smallint.rb +36 -36
- data/lib/rubydb/types/text.rb +37 -37
- data/lib/rubydb/types/time.rb +46 -46
- data/lib/rubydb/types/timestamp.rb +39 -39
- data/lib/rubydb/types/type.rb +119 -119
- data/lib/rubydb/types/uuid.rb +47 -47
- data/lib/rubydb/types/varchar.rb +37 -37
- data/lib/rubydb/version.rb +32 -32
- data/lib/rubydb/wal/archive.rb +207 -193
- data/lib/rubydb/wal/checkpoint.rb +181 -183
- data/lib/rubydb/wal/lsn.rb +94 -94
- data/lib/rubydb/wal/reader.rb +259 -260
- data/lib/rubydb/wal/record.rb +105 -105
- data/lib/rubydb/wal/segment.rb +193 -193
- data/lib/rubydb/wal/wal.rb +481 -452
- data/lib/rubydb/wal/writer.rb +236 -236
- data/lib/rubydb.rb +7 -7
- data/packaging/docker/docker-compose.failover.yml +43 -43
- data/packaging/homebrew/rubydb.rb +19 -19
- data/rubydb.gemspec +70 -57
- data/scripts/benchmark +7 -7
- data/scripts/build_accelerator +49 -0
- data/scripts/durability_drill +37 -37
- data/scripts/fuzz +63 -63
- data/scripts/release +77 -42
- data/scripts/release_check +43 -43
- data/scripts/replication_failover_drill +268 -250
- data/scripts/replication_network_failover_drill +287 -255
- data/scripts/restore_drill +45 -45
- data/scripts/security +45 -0
- metadata +102 -1
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Lesson 2: repeatable local development
|
|
2
|
+
|
|
3
|
+
The fastest safe workflow is to make local setup disposable and repeatable.
|
|
4
|
+
Keep database files under `tmp/` or another ignored directory, use separate
|
|
5
|
+
development and test paths, and run migrations from source control.
|
|
6
|
+
|
|
7
|
+
## Rails application setup
|
|
8
|
+
|
|
9
|
+
Add the RubyDB gems to a Rails application. Pin versions in a real application
|
|
10
|
+
after testing them; the versions below match the published example used by the
|
|
11
|
+
RubyDB project at the time this lesson was written.
|
|
12
|
+
|
|
13
|
+
```ruby
|
|
14
|
+
# Gemfile
|
|
15
|
+
gem "rubydb", "0.1.6"
|
|
16
|
+
gem "rubydb-activerecord", "0.1.3"
|
|
17
|
+
gem "pg" # Keep this when production may use PostgreSQL.
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Install dependencies:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
bundle install
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Configure development and test with separate embedded files:
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
# config/database.yml
|
|
30
|
+
default: &default
|
|
31
|
+
adapter: rubydb
|
|
32
|
+
embedded: true
|
|
33
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
34
|
+
|
|
35
|
+
development:
|
|
36
|
+
<<: *default
|
|
37
|
+
database: <%= Rails.root.join("tmp/rubydb_development.rdb") %>
|
|
38
|
+
|
|
39
|
+
test:
|
|
40
|
+
<<: *default
|
|
41
|
+
database: <%= Rails.root.join("tmp/rubydb_test.rdb") %>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run the schema and tests:
|
|
45
|
+
|
|
46
|
+
```sh
|
|
47
|
+
bin/rails db:create
|
|
48
|
+
bin/rails db:migrate
|
|
49
|
+
bin/rails test
|
|
50
|
+
bin/rails server
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
`db:create` is harmless only when it targets a disposable local path. Never
|
|
54
|
+
point it at a production database and never commit `.rdb` files.
|
|
55
|
+
|
|
56
|
+
## A regular Ruby smoke program
|
|
57
|
+
|
|
58
|
+
This is a complete single-process example using the public storage API:
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
# script/rubydb_smoke.rb
|
|
62
|
+
require "rubydb"
|
|
63
|
+
|
|
64
|
+
path = "tmp/rubydb_smoke.rdb"
|
|
65
|
+
engine = RubyDB::Storage::Engine.new(path)
|
|
66
|
+
begin
|
|
67
|
+
engine.execute("CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
|
|
68
|
+
engine.execute("INSERT INTO events (name) VALUES ('boot')")
|
|
69
|
+
rows = engine.execute("SELECT id, name FROM events ORDER BY id")
|
|
70
|
+
puts rows.inspect
|
|
71
|
+
ensure
|
|
72
|
+
engine.close
|
|
73
|
+
end
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Run it with:
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
bundle exec ruby script/rubydb_smoke.rb
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
For a multi-process application, replace the embedded engine with a RubyDB
|
|
83
|
+
server and `RubyDB::Client::Client`. Do not let a web process and a worker
|
|
84
|
+
open the same embedded path independently.
|
|
85
|
+
|
|
86
|
+
## Local PostgreSQL when it is the production target
|
|
87
|
+
|
|
88
|
+
Use the same application code against a local PostgreSQL instance when you
|
|
89
|
+
intend to deploy PostgreSQL. This catches dialect, type, index, and migration
|
|
90
|
+
differences early:
|
|
91
|
+
|
|
92
|
+
```sh
|
|
93
|
+
docker run --name rubydb-lesson-postgres \
|
|
94
|
+
--env POSTGRES_PASSWORD=devpassword \
|
|
95
|
+
--env POSTGRES_DB=lesson_app \
|
|
96
|
+
--publish 5432:5432 \
|
|
97
|
+
--detach postgres:16
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Set the local connection string in your shell or an ignored `.env` file:
|
|
101
|
+
|
|
102
|
+
```sh
|
|
103
|
+
DATABASE_URL=postgresql://postgres:devpassword@127.0.0.1:5432/lesson_app
|
|
104
|
+
bin/rails db:migrate
|
|
105
|
+
bin/rails test
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Do not put that password in Git. Stop and remove this disposable container
|
|
109
|
+
only after confirming it is the lesson container:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
docker stop rubydb-lesson-postgres
|
|
113
|
+
docker rm rubydb-lesson-postgres
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Checkpoint
|
|
117
|
+
|
|
118
|
+
The checkpoint passes when a new developer can clone the project, run
|
|
119
|
+
`bundle install`, migrate an empty database, run the tests, and reproduce the
|
|
120
|
+
smoke query without manually editing a database file. Continue with [lesson 3](03-embedded-rubydb.md)
|
|
121
|
+
to understand the limits of embedded mode.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Lesson 3: use RubyDB embedded deliberately
|
|
2
|
+
|
|
3
|
+
Embedded mode puts the database engine in the application process. It is
|
|
4
|
+
simple, fast to start, and useful for local development, command-line tools,
|
|
5
|
+
small internal utilities, and a service with one database-owning process.
|
|
6
|
+
|
|
7
|
+
It is not a shared file protocol. Two independent processes must not open the
|
|
8
|
+
same `.rdb` path concurrently. A Rails web server with several worker
|
|
9
|
+
processes, or a web server plus a job worker, should use RubyDB server/client
|
|
10
|
+
mode instead.
|
|
11
|
+
|
|
12
|
+
## A safe embedded boundary
|
|
13
|
+
|
|
14
|
+
```text
|
|
15
|
+
one Ruby process
|
|
16
|
+
|
|
|
17
|
+
+-- RubyDB::Storage::Engine
|
|
18
|
+
|
|
|
19
|
+
+-- tmp/service.rdb or /var/lib/service/service.rdb
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Keep the path outside the source tree and make its owner explicit:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require "rubydb"
|
|
26
|
+
|
|
27
|
+
database_path = ENV.fetch("RUBYDB_DATABASE", "tmp/service.rdb")
|
|
28
|
+
engine = RubyDB::Storage::Engine.new(database_path)
|
|
29
|
+
|
|
30
|
+
begin
|
|
31
|
+
engine.execute(<<~SQL)
|
|
32
|
+
CREATE TABLE IF NOT EXISTS jobs (
|
|
33
|
+
id INTEGER PRIMARY KEY,
|
|
34
|
+
state TEXT NOT NULL,
|
|
35
|
+
created_at TIMESTAMP
|
|
36
|
+
)
|
|
37
|
+
SQL
|
|
38
|
+
|
|
39
|
+
engine.execute("INSERT INTO jobs (state, created_at) VALUES ('queued', CURRENT_TIMESTAMP)")
|
|
40
|
+
p engine.execute("SELECT id, state FROM jobs ORDER BY id")
|
|
41
|
+
ensure
|
|
42
|
+
engine.close
|
|
43
|
+
end
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use bound values for data. Do not build SQL by interpolating request
|
|
47
|
+
parameters, usernames, or search terms.
|
|
48
|
+
|
|
49
|
+
## Rails embedded configuration
|
|
50
|
+
|
|
51
|
+
```yaml
|
|
52
|
+
development:
|
|
53
|
+
adapter: rubydb
|
|
54
|
+
embedded: true
|
|
55
|
+
database: <%= ENV.fetch("RUBYDB_DATABASE", Rails.root.join("tmp/development.rdb")) %>
|
|
56
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For tests, use a different path. For a one-process production service, put the
|
|
60
|
+
database on persistent storage, set restrictive filesystem permissions, and
|
|
61
|
+
ensure the service manager starts only one owner. If the deployment scales the
|
|
62
|
+
service horizontally, embedded mode is the wrong topology.
|
|
63
|
+
|
|
64
|
+
## Inspect and back up the file
|
|
65
|
+
|
|
66
|
+
The repository CLI has read-only inspection, verified backup, restore, and
|
|
67
|
+
doctor commands:
|
|
68
|
+
|
|
69
|
+
```sh
|
|
70
|
+
rubydb doctor --quick --json
|
|
71
|
+
rubydb inspect --database tmp/service.rdb --stats --wal
|
|
72
|
+
rubydb backup --database tmp/service.rdb --dir tmp/backups --type full --compress
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Check the installed command’s help before automating a version-specific option:
|
|
76
|
+
|
|
77
|
+
```sh
|
|
78
|
+
rubydb doctor --help
|
|
79
|
+
rubydb backup --help
|
|
80
|
+
rubydb restore --help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Keep the backup directory on a different disk or host in production. A second
|
|
84
|
+
copy on the same failed disk is not a recovery plan.
|
|
85
|
+
|
|
86
|
+
## What embedded mode does not solve
|
|
87
|
+
|
|
88
|
+
Embedded mode does not provide a network endpoint, cross-host failover,
|
|
89
|
+
automatic leader election, a connection pool shared by processes, or a managed
|
|
90
|
+
cloud backup. Those are deployment responsibilities. If the workload needs
|
|
91
|
+
them, move to server/client mode or choose a managed PostgreSQL service.
|
|
92
|
+
|
|
93
|
+
## Checkpoint
|
|
94
|
+
|
|
95
|
+
Prove that the owner rule is enforceable: document the process that opens the
|
|
96
|
+
file, the persistent path, the backup destination, and the restore operator.
|
|
97
|
+
Then deliberately run the application with two processes in a disposable test
|
|
98
|
+
environment and confirm your deployment prevents shared-file access. Continue
|
|
99
|
+
to [lesson 4](04-rails-complex-apps.md) for Rails query and migration validation.
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Lesson 4: Rails and complex application code
|
|
2
|
+
|
|
3
|
+
RubyDB can be useful for a Rails application when the application stays within
|
|
4
|
+
the adapter’s tested surface. Complex Rails code is still possible, but every
|
|
5
|
+
important query and migration must be exercised against the exact database
|
|
6
|
+
topology you will deploy.
|
|
7
|
+
|
|
8
|
+
## Pin the adapter and configure the boundary
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
# Gemfile
|
|
12
|
+
gem "rubydb", "0.1.6"
|
|
13
|
+
gem "rubydb-activerecord", "0.1.3"
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Embedded development configuration:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
# config/database.yml
|
|
20
|
+
development:
|
|
21
|
+
adapter: rubydb
|
|
22
|
+
embedded: true
|
|
23
|
+
database: <%= Rails.root.join("tmp/development.rdb") %>
|
|
24
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
25
|
+
|
|
26
|
+
test:
|
|
27
|
+
adapter: rubydb
|
|
28
|
+
embedded: true
|
|
29
|
+
database: <%= Rails.root.join("tmp/test.rdb") %>
|
|
30
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
For server mode, use one connection URL supplied by the deployment:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
production:
|
|
37
|
+
adapter: rubydb
|
|
38
|
+
embedded: false
|
|
39
|
+
url: <%= ENV.fetch("RUBYDB_URL") %>
|
|
40
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The RubyDB adapter URL is `rubydb://` or TLS-enabled `rubydbs://`; it is not a
|
|
44
|
+
PostgreSQL `DATABASE_URL`.
|
|
45
|
+
|
|
46
|
+
## Associations, joins, and eager loading
|
|
47
|
+
|
|
48
|
+
Start with ordinary Rails models:
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
class Account < ApplicationRecord
|
|
52
|
+
has_many :projects, dependent: :destroy
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class Project < ApplicationRecord
|
|
56
|
+
belongs_to :account
|
|
57
|
+
has_many :tasks, dependent: :destroy
|
|
58
|
+
|
|
59
|
+
scope :active, -> { where(status: "active") }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class Task < ApplicationRecord
|
|
63
|
+
belongs_to :project
|
|
64
|
+
end
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Exercise both the SQL shape and the object-loading behavior:
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
accounts = Account
|
|
71
|
+
.joins(:projects)
|
|
72
|
+
.merge(Project.active)
|
|
73
|
+
.where(projects: { archived: false })
|
|
74
|
+
.includes(projects: :tasks)
|
|
75
|
+
.distinct
|
|
76
|
+
.order(:name)
|
|
77
|
+
|
|
78
|
+
accounts.each do |account|
|
|
79
|
+
account.projects.each do |project|
|
|
80
|
+
puts [account.name, project.name, project.tasks.size].join(" | ")
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
This example is a validation target, not a promise that every Arel variation
|
|
86
|
+
or database-specific query will work. Add request or model tests that assert
|
|
87
|
+
the result set, duplicate behavior, `NULL` behavior, ordering, and query count.
|
|
88
|
+
|
|
89
|
+
## Transactions and migrations
|
|
90
|
+
|
|
91
|
+
Keep a transaction around a business operation and make retry behavior
|
|
92
|
+
explicit:
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
ApplicationRecord.transaction do
|
|
96
|
+
project = Project.create!(account: account, name: "Billing", status: "active")
|
|
97
|
+
project.tasks.create!(title: "Verify invoice", state: "open")
|
|
98
|
+
end
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A migration must be safe on an empty and populated database:
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
class AddStateToTasks < ActiveRecord::Migration[7.2]
|
|
105
|
+
def change
|
|
106
|
+
add_column :tasks, :state, :string, null: false, default: "open"
|
|
107
|
+
add_index :tasks, [:project_id, :state]
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For a large table, test the migration duration, lock behavior, disk usage, and
|
|
113
|
+
rollback boundary on a restored copy. Do not assume a migration that succeeds
|
|
114
|
+
on an empty local file is safe during live traffic.
|
|
115
|
+
|
|
116
|
+
## The compatibility test matrix
|
|
117
|
+
|
|
118
|
+
Run the application suite for every supported combination, including the
|
|
119
|
+
database actually used in production:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
bundle exec rails db:drop db:create db:migrate
|
|
123
|
+
bundle exec rails test
|
|
124
|
+
bundle exec rails db:schema:dump
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Repeat that sequence with RubyDB embedded, RubyDB server/client, and
|
|
128
|
+
PostgreSQL when all three are supported. Compare schema dumps and test
|
|
129
|
+
business behavior, not merely process exit codes. Rails versions and adapter
|
|
130
|
+
versions should be pinned in CI; the project’s compatibility guide is the
|
|
131
|
+
source of truth for the currently tested matrix.
|
|
132
|
+
|
|
133
|
+
## Checkpoint
|
|
134
|
+
|
|
135
|
+
The checkpoint passes when your suite covers joins, eager loading, nested
|
|
136
|
+
associations, transactions, constraints, indexes, a populated-table
|
|
137
|
+
migration, schema dump/load, and error handling. Continue to [lesson 5](05-rubydb-production-server.md)
|
|
138
|
+
to run the same application through a private RubyDB server.
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# Lesson 5: RubyDB server production setup
|
|
2
|
+
|
|
3
|
+
Server mode puts one RubyDB process in charge of the data directory and lets
|
|
4
|
+
Ruby or Rails clients connect over the RubyDB protocol. This is the correct
|
|
5
|
+
RubyDB topology when several application processes need one database. The
|
|
6
|
+
application must never open the server’s `.rdb` files directly.
|
|
7
|
+
|
|
8
|
+
## Provision the service
|
|
9
|
+
|
|
10
|
+
Use a dedicated service account and persistent storage. The commands below
|
|
11
|
+
assume a Unix-like host; adapt ownership commands to your platform:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
gem install rubydb -v 0.1.6
|
|
15
|
+
install -d -o rubydb -g rubydb -m 0700 /var/lib/rubydb/data
|
|
16
|
+
install -d -o rubydb -g rubydb -m 0750 /var/log/rubydb
|
|
17
|
+
install -d -o rubydb -g rubydb -m 0700 /etc/rubydb
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Keep `/var/lib/rubydb` on persistent storage and place backups in a separate
|
|
21
|
+
failure domain. Restrict the database port to the application network.
|
|
22
|
+
|
|
23
|
+
## Configuration and startup
|
|
24
|
+
|
|
25
|
+
Create `/etc/rubydb/production.yml` from the repository’s production template.
|
|
26
|
+
Supply credentials and TLS paths through a secret manager or protected
|
|
27
|
+
deployment environment. A production configuration needs WAL, durable storage,
|
|
28
|
+
authentication, TLS, and bounded resources.
|
|
29
|
+
|
|
30
|
+
Start the supervised foreground process:
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
rubydb --config /etc/rubydb/production.yml --env production start
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For a development smoke server, the CLI also accepts explicit settings:
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
rubydb --env production start \
|
|
40
|
+
--host 127.0.0.1 \
|
|
41
|
+
--port 7432 \
|
|
42
|
+
--data-dir /var/lib/rubydb/data \
|
|
43
|
+
--log-dir /var/log/rubydb \
|
|
44
|
+
--pid-file /run/rubydb.pid
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Use systemd, a container supervisor, or the platform’s process manager to
|
|
48
|
+
restart the process and preserve logs. Do not let an orchestrator launch two
|
|
49
|
+
writers against one embedded path.
|
|
50
|
+
|
|
51
|
+
## Straight-to-production deployment path
|
|
52
|
+
|
|
53
|
+
Use this path when RubyDB is the chosen production database for a bounded
|
|
54
|
+
service. It works for a Rails app or a regular Ruby app. For a massive shared
|
|
55
|
+
application, use the PostgreSQL path in [lesson 6](06-postgresql-massive-apps.md)
|
|
56
|
+
instead.
|
|
57
|
+
|
|
58
|
+
### 1. Prepare the database host
|
|
59
|
+
|
|
60
|
+
The database host needs a persistent local volume, a dedicated service account,
|
|
61
|
+
and a private network route from the application. Run these commands as an
|
|
62
|
+
administrator and replace paths only after verifying the target host:
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
gem install rubydb -v 0.1.6
|
|
66
|
+
useradd --system --home-dir /var/lib/rubydb --shell /usr/sbin/nologin rubydb
|
|
67
|
+
install -d -o rubydb -g rubydb -m 0700 /var/lib/rubydb/data
|
|
68
|
+
install -d -o rubydb -g rubydb -m 0750 /var/log/rubydb
|
|
69
|
+
install -d -o root -g rubydb -m 0750 /etc/rubydb
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Copy the reviewed `config/production.yml` from the RubyDB repository to
|
|
73
|
+
`/etc/rubydb/production.yml`. Keep the database directory and WAL on approved
|
|
74
|
+
persistent storage. Keep backups on another host or failure domain.
|
|
75
|
+
|
|
76
|
+
### 2. Install secrets and TLS material
|
|
77
|
+
|
|
78
|
+
Use a CA-issued certificate for a real deployment. Do not use a self-signed
|
|
79
|
+
development certificate for public traffic. Inject these values through a
|
|
80
|
+
secret manager, protected service environment, or equivalent mechanism:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
RUBYDB_USERNAME=app_rw
|
|
84
|
+
RUBYDB_PASSWORD=generated-secret
|
|
85
|
+
RUBYDB_SSL_ENABLED=true
|
|
86
|
+
RUBYDB_SSL_CERT_FILE=/etc/rubydb/tls/server.crt
|
|
87
|
+
RUBYDB_SSL_KEY_FILE=/etc/rubydb/tls/server.key
|
|
88
|
+
RUBYDB_SSL_CA_FILE=/etc/rubydb/tls/ca.crt
|
|
89
|
+
RUBYDB_SSL_VERIFY_PEER=true
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Restrict the private key and environment file to the service. Do not put
|
|
93
|
+
secrets in Git, Docker images, process arguments, shell history, or logs.
|
|
94
|
+
Allow port `7432` only from the application and administration networks.
|
|
95
|
+
|
|
96
|
+
### 3. Start and verify RubyDB
|
|
97
|
+
|
|
98
|
+
Start the foreground process under a service manager such as systemd:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
sudo -u rubydb env RUBYDB_USERNAME=app_rw RUBYDB_PASSWORD='from-secret-store' \
|
|
102
|
+
rubydb --config /etc/rubydb/production.yml --env production start
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Verify the server from the application network:
|
|
106
|
+
|
|
107
|
+
```sh
|
|
108
|
+
rubydb --config /etc/rubydb/production.yml --env production status --json
|
|
109
|
+
rubydb --config /etc/rubydb/production.yml --env production doctor --json
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Run an authenticated TLS query using the same URL that the app will use. The
|
|
113
|
+
password below is intentionally a secret-manager value, not a real credential:
|
|
114
|
+
|
|
115
|
+
```sh
|
|
116
|
+
RUBYDB_URL='rubydbs://app_rw:URL_ENCODED_PASSWORD@db.internal:7432/app?verify_peer=true&ca_file=%2Fetc%2Frubydb%2Ftls%2Fca.crt' \
|
|
117
|
+
ruby -rrubydb -e 'c=RubyDB::Client::Client.new(url: ENV.fetch("RUBYDB_URL")); p c.query("SELECT 1").to_hash; c.disconnect'
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### 4. Deploy Rails or Ruby
|
|
121
|
+
|
|
122
|
+
For Rails, use server mode and inject the URL:
|
|
123
|
+
|
|
124
|
+
```yaml
|
|
125
|
+
# config/database.yml
|
|
126
|
+
production:
|
|
127
|
+
adapter: rubydb
|
|
128
|
+
embedded: false
|
|
129
|
+
url: <%= ENV.fetch("RUBYDB_URL") %>
|
|
130
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
For a regular Ruby service:
|
|
134
|
+
|
|
135
|
+
```ruby
|
|
136
|
+
require "rubydb"
|
|
137
|
+
|
|
138
|
+
client = RubyDB::Client::Client.new(url: ENV.fetch("RUBYDB_URL"))
|
|
139
|
+
begin
|
|
140
|
+
result = client.query("SELECT 1")
|
|
141
|
+
puts result.to_hash
|
|
142
|
+
ensure
|
|
143
|
+
client.disconnect
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Deploy the application artifact with the pinned RubyDB gems, then run schema
|
|
148
|
+
migrations once from a controlled release job:
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
RAILS_ENV=production bundle exec rails db:migrate
|
|
152
|
+
RAILS_ENV=production bundle exec rails db:migrate:status
|
|
153
|
+
RAILS_ENV=production bundle exec rails runner 'puts ApplicationRecord.connection.select_value("SELECT 1")'
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
The web and worker processes connect to `RUBYDB_URL`; they never mount or open
|
|
157
|
+
the server’s database directory.
|
|
158
|
+
|
|
159
|
+
### 5. Back up before accepting traffic
|
|
160
|
+
|
|
161
|
+
Create and verify a full backup, then perform a restore into an inactive path:
|
|
162
|
+
|
|
163
|
+
```sh
|
|
164
|
+
rubydb backup --database /var/lib/rubydb/data/app.rdb \
|
|
165
|
+
--dir /var/backups/rubydb --type full --compress
|
|
166
|
+
rubydb restore --database /var/lib/rubydb/restore-check.rdb \
|
|
167
|
+
--dir /var/backups/rubydb --latest --dry-run
|
|
168
|
+
rubydb restore --database /var/lib/rubydb/restore-check.rdb \
|
|
169
|
+
--dir /var/backups/rubydb --latest --force
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Complete an actual isolated restore and run representative reads before the
|
|
173
|
+
first public request. Record the backup checksum, restore duration, RPO, RTO,
|
|
174
|
+
and the operator responsible.
|
|
175
|
+
|
|
176
|
+
### 6. Release traffic gradually
|
|
177
|
+
|
|
178
|
+
Start with a canary or a small percentage of traffic. Verify one read, one
|
|
179
|
+
write transaction, one background job, and one application error path. Watch
|
|
180
|
+
latency, errors, active connections, WAL/checkpoint growth, memory, and free
|
|
181
|
+
disk. Keep the previous app artifact and verified backup until the rollback
|
|
182
|
+
window closes.
|
|
183
|
+
|
|
184
|
+
This path makes RubyDB usable in production for a validated bounded workload;
|
|
185
|
+
it does not provide automatic multi-host high availability by itself. If the
|
|
186
|
+
service needs automatic election, cross-host failover, or PostgreSQL-specific
|
|
187
|
+
SQL, stop and complete the corresponding validation or choose PostgreSQL.
|
|
188
|
+
|
|
189
|
+
## Rails connection
|
|
190
|
+
|
|
191
|
+
```yaml
|
|
192
|
+
# config/database.yml
|
|
193
|
+
production:
|
|
194
|
+
adapter: rubydb
|
|
195
|
+
embedded: false
|
|
196
|
+
url: <%= ENV.fetch("RUBYDB_URL") %>
|
|
197
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Use TLS verification in the URL:
|
|
201
|
+
|
|
202
|
+
```text
|
|
203
|
+
rubydbs://app_user:URL_ENCODED_PASSWORD@db.internal.example:7432/app?verify_peer=true&ca_file=%2Fetc%2Frubydb%2Fca.crt
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Inject `RUBYDB_URL` from a secret manager. Percent-encode special characters
|
|
207
|
+
in credentials, and never print the complete URL. The server must separately
|
|
208
|
+
define its authentication credentials, certificate, private key, and CA.
|
|
209
|
+
|
|
210
|
+
## Smoke test before traffic
|
|
211
|
+
|
|
212
|
+
```sh
|
|
213
|
+
rubydb --config /etc/rubydb/production.yml --env production status --json
|
|
214
|
+
rubydb --config /etc/rubydb/production.yml --env production doctor --quick --json
|
|
215
|
+
RAILS_ENV=production bundle exec rails db:migrate:status
|
|
216
|
+
RAILS_ENV=production bundle exec rails runner 'puts ApplicationRecord.connection.select_value("SELECT 1")'
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Then exercise one authenticated read, one transaction that creates and updates
|
|
220
|
+
data, one background job, and one verified backup/restore drill. Capture the
|
|
221
|
+
RubyDB version, application release, configuration checksum, and test output.
|
|
222
|
+
|
|
223
|
+
## Cloud deployment shape
|
|
224
|
+
|
|
225
|
+
On a platform such as Render, use a private database service with a persistent
|
|
226
|
+
disk and a separate web service. Put the private hostname in `RUBYDB_URL` and
|
|
227
|
+
keep both services in the same region. A web-service scale-out is not database
|
|
228
|
+
high availability; maintain external backups and validate a failover design
|
|
229
|
+
before relying on it.
|
|
230
|
+
|
|
231
|
+
## Checkpoint
|
|
232
|
+
|
|
233
|
+
The checkpoint passes when multiple app processes connect over TLS, writes are
|
|
234
|
+
visible to another client, migrations run once from a release job, readiness
|
|
235
|
+
checks are monitored, and a restore has been tested into a new directory.
|
|
236
|
+
Continue to [lesson 6](06-postgresql-massive-apps.md) to decide when PostgreSQL
|
|
237
|
+
is the better production choice.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Lesson 6: PostgreSQL for large applications
|
|
2
|
+
|
|
3
|
+
For a massive or business-critical application, PostgreSQL is usually the
|
|
4
|
+
safer system of record. It has a mature ecosystem of managed services,
|
|
5
|
+
replication, backup tooling, connection poolers, extensions, observability,
|
|
6
|
+
and Rails deployment experience. Choose it when many app instances, large
|
|
7
|
+
tables, broad SQL compatibility, or a large operations team are requirements.
|
|
8
|
+
|
|
9
|
+
RubyDB can still be valuable during development or as a bounded service. The
|
|
10
|
+
choice is architectural: do not make one database responsible for a workload
|
|
11
|
+
whose concurrency, failover, or SQL requirements it has not passed.
|
|
12
|
+
|
|
13
|
+
## Rails configuration
|
|
14
|
+
|
|
15
|
+
Add the PostgreSQL adapter:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
# Gemfile
|
|
19
|
+
gem "pg"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Configure production with the provider’s secret connection URL:
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
# config/database.yml
|
|
26
|
+
production:
|
|
27
|
+
url: <%= ENV.fetch("DATABASE_URL") %>
|
|
28
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
29
|
+
checkout_timeout: <%= ENV.fetch("DB_CHECKOUT_TIMEOUT", "5") %>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The URL is normally shaped like this; use a generated password rather than
|
|
33
|
+
the sample credentials:
|
|
34
|
+
|
|
35
|
+
```text
|
|
36
|
+
postgresql://app_user:URL_ENCODED_PASSWORD@postgres.internal:5432/app_production
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Run migrations from one controlled release job:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
RAILS_ENV=production bundle exec rails db:migrate
|
|
43
|
+
RAILS_ENV=production bundle exec rails db:migrate:status
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Do not run `db:migrate` concurrently from every web instance.
|
|
47
|
+
|
|
48
|
+
## Local parity
|
|
49
|
+
|
|
50
|
+
Use a pinned PostgreSQL major version locally and in CI:
|
|
51
|
+
|
|
52
|
+
```sh
|
|
53
|
+
docker run --name lesson-postgres \
|
|
54
|
+
--env POSTGRES_PASSWORD=devpassword \
|
|
55
|
+
--env POSTGRES_DB=app_development \
|
|
56
|
+
--publish 5432:5432 \
|
|
57
|
+
--detach postgres:16
|
|
58
|
+
|
|
59
|
+
DATABASE_URL=postgresql://postgres:devpassword@127.0.0.1:5432/app_development \
|
|
60
|
+
bundle exec rails db:migrate
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The password is for a disposable local container only. Use secret storage in
|
|
64
|
+
CI and production.
|
|
65
|
+
|
|
66
|
+
## Connection pool sizing
|
|
67
|
+
|
|
68
|
+
Each Rails process can open up to its configured pool size. A first estimate is
|
|
69
|
+
`web_processes * pool_size + worker_pools + admin_headroom`, but the database
|
|
70
|
+
provider’s connection limit and actual workload decide the safe value. Measure
|
|
71
|
+
queue time and query latency. A pool setting that is larger than the database
|
|
72
|
+
limit causes timeouts rather than more throughput.
|
|
73
|
+
|
|
74
|
+
For larger deployments, evaluate a PostgreSQL-aware connection pooler and your
|
|
75
|
+
provider’s read-replica strategy. Test transactions, prepared statements,
|
|
76
|
+
failover behavior, and session settings with the pooler before production.
|
|
77
|
+
|
|
78
|
+
## SQL and data features
|
|
79
|
+
|
|
80
|
+
PostgreSQL is appropriate when the application relies on PostgreSQL-specific
|
|
81
|
+
types, functions, extensions, advanced indexing, row-level locking, or complex
|
|
82
|
+
query plans. Keep those choices explicit in migrations and tests. RubyDB’s
|
|
83
|
+
adapter is not a promise that PostgreSQL SQL can run unchanged on RubyDB.
|
|
84
|
+
|
|
85
|
+
If the application starts on RubyDB and moves to PostgreSQL, run reviewed
|
|
86
|
+
migrations on a new PostgreSQL database, export and transform data explicitly,
|
|
87
|
+
compare row counts and business totals, and test the cutover. An environment
|
|
88
|
+
variable changes where new connections go; it does not convert an `.rdb` file.
|
|
89
|
+
|
|
90
|
+
## Checkpoint
|
|
91
|
+
|
|
92
|
+
The checkpoint passes when the production Rails build can create a PostgreSQL
|
|
93
|
+
database, apply migrations once, run the full application test suite, take a
|
|
94
|
+
provider-supported backup, restore a staging copy, and demonstrate the
|
|
95
|
+
connection pool remains below the provider limit. Continue to [lesson 7](07-hybrid-microservices.md)
|
|
96
|
+
for a hybrid design using both databases responsibly.
|