Prev Next

Database / LadybugDB Interview questions

Last updated

1. What is LadybugDB? 2. What is the relationship between LadybugDB and Kuzu? 3. What is an embedded graph database? 4. What data model does LadybugDB use? 5. What query language does LadybugDB use? 6. What license does LadybugDB use? 7. What files does LadybugDB create on disk? 8. How do you create an on-disk database in Python? 9. What is an in-memory database in LadybugDB? 10. How do you create a node table in LadybugDB? 11. What is a relationship table in LadybugDB? 12. Why does LadybugDB use the word table instead of label? 13. What is the role of the primary key in a node table? 14. How do you import CSV data into LadybugDB? 15. List the client APIs available for LadybugDB? 16. What is the LOAD FROM clause? 17. What are relationship multiplicities in LadybugDB? 18. How do you run a parameterized query in LadybugDB? 19. What is the purpose of the CHECKPOINT statement? 20. What are LadybugDB extensions? 21. What is the difference between COPY FROM and LOAD FROM? 22. How does LadybugDB handle transactions? 23. What is the difference between BEGIN TRANSACTION and BEGIN TRANSACTION READ ONLY? 24. What is the difference between READ_WRITE and READ_ONLY database modes? 25. How do you troubleshoot the error Could not set lock on file? 26. What is the difference between LadybugDB and Neo4j pattern matching semantics? 27. What are the main Cypher syntax differences between LadybugDB and Neo4j? 28. How do you find shortest paths in LadybugDB? 29. How do you filter relationships inside a variable-length pattern? 30. What happens when a variable-length relationship has no upper bound? 31. How do you create and query a vector index in LadybugDB? 32. How do you run full-text search in LadybugDB? 33. How do you run graph algorithms in LadybugDB? 34. Which external relational databases can LadybugDB scan from? 35. How do you skip malformed rows during a CSV import? 36. How does CSV auto-detection work in COPY FROM? 37. How does CREATE NODE TABLE AS work? 38. What is an open-type graph in LadybugDB? 39. How does LadybugDB execute queries in parallel? 40. Explain the execution flow of a pipelined query in LadybugDB? 41. How does LadybugDB store graph data for fast joins? 42. What is factorized query processing and why does it matter for graph queries? 43. When should you use the HINT clause in LadybugDB? 44. How does MULTI_JOIN enable worst-case optimal joins in LadybugDB? 45. Explain the lifecycle of a write transaction in LadybugDB? 46. How can you support multiple read-write processes on one LadybugDB database? 47. How does filtered vector search work in LadybugDB? 48. How do mu, ml, efc and efs affect HNSW tuning in LadybugDB? 49. How does LadybugDB handle larger-than-memory workloads? 50. When would you choose LadybugDB over Neo4j?

1. What is LadybugDB?

LadybugDB (often just Ladybug) is an embedded property-graph database built for query speed and scalability. It runs inside your application process, so there is no server to install or operate, and you query it with Cypher.

It targets join-heavy analytical workloads on large graphs. Under the hood it uses columnar disk-based storage, CSR adjacency lists, vectorized and factorized execution, multi-core parallelism and serializable ACID transactions. It is MIT licensed.

You can reach it from Python, Node.js, Java, Rust, Go, Swift, C++, C and a CLI. It also interoperates with Parquet, Arrow and DuckDB data, which helps when a graph feeds machine learning or analytics pipelines.

Take quiz
How does LadybugDB run inside an application?
As a standalone server your app connects to over the network
As a managed cloud-only service
As a browser plugin only
In-process, as an embedded library
Which workload is LadybugDB optimized for?
Join-heavy analytical queries on large graphs
Simple key-value caching only
Time-series downsampling only
Full-page HTML rendering

2. What is the relationship between LadybugDB and Kuzu?

LadybugDB is the project formerly known as Kuzu. The official docs state this directly, and package listings describe Ladybug as a fork made after Kuzu's acquisition, keeping the same embedded, Cypher-based design.

In practice the concepts carry over (structured property graph, COPY FROM, node and rel tables), but the names changed. If you follow older Kuzu tutorials, expect to swap identifiers:

Item In Ladybug
Python import import ladybug as lb
CLI shell lbug
Node.js package @ladybugdb/core
Rust crate lbug
Database file extension .lbdb
Extension cache ~/.lbug/extensions

Take quiz
What was LadybugDB formerly called?
Memgraph
Kuzu
TigerGraph Lite
Neo4j Embedded
Which statement imports the Python client in the Ladybug docs?
import kuzu as lb
import ladybugdb_server as lb
import ladybug as lb
from neo4j import GraphDatabase

3. What is an embedded graph database?

An embedded graph database is a library that runs inside your application's process. Your code calls it directly; there is no separate daemon, port or login. LadybugDB works this way, which is why a Database object in your program is the database engine.

That Database object bundles the buffer manager, storage manager and transaction manager. Because of that, concurrency rules differ from a server product, which matters when several processes want the same file.

Embedded (Ladybug) Client-server
Runs in your process Runs as a separate service
No network hop for queries Every query crosses the network
You manage connection lifecycle and concurrency Server handles pooling and sessions

Take quiz
Which components live inside a LadybugDB Database object?
Only a REST router
Only a query-result cache for the browser
A cluster coordinator and shard map
Buffer manager, storage manager and transaction manager
What is one practical benefit of the embedded model?
No network round trip for each query
Automatic multi-node sharding
Built-in user login screens
Queries are always served by a remote replica

4. What data model does LadybugDB use?

LadybugDB uses a structured property graph model. You still think in nodes, relationships and properties, but you must declare a schema first: node tables, relationship tables, and strongly typed properties.

Node tables need a primary key. Relationship tables connect two node tables and carry optional properties. Each table has exactly one label, and data types follow the Postgres typing system, so a LIST holds elements of one type only.

CREATE NODE TABLE User(name STRING PRIMARY KEY, age INT64);
CREATE REL TABLE Follows(FROM User TO User, since DATE);

The practical upshot is that you pick the schema up front, and in return the engine gets typed columns it can scan in vectorized batches. If you truly need schemaless labels, look at open-type graphs created with CREATE GRAPH ... ANY.

Take quiz
What must you do before inserting data in LadybugDB?
Nothing, labels are created implicitly in the default mode
Define node and relationship tables in a schema
Install a separate schema server
Build a secondary index on every property
How many labels can one LadybugDB table have?
Up to three
Unlimited
Exactly one
None, labels do not exist

5. What query language does LadybugDB use?

LadybugDB uses Cypher and follows openCypher syntax and semantics wherever it can. If you know Neo4j-style MATCH ... WHERE ... RETURN, most queries read the same.

It adds a few of its own constructs: LOAD FROM for scanning files, SHORTEST and WSHORTEST inside variable-length patterns, a HINT clause for join order, and CALL functions for administration and extensions.

MATCH (a:User)-[:Follows]->(b:User)
WHERE a.name = 'Adam'
RETURN b.name;

A few practical notes: statements in the CLI must end with a semicolon, configuration uses standalone CALL statements such as CALL THREADS=5 that cannot be combined with RETURN, and parameters use the $name form.

Take quiz
Which clause replaces Neo4j's LOAD CSV FROM in Ladybug?
READ CSV
IMPORT FROM
SCAN CSV
LOAD FROM
LadybugDB is closest in query syntax to:
openCypher
SPARQL
Gremlin traversal steps
MongoDB aggregation pipelines

6. What license does LadybugDB use?

LadybugDB is released under the MIT license, a permissive license. You can embed it in commercial and proprietary applications without opening your own source code.

The core is written in C++ and ships with bindings for several languages, so the license applies the same way whether you call it from Python, Rust, Go or Java.

The permissive license is why Ladybug can ship inside desktop apps, notebooks and edge devices. Releases publish precompiled binaries for the CLI and the C and C++ libraries, and extensions are downloaded separately from the official repository when you run INSTALL.

Take quiz
Which license covers LadybugDB?
GPL v3 only
MIT
A commercial-only enterprise license
Server Side Public License
What does a permissive license like MIT allow?
Only non-commercial hobby use
Use only after publishing your app's source
Embedding the database in proprietary products
Use only inside academic labs

7. What files does LadybugDB create on disk?

Since version 0.11.0 LadybugDB stores a database in a single file, for example example.lbdb. That file is the only one you need to open the database; the others are temporary helpers created next to it.

File Purpose
example.lbdb Persistent data as of the last checkpoint
example.lbdb.wal Write-ahead log of updates since the last checkpoint
example.lbdb.shadow Exists only while a checkpoint runs
example.lbdb.tmp Spill file under memory pressure

Before 0.11.0 the path was a directory holding many files.

Because the WAL holds committed changes that have not been merged yet, run CHECKPOINT before copying the .lbdb file on its own, otherwise the copy reflects only the last checkpoint.

Take quiz
Which file records changes made since the last checkpoint?
The .shadow file
The .tmp file
The history.txt file
The .wal file
When does the .tmp file appear?
When intermediate data must spill because memory is tight
Every time a database opens
Only when you run CHECKPOINT
Only in in-memory mode

8. How do you create an on-disk database in Python?

Pass a file path to Database, then create a Connection from it. If the file does not exist, Ladybug creates it in read-write mode.

import ladybug as lb

db = lb.Database("example.lbdb")
conn = lb.Connection(db)

conn.execute("CREATE NODE TABLE User(name STRING PRIMARY KEY, age INT64)")
conn.execute("CREATE (:User {name: 'Alice', age: 30})")
print(list(conn.execute("MATCH (u:User) RETURN u.name, u.age")))

The package is published on PyPI (the project page is named real-ladybug); confirm the current name in the install docs. Results can also be pulled as pandas, Polars or Arrow with get_as_df(), get_as_pl() and get_as_arrow().

Keep one Database object per file in a process and create as many Connection objects from it as you need. For readers, open with lb.Database("example.lbdb", read_only=True).

Take quiz
What do you pass to lb.Database to get on-disk persistence?
The string :memory:
A file path such as example.lbdb
A server URL
Nothing, persistence is always on
Which method returns query results as a pandas DataFrame?
to_pandas_server()
fetch_frame_remote()
get_as_df()
dump_csv()

9. What is an in-memory database in LadybugDB?

If you create a database with an empty path ("") or :memory:, Ladybug runs in in-memory mode. Nothing is written to the WAL or to disk, and everything disappears when the process ends.

It suits quick analysis of small graphs. A few restrictions apply: it cannot be opened READ_ONLY, it cannot be attached, and httpfs remote file caching is not supported for it.

db = lb.Database(":memory:")
conn = lb.Connection(db)

Without a WAL there is no durability, so use in-memory mode for tests, notebooks and throwaway analysis, and switch to an on-disk path once the data must survive a restart or no longer fits in memory.

Take quiz
What happens to data in an in-memory database when the process exits?
It is saved to a .lbdb file automatically
It is replayed from the WAL
It is uploaded to cloud storage
It is lost
Which mode is not allowed for an in-memory database?
READ_ONLY
READ_WRITE
Creating node tables
Running MATCH queries

10. How do you create a node table in LadybugDB?

Use CREATE NODE TABLE with typed columns and a primary key. The key can be declared inline or as a trailing clause, and columns can carry defaults, including function calls.

CREATE NODE TABLE User(
    name STRING PRIMARY KEY,
    age INT64 DEFAULT 0,
    reg_date DATE,
    created TIMESTAMP DEFAULT current_timestamp()
);

Add IF NOT EXISTS to make the statement safe to re-run. The table name becomes the label you use in MATCH (u:User).

Type names such as STRING, INT64, DATE and TIMESTAMP follow Postgres-style typing. You can also create and fill a node table in one step with CREATE NODE TABLE ... AS LOAD FROM, and change it later with ALTER TABLE or remove it with DROP TABLE.

Take quiz
What must a node table declare?
A foreign key to a rel table
A primary key column
A vector index
An FTS index
What does IF NOT EXISTS do on CREATE NODE TABLE?
Drops and recreates the table
Appends new columns
Does nothing if the table already exists
Raises an error on the second run

11. What is a relationship table in LadybugDB?

A relationship table defines a typed edge between node tables using FROM and TO. Edges are directed, can carry properties, and can span several node-table pairs in one table.

CREATE REL TABLE Follows(FROM User TO User, since DATE);
CREATE REL TABLE Knows(FROM User TO User, FROM User TO City);

There is no comma between FROM and TO, but pairs are separated by commas. You cannot declare a primary key on a rel table; each edge gets an internal ID, comparable with ID(r1) != ID(r2).

Create the node tables first, because a rel table names them in FROM and TO. When you later load a rel file with COPY, both endpoint keys in every row must already exist as nodes.

Take quiz
Which declaration lists two node-table pairs correctly?
FROM User, TO User, FROM User, TO City
FROM (User, User) TO (User, City)
FROM User TO User AND City
FROM User TO User, FROM User TO City
Can a relationship table have a user-defined primary key?
No, each edge gets an internal ID
Yes, exactly one
Yes, but only STRING
Only in in-memory mode

12. Why does LadybugDB use the word table instead of label?

Because underneath it is a relational engine that stores sets of tuples. A node table is a table of entities; a relationship table is a table of connections. Calling them tables reflects that columns are typed properties and that rels join nodes.

The naming also signals a real constraint: every node or relationship has only one label, the name of its table. A rel table's identity is derived from the primary keys of its FROM and TO nodes, much like a foreign-key join table.

The same relational heritage is why Ladybug can scan and attach relational sources so naturally.

Take quiz
What does the term table signal about Ladybug's design?
It forbids graph queries
It stores and processes sets of tuples like a relational system
It requires SQL instead of Cypher
It stores everything as JSON documents
How is a relationship's identity tied to nodes?
Through a hand-written edge key
Through the file offset only
Through the primary keys of its FROM and TO nodes
Through a random UUID property you must add

13. What is the role of the primary key in a node table?

The primary key uniquely identifies each node, is guaranteed non-null, and gets an index for fast lookups. Supported types include STRING, numeric types, DATE and BLOB.

If you do not have a natural key, use SERIAL to get auto-incremented integers. The key also drives bulk loads: the first two columns of a rel CSV are the primary keys of the source and destination nodes.

CREATE NODE TABLE Book(id SERIAL PRIMARY KEY, title STRING);

Choose a key that is stable and compact, because it is how rel files and patterns like MATCH (u:User {name: 'Adam'}) find nodes. Duplicate keys are rejected, which is the uniqueness guarantee you get in place of a separate constraint.

Take quiz
Which type auto-generates incrementing primary key values?
UUID_AUTO
COUNTER_ROW
MAP
SERIAL
What do the first two columns of a rel CSV contain?
Primary keys of the FROM and TO nodes
Edge labels
Timestamps of load
Internal page numbers

14. How do you import CSV data into LadybugDB?

Use COPY FROM, the recommended bulk path. Load node tables first, because every endpoint in a rel file must already exist.

COPY User FROM "user.csv" (header=true);
COPY Follows FROM "follows.csv";
COPY User FROM "User*.csv";            -- glob
COPY User FROM ["u0.csv", "u1.csv"];   -- explicit list

Compressed *.csv.gz files are read directly. Wrap strings in quotes and avoid padding spaces: an integer like 213 is treated as malformed and becomes NULL.

A typical load script creates the schema, runs COPY for each node table, then for each rel table. Options go in parentheses, such as (header=true, delim='|', ignore_errors=true). Files are read in parallel by default, and SPILL_TO_DISK lets an import continue when memory runs short.

Take quiz
Why must node tables be loaded before relationship tables?
Rels are stored inside node pages
Rel rows reference existing node primary keys
Node files compress rel files
COPY FROM refuses to run twice
Can COPY FROM read a gzip-compressed CSV directly?
No, decompress first
Only for rel tables
Yes, *.csv.gz is supported
Only in in-memory mode

15. List the client APIs available for LadybugDB?

Ladybug ships official bindings for Python, Node.js, Java, Rust, Go, Swift, C++ and C, plus the CLI shell. Ruby and Nim bindings are community maintained, and a WebAssembly build (Ladybug-Wasm) runs in the browser.

Language How to get it
Node.js npm install @ladybugdb/core
Rust cargo add lbug
Go go get github.com/LadybugDB/go-ladybug
Java Maven Central, com.ladybugdb:lbug
C/C++, CLI Precompiled binaries from the GitHub releases

All bindings drive the same engine, so schema, Cypher and file formats behave identically across languages. Pick the language of your host application, use the CLI for quick exploration, and use Ladybug Explorer when you want to browse the graph visually.

Take quiz
Which APIs are community-maintained rather than official?
Python and Node.js
Java and Go
C and C++
Ruby and Nim
What is the npm package for the Node.js client?
@ladybugdb/core
ladybug-server
lbug-cli
@neo4j/embedded

16. What is the LOAD FROM clause?

LOAD FROM scans an external source and returns its rows to a query without storing them. It works on more than CSV, which is why it replaced Neo4j's LOAD CSV FROM. Column names and types are inferred from the file header.

LOAD FROM 'user.csv.gz' RETURN *;
LOAD FROM 'user.csv' (header=true) WHERE age > 30 RETURN name;

It is also the engine behind CREATE NODE TABLE ... AS LOAD FROM ..., which infers a schema and loads in one statement.

Combine it with WHERE and RETURN to preview a file, or see the Copy from subquery page when you need to reshape rows before they land in a table. Compressed .csv.gz files work too.

Take quiz
Does LOAD FROM persist rows into a table by itself?
Yes, it always creates a table
No, it only scans and returns rows
Yes, but only for Parquet
It writes them to the WAL
Where do LOAD FROM column types come from for a CSV?
A mandatory XML sidecar
The previous COPY command
Inferred from the file header and data
They are always STRING

17. What are relationship multiplicities in LadybugDB?

Multiplicity limits how many edges a node can have in each direction. The default is many-to-many. You can cap a side at one edge with MANY_ONE, ONE_MANY or ONE_ONE.

CREATE REL TABLE LivesIn(FROM User TO City, MANY_ONE);
CREATE REL TABLE Likes(FROM Pet TO User, ONE_MANY);

MANY_ONE means each user lives in at most one city while a city can have many users. "Exactly one" semantics, like a foreign key, are not supported yet.

Use multiplicity when your domain has a natural cardinality, such as one city per user. It records that intent in the schema. Since exactly-one is not available yet, enforce mandatory links in your loading code.

Take quiz
What does MANY_ONE on LivesIn(FROM User TO City) mean?
Each city has at most one user
Each user must have exactly one city
Only one LivesIn edge exists overall
Each user has at most one city
Which multiplicity is the default?
Many-to-many
One-to-one
Many-to-one
Exactly-one

18. How do you run a parameterized query in LadybugDB?

Use $name placeholders in Cypher and pass a dictionary of values. Parameters keep values out of the query text, avoid string-building mistakes, and let you reuse the same statement shape.

conn.execute(
    "MATCH (b:Book {title: $title}) RETURN b.published_year",
    {"title": "The Quantum World"}
)

The same style is used for inserting vectors: CREATE (b:Book {title: $title, title_embedding: $embeddings}) with a Python list as the value.

Parameters also carry lists, which is how embeddings are inserted without building giant strings. Other client APIs expose prepared statements as well, so check the prepared statements page for your language.

Take quiz
Which placeholder syntax does Ladybug Cypher use?
?title
$title
:title
{{title}}
What is a benefit of passing values as parameters?
It disables transactions
It forces a full table scan
Values never get concatenated into the query string
It skips type checking

19. What is the purpose of the CHECKPOINT statement?

CHECKPOINT merges the write-ahead log into the main database file and lets Ladybug discard the WAL. It runs automatically at the end of a write transaction once the WAL passes CHECKPOINT_THRESHOLD (16 MB by default) and no transactions are active.

CHECKPOINT;
CALL checkpoint_threshold=33554432;

Calling it manually only works when nothing else is active; with an open transaction it errors, because forced checkpoints are not supported.

A larger threshold means fewer, bigger checkpoints and more data accumulating in the WAL between merges; a smaller one keeps the log short at the cost of more frequent merges. Set it with CALL checkpoint_threshold=<bytes>.

Take quiz
What is the default WAL size that triggers an automatic checkpoint?
1 KB
16 GB
There is no automatic checkpoint
16 MB
Why can a manual CHECKPOINT fail?
Other transactions are still active
The database is too small
The WAL is empty only
Cypher forbids the keyword

20. What are LadybugDB extensions?

Extensions are optional modules you download and load on demand to add features without bloating the core. The flow is always INSTALL once, then LOAD per session.

INSTALL vector;
LOAD vector;

Official ones are fetched into ~/.lbug/extensions. Available families include vector and fts search, algo graph algorithms, httpfs, S3, GCS and Azure storage, json, Iceberg, Delta and Unity Catalog, llm embeddings, and relational attachments for Postgres, DuckDB, SQLite and ADBC.

Extensions also reach remote data: httpfs reads over HTTPS, and the cloud storage extensions cover S3, GCS and Azure, so you can COPY FROM a remote file once credentials are configured.

Take quiz
Where are official extensions downloaded?
/etc/ladybug/modules
~/.lbug/extensions
The database's .wal file
A temp folder deleted on exit
Which pair of statements activates the vector extension?
ENABLE vector;
IMPORT vector;
INSTALL vector; LOAD vector;
ATTACH vector;

21. What is the difference between COPY FROM and LOAD FROM?

COPY FROM bulk-writes file data into an existing table. LOAD FROM only reads a source into a query result, so you can filter, join or transform before deciding what to store.

COPY FROM LOAD FROM
Writes into a node or rel table Returns rows, stores nothing
Target table must exist No target table needed
Best for large initial loads Best for inspection and ad hoc transforms

They share the same CSV options such as header, delim and null_strings.

Rule of thumb: explore with LOAD FROM, commit with COPY FROM.

Take quiz
Which command writes file rows into a table?
LOAD FROM
SHOW FROM
ATTACH FROM
COPY FROM
What is LOAD FROM good for before storing data?
Previewing and filtering rows from a file
Creating the WAL
Changing CHECKPOINT_THRESHOLD
Dropping indexes

22. How does LadybugDB handle transactions?

Every query, DDL statement and COPY FROM runs in a transaction, and Ladybug is atomic, durable and serializable. After a commit your changes survive a crash; before a commit, a crash leaves none of them behind.

You can wrap statements yourself or let each one auto-commit. At any moment there can be many read transactions but only one write transaction.

BEGIN TRANSACTION;
CREATE (a:User {name: 'Alice', age: 72});
MATCH (a:User) RETURN *;
COMMIT;

Because transactions are serializable, readers see a consistent view while the single writer works. Use BEGIN TRANSACTION READ ONLY when you need a stable multi-query read. Keep manual transactions short and always end them with COMMIT or ROLLBACK; a crash in the middle of one discards everything it did.

Take quiz
How many write transactions can run at once?
As many as CPU cores
One
Two per connection
Unlimited with optimistic locking
What happens to an uncommitted transaction after a crash?
All changes persist
Half the statements persist
None of its changes persist
It resumes automatically on restart

23. What is the difference between BEGIN TRANSACTION and BEGIN TRANSACTION READ ONLY?

BEGIN TRANSACTION starts a read-write transaction; BEGIN TRANSACTION READ ONLY starts one that may not write. Read-only transactions are useful for two reasons.

  1. You can run several queries and be sure the data does not change between them.
  2. You do not occupy the single write slot, so a writer elsewhere is not blocked.

End either with COMMIT, or use ROLLBACK to discard the changes of a read-write transaction.

Example: a report that runs five MATCH queries should open one read-only transaction so all five see identical data even if another connection commits meanwhile. Do not leave transactions open, because an active transaction prevents automatic checkpoints.

Take quiz
Why start a READ ONLY transaction?
To skip the WAL for faster inserts
To enable multiple writers
To drop tables safely
To read consistently without blocking the writer
Which statement discards a transaction's changes?
ROLLBACK
CHECKPOINT
RETURN
DETACH

24. What is the difference between READ_WRITE and READ_ONLY database modes?

The mode is chosen when you create the Database object. A READ_WRITE database can run reads and writes (CREATE, COPY FROM); a READ_ONLY database can only read.

The rule that matters: you may have one READ_WRITE object, or several READ_ONLY objects, never a mix on the same file. A writer can change disk layout, and other objects' buffer-manager caches would not know.

Setup Safe?
One READ_WRITE object, many connections Yes
Many READ_ONLY objects, even across processes Yes
READ_WRITE plus READ_ONLY objects No

In code, readers use lb.Database("example.lbdb", read_only=True). Prefer one shared Database per process with one Connection per thread; connections from the same READ_WRITE Database can issue concurrent reads and writes safely.

Take quiz
Which combination is safe on one database file?
One READ_WRITE plus one READ_ONLY
Several READ_ONLY Database objects
Two READ_WRITE objects
A READ_WRITE object in each of two processes
Why can't a writer coexist with readers from another Database object?
Cypher forbids two MATCH queries
The WAL is single-threaded text
Their cached pages could become stale
Licenses restrict it

25. How do you troubleshoot the error Could not set lock on file?

This error means another Database object already holds the file lock. The classic case is a Jupyter notebook that built the graph while you start the CLI on the same .lbdb file.

RuntimeError: IO exception: Could not set lock on file : /path/to/example.lbdb

  1. Find the process that opened the file (notebook kernel, script, Explorer).
  2. Restart the Jupyter server or close that process so its Database object is destroyed.
  3. Reopen from one place only, or open every process READ_ONLY.
  4. For Ladybug Explorer in Docker, the lock flags are not visible across the container boundary, so stop the other process first.

Also look for a second terminal running lbug on the same path. To avoid the problem for good, open analysis tools read-only or send all writes through a single service process.

Take quiz
What usually causes the lock error?
A corrupted CSV
A missing extension
A full tmp disk only
Another process already has the database open read-write
What is the simplest fix when a Jupyter notebook holds the lock?
Restart the notebook server to release it
Delete the .wal file
Rename the table
Change CHECKPOINT_THRESHOLD

26. What is the difference between LadybugDB and Neo4j pattern matching semantics?

Neo4j applies trail semantics inside one MATCH: no relationship is repeated. LadybugDB applies walk semantics by default, so the same edge can be traversed more than once.

You can still ask for stricter behavior. Inside a recursive pattern add TRAIL (all relationships distinct) or ACYCLIC (all nodes distinct), or test a returned path with is_trail(p) and is_acyclic(p).

MATCH (a:User)-[e:Follows* trail 4..4]-(b:User)
WHERE a.name = 'Zhang'
RETURN b.name;

This explains why a query ported from Neo4j can return extra rows in Ladybug.

When porting a query, compare results on a small graph. If you relied on trail behavior to avoid revisiting edges, add TRAIL explicitly. The default walk semantics are also why unbounded recursion is capped at 30 hops.

Take quiz
Which semantics does LadybugDB use by default for patterns in MATCH?
Trail, no repeated edges
Walk, repeated edges allowed
Acyclic, no repeated nodes
Shortest path only
Which keyword inside a recursive pattern forbids repeated relationships?
WALK
LOOPFREE
TRAIL
UNIQUE_NODES

27. What are the main Cypher syntax differences between LadybugDB and Neo4j?

Ladybug follows openCypher closely but deliberately omits or renames some things. The ones that bite most often when porting queries:

Neo4j LadybugDB
FOREACH Not supported, use UNWIND
REMOVE n.prop SET n.prop = NULL
labels(n) label(n)
MATCH (n:Person WHERE n.age > 3) Move the filter to a normal WHERE
SHOW FUNCTIONS CALL show_functions() RETURN *
elementId(n) id(n)
CALL { ... } subquery Not supported; EXISTS and COUNT subqueries are

Also remember that a schema must exist before inserting data.

Types differ too: Ladybug follows Postgres typing, lists must hold one element type, and cosine similarity is ARRAY_COSINE_SIMILARITY(). Spatial functions, isNaN, e() and pi() are missing, and date() becomes current_date().

Take quiz
What replaces Neo4j's REMOVE n.prop in LadybugDB?
DELETE n.prop
DROP PROPERTY n.prop
UNSET n.prop
SET n.prop = NULL
Which function name is used instead of labels()?
label()
tags()
kind()
types()

28. How do you find shortest paths in LadybugDB?

Put a keyword right after the star in a variable-length relationship. The lower bound must be 1, and an upper bound should always be given.

Pattern Meaning
-[e* SHORTEST 1..4]-> One shortest path per destination
-[e* ALL SHORTEST 1..3]- All shortest paths
-[e:Follows* WSHORTEST(score)]-> Cheapest path by a numeric rel property
-[e:Follows* ALL WSHORTEST(score)]-> All cheapest paths

MATCH p=(a:User)-[e:Follows* WSHORTEST(score)]->(b:User)
WHERE a.name = 'Adam'
RETURN properties(nodes(p), 'name'), cost(e);

Prefer SHORTEST when you only need reachability or length, since paths do not have to be materialized.

Drop the arrow to ignore direction, as in -[e* SHORTEST 1..4]-. The docs recommend plain SHORTEST when you do not need the paths themselves, and ask you to keep upper bounds modest on dense graphs.

Take quiz
Which function returns the total weight of a WSHORTEST path?
weight(e)
cost(e)
length(e) only
score_sum(p)
What lower bound is required on shortest-path patterns?
0
2
1
Any value

29. How do you filter relationships inside a variable-length pattern?

Add a predicate group after the hop range: (r, n | WHERE ...). The first variable binds each intermediate relationship, the second each intermediate node.

MATCH p = (a:User)-[:Follows*1..2 (r, n | WHERE r.since < 2022 AND n.age > 45)]->(b:User)
WHERE a.name = 'Adam'
RETURN b.name, COUNT(*);

Predicates may use only the relationship, only the node, or an AND of both kinds. An OR mixing node and relationship properties is not supported. You can also project just the properties you need, {r.since}, {n.name}, to cut memory use.

A projection example is -[e:Follows*1..2 (r, n | WHERE r.since > 2020 | {r.since}, {n.name})]->. Only direct property projections are allowed, not expressions. It helps when paths are long and wide nodes would otherwise be copied into every intermediate result.

Take quiz
What do the two variables in (r, n | WHERE ...) bind to?
Source and destination node
Start and end timestamp
Left and right join side
An intermediate relationship and an intermediate node
Which predicate is not supported in the filter?
n.age > 45 OR r.since < 2022
r.since < 2022
n.age > 45
n.age > 45 AND r.since < 2022

30. What happens when a variable-length relationship has no upper bound?

Ladybug fills in a default maximum of 30 hops. This is needed because walk semantics allow repeated edges, so an unbounded pattern on a cyclic graph might never terminate.

You can change the default for a connection with var_length_extend_max_depth, but a tight explicit bound in the query is better for both speed and result size.

CALL var_length_extend_max_depth=10;
MATCH (a:User)-[e:Follows*]->(b:User) RETURN count(*);

A good habit is to write the bound you really need, such as *1..3 for friends of friends of friends. A bound of 30 on a dense graph can explode result counts. Adding TRAIL or ACYCLIC also removes redundant paths, and CALL TIMEOUT=3000 stops a runaway query after three seconds.

Take quiz
What is the default maximum depth if you omit the upper bound?
10
30
255
Unlimited
Why is an upper bound required with walk semantics?
Because indexes are limited to 30 pages
Because rel tables hold 30 rows
Cycles could otherwise make the traversal endless
Because Cypher forbids stars

31. How do you create and query a vector index in LadybugDB?

Load the vector extension, store embeddings as FLOAT[n] or DOUBLE[n] properties on a node table, then build an HNSW index with CREATE_VECTOR_INDEX.

INSTALL vector; LOAD vector;
CALL CREATE_VECTOR_INDEX('Book', 'book_title_index', 'title_embedding', metric := 'l2');

CALL QUERY_VECTOR_INDEX('Book', 'book_title_index', $query_vector, 2)
RETURN node.title, distance ORDER BY distance;

The result gives node and distance, so you can continue straight into a MATCH to traverse from the hits. Only node-table properties can be indexed. Use DROP_VECTOR_INDEX to remove one.

Tuning knobs are mu, ml, efc (build) and efs (search), defaulting to 30, 60, 200 and 200. Metrics are cosine, l2, l2sq and dotproduct. Embeddings can come from a Python library such as sentence-transformers or from the llm extension's create_embedding. Run CALL SHOW_INDEXES() RETURN * to confirm the index exists.

Take quiz
What does QUERY_VECTOR_INDEX return?
Only a boolean
A CSV path
The raw embedding file
A node and its distance
Where must the indexed vector live?
On a node table property
On a relationship property
In an external Parquet file only
In the WAL

32. How do you run full-text search in LadybugDB?

Load the fts extension, create an index over one or more STRING properties of a node table, then query it. Scoring uses Okapi BM25.

CALL CREATE_FTS_INDEX('Book', 'book_index', ['abstract', 'title'], stemmer := 'porter');

CALL QUERY_FTS_INDEX('Book', 'book_index', 'quantum machine', conjunctive := false, top := 3)
RETURN node.title, score ORDER BY score DESC;

Use conjunctive := true when every keyword must appear. K and B tune term-frequency saturation and length normalization. Custom stopwords can come from a node table or a CSV or Parquet file, and changing them requires rebuilding the index. CREATE_FTS_INDEX cannot share a multi-statement block with other statements.

List indexes with CALL SHOW_INDEXES() and remove one with DROP_FTS_INDEX. Stemmers cover many languages, from the default english Snowball and porter to French, German and Hindi; use none to turn stemming off.

Take quiz
Which scoring algorithm does QUERY_FTS_INDEX use?
PageRank
Okapi BM25
Levenshtein distance only
TF-only counting
What does conjunctive := true do?
Returns every row
Disables stemming
Requires all query keywords to appear
Sorts results alphabetically

33. How do you run graph algorithms in LadybugDB?

Install and load the algo extension. Algorithms run on a projected graph, a named subset of node and rel tables, optionally with filters. Available algorithms are PageRank, Weakly and Strongly Connected Components, Louvain and K-Core.

INSTALL algo; LOAD algo;
CALL PROJECT_GRAPH('G', ['Person'], ['KNOWS']);
CALL SHOW_PROJECTED_GRAPHS();
CALL DROP_PROJECTED_GRAPH('G');

A projected graph is not materialized: data is scanned from disk when an algorithm runs. It lives until you drop it or close the connection. Graphs stay directed, though WCC ignores direction.

Filters in a projection use n for nodes and r for relationships, for example {'KNOWS': 'r.id < 3'}, and each predicate may touch only its own table. With the Python async API a projected graph can seem to vanish, because queries may run on different pooled connections; create and use it on the same connection.

Take quiz
What do algo functions run on?
Only the whole database implicitly
A CSV export
A materialized in-memory copy
A projected graph
How long does a projected graph last?
Until dropped or the connection closes
Forever, stored in the .lbdb file
Exactly one query
Until the next CHECKPOINT

34. Which external relational databases can LadybugDB scan from?

Through extensions, Ladybug can scan data with LOAD FROM from PostgreSQL, DuckDB and SQLite, and from any database with an ADBC driver. You can read the data in place or copy it into node and rel tables.

Extension Source
postgres Attached PostgreSQL database
duckdb Attached DuckDB database
sqlite Attached SQLite database
adbc Any ADBC-compatible database

Prefer a dedicated extension when one exists: it supports extras such as SQL_QUERY pass-through, which the generic ADBC adapter does not.

Other sources work in a similar way: Iceberg, Delta Lake and Unity Catalog for lakehouse data, and S3, GCS or Azure for files in cloud storage. Scanning in place keeps one source of truth, while COPY ingests once for faster graph queries.

Take quiz
Which extension is the generic adapter for ADBC-compatible databases?
jdbcx
adbc
sqlbridge
rdbms_any
What extra do dedicated Postgres, DuckDB and SQLite extensions offer over adbc?
Free cloud hosting
Automatic sharding
SQL_QUERY pass-through
A GUI schema editor

35. How do you skip malformed rows during a CSV import?

Set IGNORE_ERRORS=true in the COPY FROM options. Bad rows are skipped instead of aborting the load, and you can inspect what was skipped afterwards.

COPY User FROM "user.csv" (header=true, ignore_errors=true);
CALL SHOW_WARNINGS() RETURN *;
CALL CLEAR_WARNINGS();
CALL warning_limit=1024;

Each connection stores at most WARNING_LIMIT warnings (8192 by default). Typical culprits are values with stray padding, such as 213 in an integer column.

A sensible workflow is to import with ignore_errors, check SHOW_WARNINGS, fix the source data, then CLEAR_WARNINGS before the next batch. Raise warning_limit first if you expect more than 8192 bad rows, and compare the final row count with what the file should contain. If a tool writes the text NULL for missing values, set null_strings too.

Take quiz
What is the default maximum number of warnings stored per connection?
16
1 million
Unlimited
8192
Which function lets you inspect skipped rows?
SHOW_WARNINGS
SHOW_SKIPPED_FILES
LIST_BAD_ROWS
DUMP_ERRORS

36. How does CSV auto-detection work in COPY FROM?

When you do not specify them, Ladybug detects HEADER, DELIM, QUOTE and ESCAPE from the first sample_size lines (256 by default).

  • Header: the first line is a header if its fields cannot be cast to the target column types.
  • Delimiter: chosen from ,, |, ; and tab.
  • Quote: double quote, single quote or none.
  • Escape: double quote, single quote, backslash or none.

Anything you set manually is not re-detected. With auto_detect=false the defaults apply: comma, " and \.

If detection guesses wrong, override just the field that is wrong and the rest stays automatic. For files with unusual quoting, raise sample_size so the sample covers a representative section of the file.

Take quiz
How many lines does auto-detection sample by default?
2
256
10,000
The whole file
How does Ladybug decide the first line is a header?
It starts with a hash sign
The file name contains header
Its fields fail to cast to the target column types
It has fewer than three fields

37. How does CREATE NODE TABLE AS work?

It creates a table and fills it from a subquery in a single statement, inferring the schema from the subquery's output. The source can be a file or a MATCH.

CREATE NODE TABLE Person AS LOAD FROM "person.csv" RETURN *;

CREATE NODE TABLE YoungPerson AS
    MATCH (p:Person) WHERE p.age < 25 RETURN p.*;

CREATE REL TABLE Knows(FROM Person TO Person) AS
    LOAD FROM "knows.csv" RETURN *;

Add IF NOT EXISTS to make it re-runnable. For rel tables you still name the FROM and TO tables.

This is handy for prototypes and ETL: derive a table from a file, or materialize a filtered copy of an existing table. For production schemas, an explicit CREATE NODE TABLE gives you control over keys, defaults and exact types, because inferred types come from the data. Create node tables before building a rel table from a file of endpoint keys.

Take quiz
What does CREATE NODE TABLE ... AS infer?
Only the table name
Indexes on all columns
The WAL size
The schema from the subquery's output
What source can feed CREATE NODE TABLE AS besides a file?
A MATCH query
A running REST endpoint
Only another CSV
A browser cookie

38. What is an open-type graph in LadybugDB?

Ladybug supports subgraphs that each have their own schema, data and configuration, similar to separate databases. By default they are strictly typed: every node table must be created first.

An open-type graph is created with CREATE GRAPH mygraph ANY. There you can create nodes with labels without declaring node tables first, which eases migration from GQL or Neo4j-style data.

CREATE GRAPH mygraph ANY;

Check the release notes for the current state of graph selection, since the Neo4j comparison page still says each graph is its own database.

Use the default strict mode when you control the schema and want mistakes to raise errors. Use ANY when you are importing loosely structured graph data and want to move quickly.

Take quiz
What does ANY in CREATE GRAPH mygraph ANY allow?
Anonymous users to connect
Creating labeled nodes without declaring node tables first
Skipping primary keys in rel files
Running without a WAL
What is the default subgraph type?
Open type
Schemaless JSON
Strictly typed
Read-only

39. How does LadybugDB execute queries in parallel?

It uses morsel-driven parallelism. A pipeline can run on many threads, and the pipeline's source operator hands out small chunks of input to each thread.

For a CSV scan, the source first counts lines and then dispatches different line ranges to different threads. The thread count defaults to the system maximum and can be lowered per connection.

CALL THREADS=5;
CALL TIMEOUT=3000;

Leave THREADS at the default for analytics, or lower it when Ladybug shares a machine with other work. Handing out small chunks dynamically keeps all cores busy even when data is skewed, because a thread that finishes early simply takes the next chunk. The threads belong to your application's process, and TIMEOUT in milliseconds protects against runaway queries. Imports benefit too, since COPY FROM scans CSV files in parallel by default.

Take quiz
Which operator decides how work is split across threads?
The sink operator only
The client driver
The WAL writer
The pipeline's source operator
Which setting limits the number of execution threads?
THREADS
MAX_CORES_TOTAL
WORKERS
PARALLEL_LIMIT

40. Explain the execution flow of a pipelined query in LadybugDB?

The physical plan is cut into pipelines: linear chains of operators. A source operator at the leaf scans storage or another pipeline's output, and a sink operator at the end accumulates results. Between source and sink, data flows without being materialized.

A new pipeline starts at every sink operator, meaning one that must exhaust its input first, such as HASH_JOIN_BUILD, AGGREGATE or ORDER BY. Pipelines can depend on each other: a hash join probe pipeline waits for its build pipeline.

flowchart LR
  A["Scan build side"] --> B["HASH_JOIN_BUILD sink"]
  C["Scan probe side"] --> D[HASH_JOIN_PROBE]
  B -. must finish first .-> D
  D --> E["AGGREGATE sink"]
  E --> F[Result]

Pipelining avoids writing intermediate tables between operators, so only sinks hold state. When profiling a slow query (see the Performance debugging page), look at where a sink forces a new pipeline, because that is where the query must wait for all input.

Take quiz
Where does a new pipeline begin?
After every scan
At each sink operator
At every WHERE clause
Only at the final RETURN
Which pipeline must finish before HASH_JOIN_PROBE can run?
ORDER BY on another table
The CHECKPOINT pipeline
HASH_JOIN_BUILD
A CSV header reader

41. How does LadybugDB store graph data for fast joins?

Storage is columnar and disk-based. Relationships are held in CSR (compressed sparse row) adjacency lists together with join indices, so expanding from a node to its neighbors reads a contiguous range instead of probing a hash table for every edge.

That matters because a graph pattern is a chain of joins. Ladybug can scan edges in the forward or backward direction, and the optimizer chooses the side that is cheaper given your filters. Columnar layout also lets scans touch only the properties a query needs, and vectorized operators process them in batches.

On the write side, the single-file format and the WAL keep updates durable while checkpoints fold them into the columnar files. The result behaves like an analytical column store with graph-aware indexes, rather than a pointer-chasing record store.

Take quiz
What structure holds relationship adjacency in Ladybug?
A single JSON blob per node
A global hash map in RAM only
Row-oriented heap files with pointers
CSR adjacency lists with join indices
Why does columnar storage help graph scans?
Queries read only the columns they need
It removes the need for a schema
It disables the WAL
It forces single-threaded scans

42. What is factorized query processing and why does it matter for graph queries?

Factorization is a technique where intermediate results are kept as groups instead of fully flattened tuples. Ladybug's docs list it, along with vectorization, among its core execution features.

Consider a person with 1,000 friends, each with 1,000 friends of their own. A flat join writes one tuple for every pair. A factorized form stores the person once, then the friend list, then each friend's list, and expands combinations only when a result is actually needed.

Multi-hop expansions often blow up row counts, so avoiding that repetition saves memory and CPU on join-heavy workloads.

You rarely write anything special to get this; it is an execution strategy chosen by the engine. It pairs with vectorized processing, which handles values in batches, and with worst-case optimal joins for cyclic patterns, to keep multi-hop queries tractable.

Take quiz
What does factorization avoid?
Writing to the WAL
Repeating shared values across flattened join tuples
Parsing Cypher
Using indexes
Which query shape benefits most?
Single-row primary key lookups
CREATE NODE TABLE statements
Multi-hop expansions with large fan-out
CHECKPOINT calls

43. When should you use the HINT clause in LadybugDB?

Use HINT when the optimizer picks a poor join order, or when you want to experiment with plans. It is optional and scoped to a single connected MATCH pattern; every node and relationship variable must be named and appear exactly once.

MATCH (a)-[e]->(b)
WHERE b.ID = 0
HINT a JOIN (e JOIN b)
RETURN *;

The parentheses form a binary join tree. Here (e JOIN b) starts from the filtered b and scans edges backward. In a hash join the left side is the probe and the right is the build side, so swapping sides can change performance.

Do not hint by default, since the optimizer usually does well. Reach for HINT after profiling shows a bad plan, for instance when a selective filter sits at one end of the pattern. The pattern must be connected, so MATCH (a), (b) with no link cannot be hinted. Relationship variables may be recursive, such as -[e*1..3]->.

Take quiz
What shape does a HINT join order take?
A flat comma list
A JSON object
An index name
A binary tree written with parentheses
In a hash join hint, which side is the build side?
The right side
The left side
Neither, hash joins are not allowed
Always the larger table

44. How does MULTI_JOIN enable worst-case optimal joins in LadybugDB?

For cyclic patterns like triangles, MULTI_JOIN forces a worst-case optimal join (WCOJ) operator. Instead of joining edge by edge, it fetches adjacency lists from several edges and intersects them on the shared node.

MATCH (a:person)<-[e1:knows]-(b:person)-[e2:knows]->(c:person),
      (a)-[e3:knows]->(c)
HINT (((a JOIN e1) JOIN b) MULTI_JOIN e2 MULTI_JOIN e3) JOIN c
RETURN COUNT(*)

For each (a, e1, b) tuple, the operator takes the lists for e2 and e3 and sort-merge intersects them to find c. You need at least two edge variables; with one, MULTI_JOIN degrades to a plain JOIN.

Binary joins can create huge intermediate results on triangles and cliques because they match two edges before checking the third. WCOJ avoids this by binding one node at a time through intersection. The shape rule is k tree nodes, where k-1 are edge variables meeting at one node c, plus a probe node that can be any sub-plan. Placing c on either side of the final JOIN chooses the build and probe sides.

Take quiz
What does the WCOJ operator do with adjacency lists?
Concatenates them in file order
Intersects them on the shared node
Hashes them into a Bloom filter only
Copies them to the WAL
What happens if MULTI_JOIN is used with a single relation?
It raises a syntax error
It runs a WCOJ anyway
It is treated as a regular JOIN
It doubles the thread count

45. Explain the lifecycle of a write transaction in LadybugDB?

A write transaction logs its changes to the write-ahead log and, on commit, those records are durable in the .wal file. The main .lbdb file is updated later by a checkpoint.

  1. BEGIN TRANSACTION (or an auto-wrapped statement) takes the single write slot.
  2. Changes are recorded in the WAL.
  3. COMMIT makes them durable; ROLLBACK discards them.
  4. If the WAL exceeds CHECKPOINT_THRESHOLD and no transactions are active, a checkpoint merges it into the database file using a temporary .shadow file.
  5. On success the WAL and shadow files are removed.
flowchart TD
  A[BEGIN] --> B["Write to WAL"]
  B --> C{COMMIT?}
  C -- yes --> D["Durable in WAL"]
  C -- no --> E["Rollback, discard"]
  D --> F{WAL over threshold and idle?}
  F -- yes --> G["Checkpoint via shadow file"]
  G --> H["WAL removed"]

Failure handling follows from this order. A crash before commit leaves nothing behind; a crash after commit but before checkpoint still keeps the change, because the committed WAL record is durable. While a checkpoint runs, the shadow file provides a safe path, and a manual CHECKPOINT errors if any transaction is still active.

Take quiz
When is the main .lbdb file brought up to date?
At every statement
Only on process start
When the tmp file is deleted
At a checkpoint
Which temporary file helps a checkpoint run safely?
.shadow
.lock
.idx
.snapshot

46. How can you support multiple read-write processes on one LadybugDB database?

You cannot open several read-write Database objects on the same file, so route all writers through one process. The documented pattern is an API server that owns a single READ_WRITE Database and serves many clients over HTTP or gRPC.

flowchart LR
  C1["Client 1"] --> S["API server process"]
  C2["Client 2"] --> S
  C3["Client 3"] --> S
  S --> D[(One READ_WRITE Database)]

Concurrent connections inside that server are safe; its transaction manager serializes writes. A REST-style server powered by Express.js is provided. The cost is that results travel as JSON over the network, so embedding Ladybug directly is faster when you can.

Do not point a notebook and a CLI at the same file in read-write mode; the lock error is the warning sign. Read-only analysts can open the file only when no writer process exists. If you need both at once, expose the data through the server instead. Ladybug Explorer likewise should not run against a file held by another READ_WRITE process.

Take quiz
What is the documented design for many writers?
Each client opens its own READ_WRITE object
One server process owns a single READ_WRITE Database
Mixed READ_ONLY and READ_WRITE objects
Copy the file per client and merge later
What is the trade-off of the API server approach?
Transactions are disabled
Cypher is unavailable
Query results pass as JSON over the network
Only CSV import works

47. How does filtered vector search work in LadybugDB?

You build a projected graph that keeps only the nodes passing a predicate, then pass that graph's name as the first argument of QUERY_VECTOR_INDEX instead of the table name.

CALL PROJECT_GRAPH('filtered_book', {'Book': 'n.published_year > 2010'}, []);

CALL QUERY_VECTOR_INDEX('filtered_book', 'book_title_index', $query_vector, 2)
WITH node AS n, distance AS dist
MATCH (n)-[:PublishedBy]->(p:Publisher)
RETURN n.title, p.name ORDER BY dist;

For arbitrary patterns use PROJECT_GRAPH_CYPHER, whose query must return one node variable of the indexed table. Graphs made that way work only for filtered vector search, not with the algo extension.

Projected graphs are not materialized, so defining one is cheap. Predicates use n as the node placeholder and may touch only their own table. You can chain the hits into a MATCH for hybrid search: vector similarity finds entry points, then graph traversal adds context, such as the publishers of the most similar books. Keep the filter predicate simple, because it must reference only the projected table's own properties.

Take quiz
What do you pass instead of the table name for a filtered search?
A WHERE string appended to the query vector
The CSV path
An FTS index name
The name of a projected graph
What can PROJECT_GRAPH_CYPHER graphs NOT be used with?
The algo extension
Filtered vector index search
SHOW_INDEXES
A node-returning MATCH

48. How do mu, ml, efc and efs affect HNSW tuning in LadybugDB?

Ladybug's vector index is a disk-based HNSW graph with two layers: the lower holds all vectors, the upper a sampled subset. Build-time and search-time knobs trade accuracy against cost.

Parameter Effect Default
mu Max degree in upper layer; keep below ml 30
ml Max degree in lower layer 60
pu Share of nodes sampled into the upper layer 0.05
efc Candidates considered while building 200
efs Candidates considered while searching 200
metric cosine, l2, l2sq or dotproduct cosine

Higher values improve recall but enlarge the index and slow builds or queries. cache_embeddings := true speeds construction at the cost of memory.

Start with the defaults, measure recall against exact search on a sample, then raise efs first, because a search-time change needs no rebuild. Only then rebuild with a larger ml or efc. Pick the metric that matches how your embedding model was trained.

Take quiz
Which parameter controls candidates examined at query time?
efc
efs
pu
mu
What is the downside of a higher efc?
Lower recall
Fewer supported metrics
Slower, larger index construction
It disables cosine distance

49. How does LadybugDB handle larger-than-memory workloads?

Use on-disk mode. The docs state that on-disk databases process larger-than-memory workloads, because data lives in the columnar database file and pages are cached by the buffer manager rather than loaded wholesale.

When intermediate data is bigger than available memory, Ladybug spills it to the .tmp file and removes it when the query finishes. For bulk imports, SPILL_TO_DISK (true by default) lets COPY FROM do the same.

CALL spill_to_disk=false;  -- cannot be TRUE in in-memory or read-only mode

In-memory databases cannot spill, so keep them for small datasets.

Give the buffer pool enough room (the Go example sets BufferPoolSize in its system config), keep the database on fast local storage, and reduce THREADS if memory is tight, because every thread needs working memory for its pipeline. If an import is the heavy part, keep SPILL_TO_DISK at its default of true.

Take quiz
Which mode supports larger-than-memory processing?
In-memory mode
Wasm mode only
READ_ONLY in-memory mode
On-disk mode
Where is spilled intermediate data written?
A temporary .tmp file beside the database
The .lbdb file itself
The extension cache
history.txt

50. When would you choose LadybugDB over Neo4j?

Choose Ladybug when you want an embedded engine for join-heavy analytics: knowledge graphs inside an app, recommendation or relationship analysis, notebooks, edge or in-browser use via Wasm. It needs no server and reads Parquet, Arrow, DuckDB and more.

Pick Ladybug when Pick Neo4j when
You want in-process, serverless embedding You need a managed, networked service
Analytics over large graphs dominate You rely on its wider ecosystem and tooling
A fixed schema is acceptable You need schemaless, ad hoc labels and custom indexes
One writer process is enough Many clients write to one server

Remember Ladybug limits: one writer at a time, no user-defined secondary indexes beyond primary keys and the vector and FTS extensions, and a few Cypher gaps.

A quick way to decide is to prototype your top three queries and your write pattern on both systems. Ladybug's Neo4j differences page lists exactly what needs rewriting.

Take quiz
Which need favors LadybugDB?
A multi-tenant network service
An embedded, serverless engine inside your application
Schemaless labels with custom property indexes
Many separate processes writing at once
Which limitation applies to LadybugDB?
No Cypher support
No transactions
Only one write transaction at a time
No disk persistence
«
»

Comments & Discussions