Skip to content

HA Cluster: INSERT with >5 new property keys per transaction fails with "Error on updating dictionary" #4039

@babasaikiran

Description

@babasaikiran

ArcadeDB Version

26.4.2 (build ef892b4/1776994711893/main)

Environment

  • 3-node HA cluster (arcadedb.ha.enabled: true, quorum: majority)
  • Server mode: test

Expected behavior

INSERT INTO Type CONTENT {"key_0": "v", "key_1": "v", ... "key_49": "v"} should succeed
regardless of how many new (previously unseen) property keys are in the record.

Actual behavior

When a single INSERT introduces more than ~5 new property keys that don't yet exist in the
schema dictionary, the transaction fails with:

Error on transaction commit: Error on updating dictionary for key 'key_N'
com.arcadedb.exception.SchemaException

Steps to reproduce

The script below creates a fresh database, tests INSERT with increasing numbers of new keys,
and reports pass/fail. No external dependencies beyond Python 3 standard library.

Save as dict_bug_repro.py and run:

python dict_bug_repro.py --url http://<arcade-host>:2480/api/v1 --user root --password <password>
#!/usr/bin/env python3
"""
ArcadeDB HA Dictionary Bug Reproducer

Tests INSERT with increasing numbers of new property keys per transaction.
On HA clusters, fails at ~10 new keys per transaction.
"""
import argparse
import json
import time
import urllib.request
import urllib.error
import base64
import sys

DB_NAME = "dict_bug_repro"


def parse_args():
    p = argparse.ArgumentParser(description="ArcadeDB HA dictionary bug reproducer")
    p.add_argument("--url", required=True, help="ArcadeDB HTTP endpoint (e.g. http://host:2480/api/v1)")
    p.add_argument("--user", required=True)
    p.add_argument("--password", required=True)
    return p.parse_args()


class ArcadeClient:
    def __init__(self, url, user, password):
        self.url = url.rstrip("/")
        creds = base64.b64encode(f"{user}:{password}".encode()).decode()
        self.headers = {"Content-Type": "application/json", "Authorization": f"Basic {creds}"}

    def server_command(self, cmd):
        data = json.dumps({"command": cmd}).encode()
        req = urllib.request.Request(f"{self.url}/server", data=data, headers=self.headers, method="POST")
        try:
            with urllib.request.urlopen(req, timeout=30) as r:
                return r.status, r.read().decode()
        except urllib.error.HTTPError as e:
            return e.code, e.read().decode()

    def sql(self, db, command):
        data = json.dumps({"language": "sql", "command": command}).encode()
        req = urllib.request.Request(f"{self.url}/command/{db}", data=data, headers=self.headers, method="POST")
        try:
            with urllib.request.urlopen(req, timeout=30) as r:
                return r.status, r.read().decode()
        except urllib.error.HTTPError as e:
            return e.code, e.read().decode()

    def server_info(self):
        req = urllib.request.Request(f"{self.url}/server", headers=self.headers)
        with urllib.request.urlopen(req, timeout=10) as r:
            return json.loads(r.read().decode())


def main():
    args = parse_args()
    client = ArcadeClient(args.url, args.user, args.password)

    # Server info
    print("Server Info")
    print("-" * 40)
    try:
        info = client.server_info()
        settings = {s["key"]: s["value"] for s in info.get("settings", [])}
        version = info.get("version", "?")
        ha = settings.get("arcadedb.ha.enabled", "?")
        quorum = settings.get("arcadedb.ha.quorum", "?")
        print(f"  Version:    {version}")
        print(f"  HA enabled: {ha}")
        print(f"  HA quorum:  {quorum}")
    except Exception as e:
        print(f"  Failed: {e}")
        sys.exit(1)

    # Setup
    client.server_command(f"drop database {DB_NAME}")
    status, _ = client.server_command(f"create database {DB_NAME}")
    if status != 200:
        print(f"Failed to create database")
        sys.exit(1)
    print(f"\nDatabase '{DB_NAME}' created\n")

    results = []

    # Test 1: INSERT with N new keys
    print("Test 1: INSERT with N new property keys (one transaction)")
    print("-" * 55)
    for n in [1, 2, 3, 5, 8, 10, 15, 20, 50]:
        client.sql(DB_NAME, "DROP TYPE T IF EXISTS UNSAFE")
        client.sql(DB_NAME, "CREATE VERTEX TYPE T IF NOT EXISTS")
        record = {f"key_{i}": f"value_{i}" for i in range(n)}
        status, body = client.sql(DB_NAME, f"INSERT INTO T CONTENT {json.dumps(record)}")
        passed = status == 200
        print(f"  {n:3d} new keys: {'PASS' if passed else 'FAIL'}")
        if not passed:
            detail = json.loads(body).get("detail", "") if body.startswith("{") else body
            print(f"      Error: {str(detail)[:120]}")
        results.append((f"INSERT {n} new keys", passed))
        client.sql(DB_NAME, "DROP TYPE T IF EXISTS UNSAFE")

    # Test 2: 50 keys one at a time (1 key per transaction)
    print(f"\nTest 2: 50 sequential INSERTs (1 new key each)")
    print("-" * 55)
    client.sql(DB_NAME, "CREATE VERTEX TYPE T IF NOT EXISTS")
    ok = 0
    for i in range(50):
        status, _ = client.sql(DB_NAME, f"INSERT INTO T CONTENT {json.dumps({f'prop_{i}': f'v_{i}'})}")
        if status == 200:
            ok += 1
    print(f"  {ok}/50 passed")
    results.append(("50 sequential 1-key INSERTs", ok == 50))
    client.sql(DB_NAME, "DROP TYPE T IF EXISTS UNSAFE")

    # Summary
    print(f"\nSummary")
    print("=" * 55)
    for name, passed in results:
        print(f"  {'PASS' if passed else 'FAIL'}  {name}")

    # Cleanup
    client.server_command(f"drop database {DB_NAME}")
    print(f"\nDatabase '{DB_NAME}' dropped. Done.")


if __name__ == "__main__":
    main()

Sample output (bug confirmed)

Server Info
----------------------------------------
  Version:    26.4.2
  HA enabled: True
  HA quorum:  majority

Database 'dict_bug_repro' created

Test 1: INSERT with N new property keys (one transaction)
-------------------------------------------------------
    1 new keys: PASS
    2 new keys: PASS
    3 new keys: PASS
    5 new keys: PASS
    8 new keys: PASS
   10 new keys: FAIL
      Error: Error on updating dictionary for key 'key_9'
   15 new keys: FAIL
   20 new keys: FAIL
   50 new keys: FAIL

Test 2: 50 sequential INSERTs (1 new key each)
-------------------------------------------------------
  50/50 passed


Summary
=======================================================
  PASS  INSERT 1 new keys
  PASS  INSERT 2 new keys
  PASS  INSERT 3 new keys
  PASS  INSERT 5 new keys
  PASS  INSERT 8 new keys
  FAIL  INSERT 10 new keys
  FAIL  INSERT 15 new keys
  FAIL  INSERT 20 new keys
  FAIL  INSERT 50 new keys
  PASS  50 sequential 1-key INSERTs

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No fields configured for Bug.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions