Skip to content

In-memory metastore overlay over MySQL (Phase 1, #347)#390

Merged
em3s merged 9 commits into
mainfrom
feat/issue-347-overlay-metastore-label
Jul 16, 2026
Merged

In-memory metastore overlay over MySQL (Phase 1, #347)#390
em3s merged 9 commits into
mainfrom
feat/issue-347-overlay-metastore-label

Conversation

@em3s

@em3s em3s commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 1 of metastore consolidation (#347): extend LocalBackedJdbcHashLabel with an operational overlay layer so the metastore is served from a fast store while MySQL becomes a read-only fallback.

This PR scopes the overlay to an in-memory ByteArrayIndexedLabel — a deploy/rollback-safe stepping stone that needs no HBase provisioning. Durable state stays in MySQL (mirrored on writes). Swapping the overlay for a persistent HBase-backed label is a later step.

Part of #347

Changes

  • LocalBackedJdbcHashLabel — three layers: local seed (in-memory), operational overlay (in-memory), MySQL base
    • Write: overlay is the target; all ops mirror to MySQL for rollback safety (useJdbcMetastore=true)
    • Read/scan: overlay wins on (src,tgt) dedup; MySQL rows appear only when the overlay has no entry
    • Scan: indexName=null__default__ prefix-scan index for both indexed backends; MySQL keeps the original filter
    • Count: local + overlay only — MySQL cannot count (JdbcHashLabel returns a -1 sentinel)
    • useJdbcMetastore=false bypasses MySQL entirely (overlay-only)
  • GraphDefaults / Graph — add consolidatedStore: ByteArrayStore (dedicated in-memory overlay store, separate from the local seed store)
  • GraphConfig — add useJdbcMetastore toggle (default true)
  • LocalBackedJdbcHashLabelOverlayTest — mockk unit tests covering write routing, DELETE mirroring, read dedup, count (overlay + local, MySQL never consulted), and lock delegation

Merge policy

Op            | local (seed) | overlay (memory) | MySQL (base) | Rule
--------------|--------------|------------------|--------------|------------------------------
INSERT/UPDATE | useLocal=T   | useLocal=F       | mirrored     | overlay is write target
DELETE        | useLocal=T   | useLocal=F       | mirrored     | both layers deleted → no resurrection
read/scan     | always       | always           | fallback*    | overlay wins on (src,tgt) dedup
count         | always       | always           | never        | local + overlay; MySQL cannot count

* MySQL merge is skipped when useJdbcMetastore=false.

How to Test

./gradlew :engine:test — 621 tests pass. LocalBackedJdbcHashLabelOverlayTest covers write routing, DELETE mirroring, read dedup, count (MySQL never consulted), and lock delegation; LocalBackedJdbcHashLabelTest covers __default__ scan routing and the no-sentinel count invariant.

AI Assistance

  • This PR was written with AI assistance.
    • Tool / model: Claude Code (Opus 4.8)

…nds to HBase r/w + MySQL read-only (#347)

Phase 1 overlay of metastore consolidation (#347).

Extends the existing `LocalBackedJdbcHashLabel` (local H2 + MySQL) with a
third HBase layer that becomes the operational read/write target, while MySQL
is demoted to a read-only fallback.

## Changes

- `LocalBackedJdbcHashLabel`: add `consolidatedLabel` (HBaseHashLabel) field
  - Write (INSERT/UPDATE): HBase only; MySQL never mutated
  - Write (DELETE): mirrored to MySQL so deleted rows cannot resurface on read
  - Read: HBase wins on (src, tgt) dedup; MySQL rows appear only when HBase
    has no entry for that key
  - Count: HBase wins on src dedup
- `GraphDefaults` / `Graph`: propagate `consolidatedMetastore: Mono<HBaseTables>`
  - URI derived from tenant: `datastore://{tenant}/actionbase_metastore`
  - `.cache()` applied so the HBase Table object is not re-created per call
- `GraphConfig`: add `consolidatedMetastoreUri` override (used in tests for
  isolation via a random namespace per Graph instance)
- `GraphFixtures`: inject per-test UUID namespace to prevent mock HBase sharing
- `LocalBackedJdbcHashLabelOverlayTest`: unit tests with mockk + @ObjectSource
  covering write routing, DELETE mirroring, read dedup, and count dedup

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@em3s em3s force-pushed the feat/issue-347-overlay-metastore-label branch from c8306ec to 15e9784 Compare July 3, 2026 09:42
@em3s em3s changed the title feat(engine): overlay metastore label (HBase r/w + MySQL read-only) feat(engine): overlay metastore label — HBase r/w + MySQL read-only (#347) Jul 3, 2026
em3s and others added 3 commits July 3, 2026 18:54
…erlay for scan support

HBaseHashLabel.scan() throws UnsupportedOperationException, breaking
DdlService.getAll() which issues indexName=null / prefix-only scans.

Switch consolidatedLabel to HBaseIndexedLabel and inject a default
prefix-scan index (no fields) so scans route through without predicates.
Patch incoming indexName=null to DEFAULT_SCAN_INDEX before forwarding to
HBase.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
- Merge mergeOverlay + mergeCountOverlay into single merge(keyOf) function
- Derive indices/indexNameToIndex from indexedEntity in create()
- Extract coder local variable in create() to remove duplication

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@em3s

em3s commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

Phase 2 Migration Plan

Phase 1 overlay is now live. To migrate MySQL → HBase and remove MySQL:

Step 1 — Seed HBase from MySQL (insert-only)

For each metadata label (service, storage, label, query, alias):

for each database (tenant):
    label.getAll(database)          // reads from MySQL (fallback layer)
    → for each entity:
        consolidatedLabel.mutate(INSERT, failOnExist=false)   // setnx — skip if HBase already has it

After this, every MySQL row exists in HBase. Phase 1 HBase writes are never overwritten because failOnExist=false (= setnx) preserves the newer HBase version.

Step 2 — Remove MySQL from the read path

Once the seed is verified (reconcile MySQL key count vs HBase key count):

  • merge(hbase, mysql, keyOf) → return hbase directly (drop globalLabel reads)
  • Remove globalLabel field from LocalBackedJdbcHashLabel
  • Remove DELETE mirroring to MySQL

Step 3 — Remove JDBC/H2 entirely (Phase 3)

  • Drop LocalBackedJdbcHashLabel, JdbcHashLabel, LocalStorage, JdbcStorage
  • Route local_backed_metastore labels directly to HBaseIndexedLabel
  • Remove MySQL/H2/Exposed/HikariCP dependencies

@em3s em3s changed the title feat(engine): overlay metastore label — HBase r/w + MySQL read-only (#347) HBase overlay on MySQL metastore Jul 3, 2026
@em3s em3s marked this pull request as ready for review July 3, 2026 10:35
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. enhancement New feature or request labels Jul 3, 2026
… toggle

- Mirror all writes (INSERT/UPDATE/DELETE) to MySQL for rollback safety
- Add useJdbcMetastore flag (default: true); set false to bypass MySQL entirely
- Refactor LocalBackedJdbcHashLabel read paths with remoteEdges/remoteCounts helpers
- Wire flag through GraphConfig → GraphDefaults → LocalBackedJdbcHashLabel
- Expose as kc.graph.use-jdbc-metastore in application.yaml

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
em3s added a commit that referenced this pull request Jul 13, 2026
The wiring step requires changing the constructor to inject labels
directly (as PR #390 did) before the local label's coder type can
change from String to ByteArray.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@em3s em3s self-assigned this Jul 15, 2026
…y-metastore-label

# Conflicts:
#	engine/src/main/kotlin/com/kakao/actionbase/v2/engine/GraphDefaults.kt
#	engine/src/main/kotlin/com/kakao/actionbase/v2/engine/label/metastore/LocalBackedJdbcHashLabel.kt
@em3s

em3s commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Reverse-merged main (7d731fd). Conflicts in GraphDefaults.kt and LocalBackedJdbcHashLabel.kt resolved: local layer is now ByteArrayIndexedLabel (#413).

count() now merges local + HBase overlay only — MySQL can't count (returns a -1 sentinel), so it's excluded. Both count tests updated to match. :engine:test green.

em3s and others added 2 commits July 16, 2026 15:39
…ing it

Comment out the explicit yaml value so the code default (true) governs; a
deployer sets use-jdbc-metastore: false to opt into HBase-only mode.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The consolidated metastore URI hardcoded config.tenant as the HBase namespace,
which need not match the cluster's actually-configured namespace. Introduce a
NAMESPACE_SENTINEL ("__default__") that DefaultHBaseCluster.getTable resolves to
its own configured namespace, and point the default metastore URI at it. Literal
namespaces (used by tests) are unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@em3s

em3s commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Updated: the overlay is now an in-memory ByteArrayIndexedLabel, not HBase-backed. No HBase table provisioning is required, and the change is deploy/rollback-safe.

As-is (default use-jdbc-metastore: true) is not breaking — writes go to the in-memory overlay + mirror to MySQL, reads merge both (overlay wins), so nothing becomes invisible. Durable state stays in MySQL.

Deploy → rollback is safe — MySQL holds every operational write, and the overlay is volatile with no persistent footprint, so rolling back to the previous version loses nothing.

use-jdbc-metastore: false is breaking — it drops the MySQL fallback, so MySQL-only metadata disappears; the system must be migrated first.

Next steps (later PRs):

  • Migrate MySQL → HBase
  • Swap the in-memory overlay for a persistent HBase-backed label
  • Switch to false (HBase-only)

Scope Phase 1 to a deploy/rollback-safe stepping stone: the consolidated
overlay is now an in-memory ByteArrayIndexedLabel instead of an HBase-backed
label. Durable state stays in MySQL (mirrored on writes), so no HBase table
provisioning is required to boot, and rolling back leaves no persistent
artifact. Swapping the overlay to a persistent HBase-backed label is a later
step.

Removes the consolidatedMetastore (Mono<HBaseTables>) plumbing, the
consolidatedMetastoreUri config, and the __default__ namespace sentinel that
only served the HBase metastore URI. The overlay store is a dedicated
ByteArrayStore, separate from the local seed store.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@em3s em3s changed the title HBase overlay on MySQL metastore In-memory metastore overlay over MySQL (Phase 1, #347) Jul 16, 2026
@em3s

em3s commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

Optimistic merge.

@em3s em3s merged commit f76d063 into main Jul 16, 2026
3 checks passed
@em3s em3s deleted the feat/issue-347-overlay-metastore-label branch July 16, 2026 07:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant