November 11, 2024·4 min read·engineeringdragonflysearchsnapshotsgcs

Dragonfly adds prefix matching for search tags

Dragonfly 1.25 adds prefix queries over tag values and GCS snapshot storage. A change to the snapshot output path also removes an intermediate record channel while preserving serialized order.

A tag query can ask for an exact category or for a family of categories sharing a prefix. Those are different index operations, even when the application expresses both through the same search field. Dragonfly's tag-prefix implementation adds the latter form. Its tests combine exact and prefix alternatives in one tag expression and distinguish prefix matches from occurrences later in a value.

The change ships in Dragonfly 1.25.0, released on November 11. The release also adds Google Cloud Storage snapshot support and changes the snapshot and replication output path. These are separate additions, but both concern avoiding application-side work around a database operation that the engine can express directly.

I prefer Dragonfly where one process can provide the required cache capacity with fewer operational components. Tag-prefix queries fit that preference when they let an application query its existing index directly. Their usefulness depends on the precise matching semantics, since a prefix operator is not a general substring search.

A prefix applies to an individual tag alternative

The prefix-query test uses a colour field and an expression combining green*, orange, and yellow*. The wildcard applies to the first and third alternatives. Here, the middle alternative remains exact. This lets an application mix a selected category with families of related names in one expression.

FT.SEARCH items "@color:{green* | orange | yellow*}"

The test accepts green, greenish, and green-forestish, alongside corresponding yellow-prefixed values and exact orange. It rejects orangeish, because that alternative has no wildcard. It also rejects darkgreen and light-yellow, whose matching text is not at the beginning of the value.

Those negative cases define the feature as much as the successful ones. A user interface offering category-prefix filtering can rely on the distinction between green* and an arbitrary occurrence of green. Treating the expression as a substring search would return a different set of documents. The implementation test makes the intended boundary visible without relying on a performance measurement.

The feature also changes how much vocabulary an application has to enumerate itself. An exact-only query over several related tags requires the caller to supply those tags individually. A prefix alternative can describe that set by its naming rule. That is useful only when the names themselves encode the grouping the application intends.

GCS becomes a snapshot destination and source

The initial GCS save/load change demonstrates a bucket path in the server's --dir option. Its example saves a Dragonfly-format snapshot with an explicit basename and loads the corresponding summary object. The pull request initially qualifies support as manual save and load, so it is necessary to examine the follow-up included in the release as well.

--dir=gs://bucket/path

With that directory configured and access to the bucket available, the source demonstrates these protocol commands. The bucket and path identify the configured storage location.

SET foo bar
SAVE DF gsdump
DFLY LOAD gs://bucket/path/gsdump-summary.dfs

The follow-up storage change implements GCS snapshot discovery where the earlier code returned an unsupported error. It lists objects under the bucket prefix, passes their names and modification times to the matching logic, and constructs a GCS URL for the selected object. A failure to list or locate a matching snapshot returns a corresponding error.

This separates two capabilities that can otherwise be conflated. Reading a snapshot from a fully specified object path is one operation. Finding the snapshot associated with a configured directory and filename is another. Both changes appear in the release, so describing the final implementation solely through the earlier manual example would omit the discovery work.

I find a native object-storage path useful because snapshot placement becomes part of the engine's storage configuration. The source establishes saving, loading, and discovery without requiring an application to imitate those operations through local-file handling. It does not make bucket access implicit or establish a particular retention policy, so those remain separate deployment choices.

Snapshot output no longer requires a record channel

The snapshot output change replaces the snapshot's record-channel destination with callbacks for pushing serialized data and reporting completion. The release identifies efficiency around large entries as the motivation for the included replication and snapshotting improvements. Its patch shows the structural change without providing a general numeric speedup.

Previously, the snapshot represented each outgoing record with an identifier and value before pushing it to the channel. The new path passes the serialized string to an output callback. Snapshot memory accounting consequently stops adding the removed channel's capacity to the serializer buffer capacity. This changes the intermediate buffering arrangement, not the contents the snapshot is meant to represent.

Ordering still requires coordination. The implementation retains record sequence numbers and waits until a record is next before calling the output callback. After the callback returns, it advances the last-pushed identifier and wakes waiting producers. Removing the channel therefore does not mean removing the ordering requirement between producers.

Completion also becomes explicit through the callback supplied to the snapshot. That fits the new interface, where the snapshot no longer owns a channel whose closing operation previously signalled progress. The patch keeps the possibility of blocking in the push path, so it should not be described as making all serialization nonblocking.

Rank queries can return their score together

Another addition in this release supports WITHSCORE on ZRANK and ZREVRANK. Its tests expect an array containing the rank and score when the member exists. Missing members or keys still produce a nil reply. The option is singular, and the tests reject WITHSCORES.

The author explains that the internal sorted-map method avoids a second lookup in the score map. At the protocol level, the client receives the score with the rank in one operation. This is a smaller improvement than the new storage destination, but it follows the same application-facing principle. The server exposes the combined result it can obtain through its own data structures, while the tests define the reply shape and missing-value behaviour.