@@ -322,11 +322,13 @@ func (tx *Txn) GetPartitionMetadata(
322322 errors .Wrapf (err , "getting partition metadata for %d" , partitionKey )
323323 }
324324
325- metadata , err := tx .extractMetadataFromKVResult (treeKey , partitionKey , & b .Results [0 ])
326- if err != nil {
327- return cspann.PartitionMetadata {}, err
325+ // If we're preparing to update the root partition, then lazily create its
326+ // metadata if it does not yet exist.
327+ if forUpdate && partitionKey == cspann .RootKey && b .Results [0 ].Rows [0 ].Value == nil {
328+ return tx .createRootPartition (ctx , metadataKey )
328329 }
329- return metadata , nil
330+
331+ return tx .extractMetadataFromKVResult (treeKey , partitionKey , & b .Results [0 ])
330332}
331333
332334// AddToPartition implements the cspann.Txn interface.
@@ -575,6 +577,42 @@ func (tx *Txn) GetFullVectors(
575577 return err
576578}
577579
580+ // createRootPartition uses the KV CPut operation to create metadata for the
581+ // root partition, and then returns that metadata.
582+ //
583+ // NOTE: CPut always "sees" the latest write of the metadata record, even if the
584+ // timestamp of that write is higher than this transaction's.
585+ func (tx * Txn ) createRootPartition (
586+ ctx context.Context , metadataKey roachpb.Key ,
587+ ) (cspann.PartitionMetadata , error ) {
588+ b := tx .kv .NewBatch ()
589+ metadata := cspann.PartitionMetadata {Level : cspann .LeafLevel , Centroid : tx .store .emptyVec }
590+ encoded , err := EncodePartitionMetadata (metadata .Level , metadata .Centroid )
591+ if err != nil {
592+ return cspann.PartitionMetadata {}, err
593+ }
594+
595+ // Use CPutAllowingIfNotExists in order to handle the case where the same
596+ // transaction inserts multiple vectors (e.g. multiple VALUES rows). In that
597+ // case, the first row will trigger creation of the metadata record. However,
598+ // subsequent inserts will not be able to "see" this record, since they will
599+ // read at a lower sequence number than the metadata record was written.
600+ // However, CPutAllowingIfNotExists will read at the higher sequence number
601+ // and see that the record was already created.
602+ //
603+ // On the other hand, if a different transaction wrote the record, it will
604+ // have a higher timestamp, and that will trigger a WriteTooOld error.
605+ // Transactions which lose that race need to be refreshed.
606+ var roachval roachpb.Value
607+ roachval .SetBytes (encoded )
608+ b .CPutAllowingIfNotExists (metadataKey , & roachval , roachval .TagAndDataBytes ())
609+ if err := tx .kv .Run (ctx , b ); err != nil {
610+ // Lost the race to a different transaction.
611+ return cspann.PartitionMetadata {}, errors .Wrapf (err , "creating root partition metadata" )
612+ }
613+ return metadata , nil
614+ }
615+
578616// QuantizeAndEncode quantizes the given vector (which has already been
579617// randomized by the caller) with respect to the given centroid. It returns the
580618// encoded form of that quantized vector.
0 commit comments