Skip to content

Commit fa4dd9c

Browse files
fm3MichaelBuessemeyercoderabbitai[bot]
authored
Improve volume and segment index performance (#8460)
* WIP: Improve segment index performance * implementation with triple cast on every element, not faster than scala * measure what else is slow * wip cleanup, measure other stuff * wip: use volume bucket buffer, fossil multi-put * logging * WIP rewrite segmentIndexBuffer to include caching, use Set * use new segment index buffer functionality * WIP fossil multi-get for buckets * fix setting volumeBucketDataHasChanged * do not parallelize prefill * use batched multi-get and multi-put * connect to temporaryStore, compress if needed * load buckets from temporarystore, remote fallback layer * load buckets from fallback layer in one request * some cleanup * readerOnly segmentIndexBuffer * cleanup * cleanup * format, remove logging * debug wrong volume values for editable mapping (id seems to be mapped with outdated mapping?) * request correct version of editable mapping data * changelog, migration * cleanup * make cpp types more explicit * clean up cpp code * const, try/catch * clang-format * size_t for index * Do not cache EditableMappingBucketProvider across versions * pr feedback part 1; consistent volumeLayer naming * use fossil multi-get when requesting multiple segmentIds from segment index * use map for more efficient lookups during gathering segmentIndex values * add one more conversion from set to seq for less map overhead * Update webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeSegmentIndexBuffer.scala Co-authored-by: MichaelBuessemeyer <[email protected]> * Update webknossos-tracingstore/app/com/scalableminds/webknossos/tracingstore/tracings/volume/VolumeSegmentIndexBuffer.scala Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: MichaelBuessemeyer <[email protected]> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent e72cf9e commit fa4dd9c

38 files changed

+1164
-766
lines changed

.editorconfig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ insert_final_newline = true
1212
[*.py]
1313
indent_size = 4
1414

15+
[*.cpp]
16+
indent_size = 4
17+
1518
[*.md]
1619
trim_trailing_whitespace = false
1720

CHANGELOG.unreleased.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
1313
### Added
1414
- Added a credit system making payment for long running jobs possible. For now it is in testing phase. [#8352](https://github.com/scalableminds/webknossos/pull/8352)
1515
- The maximum available storage of an organization is now enforced during upload. [#8385](https://github.com/scalableminds/webknossos/pull/8385)
16+
- Performance improvements for volume annotation save requests. [#8460](https://github.com/scalableminds/webknossos/pull/8460)
1617

1718
### Changed
1819

@@ -22,7 +23,8 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
2223
- Re-enabled jobs planned to be paid with credits for organizations without a paid plan. [#8478](https://github.com/scalableminds/webknossos/pull/8478)
2324
- Fixed that the dataset extent tooltip in the right details bar in the dashboard did not properly update when switching datasets. [#8477](https://github.com/scalableminds/webknossos/pull/8477)
2425
- Fixed a bug where task creation with volume zip as input would fail. [#8468](https://github.com/scalableminds/webknossos/pull/8468)
25-
- Fixed that a warning message about a newer version of an annotation was shown multiple times. [#8486](https://github.com/scalableminds/webknossos/pull/8486)
26+
- Fixed a bug where segment statistics would sometimes be wrong in case of an on-disk segmentation fallback layer with segment index file. [#8460](https://github.com/scalableminds/webknossos/pull/8460)
27+
- Fixed a bug where sometimes outdated segment statistics would be displayed. [#8460](https://github.com/scalableminds/webknossos/pull/8460)
2628

2729
### Removed
2830

MIGRATIONS.unreleased.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md).
88
## Unreleased
99
[Commits](https://github.com/scalableminds/webknossos/compare/25.03.0...HEAD)
1010

11+
- New FossilDB version `0.1.37` (`master__525:` on dockerhub) is required. [#8460](https://github.com/scalableminds/webknossos/pull/8460)
12+
1113
### Postgres Evolutions:
1214
- [129-credit-transactions.sql](conf/evolutions/129-credit-transactions.sql)
1315
- [130-replace-text-types.sql](conf/evolutions/130-replace-text-types.sql)

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ services:
273273

274274
# FossilDB
275275
fossildb:
276-
image: scalableminds/fossildb:master__510
276+
image: scalableminds/fossildb:master__525
277277
command:
278278
- fossildb
279279
- -c

fossildb/version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
0.1.34
1+
0.1.37

tools/proxy/proxy.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ proxy.on("error", (err, req, res) => {
9696
<head>
9797
<title>503 Service Unavailable</title>
9898
<script type="text/javascript">
99-
let countdown = 5;
99+
let countdown = 3;
100100
function updateCountdown() {
101101
document.getElementById('countdown').textContent = countdown;
102102
countdown--;
@@ -114,7 +114,7 @@ proxy.on("error", (err, req, res) => {
114114
<body>
115115
<h1>Bad gateway</h1>
116116
<p>The server might still be starting up, please try again in a few seconds or check console output.</p>
117-
<p>Reloading in <span id="countdown">5</span> seconds...</p>
117+
<p>Reloading in <span id="countdown">3</span> seconds...</p>
118118
</body>
119119
</html>
120120
`);

util/src/main/scala/com/scalableminds/util/tools/BoxImplicits.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@ trait BoxImplicits {
2323
val failures = boxes.collect { case f: Failure => f }
2424
if (failures.isEmpty) None else Some(failures.map(_.msg))
2525
}
26+
27+
def assertNoFailure(boxes: Seq[Box[_]]): Box[Unit] = {
28+
val firstFailure = boxes.find {
29+
case _: Failure => true
30+
case _ => false
31+
}
32+
firstFailure match {
33+
case Some(failure) => Failure(s"At least one failure contained in list of ${boxes.length} boxes: $failure")
34+
case None => Full(())
35+
}
36+
}
37+
2638
}
2739

2840
object BoxImplicits extends BoxImplicits
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.scalableminds.webknossos.datastore.helpers
2+
3+
import com.github.sbt.jni.nativeLoader
4+
5+
@nativeLoader("webknossosJni0")
6+
class NativeBucketScanner() {
7+
@native def collectSegmentIds(bucketBytes: Array[Byte], bytesPerElement: Int, isSigned: Boolean): Array[Long]
8+
}

webknossos-datastore/app/com/scalableminds/webknossos/datastore/helpers/SegmentStatistics.scala

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.scalableminds.webknossos.datastore.helpers
22

33
import com.scalableminds.util.geometry.{BoundingBox, Vec3Int}
44
import com.scalableminds.util.tools.{Fox, FoxImplicits}
5-
import com.scalableminds.webknossos.datastore.geometry.ListOfVec3IntProto
5+
import com.scalableminds.webknossos.datastore.geometry.Vec3IntProto
66
import com.scalableminds.webknossos.datastore.models.datasource.DataLayer
77
import com.scalableminds.webknossos.datastore.models.{AdditionalCoordinate, SegmentInteger}
88
import play.api.libs.json.{Json, OFormat}
@@ -22,15 +22,15 @@ trait SegmentStatistics extends ProtoGeometryImplicits with FoxImplicits {
2222
def calculateSegmentVolume(segmentId: Long,
2323
mag: Vec3Int,
2424
additionalCoordinates: Option[Seq[AdditionalCoordinate]],
25-
getBucketPositions: (Long, Vec3Int) => Fox[ListOfVec3IntProto],
25+
getBucketPositions: (Long, Vec3Int) => Fox[Set[Vec3IntProto]],
2626
getTypedDataForBucketPosition: (
2727
Vec3Int,
2828
Vec3Int,
2929
Option[Seq[AdditionalCoordinate]]) => Fox[Array[SegmentInteger]])(
3030
implicit ec: ExecutionContext): Fox[Long] =
3131
for {
32-
bucketPositionsProtos: ListOfVec3IntProto <- getBucketPositions(segmentId, mag)
33-
bucketPositionsInMag = bucketPositionsProtos.values.map(vec3IntFromProto)
32+
bucketPositionsProtos: Set[Vec3IntProto] <- getBucketPositions(segmentId, mag)
33+
bucketPositionsInMag = bucketPositionsProtos.map(vec3IntFromProto)
3434
volumeBoxes <- Fox.serialSequence(bucketPositionsInMag.toList)(bucketPosition =>
3535
for {
3636
dataTyped: Array[SegmentInteger] <- getTypedDataForBucketPosition(bucketPosition, mag, additionalCoordinates)
@@ -43,15 +43,15 @@ trait SegmentStatistics extends ProtoGeometryImplicits with FoxImplicits {
4343
def calculateSegmentBoundingBox(segmentId: Long,
4444
mag: Vec3Int,
4545
additionalCoordinates: Option[Seq[AdditionalCoordinate]],
46-
getBucketPositions: (Long, Vec3Int) => Fox[ListOfVec3IntProto],
46+
getBucketPositions: (Long, Vec3Int) => Fox[Set[Vec3IntProto]],
4747
getTypedDataForBucketPosition: (
4848
Vec3Int,
4949
Vec3Int,
5050
Option[Seq[AdditionalCoordinate]]) => Fox[Array[SegmentInteger]])(
5151
implicit ec: ExecutionContext): Fox[BoundingBox] =
5252
for {
53-
allBucketPositions: ListOfVec3IntProto <- getBucketPositions(segmentId, mag)
54-
relevantBucketPositions = filterOutInnerBucketPositions(allBucketPositions)
53+
allBucketPositions: Set[Vec3IntProto] <- getBucketPositions(segmentId, mag)
54+
relevantBucketPositions = filterOutInnerBucketPositions(allBucketPositions.toSeq)
5555
boundingBoxMutable = scala.collection.mutable.ListBuffer[Int](Int.MaxValue,
5656
Int.MaxValue,
5757
Int.MaxValue,
@@ -78,16 +78,16 @@ trait SegmentStatistics extends ProtoGeometryImplicits with FoxImplicits {
7878
)
7979

8080
// The buckets that form the outer walls of the bounding box are relevant (in each of those the real min/max voxel positions could occur)
81-
private def filterOutInnerBucketPositions(bucketPositions: ListOfVec3IntProto): Seq[Vec3Int] =
82-
if (bucketPositions.values.isEmpty) List.empty
81+
private def filterOutInnerBucketPositions(bucketPositions: Seq[Vec3IntProto]): Seq[Vec3Int] =
82+
if (bucketPositions.isEmpty) Seq.empty
8383
else {
84-
val minX = bucketPositions.values.map(_.x).min
85-
val minY = bucketPositions.values.map(_.y).min
86-
val minZ = bucketPositions.values.map(_.z).min
87-
val maxX = bucketPositions.values.map(_.x).max
88-
val maxY = bucketPositions.values.map(_.y).max
89-
val maxZ = bucketPositions.values.map(_.z).max
90-
bucketPositions.values
84+
val minX = bucketPositions.map(_.x).min
85+
val minY = bucketPositions.map(_.y).min
86+
val minZ = bucketPositions.map(_.z).min
87+
val maxX = bucketPositions.map(_.x).max
88+
val maxY = bucketPositions.map(_.y).max
89+
val maxZ = bucketPositions.map(_.z).max
90+
bucketPositions
9191
.filter(pos =>
9292
pos.x == minX || pos.x == maxX || pos.y == minY || pos.y == maxY || pos.z == minZ || pos.z == maxZ)
9393
.map(vec3IntFromProto)

webknossos-datastore/app/com/scalableminds/webknossos/datastore/services/SegmentIndexFileService.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import com.scalableminds.util.geometry.{BoundingBox, Vec3Int}
55
import com.scalableminds.util.io.PathUtils
66
import com.scalableminds.util.tools.{Fox, FoxImplicits}
77
import com.scalableminds.webknossos.datastore.DataStoreConfig
8-
import com.scalableminds.webknossos.datastore.geometry.ListOfVec3IntProto
8+
import com.scalableminds.webknossos.datastore.geometry.Vec3IntProto
99
import com.scalableminds.webknossos.datastore.helpers.SegmentStatistics
1010
import com.scalableminds.webknossos.datastore.models.datasource.DataLayer
1111
import com.scalableminds.webknossos.datastore.models.requests.{
@@ -176,7 +176,7 @@ class SegmentIndexFileService @Inject()(config: DataStoreConfig,
176176
organizationId: String,
177177
datasetDirectoryName: String,
178178
dataLayerName: String,
179-
mappingName: Option[String])(segmentOrAgglomerateId: Long, mag: Vec3Int): Fox[ListOfVec3IntProto] =
179+
mappingName: Option[String])(segmentOrAgglomerateId: Long, mag: Vec3Int): Fox[Set[Vec3IntProto]] =
180180
for {
181181
segmentIds <- getSegmentIdsForAgglomerateIdIfNeeded(organizationId,
182182
datasetDirectoryName,
@@ -185,8 +185,8 @@ class SegmentIndexFileService @Inject()(config: DataStoreConfig,
185185
mappingName)
186186
positionsPerSegment <- Fox.serialCombined(segmentIds)(segmentId =>
187187
getBucketPositions(organizationId, datasetDirectoryName, dataLayerName, segmentId, mag))
188-
positionsCollected = positionsPerSegment.flatten.distinct
189-
} yield ListOfVec3IntProto.of(positionsCollected.map(vec3IntToProto))
188+
positionsCollected = positionsPerSegment.flatten.toSet.map(vec3IntToProto)
189+
} yield positionsCollected
190190

191191
private def getBucketPositions(organizationId: String,
192192
datasetDirectoryName: String,

0 commit comments

Comments
 (0)