github.com/janelia-flyem/dvid@v1.0.0/datatype/labelarray/write.go (about) 1 package labelarray 2 3 import ( 4 "fmt" 5 "sync" 6 7 "github.com/janelia-flyem/dvid/datastore" 8 "github.com/janelia-flyem/dvid/datatype/common/downres" 9 "github.com/janelia-flyem/dvid/datatype/common/labels" 10 "github.com/janelia-flyem/dvid/datatype/imageblk" 11 "github.com/janelia-flyem/dvid/dvid" 12 "github.com/janelia-flyem/dvid/server" 13 "github.com/janelia-flyem/dvid/storage" 14 ) 15 16 type putOperation struct { 17 data []byte // the full label volume sent to PUT 18 scale uint8 19 subvol *dvid.Subvolume 20 indexZYX dvid.IndexZYX 21 version dvid.VersionID 22 mutate bool // if false, we just ingest without needing to GET previous value 23 mutID uint64 // should be unique within a server's uptime. 24 downresMut *downres.Mutation 25 blockCh chan blockChange 26 } 27 28 // PutLabels persists voxels from a subvolume into the storage engine. This involves transforming 29 // a supplied volume of uint64 with known geometry into many labels.Block that tiles the subvolume. 30 // Messages are sent to subscribers for ingest or mutate events. 31 func (d *Data) PutLabels(v dvid.VersionID, subvol *dvid.Subvolume, data []byte, roiname dvid.InstanceName, mutate bool) error { 32 if subvol.DataShape().ShapeDimensions() != 3 { 33 return fmt.Errorf("cannot store labels for data %q in non 3D format", d.DataName()) 34 } 35 36 // Make sure data is block-aligned 37 if !dvid.BlockAligned(subvol, d.BlockSize()) { 38 return fmt.Errorf("cannot store labels for data %q in non-block aligned geometry %s -> %s", d.DataName(), subvol.StartPoint(), subvol.EndPoint()) 39 } 40 41 // Make sure the received data buffer is of appropriate size. 42 labelBytes := subvol.Size().Prod() * 8 43 if labelBytes != int64(len(data)) { 44 return fmt.Errorf("expected %d bytes for data %q label PUT but only received %d bytes", labelBytes, d.DataName(), len(data)) 45 } 46 47 r, err := imageblk.GetROI(v, roiname, subvol) 48 if err != nil { 49 return err 50 } 51 52 // Only do one request at a time, although each request can start many goroutines. 53 if subvol.NumVoxels() > 256*256*256 { 54 server.LargeMutationMutex.Lock() 55 defer server.LargeMutationMutex.Unlock() 56 } 57 58 // Keep track of changing extents, labels and mark repo as dirty if changed. 59 var extentChanged bool 60 defer func() { 61 if extentChanged { 62 err := datastore.SaveDataByVersion(v, d) 63 if err != nil { 64 dvid.Infof("Error in trying to save repo on change: %v\n", err) 65 } 66 } 67 }() 68 69 // Track point extents 70 ctx := datastore.NewVersionedCtx(d, v) 71 extents := d.Extents() 72 if extents.AdjustPoints(subvol.StartPoint(), subvol.EndPoint()) { 73 extentChanged = true 74 if err := d.PostExtents(ctx, extents.MinPoint, extents.MaxPoint); err != nil { 75 return err 76 } 77 } 78 79 // extract buffer interface if it exists 80 var putbuffer storage.RequestBuffer 81 store, err := datastore.GetOrderedKeyValueDB(d) 82 if err != nil { 83 return fmt.Errorf("Data type imageblk had error initializing store: %v\n", err) 84 } 85 if req, ok := store.(storage.KeyValueRequester); ok { 86 putbuffer = req.NewBuffer(ctx) 87 } 88 89 // Iterate through index space for this data. 90 mutID := d.NewMutationID() 91 downresMut := downres.NewMutation(d, v, mutID) 92 93 wg := new(sync.WaitGroup) 94 95 blockCh := make(chan blockChange, 100) 96 go d.aggregateBlockChanges(v, blockCh) 97 98 blocks := 0 99 for it, err := subvol.NewIndexZYXIterator(d.BlockSize()); err == nil && it.Valid(); it.NextSpan() { 100 i0, i1, err := it.IndexSpan() 101 if err != nil { 102 close(blockCh) 103 return err 104 } 105 ptBeg := i0.Duplicate().(dvid.ChunkIndexer) 106 ptEnd := i1.Duplicate().(dvid.ChunkIndexer) 107 108 begX := ptBeg.Value(0) 109 endX := ptEnd.Value(0) 110 111 if extents.AdjustIndices(ptBeg, ptEnd) { 112 extentChanged = true 113 } 114 115 wg.Add(int(endX-begX) + 1) 116 c := dvid.ChunkPoint3d{begX, ptBeg.Value(1), ptBeg.Value(2)} 117 for x := begX; x <= endX; x++ { 118 c[0] = x 119 curIndex := dvid.IndexZYX(c) 120 121 // Don't PUT if this index is outside a specified ROI 122 if r != nil && r.Iter != nil && !r.Iter.InsideFast(curIndex) { 123 wg.Done() 124 continue 125 } 126 127 putOp := &putOperation{ 128 data: data, 129 subvol: subvol, 130 indexZYX: curIndex, 131 version: v, 132 mutate: mutate, 133 mutID: mutID, 134 downresMut: downresMut, 135 blockCh: blockCh, 136 } 137 server.CheckChunkThrottling() 138 go d.putChunk(putOp, wg, putbuffer) 139 blocks++ 140 } 141 } 142 wg.Wait() 143 close(blockCh) 144 145 // if a bufferable op, flush 146 if putbuffer != nil { 147 putbuffer.Flush() 148 } 149 150 return downresMut.Execute() 151 } 152 153 // Puts a chunk of data as part of a mapped operation. 154 // Only some multiple of the # of CPU cores can be used for chunk handling before 155 // it waits for chunk processing to abate via the buffered server.HandlerToken channel. 156 func (d *Data) putChunk(op *putOperation, wg *sync.WaitGroup, putbuffer storage.RequestBuffer) { 157 defer func() { 158 // After processing a chunk, return the token. 159 server.HandlerToken <- 1 160 161 // Notify the requestor that this chunk is done. 162 wg.Done() 163 }() 164 165 bcoord := op.indexZYX.ToIZYXString() 166 ctx := datastore.NewVersionedCtx(d, op.version) 167 168 // If we are mutating, get the previous label Block 169 var scale uint8 170 var oldBlock *labels.PositionedBlock 171 if op.mutate { 172 var err error 173 if oldBlock, err = d.getLabelBlock(ctx, scale, bcoord); err != nil { 174 dvid.Errorf("Unable to load previous block in %q, key %v: %v\n", d.DataName(), bcoord, err) 175 return 176 } 177 } 178 179 // Get the current label Block from the received label array 180 blockSize, ok := d.BlockSize().(dvid.Point3d) 181 if !ok { 182 dvid.Errorf("can't putChunk() on data %q with non-3d block size: %s", d.DataName(), d.BlockSize()) 183 return 184 } 185 curBlock, err := labels.SubvolumeToBlock(op.subvol, op.data, op.indexZYX, blockSize) 186 if err != nil { 187 dvid.Errorf("error creating compressed block from label array at %s", op.subvol) 188 return 189 } 190 go d.updateBlockMaxLabel(op.version, curBlock) 191 192 blockData, _ := curBlock.MarshalBinary() 193 serialization, err := dvid.SerializeData(blockData, d.Compression(), d.Checksum()) 194 if err != nil { 195 dvid.Errorf("Unable to serialize block in %q: %v\n", d.DataName(), err) 196 return 197 } 198 199 store, err := datastore.GetOrderedKeyValueDB(d) 200 if err != nil { 201 dvid.Errorf("Data type imageblk had error initializing store: %v\n", err) 202 return 203 } 204 205 callback := func(ready chan error) { 206 if ready != nil { 207 if resperr := <-ready; resperr != nil { 208 dvid.Errorf("Unable to PUT voxel data for block %v: %v\n", bcoord, resperr) 209 return 210 } 211 } 212 var event string 213 var delta interface{} 214 if oldBlock != nil && op.mutate { 215 event = labels.MutateBlockEvent 216 block := MutatedBlock{op.mutID, bcoord, &(oldBlock.Block), curBlock} 217 d.handleBlockMutate(op.version, op.blockCh, block) 218 delta = block 219 } else { 220 event = labels.IngestBlockEvent 221 block := IngestedBlock{op.mutID, bcoord, curBlock} 222 d.handleBlockIndexing(op.version, op.blockCh, block) 223 delta = block 224 } 225 if err := op.downresMut.BlockMutated(bcoord, curBlock); err != nil { 226 dvid.Errorf("data %q publishing downres: %v\n", d.DataName(), err) 227 } 228 evt := datastore.SyncEvent{d.DataUUID(), event} 229 msg := datastore.SyncMessage{event, op.version, delta} 230 if err := datastore.NotifySubscribers(evt, msg); err != nil { 231 dvid.Errorf("Unable to notify subscribers of event %s in %s\n", event, d.DataName()) 232 } 233 } 234 235 // put data -- use buffer if available 236 tk := NewBlockTKeyByCoord(op.scale, bcoord) 237 if putbuffer != nil { 238 ready := make(chan error, 1) 239 go callback(ready) 240 putbuffer.PutCallback(ctx, tk, serialization, ready) 241 } else { 242 if err := store.Put(ctx, tk, serialization); err != nil { 243 dvid.Errorf("Unable to PUT voxel data for block %s: %v\n", bcoord, err) 244 return 245 } 246 callback(nil) 247 } 248 } 249 250 // Writes a XY image into the blocks that intersect it. This function assumes the 251 // blocks have been allocated and if necessary, filled with old data. 252 func (d *Data) writeXYImage(v dvid.VersionID, vox *imageblk.Voxels, b storage.TKeyValues) (extentChanged bool, err error) { 253 254 // Setup concurrency in image -> block transfers. 255 var wg sync.WaitGroup 256 defer wg.Wait() 257 258 // Iterate through index space for this data using ZYX ordering. 259 blockSize := d.BlockSize() 260 var startingBlock int32 261 262 for it, err := vox.NewIndexIterator(blockSize); err == nil && it.Valid(); it.NextSpan() { 263 indexBeg, indexEnd, err := it.IndexSpan() 264 if err != nil { 265 return extentChanged, err 266 } 267 268 ptBeg := indexBeg.Duplicate().(dvid.ChunkIndexer) 269 ptEnd := indexEnd.Duplicate().(dvid.ChunkIndexer) 270 271 // Track point extents 272 if d.Extents().AdjustIndices(ptBeg, ptEnd) { 273 extentChanged = true 274 } 275 276 // Do image -> block transfers in concurrent goroutines. 277 begX := ptBeg.Value(0) 278 endX := ptEnd.Value(0) 279 280 server.CheckChunkThrottling() 281 wg.Add(1) 282 go func(blockNum int32) { 283 c := dvid.ChunkPoint3d{begX, ptBeg.Value(1), ptBeg.Value(2)} 284 for x := begX; x <= endX; x++ { 285 c[0] = x 286 curIndex := dvid.IndexZYX(c) 287 b[blockNum].K = NewBlockTKey(0, &curIndex) 288 289 // Write this slice data into the block. 290 vox.WriteBlock(&(b[blockNum]), blockSize) 291 blockNum++ 292 } 293 server.HandlerToken <- 1 294 wg.Done() 295 }(startingBlock) 296 297 startingBlock += (endX - begX + 1) 298 } 299 return 300 } 301 302 // KVWriteSize is the # of key-value pairs we will write as one atomic batch write. 303 const KVWriteSize = 500 304 305 // TODO -- Clean up all the writing and simplify now that we have block-aligned writes. 306 // writeBlocks ingests blocks of voxel data asynchronously using batch writes. 307 func (d *Data) writeBlocks(v dvid.VersionID, b storage.TKeyValues, wg1, wg2 *sync.WaitGroup) error { 308 batcher, err := datastore.GetKeyValueBatcher(d) 309 if err != nil { 310 return err 311 } 312 313 preCompress, postCompress := 0, 0 314 blockSize := d.BlockSize().(dvid.Point3d) 315 316 ctx := datastore.NewVersionedCtx(d, v) 317 evt := datastore.SyncEvent{d.DataUUID(), labels.IngestBlockEvent} 318 319 server.CheckChunkThrottling() 320 blockCh := make(chan blockChange, 100) 321 go d.aggregateBlockChanges(v, blockCh) 322 go func() { 323 defer func() { 324 wg1.Done() 325 wg2.Done() 326 dvid.Debugf("Wrote voxel blocks. Before %s: %d bytes. After: %d bytes\n", d.Compression(), preCompress, postCompress) 327 close(blockCh) 328 server.HandlerToken <- 1 329 }() 330 331 mutID := d.NewMutationID() 332 batch := batcher.NewBatch(ctx) 333 for i, block := range b { 334 preCompress += len(block.V) 335 lblBlock, err := labels.MakeBlock(block.V, blockSize) 336 if err != nil { 337 dvid.Errorf("unable to compute dvid block compression in %q: %v\n", d.DataName(), err) 338 return 339 } 340 go d.updateBlockMaxLabel(v, lblBlock) 341 342 compressed, _ := lblBlock.MarshalBinary() 343 serialization, err := dvid.SerializeData(compressed, d.Compression(), d.Checksum()) 344 if err != nil { 345 dvid.Errorf("Unable to serialize block in %q: %v\n", d.DataName(), err) 346 return 347 } 348 postCompress += len(serialization) 349 batch.Put(block.K, serialization) 350 351 _, indexZYX, err := DecodeBlockTKey(block.K) 352 if err != nil { 353 dvid.Errorf("Unable to recover index from block key: %v\n", block.K) 354 return 355 } 356 357 block := IngestedBlock{mutID, indexZYX.ToIZYXString(), lblBlock} 358 d.handleBlockIndexing(v, blockCh, block) 359 360 msg := datastore.SyncMessage{labels.IngestBlockEvent, v, block} 361 if err := datastore.NotifySubscribers(evt, msg); err != nil { 362 dvid.Errorf("Unable to notify subscribers of ChangeBlockEvent in %s\n", d.DataName()) 363 return 364 } 365 366 // Check if we should commit 367 if i%KVWriteSize == KVWriteSize-1 { 368 if err := batch.Commit(); err != nil { 369 dvid.Errorf("Error on trying to write batch: %v\n", err) 370 return 371 } 372 batch = batcher.NewBatch(ctx) 373 } 374 } 375 if err := batch.Commit(); err != nil { 376 dvid.Errorf("Error on trying to write batch: %v\n", err) 377 return 378 } 379 }() 380 return nil 381 }