rubydb 0.1.0
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 +7 -0
- data/.gitattributes +20 -0
- data/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- data/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- data/.github/ISSUE_TEMPLATE/performance.yml +0 -0
- data/.github/ISSUE_TEMPLATE/security.yml +0 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +15 -0
- data/.github/dependabot.yml +0 -0
- data/.github/workflows/benchmark.yml +26 -0
- data/.github/workflows/compatibility.yml +63 -0
- data/.github/workflows/fuzz.yml +33 -0
- data/.github/workflows/lint.yml +21 -0
- data/.github/workflows/operations.yml +24 -0
- data/.github/workflows/production-validation.yml +111 -0
- data/.github/workflows/release.yml +77 -0
- data/.github/workflows/security.yml +37 -0
- data/.github/workflows/test.yml +26 -0
- data/.github/workflows/workload.yml +58 -0
- data/.gitignore +43 -0
- data/.rspec +4 -0
- data/.rubocop.yml +44 -0
- data/.ruby-version +1 -0
- data/.standard.yml +14 -0
- data/ARCHITECTURE.md +21 -0
- data/CHANGELOG.md +31 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/CONTRIBUTING.md +29 -0
- data/GOVERNANCE.md +16 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +71 -0
- data/LICENSE +60 -0
- data/README.md +162 -0
- data/ROADMAP.md +27 -0
- data/Rakefile +71 -0
- data/SECURITY.md +54 -0
- data/SUPPORT.md +14 -0
- data/adapters/activerecord/Gemfile +11 -0
- data/adapters/activerecord/README.md +9 -0
- data/adapters/activerecord/lib/active_record/connection_adapters/rubydb_adapter.rb +887 -0
- data/adapters/activerecord/rubydb-activerecord.gemspec +27 -0
- data/adapters/activerecord/spec/rubydb_adapter_integration_spec.rb +151 -0
- data/adapters/ruby/README.md +18 -0
- data/adapters/sequel/README.md +11 -0
- data/bin/rubydb +19 -0
- data/bin/rubydb-server +126 -0
- data/config/development.yml +0 -0
- data/config/monitoring/prometheus-alerts.yml +39 -0
- data/config/production.yml +36 -0
- data/config/test.yml +0 -0
- data/docs/README.md +71 -0
- data/docs/architecture/concurrency.md +14 -0
- data/docs/architecture/current-state.md +125 -0
- data/docs/architecture/execution-engine.md +25 -0
- data/docs/architecture/indexes.md +19 -0
- data/docs/architecture/mvcc.md +19 -0
- data/docs/architecture/overview.md +13 -0
- data/docs/architecture/pages.md +11 -0
- data/docs/architecture/production-roadmap.md +82 -0
- data/docs/architecture/query-planner.md +20 -0
- data/docs/architecture/recovery.md +18 -0
- data/docs/architecture/sql-engine.md +12 -0
- data/docs/architecture/storage-engine.md +14 -0
- data/docs/architecture/transactions.md +10 -0
- data/docs/architecture/wal.md +28 -0
- data/docs/cli-cheatsheet.md +98 -0
- data/docs/cli.md +275 -0
- data/docs/contributing/architecture.md +9 -0
- data/docs/contributing/benchmarking.md +14 -0
- data/docs/contributing/development.md +16 -0
- data/docs/contributing/release-process.md +49 -0
- data/docs/contributing/testing.md +16 -0
- data/docs/debugging.md +229 -0
- data/docs/developer/branching.md +10 -0
- data/docs/developer/database-diff.md +10 -0
- data/docs/developer/local-development.md +17 -0
- data/docs/developer/snapshots.md +9 -0
- data/docs/developer/temporal-data.md +10 -0
- data/docs/developer-guide.md +297 -0
- data/docs/getting-started/first-database.md +16 -0
- data/docs/getting-started/first-query.md +13 -0
- data/docs/getting-started/installation.md +19 -0
- data/docs/getting-started/local-to-production.md +300 -0
- data/docs/getting-started/quickstart.md +17 -0
- data/docs/getting-started/rails.md +16 -0
- data/docs/hardening_backlog.md +93 -0
- data/docs/lessons-learned.md +112 -0
- data/docs/operations/backups.md +33 -0
- data/docs/operations/disaster-recovery.md +31 -0
- data/docs/operations/failover.md +30 -0
- data/docs/operations/monitoring.md +25 -0
- data/docs/operations/production-guide.md +295 -0
- data/docs/operations/production-runbook.md +45 -0
- data/docs/operations/replication.md +33 -0
- data/docs/operations/restore.md +6 -0
- data/docs/operations/runbook.md +34 -0
- data/docs/operations/upgrades.md +14 -0
- data/docs/operations/workload-testing.md +17 -0
- data/docs/production-readiness.md +118 -0
- data/docs/production_validation.md +150 -0
- data/docs/rails/active-record.md +11 -0
- data/docs/rails/compatibility-guide.md +90 -0
- data/docs/rails/database-yml.md +92 -0
- data/docs/rails/installation.md +17 -0
- data/docs/rails/migrations.md +17 -0
- data/docs/rails/production.md +82 -0
- data/docs/rails/troubleshooting.md +18 -0
- data/docs/release.md +59 -0
- data/docs/server/architecture.md +10 -0
- data/docs/server/authentication.md +10 -0
- data/docs/server/configuration.md +16 -0
- data/docs/server/connection-pooling.md +10 -0
- data/docs/server/deployment.md +10 -0
- data/docs/server/protocol.md +12 -0
- data/docs/sql/compatibility-guide.md +82 -0
- data/docs/sql/compatibility.md +39 -0
- data/docs/sql/data-types.md +10 -0
- data/docs/sql/functions.md +9 -0
- data/docs/sql/joins.md +9 -0
- data/docs/sql/operators.md +9 -0
- data/docs/sql/sqlite-compatibility.md +21 -0
- data/docs/sql/syntax.md +10 -0
- data/docs/sql/transactions.md +10 -0
- data/docs/troubleshooting.md +244 -0
- data/exe/rubydb +23 -0
- data/lib/rubydb/backup/archive.rb +334 -0
- data/lib/rubydb/backup/backup.rb +401 -0
- data/lib/rubydb/backup/incremental.rb +353 -0
- data/lib/rubydb/backup/restore.rb +290 -0
- data/lib/rubydb/backup/snapshot.rb +267 -0
- data/lib/rubydb/backup/verification.rb +279 -0
- data/lib/rubydb/branching/branch.rb +181 -0
- data/lib/rubydb/branching/branch_manager.rb +311 -0
- data/lib/rubydb/branching/branch_metadata.rb +140 -0
- data/lib/rubydb/branching/checkout.rb +166 -0
- data/lib/rubydb/branching/copy_on_write.rb +272 -0
- data/lib/rubydb/branching/diff.rb +138 -0
- data/lib/rubydb/branching/merge.rb +285 -0
- data/lib/rubydb/build_info.rb +15 -0
- data/lib/rubydb/catalog/catalog.rb +391 -0
- data/lib/rubydb/catalog/column.rb +112 -0
- data/lib/rubydb/catalog/constraint.rb +180 -0
- data/lib/rubydb/catalog/database.rb +184 -0
- data/lib/rubydb/catalog/index.rb +97 -0
- data/lib/rubydb/catalog/schema.rb +103 -0
- data/lib/rubydb/catalog/sequence.rb +90 -0
- data/lib/rubydb/catalog/system_catalog.rb +698 -0
- data/lib/rubydb/catalog/table.rb +178 -0
- data/lib/rubydb/catalog/trigger.rb +102 -0
- data/lib/rubydb/catalog/view.rb +66 -0
- data/lib/rubydb/cli/application.rb +163 -0
- data/lib/rubydb/cli/commands/backup.rb +81 -0
- data/lib/rubydb/cli/commands/branch.rb +72 -0
- data/lib/rubydb/cli/commands/checkout.rb +54 -0
- data/lib/rubydb/cli/commands/create.rb +58 -0
- data/lib/rubydb/cli/commands/diff.rb +77 -0
- data/lib/rubydb/cli/commands/doctor.rb +74 -0
- data/lib/rubydb/cli/commands/drop.rb +57 -0
- data/lib/rubydb/cli/commands/init.rb +102 -0
- data/lib/rubydb/cli/commands/inspect.rb +95 -0
- data/lib/rubydb/cli/commands/merge.rb +63 -0
- data/lib/rubydb/cli/commands/migrate.rb +62 -0
- data/lib/rubydb/cli/commands/restart.rb +39 -0
- data/lib/rubydb/cli/commands/restore.rb +121 -0
- data/lib/rubydb/cli/commands/shell.rb +365 -0
- data/lib/rubydb/cli/commands/snapshot.rb +79 -0
- data/lib/rubydb/cli/commands/start.rb +82 -0
- data/lib/rubydb/cli/commands/status.rb +92 -0
- data/lib/rubydb/cli/commands/stop.rb +47 -0
- data/lib/rubydb/cli/commands/vacuum.rb +58 -0
- data/lib/rubydb/cli/formatter.rb +221 -0
- data/lib/rubydb/cli/output.rb +168 -0
- data/lib/rubydb/client/client.rb +304 -0
- data/lib/rubydb/client/connection.rb +415 -0
- data/lib/rubydb/client/connection_pool.rb +168 -0
- data/lib/rubydb/client/connection_url.rb +96 -0
- data/lib/rubydb/client/prepared_statement.rb +60 -0
- data/lib/rubydb/client/result.rb +123 -0
- data/lib/rubydb/client/statement.rb +52 -0
- data/lib/rubydb/client/transaction.rb +130 -0
- data/lib/rubydb/concurrency/concurrency.rb +19 -0
- data/lib/rubydb/concurrency/deadlock_detector.rb +150 -0
- data/lib/rubydb/concurrency/latch.rb +101 -0
- data/lib/rubydb/concurrency/lock_graph.rb +165 -0
- data/lib/rubydb/concurrency/mutex.rb +183 -0
- data/lib/rubydb/concurrency/rw_lock.rb +180 -0
- data/lib/rubydb/concurrency/scheduler.rb +250 -0
- data/lib/rubydb/concurrency/worker_pool.rb +143 -0
- data/lib/rubydb/configuration/config.rb +170 -0
- data/lib/rubydb/configuration/defaults.rb +179 -0
- data/lib/rubydb/configuration/environment.rb +152 -0
- data/lib/rubydb/configuration/parser.rb +185 -0
- data/lib/rubydb/configuration/validation.rb +221 -0
- data/lib/rubydb/constants.rb +74 -0
- data/lib/rubydb/constraints/check.rb +181 -0
- data/lib/rubydb/constraints/constraint.rb +101 -0
- data/lib/rubydb/constraints/foreign_key.rb +130 -0
- data/lib/rubydb/constraints/not_null.rb +64 -0
- data/lib/rubydb/constraints/primary_key.rb +99 -0
- data/lib/rubydb/constraints/unique.rb +108 -0
- data/lib/rubydb/constraints/validator.rb +350 -0
- data/lib/rubydb/errors/authentication_error.rb +10 -0
- data/lib/rubydb/errors/authorization_error.rb +23 -0
- data/lib/rubydb/errors/client_error.rb +10 -0
- data/lib/rubydb/errors/configuration_error.rb +10 -0
- data/lib/rubydb/errors/connection_error.rb +10 -0
- data/lib/rubydb/errors/constraint_error.rb +23 -0
- data/lib/rubydb/errors/corruption_error.rb +10 -0
- data/lib/rubydb/errors/database_error.rb +10 -0
- data/lib/rubydb/errors/error.rb +20 -0
- data/lib/rubydb/errors/execution_error.rb +10 -0
- data/lib/rubydb/errors/parser_error.rb +10 -0
- data/lib/rubydb/errors/recovery_error.rb +10 -0
- data/lib/rubydb/errors/replication_error.rb +10 -0
- data/lib/rubydb/errors/server_error.rb +6 -0
- data/lib/rubydb/errors/storage_error.rb +10 -0
- data/lib/rubydb/errors/transaction_error.rb +10 -0
- data/lib/rubydb/execution/aggregate_executor.rb +138 -0
- data/lib/rubydb/execution/delete_executor.rb +112 -0
- data/lib/rubydb/execution/distinct_executor.rb +135 -0
- data/lib/rubydb/execution/executor.rb +1192 -0
- data/lib/rubydb/execution/expression.rb +193 -0
- data/lib/rubydb/execution/index_scan.rb +142 -0
- data/lib/rubydb/execution/insert_executor.rb +217 -0
- data/lib/rubydb/execution/join_executor.rb +249 -0
- data/lib/rubydb/execution/limit_executor.rb +85 -0
- data/lib/rubydb/execution/optimizer.rb +215 -0
- data/lib/rubydb/execution/plan.rb +361 -0
- data/lib/rubydb/execution/planner.rb +546 -0
- data/lib/rubydb/execution/predicate.rb +235 -0
- data/lib/rubydb/execution/scan.rb +49 -0
- data/lib/rubydb/execution/sequential_scan.rb +63 -0
- data/lib/rubydb/execution/sort_executor.rb +185 -0
- data/lib/rubydb/execution/update_executor.rb +162 -0
- data/lib/rubydb/functions/aggregate.rb +70 -0
- data/lib/rubydb/functions/date_functions.rb +278 -0
- data/lib/rubydb/functions/function.rb +85 -0
- data/lib/rubydb/functions/json_functions.rb +215 -0
- data/lib/rubydb/functions/numeric_functions.rb +346 -0
- data/lib/rubydb/functions/scalar.rb +52 -0
- data/lib/rubydb/functions/string_functions.rb +383 -0
- data/lib/rubydb/functions/system_functions.rb +246 -0
- data/lib/rubydb/history/as_of.rb +238 -0
- data/lib/rubydb/history/change.rb +105 -0
- data/lib/rubydb/history/history.rb +131 -0
- data/lib/rubydb/history/history_manager.rb +229 -0
- data/lib/rubydb/history/temporal_query.rb +202 -0
- data/lib/rubydb/history/timeline.rb +144 -0
- data/lib/rubydb/indexes/btree.rb +186 -0
- data/lib/rubydb/indexes/btree_cursor.rb +258 -0
- data/lib/rubydb/indexes/btree_node.rb +385 -0
- data/lib/rubydb/indexes/hash_index.rb +150 -0
- data/lib/rubydb/indexes/index.rb +71 -0
- data/lib/rubydb/indexes/index_manager.rb +406 -0
- data/lib/rubydb/indexes/index_scan.rb +470 -0
- data/lib/rubydb/migrations/migration.rb +254 -0
- data/lib/rubydb/migrations/migration_lock.rb +146 -0
- data/lib/rubydb/migrations/migration_manager.rb +176 -0
- data/lib/rubydb/migrations/migration_version.rb +71 -0
- data/lib/rubydb/migrations/schema_diff.rb +211 -0
- data/lib/rubydb/migrations/schema_version.rb +64 -0
- data/lib/rubydb/monitoring/events.rb +160 -0
- data/lib/rubydb/monitoring/health.rb +222 -0
- data/lib/rubydb/monitoring/logger.rb +193 -0
- data/lib/rubydb/monitoring/metrics.rb +359 -0
- data/lib/rubydb/monitoring/performance.rb +176 -0
- data/lib/rubydb/monitoring/statistics.rb +170 -0
- data/lib/rubydb/mvcc/garbage_collector.rb +199 -0
- data/lib/rubydb/mvcc/mvcc.rb +16 -0
- data/lib/rubydb/mvcc/snapshot.rb +147 -0
- data/lib/rubydb/mvcc/vacuum.rb +180 -0
- data/lib/rubydb/mvcc/version.rb +106 -0
- data/lib/rubydb/mvcc/version_store.rb +398 -0
- data/lib/rubydb/mvcc/visibility.rb +109 -0
- data/lib/rubydb/protocol/capabilities.rb +125 -0
- data/lib/rubydb/protocol/decoder.rb +145 -0
- data/lib/rubydb/protocol/encoder.rb +136 -0
- data/lib/rubydb/protocol/handshake.rb +305 -0
- data/lib/rubydb/protocol/message.rb +121 -0
- data/lib/rubydb/protocol/protocol.rb +277 -0
- data/lib/rubydb/protocol/version.rb +54 -0
- data/lib/rubydb/rails/adapter.rb +239 -0
- data/lib/rubydb/rails/connection.rb +314 -0
- data/lib/rubydb/rails/database_statements.rb +122 -0
- data/lib/rubydb/rails/migration.rb +131 -0
- data/lib/rubydb/rails/quoting.rb +109 -0
- data/lib/rubydb/rails/result.rb +120 -0
- data/lib/rubydb/rails/schema_statements.rb +339 -0
- data/lib/rubydb/rails/transaction.rb +105 -0
- data/lib/rubydb/rails/type.rb +126 -0
- data/lib/rubydb/recovery/checkpoint.rb +257 -0
- data/lib/rubydb/recovery/consistency.rb +467 -0
- data/lib/rubydb/recovery/corruption_detector.rb +5 -0
- data/lib/rubydb/recovery/crash_recovery.rb +387 -0
- data/lib/rubydb/recovery/recovery_manager.rb +206 -0
- data/lib/rubydb/recovery/redo.rb +237 -0
- data/lib/rubydb/recovery/undo.rb +206 -0
- data/lib/rubydb/replication/failover.rb +5 -0
- data/lib/rubydb/replication/fencing.rb +63 -0
- data/lib/rubydb/replication/primary.rb +450 -0
- data/lib/rubydb/replication/replica.rb +384 -0
- data/lib/rubydb/replication/replication_log.rb +200 -0
- data/lib/rubydb/replication/replication_manager.rb +308 -0
- data/lib/rubydb/replication/replication_slot.rb +295 -0
- data/lib/rubydb/replication/replication_stream.rb +201 -0
- data/lib/rubydb/rubydb.rb +560 -0
- data/lib/rubydb/security/access_control.rb +254 -0
- data/lib/rubydb/security/audit_log.rb +213 -0
- data/lib/rubydb/security/authentication.rb +302 -0
- data/lib/rubydb/security/authorization.rb +282 -0
- data/lib/rubydb/security/credentials.rb +196 -0
- data/lib/rubydb/security/password.rb +215 -0
- data/lib/rubydb/security/permissions.rb +74 -0
- data/lib/rubydb/security/role.rb +101 -0
- data/lib/rubydb/security/user.rb +86 -0
- data/lib/rubydb/server/connection.rb +366 -0
- data/lib/rubydb/server/connection_pool.rb +193 -0
- data/lib/rubydb/server/lifecycle.rb +228 -0
- data/lib/rubydb/server/listener.rb +136 -0
- data/lib/rubydb/server/request_handler.rb +276 -0
- data/lib/rubydb/server/server.rb +364 -0
- data/lib/rubydb/server/session.rb +369 -0
- data/lib/rubydb/server/worker.rb +210 -0
- data/lib/rubydb/server/worker_pool.rb +168 -0
- data/lib/rubydb/sql/ast/alter_table.rb +169 -0
- data/lib/rubydb/sql/ast/begin_transaction.rb +47 -0
- data/lib/rubydb/sql/ast/commit.rb +37 -0
- data/lib/rubydb/sql/ast/constraint.rb +83 -0
- data/lib/rubydb/sql/ast/create_database.rb +41 -0
- data/lib/rubydb/sql/ast/create_index.rb +61 -0
- data/lib/rubydb/sql/ast/create_schema.rb +52 -0
- data/lib/rubydb/sql/ast/create_table.rb +187 -0
- data/lib/rubydb/sql/ast/delete.rb +54 -0
- data/lib/rubydb/sql/ast/drop_database.rb +41 -0
- data/lib/rubydb/sql/ast/drop_index.rb +41 -0
- data/lib/rubydb/sql/ast/drop_schema.rb +49 -0
- data/lib/rubydb/sql/ast/drop_table.rb +49 -0
- data/lib/rubydb/sql/ast/explain.rb +64 -0
- data/lib/rubydb/sql/ast/expression.rb +604 -0
- data/lib/rubydb/sql/ast/insert.rb +82 -0
- data/lib/rubydb/sql/ast/node.rb +42 -0
- data/lib/rubydb/sql/ast/rollback.rb +63 -0
- data/lib/rubydb/sql/ast/savepoint.rb +59 -0
- data/lib/rubydb/sql/ast/select.rb +88 -0
- data/lib/rubydb/sql/ast/set_operation.rb +20 -0
- data/lib/rubydb/sql/ast/trigger.rb +29 -0
- data/lib/rubydb/sql/ast/update.rb +88 -0
- data/lib/rubydb/sql/ast/vacuum.rb +19 -0
- data/lib/rubydb/sql/ast/view.rb +32 -0
- data/lib/rubydb/sql/ast/with.rb +32 -0
- data/lib/rubydb/sql/grammar.rb +86 -0
- data/lib/rubydb/sql/keywords.rb +156 -0
- data/lib/rubydb/sql/lexer.rb +214 -0
- data/lib/rubydb/sql/operators.rb +100 -0
- data/lib/rubydb/sql/parser.rb +1185 -0
- data/lib/rubydb/sql/planner/analyzer.rb +302 -0
- data/lib/rubydb/sql/planner/binder.rb +550 -0
- data/lib/rubydb/sql/planner/type_checker.rb +431 -0
- data/lib/rubydb/sql/token.rb +210 -0
- data/lib/rubydb/storage/buffer_frame.rb +44 -0
- data/lib/rubydb/storage/buffer_pool.rb +155 -0
- data/lib/rubydb/storage/database_lock.rb +74 -0
- data/lib/rubydb/storage/deserializer.rb +342 -0
- data/lib/rubydb/storage/engine.rb +2338 -0
- data/lib/rubydb/storage/file_manager.rb +187 -0
- data/lib/rubydb/storage/free_space_map.rb +81 -0
- data/lib/rubydb/storage/page.rb +94 -0
- data/lib/rubydb/storage/page_allocator.rb +855 -0
- data/lib/rubydb/storage/page_header.rb +67 -0
- data/lib/rubydb/storage/page_manager.rb +131 -0
- data/lib/rubydb/storage/record.rb +58 -0
- data/lib/rubydb/storage/row.rb +78 -0
- data/lib/rubydb/storage/serializer.rb +51 -0
- data/lib/rubydb/storage/storage_layout.rb +151 -0
- data/lib/rubydb/storage/storage_manager.rb +114 -0
- data/lib/rubydb/storage/tuple.rb +461 -0
- data/lib/rubydb/storage/visibility_map.rb +984 -0
- data/lib/rubydb/transactions/commit_manager.rb +220 -0
- data/lib/rubydb/transactions/isolation.rb +98 -0
- data/lib/rubydb/transactions/lock.rb +76 -0
- data/lib/rubydb/transactions/lock_manager.rb +362 -0
- data/lib/rubydb/transactions/savepoint.rb +143 -0
- data/lib/rubydb/transactions/transaction.rb +215 -0
- data/lib/rubydb/transactions/transaction_id.rb +84 -0
- data/lib/rubydb/transactions/transaction_log.rb +257 -0
- data/lib/rubydb/transactions/transaction_manager.rb +435 -0
- data/lib/rubydb/types/bigint.rb +36 -0
- data/lib/rubydb/types/blob.rb +37 -0
- data/lib/rubydb/types/boolean.rb +34 -0
- data/lib/rubydb/types/date.rb +39 -0
- data/lib/rubydb/types/decimal.rb +48 -0
- data/lib/rubydb/types/float.rb +34 -0
- data/lib/rubydb/types/integer.rb +36 -0
- data/lib/rubydb/types/json.rb +41 -0
- data/lib/rubydb/types/null.rb +34 -0
- data/lib/rubydb/types/smallint.rb +36 -0
- data/lib/rubydb/types/text.rb +37 -0
- data/lib/rubydb/types/time.rb +46 -0
- data/lib/rubydb/types/timestamp.rb +39 -0
- data/lib/rubydb/types/type.rb +119 -0
- data/lib/rubydb/types/uuid.rb +47 -0
- data/lib/rubydb/types/varchar.rb +37 -0
- data/lib/rubydb/version.rb +33 -0
- data/lib/rubydb/wal/archive.rb +193 -0
- data/lib/rubydb/wal/checkpoint.rb +183 -0
- data/lib/rubydb/wal/lsn.rb +94 -0
- data/lib/rubydb/wal/reader.rb +260 -0
- data/lib/rubydb/wal/record.rb +105 -0
- data/lib/rubydb/wal/segment.rb +193 -0
- data/lib/rubydb/wal/wal.rb +452 -0
- data/lib/rubydb/wal/writer.rb +236 -0
- data/lib/rubydb.rb +7 -0
- data/packaging/docker/Dockerfile +36 -0
- data/packaging/docker/docker-compose.failover.yml +43 -0
- data/packaging/docker/entrypoint.sh +15 -0
- data/packaging/homebrew/rubydb.rb +19 -0
- data/packaging/systemd/rubydb.service +22 -0
- data/rubydb.gemspec +57 -0
- data/scripts/benchmark +7 -0
- data/scripts/durability_drill +37 -0
- data/scripts/format +0 -0
- data/scripts/fuzz +63 -0
- data/scripts/lint +0 -0
- data/scripts/release +50 -0
- data/scripts/release_check +43 -0
- data/scripts/replication_failover_drill +250 -0
- data/scripts/replication_network_failover_drill +255 -0
- data/scripts/restore_drill +45 -0
- data/scripts/security +0 -0
- data/scripts/setup +0 -0
- data/scripts/test +0 -0
- data/scripts/test-chaos +0 -0
- data/scripts/test-crash +0 -0
- data/scripts/test-integration +0 -0
- data/scripts/test-unit +0 -0
- data/visibility_map.json +1 -0
- metadata +538 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# First query
|
|
2
|
+
|
|
3
|
+
RubyDB executes the documented SQL subset through its lexer, parser, planner,
|
|
4
|
+
and executor:
|
|
5
|
+
|
|
6
|
+
```ruby
|
|
7
|
+
rows = engine.execute("SELECT id, email FROM accounts WHERE id = 1 ORDER BY id LIMIT 10")
|
|
8
|
+
puts rows.inspect
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Use the Rails connection or client binding APIs for external values. Do not
|
|
12
|
+
interpolate untrusted input into SQL. See [SQL compatibility](../sql/compatibility.md)
|
|
13
|
+
for supported statements and explicit boundaries.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Installation
|
|
2
|
+
|
|
3
|
+
RubyDB requires Ruby 3.3 or newer in the current support policy.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
gem install rubydb --pre
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
From a checkout:
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
bundle install
|
|
13
|
+
bundle exec rspec
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
For Rails, install the adapter from `adapters/activerecord` and use the
|
|
17
|
+
configuration documented in `docs/rails/database-yml.md`. Pin RubyDB and Ruby
|
|
18
|
+
versions in production and test the exact gem on a restored staging backup
|
|
19
|
+
before deployment.
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# From local development to production
|
|
2
|
+
|
|
3
|
+
This guide is for a beginner starting with a Ruby or Rails application. It
|
|
4
|
+
shows two valid deployment choices:
|
|
5
|
+
|
|
6
|
+
1. develop with RubyDB locally and deploy with PostgreSQL; or
|
|
7
|
+
2. develop with RubyDB locally and keep RubyDB in production.
|
|
8
|
+
|
|
9
|
+
The application code can be similar in both cases, but the database is not
|
|
10
|
+
interchangeable by copying files. Choose the production database before
|
|
11
|
+
launch, run the application’s migrations and query tests against it, and make
|
|
12
|
+
backups before importing important data.
|
|
13
|
+
|
|
14
|
+
## Which path should I choose?
|
|
15
|
+
|
|
16
|
+
| Goal | Local database | Production database | Recommended when |
|
|
17
|
+
| --- | --- | --- | --- |
|
|
18
|
+
| Learn/build quickly | RubyDB embedded | PostgreSQL | You want the broadest hosting and Rails ecosystem |
|
|
19
|
+
| Keep one Ruby-native database | RubyDB embedded | RubyDB server | Your SQL/workload fits RubyDB and you can operate the server |
|
|
20
|
+
| Prototype only | RubyDB embedded | RubyDB embedded | One process owns the path and data is non-critical |
|
|
21
|
+
|
|
22
|
+
For a public or business-critical application, PostgreSQL is the safer default
|
|
23
|
+
until RubyDB has been validated against your complete workload, backup/restore
|
|
24
|
+
process, concurrency, and failover requirements. RubyDB is not a complete
|
|
25
|
+
PostgreSQL or SQLite replacement.
|
|
26
|
+
|
|
27
|
+
## Part 1: local Rails development with RubyDB
|
|
28
|
+
|
|
29
|
+
### 1. Add the gems
|
|
30
|
+
|
|
31
|
+
In the Rails application `Gemfile`:
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
gem "rubydb"
|
|
35
|
+
gem "rubydb-activerecord"
|
|
36
|
+
gem "pg" # Keep this if PostgreSQL is a possible production target.
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Run:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
bundle install
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 2. Configure development and test
|
|
46
|
+
|
|
47
|
+
Use an embedded path for local development. Only the Rails process should own
|
|
48
|
+
each path:
|
|
49
|
+
|
|
50
|
+
```yaml
|
|
51
|
+
default: &default
|
|
52
|
+
adapter: rubydb
|
|
53
|
+
embedded: true
|
|
54
|
+
database: <%= Rails.root.join("tmp/rubydb_development.rdb") %>
|
|
55
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
56
|
+
|
|
57
|
+
development:
|
|
58
|
+
<<: *default
|
|
59
|
+
|
|
60
|
+
test:
|
|
61
|
+
<<: *default
|
|
62
|
+
database: <%= Rails.root.join("tmp/rubydb_test.rdb") %>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Use separate development and test paths. Do not put the database under a
|
|
66
|
+
source-controlled directory. Do not let a web server, job worker, and console
|
|
67
|
+
open the same embedded path at the same time.
|
|
68
|
+
|
|
69
|
+
### 3. Create the schema and run the app
|
|
70
|
+
|
|
71
|
+
```sh
|
|
72
|
+
bin/rails db:create
|
|
73
|
+
bin/rails db:migrate
|
|
74
|
+
bin/rails db:seed # only if your seed data is safe to recreate
|
|
75
|
+
bin/rails test
|
|
76
|
+
bin/rails server
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Exercise real application flows: sign-up, login, CRUD, joins, eager loading,
|
|
80
|
+
background jobs, file metadata, transactions, and error handling. Do not test
|
|
81
|
+
only that the server boots.
|
|
82
|
+
|
|
83
|
+
### 4. Use RubyDB in a regular Ruby app
|
|
84
|
+
|
|
85
|
+
Local single-process code can use the embedded engine:
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
require "rubydb"
|
|
89
|
+
|
|
90
|
+
engine = RubyDB::Storage::Engine.new("tmp/app.rdb")
|
|
91
|
+
begin
|
|
92
|
+
engine.execute("CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY, name TEXT)")
|
|
93
|
+
engine.execute("INSERT INTO events (name) VALUES (?)", ["boot"])
|
|
94
|
+
p engine.execute("SELECT * FROM events")
|
|
95
|
+
ensure
|
|
96
|
+
engine.close
|
|
97
|
+
end
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
For multiple processes, use the client/server connection described below.
|
|
101
|
+
|
|
102
|
+
## Part 2A: deploy the same app on PostgreSQL
|
|
103
|
+
|
|
104
|
+
This path keeps RubyDB for local development and uses PostgreSQL in production.
|
|
105
|
+
It is a database migration, not a file copy.
|
|
106
|
+
|
|
107
|
+
### 1. Configure the production Rails block
|
|
108
|
+
|
|
109
|
+
```yaml
|
|
110
|
+
production:
|
|
111
|
+
adapter: postgresql
|
|
112
|
+
url: <%= ENV.fetch("DATABASE_URL") %>
|
|
113
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The hosting provider supplies a PostgreSQL URL such as:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
postgresql://app_user:password@postgres.example.com:5432/my_app
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Keep `DATABASE_URL` in the provider’s secret settings. Never commit it.
|
|
123
|
+
|
|
124
|
+
### 2. Review the schema and SQL
|
|
125
|
+
|
|
126
|
+
Before switching, review RubyDB-specific behavior:
|
|
127
|
+
|
|
128
|
+
* integer primary-key/autoincrement behavior;
|
|
129
|
+
* `BOOLEAN`, `JSON`, `UUID`, dates, timestamps, and decimals;
|
|
130
|
+
* indexes, foreign keys, check constraints, and defaults;
|
|
131
|
+
* `NULL`, ordering, grouping, joins, upserts, and functions;
|
|
132
|
+
* migrations that alter populated tables; and
|
|
133
|
+
* raw SQL, extensions, pragmas, and database-specific functions.
|
|
134
|
+
|
|
135
|
+
Run `db:schema:dump` and inspect the generated schema. Do not assume every
|
|
136
|
+
RubyDB schema statement is valid PostgreSQL syntax. Fix migrations or use
|
|
137
|
+
adapter-specific migrations deliberately and document the difference.
|
|
138
|
+
|
|
139
|
+
### 3. Transfer data safely
|
|
140
|
+
|
|
141
|
+
RubyDB `.rdb` files cannot be opened by PostgreSQL. A safe small-dataset flow is:
|
|
142
|
+
|
|
143
|
+
1. stop writes to the RubyDB application;
|
|
144
|
+
2. create a verified RubyDB backup;
|
|
145
|
+
3. create the PostgreSQL database and run reviewed migrations;
|
|
146
|
+
4. export each table in a deterministic order, including parent tables first;
|
|
147
|
+
5. transform types and IDs explicitly;
|
|
148
|
+
6. import into PostgreSQL using bound/escaped data or PostgreSQL `COPY`;
|
|
149
|
+
7. compare row counts, checksums, foreign keys, indexes, and business totals;
|
|
150
|
+
8. run the application test and smoke suite against PostgreSQL; and
|
|
151
|
+
9. switch traffic only after the verification report is approved.
|
|
152
|
+
|
|
153
|
+
For large or live data, use a purpose-built migration process with checkpoints,
|
|
154
|
+
retries, idempotency, and a cutover plan. Do not write a one-off script that
|
|
155
|
+
silently skips a row or converts an unknown type to text.
|
|
156
|
+
|
|
157
|
+
### 4. Test production configuration before cutover
|
|
158
|
+
|
|
159
|
+
```sh
|
|
160
|
+
RAILS_ENV=production DATABASE_URL='postgresql://...' bin/rails db:migrate
|
|
161
|
+
RAILS_ENV=production DATABASE_URL='postgresql://...' bin/rails db:seed
|
|
162
|
+
RAILS_ENV=production DATABASE_URL='postgresql://...' bin/rails test
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Run this against a restored staging database, not the only production copy.
|
|
166
|
+
Keep the RubyDB backup until PostgreSQL row counts and application behavior
|
|
167
|
+
have been accepted.
|
|
168
|
+
|
|
169
|
+
## Part 2B: deploy RubyDB in production
|
|
170
|
+
|
|
171
|
+
This path runs one managed RubyDB server and connects Rails/Ruby processes to it
|
|
172
|
+
over the RubyDB protocol. The application never opens the server’s data file.
|
|
173
|
+
|
|
174
|
+
### 1. Provision a database host
|
|
175
|
+
|
|
176
|
+
Install a pinned RubyDB release, create a dedicated service account, and attach
|
|
177
|
+
persistent storage. The data directory must survive process restarts and
|
|
178
|
+
deploys. Keep backups on a separate system or failure domain.
|
|
179
|
+
|
|
180
|
+
```sh
|
|
181
|
+
gem install rubydb -v 0.1.0
|
|
182
|
+
install -d -o rubydb -g rubydb -m 0700 /var/lib/rubydb/data
|
|
183
|
+
install -d -o rubydb -g rubydb -m 0750 /var/log/rubydb
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Use the complete setup in the [production operations guide](../operations/production-guide.md)
|
|
187
|
+
for service supervision, TLS, authentication, resource limits, backups, and
|
|
188
|
+
monitoring.
|
|
189
|
+
|
|
190
|
+
### 2. Start the RubyDB server
|
|
191
|
+
|
|
192
|
+
Use a reviewed production configuration with WAL, authentication, TLS, and
|
|
193
|
+
bounded resources:
|
|
194
|
+
|
|
195
|
+
```sh
|
|
196
|
+
rubydb --config /etc/rubydb/production.yml --env production start
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Keep port `7432` on a private network. Verify the server before connecting the
|
|
200
|
+
application:
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
rubydb --config /etc/rubydb/production.yml --env production status --json
|
|
204
|
+
rubydb --config /etc/rubydb/production.yml --env production doctor --json
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### 3. Put one RubyDB URL in the application environment
|
|
208
|
+
|
|
209
|
+
RubyDB supports its own URL format. `rubydbs` enables TLS:
|
|
210
|
+
|
|
211
|
+
```text
|
|
212
|
+
RUBYDB_URL=rubydbs://app_user:URL_ENCODED_PASSWORD@db.internal.example:7432/my_app?verify_peer=true&ca_file=%2Fetc%2Frubydb%2Fca.crt
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Special characters in usernames and passwords must be percent-encoded. Store
|
|
216
|
+
the complete value in a secret manager. Do not print it during deploys.
|
|
217
|
+
|
|
218
|
+
### 4. Configure Rails
|
|
219
|
+
|
|
220
|
+
```yaml
|
|
221
|
+
production:
|
|
222
|
+
adapter: rubydb
|
|
223
|
+
embedded: false
|
|
224
|
+
url: <%= ENV.fetch("RUBYDB_URL") %>
|
|
225
|
+
pool: <%= ENV.fetch("RAILS_MAX_THREADS", "5") %>
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Run the migration once from a controlled release job:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
RAILS_ENV=production bundle exec rails db:migrate
|
|
232
|
+
RAILS_ENV=production bundle exec rails runner 'puts User.count'
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
Do not run migrations simultaneously from every web process. Confirm that the
|
|
236
|
+
Rails pool fits below the RubyDB server connection limit with room for workers,
|
|
237
|
+
monitoring, and administration.
|
|
238
|
+
|
|
239
|
+
### 5. Configure regular Ruby
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
require "rubydb"
|
|
243
|
+
|
|
244
|
+
client = RubyDB::Client::Client.new(url: ENV.fetch("RUBYDB_URL"))
|
|
245
|
+
begin
|
|
246
|
+
result = client.query("SELECT 1")
|
|
247
|
+
puts result.to_hash
|
|
248
|
+
ensure
|
|
249
|
+
client.disconnect
|
|
250
|
+
end
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Use idempotency keys for retried writes. A timeout or broken connection does
|
|
254
|
+
not prove that a write was rolled back; check the operation outcome before
|
|
255
|
+
retrying a non-idempotent request.
|
|
256
|
+
|
|
257
|
+
### 6. Perform a production smoke test
|
|
258
|
+
|
|
259
|
+
Before routing users:
|
|
260
|
+
|
|
261
|
+
```sh
|
|
262
|
+
RAILS_ENV=production bundle exec rails db:migrate:status
|
|
263
|
+
RAILS_ENV=production bundle exec rails runner 'puts User.limit(1).to_a.inspect'
|
|
264
|
+
rubydb --config /etc/rubydb/production.yml --env production doctor --json
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Then test one authenticated read, one create/update/delete transaction, one
|
|
268
|
+
background job, one backup, and one restore into a separate directory. Confirm
|
|
269
|
+
logs, metrics, disk alerts, WAL/checkpoint state, and rollback ownership.
|
|
270
|
+
|
|
271
|
+
## Render-style cloud deployment
|
|
272
|
+
|
|
273
|
+
On a platform such as Render, use a private service for the RubyDB server and a
|
|
274
|
+
web service for Rails. Attach a persistent disk to the RubyDB service and set
|
|
275
|
+
`RUBYDB_URL` on the Rails service. Keep both services in the same region and
|
|
276
|
+
use the private hostname. A platform’s default filesystem is often ephemeral;
|
|
277
|
+
verify the provider’s storage behavior before using it for database files.
|
|
278
|
+
|
|
279
|
+
A single persistent disk normally means one RubyDB primary instance. Do not
|
|
280
|
+
assume that increasing the web-service instance count creates database
|
|
281
|
+
high availability. Maintain external backups and validate failover separately.
|
|
282
|
+
|
|
283
|
+
## Final beginner checklist
|
|
284
|
+
|
|
285
|
+
Before calling the app production-ready, confirm:
|
|
286
|
+
|
|
287
|
+
* the production database choice is written down;
|
|
288
|
+
* all application SQL and migrations pass against that database;
|
|
289
|
+
* credentials are environment/secret-manager values, not source files;
|
|
290
|
+
* RubyDB uses server mode when more than one process connects;
|
|
291
|
+
* TLS, authentication, private networking, and least privilege are enabled;
|
|
292
|
+
* a verified backup can be restored on another directory or host;
|
|
293
|
+
* monitoring and alerts are visible to an on-call owner;
|
|
294
|
+
* connection, lock, request, and shutdown timeouts are bounded; and
|
|
295
|
+
* rollback, data migration, and incident procedures have been rehearsed.
|
|
296
|
+
|
|
297
|
+
For deeper details, continue with [Rails compatibility](../rails/compatibility-guide.md),
|
|
298
|
+
[production operations](../operations/production-guide.md),
|
|
299
|
+
[troubleshooting](../troubleshooting.md), and [debugging](../debugging.md).
|
|
300
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Quick start
|
|
2
|
+
|
|
3
|
+
Create a local database in one owning Ruby process:
|
|
4
|
+
|
|
5
|
+
```ruby
|
|
6
|
+
require "rubydb"
|
|
7
|
+
|
|
8
|
+
engine = RubyDB::Storage::Engine.new("tmp/quickstart.rdb")
|
|
9
|
+
engine.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL)")
|
|
10
|
+
engine.execute("INSERT INTO users (id, name) VALUES (1, 'Aldane')")
|
|
11
|
+
p engine.execute("SELECT * FROM users")
|
|
12
|
+
engine.close
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
For a full walkthrough see [first database](first-database.md) and
|
|
16
|
+
[first query](first-query.md). For multiple processes, use the server/client
|
|
17
|
+
topology and do not share an embedded path.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Rails quick start
|
|
2
|
+
|
|
3
|
+
The runnable application under `examples/rails_app` demonstrates a real Rails
|
|
4
|
+
migration, model, query, and server:
|
|
5
|
+
|
|
6
|
+
```sh
|
|
7
|
+
cd examples/rails_app
|
|
8
|
+
bundle install
|
|
9
|
+
bundle exec ruby bin/rails db:migrate
|
|
10
|
+
bundle exec ruby bin/rails server -b 127.0.0.1 -p 3001
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Use the ActiveRecord adapter only within its tested feature surface. For a
|
|
14
|
+
production Rails deployment, use server mode for multiple processes, protect
|
|
15
|
+
TLS/authentication secrets, back up before migrations, and run the exact Rails
|
|
16
|
+
matrix and populated-schema checks described in `docs/rails/production.md`.
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Remaining production work
|
|
2
|
+
|
|
3
|
+
Completed checkpoints: exclusive embedded engine ownership, duplicate-open
|
|
4
|
+
rejection, release after process exit, initialization failure cleanup, fail-closed
|
|
5
|
+
metadata and recovery startup, atomic metadata replacement, and deterministic
|
|
6
|
+
background-maintenance shutdown.
|
|
7
|
+
|
|
8
|
+
The following work remains open; passing the regression suite does not certify
|
|
9
|
+
these capabilities.
|
|
10
|
+
|
|
11
|
+
1. Server concurrency: cancellation and sustained load with latency percentiles.
|
|
12
|
+
Client deadlines are checked before execution and propagated into the query
|
|
13
|
+
executor, which checks during long read phases. Engine transaction state is
|
|
14
|
+
now scoped per client connection thread, and concurrent commit/rollback
|
|
15
|
+
behavior is covered. Multi-process durability is covered; wire-level
|
|
16
|
+
cancellation is now request-scoped and cooperative: a client can send
|
|
17
|
+
a cancel frame while the connection reader remains active, and the server
|
|
18
|
+
acknowledges it only for the active request. Mixed transaction reference-log
|
|
19
|
+
and restart validation remain open.
|
|
20
|
+
2. Persistence: fault-test disk-full and interrupted checkpoints/schema changes;
|
|
21
|
+
index metadata load and write errors now fail visibly, and failed schema
|
|
22
|
+
publications roll back in-memory state. Metadata and index catalogs are
|
|
23
|
+
published through unique temporary files with flush/fsync/atomic rename.
|
|
24
|
+
Commit acknowledgements now expose durable versus uncertain WAL state and
|
|
25
|
+
recovery-required post-WAL flush failures. Storage accepts an explicit
|
|
26
|
+
`io_fault_injector` hook for page writes, file extension/truncation, and
|
|
27
|
+
sync operations; those paths now verify typed failures and descriptor
|
|
28
|
+
cleanup. Compaction now reads the engine's actual record-header layout,
|
|
29
|
+
observes dirty buffer-pool pages, and is covered through reopen validation.
|
|
30
|
+
Real filesystem quota and power-loss tests remain environment work.
|
|
31
|
+
3. SQL correctness: ambiguous identifiers and broader dialect-specific edge
|
|
32
|
+
cases remain open. Boolean false values, `IS NULL`, NULL comparison behavior, and
|
|
33
|
+
outer-join NULL extension now have regression coverage. Non-recursive CTEs,
|
|
34
|
+
subqueries, set operations, targeted `ON CONFLICT DO UPDATE`, and
|
|
35
|
+
ranking/partition window functions, explicit `ROWS` window frames, bounded
|
|
36
|
+
recursive CTEs, dependency-aware inner-join reordering, and targetless
|
|
37
|
+
conflict updates using primary/unique definitions are implemented; multi-row
|
|
38
|
+
`VALUES` sources are now atomic when executed outside a caller transaction.
|
|
39
|
+
Broader dialect upsert forms and statistics-driven plan costing remain open.
|
|
40
|
+
4. Replication: synchronized acknowledgements and promotion with an explicit
|
|
41
|
+
recovery point. Engine commits now package all committed row changes into a
|
|
42
|
+
single replication envelope after the local WAL commit point. Primary
|
|
43
|
+
connections bootstrap the catalog before row replay, including empty replicas. TCP input now
|
|
44
|
+
uses bounded newline framing and replay positions are fsynced before ack;
|
|
45
|
+
replica engine mutation entry points are read-only except for internal replay.
|
|
46
|
+
Promotion now rejects any received/replayed LSN gap and can require a
|
|
47
|
+
caller-supplied recovery point. Active primary engine mutations validate the
|
|
48
|
+
fencing lease before writing. Replication shutdown now closes established
|
|
49
|
+
replica sockets, and a reconnect/catch-up partition test verifies that the
|
|
50
|
+
replica resumes from the durable log. A process-level failover drill now
|
|
51
|
+
kills and replaces an independent primary, advances the fencing epoch from
|
|
52
|
+
another process, rejects the stale writer, and verifies replica catch-up.
|
|
53
|
+
A reproducible TCP fault-proxy drill now exercises live partition, healing,
|
|
54
|
+
catch-up, fencing, and promotion while the primary remains alive. True
|
|
55
|
+
multi-host partitions, independent fencing infrastructure, and split-brain
|
|
56
|
+
recovery still require deployment validation before adding automatic election.
|
|
57
|
+
5. Rails: populated migration round trips, eager loading, nested associations,
|
|
58
|
+
and live adapter coverage are present. CI now exercises Rails 7.1, 7.2, and
|
|
59
|
+
8.0 against the adapter; connection-pool behavior and each version's
|
|
60
|
+
compatibility result still require hosted-run evidence. Migration
|
|
61
|
+
tracking now uses stable content checksums and fails closed for changed or
|
|
62
|
+
missing applied migrations. Native and ActiveRecord schema dumps now
|
|
63
|
+
round-trip automatic/custom primary-key modes, defaults, and indexes through
|
|
64
|
+
live engines.
|
|
65
|
+
6. Operations: backup manifest writes now use durable atomic publication, live
|
|
66
|
+
engines flush WAL/storage before physical backup, and a scheduled restore
|
|
67
|
+
drill reopens restored files. Upgrade tests, measured resource limits,
|
|
68
|
+
alerting, and full security review remain open. Replication peers now
|
|
69
|
+
support constant-time shared-token authentication when configured; TLS and
|
|
70
|
+
credential rotation procedures still require deployment validation.
|
|
71
|
+
|
|
72
|
+
7. Release engineering: cross-platform Ruby 3.3/3.4 CI, Rails 7.1/7.2/8.0
|
|
73
|
+
adapter jobs, and a deterministic bounded fuzz safety workflow are wired
|
|
74
|
+
into GitHub Actions. CI enforces 25% line and 20% branch coverage (the
|
|
75
|
+
current audit measured 62.0% line and 32.84% branch). Release provenance
|
|
76
|
+
signing is enabled, RubyGems gem-level signing requires protected
|
|
77
|
+
key/certificate paths, tag/changelog preflight fails closed, dependency
|
|
78
|
+
audit runs weekly, and tag releases can publish generated GitHub release
|
|
79
|
+
notes. A maintainer must still provision the RubyGems signing secrets and
|
|
80
|
+
review generated notes before publication.
|
|
81
|
+
|
|
82
|
+
Deployment tests must record the commit, platform, workload and measured
|
|
83
|
+
results. Keep untested features marked as unvalidated.
|
|
84
|
+
|
|
85
|
+
## Latest documentation checkpoint
|
|
86
|
+
|
|
87
|
+
The current repository also ships a tested common SQLite-style compatibility
|
|
88
|
+
profile, a durability release gate, Prometheus alert rules, a multi-node Docker
|
|
89
|
+
topology manifest, a production runbook, and a release checklist. The lessons
|
|
90
|
+
from these hardening changes are collected in
|
|
91
|
+
[`docs/lessons-learned.md`](lessons-learned.md). These artifacts improve
|
|
92
|
+
repeatability but do not replace hosted multi-host, physical-filesystem, or
|
|
93
|
+
independent security validation.
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# RubyDB lessons learned
|
|
2
|
+
|
|
3
|
+
This page records the engineering lessons from hardening RubyDB. Each lesson is
|
|
4
|
+
paired with the rule it creates and the validation that protects it. New
|
|
5
|
+
features should update this page when they change an operational guarantee.
|
|
6
|
+
|
|
7
|
+
## Correctness before breadth
|
|
8
|
+
|
|
9
|
+
An implemented parser branch, adapter method, or replication class is not a
|
|
10
|
+
production feature until it is exercised end to end. A narrow, explicit
|
|
11
|
+
compatibility contract is safer than accepting syntax with incomplete
|
|
12
|
+
semantics.
|
|
13
|
+
|
|
14
|
+
Rule: document supported behavior and fail closed for unsupported behavior.
|
|
15
|
+
Validation: integration specs and the SQL compatibility documents must agree.
|
|
16
|
+
|
|
17
|
+
## Durability is a sequence, not a boolean
|
|
18
|
+
|
|
19
|
+
“Write succeeded” and “data is durable” are different outcomes. WAL ordering,
|
|
20
|
+
flush errors, atomic metadata publication, checksums, recovery, and restore must
|
|
21
|
+
be tested together. A successful in-memory mutation cannot hide a failed
|
|
22
|
+
publication or sync.
|
|
23
|
+
|
|
24
|
+
Rule: never report a durable acknowledgement before the required WAL and file
|
|
25
|
+
operations succeed. Preserve uncertain state for recovery instead of guessing.
|
|
26
|
+
Validation: filesystem fault injection, crash recovery, compaction/reopen, and
|
|
27
|
+
backup/restore drills.
|
|
28
|
+
|
|
29
|
+
## Ownership must be explicit
|
|
30
|
+
|
|
31
|
+
An embedded file cannot safely be opened by multiple independent application
|
|
32
|
+
processes merely because each process has a mutex. The operating-system lock and
|
|
33
|
+
the server boundary are part of the data-safety model.
|
|
34
|
+
|
|
35
|
+
Rule: one embedded owner; multiple processes use the server/client topology.
|
|
36
|
+
Validation: duplicate-open rejection, process workloads, and connection-limit
|
|
37
|
+
tests.
|
|
38
|
+
|
|
39
|
+
## Replication needs fencing before election
|
|
40
|
+
|
|
41
|
+
Replica catch-up alone does not prevent two writers. Promotion requires a
|
|
42
|
+
known recovery point, durable fencing, stale-writer rejection, and operator
|
|
43
|
+
evidence. Automatic election without an independent fencing authority can turn
|
|
44
|
+
a network partition into split-brain writes.
|
|
45
|
+
|
|
46
|
+
Rule: keep automatic election disabled until multi-host fencing is independently
|
|
47
|
+
validated in the deployment environment.
|
|
48
|
+
Validation: process replacement, TCP partition/healing, stale-primary rejection,
|
|
49
|
+
and a real multi-host drill.
|
|
50
|
+
|
|
51
|
+
## Cancellation must reach the wire and the executor
|
|
52
|
+
|
|
53
|
+
A client-side timeout that only stops waiting does not stop database work. The
|
|
54
|
+
server must keep reading control frames, mark the active request cancelled, and
|
|
55
|
+
the executor must check cooperatively during long operations.
|
|
56
|
+
|
|
57
|
+
Rule: test cancellation as an in-flight protocol event, not as a mocked timeout.
|
|
58
|
+
Validation: the production soak sends a real cancel frame and checks the server
|
|
59
|
+
request lifecycle.
|
|
60
|
+
|
|
61
|
+
## Deadlock recovery belongs to transaction ownership
|
|
62
|
+
|
|
63
|
+
Detecting a cycle is insufficient if the chosen victim is not rolled back and
|
|
64
|
+
its locks are not released. The lock manager and transaction manager must share
|
|
65
|
+
the same victim identity and lifecycle.
|
|
66
|
+
|
|
67
|
+
Rule: every resolved deadlock has a victim, rollback, lock release, and a
|
|
68
|
+
retryable error for the victim.
|
|
69
|
+
Validation: the two-transaction cycle specs and the production soak.
|
|
70
|
+
|
|
71
|
+
## Adapters are compatibility products
|
|
72
|
+
|
|
73
|
+
ActiveRecord compatibility includes schema introspection, quoted identifiers,
|
|
74
|
+
binds, associations, eager loading, migrations, schema dumps, connection pools,
|
|
75
|
+
and error behavior. Passing one CRUD example is not Rails compatibility.
|
|
76
|
+
|
|
77
|
+
Rule: test the adapter against every supported Rails version and a populated
|
|
78
|
+
schema, not only an empty database.
|
|
79
|
+
Validation: the Rails matrix and adapter integration suite for Rails 7.1, 7.2,
|
|
80
|
+
and 8.0.
|
|
81
|
+
|
|
82
|
+
## Operations are part of the implementation
|
|
83
|
+
|
|
84
|
+
Backups, restore destinations, monitoring thresholds, certificate rotation,
|
|
85
|
+
secret storage, upgrade rollback, incident preservation, and release evidence
|
|
86
|
+
are not afterthoughts. Operators need safe commands and clear stop conditions.
|
|
87
|
+
|
|
88
|
+
Rule: every destructive or irreversible operation must have a dry run, a backup
|
|
89
|
+
boundary, and a documented recovery path.
|
|
90
|
+
Validation: the production runbook, restore drill, alert rules, and release
|
|
91
|
+
preflight.
|
|
92
|
+
|
|
93
|
+
## Evidence must be reproducible
|
|
94
|
+
|
|
95
|
+
Test counts without the commit, runtime, platform, workload, seed, and result
|
|
96
|
+
artifact are difficult to trust or reproduce.
|
|
97
|
+
|
|
98
|
+
Rule: archive machine-readable workload output and record commit, Ruby/Rails
|
|
99
|
+
versions, OS, resources, seed, latency percentiles, recovery time, and RPO/RTO.
|
|
100
|
+
Validation: CI artifacts, bounded fuzz seeds, benchmark output, and release
|
|
101
|
+
checklists.
|
|
102
|
+
|
|
103
|
+
## Security claims require a separate review
|
|
104
|
+
|
|
105
|
+
Passing authentication and TLS tests does not review deployment identity,
|
|
106
|
+
filesystem permissions, secret lifecycle, dependency exposure, or incident
|
|
107
|
+
response.
|
|
108
|
+
|
|
109
|
+
Rule: treat the repository security suite as a baseline and require an
|
|
110
|
+
independent review before sensitive or regulated workloads.
|
|
111
|
+
Validation: dependency audit, CodeQL, TLS/auth tests, deployment review, and
|
|
112
|
+
credential/certificate rotation drills.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Backup policy
|
|
2
|
+
|
|
3
|
+
Backups are only useful after they have been verified and restored. Keep the
|
|
4
|
+
database file, WAL chain, backup manifest, and checksum together. Store a copy
|
|
5
|
+
outside the database host and encrypt it with the organization’s approved
|
|
6
|
+
archive tooling.
|
|
7
|
+
|
|
8
|
+
## Create and verify
|
|
9
|
+
|
|
10
|
+
Stop writes for a maintenance backup when possible, then run:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
rubydb backup --dir /var/lib/rubydb/backups
|
|
14
|
+
rubydb restore --latest --dry-run --dir /var/lib/rubydb/backups
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The backup implementation flushes the WAL and storage before copying files and
|
|
18
|
+
publishes its manifest atomically. A successful command is not a substitute for
|
|
19
|
+
the scheduled restore drill in `scripts/restore_drill`.
|
|
20
|
+
|
|
21
|
+
## Retention and deletion
|
|
22
|
+
|
|
23
|
+
Use a documented retention schedule such as daily backups for 14 days, weekly
|
|
24
|
+
backups for 12 weeks, and monthly backups for the required compliance period.
|
|
25
|
+
Delete only verified, expired backup sets; never delete a base backup while an
|
|
26
|
+
incremental or differential chain depends on it.
|
|
27
|
+
|
|
28
|
+
## Recovery point checks
|
|
29
|
+
|
|
30
|
+
Record the backup name, manifest checksum, database version, page size, WAL
|
|
31
|
+
position, and verification result in the deployment record. Before restoring,
|
|
32
|
+
confirm that the selected backup is complete and that its destination is a new,
|
|
33
|
+
inactive directory.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Disaster recovery
|
|
2
|
+
|
|
3
|
+
Define application-specific recovery objectives before deployment:
|
|
4
|
+
|
|
5
|
+
- RPO: the maximum acceptable committed data loss.
|
|
6
|
+
- RTO: the maximum time to restore service.
|
|
7
|
+
- retention: how long backups and incident copies must be kept.
|
|
8
|
+
|
|
9
|
+
RubyDB’s verified recovery path is restore to a new directory followed by an
|
|
10
|
+
engine reopen and row/catalog verification. It does not silently repair
|
|
11
|
+
unknown corruption or perform automatic cross-region failover.
|
|
12
|
+
|
|
13
|
+
## Recovery procedure
|
|
14
|
+
|
|
15
|
+
1. Declare the incident and stop application writes to the affected database.
|
|
16
|
+
2. Preserve the original data directory, WAL, logs, configuration, and process
|
|
17
|
+
information. Work from a copied incident set.
|
|
18
|
+
3. Select the newest backup whose manifest and checksum verify within the RPO.
|
|
19
|
+
4. Restore into a new directory and run `rubydb restore --dry-run` before
|
|
20
|
+
opening it.
|
|
21
|
+
5. Open the restored database, run migrations/status checks, and execute the
|
|
22
|
+
application smoke and integrity queries.
|
|
23
|
+
6. Switch the application to the restored directory only after validation.
|
|
24
|
+
7. Preserve the failed directory until the incident review is complete.
|
|
25
|
+
|
|
26
|
+
Run `ruby scripts/restore_drill` in staging on the same filesystem and RubyDB
|
|
27
|
+
release family used in production. Record measured restore time, recovered WAL
|
|
28
|
+
position, row counts, and any gaps against RTO/RPO.
|
|
29
|
+
|
|
30
|
+
Do not remove WAL, run vacuum, force promotion, or overwrite the source during
|
|
31
|
+
an active corruption investigation.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Failover procedure
|
|
2
|
+
|
|
3
|
+
Automatic election is intentionally disabled until fencing and partition
|
|
4
|
+
testing are complete. Failover is an operator-controlled procedure.
|
|
5
|
+
|
|
6
|
+
## Promote a replica safely
|
|
7
|
+
|
|
8
|
+
1. Confirm the primary is stopped or fenced and cannot accept writes.
|
|
9
|
+
2. Confirm the candidate replica is `SYNCED`, or is `DISCONNECTED` only after
|
|
10
|
+
it was previously synchronized, and that received and replayed WAL
|
|
11
|
+
positions are equal.
|
|
12
|
+
3. Compare the candidate replay position with the incident recovery point.
|
|
13
|
+
4. Promote only after the candidate passes the configured fencing checks.
|
|
14
|
+
5. Point clients at the promoted node and run read/write smoke queries.
|
|
15
|
+
6. Keep the old primary isolated until its data and fencing epoch are reviewed.
|
|
16
|
+
|
|
17
|
+
Promotion must fail closed when a replica is lagging, has never replayed a
|
|
18
|
+
durable position, or is missing a required recovery point. A disconnected but
|
|
19
|
+
caught-up replica is eligible only for explicit operator promotion after the
|
|
20
|
+
old primary is stopped or fenced. Never force promotion to hide a WAL gap.
|
|
21
|
+
|
|
22
|
+
## Rejoin the old primary
|
|
23
|
+
|
|
24
|
+
Treat the old primary as a stale writer. Preserve its files, compare WAL and
|
|
25
|
+
catalog state, and rebuild or re-bootstrap it as a replica if divergence cannot
|
|
26
|
+
be proven absent. Re-enable it for writes only after a new fencing lease and a
|
|
27
|
+
clean recovery validation.
|
|
28
|
+
|
|
29
|
+
Record the old and new fencing epochs, replica LSNs, promotion time, observed
|
|
30
|
+
lag, and application error rate in the incident record.
|