Skip to content

database.partitioned: automatic partition sync on engine start with lifecycle hooks#298

Merged
intel352 merged 5 commits intomainfrom
copilot/add-automatic-partition-sync
Mar 12, 2026
Merged

database.partitioned: automatic partition sync on engine start with lifecycle hooks#298
intel352 merged 5 commits intomainfrom
copilot/add-automatic-partition-sync

Conversation

Copy link
Contributor

Copilot AI commented Mar 11, 2026

database.partitioned exposes SyncPartitionsFromSource() but never calls it automatically — consumers must write custom Go code to iterate the service registry post-start. This makes partition sync invisible to YAML config and forces every consumer to rediscover the pattern independently.

Changes

module/database_partitioned.go

  • AutoSync *bool — new config field; defaults to true when any sourceTable is configured. Start() calls SyncPartitionsFromSource() after the DB connection is established. Set autoSync: false to opt out.
  • SyncInterval string — duration string (e.g. "60s"); when set and a sourceTable exists, a background goroutine periodically re-syncs partitions. Errors are logged; Stop() cleanly terminates the goroutine.
  • Captures modular.Logger in Init() for periodic-sync error reporting.

plugins/storage/plugin.go

  • Factory now parses autoSync (bool) and syncInterval (string) from config map.
  • Schema updated with field definitions and defaults for both fields.

Example

modules:
  - name: db
    type: database.partitioned
    config:
      driver: pgx
      dsn: $DB_DSN
      partitionKey: tenant_id
      tables: [orders, invoices, audit_logs]
      sourceTable: organizations
      sourceColumn: id
      autoSync: true       # sync on startup (default when sourceTable is set)
      syncInterval: 60s    # re-sync every 60s for new tenants added at runtime

No application-level Go code required to trigger partition sync.

Original prompt

This section details on the original issue you should resolve

<issue_title>database.partitioned: automatic partition sync on engine start with lifecycle hooks</issue_title>
<issue_description>## Problem

The database.partitioned module supports sourceTable and sourceColumn for deriving partition values from existing data, and exposes SyncPartitionsFromSource() via the PartitionManager interface. However, this method is never called automatically by the engine — consumers must write custom Go code to iterate the service registry after engine.Start() and invoke it manually:

func SyncPartitions(ctx context.Context, engine *wf.StdEngine, logger *slog.Logger) {
    for name, svc := range engine.App().SvcRegistry() {
        if mgr, ok := svc.(wfmodule.PartitionManager); ok {
            if err := mgr.SyncPartitionsFromSource(ctx); err != nil {
                logger.Error("Failed to sync partitions", "module", name, "error", err)
            }
        }
    }
}

This defeats the purpose of YAML-driven configuration — the partition sync behavior is invisible to the config and requires application-level Go code.

Use Case

Multi-tenant SaaS applications where tenants are stored in a database table (e.g., organizations, tenants, affiliates). PostgreSQL LIST partitions must exist before any queries can target tenant-specific data. When a new tenant is onboarded (row inserted into the source table), partitions for all managed tables need to be created automatically.

Proposed Solution

1. Auto-sync on startup

When sourceTable and sourceColumn are configured, the module should automatically call SyncPartitionsFromSource() during Init() or as a post-start hook — after database connections are established but before the HTTP server begins accepting requests.

modules:
  - name: db
    type: database.partitioned
    config:
      sourceTable: organizations
      sourceColumn: id
      autoSync: true          # sync on startup (default when sourceTable is set)
      tables: [orders, invoices, audit_logs]

2. Watch for source table changes

For long-running services, new tenants added after startup also need partitions. Options:

  • Poll interval: periodically re-sync (simple, good enough for most cases)
  • LISTEN/NOTIFY: PostgreSQL trigger on the source table pushes changes
  • Lifecycle hook: expose an OnPartitionSync callback or event so applications can trigger sync from their own onboarding flows
config:
  sourceTable: organizations
  sourceColumn: id
  syncInterval: 60s           # re-sync every 60 seconds

3. Engine lifecycle hooks (broader feature)

More generally, the engine lacks a hook system for "after all modules initialized and connections established, before accepting traffic." This would benefit not just partition sync but any module that needs post-start setup. A postStart hook phase (after Start(), before the HTTP listener accepts connections) would eliminate an entire class of custom Go boilerplate.

Current Workaround

