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  }