Application code manually iterates the service registry and type-asserts to PartitionManager. This works but:

  • Is invisible to the YAML config (ops can't tell from config alone that sync happens)
  • Must be called in the right order (after Start(), before traffic)
  • Every consumer of database.partitioned must independently discover this pattern</issue_description>

Comments on the Issue (you are @copilot in this section)


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copilot AI changed the title [WIP] Add automatic partition sync on engine start with lifecycle hooks database.partitioned: automatic partition sync on engine start with lifecycle hooks Mar 11, 2026
@intel352 intel352 marked this pull request as ready for review March 11, 2026 23:14
Copilot AI review requested due to automatic review settings March 11, 2026 23:14
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds lifecycle-driven partition synchronization to the database.partitioned module so partition creation can be fully configured via YAML (startup auto-sync + optional periodic re-sync), without requiring application-level Go code.

Changes:

  • Introduces autoSync (defaulting to enabled when a sourceTable exists) and syncInterval for periodic re-sync in database.partitioned.
  • Implements startup sync and a stoppable background goroutine for interval-based sync with module lifecycle (Start/Stop) and logger capture.
  • Extends the storage plugin factory + schema to surface autoSync and syncInterval in configuration/UI.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.

File Description
module/database_partitioned.go Adds startup auto-sync and periodic sync goroutine management in module lifecycle.
plugins/storage/plugin.go Parses new config keys and exposes them in the module schema/defaults.
module/database_partitioned_test.go Adds unit tests covering auto-sync defaults, opt-out behavior, interval parsing, and goroutine lifecycle.

Comment on lines +255 to +258
if autoSync && hasSourceTable {
if err := p.SyncPartitionsFromSource(ctx); err != nil {
return fmt.Errorf("partitioned database %q: auto-sync on startup failed: %w", p.name, err)
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If SyncPartitionsFromSource fails here, the underlying DB connection may already be opened by p.base.Start(). Returning without closing it can leak the connection when engine startup aborts. Consider cleaning up (e.g., p.base.Stop/Close) before returning this error, or use a deferred rollback that runs when Start exits with error.

Copilot uses AI. Check for mistakes.
Comment on lines +263 to +266
interval, err := time.ParseDuration(p.config.SyncInterval)
if err != nil {
return fmt.Errorf("partitioned database %q: invalid syncInterval %q: %w", p.name, p.config.SyncInterval, err)
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If syncInterval parsing fails after p.base.Start() succeeds, Start returns an error but leaves the DB connection opened. Consider closing the base DB (and any started sync state) before returning to avoid leaking resources on startup failure.

Copilot uses AI. Check for mistakes.
Comment on lines +267 to +271
if interval > 0 {
p.syncStop = make(chan struct{})
p.syncWg.Add(1)
go p.runPeriodicSync(ctx, interval)
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Periodic sync can be started even when no DB was opened (e.g., DSN empty makes p.base.Start() a no-op). That leads to an always-failing ticker loop and repeated error logs. Consider validating that p.base.DB() is non-nil (or DSN is configured) before launching the goroutine, and return a clear Start error (or skip starting the loop) when it isn’t.

Copilot uses AI. Check for mistakes.
@intel352
Copy link
Contributor

@copilot apply changes based on the comments in this thread

Build failing, lint failing, test failing, config validation failing

…ky test

Co-authored-by: intel352 <77607+intel352@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 12, 2026

@copilot apply changes based on the comments in this thread

Build failing, lint failing, test fail...

Applied all four review fixes in df4dd18:

  1. Resource leaks on Start failurep.base.Stop(ctx) is now called before returning any error from Start() (both auto-sync failure and syncInterval parse error), so the DB connection opened by p.base.Start() is never leaked.

  2. Nil-DB goroutine guard — Added if p.base.DB() == nil { _ = p.base.Stop(ctx); return error } before launching the periodic sync goroutine. When syncInterval is configured but no DSN is provided (DB is nil), Start() returns a clear error ("syncInterval requires an open database connection") instead of starting an always-failing ticker loop.

  3. Flaky testTestPartitionedDatabase_PeriodicSync_GoroutineLifecycle now uses a real sqlite :memory: DB (satisfying the nil-DB guard), and the time.Sleep(150ms) is removed. The test calls Stop() directly and waits for it to return within a 2-second timeout.

  4. Added TestPartitionedDatabase_SyncInterval_NilDB_ReturnsError to explicitly cover the new nil-DB guard path.

Regarding the CI go mod tidy and lint (modernize/rules.go) failures — these are pre-existing on the base branch and unrelated to the changes in this PR.

@github-actions
Copy link

github-actions bot commented Mar 12, 2026

⏱ Benchmark Results

No significant performance regressions detected.

benchstat comparison (baseline → PR)
## benchstat: baseline → PR
baseline-bench.txt:244: parsing iteration count: invalid syntax
baseline-bench.txt:306617: parsing iteration count: invalid syntax
baseline-bench.txt:596433: parsing iteration count: invalid syntax
baseline-bench.txt:884572: parsing iteration count: invalid syntax
baseline-bench.txt:1186922: parsing iteration count: invalid syntax
baseline-bench.txt:1482537: parsing iteration count: invalid syntax
benchmark-results.txt:244: parsing iteration count: invalid syntax
benchmark-results.txt:307313: parsing iteration count: invalid syntax
benchmark-results.txt:636417: parsing iteration count: invalid syntax
benchmark-results.txt:1157419: parsing iteration count: invalid syntax
benchmark-results.txt:1474760: parsing iteration count: invalid syntax
benchmark-results.txt:1765440: parsing iteration count: invalid syntax
goos: linux
goarch: amd64
pkg: github.com/GoCodeAlone/workflow/dynamic
cpu: AMD EPYC 7763 64-Core Processor                
                            │ baseline-bench.txt │       benchmark-results.txt        │
                            │       sec/op       │    sec/op     vs base              │
InterpreterCreation-4               7.200m ± 58%   5.735m ± 73%       ~ (p=0.485 n=6)
ComponentLoad-4                     3.483m ±  5%   3.459m ±  4%       ~ (p=0.132 n=6)
ComponentExecute-4                  2.026µ ±  2%   2.007µ ±  2%  -0.99% (p=0.045 n=6)
PoolContention/workers-1-4          1.103µ ±  3%   1.093µ ±  3%       ~ (p=0.223 n=6)
PoolContention/workers-2-4          1.092µ ±  1%   1.087µ ±  1%       ~ (p=0.058 n=6)
PoolContention/workers-4-4          1.102µ ±  1%   1.087µ ±  1%  -1.36% (p=0.002 n=6)
PoolContention/workers-8-4          1.098µ ±  2%   1.090µ ±  1%  -0.68% (p=0.039 n=6)
PoolContention/workers-16-4         1.095µ ±  2%   1.090µ ±  0%       ~ (p=1.000 n=6)
ComponentLifecycle-4                3.455m ±  0%   3.458m ±  1%       ~ (p=0.485 n=6)
SourceValidation-4                  2.284µ ±  1%   2.287µ ±  1%       ~ (p=0.589 n=6)
RegistryConcurrent-4                771.9n ±  3%   776.3n ±  3%       ~ (p=0.937 n=6)
LoaderLoadFromString-4              3.468m ±  0%   3.479m ±  0%  +0.32% (p=0.004 n=6)
geomean                             18.60µ         18.18µ        -2.24%

                            │ baseline-bench.txt │        benchmark-results.txt         │
                            │        B/op        │     B/op      vs base                │
InterpreterCreation-4               1.944Mi ± 0%   1.944Mi ± 0%       ~ (p=0.790 n=6)
ComponentLoad-4                     2.097Mi ± 0%   2.097Mi ± 0%       ~ (p=0.485 n=6)
ComponentExecute-4                  1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4          1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4         1.203Ki ± 0%   1.203Ki ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                2.099Mi ± 0%   2.099Mi ± 0%       ~ (p=0.959 n=6)
SourceValidation-4                  1.984Ki ± 0%   1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                1.133Ki ± 0%   1.133Ki ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4              2.099Mi ± 0%   2.099Mi ± 0%       ~ (p=0.818 n=6)
geomean                             15.05Ki        15.05Ki       -0.00%
¹ all samples are equal

                            │ baseline-bench.txt │        benchmark-results.txt        │
                            │     allocs/op      │  allocs/op   vs base                │
InterpreterCreation-4                15.09k ± 0%   15.09k ± 0%       ~ (p=1.000 n=6) ¹
ComponentLoad-4                      17.43k ± 0%   17.43k ± 0%       ~ (p=1.000 n=6)
ComponentExecute-4                    25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-1-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-2-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-4-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-8-4            25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
PoolContention/workers-16-4           25.00 ± 0%    25.00 ± 0%       ~ (p=1.000 n=6) ¹
ComponentLifecycle-4                 17.48k ± 0%   17.48k ± 0%       ~ (p=1.000 n=6) ¹
SourceValidation-4                    32.00 ± 0%    32.00 ± 0%       ~ (p=1.000 n=6) ¹
RegistryConcurrent-4                  2.000 ± 0%    2.000 ± 0%       ~ (p=1.000 n=6) ¹
LoaderLoadFromString-4               17.47k ± 0%   17.47k ± 0%       ~ (p=1.000 n=6) ¹
geomean                               181.2         181.2       +0.00%
¹ all samples are equal

pkg: github.com/GoCodeAlone/workflow/middleware
                                  │ baseline-bench.txt │       benchmark-results.txt       │
                                  │       sec/op       │   sec/op     vs base              │
CircuitBreakerDetection-4                  287.2n ± 4%   286.2n ± 3%       ~ (p=0.394 n=6)
CircuitBreakerExecution_Success-4          22.53n ± 0%   22.49n ± 0%       ~ (p=0.056 n=6)
CircuitBreakerExecution_Failure-4          65.38n ± 0%   64.49n ± 0%  -1.36% (p=0.002 n=6)
geomean                                    75.06n        74.60n       -0.62%

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │        B/op        │    B/op     vs base                │
CircuitBreakerDetection-4                 144.0 ± 0%     144.0 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                  │ baseline-bench.txt │       benchmark-results.txt        │
                                  │     allocs/op      │ allocs/op   vs base                │
CircuitBreakerDetection-4                 1.000 ± 0%     1.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Success-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
CircuitBreakerExecution_Failure-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                              ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/module
                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │       sec/op       │    sec/op     vs base              │
JQTransform_Simple-4                     914.2n ± 22%   887.8n ± 25%       ~ (p=0.699 n=6)
JQTransform_ObjectConstruction-4         1.455µ ±  0%   1.472µ ±  2%  +1.13% (p=0.004 n=6)
JQTransform_ArraySelect-4                3.297µ ±  1%   3.363µ ±  2%  +2.00% (p=0.002 n=6)
JQTransform_Complex-4                    37.83µ ±  1%   38.71µ ±  1%  +2.33% (p=0.002 n=6)
JQTransform_Throughput-4                 1.798µ ±  1%   1.806µ ±  2%  +0.42% (p=0.039 n=6)
SSEPublishDelivery-4                     66.86n ±  0%   65.98n ±  0%  -1.31% (p=0.002 n=6)
geomean                                  1.647µ         1.651µ        +0.26%

                                 │ baseline-bench.txt │        benchmark-results.txt         │
                                 │        B/op        │     B/op      vs base                │
JQTransform_Simple-4                   1.273Ki ± 0%     1.273Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4       1.773Ki ± 0%     1.773Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4              2.625Ki ± 0%     2.625Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                  16.22Ki ± 0%     16.22Ki ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4               1.984Ki ± 0%     1.984Ki ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%       0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²                 +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                 │ baseline-bench.txt │       benchmark-results.txt        │
                                 │     allocs/op      │ allocs/op   vs base                │
JQTransform_Simple-4                     10.00 ± 0%     10.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ObjectConstruction-4         15.00 ± 0%     15.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_ArraySelect-4                30.00 ± 0%     30.00 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Complex-4                    324.0 ± 0%     324.0 ± 0%       ~ (p=1.000 n=6) ¹
JQTransform_Throughput-4                 17.00 ± 0%     17.00 ± 0%       ~ (p=1.000 n=6) ¹
SSEPublishDelivery-4                     0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                             ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/schema
                                    │ baseline-bench.txt │       benchmark-results.txt       │
                                    │       sec/op       │   sec/op     vs base              │
SchemaValidation_Simple-4                    1.124µ ± 3%   1.107µ ± 2%       ~ (p=0.121 n=6)
SchemaValidation_AllFields-4                 1.690µ ± 6%   1.667µ ± 4%       ~ (p=0.117 n=6)
SchemaValidation_FormatValidation-4          1.599µ ± 1%   1.598µ ± 1%       ~ (p=0.974 n=6)
SchemaValidation_ManySchemas-4               1.820µ ± 3%   1.831µ ± 2%       ~ (p=0.513 n=6)
geomean                                      1.533µ        1.524µ       -0.61%

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │        B/op        │    B/op     vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

                                    │ baseline-bench.txt │       benchmark-results.txt        │
                                    │     allocs/op      │ allocs/op   vs base                │
SchemaValidation_Simple-4                   0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_AllFields-4                0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_FormatValidation-4         0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
SchemaValidation_ManySchemas-4              0.000 ± 0%     0.000 ± 0%       ~ (p=1.000 n=6) ¹
geomean                                                ²               +0.00%               ²
¹ all samples are equal
² summaries must be >0 to compute geomean

pkg: github.com/GoCodeAlone/workflow/store
                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │       sec/op       │    sec/op     vs base               │
EventStoreAppend_InMemory-4                1.358µ ± 22%   1.349µ ± 17%        ~ (p=0.937 n=6)
EventStoreAppend_SQLite-4                  1.564m ±  9%   1.905m ±  5%  +21.82% (p=0.002 n=6)
GetTimeline_InMemory/events-10-4           14.03µ ±  2%   14.04µ ±  4%        ~ (p=0.937 n=6)
GetTimeline_InMemory/events-50-4           70.13µ ± 14%   77.09µ ±  1%        ~ (p=0.818 n=6)
GetTimeline_InMemory/events-100-4          123.9µ ±  0%   154.1µ ± 20%        ~ (p=0.310 n=6)
GetTimeline_InMemory/events-500-4          638.7µ ±  1%   638.0µ ±  0%        ~ (p=0.394 n=6)
GetTimeline_InMemory/events-1000-4         1.304m ±  0%   1.308m ±  2%        ~ (p=0.240 n=6)
GetTimeline_SQLite/events-10-4             112.0µ ±  1%   108.3µ ±  1%   -3.33% (p=0.002 n=6)
GetTimeline_SQLite/events-50-4             253.9µ ±  7%   251.9µ ±  1%   -0.82% (p=0.004 n=6)
GetTimeline_SQLite/events-100-4            427.8µ ±  1%   422.5µ ±  1%   -1.24% (p=0.002 n=6)
GetTimeline_SQLite/events-500-4            1.798m ±  0%   1.801m ±  1%        ~ (p=0.485 n=6)
GetTimeline_SQLite/events-1000-4           3.516m ±  1%   3.493m ±  2%        ~ (p=0.093 n=6)
geomean                                    224.7µ         233.3µ         +3.79%

                                   │ baseline-bench.txt │        benchmark-results.txt         │
                                   │        B/op        │     B/op      vs base                │
EventStoreAppend_InMemory-4                  782.5 ± 8%     797.5 ± 7%       ~ (p=0.779 n=6)
EventStoreAppend_SQLite-4                  1.981Ki ± 2%   1.983Ki ± 1%       ~ (p=0.777 n=6)
GetTimeline_InMemory/events-10-4           7.953Ki ± 0%   7.953Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4           46.62Ki ± 0%   46.62Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4          94.48Ki ± 0%   94.48Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4          472.8Ki ± 0%   472.8Ki ± 0%       ~ (p=0.545 n=6)
GetTimeline_InMemory/events-1000-4         944.3Ki ± 0%   944.3Ki ± 0%       ~ (p=0.675 n=6)
GetTimeline_SQLite/events-10-4             16.74Ki ± 0%   16.74Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4             87.14Ki ± 0%   87.14Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4            175.4Ki ± 0%   175.4Ki ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4            846.1Ki ± 0%   846.1Ki ± 0%       ~ (p=0.145 n=6)
GetTimeline_SQLite/events-1000-4           1.639Mi ± 0%   1.639Mi ± 0%       ~ (p=1.000 n=6)
geomean                                    67.28Ki        67.39Ki       +0.17%
¹ all samples are equal

                                   │ baseline-bench.txt │        benchmark-results.txt        │
                                   │     allocs/op      │  allocs/op   vs base                │
EventStoreAppend_InMemory-4                  7.000 ± 0%    7.000 ± 0%       ~ (p=1.000 n=6) ¹
EventStoreAppend_SQLite-4                    53.00 ± 0%    53.00 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-10-4             125.0 ± 0%    125.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-50-4             653.0 ± 0%    653.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-100-4           1.306k ± 0%   1.306k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-500-4           6.514k ± 0%   6.514k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_InMemory/events-1000-4          13.02k ± 0%   13.02k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-10-4               382.0 ± 0%    382.0 ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-50-4              1.852k ± 0%   1.852k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-100-4             3.681k ± 0%   3.681k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-500-4             18.54k ± 0%   18.54k ± 0%       ~ (p=1.000 n=6) ¹
GetTimeline_SQLite/events-1000-4            37.29k ± 0%   37.29k ± 0%       ~ (p=1.000 n=6) ¹
geomean                                     1.162k        1.162k       +0.00%
¹ all samples are equal

Benchmarks run with go test -bench=. -benchmem -count=6.
Regressions ≥ 20% are flagged. Results compared via benchstat.

@intel352 intel352 merged commit 00f9451 into main Mar 12, 2026
15 of 16 checks passed
@intel352 intel352 deleted the copilot/add-automatic-partition-sync branch March 12, 2026 04:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

database.partitioned: automatic partition sync on engine start with lifecycle hooks

3 participants