From 8a8c5e3e3032d1daa11ff4e404095f743c176d86 Mon Sep 17 00:00:00 2001
From: Bob Evans <robert.evans25@gmail.com>
Date: Thu, 24 Oct 2024 17:26:06 -0400
Subject: [PATCH 1/2] feat: Removed children from segments.

---
 lib/db/parsed-statement.js                    |   2 +-
 lib/instrumentation/nextjs/utils.js           |  13 -
 lib/metrics/recorders/custom.js               |   2 +-
 lib/metrics/recorders/generic.js              |   2 +-
 lib/metrics/recorders/http.js                 |   2 +-
 lib/metrics/recorders/http_external.js        |   2 +-
 lib/metrics/recorders/message-transaction.js  |   2 +-
 lib/metrics/recorders/other.js                |   2 +-
 lib/shim/datastore-shim.js                    |   2 +-
 lib/shim/shim.js                              |   3 +-
 lib/shim/webframework-shim/middleware.js      |   2 +-
 .../trace/exclusive-time-calculator.js        |   6 +-
 lib/transaction/trace/index.js                | 156 +++++---
 lib/transaction/trace/segment.js              | 168 +-------
 lib/transaction/tracer/index.js               |   9 +-
 test/integration/cat/cat.tap.js               |   8 +-
 test/integration/core/crypto.tap.js           |  12 +-
 test/integration/core/fs.tap.js               |  21 +-
 test/integration/core/net.tap.js              |  62 +--
 test/integration/core/timers.tap.js           |  31 +-
 test/integration/core/verify.js               |  14 +-
 test/integration/instrumentation/fetch.tap.js |  25 +-
 .../instrumentation/http-outbound.tap.js      |  36 +-
 test/integration/transaction/tracer.tap.js    |  71 ++--
 test/lib/agent_helper.js                      |   8 -
 test/lib/custom-assertions.js                 |  29 +-
 test/lib/custom-tap-assertions.js             |  46 ---
 test/lib/metrics_helper.js                    |  28 +-
 test/unit/agent/agent.test.js                 |   5 -
 .../api-start-background-transaction.test.js  |   5 +-
 .../api/api-start-web-transaction.test.js     |   5 +-
 .../instrumentation/core/promises.test.js     |   2 +-
 .../instrumentation/http/outbound.test.js     |  24 +-
 .../instrumentation/prisma-client.test.js     |   6 +-
 test/unit/instrumentation/redis.test.js       |   2 +-
 test/unit/llm-events/openai/common.js         |   6 +-
 test/unit/shim/datastore-shim.test.js         |  40 +-
 test/unit/shim/message-shim.test.js           |  36 +-
 test/unit/shim/shim.test.js                   |  62 +--
 test/unit/spans/span-event.test.js            |  10 +-
 test/unit/spans/streaming-span-event.test.js  |  10 +-
 test/unit/transaction/trace/index.test.js     | 367 +++++++-----------
 test/unit/transaction/trace/segment.test.js   | 150 ++-----
 test/unit/transaction/tracer.test.js          |  15 +
 test/versioned-external/external-repos.js     |   2 +-
 test/versioned/amqplib/amqp-utils.js          |  28 +-
 test/versioned/amqplib/callback.test.js       |   2 +-
 test/versioned/amqplib/promises.test.js       |   2 +-
 .../aws-sdk-v2/amazon-dax-client.test.js      |  12 +-
 test/versioned/aws-sdk-v2/dynamodb.test.js    |  12 +-
 .../aws-sdk-v2/http-services.test.js          |   6 +-
 test/versioned/aws-sdk-v2/s3.test.js          |   6 +-
 test/versioned/aws-sdk-v2/sns.test.js         |  12 +-
 test/versioned/aws-sdk-v2/sqs.test.js         |  12 +-
 .../bedrock-chat-completions.test.js          |   4 +
 .../aws-sdk-v3/bedrock-embeddings.test.js     |  12 +-
 .../aws-sdk-v3/client-dynamodb.test.js        |  12 +-
 test/versioned/aws-sdk-v3/common.js           |  28 +-
 .../versioned/aws-sdk-v3/lib-dynamodb.test.js |  12 +-
 test/versioned/aws-sdk-v3/sns.test.js         |  12 +-
 test/versioned/aws-sdk-v3/sqs.test.js         |  12 +-
 test/versioned/cassandra-driver/query.test.js |  30 +-
 test/versioned/connect/route.test.js          |   4 +-
 .../disabled-express.test.js                  |   2 +-
 .../disabled-ioredis.test.js                  |   4 +-
 test/versioned/elastic/elasticsearch.tap.js   |  30 +-
 .../elastic/elasticsearchNoop.tap.js          |   2 +-
 test/versioned/express-esm/segments.tap.mjs   |  24 +-
 .../express-esm/transaction-naming.tap.mjs    |   3 +-
 test/versioned/express/async-handlers.test.js |   4 +-
 test/versioned/express/bare-router.test.js    |   2 +-
 .../express/client-disconnect.test.js         |   1 +
 test/versioned/express/router-params.test.js  |   2 +-
 test/versioned/express/segments.test.js       | 130 ++++---
 .../express/transaction-naming.test.js        |   3 +-
 test/versioned/fastify/add-hook.test.js       |   4 +-
 .../fastify/code-level-metrics-hooks.test.js  |  11 +-
 .../code-level-metrics-middleware.test.js     |  17 +-
 test/versioned/fastify/naming-common.js       |   2 +-
 test/versioned/grpc/util.cjs                  |   4 +-
 test/versioned/hapi/render.tap.js             |   7 +-
 test/versioned/hapi/router.tap.js             |   5 +-
 test/versioned/hapi/segments.tap.js           |  32 +-
 test/versioned/ioredis/ioredis.tap.js         |   9 +-
 test/versioned/kafkajs/kafka.tap.js           |  28 +-
 test/versioned/kafkajs/utils.js               |   4 +-
 test/versioned/koa/code-level-metrics.tap.js  |  30 +-
 test/versioned/koa/koa-route.tap.js           |  12 +-
 test/versioned/koa/koa.tap.js                 |   6 +-
 test/versioned/koa/router-common.js           |  52 +--
 test/versioned/langchain/common.js            |  16 +-
 .../langchain/runnables-streaming.tap.js      |   2 +-
 test/versioned/langchain/runnables.tap.js     |   2 +-
 test/versioned/langchain/tools.tap.js         |   9 +-
 test/versioned/langchain/vectorstore.tap.js   |   2 +-
 test/versioned/memcached/memcached.tap.js     |  66 ++--
 test/versioned/mongodb-esm/db.test.mjs        |   7 +-
 .../versioned/mongodb-esm/test-assertions.mjs |  15 +-
 test/versioned/mongodb/collection-common.js   |  36 +-
 test/versioned/mongodb/db-common.js           |  11 +-
 test/versioned/mysql/basic-pool.tap.js        |  45 ++-
 test/versioned/mysql/basic.tap.js             |  31 +-
 test/versioned/mysql/pooling.tap.js           |  14 +-
 test/versioned/mysql2/basic-pool.tap.js       |  39 +-
 test/versioned/mysql2/basic.tap.js            |  31 +-
 test/versioned/mysql2/pooling.tap.js          |  14 +-
 test/versioned/mysql2/promises.tap.js         |   2 +-
 test/versioned/nextjs/attributes.tap.js       |  23 +-
 test/versioned/nextjs/helpers.js              |  25 +-
 test/versioned/nextjs/segments.tap.js         |   6 +-
 test/versioned/openai/chat-completions.tap.js |   4 +-
 test/versioned/openai/common.js               |   8 +-
 test/versioned/openai/embeddings.tap.js       |   6 +-
 test/versioned/pg-esm/pg.common.mjs           |  14 +-
 test/versioned/pg/pg.common.js                |  17 +-
 test/versioned/prisma/prisma.tap.js           |   6 +-
 test/versioned/prisma/utils.js                |   6 +-
 test/versioned/q/q.tap.js                     |   6 +-
 .../redis/redis-v4-legacy-mode.tap.js         |  27 +-
 test/versioned/redis/redis-v4.tap.js          |  28 +-
 test/versioned/redis/redis.tap.js             |  40 +-
 test/versioned/restify/router.tap.js          |   2 +-
 test/versioned/superagent/async-await.tap.js  |   5 +-
 test/versioned/superagent/superagent.tap.js   |  10 +-
 test/versioned/undici/requests.tap.js         |  33 +-
 .../versioned/when/legacy-promise-segments.js |  59 +--
 126 files changed, 1415 insertions(+), 1411 deletions(-)

diff --git a/lib/db/parsed-statement.js b/lib/db/parsed-statement.js
index 1e23e39b07..6b0003e7b1 100644
--- a/lib/db/parsed-statement.js
+++ b/lib/db/parsed-statement.js
@@ -23,7 +23,7 @@ function ParsedStatement(type, operation, collection, raw) {
 
 ParsedStatement.prototype.recordMetrics = function recordMetrics(segment, scope, transaction) {
   const duration = segment.getDurationInMillis()
-  const exclusive = segment.getExclusiveDurationInMillis()
+  const exclusive = segment.getExclusiveDurationInMillis(transaction.trace)
   const type = transaction.isWeb() ? DB.WEB : DB.OTHER
   const thisTypeSlash = this.type + '/'
   const operation = DB.OPERATION + '/' + thisTypeSlash + this.operation
diff --git a/lib/instrumentation/nextjs/utils.js b/lib/instrumentation/nextjs/utils.js
index 95c0645a32..3e0aef73f0 100644
--- a/lib/instrumentation/nextjs/utils.js
+++ b/lib/instrumentation/nextjs/utils.js
@@ -56,16 +56,3 @@ utils.isMiddlewareInstrumentationSupported = function isMiddlewareInstrumentatio
     semver.gte(version, MIN_MW_SUPPORTED_VERSION) && semver.lte(version, MAX_MW_SUPPORTED_VERSION)
   )
 }
-
-/**
- * Depending on the Next.js version the segment tree varies as it adds setTimeout segments.
- * This util will find the segment that has `getServerSideProps` in the name
- *
- * @param {object} rootSegment trace root
- * @returns {object} getServerSideProps segment
- */
-utils.getServerSidePropsSegment = function getServerSidePropsSegment(rootSegment) {
-  return rootSegment.children[0].children.find((segment) =>
-    segment.name.includes('getServerSideProps')
-  )
-}
diff --git a/lib/metrics/recorders/custom.js b/lib/metrics/recorders/custom.js
index c36cebe44b..f4623202e1 100644
--- a/lib/metrics/recorders/custom.js
+++ b/lib/metrics/recorders/custom.js
@@ -9,7 +9,7 @@ const NAMES = require('../names')
 
 function record(segment, scope, transaction) {
   const duration = segment.getDurationInMillis()
-  const exclusive = segment.getExclusiveDurationInMillis()
+  const exclusive = segment.getExclusiveDurationInMillis(transaction.trace)
   const name = NAMES.CUSTOM + NAMES.ACTION_DELIMITER + segment.name
 
   if (scope) {
diff --git a/lib/metrics/recorders/generic.js b/lib/metrics/recorders/generic.js
index f46293c16c..945b61c3c6 100644
--- a/lib/metrics/recorders/generic.js
+++ b/lib/metrics/recorders/generic.js
@@ -7,7 +7,7 @@
 
 function record(segment, scope, transaction) {
   const duration = segment.getDurationInMillis()
-  const exclusive = segment.getExclusiveDurationInMillis()
+  const exclusive = segment.getExclusiveDurationInMillis(transaction.trace)
 
   if (scope) {
     transaction.measure(segment.name, scope, duration, exclusive)
diff --git a/lib/metrics/recorders/http.js b/lib/metrics/recorders/http.js
index 777424f6bf..4ef57aff07 100644
--- a/lib/metrics/recorders/http.js
+++ b/lib/metrics/recorders/http.js
@@ -23,7 +23,7 @@ function recordWeb(segment, scope, tx) {
 
   const duration = segment.getDurationInMillis()
   const totalTime = tx.trace.getTotalTimeDurationInMillis()
-  const exclusive = segment.getExclusiveDurationInMillis()
+  const exclusive = segment.getExclusiveDurationInMillis(tx.trace)
   const partial = segment.partialName
   const config = tx.agent.config
   // named / key transaction support requires per-name apdexT
diff --git a/lib/metrics/recorders/http_external.js b/lib/metrics/recorders/http_external.js
index 252a97f1f0..0a656b407f 100644
--- a/lib/metrics/recorders/http_external.js
+++ b/lib/metrics/recorders/http_external.js
@@ -10,7 +10,7 @@ const EXTERNAL = require('../../metrics/names').EXTERNAL
 function recordExternal(host, library) {
   return function externalRecorder(segment, scope, transaction) {
     const duration = segment.getDurationInMillis()
-    const exclusive = segment.getExclusiveDurationInMillis()
+    const exclusive = segment.getExclusiveDurationInMillis(transaction.trace)
     const metricName = EXTERNAL.PREFIX + host + '/' + library
     const rollupType = transaction.isWeb() ? EXTERNAL.WEB : EXTERNAL.OTHER
     const rollupHost = EXTERNAL.PREFIX + host + '/all'
diff --git a/lib/metrics/recorders/message-transaction.js b/lib/metrics/recorders/message-transaction.js
index c880226b1d..740223d867 100644
--- a/lib/metrics/recorders/message-transaction.js
+++ b/lib/metrics/recorders/message-transaction.js
@@ -13,7 +13,7 @@ function recordMessageTransaction(segment, scope, tx) {
   }
 
   const duration = segment.getDurationInMillis()
-  const exclusive = segment.getExclusiveDurationInMillis()
+  const exclusive = segment.getExclusiveDurationInMillis(tx.trace)
   const totalTime = tx.trace.getTotalTimeDurationInMillis()
 
   if (scope) {
diff --git a/lib/metrics/recorders/other.js b/lib/metrics/recorders/other.js
index 0637ccaaa9..47f0c642c1 100644
--- a/lib/metrics/recorders/other.js
+++ b/lib/metrics/recorders/other.js
@@ -15,7 +15,7 @@ function recordBackground(segment, scope, tx) {
   }
 
   const duration = segment.getDurationInMillis()
-  const exclusive = segment.getExclusiveDurationInMillis()
+  const exclusive = segment.getExclusiveDurationInMillis(tx.trace)
   const totalTime = tx.trace.getTotalTimeDurationInMillis()
   const name = segment.partialName
 
diff --git a/lib/shim/datastore-shim.js b/lib/shim/datastore-shim.js
index 28cab904cc..1e6fa95b78 100644
--- a/lib/shim/datastore-shim.js
+++ b/lib/shim/datastore-shim.js
@@ -632,7 +632,7 @@ function _recordOperationMetrics(segment, scope, transaction) {
   }
 
   const duration = segment.getDurationInMillis()
-  const exclusive = segment.getExclusiveDurationInMillis()
+  const exclusive = segment.getExclusiveDurationInMillis(transaction.trace)
   const type = transaction.isWeb() ? 'allWeb' : 'allOther'
   const operation = segment.name
 
diff --git a/lib/shim/shim.js b/lib/shim/shim.js
index 3a9e5ffa62..120a9f6e3a 100644
--- a/lib/shim/shim.js
+++ b/lib/shim/shim.js
@@ -1325,8 +1325,7 @@ function createSegment(name, recorder, parent) {
  * @param {Shim} params.shim instance of shim
  * @param {Transaction} params.transaction active transaction
  * @param {TraceSegment} params.parent the segment that will be the parent of the newly created segment
- * @param params.spec
- * @param {string|specs.SegmentSpec} spec options for creating segment
+ * @param {string|specs.SegmentSpec} params.spec options for creating segment
  * @returns {?TraceSegment} A new trace segment if a transaction is active, else
  *  `null` is returned.
  */
diff --git a/lib/shim/webframework-shim/middleware.js b/lib/shim/webframework-shim/middleware.js
index 0cd2cd4fd2..f8f147c41f 100644
--- a/lib/shim/webframework-shim/middleware.js
+++ b/lib/shim/webframework-shim/middleware.js
@@ -336,7 +336,7 @@ module.exports._recordMiddleware = function _recordMiddleware(shim, middleware,
 function _makeMiddlewareRecorder(_shim, metricName) {
   return function middlewareMetricRecorder(segment, scope, transaction) {
     const duration = segment.getDurationInMillis()
-    const exclusive = segment.getExclusiveDurationInMillis()
+    const exclusive = segment.getExclusiveDurationInMillis(transaction.trace)
 
     if (scope) {
       transaction.measure(metricName, scope, duration, exclusive)
diff --git a/lib/transaction/trace/exclusive-time-calculator.js b/lib/transaction/trace/exclusive-time-calculator.js
index fd2fb022ec..9d01a1c90c 100644
--- a/lib/transaction/trace/exclusive-time-calculator.js
+++ b/lib/transaction/trace/exclusive-time-calculator.js
@@ -6,7 +6,9 @@
 'use strict'
 
 class ExclusiveCalculator {
-  constructor(root) {
+  constructor(root, trace) {
+    this.trace = trace
+    this.id = root.id
     this.toProcess = [root]
     // use a second stack to do a post-order traversal
     this.parentStack = []
@@ -19,7 +21,7 @@ class ExclusiveCalculator {
   process() {
     while (this.toProcess.length) {
       const segment = this.toProcess.pop()
-      const children = segment.getChildren()
+      const children = this.trace.getChildren(segment.id)
       // when we hit a leaf, calc the exclusive time and report the time
       // range to the parent
       if (children.length === 0) {
diff --git a/lib/transaction/trace/index.js b/lib/transaction/trace/index.js
index c7c22121c1..168832efc3 100644
--- a/lib/transaction/trace/index.js
+++ b/lib/transaction/trace/index.js
@@ -41,7 +41,7 @@ function Trace(transaction) {
   this.root.start()
 
   this.intrinsics = Object.create(null)
-  this.segmentsSeen = 0
+  this.segments = []
   this.totalTimeCache = null
 
   this.custom = new Attributes(ATTRIBUTE_SCOPE, MAXIMUM_CUSTOM_ATTRIBUTES)
@@ -63,16 +63,11 @@ function Trace(transaction) {
  * segments that support recording.
  */
 Trace.prototype.end = function end() {
-  const segments = [this.root]
+  this.root.finalize(this)
+  const segments = this.segments
 
-  while (segments.length) {
-    const segment = segments.pop()
-    segment.finalize()
-
-    const children = segment.getChildren()
-    for (let i = 0; i < children.length; ++i) {
-      segments.push(children[i])
-    }
+  for (let i = 0; i < segments.length; i++) {
+    segments[i].finalize(this)
   }
 }
 
@@ -85,14 +80,13 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
   if (!shouldGenerateSpanEvents(config, this.transaction)) {
     return
   }
-  const toProcess = []
 
   // Root segment does not become a span, so we need to process it separately.
   const spanAggregator = this.transaction.agent.spanEventAggregator
 
-  const children = this.root.getChildren()
+  const segments = this.segments
 
-  if (children.length > 0) {
+  if (segments.length > 0) {
     // At the point where these attributes are available, we only have a
     // root span. Adding attributes to first non-root span here.
     const attributeMap = {
@@ -106,41 +100,28 @@ Trace.prototype.generateSpanEvents = function generateSpanEvents() {
 
     for (const [key, value] of Object.entries(attributeMap)) {
       if (value !== null) {
-        children[0].addSpanAttribute(key, value)
+        segments[0].addSpanAttribute(key, value)
       }
     }
   }
 
-  for (let i = 0; i < children.length; ++i) {
-    toProcess.push(new DTTraceNode(children[i], this.transaction.parentSpanId, true))
-  }
-
-  while (toProcess.length) {
-    const segmentInfo = toProcess.pop()
-    const segment = segmentInfo.segment
-
+  for (let i = 0; i < segments.length; ++i) {
+    const segment = segments[i]
+    const isRoot = segment.parentId === this.root.id
+    const parentId = isRoot ? this.transaction.parentSpanId : segment.parentId
     // Even though at some point we might want to stop adding events because all the priorities
     // should be the same, we need to count the spans as seen.
     spanAggregator.addSegment({
       segment,
       transaction: this.transaction,
-      parentId: segmentInfo.parentId,
-      isRoot: segmentInfo.isRoot
+      parentId,
+      isRoot
     })
-
-    const nodes = segment.getChildren()
-    for (let i = 0; i < nodes.length; ++i) {
-      const node = new DTTraceNode(nodes[i], segment.id)
-      toProcess.push(node)
-    }
   }
 }
 
 function shouldGenerateSpanEvents(config, txn) {
-  if (!config.distributed_tracing.enabled) {
-    return false
-  }
-  if (!config.span_events.enabled) {
+  if (!(config.distributed_tracing.enabled && config.span_events.enabled)) {
     return false
   }
 
@@ -148,12 +129,6 @@ function shouldGenerateSpanEvents(config, txn) {
   return infiniteTracingConfigured || txn.sampled
 }
 
-function DTTraceNode(segment, parentId, isRoot = false) {
-  this.segment = segment
-  this.parentId = parentId
-  this.isRoot = isRoot
-}
-
 /**
  * Add a child to the list of segments.
  *
@@ -220,7 +195,7 @@ Trace.prototype.addCustomAttribute = function addCustomAttribute(key, value) {
  *                   traces, in milliseconds.
  */
 Trace.prototype.getExclusiveDurationInMillis = function getExclusiveDurationInMillis() {
-  return this.root.getExclusiveDurationInMillis()
+  return this.root.getExclusiveDurationInMillis(this)
 }
 
 /**
@@ -234,16 +209,14 @@ Trace.prototype.getTotalTimeDurationInMillis = function getTotalTimeDurationInMi
   if (this.totalTimeCache !== null) {
     return this.totalTimeCache
   }
-  if (this.root.children.length === 0) {
+  const segments = this.segments
+  if (segments.length === 0) {
     return 0
   }
-  const segments = this.root.getChildren()
-  let totalTimeInMillis = 0
 
-  while (segments.length !== 0) {
-    const segment = segments.pop()
-    totalTimeInMillis += segment.getExclusiveDurationInMillis()
-    segment.getChildren().forEach((childSegment) => segments.push(childSegment))
+  let totalTimeInMillis = 0
+  for (let i = 0; i < segments.length; i++) {
+    totalTimeInMillis += segments[i].getExclusiveDurationInMillis(this)
   }
 
   if (!this.transaction.isActive()) {
@@ -366,6 +339,85 @@ Trace.prototype._getRequestUri = function _getRequestUri() {
   return requestUri
 }
 
+Trace.prototype.getChildren = function getChildren(id) {
+  return this.segments.filter((segment) => segment.parentId === id)
+}
+
+Trace.prototype.getCollectedChildren = function getCollectedChildren(id) {
+  return this.segments.filter(
+    (segment) => segment.parentId === id && segment._collect && !segment.ignore
+  )
+}
+
+Trace.prototype.getParent = function getParent(parentId) {
+  return this.segments.filter((segment) => segment.id === parentId)[0]
+}
+
+/**
+ * This is perhaps the most poorly-documented element of transaction traces:
+ * what do each of the segment representations look like prior to encoding?
+ * Spelunking in the code for the other agents has revealed that each child
+ * node is an array with the following field in the following order:
+ *
+ * 0: entry timestamp relative to transaction start time
+ * 1: exit timestamp
+ * 2: metric name
+ * 3: parameters as a name -> value JSON dictionary
+ * 4: any child segments
+ *
+ * Other agents include further fields in this. I haven't gotten to the bottom
+ * of all of them (and Ruby, of course, sends marshalled Ruby object), but
+ * here's what I know so far:
+ *
+ * in Java:
+ * 5: class name
+ * 6: method name
+ *
+ * in Python:
+ * 5: a "label"
+ *
+ * FIXME: I don't know if it makes sense to add custom fields for Node. TBD
+ */
+Trace.prototype.toJSON = function toJSON() {
+  // use depth-first search on the segment tree using stack
+  const resultDest = []
+  // array of objects relating a segment and the destination for its
+  // serialized data.
+  const segmentsToProcess = [
+    {
+      segment: this.root,
+      destination: resultDest
+    }
+  ]
+
+  while (segmentsToProcess.length !== 0) {
+    const { segment, destination } = segmentsToProcess.pop()
+    const start = segment.timer.startedRelativeTo(this.root.timer)
+    const duration = segment.getDurationInMillis()
+
+    const segmentChildren = this.getCollectedChildren(segment.id)
+    const childArray = []
+
+    // push serialized data into the specified destination
+    destination.push([start, start + duration, segment.name, segment.getAttributes(), childArray])
+
+    if (segmentChildren.length) {
+      // push the children and the parent's children array into the stack.
+      // to preserve the chronological order of the children, push them
+      // onto the stack backwards (so the first one created is on top).
+      for (let i = segmentChildren.length - 1; i >= 0; --i) {
+        segmentsToProcess.push({
+          segment: segmentChildren[i],
+          destination: childArray
+        })
+      }
+    }
+  }
+
+  // pull the result out of the array we serialized it into
+  return resultDest[0]
+}
+
 /**
  * Serializes the trace into the expected JSON format to be sent.
  *
@@ -379,17 +431,21 @@ Trace.prototype._serializeTrace = function _serializeTrace() {
     intrinsics: this.intrinsics
   }
 
-  return [
+  const trace = [
     this.root.timer.start * FROM_MILLIS,
     {}, // moved to agentAttributes
     {
       // hint to RPM for how to display this trace's segments
       nr_flatten_leading: false
     }, // moved to userAttributes
-    this.root.toJSON(),
+    this.toJSON(),
     attributes,
     [] // FIXME: parameter groups
   ]
+
+  // clear out segments
+  this.segments = []
+  return trace
 }
 
 module.exports = Trace
diff --git a/lib/transaction/trace/segment.js b/lib/transaction/trace/segment.js
index bc3f0f9bed..a03c7b6e59 100644
--- a/lib/transaction/trace/segment.js
+++ b/lib/transaction/trace/segment.js
@@ -6,7 +6,6 @@
 'use strict'
 
 const { DESTINATIONS } = require('../../config/attribute-filter')
-const logger = require('../../logger').child({ component: 'segment' })
 const Timer = require('../../timer')
 const hashes = require('../../util/hashes')
 
@@ -34,20 +33,21 @@ const ATTRIBUTE_SCOPE = 'segment'
  * @param {object} params.config agent config
  * @param {string} params.name Human-readable name for this segment (e.g. 'http', 'net', 'express',
  *  'mysql', etc).
+ *  @param {number} params.parentId parent id of segment
  * @param {boolean} params.collect flag to collect as part of transaction trace
  * @param {TraceSegment} params.root root segment
  * @param {boolean} params.isRoot flag to indicate it is the root segment
  */
-function TraceSegment({ config, name, collect, root, isRoot = false }) {
+function TraceSegment({ config, name, collect, parentId, root, isRoot = false }) {
   this.isRoot = isRoot
   this.root = root
   this.name = name
   this.attributes = new Attributes(ATTRIBUTE_SCOPE)
-  this.children = []
   this.spansEnabled = config?.distributed_tracing?.enabled && config?.span_events?.enabled
 
   // Generate a unique id for use in span events.
   this.id = hashes.makeId()
+  this.parentId = parentId
   this.timer = new Timer()
 
   this.internal = false
@@ -173,7 +173,7 @@ TraceSegment.prototype.end = function end() {
   this._updateRootTimer()
 }
 
-TraceSegment.prototype.finalize = function finalize() {
+TraceSegment.prototype.finalize = function finalize(trace) {
   if (this.timer.softEnd()) {
     this._updateRootTimer()
     // timer.softEnd() returns true if the timer was ended prematurely, so
@@ -181,7 +181,7 @@ TraceSegment.prototype.finalize = function finalize() {
     this.name = NAMES.TRUNCATED.PREFIX + this.name
   }
 
-  this.addAttribute('nr_exclusive_duration_millis', this.getExclusiveDurationInMillis())
+  this.addAttribute('nr_exclusive_duration_millis', this.getExclusiveDurationInMillis(trace))
 }
 
 /**
@@ -206,36 +206,6 @@ TraceSegment.prototype._isEnded = function _isEnded() {
   return !this.timer.isActive() || this.timer.touched
 }
 
-/**
- * Add a new segment to a parent(scope) implicitly bounded by this segment.
- *
- * @param {object} params to function
- * @param {object} params.config agent config
- * @param {string} params.name Human-readable name for this segment (e.g. 'http', 'net', 'express',
- *  'mysql', etc).
- * @param {boolean} params.collect flag to collect as part of transaction trace
- * @param {TraceSegment} params.root root segment
- * @returns {TraceSegment} New nested TraceSegment.
- */
-TraceSegment.prototype.add = function add({ config, name, collect, root }) {
-  // this is needed here to check when add is called directly on segment
-  if (this.opaque) {
-    logger.trace('Skipping child addition on opaque segment')
-    return this
-  }
-
-  const segment = new TraceSegment({ config, name, collect, root })
-
-  this.children.push(segment)
-
-  // This should only be used in testing
-  if (config.debug && config.debug.double_linked_transactions) {
-    segment.parent = this
-  }
-
-  return segment
-}
-
 /**
  * Set the duration of the segment explicitly.
  *
@@ -271,139 +241,15 @@ function _setExclusiveDurationInMillis(duration) {
  */
 TraceSegment.prototype.getExclusiveDurationInMillis = getExclusiveDurationInMillis
 
-function getExclusiveDurationInMillis() {
+function getExclusiveDurationInMillis(trace) {
   if (this._exclusiveDuration == null) {
     // Calculate the exclusive time for the subtree rooted at `this`
-    const calculator = new ExclusiveCalculator(this)
+    const calculator = new ExclusiveCalculator(this, trace)
     calculator.process()
   }
   return this._exclusiveDuration
 }
 
-TraceSegment.prototype.getChildren = function getChildren() {
-  const children = []
-  for (let i = 0, len = this.children.length; i < len; ++i) {
-    if (!this.children[i].ignore) {
-      children.push(this.children[i])
-    }
-  }
-  return children
-}
-
-TraceSegment.prototype.getCollectedChildren = function getCollectedChildren() {
-  const children = []
-  for (let i = 0, len = this.children.length; i < len; ++i) {
-    if (this.children[i]._collect && !this.children[i].ignore) {
-      children.push(this.children[i])
-    }
-  }
-  return children
-}
-
-/**
- * Enumerate the timings of this segment's descendants.
- *
- * @param {number} end The end of this segment, to keep the calculated
- *                     duration from exceeding the duration of the
- *                     parent. Defaults to Infinity.
- * @returns {Array} Unsorted list of [start, end] pairs, with no pair
- *                  having an end greater than the passed in end time.
- */
-TraceSegment.prototype._getChildPairs = function _getChildPairs(end) {
-  // quick optimization
-  if (this.children.length < 1) {
-    return []
-  }
-  if (!end) {
-    end = Infinity
-  }
-
-  let children = this.getChildren()
-  const childPairs = []
-  while (children.length) {
-    const child = children.pop()
-    const pair = child.timer.toRange()
-
-    if (pair[0] >= end) {
-      continue
-    }
-
-    children = children.concat(child.getChildren())
-
-    pair[1] = Math.min(pair[1], end)
-    childPairs.push(pair)
-  }
-
-  return childPairs
-}
-
-/**
- * This is perhaps the most poorly-documented element of transaction traces:
- * what do each of the segment representations look like prior to encoding?
- * Spelunking in the code for the other agents has revealed that each child
- * node is an array with the following field in the following order:
- *
- * 0: entry timestamp relative to transaction start time
- * 1: exit timestamp
- * 2: metric name
- * 3: parameters as a name -> value JSON dictionary
- * 4: any child segments
- *
- * Other agents include further fields in this. I haven't gotten to the bottom
- * of all of them (and Ruby, of course, sends marshalled Ruby object), but
- * here's what I know so far:
- *
- * in Java:
- * 5: class name
- * 6: method name
- *
- * in Python:
- * 5: a "label"
- *
- * FIXME: I don't know if it makes sense to add custom fields for Node. TBD
- */
-TraceSegment.prototype.toJSON = function toJSON() {
-  const root = this.isRoot ? this : this.root
-  // use depth-first search on the segment tree using stack
-  const resultDest = []
-  // array of objects relating a segment and the destination for its
-  // serialized data.
-  const segmentsToProcess = [
-    {
-      segment: this,
-      destination: resultDest
-    }
-  ]
-
-  while (segmentsToProcess.length !== 0) {
-    const { segment, destination } = segmentsToProcess.pop()
-
-    const start = segment.timer.startedRelativeTo(root.timer)
-    const duration = segment.getDurationInMillis()
-
-    const segmentChildren = segment.getCollectedChildren()
-    const childArray = []
-
-    // push serialized data into the specified destination
-    destination.push([start, start + duration, segment.name, segment.getAttributes(), childArray])
-
-    if (segmentChildren.length) {
-      // push the children and the parent's children array into the stack.
-      // to preserve the chronological order of the children, push them
-      // onto the stack backwards (so the first one created is on top).
-      for (let i = segmentChildren.length - 1; i >= 0; --i) {
-        segmentsToProcess.push({
-          segment: segmentChildren[i],
-          destination: childArray
-        })
-      }
-    }
-  }
-
-  // pull the result out of the array we serialized it into
-  return resultDest[0]
-}
-
 /**
  * Adds all the relevant segment attributes for an External http request
  *
diff --git a/lib/transaction/tracer/index.js b/lib/transaction/tracer/index.js
index 72e789d1bf..f61330880d 100644
--- a/lib/transaction/tracer/index.js
+++ b/lib/transaction/tracer/index.js
@@ -13,6 +13,7 @@ const SKIP_WRAPPING_FUNCTION_MESSAGE = 'Not wrapping "%s" because it was not a f
 const CREATE_SEGMENT_MESSAGE = 'Creating "%s" segment for transaction %s.'
 const { addCLMAttributes: maybeAddCLMAttributes } = require('../../util/code-level-metrics')
 const AsyncLocalContextManager = require('../../context-manager/async-local-context-manager')
+const TraceSegment = require('../trace/segment')
 
 module.exports = Tracer
 
@@ -101,22 +102,24 @@ function createSegment({ name, recorder, parent, transaction }) {
   logger.trace('Adding segment %s to %s in %s', name, parent.name, transaction.id)
 
   let collect = true
-  if (transaction.trace.segmentsSeen++ >= this.agent.config.max_trace_segments) {
+  if (transaction.trace.segments.length >= this.agent.config.max_trace_segments) {
     collect = false
   }
 
   transaction.incrementCounters()
 
-  const segment = parent.add({
+  const segment = new TraceSegment({
     config: this.agent.config,
     name,
     collect,
-    root: transaction.trace.root
+    root: transaction.trace.root,
+    parentId: parent.id
   })
 
   if (recorder) {
     transaction.addRecorder(recorder.bind(null, segment))
   }
+  transaction.trace.segments.push(segment)
 
   return segment
 }
diff --git a/test/integration/cat/cat.tap.js b/test/integration/cat/cat.tap.js
index 46703848e3..914e5e3f27 100644
--- a/test/integration/cat/cat.tap.js
+++ b/test/integration/cat/cat.tap.js
@@ -161,8 +161,8 @@ test('cross application tracing full integration', function (t) {
       )
 
       // check the external segment for its properties
-      const externalSegment =
-        trace.root.children[0].children[trace.root.children[0].children.length - 1]
+      const [webSegment] = trace.getChildren(trace.root.id)
+      const [externalSegment] = trace.getChildren(webSegment.id)
       t.equal(
         externalSegment.name.split('/')[0],
         'ExternalTransaction',
@@ -228,8 +228,8 @@ test('cross application tracing full integration', function (t) {
       )
 
       // check the external segment for its properties
-      const externalSegment =
-        trace.root.children[0].children[trace.root.children[0].children.length - 1]
+      const [webSegment] = trace.getChildren(trace.root.id)
+      const [externalSegment] = trace.getChildren(webSegment.id)
       t.equal(
         externalSegment.name.split('/')[0],
         'ExternalTransaction',
diff --git a/test/integration/core/crypto.tap.js b/test/integration/core/crypto.tap.js
index 9e50557c72..9c6e764dc4 100644
--- a/test/integration/core/crypto.tap.js
+++ b/test/integration/core/crypto.tap.js
@@ -38,7 +38,8 @@ test('sync randomBytes', function (t) {
     const bytes = crypto.randomBytes(32)
     t.ok(bytes instanceof Buffer)
     t.equal(bytes.length, 32)
-    t.equal(transaction.trace.root.children.length, 0)
+    const children = transaction.trace.getChildren(transaction.trace.root.id)
+    t.equal(children.length, 0)
     t.end()
   })
 })
@@ -62,7 +63,8 @@ test('sync pseudoRandomBytes', function (t) {
     const bytes = crypto.pseudoRandomBytes(32)
     t.ok(bytes instanceof Buffer)
     t.equal(bytes.length, 32)
-    t.equal(transaction.trace.root.children.length, 0)
+    const children = transaction.trace.getChildren(transaction.trace.root.id)
+    t.equal(children.length, 0)
     t.end()
   })
 })
@@ -92,7 +94,8 @@ test('sync randomFill', function (t) {
     crypto.randomFillSync(buf)
     t.ok(buf instanceof Buffer)
     t.equal(buf.length, 10)
-    t.equal(transaction.trace.root.children.length, 0)
+    const children = transaction.trace.getChildren(transaction.trace.root.id)
+    t.equal(children.length, 0)
     t.end()
   })
 })
@@ -120,7 +123,8 @@ test('scryptSync', (t) => {
     const buf = crypto.scryptSync('secret', 'salt', 10)
     t.ok(buf instanceof Buffer)
     t.equal(buf.length, 10)
-    t.equal(transaction.trace.root.children.length, 0)
+    const children = transaction.trace.getChildren(transaction.trace.root.id)
+    t.equal(children.length, 0)
     t.end()
   })
 })
diff --git a/test/integration/core/fs.tap.js b/test/integration/core/fs.tap.js
index 9b58727297..f338d31851 100644
--- a/test/integration/core/fs.tap.js
+++ b/test/integration/core/fs.tap.js
@@ -612,7 +612,7 @@ test('readFile', function (t) {
       // io.js changed their implementation of fs.readFile to use process.binding.
       // This caused the file opening not to be added to the trace when using io.js.
       // By checking this value, we can determine whether or not to expect it.
-      if (agent.getTransaction().trace.root.children[0].children.length === 1) {
+      if (agent.tracer.getSegment()) {
         expectFSOpen = false
       }
       verifySegments(
@@ -705,7 +705,8 @@ test('read', function (t) {
       t.equal(len, 12, 'should read correct number of bytes')
       t.equal(data.toString('utf8'), content)
       t.equal(agent.getTransaction(), trans, 'should preserve transaction')
-      t.equal(trans.trace.root.children.length, 0, 'should not create any segments')
+      const children = trans.trace.getChildren(trans.trace.root.id)
+      t.equal(children.length, 0, 'should not create any segments')
       t.end()
     })
   })
@@ -725,7 +726,8 @@ test('write', function (t) {
       t.equal(len, 12, 'should write correct number of bytes')
       t.equal(fs.readFileSync(name, 'utf8'), content)
       t.equal(agent.getTransaction(), trans, 'should preserve transaction')
-      t.equal(trans.trace.root.children.length, 0, 'should not create any segments')
+      const children = trans.trace.getChildren(trans.trace.root.id)
+      t.equal(children.length, 0, 'should not create any segments')
       t.end()
     })
   })
@@ -746,7 +748,8 @@ test('watch (file)', function (t) {
 
         t.equal(file, 'watch-file', 'should have correct file name')
         t.equal(agent.getTransaction(), trans, 'should preserve transaction')
-        t.equal(trans.trace.root.children.length, 1, 'should not create any segments')
+        const children = trans.trace.getChildren(trans.trace.root.id)
+        t.equal(children.length, 1, 'should not create any segments')
         watcher.close()
       })
       fs.writeFile(name, content + 'more', function (err) {
@@ -769,7 +772,8 @@ test('watch (dir)', function (t) {
         t.equal(ev, 'rename')
         t.equal(file, 'watch-dir')
         t.equal(agent.getTransaction(), trans, 'should preserve transaction')
-        t.equal(trans.trace.root.children.length, 1, 'should not create any segments')
+        const children = trans.trace.getChildren(trans.trace.root.id)
+        t.equal(children.length, 1, 'should not create any segments')
         watcher.close()
       })
       fs.writeFile(name, content, function (err) {
@@ -795,9 +799,9 @@ test('watch emitter', function (t) {
         t.equal(file, 'watch', 'should be for correct directory')
 
         const tx = agent.getTransaction()
-        const root = trans.trace.root
         t.equal(tx && tx.id, trans.id, 'should preserve transaction')
-        t.equal(root.children.length, 1, 'should not create any segments')
+        const children = trans.trace.getChildren(trans.trace.root.id)
+        t.equal(children.length, 1, 'should not create any segments')
 
         watcher.close()
       })
@@ -826,7 +830,8 @@ test('watchFile', function (t) {
         t.ok(cur.size > prev.size, 'content modified')
 
         t.equal(agent.getTransaction(), trans, 'should preserve transaction')
-        t.equal(trans.trace.root.children.length, 0, 'should not create any segments')
+        const children = trans.trace.getChildren(trans.trace.root.id)
+        t.equal(children.length, 0, 'should not create any segments')
         fs.unwatchFile(name, onChange)
       }
     })
diff --git a/test/integration/core/net.tap.js b/test/integration/core/net.tap.js
index b75da4afdc..1c9432b637 100644
--- a/test/integration/core/net.tap.js
+++ b/test/integration/core/net.tap.js
@@ -43,16 +43,19 @@ test('createServer', function createServerTest(t) {
     }
 
     function onClose() {
-      const root = agent.getTransaction().trace.root
-      t.equal(root.children.length, 2, 'should have a single child')
-      const child = root.children[1]
+      const transaction = agent.getTransaction()
+      const children = transaction.trace.getChildren(transaction.trace.root.id)
+      t.equal(children.length, 2, 'should have a single child')
+      const child = children[1]
+      const childChildren = transaction.trace.getChildren(child.id)
       t.equal(child.name, 'net.Server.onconnection', 'child segment should have correct name')
       t.ok(child.timer.touched, 'child should started and ended')
-      t.equal(child.children.length, 1, 'child should have a single child segment')
-      const timeout = child.children[0]
+      t.equal(childChildren.length, 1, 'child should have a single child segment')
+      const timeout = childChildren[0]
+      const timeoutChildren = transaction.trace.getChildren(timeout.id)
       t.equal(timeout.name, 'timers.setTimeout', 'timeout segment should have correct name')
       t.ok(timeout.timer.touched, 'timeout should started and ended')
-      t.equal(timeout.children.length, 1, 'timeout should have a single callback segment')
+      t.equal(timeoutChildren.length, 1, 'timeout should have a single callback segment')
       t.end()
     }
   })
@@ -101,32 +104,35 @@ test('connect', function connectTest(t) {
         return t.end()
       }
 
-      const root = agent.getTransaction().trace.root
-      t.equal(root.children.length, 1, 'should have a single child')
-      let connectSegment = root.children[0]
+      const transaction = agent.getTransaction()
+      const children = transaction.trace.getChildren(transaction.trace.root.id)
+      t.equal(children.length, 1, 'should have a single child')
+      let connectSegment = children[0]
       t.equal(
         connectSegment.name,
         'net.createConnection',
         'connect segment should have correct name'
       )
       t.ok(connectSegment.timer.touched, 'connect should started and ended')
+      let connectChildren = transaction.trace.getChildren(connectSegment.id)
 
       // Depending on the version of Node there may be another connection segment
       // floating in the trace.
-      if (connectSegment.children[0].name === 'net.Socket.connect') {
-        connectSegment = connectSegment.children[0]
+      if (connectChildren[0].name === 'net.Socket.connect') {
+        connectSegment = connectChildren[0]
       }
+      connectChildren = transaction.trace.getChildren(connectSegment.id)
 
-      t.equal(connectSegment.children.length, 2, 'connect should have a two child segment')
-      const dnsSegment = connectSegment.children[0]
-      const timeoutSegment = connectSegment.children[1]
-
+      t.equal(connectChildren.length, 2, 'connect should have a two child segment')
+      const [dnsSegment, timeoutSegment] = connectChildren
       t.equal(dnsSegment.name, 'dns.lookup', 'dns segment should have correct name')
       t.ok(dnsSegment.timer.touched, 'dns segment should started and ended')
-      t.equal(dnsSegment.children.length, 1, 'dns should have a single callback segment')
+      const dnsChildren = transaction.trace.getChildren(dnsSegment.id)
+      t.equal(dnsChildren.length, 1, 'dns should have a single callback segment')
       t.equal(timeoutSegment.name, 'timers.setTimeout', 'timeout segment should have correct name')
       t.ok(timeoutSegment.timer.touched, 'timeout should started and ended')
-      t.equal(timeoutSegment.children.length, 1, 'timeout should have a single callback segment')
+      const timeoutChildren = transaction.trace.getChildren(timeoutSegment.id)
+      t.equal(timeoutChildren.length, 1, 'timeout should have a single callback segment')
       t.end()
     }
   }
@@ -162,34 +168,38 @@ test('createServer and connect', function createServerTest(t) {
     }
 
     function onClose() {
-      const root = agent.getTransaction().trace.root
-      t.equal(root.children.length, 2, 'should have 2 children')
-      let clientSegment = root.children[0]
+      const transaction = agent.getTransaction()
+      const children = transaction.trace.getChildren(transaction.trace.root.id)
+      t.equal(children.length, 2, 'should have 2 children')
+      let clientSegment = children[0]
       t.equal(clientSegment.name, 'net.connect', 'server segment should have correct name')
       t.ok(clientSegment.timer.touched, 'server should started and ended')
+      let clientChildren = transaction.trace.getChildren(clientSegment.id)
 
       // Depending on the version of Node there may be another connection segment
       // floating in the trace.
-      if (clientSegment.children[0].name === 'net.Socket.connect') {
-        clientSegment = clientSegment.children[0]
+      if (clientChildren[0].name === 'net.Socket.connect') {
+        clientSegment = clientChildren[0]
       }
+      clientChildren = transaction.trace.getChildren(clientSegment.id)
 
-      t.equal(clientSegment.children.length, 1, 'clientSegment should only have one child')
-      const dnsSegment = clientSegment.children[0]
+      t.equal(clientChildren.length, 1, 'clientSegment should only have one child')
+      const dnsSegment = clientChildren[0]
       if (dnsSegment) {
         t.equal(dnsSegment.name, 'dns.lookup', 'dnsSegment is named properly')
       } else {
         t.fail('did not have children, prevent undefined property lookup')
       }
 
-      const serverSegment = root.children[1]
+      const serverSegment = children[1]
       t.equal(
         serverSegment.name,
         'net.Server.onconnection',
         'server segment should have correct name'
       )
       t.ok(serverSegment.timer.touched, 'server should started and ended')
-      t.equal(serverSegment.children.length, 0, 'should not have any server segments')
+      const serverChildren = transaction.trace.getChildren(serverSegment.id)
+      t.equal(serverChildren.length, 0, 'should not have any server segments')
       t.end()
     }
   })
diff --git a/test/integration/core/timers.tap.js b/test/integration/core/timers.tap.js
index ef2d92cb61..23315028c8 100644
--- a/test/integration/core/timers.tap.js
+++ b/test/integration/core/timers.tap.js
@@ -28,10 +28,9 @@ tap.test('setImmediate', function testSetImmediate(t) {
     helper.runInTransaction(agent, function transactionWrapper(tx) {
       timers.setImmediate(function anonymous() {
         t.equal(agent.getTransaction().id, tx.id, 'should be in expected transaction')
-        t.notOk(
-          agent.getTransaction().trace.root.children.length,
-          'should not have any segment for setImmediate'
-        )
+        const transaction = agent.getTransaction()
+        const children = transaction.trace.getChildren(transaction.trace.root.id)
+        t.equal(children.length, 0)
       })
     })
   })
@@ -102,11 +101,13 @@ tap.test('setImmediate', function testSetImmediate(t) {
       helper.runInSegment(agent, 'test-segment', () => {
         const segment = agent.tracer.getSegment()
         t.not(segment.name, 'test-segment')
-        t.equal(segment.children.length, 0, 'should not propagate segments when transaction ends')
+        const children = transaction.trace.getChildren(segment.id)
+        t.equal(children.length, 0, 'should not propagate segments when transaction ends')
         setImmediate(() => {
           const segment = agent.tracer.getSegment()
           t.not(segment.name, 'test-segment')
-          t.equal(segment.children.length, 0, 'should not propagate segments when transaction ends')
+          const children = transaction.trace.getChildren(segment.id)
+          t.equal(children.length, 0, 'should not propagate segments when transaction ends')
           t.end()
         })
       })
@@ -138,7 +139,8 @@ tap.test('global setImmediate', function testSetImmediate(t) {
   helper.runInTransaction(agent, function transactionWrapper(transaction) {
     setImmediate(function anonymous() {
       t.equal(agent.getTransaction(), transaction)
-      t.equal(agent.getTransaction().trace.root.children.length, 0)
+      const children = transaction.trace.getChildren(transaction.trace.root.id)
+      t.equal(children.length, 0)
       t.end()
     })
   })
@@ -159,7 +161,8 @@ tap.test('nextTick', function testNextTick(t) {
   helper.runInTransaction(agent, function transactionWrapper(transaction) {
     process.nextTick(function callback() {
       t.equal(agent.getTransaction(), transaction)
-      t.equal(agent.getTransaction().trace.root.children.length, 0)
+      const children = transaction.trace.getChildren(transaction.trace.root.id)
+      t.equal(children.length, 0)
       t.end()
     })
   })
@@ -173,7 +176,8 @@ tap.test('nextTick with extra args', function testNextTick(t) {
     process.nextTick(
       function callback() {
         t.equal(agent.getTransaction(), transaction)
-        t.equal(agent.getTransaction().trace.root.children.length, 0)
+        const children = transaction.trace.getChildren(transaction.trace.root.id)
+        t.equal(children.length, 0)
         t.same([].slice.call(arguments), [1, 2, 3])
         process.nextTick = original
         t.end()
@@ -201,7 +205,8 @@ tap.test('clearImmediate', (t) => {
   helper.runInTransaction(agent, function transactionWrapper(transaction) {
     process.nextTick(function callback() {
       const timer2 = setImmediate(t.fail)
-      t.notOk(transaction.trace.root.children[0])
+      const children = transaction.trace.getChildren(transaction.trace.root.id)
+      t.equal(children.length, 0)
       clearImmediate(timer2)
       setImmediate(t.end.bind(t))
     })
@@ -225,7 +230,7 @@ tap.test('clearTimeout should ignore segment created for timer', (t) => {
     process.nextTick(function callback() {
       const timer = setTimeout(t.fail)
 
-      const timerSegment = transaction.trace.root.children[0]
+      const [timerSegment] = transaction.trace.getChildren(transaction.trace.root.id)
       t.equal(timerSegment.name, 'timers.setTimeout')
       t.equal(timerSegment.ignore, false)
 
@@ -249,7 +254,7 @@ tap.test('clearTimeout should not ignore parent segment when opaque', (t) => {
 
         const timer = setTimeout(t.fail)
 
-        const parentSegment = transaction.trace.root.children[0]
+        const [parentSegment] = transaction.trace.getChildren(transaction.trace.root.id)
         t.equal(parentSegment.name, expectedParentName)
         t.equal(parentSegment.ignore, false)
 
@@ -274,7 +279,7 @@ tap.test('clearTimeout should not ignore parent segment when internal', (t) => {
 
         const timer = setTimeout(t.fail)
 
-        const parentSegment = transaction.trace.root.children[0]
+        const [parentSegment] = transaction.trace.getChildren(transaction.trace.root.id)
         t.equal(parentSegment.name, expectedParentName)
         t.equal(parentSegment.ignore, false)
 
diff --git a/test/integration/core/verify.js b/test/integration/core/verify.js
index c6f209ba04..9c3dcd7570 100644
--- a/test/integration/core/verify.js
+++ b/test/integration/core/verify.js
@@ -8,21 +8,23 @@
 module.exports = verifySegments
 
 function verifySegments(t, agent, name, extras, done) {
-  const root = agent.getTransaction().trace.root
+  const { trace } = agent.getTransaction()
+  const children = trace.getChildren(trace.root.id)
   if (!extras) {
     extras = []
   }
-  t.equal(root.children.length, 1, 'should have a single child')
-  const child = root.children[0]
+  t.equal(children.length, 1, 'should have a single child')
+  const child = children[0]
+  const childChildren = trace.getChildren(child.id)
   t.equal(child.name, name, 'child segment should have correct name')
   t.ok(child.timer.touched, 'child should started and ended')
-  t.equal(child.children.length, 1 + extras.length, 'child should have a single callback segment')
+  t.equal(childChildren.length, 1 + extras.length, 'child should have a single callback segment')
 
   for (let i = 0; i < extras.length; ++i) {
-    t.equal(child.children[i].name, extras[i])
+    t.equal(childChildren[i].name, extras[i])
   }
 
-  const callback = child.children[child.children.length - 1]
+  const callback = childChildren[childChildren.length - 1]
   t.ok(
     callback.name === 'Callback: anonymous' || callback.name === 'Callback: <anonymous>',
     'callback segment should have correct name'
diff --git a/test/integration/instrumentation/fetch.tap.js b/test/integration/instrumentation/fetch.tap.js
index 3dbe1c4251..cffef313f0 100644
--- a/test/integration/instrumentation/fetch.tap.js
+++ b/test/integration/instrumentation/fetch.tap.js
@@ -81,7 +81,7 @@ tap.test('fetch', { skip: semver.lte(process.version, '18.0.0') }, function (t)
       })
       t.equal(status, 200)
 
-      t.assertSegments(tx.trace.root, [`External/${HOST}/post`], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/post`], { exact: false })
       tx.end()
       t.end()
     })
@@ -91,7 +91,7 @@ tap.test('fetch', { skip: semver.lte(process.version, '18.0.0') }, function (t)
     helper.runInTransaction(agent, async (tx) => {
       const { status } = await fetch(`${REQUEST_URL}/get?a=b&c=d`)
       t.equal(status, 200)
-      const segment = metrics.findSegment(tx.trace.root, `External/${HOST}/get`)
+      const segment = metrics.findSegment(tx.trace, tx.trace.root, `External/${HOST}/get`)
       const attrs = segment.getAttributes()
       t.equal(attrs.url, `${REQUEST_URL}/get`)
       t.equal(attrs.procedure, 'GET')
@@ -152,7 +152,7 @@ tap.test('fetch', { skip: semver.lte(process.version, '18.0.0') }, function (t)
       const [{ status }, { status: status2 }] = await Promise.all([req1, req2])
       t.equal(status, 200)
       t.equal(status2, 200)
-      t.assertSegments(tx.trace.root, [`External/${HOST}/post`, `External/${HOST}/put`], {
+      t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/post`, `External/${HOST}/put`], {
         exact: false
       })
       tx.end()
@@ -168,7 +168,7 @@ tap.test('fetch', { skip: semver.lte(process.version, '18.0.0') }, function (t)
         })
       } catch (err) {
         t.equal(err.message, 'fetch failed')
-        t.assertSegments(tx.trace.root, ['External/invalidurl/foo'], { exact: false })
+        t.assertSegments(tx.trace, tx.trace.root, ['External/invalidurl/foo'], { exact: false })
         t.equal(tx.exceptions.length, 1)
         tx.end()
         t.end()
@@ -188,7 +188,7 @@ tap.test('fetch', { skip: semver.lte(process.version, '18.0.0') }, function (t)
         }, 100)
         await req
       } catch (err) {
-        t.assertSegments(tx.trace.root, [`External/${HOST}/delay/1000`], { exact: false })
+        t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/delay/1000`], { exact: false })
         t.equal(tx.exceptions.length, 1)
         t.equal(tx.exceptions[0].error.name, 'AbortError')
         tx.end()
@@ -215,11 +215,16 @@ tap.test('fetch', { skip: semver.lte(process.version, '18.0.0') }, function (t)
       try {
         await req
       } catch (error) {
-        t.assertSegments(transaction.trace.root, [`External/localhost:${port}/`], {
-          exact: false
-        })
+        t.assertSegments(
+          transaction.trace,
+          transaction.trace.root,
+          [`External/localhost:${port}/`],
+          {
+            exact: false
+          }
+        )
 
-        const segments = transaction.trace.root.children
+        const segments = transaction.trace.getChildren(transaction.trace.root.id)
         const segment = segments[segments.length - 1]
 
         t.ok(segment.timer.start, 'should have started')
@@ -236,7 +241,7 @@ tap.test('fetch', { skip: semver.lte(process.version, '18.0.0') }, function (t)
     helper.runInTransaction(agent, async (tx) => {
       const { status } = await fetch(`${REQUEST_URL}/status/400`)
       t.equal(status, 400)
-      t.assertSegments(tx.trace.root, [`External/${HOST}/status/400`], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/status/400`], { exact: false })
       tx.end()
       t.end()
     })
diff --git a/test/integration/instrumentation/http-outbound.tap.js b/test/integration/instrumentation/http-outbound.tap.js
index da629ebf8f..7ea7377c3b 100644
--- a/test/integration/instrumentation/http-outbound.tap.js
+++ b/test/integration/instrumentation/http-outbound.tap.js
@@ -30,11 +30,11 @@ tap.test('external requests', function (t) {
 
     notVeryReliable.listen(0)
 
-    helper.runInTransaction(agent, function inTransaction() {
+    helper.runInTransaction(agent, function inTransaction(tx) {
       const req = http.get(notVeryReliable.address())
 
       req.on('error', function onError() {
-        const segment = agent.tracer.getTransaction().trace.root.children[0]
+        const [segment] = tx.trace.getChildren(tx.trace.root.id)
 
         t.equal(
           segment.name,
@@ -82,7 +82,7 @@ tap.test('external requests', function (t) {
     })
 
     function check(tx) {
-      const external = tx.trace.root.children[0]
+      const [external] = tx.trace.getChildren(tx.trace.root.id)
       t.equal(
         external.name,
         'External/localhost:' + server.address().port + '/some/path',
@@ -90,21 +90,24 @@ tap.test('external requests', function (t) {
       )
       t.ok(external.timer.start, 'should have started')
       t.ok(external.timer.hasEnd(), 'should have ended')
-      t.ok(external.children.length, 'should have children')
+      const externalChildren = tx.trace.getChildren(external.id)
+      t.ok(externalChildren.length, 'should have children')
 
-      let connect = external.children[0]
+      let connect = externalChildren[0]
       t.equal(connect.name, 'http.Agent#createConnection', 'should be connect segment')
-      t.equal(connect.children.length, 1, 'connect should have 1 child')
+      let connectChildren = tx.trace.getChildren(connect.id)
+      t.equal(connectChildren.length, 1, 'connect should have 1 child')
 
       // There is potentially an extra layer of create/connect segments.
-      if (connect.children[0].name === 'net.Socket.connect') {
-        connect = connect.children[0]
+      if (connectChildren[0].name === 'net.Socket.connect') {
+        connect = connectChildren[0]
       }
+      connectChildren = tx.trace.getChildren(connect.id)
 
-      const dnsLookup = connect.children[0]
+      const dnsLookup = connectChildren[0]
       t.equal(dnsLookup.name, 'dns.lookup', 'should be dns.lookup segment')
 
-      const callback = external.children[external.children.length - 1]
+      const callback = externalChildren[externalChildren.length - 1]
       t.equal(callback.name, 'timers.setTimeout', 'should have timeout segment')
 
       t.end()
@@ -122,15 +125,16 @@ tap.test('external requests', function (t) {
     })
 
     function check() {
-      const root = agent.tracer.getTransaction().trace.root
-      const segment = root.children[0]
+      const tx = agent.getTransaction()
+      const [segment] = tx.trace.getChildren(tx.trace.root.id)
 
       t.equal(segment.name, 'External/example.com/', 'should be named')
       t.ok(segment.timer.start, 'should have started')
       t.ok(segment.timer.hasEnd(), 'should have ended')
-      t.equal(segment.children.length, 1, 'should have 1 child')
+      const segmentChildren = tx.trace.getChildren(segment.id)
+      t.equal(segmentChildren.length, 1, 'should have 1 child')
 
-      const notDuped = segment.children[0]
+      const notDuped = segmentChildren[0]
       t.not(
         notDuped.name,
         segment.name,
@@ -167,7 +171,7 @@ tap.test('external requests', function (t) {
       http.get('http://example.com', (res) => {
         res.resume()
         res.on('end', () => {
-          const segment = tx.trace.root.children[0]
+          const [segment] = tx.trace.getChildren(tx.trace.root.id)
           t.equal(segment.name, 'External/example.com/', 'should create external segment')
           t.end()
         })
@@ -181,7 +185,7 @@ tap.test('external requests', function (t) {
       const req = http.get('http://example.com', (res) => {
         res.resume()
         res.on('end', () => {
-          const segment = tx.trace.root.children[0]
+          const [segment] = tx.trace.getChildren(tx.trace.root.id)
           const attrs = segment.getAttributes()
           t.same(attrs, {
             url: 'http://example.com/',
diff --git a/test/integration/transaction/tracer.tap.js b/test/integration/transaction/tracer.tap.js
index e1356d7f04..59320d217f 100644
--- a/test/integration/transaction/tracer.tap.js
+++ b/test/integration/transaction/tracer.tap.js
@@ -287,9 +287,11 @@ test('getSegment', function testGetTransaction(t) {
     t.equal(tracer.getSegment(), tracer.getSegment())
 
     setTimeout(function onTimeout() {
-      const segment = root.children[0].children[0]
-      t.equal(tracer.getSegment(), segment)
-      t.equal(tracer.getSegment().name, 'Callback: onTimeout')
+      const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+      const [setTimeoutCb] = transaction.trace.getChildren(child.id)
+      const segment = tracer.getSegment()
+      t.equal(segment, setTimeoutCb)
+      t.equal(segment.name, 'Callback: onTimeout')
     }, 0)
   })
 })
@@ -375,12 +377,14 @@ test('addSegment', function addSegmentTest(t) {
 
     t.equal(segment.name, 'inside')
     root = transaction.trace.root
-    t.equal(root.children[0], segment)
+    let [child] = transaction.trace.getChildren(root.id)
+    t.equal(child, segment)
 
     const outside = tracer.addSegment('outside', null, root, false, check)
+    ;[, child] = transaction.trace.getChildren(root.id)
 
     t.equal(outside.name, 'outside')
-    t.equal(root.children[1], outside)
+    t.equal(child, outside)
 
     t.end()
   })
@@ -403,7 +407,8 @@ test('addSegment + recorder', function addSegmentTest(t) {
 
     t.equal(segment.name, 'inside')
     t.equal(segment.timer.hrDuration, null)
-    t.equal(root.children[0], segment)
+    const [child] = transaction.trace.getChildren(root.id)
+    t.equal(child, segment)
 
     transaction.end()
     t.end()
@@ -433,7 +438,8 @@ test('addSegment + full', function addSegmentTest(t) {
 
     t.equal(segment.name, 'inside')
     t.ok(segment.timer.hrDuration)
-    t.equal(root.children[0], segment)
+    const [child] = transaction.trace.getChildren(root.id)
+    t.equal(child, segment)
 
     transaction.end()
     // because having plan + end after async causes issues
@@ -615,12 +621,14 @@ test('wrapFunction', function testwrapFunction(t) {
   function makeCallback(val) {
     return function callback(parent, arg) {
       const segment = tracer.getSegment()
+      const transaction = tracer.getTransaction()
       t.equal(arg, val)
       t.equal(this, inner)
       if (parent) {
+        const children = transaction.trace.getChildren(parent.id)
         t.ok(segment.timer.hrstart)
         t.notOk(segment.timer.hrDuration)
-        t.not(parent.children.indexOf(segment), -1)
+        t.not(children.indexOf(segment), -1)
       }
 
       return val
@@ -641,8 +649,10 @@ test('wrapFunction', function testwrapFunction(t) {
 
     t.equal(this, outer)
     process.nextTick(function next() {
+      let children
       if (segment) {
-        t.equal(segment.children.length, 0)
+        children = transaction.trace.getChildren(segment.id)
+        t.equal(children.length, 0)
       }
 
       t.equal(a.call(inner, segment, 'a'), 'a')
@@ -650,7 +660,8 @@ test('wrapFunction', function testwrapFunction(t) {
       t.equal(c.call(inner, segment, 'c'), 'c')
 
       if (segment) {
-        segment.children.forEach(function (child) {
+        children = transaction.trace.getChildren(segment.id)
+        children.forEach(function (child) {
           t.ok(child.timer.hrstart)
           t.ok(child.timer.hrDuration)
         })
@@ -708,13 +719,15 @@ test('wrapFunctionLast', function testwrapFunctionLast(t) {
 
   function callback(parent, callbackArgs) {
     const segment = tracer.getSegment()
+    const transaction = tracer.getTransaction()
     t.same(callbackArgs, [1, 2, 3])
     t.equal(this, inner)
 
     if (parent) {
       t.ok(segment.timer.hrstart)
       t.notOk(segment.timer.hrDuration)
-      t.equal(parent.children[0], segment)
+      const [child] = transaction.trace.getChildren(parent.id)
+      t.equal(child, segment)
     }
 
     return innerReturn
@@ -736,16 +749,19 @@ test('wrapFunctionLast', function testwrapFunctionLast(t) {
 
     t.equal(this, outer)
     process.nextTick(function next() {
+      let children
       if (segment) {
-        t.equal(segment.children.length, 0)
+        children = transaction.trace.getChildren(segment.id)
+        t.equal(children.length, 0)
       }
 
       t.equal(cb.call(inner, segment, cbArgs), innerReturn)
 
       if (segment) {
-        t.equal(segment.children.length, 1)
-        t.ok(segment.children[0].timer.hrstart)
-        t.ok(segment.children[0].timer.hrDuration)
+        children = transaction.trace.getChildren(segment.id)
+        t.equal(children.length, 1)
+        t.ok(children[0].timer.hrstart)
+        t.ok(children[0].timer.hrDuration)
         t.ok(segment.timer.hrDuration)
         transaction.end()
       }
@@ -782,13 +798,15 @@ test('wrapFunctionFirst', function testwrapFunctionFirst(t) {
 
   function callback(parent, args) {
     const segment = tracer.getSegment()
+    const transaction = tracer.getTransaction()
     t.same(args, [1, 2, 3])
     t.equal(this, inner)
 
     if (parent) {
       t.ok(segment.timer.hrstart)
       t.notOk(segment.timer.hrDuration)
-      t.equal(parent.children[0], segment)
+      const [child] = transaction.trace.getChildren(parent.id)
+      t.equal(child, segment)
     }
 
     return innerReturn
@@ -809,16 +827,19 @@ test('wrapFunctionFirst', function testwrapFunctionFirst(t) {
 
     t.equal(this, outer)
     process.nextTick(function next() {
+      let children
       if (segment) {
-        t.equal(segment.children.length, 0)
+        children = transaction.trace.getChildren(segment.id)
+        t.equal(children.length, 0)
       }
 
       t.equal(cb.call(inner, segment, args), innerReturn)
 
       if (segment) {
-        t.equal(segment.children.length, 1)
-        t.ok(segment.children[0].timer.hrstart)
-        t.ok(segment.children[0].timer.hrDuration)
+        children = transaction.trace.getChildren(segment.id)
+        t.equal(children.length, 1)
+        t.ok(children[0].timer.hrstart)
+        t.ok(children[0].timer.hrDuration)
         t.ok(segment.timer.hrDuration)
         transaction.end()
       }
@@ -844,8 +865,9 @@ test('wrapSyncFunction', function testwrapSyncFunction(t) {
 
   helper.runInTransaction(agent, function inTrans(transaction) {
     wrapped(transaction, [4], 4)
-    t.ok(transaction.trace.root.children[0].timer.hrstart)
-    t.ok(transaction.trace.root.children[0].timer.hrDuration)
+    const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+    t.ok(child.timer.hrstart)
+    t.ok(child.timer.hrDuration)
     transaction.end()
   })
 
@@ -857,8 +879,9 @@ test('wrapSyncFunction', function testwrapSyncFunction(t) {
     }
   }
 
-  function record(segment) {
-    t.equal(segment, segment.root.children[0])
+  function record(segment, scope, transaction) {
+    const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+    t.equal(segment, child)
     t.equal(segment.name, 'my segment')
     t.end()
   }
diff --git a/test/lib/agent_helper.js b/test/lib/agent_helper.js
index 54cd3db684..f2994538b5 100644
--- a/test/lib/agent_helper.js
+++ b/test/lib/agent_helper.js
@@ -85,14 +85,6 @@ helper.loadMockedAgent = function loadMockedAgent(conf, setState = true) {
   // agent needs a 'real' configuration
   const configurator = require('../../lib/config')
   const config = configurator.createInstance(conf)
-
-  if (!config.debug) {
-    config.debug = {}
-  }
-
-  // adds link to parents node in traces for easier testing
-  config.debug.double_linked_transactions = true
-
   // stub applications
   config.applications = () => ['New Relic for Node.js tests']
 
diff --git a/test/lib/custom-assertions.js b/test/lib/custom-assertions.js
index d36be90487..cb62e80e60 100644
--- a/test/lib/custom-assertions.js
+++ b/test/lib/custom-assertions.js
@@ -83,13 +83,17 @@ function isNonWritable({ obj, key, value }) {
  *  Verifies the expected length of children segments and that every
  *  id matches between a segment array and the children
  *
- *  @param {Object} parent trace
- *  @param {Array} segments list of expected segments
+ *  @param {object} params to function
+ *  @param {TraceSegment} params.parent segment
+ *  @param {Array} params.segments list of expected segments
+ *  @param {Trace} params.trace transaction trace
+
  */
-function compareSegments(parent, segments) {
-  assert.ok(parent.children.length, segments.length, 'should be the same amount of children')
+function compareSegments({ parent, segments, trace }) {
+  const parentChildren = trace.getChildren(parent.id)
+  assert.ok(parentChildren.length, segments.length, 'should be the same amount of children')
   segments.forEach((segment, index) => {
-    assert.equal(parent.children[index].id, segment.id, 'should have same ids')
+    assert.equal(parentChildren[index].id, segment.id, 'should have same ids')
   })
 }
 
@@ -107,7 +111,7 @@ function compareSegments(parent, segments) {
  *                                  segment may or may not be created by code that is not
  *                                  directly under test.  Only used when `exact` is true.
  */
-function assertSegments(parent, expected, options) {
+function assertSegments(trace, parent, expected, options) {
   let child
   let childCount = 0
 
@@ -120,7 +124,8 @@ function assertSegments(parent, expected, options) {
   }
 
   function getChildren(_parent) {
-    return _parent.children.filter(function (item) {
+    const children = trace.getChildren(_parent.id)
+    return children.filter(function (item) {
       if (exact && options && options.exclude) {
         return options.exclude.indexOf(item.name) === -1
       }
@@ -155,7 +160,7 @@ function assertSegments(parent, expected, options) {
           )
         }
       } else if (typeof sequenceItem === 'object') {
-        assertSegments(child, sequenceItem, options)
+        assertSegments(trace, child, sequenceItem, options)
       }
     }
 
@@ -167,14 +172,14 @@ function assertSegments(parent, expected, options) {
 
       if (typeof sequenceItem === 'string') {
         // find corresponding child in parent
-        for (let j = 0; j < parent.children.length; j++) {
-          if (parent.children[j].name === sequenceItem) {
-            child = parent.children[j]
+        for (let j = 0; j < children.length; j++) {
+          if (children[j].name === sequenceItem) {
+            child = children[j]
           }
         }
         assert.ok(child, 'segment "' + parent.name + '" should have child "' + sequenceItem + '"')
         if (typeof expected[i + 1] === 'object') {
-          assertSegments(child, expected[i + 1], exact)
+          assertSegments(trace, child, expected[i + 1], exact)
         }
       }
     }
diff --git a/test/lib/custom-tap-assertions.js b/test/lib/custom-tap-assertions.js
index 78be0f138c..b6e83108bc 100644
--- a/test/lib/custom-tap-assertions.js
+++ b/test/lib/custom-tap-assertions.js
@@ -6,18 +6,6 @@
 'use strict'
 const tap = require('tap')
 tap.Test.prototype.addAssert('clmAttrs', 1, assertCLMAttrs)
-tap.Test.prototype.addAssert('isNonWritable', 1, isNonWritable)
-tap.Test.prototype.addAssert('compareSegments', 2, compareSegments)
-tap.Test.prototype.addAssert('exactClmAttrs', 2, assertExactClmAttrs)
-
-function assertExactClmAttrs(segmentStub, expectedAttrs) {
-  const attrs = segmentStub.addAttribute.args
-  const attrsObj = attrs.reduce((obj, [key, value]) => {
-    obj[key] = value
-    return obj
-  }, {})
-  this.same(attrsObj, expectedAttrs, 'CLM attrs should match')
-}
 
 /**
  * Asserts the appropriate Code Level Metrics attributes on a segment
@@ -49,37 +37,3 @@ function assertCLMAttrs({ segments, enabled: clmEnabled, skipFull = false }) {
     }
   })
 }
-
-/**
- * assertion to test if a property is non-writable
- *
- * @param {Object} params
- * @param {Object} params.obj obj to assign value
- * @param {string} params.key key to assign value
- * @param {string} params.value expected value of obj[key]
- */
-function isNonWritable({ obj, key, value }) {
-  this.throws(function () {
-    obj[key] = 'testNonWritable test value'
-  }, new RegExp("(read only property '" + key + "'|Cannot set property " + key + ')'))
-
-  if (value) {
-    this.equal(obj[key], value)
-  } else {
-    this.not(obj[key], 'testNonWritable test value', 'should not set value when non-writable')
-  }
-}
-
-/**
- *  Verifies the expected length of children segments and that every
- *  id matches between a segment array and the children
- *
- *  @param {Object} parent trace
- *  @param {Array} segments list of expected segments
- */
-function compareSegments(parent, segments) {
-  this.ok(parent.children.length, segments.length, 'should be the same amount of children')
-  segments.forEach((segment, index) => {
-    this.equal(parent.children[index].id, segment.id, 'should have same ids')
-  })
-}
diff --git a/test/lib/metrics_helper.js b/test/lib/metrics_helper.js
index b5e3cb1370..6e2d6f8b51 100644
--- a/test/lib/metrics_helper.js
+++ b/test/lib/metrics_helper.js
@@ -12,7 +12,7 @@ const { isSimpleObject } = require('../../lib/util/objects')
 exports.findSegment = findSegment
 exports.getMetricHostName = getMetricHostName
 tap.Test.prototype.addAssert('assertMetrics', 4, assertMetrics)
-tap.Test.prototype.addAssert('assertSegments', 3, assertSegments)
+tap.Test.prototype.addAssert('assertSegments', 4, assertSegments)
 tap.Test.prototype.addAssert('assertMetricValues', 3, assertMetricValues)
 
 /**
@@ -119,7 +119,7 @@ function assertMetricValues(transaction, expected, exact) {
  *                                  segment may or may not be created by code that is not
  *                                  directly under test.  Only used when `exact` is true.
  */
-function assertSegments(parent, expected, options) {
+function assertSegments(trace, parent, expected, options) {
   let child
   let childCount = 0
 
@@ -132,7 +132,8 @@ function assertSegments(parent, expected, options) {
   }
 
   function getChildren(_parent) {
-    return _parent.children.filter(function (item) {
+    const children = trace.getChildren(_parent.id)
+    return children.filter(function (item) {
       if (exact && options && options.exclude) {
         return options.exclude.indexOf(item.name) === -1
       }
@@ -167,7 +168,7 @@ function assertSegments(parent, expected, options) {
           )
         }
       } else if (typeof sequenceItem === 'object') {
-        this.assertSegments(child, sequenceItem, options)
+        this.assertSegments(trace, child, sequenceItem, options)
       }
     }
 
@@ -179,27 +180,28 @@ function assertSegments(parent, expected, options) {
 
       if (typeof sequenceItem === 'string') {
         // find corresponding child in parent
-        for (let j = 0; j < parent.children.length; j++) {
-          if (parent.children[j].name === sequenceItem) {
-            child = parent.children[j]
+        for (let j = 0; j < children.length; j++) {
+          if (children[j].name === sequenceItem) {
+            child = children[j]
           }
         }
         this.ok(child, 'segment "' + parent.name + '" should have child "' + sequenceItem + '"')
         if (typeof expected[i + 1] === 'object') {
-          this.assertSegments(child, expected[i + 1], exact)
+          this.assertSegments(trace, child, expected[i + 1], exact)
         }
       }
     }
   }
 }
 
-function findSegment(root, name) {
+function findSegment(trace, root, name) {
+  const children = trace.getChildren(root.id)
   if (root.name === name) {
     return root
-  } else if (root.children && root.children.length) {
-    for (let i = 0; i < root.children.length; i++) {
-      const child = root.children[i]
-      const found = findSegment(child, name)
+  } else if (children.length) {
+    for (let i = 0; i < children.length; i++) {
+      const child = children[i]
+      const found = findSegment(trace, child, name)
       if (found) {
         return found
       }
diff --git a/test/unit/agent/agent.test.js b/test/unit/agent/agent.test.js
index 044cbd0b74..6f02918513 100644
--- a/test/unit/agent/agent.test.js
+++ b/test/unit/agent/agent.test.js
@@ -101,11 +101,6 @@ test('when loaded with defaults', async (t) => {
     const { agent } = t.nr
     assert.throws(() => agent.setState('bogus'), /Invalid state bogus/)
   })
-
-  await t.test('has some debugging configuration by default', (t) => {
-    const { agent } = t.nr
-    assert.equal(Object.hasOwn(agent.config, 'debug'), true)
-  })
 })
 
 test('should load naming rules when configured', () => {
diff --git a/test/unit/api/api-start-background-transaction.test.js b/test/unit/api/api-start-background-transaction.test.js
index bbbfda97d5..fd075f6c32 100644
--- a/test/unit/api/api-start-background-transaction.test.js
+++ b/test/unit/api/api-start-background-transaction.test.js
@@ -51,7 +51,7 @@ test('Agent API - startBackgroundTransaction', async (t) => {
       assert.ok(transaction.isActive())
 
       const currentSegment = tracer.getSegment()
-      const nestedSegment = currentSegment.children[0]
+      const [nestedSegment] = transaction.trace.getChildren(currentSegment.id)
       assert.equal(nestedSegment.name, 'Nodejs/nested')
     })
 
@@ -221,7 +221,8 @@ test('Agent API - startBackgroundTransaction', async (t) => {
           api.startBackgroundTransaction('nested-clm-test', function () {
             nested({ api })
             const currentSegment = tracer.getSegment()
-            const nestedSegment = currentSegment.children[0]
+            const transaction = agent.tracer.getTransaction()
+            const [nestedSegment] = transaction.trace.getChildren(currentSegment.id)
             assertCLMAttrs({
               segments: [
                 {
diff --git a/test/unit/api/api-start-web-transaction.test.js b/test/unit/api/api-start-web-transaction.test.js
index 95d1dc839c..7eb39274fa 100644
--- a/test/unit/api/api-start-web-transaction.test.js
+++ b/test/unit/api/api-start-web-transaction.test.js
@@ -49,7 +49,7 @@ test('Agent API - startWebTransaction', async (t) => {
       assert.ok(transaction.isActive())
 
       const currentSegment = tracer.getSegment()
-      const nestedSegment = currentSegment.children[0]
+      const [nestedSegment] = transaction.trace.getChildren(currentSegment.id)
       assert.equal(nestedSegment.name, 'nested')
     })
 
@@ -174,9 +174,10 @@ test('Agent API - startWebTransaction', async (t) => {
           const { agent, api, tracer } = t.nr
           agent.config.code_level_metrics.enabled = enabled
           api.startWebTransaction('clm-nested-test', function () {
+            const tx = agent.tracer.getTransaction()
             nested({ api })
             const currentSegment = tracer.getSegment()
-            const nestedSegment = currentSegment.children[0]
+            const [nestedSegment] = tx.trace.getChildren(currentSegment.id)
             assertCLMAttrs({
               segments: [
                 {
diff --git a/test/unit/instrumentation/core/promises.test.js b/test/unit/instrumentation/core/promises.test.js
index 73074d322b..89cc6fad25 100644
--- a/test/unit/instrumentation/core/promises.test.js
+++ b/test/unit/instrumentation/core/promises.test.js
@@ -158,7 +158,7 @@ function checkTrace(t, tx) {
   const expectedSegment = tracer.getSegment()
   const segment = tx.trace.root
   assert.equal(segment.name, 'a')
-  assert.equal(segment.children.length, 0)
+  assert.equal(tx.trace.getChildren(segment.id).length, 0)
   // verify current segment is same as trace root
   assert.deepEqual(segment.name, expectedSegment.name, 'current segment is same as one in tracer')
   return Promise.resolve()
diff --git a/test/unit/instrumentation/http/outbound.test.js b/test/unit/instrumentation/http/outbound.test.js
index 508b0daba6..b0f0b96b21 100644
--- a/test/unit/instrumentation/http/outbound.test.js
+++ b/test/unit/instrumentation/http/outbound.test.js
@@ -49,7 +49,8 @@ test('instrumentOutbound', async (t) => {
       const req = new events.EventEmitter()
       helper.runInTransaction(agent, function (transaction) {
         instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest)
-        assert.deepEqual(transaction.trace.root.children[0].getAttributes(), {})
+        const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+        assert.deepEqual(child.getAttributes(), {})
 
         function makeFakeRequest() {
           req.path = '/asdf?a=b&another=yourself&thing&grownup=true'
@@ -66,7 +67,8 @@ test('instrumentOutbound', async (t) => {
     const req = new events.EventEmitter()
     helper.runInTransaction(agent, function (transaction) {
       instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest)
-      assert.deepEqual(transaction.trace.root.children[0].getAttributes(), {
+      const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+      assert.deepEqual(child.getAttributes(), {
         procedure: 'GET',
         url: `http://${HOSTNAME}:${PORT}/asdf`
       })
@@ -91,7 +93,8 @@ test('instrumentOutbound', async (t) => {
     const req = new events.EventEmitter()
     helper.runInTransaction(agent, function (transaction) {
       instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest)
-      assert.deepEqual(transaction.trace.root.children[0].getAttributes(), {
+      const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+      assert.deepEqual(child.getAttributes(), {
         procedure: 'GET',
         url: `http://${HOSTNAME}:${PORT}/***`
       })
@@ -112,7 +115,8 @@ test('instrumentOutbound', async (t) => {
       const name = NAMES.EXTERNAL.PREFIX + HOSTNAME + ':' + PORT + path
 
       instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest)
-      assert.equal(transaction.trace.root.children[0].name, name)
+      const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+      assert.equal(child.name, name)
 
       function makeFakeRequest() {
         req.path = '/asdf?a=b&another=yourself&thing&grownup=true'
@@ -128,8 +132,9 @@ test('instrumentOutbound', async (t) => {
     helper.runInTransaction(agent, function (transaction) {
       agent.config.attributes.enabled = true
       instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest)
+      const [child] = transaction.trace.getChildren(transaction.trace.root.id)
       assert.deepEqual(
-        transaction.trace.root.children[0].attributes.get(DESTINATIONS.SPAN_EVENT),
+        child.attributes.get(DESTINATIONS.SPAN_EVENT),
         {
           'hostname': HOSTNAME,
           'port': PORT,
@@ -175,7 +180,8 @@ test('instrumentOutbound', async (t) => {
       const name = NAMES.EXTERNAL.PREFIX + HOSTNAME + ':' + PORT + path
       req.path = path
       instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest)
-      assert.equal(transaction.trace.root.children[0].name, name)
+      const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+      assert.equal(child.name, name)
       end()
     })
 
@@ -193,7 +199,8 @@ test('instrumentOutbound', async (t) => {
       const name = NAMES.EXTERNAL.PREFIX + HOSTNAME + ':' + PORT + '/newrelic'
       req.path = path
       instrumentOutbound(agent, { host: HOSTNAME, port: PORT }, makeFakeRequest)
-      assert.equal(transaction.trace.root.children[0].name, name)
+      const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+      assert.equal(child.name, name)
     })
 
     function makeFakeRequest() {
@@ -473,7 +480,8 @@ test('when working with http.request', async (t) => {
       opts.method = 'POST'
 
       const req = http.request(opts, function (res) {
-        const attributes = transaction.trace.root.children[0].getAttributes()
+        const [child] = transaction.trace.getChildren(transaction.trace.root.id)
+        const attributes = child.getAttributes()
         assert.equal(attributes.url, 'http://www.google.com/index.html')
         assert.equal(attributes.procedure, 'POST')
         res.resume()
diff --git a/test/unit/instrumentation/prisma-client.test.js b/test/unit/instrumentation/prisma-client.test.js
index 4101d0e06f..dd299cbecf 100644
--- a/test/unit/instrumentation/prisma-client.test.js
+++ b/test/unit/instrumentation/prisma-client.test.js
@@ -127,7 +127,7 @@ test('PrismaClient unit.tests', async (t) => {
         args: { query: 'select test from schema.unit-test;' },
         action: 'executeRaw'
       })
-      const { children } = tx.trace.root
+      const children = tx.trace.getChildren(tx.trace.root.id)
       assert.equal(children.length, 3, 'should have 3 segments')
       const [firstSegment, secondSegment, thirdSegment] = children
       assert.equal(firstSegment.name, 'Datastore/statement/Prisma/user/create')
@@ -180,7 +180,7 @@ test('PrismaClient unit.tests', async (t) => {
 
     helper.runInTransaction(agent, async (tx) => {
       await client._executeRequest({ action: 'executeRaw' })
-      const { children } = tx.trace.root
+      const children = tx.trace.getChildren(tx.trace.root.id)
       const [firstSegment] = children
       assert.equal(firstSegment.name, 'Datastore/statement/Prisma/other/other')
       end()
@@ -228,7 +228,7 @@ test('PrismaClient unit.tests', async (t) => {
         args: [['select test from unit-test;']],
         action: 'executeRaw'
       })
-      const { children } = tx.trace.root
+      const children = tx.trace.getChildren(tx.trace.root.id)
       assert.equal(children.length, 2, 'should have 3 segments')
       const [firstSegment, secondSegment] = children
       assert.equal(firstSegment.name, 'Datastore/statement/Prisma/user/create')
diff --git a/test/unit/instrumentation/redis.test.js b/test/unit/instrumentation/redis.test.js
index 36fa840b83..5f53feefdf 100644
--- a/test/unit/instrumentation/redis.test.js
+++ b/test/unit/instrumentation/redis.test.js
@@ -184,7 +184,7 @@ test('createClient saves connection options', async function (t) {
     helper.runInTransaction(agent, async function (tx) {
       await client.queue.addCommand(['test', 'key', 'value'])
       await client2.queue.addCommand(['test2', 'key2', 'value2'])
-      const [redisSegment, redisSegment2] = tx.trace.root.children
+      const [redisSegment, redisSegment2] = tx.trace.getChildren(tx.trace.root.id)
       const attrs = redisSegment.getAttributes()
       assert.deepEqual(
         attrs,
diff --git a/test/unit/llm-events/openai/common.js b/test/unit/llm-events/openai/common.js
index 1eab799cb4..93310fb04b 100644
--- a/test/unit/llm-events/openai/common.js
+++ b/test/unit/llm-events/openai/common.js
@@ -45,18 +45,20 @@ const req = {
 
 function getExpectedResult(tx, event, type, completionId) {
   const trace = tx.trace.root
+  const [child] = tx.trace.getChildren(trace.id)
+  const spanId = child.id
   let expected = {
     'id': event.id,
     'appName': 'New Relic for Node.js tests',
     'request_id': 'req-id',
     'trace_id': tx.traceId,
-    'span_id': trace.children[0].id,
+    'span_id': spanId,
     'response.model': 'gpt-3.5-turbo-0613',
     'vendor': 'openai',
     'ingest_source': 'Node'
   }
   const resKeys = {
-    'duration': trace.children[0].getDurationInMillis(),
+    'duration': child.getDurationInMillis(),
     'request.model': 'gpt-3.5-turbo-0613',
     'response.organization': 'new-relic',
     'response.headers.llmVersion': '1.0.0',
diff --git a/test/unit/shim/datastore-shim.test.js b/test/unit/shim/datastore-shim.test.js
index 344367a5af..83c96766b8 100644
--- a/test/unit/shim/datastore-shim.test.js
+++ b/test/unit/shim/datastore-shim.test.js
@@ -33,14 +33,9 @@ test('DatastoreShim', async function (t) {
         return agent.tracer.getSegment()
       },
       withNested: function () {
-        const transaction = agent.tracer.getTransaction()
+        const tx = agent.tracer.getTransaction()
         const segment = agent.tracer.getSegment()
-        segment.add({
-          config: agent.config,
-          name: 'ChildSegment',
-          root: transaction.trace.root
-        })
-
+        tx.trace.add('ChildSegment', null, segment)
         return segment
       }
     }
@@ -381,13 +376,15 @@ test('DatastoreShim', async function (t) {
       shim.recordOperation(wrappable, 'withNested', () => {
         return new OperationSpec({ name: 'test', opaque: false })
       })
-      helper.runInTransaction(agent, () => {
+      helper.runInTransaction(agent, (tx) => {
         const startingSegment = agent.tracer.getSegment()
         const segment = wrappable.withNested()
         assert.notEqual(segment, startingSegment)
         assert.equal(segment.name, 'Datastore/operation/Cassandra/test')
-        assert.equal(segment.children.length, 1)
-        const [childSegment] = segment.children
+
+        const children = tx.trace.getChildren(segment.id)
+        assert.equal(children.length, 1)
+        const [childSegment] = children
         assert.equal(childSegment.name, 'ChildSegment')
         end()
       })
@@ -398,12 +395,13 @@ test('DatastoreShim', async function (t) {
       shim.recordOperation(wrappable, 'withNested', () => {
         return new OperationSpec({ name: 'test', opaque: true })
       })
-      helper.runInTransaction(agent, () => {
+      helper.runInTransaction(agent, (tx) => {
         const startingSegment = agent.tracer.getSegment()
         const segment = wrappable.withNested()
         assert.notEqual(segment, startingSegment)
         assert.equal(segment.name, 'Datastore/operation/Cassandra/test')
-        assert.equal(segment.children.length, 0)
+        const children = tx.trace.getChildren(segment.id)
+        assert.equal(children.length, 0)
         end()
       })
     })
@@ -778,7 +776,8 @@ test('DatastoreShim', async function (t) {
       helper.runInTransaction(agent, (tx) => {
         wrappable.bar()
         const rootSegment = agent.tracer.getSegment()
-        const attrs = rootSegment.children[0].getAttributes()
+        const [child] = tx.trace.getChildren(rootSegment.id)
+        const attrs = child.getAttributes()
         assert.equal(
           attrs['test-attr'],
           'unit-test',
@@ -923,7 +922,7 @@ test('DatastoreShim', async function (t) {
 
     await t.test('should create a new segment on the first call', function (t, end) {
       const { agent, shim, wrappable } = t.nr
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const args = [1, 2, wrappable.getActiveSegment]
         shim.bindRowCallbackSegment(args, shim.LAST)
 
@@ -931,14 +930,15 @@ test('DatastoreShim', async function (t) {
         const segment = shim.getSegment()
         const cbSegment = args[2]()
         assert.notEqual(cbSegment, segment)
-        assert.ok(segment.children.includes(cbSegment))
+        const children = tx.trace.getChildren(segment.id)
+        assert.ok(children.includes(cbSegment))
         end()
       })
     })
 
     await t.test('should not create a new segment for calls after the first', function (t, end) {
       const { agent, shim, wrappable } = t.nr
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const args = [1, 2, wrappable.getActiveSegment]
         shim.bindRowCallbackSegment(args, shim.LAST)
 
@@ -946,13 +946,15 @@ test('DatastoreShim', async function (t) {
         const segment = shim.getSegment()
         const cbSegment = args[2]()
         assert.notEqual(cbSegment, segment)
-        assert.ok(segment.children.includes(cbSegment))
-        assert.equal(segment.children.length, 1)
+        let children = tx.trace.getChildren(segment.id)
+        assert.ok(children.includes(cbSegment))
+        assert.equal(children.length, 1)
 
         // Call it a second time and see if we have the same segment.
         const cbSegment2 = args[2]()
         assert.equal(cbSegment2, cbSegment)
-        assert.equal(segment.children.length, 1)
+        children = tx.trace.getChildren(segment.id)
+        assert.equal(children.length, 1)
         end()
       })
     })
diff --git a/test/unit/shim/message-shim.test.js b/test/unit/shim/message-shim.test.js
index e81538540b..148ea75754 100644
--- a/test/unit/shim/message-shim.test.js
+++ b/test/unit/shim/message-shim.test.js
@@ -44,12 +44,7 @@ test('MessageShim', async function (t) {
       withNested: function () {
         const transaction = agent.tracer.getTransaction()
         const segment = agent.tracer.getSegment()
-        segment.add({
-          config: agent.config,
-          name: 'ChildSegment',
-          root: transaction.trace.root
-        })
-
+        transaction.trace.add('ChildSegment', null, segment)
         return segment
       }
     }
@@ -329,12 +324,13 @@ test('MessageShim', async function (t) {
         return new MessageSpec({ destinationName: 'foobar', opaque: false })
       })
 
-      helper.runInTransaction(agent, () => {
+      helper.runInTransaction(agent, (tx) => {
         const segment = wrappable.withNested()
         assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/foobar')
 
-        assert.equal(segment.children.length, 1)
-        const [childSegment] = segment.children
+        const children = tx.trace.getChildren(segment.id)
+        assert.equal(children.length, 1)
+        const [childSegment] = children
         assert.equal(childSegment.name, 'ChildSegment')
         end()
       })
@@ -346,11 +342,12 @@ test('MessageShim', async function (t) {
         return new MessageSpec({ destinationName: 'foobar', opaque: true })
       })
 
-      helper.runInTransaction(agent, () => {
+      helper.runInTransaction(agent, (tx) => {
         const segment = wrappable.withNested()
         assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Produce/Named/foobar')
 
-        assert.equal(segment.children.length, 0)
+        const children = tx.trace.getChildren(segment.id)
+        assert.equal(children.length, 0)
         end()
       })
     })
@@ -623,12 +620,13 @@ test('MessageShim', async function (t) {
         return new MessageSpec({ destinationName: 'foobar', opaque: false })
       })
 
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const segment = wrappable.withNested()
         assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Consume/Named/foobar')
 
-        assert.equal(segment.children.length, 1)
-        const [childSegment] = segment.children
+        const children = tx.trace.getChildren(segment.id)
+        assert.equal(children.length, 1)
+        const [childSegment] = children
         assert.equal(childSegment.name, 'ChildSegment')
         end()
       })
@@ -640,10 +638,11 @@ test('MessageShim', async function (t) {
         return new MessageSpec({ destinationName: 'foobar', opaque: true })
       })
 
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const segment = wrappable.withNested()
         assert.equal(segment.name, 'MessageBroker/RabbitMQ/Exchange/Consume/Named/foobar')
-        assert.equal(segment.children.length, 0)
+        const children = tx.trace.getChildren(segment.id)
+        assert.equal(children.length, 0)
         end()
       })
     })
@@ -1213,11 +1212,12 @@ test('MessageShim', async function (t) {
 
     await t.test('should bind the subscribe callback', function (t, end) {
       const { agent, shim, wrapped } = t.nr
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
+        const { trace } = tx
         const parent = wrapped('my.queue', null, function subCb() {
           const segment = shim.getSegment()
           assert.equal(segment.name, 'Callback: subCb')
-          compareSegments(parent, [segment])
+          compareSegments({ parent, segments: [segment], trace })
           end()
         })
         assert.ok(parent)
diff --git a/test/unit/shim/shim.test.js b/test/unit/shim/shim.test.js
index aab0645c28..3563a9d4fe 100644
--- a/test/unit/shim/shim.test.js
+++ b/test/unit/shim/shim.test.js
@@ -1117,7 +1117,9 @@ test('Shim', async function (t) {
 
       stream.on('foobar', function () {
         const emitSegment = shim.getSegment()
-        assert.equal(emitSegment.parent, stream.segment)
+        const tx = agent.tracer.getTransaction()
+        const children = tx.trace.getChildren(stream.segment.id)
+        assert.ok(children.includes(emitSegment))
         end()
       })
 
@@ -1134,28 +1136,32 @@ test('Shim', async function (t) {
         return new RecorderSpec({ name: 'test segment', stream: 'foobar' })
       })
 
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const ret = wrapped()
         assert.equal(ret, stream)
         // Emit the event and check the segment name.
-        assert.equal(stream.segment.children.length, 0)
+        let children = tx.trace.getChildren(stream.segment.id)
+        assert.equal(children.length, 0)
         stream.emit('foobar')
-        assert.equal(stream.segment.children.length, 1)
+        children = tx.trace.getChildren(stream.segment.id)
+        assert.equal(children.length, 1)
 
-        const [eventSegment] = stream.segment.children
+        const [eventSegment] = children
         assert.match(eventSegment.name, /Event callback: foobar/)
         assert.equal(eventSegment.getAttributes().count, 1)
 
         // Emit it again and see if the name updated.
         stream.emit('foobar')
-        assert.equal(stream.segment.children.length, 1)
-        assert.equal(stream.segment.children[0], eventSegment)
+        children = tx.trace.getChildren(stream.segment.id)
+        assert.equal(children.length, 1)
+        assert.equal(children[0], eventSegment)
         assert.equal(eventSegment.getAttributes().count, 2)
 
         // Emit it once more and see if the name updated again.
         stream.emit('foobar')
-        assert.equal(stream.segment.children.length, 1)
-        assert.equal(stream.segment.children[0], eventSegment)
+        children = tx.trace.getChildren(stream.segment.id)
+        assert.equal(children.length, 1)
+        assert.equal(children[0], eventSegment)
         assert.equal(eventSegment.getAttributes().count, 3)
       })
     })
@@ -1567,12 +1573,14 @@ test('Shim', async function (t) {
         })
       })
 
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const parentSegment = shim.getSegment()
         const resultingSegment = wrapped(cb)
 
         assert.notEqual(resultingSegment, parentSegment)
-        assert.ok(parentSegment.children.includes(resultingSegment))
+
+        const children = tx.trace.getChildren(parentSegment.id)
+        assert.ok(children.includes(resultingSegment))
         end()
       })
     })
@@ -1593,12 +1601,13 @@ test('Shim', async function (t) {
         })
       })
 
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const parentSegment = shim.getSegment()
         const resultingSegment = wrapped()
 
         assert.equal(resultingSegment, parentSegment)
-        assert.ok(!parentSegment.children.includes(resultingSegment))
+        const children = tx.trace.getChildren(parentSegment.id)
+        assert.ok(!children.includes(resultingSegment))
         end()
       })
     })
@@ -2105,7 +2114,7 @@ test('Shim', async function (t) {
 
     await t.test('should create a new segment', function (t, end) {
       const { agent, shim, wrappable } = t.nr
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const args = [wrappable.getActiveSegment]
         const segment = wrappable.getActiveSegment()
         const parent = shim.createSegment({ name: 'test segment', parent: segment })
@@ -2114,7 +2123,7 @@ test('Shim', async function (t) {
 
         assert.notEqual(cbSegment, segment)
         assert.notEqual(cbSegment, parent)
-        compareSegments(parent, [cbSegment])
+        compareSegments({ parent, segments: [cbSegment], trace: tx.trace })
         end()
       })
     })
@@ -2129,7 +2138,7 @@ test('Shim', async function (t) {
         const cbSegment = args[0]()
 
         assert.notEqual(cbSegment, parent)
-        compareSegments(parent, [cbSegment])
+        compareSegments({ parent, segments: [cbSegment], trace: tx.trace })
         assert.equal(parent.opaque, false)
         end()
       })
@@ -2137,14 +2146,14 @@ test('Shim', async function (t) {
 
     await t.test('should default the `parentSegment` to the current one', function (t, end) {
       const { agent, shim, wrappable } = t.nr
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const args = [wrappable.getActiveSegment]
         const segment = wrappable.getActiveSegment()
         shim.bindCallbackSegment({}, args, shim.LAST)
         const cbSegment = args[0]()
 
         assert.notEqual(cbSegment, segment)
-        compareSegments(segment, [cbSegment])
+        compareSegments({ parent: segment, segments: [cbSegment], trace: tx.trace })
         end()
       })
     })
@@ -2157,14 +2166,14 @@ test('Shim', async function (t) {
           executed = true
         }
       }
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const args = [wrappable.getActiveSegment]
         const segment = wrappable.getActiveSegment()
         shim.bindCallbackSegment(spec, args, shim.LAST)
         const cbSegment = args[0]()
 
         assert.notEqual(cbSegment, segment)
-        compareSegments(segment, [cbSegment])
+        compareSegments({ parent: segment, segments: [cbSegment], trace: tx.trace })
         assert.equal(executed, true)
         end()
       })
@@ -2332,7 +2341,7 @@ test('Shim', async function (t) {
         const parent = shim.createSegment({ name: 'parent', parent: tx.trace.root })
         const child = shim.createSegment('child', parent)
         assert.equal(child.name, 'child')
-        compareSegments(parent, [child])
+        compareSegments({ parent, segments: [child], trace: tx.trace })
         end()
       })
     })
@@ -2343,7 +2352,7 @@ test('Shim', async function (t) {
         const parent = shim.createSegment('parent', tx.trace.root)
         const child = shim.createSegment('child', null, parent)
         assert.equal(child.name, 'child')
-        compareSegments(parent, [child])
+        compareSegments({ parent, segments: [child], trace: tx.trace })
         end()
       })
     })
@@ -2355,7 +2364,8 @@ test('Shim', async function (t) {
         parent.opaque = true
         const child = shim.createSegment('child', parent)
         assert.equal(child.name, 'parent')
-        assert.deepEqual(parent.children, [])
+        const children = tx.trace.getChildren(parent.id)
+        assert.deepEqual(children, [])
         end()
       })
     })
@@ -2378,10 +2388,10 @@ test('Shim', async function (t) {
 
     await t.test('should default to the current segment as the parent', function (t, end) {
       const { agent, shim } = t.nr
-      helper.runInTransaction(agent, function () {
+      helper.runInTransaction(agent, function (tx) {
         const parent = shim.getSegment()
         const child = shim.createSegment('child', parent)
-        compareSegments(parent, [child])
+        compareSegments({ parent, segments: [child], trace: tx.trace })
         end()
       })
     })
@@ -2408,7 +2418,7 @@ test('Shim', async function (t) {
         const parent = shim.createSegment('parent', tx.trace.root)
         const child = shim.createSegment({ name: 'child', parent })
         assert.equal(child.name, 'child')
-        compareSegments(parent, [child])
+        compareSegments({ parent, segments: [child], trace: tx.trace })
         end()
       })
     })
diff --git a/test/unit/spans/span-event.test.js b/test/unit/spans/span-event.test.js
index 33fd151c4d..be81e50fe8 100644
--- a/test/unit/spans/span-event.test.js
+++ b/test/unit/spans/span-event.test.js
@@ -62,7 +62,8 @@ test('fromSegment()', async (t) => {
       transaction.priority = 42
 
       setTimeout(() => {
-        const segment = agent.tracer.getTransaction().trace.root.children[0]
+        const tx = agent.tracer.getTransaction()
+        const [segment] = tx.trace.getChildren(tx.trace.root.id)
         segment.addSpanAttribute('SpiderSpan', 'web')
         segment.addSpanAttribute('host', 'my-host')
         segment.addSpanAttribute('port', 222)
@@ -135,7 +136,8 @@ test('fromSegment()', async (t) => {
       https.get('https://example.com?foo=bar', (res) => {
         res.resume()
         res.on('end', () => {
-          const segment = agent.tracer.getTransaction().trace.root.children[0]
+          const tx = agent.tracer.getTransaction()
+          const [segment] = tx.trace.getChildren(tx.trace.root.id)
           const span = SpanEvent.fromSegment(segment, transaction, 'parent')
 
           // Should have all the normal properties.
@@ -237,7 +239,7 @@ test('fromSegment()', async (t) => {
 
       dsConn.myDbOp(longQuery, () => {
         transaction.end()
-        const segment = transaction.trace.root.children[0]
+        const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
         const span = SpanEvent.fromSegment(segment, transaction, 'parent')
 
         // Should have all the normal properties.
@@ -357,7 +359,7 @@ test('fromSegment()', async (t) => {
 
         res.resume()
         res.on('end', () => {
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           assert.ok(segment.name.startsWith('Truncated'))
 
           const span = SpanEvent.fromSegment(segment, transaction)
diff --git a/test/unit/spans/streaming-span-event.test.js b/test/unit/spans/streaming-span-event.test.js
index 3e92aaa3cc..a92380ce71 100644
--- a/test/unit/spans/streaming-span-event.test.js
+++ b/test/unit/spans/streaming-span-event.test.js
@@ -60,7 +60,8 @@ test('fromSegment()', async (t) => {
       transaction.priority = 42
 
       setTimeout(() => {
-        const segment = agent.tracer.getTransaction().trace.root.children[0]
+        const tx = agent.tracer.getTransaction()
+        const [segment] = tx.trace.getChildren(tx.trace.root.id)
         const spanContext = segment.getSpanContext()
         spanContext.addCustomAttribute('Span Lee', 'no prize')
         segment.addSpanAttribute('host', 'my-host')
@@ -130,7 +131,8 @@ test('fromSegment()', async (t) => {
       https.get('https://example.com?foo=bar', (res) => {
         res.resume()
         res.on('end', () => {
-          const segment = agent.tracer.getTransaction().trace.root.children[0]
+          const tx = agent.tracer.getTransaction()
+          const [segment] = tx.trace.getChildren(tx.trace.root.id)
           const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent')
 
           // Should have all the normal properties.
@@ -237,7 +239,7 @@ test('fromSegment()', async (t) => {
 
       dsConn.myDbOp(longQuery, () => {
         transaction.end()
-        const segment = transaction.trace.root.children[0]
+        const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
         const span = StreamingSpanEvent.fromSegment(segment, transaction, 'parent')
 
         // Should have all the normal properties.
@@ -387,7 +389,7 @@ test('fromSegment()', async (t) => {
 
         res.resume()
         res.on('end', () => {
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           assert.ok(segment.name.startsWith('Truncated'))
 
           const span = StreamingSpanEvent.fromSegment(segment, transaction)
diff --git a/test/unit/transaction/trace/index.test.js b/test/unit/transaction/trace/index.test.js
index 46316f3236..ed503c5d30 100644
--- a/test/unit/transaction/trace/index.test.js
+++ b/test/unit/transaction/trace/index.test.js
@@ -119,7 +119,6 @@ test('Trace', async (t) => {
     child2.end()
     trace.root.end()
     transaction.end()
-    trace.generateSpanEvents()
 
     const events = agent.spanEventAggregator.getEvents()
     const nested = events[0]
@@ -232,7 +231,7 @@ test('Trace', async (t) => {
     transaction.acceptDistributedTraceHeaders('HTTP', headers)
 
     // Create at least one segment
-    const trace = new Trace(transaction)
+    const trace = transaction.trace
     const child = (transaction.baseSegment = trace.add('test'))
 
     child.start()
@@ -272,7 +271,7 @@ test('Trace', async (t) => {
     const transaction = new Transaction(agent)
     transaction.sampled = true
 
-    const trace = new Trace(transaction)
+    const trace = transaction.trace
 
     // add a child segment
     const child = (transaction.baseSegment = trace.add('test'))
@@ -311,7 +310,9 @@ test('when serializing synchronously', async (t) => {
 
   await t.test('should produce a transaction trace in the expected format', async (t) => {
     const { details } = t.nr
+    assert.equal(details.trace.segments.length, 3)
     const traceJSON = details.trace.generateJSONSync()
+    assert.equal(details.trace.segments.length, 0)
     const reconstituted = await codecDecodeAsync(traceJSON[4])
     assert.deepEqual(traceJSON, details.expectedEncoding, 'full trace JSON')
 
@@ -382,7 +383,9 @@ test('when serializing asynchronously', async (t) => {
 
   await t.test('should produce a transaction trace in the expected format', async (t) => {
     const { details } = t.nr
+    assert.equal(details.trace.segments.length, 3)
     const traceJSON = await details.trace.generateJSONAsync()
+    assert.equal(details.trace.segments.length, 0)
     const reconstituted = await codecDecodeAsync(traceJSON[4])
 
     assert.deepEqual(traceJSON, details.expectedEncoding, 'full trace JSON')
@@ -478,244 +481,158 @@ test('when inserting segments', async (t) => {
   })
 
   await t.test('should report total time', (t) => {
-    const { agent, trace } = t.nr
-    const root = trace.root
+    const { trace } = t.nr
     trace.setDurationInMillis(40, 0)
     const child = trace.add('Custom/Test18/Child1')
 
     child.setDurationInMillis(27, 0)
-    let seg = child.add({
-      config: agent.config,
-      name: 'UnitTest',
-      collect: true,
-      root
-    })
+    let seg = trace.add('UnitTest', null, child)
     seg.setDurationInMillis(9, 1)
-    seg = child.add({
-      config: agent.config,
-      name: 'UnitTest1',
-      collect: true,
-      root
-    })
+    seg = trace.add('UnitTest1', null, child)
     seg.setDurationInMillis(13, 1)
-    seg = child.add({
-      config: agent.config,
-      name: 'UnitTest2',
-      collect: true,
-      root
-    })
+    seg = trace.add('UnitTest2', null, child)
     seg.setDurationInMillis(9, 16)
-    seg = child.add({
-      config: agent.config,
-      name: 'UnitTest2',
-      collect: true,
-      root
-    })
+    seg = trace.add('UnitTest2', null, child)
     seg.setDurationInMillis(14, 16)
     assert.equal(trace.getTotalTimeDurationInMillis(), 48)
   })
 
   await t.test('should report total time on branched traces', (t) => {
-    const { trace, agent } = t.nr
-    const root = trace.root
+    const { trace } = t.nr
     trace.setDurationInMillis(40, 0)
-    const child = trace.add('Custom/Test18/Child1')
+    const child = trace.add('Custom/Test18/Child1', null, trace.root)
     child.setDurationInMillis(27, 0)
-    const seg1 = child.add({
-      config: agent.config,
-      name: 'UnitTest',
-      collect: true,
-      root
-    })
+    const seg1 = trace.add('UnitTest', null, child)
     seg1.setDurationInMillis(9, 1)
-    let seg = child.add({
-      config: agent.config,
-      name: 'UnitTest1',
-      collect: true,
-      root
-    })
+    let seg = trace.add('UnitTest1', null, child)
     seg.setDurationInMillis(13, 1)
-    seg = seg1.add({
-      config: agent.config,
-      name: 'UnitTest2',
-      collect: true,
-      root
-    })
+    seg = trace.add('UnitTest2', null, seg1)
     seg.setDurationInMillis(9, 16)
-    seg = seg1.add({
-      config: agent.config,
-      name: 'UnitTest2',
-      collect: true,
-      root
-    })
+    seg = trace.add('UnitTest2', null, seg1)
     seg.setDurationInMillis(14, 16)
     assert.equal(trace.getTotalTimeDurationInMillis(), 48)
   })
 
   await t.test('should report the expected trees for trees with uncollected segments', (t) => {
-    const { agent, trace } = t.nr
-    const root = trace.root
+    const { trace } = t.nr
     const expectedTrace = [
       0,
-      27,
-      'Root',
-      { nr_exclusive_duration_millis: 3 },
+      40,
+      'ROOT',
+      { nr_exclusive_duration_millis: 10 },
       [
         [
-          1,
-          10,
-          'first',
-          { nr_exclusive_duration_millis: 9 },
-          [[16, 25, 'first-first', { nr_exclusive_duration_millis: 9 }, []]]
-        ],
-        [
-          1,
-          14,
-          'second',
-          { nr_exclusive_duration_millis: 13 },
+          0,
+          27,
+          'Root',
+          { nr_exclusive_duration_millis: 3 },
           [
-            [16, 25, 'second-first', { nr_exclusive_duration_millis: 9 }, []],
-            [16, 25, 'second-second', { nr_exclusive_duration_millis: 9 }, []]
+            [
+              1,
+              10,
+              'first',
+              { nr_exclusive_duration_millis: 9 },
+              [[16, 25, 'first-first', { nr_exclusive_duration_millis: 9 }, []]]
+            ],
+            [
+              1,
+              14,
+              'second',
+              { nr_exclusive_duration_millis: 13 },
+              [
+                [16, 25, 'second-first', { nr_exclusive_duration_millis: 9 }, []],
+                [16, 25, 'second-second', { nr_exclusive_duration_millis: 9 }, []]
+              ]
+            ]
           ]
         ]
       ]
     ]
 
     trace.setDurationInMillis(40, 0)
-    const child = trace.add('Root')
+    const child = trace.add('Root', null, trace.root)
 
     child.setDurationInMillis(27, 0)
-    const seg1 = child.add({
-      config: agent.config,
-      name: 'first',
-      collect: true,
-      root
-    })
+    const seg1 = trace.add('first', null, child)
 
     seg1.setDurationInMillis(9, 1)
-    const seg2 = child.add({
-      config: agent.config,
-      name: 'second',
-      collect: true,
-      root
-    })
+    const seg2 = trace.add('second', null, child)
     seg2.setDurationInMillis(13, 1)
-    let seg = seg1.add({
-      config: agent.config,
-      name: 'first-first',
-      collect: true,
-      root
-    })
+    let seg = trace.add('first-first', null, seg1)
     seg.setDurationInMillis(9, 16)
-    seg = seg1.add({
-      config: agent.config,
-      name: 'first-second',
-      collect: true,
-      root
-    })
+    seg = trace.add('first-second', null, seg1)
     seg.setDurationInMillis(14, 16)
     seg._collect = false
-    seg = seg2.add({
-      config: agent.config,
-      name: 'second-first',
-      collect: true,
-      root
-    })
+    seg = trace.add('second-first', null, seg2)
     seg.setDurationInMillis(9, 16)
-    seg = seg2.add({
-      config: agent.config,
-      name: 'second-second',
-      collect: true,
-      root
-    })
+    seg = trace.add('second-second', null, seg2)
     seg.setDurationInMillis(9, 16)
 
     trace.end()
 
-    assert.deepEqual(child.toJSON(), expectedTrace)
+    assert.deepEqual(trace.toJSON(), expectedTrace)
   })
 
   await t.test('should report the expected trees for branched trees', (t) => {
-    const { agent, trace } = t.nr
+    const { trace } = t.nr
     const expectedTrace = [
       0,
-      27,
-      'Root',
-      { nr_exclusive_duration_millis: 3 },
+      40,
+      'ROOT',
+      { nr_exclusive_duration_millis: 10 },
       [
         [
-          1,
-          10,
-          'first',
-          { nr_exclusive_duration_millis: 9 },
-          [
-            [16, 25, 'first-first', { nr_exclusive_duration_millis: 9 }, []],
-            [16, 30, 'first-second', { nr_exclusive_duration_millis: 14 }, []]
-          ]
-        ],
-        [
-          1,
-          14,
-          'second',
-          { nr_exclusive_duration_millis: 13 },
+          0,
+          27,
+          'Root',
+          { nr_exclusive_duration_millis: 3 },
           [
-            [16, 25, 'second-first', { nr_exclusive_duration_millis: 9 }, []],
-            [16, 25, 'second-second', { nr_exclusive_duration_millis: 9 }, []]
+            [
+              1,
+              10,
+              'first',
+              { nr_exclusive_duration_millis: 9 },
+              [
+                [16, 25, 'first-first', { nr_exclusive_duration_millis: 9 }, []],
+                [16, 30, 'first-second', { nr_exclusive_duration_millis: 14 }, []]
+              ]
+            ],
+            [
+              1,
+              14,
+              'second',
+              { nr_exclusive_duration_millis: 13 },
+              [
+                [16, 25, 'second-first', { nr_exclusive_duration_millis: 9 }, []],
+                [16, 25, 'second-second', { nr_exclusive_duration_millis: 9 }, []]
+              ]
+            ]
           ]
         ]
       ]
     ]
+
     trace.setDurationInMillis(40, 0)
-    const child = trace.add('Root')
-    const root = trace.root
+    const child = trace.add('Root', null, trace.root)
 
     child.setDurationInMillis(27, 0)
-    const seg1 = child.add({
-      config: agent.config,
-      name: 'first',
-      collect: true,
-      root
-    })
+    const seg1 = trace.add('first', null, child)
+
     seg1.setDurationInMillis(9, 1)
-    const seg2 = child.add({
-      config: agent.config,
-      name: 'second',
-      collect: true,
-      root
-    })
+    const seg2 = trace.add('second', null, child)
     seg2.setDurationInMillis(13, 1)
-    let seg = seg1.add({
-      config: agent.config,
-      name: 'first-first',
-      collect: true,
-      root
-    })
+    let seg = trace.add('first-first', null, seg1)
     seg.setDurationInMillis(9, 16)
-    seg = seg1.add({
-      config: agent.config,
-      name: 'first-second',
-      collect: true,
-      root
-    })
+    seg = trace.add('first-second', null, seg1)
     seg.setDurationInMillis(14, 16)
-    seg = seg2.add({
-      config: agent.config,
-      name: 'second-first',
-      collect: true,
-      root
-    })
+    seg = trace.add('second-first', null, seg2)
     seg.setDurationInMillis(9, 16)
-    seg = seg2.add({
-      config: agent.config,
-      name: 'second-second',
-      collect: true,
-      root
-    })
+    seg = trace.add('second-second', null, seg2)
     seg.setDurationInMillis(9, 16)
 
     trace.end()
 
-    assert.deepEqual(child.toJSON(), expectedTrace)
+    assert.deepEqual(trace.toJSON(), expectedTrace)
   })
 
   await t.test('should measure exclusive time vs total time at each level of the graph', (t) => {
@@ -755,11 +672,10 @@ test('when inserting segments', async (t) => {
   })
 
   await t.test('should accurately sum overlapping subtrees', (t) => {
-    const { agent, trace } = t.nr
+    const { trace } = t.nr
     trace.setDurationInMillis(42)
 
     const now = Date.now()
-    const root = trace.root
 
     // create a long child on its own
     const child1 = trace.add('Custom/Test20/Child1')
@@ -767,39 +683,21 @@ test('when inserting segments', async (t) => {
     child1.setDurationInMillis(33, now)
 
     // add another, short child as a sibling
-    const child2 = child1.add({
-      config: agent.config,
-      name: 'Custom/Test20/Child2',
-      collect: true,
-      root
-    })
-
+    const child2 = trace.add('Custom/Test20/Child2', null, child1)
     child2.setDurationInMillis(5, now)
 
     // add two disjoint children of the second segment encompassed by the first segment
-    const child3 = child2.add({
-      config: agent.config,
-      name: 'Custom/Test20/Child3',
-      collect: true,
-      root
-    })
-
+    const child3 = trace.add('Custom/Test20/Child3', null, child2)
     child3.setDurationInMillis(11, now)
 
-    const child4 = child2.add({
-      config: agent.config,
-      name: 'Custom/Test20/Child3',
-      collect: true,
-      root
-    })
-
+    const child4 = trace.add('Custom/Test20/Child3', null, child2)
     child4.setDurationInMillis(11, now + 16)
 
     assert.equal(trace.getExclusiveDurationInMillis(), 9)
-    assert.equal(child4.getExclusiveDurationInMillis(), 11)
-    assert.equal(child3.getExclusiveDurationInMillis(), 11)
-    assert.equal(child2.getExclusiveDurationInMillis(), 0)
-    assert.equal(child1.getExclusiveDurationInMillis(), 11)
+    assert.equal(child4.getExclusiveDurationInMillis(trace), 11)
+    assert.equal(child3.getExclusiveDurationInMillis(trace), 11)
+    assert.equal(child2.getExclusiveDurationInMillis(trace), 0)
+    assert.equal(child1.getExclusiveDurationInMillis(trace), 11)
   })
 
   await t.test('should accurately sum partially overlapping segments', (t) => {
@@ -852,14 +750,22 @@ test('when inserting segments', async (t) => {
       }
     }
 
-    assert.equal(trace.root.children.length, 950)
+    assert.equal(trace.segments.length, 950)
     assert.equal(transaction._recorders.length, 950)
-    trace.segmentCount = 0
-    trace.root.children = []
-    trace.recorders = []
-
+    trace.end()
     function noop() {}
   })
+
+  await t.test('should not cause a stack overflow', { timeout: 30000 }, (t) => {
+    const { trace } = t.nr
+    for (let i = 0; i < 9000; ++i) {
+      trace.add(`Child ${i}`)
+    }
+
+    assert.doesNotThrow(function () {
+      trace.toJSON()
+    })
+  })
 })
 
 test('should set URI to null when request.uri attribute is excluded globally', async (t) => {
@@ -1031,14 +937,9 @@ test('infinite tracing', async (t) => {
 
 function addTwoSegments(transaction) {
   const trace = transaction.trace
-  const root = trace.root
   const child1 = (transaction.baseSegment = trace.add('test'))
   child1.start()
-  const child2 = child1.add({
-    config: transaction.agent.config,
-    name: 'nested',
-    root
-  })
+  const child2 = trace.add('nested', null, child1)
   child2.start()
   child1.end()
   child2.end()
@@ -1057,10 +958,7 @@ async function makeTrace(agent) {
   transaction.url = URL
   transaction.verb = 'GET'
 
-  transaction.timer.setDurationInMillis(DURATION)
-
   const trace = transaction.trace
-  const root = trace.root
 
   // promisifying `trace.generateJSON` so tests do not have to call done
   // and instead use async/await
@@ -1069,33 +967,19 @@ async function makeTrace(agent) {
   assert.ok(start > 0, "root segment's start time")
   trace.setDurationInMillis(DURATION, 0)
 
-  const web = trace.root.add({
-    config: agent.config,
-    name: URL,
-    collect: true,
-    root
-  })
+  const web = trace.add(URL)
   transaction.baseSegment = web
   transaction.finalizeNameFromUri(URL, 200)
   // top-level element will share a duration with the quasi-ROOT node
   web.setDurationInMillis(DURATION, 0)
 
-  const db = web.add({
-    config: agent.config,
-    name: 'Database/statement/AntiSQL/select/getSome',
-    collect: true,
-    root
-  })
+  const db = trace.add('Database/statement/AntiSQL/select/getSome', null, web)
   db.setDurationInMillis(14, 3)
 
-  const memcache = web.add({
-    config: agent.config,
-    name: 'Datastore/operation/Memcache/lookup',
-    collect: true,
-    root
-  })
+  const memcache = trace.add('Datastore/operation/Memcache/lookup', null, web)
   memcache.setDurationInMillis(20, 8)
 
+  transaction.timer.setDurationInMillis(DURATION)
   trace.end()
 
   /*
@@ -1103,6 +987,21 @@ async function makeTrace(agent) {
    * outermost version having its scope always set to 'ROOT'. The null bits
    * are parameters, which are optional, and so far, unimplemented for Node.
    */
+  const dbSegment = [
+    3,
+    17,
+    'Database/statement/AntiSQL/select/getSome',
+    { nr_exclusive_duration_millis: 14 },
+    []
+  ]
+  const memcacheSegment = [
+    8,
+    28,
+    'Datastore/operation/Memcache/lookup',
+    { nr_exclusive_duration_millis: 20 },
+    []
+  ]
+
   const rootSegment = [
     0,
     DURATION,
@@ -1118,15 +1017,10 @@ async function makeTrace(agent) {
           'request.parameters.test': 'value',
           'nr_exclusive_duration_millis': 8
         },
-        [
-          // TODO: ensure that the ordering is correct WRT start time
-          db.toJSON(),
-          memcache.toJSON()
-        ]
+        [dbSegment, memcacheSegment]
       ]
     ]
   ]
-
   const rootNode = [
     trace.root.timer.start / 1000,
     {},
@@ -1147,7 +1041,6 @@ async function makeTrace(agent) {
   return {
     transaction,
     trace,
-    rootSegment,
     rootNode,
     expectedEncoding: [
       0,
diff --git a/test/unit/transaction/trace/segment.test.js b/test/unit/transaction/trace/segment.test.js
index c8bcf63e6c..1522734fe0 100644
--- a/test/unit/transaction/trace/segment.test.js
+++ b/test/unit/transaction/trace/segment.test.js
@@ -27,36 +27,6 @@ test('TraceSegment', async (t) => {
   t.beforeEach(beforeEach)
   t.afterEach(afterEach)
 
-  await t.test('should not add new children when marked as opaque', (t) => {
-    const { agent } = t.nr
-    const trans = new Transaction(agent)
-    const root = trans.trace.root
-    const segment = new TraceSegment({
-      config: agent.config,
-      name: 'UnitTest',
-      collect: true,
-      root
-    })
-    assert.ok(!segment.opaque)
-    segment.opaque = true
-    segment.add({
-      config: agent.config,
-      name: 'child',
-      collect: true,
-      root
-    })
-    assert.equal(segment.children.length, 0)
-    segment.opaque = false
-    segment.add({
-      config: agent.config,
-      name: 'child',
-      collect: true,
-      root
-    })
-    assert.equal(segment.children.length, 1)
-    trans.end()
-  })
-
   await t.test('has a name', (t) => {
     const { agent } = t.nr
     const trans = new Transaction(agent)
@@ -70,19 +40,6 @@ test('TraceSegment', async (t) => {
     assert.equal(success.name, 'UnitTest')
   })
 
-  await t.test('is created with no children', (t) => {
-    const { agent } = t.nr
-    const trans = new Transaction(agent)
-    const root = trans.trace.root
-    const segment = new TraceSegment({
-      config: agent.config,
-      name: 'UnitTest',
-      collect: true,
-      root
-    })
-    assert.equal(segment.children.length, 0)
-  })
-
   await t.test('has a timer', (t) => {
     const { agent } = t.nr
     const trans = new Transaction(agent)
@@ -202,7 +159,7 @@ test('TraceSegment', async (t) => {
       collect: true,
       root
     })
-    segment.toJSON()
+    transaction.trace.toJSON()
     assert.deepEqual(segment.getAttributes(), {})
   })
 
@@ -231,8 +188,9 @@ test('TraceSegment', async (t) => {
 
     trace.end()
 
-    // See documentation on TraceSegment.toJSON for what goes in which field.
-    assert.deepEqual(segment.toJSON(), [
+    // get serialized segment from trace
+    const serializedSegment = trace.toJSON()[4][0]
+    assert.deepEqual(serializedSegment, [
       3,
       17,
       'DB/select/getSome',
@@ -283,7 +241,7 @@ test('TraceSegment', async (t) => {
     segment.timer.start = 1001
     segment.overwriteDurationInMillis(3)
 
-    segment.finalize()
+    segment.finalize(transaction.trace)
 
     assert.equal(segment.name, `Truncated/${segmentName}`)
     assert.equal(root.getDurationInMillis(), 4)
@@ -299,17 +257,9 @@ test('with children created from URLs', async (t) => {
 
     const transaction = new Transaction(ctx.nr.agent)
     const trace = transaction.trace
-    const root = transaction.trace.root
-    const segment = trace.add('UnitTest')
-
     const url = '/test?test1=value1&test2&test3=50&test4='
 
-    const webChild = segment.add({
-      config: ctx.nr.agent,
-      name: url,
-      collect: true,
-      root
-    })
+    const webChild = trace.add(url)
     transaction.baseSegment = webChild
     transaction.finalizeNameFromUri(url, 200)
 
@@ -318,6 +268,7 @@ test('with children created from URLs', async (t) => {
 
     trace.end()
     ctx.nr.webChild = webChild
+    ctx.nr.trace = trace
   })
 
   t.afterEach(afterEach)
@@ -350,8 +301,10 @@ test('with children created from URLs', async (t) => {
   })
 
   await t.test('should serialize the segment with the parameters', (t) => {
-    const { webChild } = t.nr
-    assert.deepEqual(webChild.toJSON(), [
+    const { trace } = t.nr
+    // get serialized segment from trace
+    const serializedSegment = trace.toJSON()[4][0]
+    assert.deepEqual(serializedSegment, [
       0,
       1,
       'WebTransaction/NormalizedUri/*',
@@ -374,11 +327,8 @@ test('with parameters parsed out by framework', async (t) => {
 
     const transaction = new Transaction(ctx.nr.agent)
     const trace = transaction.trace
-    const root = trace.root
     trace.mer = 6
 
-    const segment = trace.add('UnitTest')
-
     const url = '/test'
     const params = {}
 
@@ -387,12 +337,7 @@ test('with parameters parsed out by framework', async (t) => {
     params[1] = 'another'
     params.test3 = '50'
 
-    const webChild = segment.add({
-      config: ctx.nr.agent.config,
-      name: url,
-      collect: true,
-      root
-    })
+    const webChild = trace.add(url)
     transaction.trace.attributes.addAttributes(DESTINATIONS.TRANS_SCOPE, params)
     transaction.baseSegment = webChild
     transaction.finalizeNameFromUri(url, 200)
@@ -429,7 +374,7 @@ test('with parameters parsed out by framework', async (t) => {
   })
 
   await t.test('should serialize the segment with the parameters', (t) => {
-    const { webChild } = t.nr
+    const { trace } = t.nr
     const expected = [
       0,
       1,
@@ -442,7 +387,9 @@ test('with parameters parsed out by framework', async (t) => {
       },
       []
     ]
-    assert.deepEqual(webChild.toJSON(), expected)
+    // get serialized segment from trace
+    const serializedSegment = trace.toJSON()[4][0]
+    assert.deepEqual(serializedSegment, expected)
   })
 })
 
@@ -453,17 +400,9 @@ test('with attributes.enabled set to false', async (t) => {
 
     const transaction = new Transaction(ctx.nr.agent)
     const trace = transaction.trace
-    const root = trace.root
-    const segment = trace.add('UnitTest')
     const url = '/test?test1=value1&test2&test3=50&test4='
 
-    const webChild = segment.add({
-      config: ctx.nr.agent.config,
-      name: url,
-      collect: true,
-
-      root
-    })
+    const webChild = trace.add(url)
     webChild.addAttribute('test', 'non-null value')
     transaction.baseSegment = webChild
     transaction.finalizeNameFromUri(url, 200)
@@ -471,6 +410,7 @@ test('with attributes.enabled set to false', async (t) => {
     trace.setDurationInMillis(1, 0)
     webChild.setDurationInMillis(1, 0)
     ctx.nr.webChild = webChild
+    ctx.nr.trace = trace
   })
   t.afterEach(afterEach)
 
@@ -485,9 +425,11 @@ test('with attributes.enabled set to false', async (t) => {
   })
 
   await t.test('should serialize the segment without the parameters', (t) => {
-    const { webChild } = t.nr
+    const { trace } = t.nr
     const expected = [0, 1, 'WebTransaction/NormalizedUri/*', {}, []]
-    assert.deepEqual(webChild.toJSON(), expected)
+    // get serialized segment from trace
+    const serializedSegment = trace.toJSON()[4][0]
+    assert.deepEqual(serializedSegment, expected)
   })
 })
 
@@ -504,18 +446,9 @@ test('with attributes.enabled set', async (t) => {
 
     const transaction = new Transaction(ctx.nr.agent)
     const trace = transaction.trace
-    const root = trace.root
-    const segment = trace.add('UnitTest')
-
     const url = '/test?test1=value1&test2&test3=50&test4='
 
-    const webChild = segment.add({
-      config: ctx.nr.agent.config,
-      name: url,
-      collect: true,
-
-      root
-    })
+    const webChild = trace.add(url)
     transaction.baseSegment = webChild
     transaction.finalizeNameFromUri(url, 200)
     webChild.markAsWeb(transaction)
@@ -526,6 +459,7 @@ test('with attributes.enabled set', async (t) => {
     ctx.nr.webChild = webChild
 
     trace.end()
+    ctx.nr.trace = trace
   })
   t.afterEach(afterEach)
 
@@ -558,8 +492,10 @@ test('with attributes.enabled set', async (t) => {
   })
 
   await t.test('should serialize the segment with the parameters', (t) => {
-    const { webChild } = t.nr
-    assert.deepEqual(webChild.toJSON(), [
+    const { trace } = t.nr
+    // get the specific segment from serialized trace
+    const serializedSegment = trace.toJSON()[4][0]
+    assert.deepEqual(serializedSegment, [
       0,
       1,
       'WebTransaction/NormalizedUri/*',
@@ -578,12 +514,7 @@ test('when serialized', async (t) => {
     const agent = helper.loadMockedAgent()
     const transaction = new Transaction(agent)
     const root = transaction.trace.root
-    const segment = new TraceSegment({
-      config: agent.config,
-      name: 'UnitTest',
-      collect: true,
-      root
-    })
+    const segment = transaction.trace.add('UnitTest')
 
     ctx.nr = {
       agent: agent,
@@ -600,9 +531,9 @@ test('when serialized', async (t) => {
   })
 
   await t.test('should create a plain JS array', (t) => {
-    const { segment } = t.nr
+    const { segment, transaction } = t.nr
     segment.end()
-    const js = segment.toJSON()
+    const js = transaction.trace.toJSON()[4][0]
 
     assert.ok(Array.isArray(js))
     assert.equal(typeof js[0], 'number')
@@ -615,25 +546,6 @@ test('when serialized', async (t) => {
     assert.ok(Array.isArray(js[4]))
     assert.equal(js[4].length, 0)
   })
-
-  await t.test('should not cause a stack overflow', { timeout: 30000 }, (t) => {
-    const { segment, agent, root } = t.nr
-    let parent = segment
-    for (let i = 0; i < 9000; ++i) {
-      const child = new TraceSegment({
-        config: agent.config,
-        name: 'Child ' + i,
-        collect: true,
-        root
-      })
-      parent.children.push(child)
-      parent = child
-    }
-
-    assert.doesNotThrow(function () {
-      segment.toJSON()
-    })
-  })
 })
 
 test('getSpanContext', async (t) => {
diff --git a/test/unit/transaction/tracer.test.js b/test/unit/transaction/tracer.test.js
index a2ecbd101f..fb8298eba4 100644
--- a/test/unit/transaction/tracer.test.js
+++ b/test/unit/transaction/tracer.test.js
@@ -209,11 +209,13 @@ test('Tracer', async function (t) {
       assert.equal(agent.segmentsCreatedInHarvest, 1)
       assert.equal(tx.numSegments, 1)
       assert.equal(agent.activeTransactions, 1)
+      assert.equal(tx.trace.segments.length, 0)
 
       tracer.createSegment({ name: 'Test', parent: tx.trace.root, transaction: tx })
       assert.equal(agent.totalActiveSegments, 2)
       assert.equal(agent.segmentsCreatedInHarvest, 2)
       assert.equal(tx.numSegments, 2)
+      assert.equal(tx.trace.segments.length, 1)
       tx.end()
 
       assert.equal(agent.activeTransactions, 0)
@@ -230,5 +232,18 @@ test('Tracer', async function (t) {
         })
       }, 10)
     })
+    await t.test('skip adding children when parent is opaque', (t) => {
+      const { agent, tracer } = t.nr
+      const tx = new Transaction(agent)
+      tracer.setSegment({ transaction: tx, segment: tx.trace.root })
+      const segment = tracer.createSegment({ name: 'Test', parent: tx.trace.root, transaction: tx })
+      segment.opaque = true
+      const segment2 = tracer.createSegment({ name: 'Test1', parent: segment, transaction: tx })
+      const segment3 = tracer.createSegment({ name: 'Test2', parent: segment, transaction: tx })
+      assert.equal(segment2.id, segment.id)
+      assert.equal(segment3.id, segment.id)
+      assert.equal(tx.trace.segments.length, 1)
+      tx.end()
+    })
   })
 })
diff --git a/test/versioned-external/external-repos.js b/test/versioned-external/external-repos.js
index f2c70b6260..c12f3a1c5a 100644
--- a/test/versioned-external/external-repos.js
+++ b/test/versioned-external/external-repos.js
@@ -15,7 +15,7 @@ const repos = [
   {
     name: 'apollo-server',
     repository: 'https://github.com/newrelic/newrelic-node-apollo-server-plugin.git',
-    branch: 'remove-transaction-from-segment',
+    branch: 'remove-child-segments',
     additionalFiles: [
       'tests/agent-testing.js',
       'tests/create-apollo-server-setup.js',
diff --git a/test/versioned/amqplib/amqp-utils.js b/test/versioned/amqplib/amqp-utils.js
index c0ac47fcce..19a363753f 100644
--- a/test/versioned/amqplib/amqp-utils.js
+++ b/test/versioned/amqplib/amqp-utils.js
@@ -27,7 +27,7 @@ exports.verifyTransaction = verifyTransaction
 exports.getChannel = getChannel
 
 function verifySubscribe(tx, exchange, routingKey) {
-  const isCallback = !!metrics.findSegment(tx.trace.root, 'Callback: <anonymous>')
+  const isCallback = !!metrics.findSegment(tx.trace, tx.trace.root, 'Callback: <anonymous>')
 
   let segments = []
 
@@ -39,7 +39,7 @@ function verifySubscribe(tx, exchange, routingKey) {
     segments = ['MessageBroker/RabbitMQ/Exchange/Produce/Named/' + exchange]
   }
 
-  assertSegments(tx.trace.root, segments)
+  assertSegments(tx.trace, tx.trace.root, segments)
 
   assertMetrics(
     tx.metrics,
@@ -51,6 +51,7 @@ function verifySubscribe(tx, exchange, routingKey) {
   assert.equal(tx.getFullName(), null, 'should not set transaction name')
 
   const consume = metrics.findSegment(
+    tx.trace,
     tx.trace.root,
     'MessageBroker/RabbitMQ/Exchange/Produce/Named/' + exchange
   )
@@ -91,7 +92,7 @@ function verifyDistributedTrace(produceTransaction, consumeTransaction) {
     consumeTransaction.traceId,
     'should have proper trace id'
   )
-  const produceSegment = produceTransaction.trace.root.children[0]
+  const [produceSegment] = produceTransaction.trace.getChildren(produceTransaction.trace.root.id)
   assert.equal(
     produceSegment.id,
     consumeTransaction.parentSpanId,
@@ -121,6 +122,7 @@ function verifyConsumeTransaction(tx, exchange, queue, routingKey) {
   )
 
   const consume = metrics.findSegment(
+    tx.trace,
     tx.trace.root,
     'OtherTransaction/Message/RabbitMQ/Exchange/Named/' + exchange
   )
@@ -143,7 +145,7 @@ function verifyConsumeTransaction(tx, exchange, queue, routingKey) {
 }
 
 function verifySendToQueue(tx) {
-  assertSegments(tx.trace.root, ['MessageBroker/RabbitMQ/Exchange/Produce/Named/Default'])
+  assertSegments(tx.trace, tx.trace.root, ['MessageBroker/RabbitMQ/Exchange/Produce/Named/Default'])
 
   assertMetrics(
     tx.metrics,
@@ -153,6 +155,7 @@ function verifySendToQueue(tx) {
   )
 
   const segment = metrics.findSegment(
+    tx.trace,
     tx.trace.root,
     'MessageBroker/RabbitMQ/Exchange/Produce/Named/Default'
   )
@@ -165,7 +168,7 @@ function verifySendToQueue(tx) {
 }
 
 function verifyProduce(tx, exchangeName, routingKey) {
-  const isCallback = !!metrics.findSegment(tx.trace.root, 'Callback: <anonymous>')
+  const isCallback = !!metrics.findSegment(tx.trace, tx.trace.root, 'Callback: <anonymous>')
   let segments = []
 
   if (isCallback) {
@@ -197,7 +200,7 @@ function verifyProduce(tx, exchangeName, routingKey) {
     ]
   }
 
-  assertSegments(tx.trace.root, segments, 'should have expected segments')
+  assertSegments(tx.trace, tx.trace.root, segments, 'should have expected segments')
 
   assertMetrics(
     tx.metrics,
@@ -207,6 +210,7 @@ function verifyProduce(tx, exchangeName, routingKey) {
   )
 
   const segment = metrics.findSegment(
+    tx.trace,
     tx.trace.root,
     'MessageBroker/RabbitMQ/Exchange/Produce/Named/' + exchangeName
   )
@@ -222,17 +226,17 @@ function verifyProduce(tx, exchangeName, routingKey) {
 }
 
 function verifyGet({ tx, exchangeName, routingKey, queue, assertAttr }) {
-  const isCallback = !!metrics.findSegment(tx.trace.root, 'Callback: <anonymous>')
+  const isCallback = !!metrics.findSegment(tx.trace, tx.trace.root, 'Callback: <anonymous>')
   const produceName = 'MessageBroker/RabbitMQ/Exchange/Produce/Named/' + exchangeName
   const consumeName = 'MessageBroker/RabbitMQ/Exchange/Consume/Named/' + queue
   if (isCallback) {
-    assertSegments(tx.trace.root, [produceName, consumeName, ['Callback: <anonymous>']])
+    assertSegments(tx.trace, tx.trace.root, [produceName, consumeName, ['Callback: <anonymous>']])
   } else {
-    assertSegments(tx.trace.root, [produceName, consumeName])
+    assertSegments(tx.trace, tx.trace.root, [produceName, consumeName])
   }
   assertMetrics(tx.metrics, [[{ name: produceName }], [{ name: consumeName }]], false, false)
   if (assertAttr) {
-    const segment = metrics.findSegment(tx.trace.root, consumeName)
+    const segment = metrics.findSegment(tx.trace, tx.trace.root, consumeName)
     const attributes = segment.getAttributes()
     assert.equal(attributes.host, params.rabbitmq_host, 'should have host on segment')
     assert.equal(attributes.port, params.rabbitmq_port, 'should have port on segment')
@@ -241,7 +245,7 @@ function verifyGet({ tx, exchangeName, routingKey, queue, assertAttr }) {
 }
 
 function verifyPurge(tx) {
-  const isCallback = !!metrics.findSegment(tx.trace.root, 'Callback: <anonymous>')
+  const isCallback = !!metrics.findSegment(tx.trace, tx.trace.root, 'Callback: <anonymous>')
   let segments = []
 
   if (isCallback) {
@@ -272,7 +276,7 @@ function verifyPurge(tx) {
       'MessageBroker/RabbitMQ/Queue/Purge/Temp'
     ]
   }
-  assertSegments(tx.trace.root, segments, 'should have expected segments')
+  assertSegments(tx.trace, tx.trace.root, segments, 'should have expected segments')
 
   assertMetrics(tx.metrics, [[{ name: 'MessageBroker/RabbitMQ/Queue/Purge/Temp' }]], false, false)
 }
diff --git a/test/versioned/amqplib/callback.test.js b/test/versioned/amqplib/callback.test.js
index 9d1e7e87f6..f14f635125 100644
--- a/test/versioned/amqplib/callback.test.js
+++ b/test/versioned/amqplib/callback.test.js
@@ -74,7 +74,7 @@ test('amqplib callback instrumentation', async function (t) {
     helper.runInTransaction(agent, function (tx) {
       amqplib.connect(amqpUtils.CON_STRING, null, function (err, _conn) {
         assert.ok(!err, 'should not break connection')
-        const [segment] = tx.trace.root.children
+        const [segment] = tx.trace.getChildren(tx.trace.root.id)
         assert.equal(segment.name, 'amqplib.connect')
         const attrs = segment.getAttributes()
         assert.equal(attrs.host, 'localhost')
diff --git a/test/versioned/amqplib/promises.test.js b/test/versioned/amqplib/promises.test.js
index 7999e04a6d..3ab386cdf7 100644
--- a/test/versioned/amqplib/promises.test.js
+++ b/test/versioned/amqplib/promises.test.js
@@ -65,7 +65,7 @@ test('amqplib promise instrumentation', async function (t) {
     const { agent, amqplib } = t.nr
     await helper.runInTransaction(agent, async function (tx) {
       const _conn = await amqplib.connect(amqpUtils.CON_STRING)
-      const [segment] = tx.trace.root.children
+      const [segment] = tx.trace.getChildren(tx.trace.root.id)
       assert.equal(segment.name, 'amqplib.connect')
       const attrs = segment.getAttributes()
       assert.equal(attrs.host, 'localhost')
diff --git a/test/versioned/aws-sdk-v2/amazon-dax-client.test.js b/test/versioned/aws-sdk-v2/amazon-dax-client.test.js
index 6070538c7f..b5ee8bab88 100644
--- a/test/versioned/aws-sdk-v2/amazon-dax-client.test.js
+++ b/test/versioned/aws-sdk-v2/amazon-dax-client.test.js
@@ -61,11 +61,19 @@ test('amazon-dax-client', async (t) => {
         const root = transaction.trace.root
 
         // Won't have the attributes cause not making web request...
-        const segments = common.getMatchingSegments(root, common.DATASTORE_PATTERN)
+        const segments = common.getMatchingSegments({
+          trace: transaction.trace,
+          segment: root,
+          pattern: common.DATASTORE_PATTERN
+        })
 
         assert.equal(segments.length, 1)
 
-        const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN)
+        const externalSegments = common.checkAWSAttributes({
+          trace: transaction.trace,
+          segment: root,
+          pattern: common.EXTERN_PATTERN
+        })
         assert.equal(externalSegments.length, 0, 'should not have any External segments')
 
         const segment = segments[0]
diff --git a/test/versioned/aws-sdk-v2/dynamodb.test.js b/test/versioned/aws-sdk-v2/dynamodb.test.js
index 1a27862846..658e534b3b 100644
--- a/test/versioned/aws-sdk-v2/dynamodb.test.js
+++ b/test/versioned/aws-sdk-v2/dynamodb.test.js
@@ -92,11 +92,19 @@ test('DynamoDB', async (t) => {
 
 function finish(end, tests, tx) {
   const root = tx.trace.root
-  const segments = common.checkAWSAttributes(root, common.DATASTORE_PATTERN)
+  const segments = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.DATASTORE_PATTERN
+  })
 
   assert.equal(segments.length, tests.length, `should have ${tests.length} aws datastore segments`)
 
-  const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN)
+  const externalSegments = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.EXTERN_PATTERN
+  })
   assert.equal(externalSegments.length, 0, 'should not have any External segments')
 
   segments.forEach((segment, i) => {
diff --git a/test/versioned/aws-sdk-v2/http-services.test.js b/test/versioned/aws-sdk-v2/http-services.test.js
index ecf9e525df..951c9a08c4 100644
--- a/test/versioned/aws-sdk-v2/http-services.test.js
+++ b/test/versioned/aws-sdk-v2/http-services.test.js
@@ -238,7 +238,11 @@ test('AWS HTTP Services', async (t) => {
 })
 
 function finish(end, service, operation, tx) {
-  const externals = common.checkAWSAttributes(tx.trace.root, common.EXTERN_PATTERN)
+  const externals = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: tx.trace.root,
+    pattern: common.EXTERN_PATTERN
+  })
   if (assert.equal(externals.length, 1, 'should have an aws external')) {
     const attrs = externals[0].attributes.get(common.SEGMENT_DESTINATION)
     match(attrs, {
diff --git a/test/versioned/aws-sdk-v2/s3.test.js b/test/versioned/aws-sdk-v2/s3.test.js
index 50a2fe6d84..69a45ae9e5 100644
--- a/test/versioned/aws-sdk-v2/s3.test.js
+++ b/test/versioned/aws-sdk-v2/s3.test.js
@@ -82,7 +82,11 @@ test('S3 buckets', async (t) => {
 })
 
 function finish(end, tx) {
-  const externals = common.checkAWSAttributes(tx.trace.root, common.EXTERN_PATTERN)
+  const externals = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: tx.trace.root,
+    pattern: common.EXTERN_PATTERN
+  })
   assert.equal(externals.length, 3, 'should have 3 aws externals')
   const [head, create, del] = externals
   checkAttrs(head, 'headBucket')
diff --git a/test/versioned/aws-sdk-v2/sns.test.js b/test/versioned/aws-sdk-v2/sns.test.js
index 372cb8e71d..a2629bba87 100644
--- a/test/versioned/aws-sdk-v2/sns.test.js
+++ b/test/versioned/aws-sdk-v2/sns.test.js
@@ -72,10 +72,18 @@ test('SNS', async (t) => {
 function finish(end, tx) {
   const root = tx.trace.root
 
-  const messages = common.checkAWSAttributes(root, common.SNS_PATTERN)
+  const messages = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.SNS_PATTERN
+  })
   assert.equal(messages.length, 1, 'should have 1 message broker segment')
 
-  const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN)
+  const externalSegments = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.EXTERN_PATTERN
+  })
   assert.equal(externalSegments.length, 0, 'should not have any External segments')
 
   const attrs = messages[0].attributes.get(common.SEGMENT_DESTINATION)
diff --git a/test/versioned/aws-sdk-v2/sqs.test.js b/test/versioned/aws-sdk-v2/sqs.test.js
index 4b97825b85..733d8fca88 100644
--- a/test/versioned/aws-sdk-v2/sqs.test.js
+++ b/test/versioned/aws-sdk-v2/sqs.test.js
@@ -154,7 +154,11 @@ function finish({
   const expectedSegmentCount = 3
 
   const root = transaction.trace.root
-  const segments = common.checkAWSAttributes(root, common.SQS_PATTERN)
+  const segments = common.checkAWSAttributes({
+    trace: transaction.trace,
+    segment: root,
+    pattern: common.SQS_PATTERN
+  })
 
   assert.equal(
     segments.length,
@@ -162,7 +166,11 @@ function finish({
     `should have ${expectedSegmentCount} AWS MessageBroker/SQS segments`
   )
 
-  const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN)
+  const externalSegments = common.checkAWSAttributes({
+    trace: transaction.trace,
+    segment: root,
+    pattern: common.EXTERN_PATTERN
+  })
   assert.equal(externalSegments.length, 0, 'should not have any External segments')
 
   const [sendMessage, sendMessageBatch, receiveMessage] = segments
diff --git a/test/versioned/aws-sdk-v3/bedrock-chat-completions.test.js b/test/versioned/aws-sdk-v3/bedrock-chat-completions.test.js
index ffc90e570e..8ab8a67c60 100644
--- a/test/versioned/aws-sdk-v3/bedrock-chat-completions.test.js
+++ b/test/versioned/aws-sdk-v3/bedrock-chat-completions.test.js
@@ -106,6 +106,7 @@ test.afterEach(afterEach)
       assert.equal(response.$metadata.requestId, expected.headers['x-amzn-requestid'])
       assert.deepEqual(body, expected.body)
       assertSegments(
+        tx.trace,
         tx.trace.root,
         ['Llm/completion/Bedrock/InvokeModelCommand', [expectedExternalPath(modelId)]],
         { exact: false }
@@ -297,6 +298,7 @@ test.afterEach(afterEach)
       })
 
       assertSegments(
+        tx.trace,
         tx.trace.root,
         ['Llm/completion/Bedrock/InvokeModelCommand', [expectedExternalPath(modelId)]],
         { exact: false }
@@ -434,6 +436,7 @@ test(`ai21: should properly create errors on create completion (streamed)`, asyn
     })
 
     assertSegments(
+      tx.trace,
       tx.trace.root,
       [
         'Llm/completion/Bedrock/InvokeModelWithResponseStreamCommand',
@@ -497,6 +500,7 @@ test(`models that do not support streaming should be handled`, async (t) => {
     })
 
     assertSegments(
+      tx.trace,
       tx.trace.root,
       [
         'Llm/embedding/Bedrock/InvokeModelWithResponseStreamCommand',
diff --git a/test/versioned/aws-sdk-v3/bedrock-embeddings.test.js b/test/versioned/aws-sdk-v3/bedrock-embeddings.test.js
index 830d750aab..7ec4e851ac 100644
--- a/test/versioned/aws-sdk-v3/bedrock-embeddings.test.js
+++ b/test/versioned/aws-sdk-v3/bedrock-embeddings.test.js
@@ -68,6 +68,7 @@ test.afterEach(afterEach)
       assert.equal(response.$metadata.requestId, expected.headers['x-amzn-requestid'])
       assert.deepEqual(body, expected.body)
       assertSegments(
+        tx.trace,
         tx.trace.root,
         ['Llm/embedding/Bedrock/InvokeModelCommand', [expectedExternalPath(modelId)]],
         { exact: false }
@@ -87,17 +88,18 @@ test.afterEach(afterEach)
       const events = agent.customEventAggregator.events.toArray()
       assert.equal(events.length, 1)
       const embedding = events.filter(([{ type }]) => type === 'LlmEmbedding')[0]
+      const [segment] = tx.trace.getChildren(tx.trace.root.id)
       const expectedEmbedding = {
         'id': /[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}/,
         'appName': 'New Relic for Node.js tests',
         'request_id': '743dd35b-744b-4ddf-b5c6-c0f3de2e3142',
         'trace_id': tx.traceId,
-        'span_id': tx.trace.root.children[0].id,
+        'span_id': segment.id,
         'response.model': modelId,
         'vendor': 'bedrock',
         'ingest_source': 'Node',
         'request.model': modelId,
-        'duration': tx.trace.root.children[0].getDurationInMillis(),
+        'duration': segment.getDurationInMillis(),
         'input': prompt,
         'error': false
       }
@@ -157,6 +159,7 @@ test.afterEach(afterEach)
       })
 
       assertSegments(
+        tx.trace,
         tx.trace.root,
         ['Llm/embedding/Bedrock/InvokeModelCommand', [expectedExternalPath(modelId)]],
         { exact: false }
@@ -164,17 +167,18 @@ test.afterEach(afterEach)
       const events = agent.customEventAggregator.events.toArray()
       assert.equal(events.length, 1)
       const embedding = events.filter(([{ type }]) => type === 'LlmEmbedding')[0]
+      const [segment] = tx.trace.getChildren(tx.trace.root.id)
       const expectedEmbedding = {
         'id': /[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}/,
         'appName': 'New Relic for Node.js tests',
         'request_id': '743dd35b-744b-4ddf-b5c6-c0f3de2e3142',
         'trace_id': tx.traceId,
-        'span_id': tx.trace.root.children[0].id,
+        'span_id': segment.id,
         'response.model': modelId,
         'vendor': 'bedrock',
         'ingest_source': 'Node',
         'request.model': modelId,
-        'duration': tx.trace.root.children[0].getDurationInMillis(),
+        'duration': segment.getDurationInMillis(),
         'input': prompt,
         'error': true
       }
diff --git a/test/versioned/aws-sdk-v3/client-dynamodb.test.js b/test/versioned/aws-sdk-v3/client-dynamodb.test.js
index 804a380d64..708050f04b 100644
--- a/test/versioned/aws-sdk-v3/client-dynamodb.test.js
+++ b/test/versioned/aws-sdk-v3/client-dynamodb.test.js
@@ -160,7 +160,11 @@ function createCommands({ lib, tableName }) {
 
 function finish({ commands, tx, setDatastoreSpy }) {
   const root = tx.trace.root
-  const segments = common.checkAWSAttributes(root, common.DATASTORE_PATTERN)
+  const segments = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.DATASTORE_PATTERN
+  })
 
   assert.equal(
     segments.length,
@@ -168,7 +172,11 @@ function finish({ commands, tx, setDatastoreSpy }) {
     `should have ${commands.length} AWS datastore segments`
   )
 
-  const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN)
+  const externalSegments = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.EXTERN_PATTERN
+  })
   assert.equal(externalSegments.length, 0, 'should not have any External segments')
 
   segments.forEach((segment, i) => {
diff --git a/test/versioned/aws-sdk-v3/common.js b/test/versioned/aws-sdk-v3/common.js
index 0a58c1a816..06da50a320 100644
--- a/test/versioned/aws-sdk-v3/common.js
+++ b/test/versioned/aws-sdk-v3/common.js
@@ -17,7 +17,7 @@ const assert = require('node:assert')
 const SEGMENT_DESTINATION = TRANS_SEGMENT
 const helper = require('../../lib/agent_helper')
 
-function checkAWSAttributes(segment, pattern, markedSegments = []) {
+function checkAWSAttributes({ trace, segment, pattern, markedSegments = [] }) {
   const expectedAttrs = {
     'aws.operation': String,
     'aws.service': String,
@@ -30,27 +30,33 @@ function checkAWSAttributes(segment, pattern, markedSegments = []) {
     const attrs = segment.attributes.get(TRANS_SEGMENT)
     match(attrs, expectedAttrs)
   }
-  segment.children.forEach((child) => {
-    checkAWSAttributes(child, pattern, markedSegments)
+  const children = trace.getChildren(segment.id)
+  children.forEach((child) => {
+    checkAWSAttributes({ trace, segment: child, pattern, markedSegments })
   })
 
   return markedSegments
 }
 
-function getMatchingSegments(segment, pattern, markedSegments = []) {
+function getMatchingSegments({ trace, segment, pattern, markedSegments = [] }) {
   if (pattern.test(segment.name)) {
     markedSegments.push(segment)
   }
 
-  segment.children.forEach((child) => {
-    getMatchingSegments(child, pattern, markedSegments)
+  const children = trace.getChildren(segment.id)
+  children.forEach((child) => {
+    getMatchingSegments({ trace, segment: child, pattern, markedSegments })
   })
 
   return markedSegments
 }
 
 function checkExternals({ service, operations, tx, end }) {
-  const externals = checkAWSAttributes(tx.trace.root, EXTERN_PATTERN)
+  const externals = checkAWSAttributes({
+    trace: tx.trace,
+    segment: tx.trace.root,
+    pattern: EXTERN_PATTERN
+  })
   assert.equal(
     externals.length,
     operations.length,
@@ -71,11 +77,12 @@ function checkExternals({ service, operations, tx, end }) {
 }
 
 function assertChatCompletionMessages({ tx, chatMsgs, expectedId, modelId, prompt, resContent }) {
+  const [segment] = tx.trace.getChildren(tx.trace.root.id)
   const baseMsg = {
     'appName': 'New Relic for Node.js tests',
     'request_id': 'eda0760a-c3f0-4fc1-9a1e-75559d642866',
     'trace_id': tx.traceId,
-    'span_id': tx.trace.root.children[0].id,
+    'span_id': segment.id,
     'response.model': modelId,
     'vendor': 'bedrock',
     'ingest_source': 'Node',
@@ -111,18 +118,19 @@ function assertChatCompletionMessages({ tx, chatMsgs, expectedId, modelId, promp
 }
 
 function assertChatCompletionSummary({ tx, modelId, chatSummary, error = false, numMsgs = 2 }) {
+  const [segment] = tx.trace.getChildren(tx.trace.root.id)
   const expectedChatSummary = {
     'id': /[\w]{8}-[\w]{4}-[\w]{4}-[\w]{4}-[\w]{12}/,
     'appName': 'New Relic for Node.js tests',
     'request_id': 'eda0760a-c3f0-4fc1-9a1e-75559d642866',
     'llm.conversation_id': 'convo-id',
     'trace_id': tx.traceId,
-    'span_id': tx.trace.root.children[0].id,
+    'span_id': segment.id,
     'response.model': modelId,
     'vendor': 'bedrock',
     'ingest_source': 'Node',
     'request.model': modelId,
-    'duration': tx.trace.root.children[0].getDurationInMillis(),
+    'duration': segment.getDurationInMillis(),
     'response.number_of_messages': error ? 1 : numMsgs,
     'response.choices.finish_reason': error ? undefined : 'endoftext',
     'request.temperature': 0.5,
diff --git a/test/versioned/aws-sdk-v3/lib-dynamodb.test.js b/test/versioned/aws-sdk-v3/lib-dynamodb.test.js
index 71ce035056..729dc2749d 100644
--- a/test/versioned/aws-sdk-v3/lib-dynamodb.test.js
+++ b/test/versioned/aws-sdk-v3/lib-dynamodb.test.js
@@ -130,11 +130,19 @@ test('DynamoDB', async (t) => {
 
 function finish(end, tests, tx) {
   const root = tx.trace.root
-  const segments = common.checkAWSAttributes(root, common.DATASTORE_PATTERN)
+  const segments = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.DATASTORE_PATTERN
+  })
 
   assert.equal(segments.length, tests.length, `should have ${tests.length} aws datastore segments`)
 
-  const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN)
+  const externalSegments = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.EXTERN_PATTERN
+  })
   assert.equal(externalSegments.length, 0, 'should not have any External segments')
 
   segments.forEach((segment, i) => {
diff --git a/test/versioned/aws-sdk-v3/sns.test.js b/test/versioned/aws-sdk-v3/sns.test.js
index 9f6b679143..34d35615e4 100644
--- a/test/versioned/aws-sdk-v3/sns.test.js
+++ b/test/versioned/aws-sdk-v3/sns.test.js
@@ -207,11 +207,19 @@ test('SNS', async (t) => {
 function finish(end, tx, destName, setLibrarySpy) {
   const root = tx.trace.root
 
-  const messages = common.checkAWSAttributes(root, common.SNS_PATTERN)
+  const messages = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.SNS_PATTERN
+  })
   assert.equal(messages.length, 1, 'should have 1 message broker segment')
   assert.ok(messages[0].name.endsWith(destName), 'should have appropriate destination')
 
-  const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN)
+  const externalSegments = common.checkAWSAttributes({
+    trace: tx.trace,
+    segment: root,
+    pattern: common.EXTERN_PATTERN
+  })
   assert.equal(externalSegments.length, 0, 'should not have any External segments')
 
   const attrs = messages[0].attributes.get(common.SEGMENT_DESTINATION)
diff --git a/test/versioned/aws-sdk-v3/sqs.test.js b/test/versioned/aws-sdk-v3/sqs.test.js
index 8f4b523fab..05471b00ad 100644
--- a/test/versioned/aws-sdk-v3/sqs.test.js
+++ b/test/versioned/aws-sdk-v3/sqs.test.js
@@ -91,7 +91,11 @@ function finish({ transaction, queueName, setLibrarySpy }) {
   const expectedSegmentCount = 3
 
   const root = transaction.trace.root
-  const segments = common.checkAWSAttributes(root, common.SQS_PATTERN)
+  const segments = common.checkAWSAttributes({
+    trace: transaction.trace,
+    segment: root,
+    pattern: common.SQS_PATTERN
+  })
 
   assert.equal(
     segments.length,
@@ -99,7 +103,11 @@ function finish({ transaction, queueName, setLibrarySpy }) {
     `should have ${expectedSegmentCount} AWS MessageBroker/SQS segments`
   )
 
-  const externalSegments = common.checkAWSAttributes(root, common.EXTERN_PATTERN)
+  const externalSegments = common.checkAWSAttributes({
+    trace: transaction.trace,
+    segment: root,
+    pattern: common.EXTERN_PATTERN
+  })
   assert.equal(externalSegments.length, 0, 'should not have any External segments')
 
   const [sendMessage, sendMessageBatch, receiveMessage] = segments
diff --git a/test/versioned/cassandra-driver/query.test.js b/test/versioned/cassandra-driver/query.test.js
index 3022d70fe2..017852c7c2 100644
--- a/test/versioned/cassandra-driver/query.test.js
+++ b/test/versioned/cassandra-driver/query.test.js
@@ -111,11 +111,8 @@ test('executeBatch - callback style', (t, end) => {
         assert.ok(agent.getTransaction(), 'transaction should still be visible')
         assert.equal(value.rows[0][COL], colValArr[0], 'cassandra client should still work')
 
-        assert.equal(
-          transaction.trace.root.children.length,
-          1,
-          'there should be only one child of the root'
-        )
+        const children = transaction.trace.getChildren(transaction.trace.root.id)
+        assert.equal(children.length, 1, 'there should be only one child of the root')
         verifyTrace(agent, transaction.trace, `${KS}.${FAM}`)
         transaction.end()
         checkMetric(agent)
@@ -143,12 +140,8 @@ test('executeBatch - promise style', (t, end) => {
           .then((result) => {
             assert.ok(agent.getTransaction(), 'transaction should still be visible')
             assert.equal(result.rows[0][COL], colValArr[0], 'cassandra client should still work')
-
-            assert.equal(
-              transaction.trace.root.children.length,
-              2,
-              'there should be two children of the root'
-            )
+            const children = transaction.trace.getChildren(transaction.trace.root.id)
+            assert.equal(children.length, 2, 'there should be two children of the root')
             verifyTrace(agent, transaction.trace, `${KS}.${FAM}`)
             transaction.end()
             checkMetric(agent)
@@ -233,6 +226,7 @@ function verifyTrace(agent, trace, table) {
   assert.ok(trace.root, 'root element should exist')
 
   const setSegment = findSegment(
+    trace,
     trace.root,
     'Datastore/statement/Cassandra/' + table + '/insert/batch'
   )
@@ -242,18 +236,22 @@ function verifyTrace(agent, trace, table) {
   if (setSegment) {
     verifyTraceSegment(agent, setSegment, 'insert/batch')
 
+    const children = trace.getChildren(setSegment.id)
     assert.ok(
-      setSegment.children.length >= 2,
+      children.length >= 2,
       'set should have at least a dns lookup and callback/promise child'
     )
-
-    const getSegment = findSegment(trace.root, 'Datastore/statement/Cassandra/' + table + '/select')
+    const getSegment = findSegment(
+      trace,
+      trace.root,
+      'Datastore/statement/Cassandra/' + table + '/select'
+    )
     assert.ok(getSegment, 'trace segment for select should exist')
 
     if (getSegment) {
+      const getChildren = trace.getChildren(getSegment.id)
       verifyTraceSegment(agent, getSegment, 'select')
-
-      assert.ok(getSegment.children.length >= 1, 'get should have a callback/promise segment')
+      assert.ok(getChildren.length >= 1, 'get should have a callback/promise segment')
       assert.ok(getSegment.timer.hrDuration, 'trace segment should have ended')
     }
   }
diff --git a/test/versioned/connect/route.test.js b/test/versioned/connect/route.test.js
index 46c5196b42..c238a809c3 100644
--- a/test/versioned/connect/route.test.js
+++ b/test/versioned/connect/route.test.js
@@ -39,7 +39,7 @@ test('should properly name transaction from route name', async (t) => {
     plan.equal(tx.verb, 'GET', 'HTTP method is GET')
     plan.equal(tx.statusCode, 200, 'status code is OK')
     plan.ok(tx.trace, 'transaction has trace')
-    const web = tx.trace.root.children[0]
+    const [web] = tx.trace.getChildren(tx.trace.root.id)
     plan.ok(web, 'trace has web segment')
     plan.equal(web.name, tx.name, 'segment name and transaction name match')
     plan.equal(web.partialName, 'Connect/GET//foo', 'should have partial name for apdex')
@@ -73,7 +73,7 @@ test('should default to `/` when no route is specified', async (t) => {
     plan.equal(tx.verb, 'GET', 'HTTP method is GET')
     plan.equal(tx.statusCode, 200, 'status code is OK')
     plan.ok(tx.trace, 'transaction has trace')
-    const web = tx.trace.root.children[0]
+    const [web] = tx.trace.getChildren(tx.trace.root.id)
     plan.ok(web, 'trace has web segment')
     plan.equal(web.name, tx.name, 'segment name and transaction name match')
     plan.equal(web.partialName, 'Connect/GET//', 'should have partial name for apdex')
diff --git a/test/versioned/disabled-instrumentation/disabled-express.test.js b/test/versioned/disabled-instrumentation/disabled-express.test.js
index 83763685c4..fd67eeabda 100644
--- a/test/versioned/disabled-instrumentation/disabled-express.test.js
+++ b/test/versioned/disabled-instrumentation/disabled-express.test.js
@@ -36,7 +36,7 @@ test('should still record child segments if express instrumentation is disabled'
       assert.equal(tx.name, 'WebTransaction/NormalizedUri/*', 'should not name transactions')
       const rootSegment = tx.trace.root
       const expectedSegments = ['WebTransaction/NormalizedUri/*', ['Datastore/operation/Redis/get']]
-      assertSegments(rootSegment, expectedSegments)
+      assertSegments(tx.trace, rootSegment, expectedSegments)
       resolve()
     })
   })
diff --git a/test/versioned/disabled-instrumentation/disabled-ioredis.test.js b/test/versioned/disabled-instrumentation/disabled-ioredis.test.js
index c433d08789..684e217ce6 100644
--- a/test/versioned/disabled-instrumentation/disabled-ioredis.test.js
+++ b/test/versioned/disabled-instrumentation/disabled-ioredis.test.js
@@ -48,7 +48,7 @@ test('Disabled PG scenarios', async (t) => {
       await collection.countDocuments()
       await redisClient.get('bar')
       tx.end()
-      assertSegments(tx.trace.root, [
+      assertSegments(tx.trace, tx.trace.root, [
         'Datastore/statement/MongoDB/disabled-inst-test/aggregate',
         'Datastore/statement/MongoDB/disabled-inst-test/next'
       ])
@@ -65,7 +65,7 @@ test('Disabled PG scenarios', async (t) => {
           redisClient.get('bar', (innerErr) => {
             tx.end()
             assert.equal(innerErr, null)
-            assertSegments(tx.trace.root, [
+            assertSegments(tx.trace, tx.trace.root, [
               'Datastore/statement/MongoDB/disabled-inst-test/aggregate',
               'Datastore/statement/MongoDB/disabled-inst-test/next'
             ])
diff --git a/test/versioned/elastic/elasticsearch.tap.js b/test/versioned/elastic/elasticsearch.tap.js
index 3f33fe60cc..2502c61dae 100644
--- a/test/versioned/elastic/elasticsearch.tap.js
+++ b/test/versioned/elastic/elasticsearch.tap.js
@@ -108,8 +108,7 @@ test('Elasticsearch instrumentation', (t) => {
       t.ok(transaction, 'transaction should be visible')
       await client.indices.create({ index })
       const trace = transaction.trace
-      t.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist')
-      const firstChild = trace.root.children[0]
+      const [firstChild] = trace.getChildren(trace.root.id)
       t.equal(
         firstChild.name,
         `Datastore/statement/ElasticSearch/${index}/index.create`,
@@ -145,8 +144,7 @@ test('Elasticsearch instrumentation', (t) => {
       await client.bulk(setBulkBody(operations, pkgVersion))
       t.ok(transaction, 'transaction should still be visible after bulk create')
       const trace = transaction.trace
-      t.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist')
-      const firstChild = trace.root.children[0]
+      const [firstChild] = trace.getChildren(trace.root.id)
       t.equal(
         firstChild.name,
         'Datastore/statement/ElasticSearch/any/bulk.create',
@@ -184,10 +182,9 @@ test('Elasticsearch instrumentation', (t) => {
       })
       t.ok(transaction, 'transaction should still be visible after bulk create')
       const trace = transaction.trace
-      t.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist')
-      t.ok(trace?.root?.children?.[1], 'trace, trace root, and second child should exist')
       // helper interface results in a first child of timers.setTimeout, with the second child related to the operation
-      const secondChild = trace.root.children[1]
+      const [firstChild, secondChild] = trace.getChildren(trace.root.id)
+      t.ok(firstChild)
       t.equal(
         secondChild.name,
         'Datastore/statement/ElasticSearch/any/bulk.create',
@@ -207,8 +204,7 @@ test('Elasticsearch instrumentation', (t) => {
       t.ok(search, 'search should return a result')
       t.ok(transaction, 'transaction should still be visible after search')
       const trace = transaction.trace
-      t.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist')
-      const firstChild = trace.root.children[0]
+      const [firstChild] = trace.getChildren(trace.root.id)
       t.match(
         firstChild.name,
         `Datastore/statement/ElasticSearch/${DB_INDEX_2}/search`,
@@ -242,8 +238,7 @@ test('Elasticsearch instrumentation', (t) => {
       t.ok(search, 'search should return a result')
       t.ok(transaction, 'transaction should still be visible after search')
       const trace = transaction.trace
-      t.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist')
-      const firstChild = trace.root.children[0]
+      const [firstChild] = trace.getChildren(trace.root.id)
       t.match(
         firstChild.name,
         `Datastore/statement/ElasticSearch/${DB_INDEX}/search`,
@@ -280,8 +275,7 @@ test('Elasticsearch instrumentation', (t) => {
       t.ok(search, 'search should return a result')
       t.ok(transaction, 'transaction should still be visible after search')
       const trace = transaction.trace
-      t.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist')
-      const firstChild = trace.root.children[0]
+      const [firstChild] = trace.getChildren(trace.root.id)
       t.match(
         firstChild.name,
         'Datastore/statement/ElasticSearch/any/search',
@@ -327,8 +321,7 @@ test('Elasticsearch instrumentation', (t) => {
       t.equal(results?.[1]?.hits?.hits?.length, 10, 'second search should return ten results')
       t.ok(transaction, 'transaction should still be visible after search')
       const trace = transaction.trace
-      t.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist')
-      const firstChild = trace.root.children[0]
+      const [firstChild] = trace.getChildren(trace.root.id)
       t.match(
         firstChild.name,
         'Datastore/statement/ElasticSearch/any/msearch.create',
@@ -367,8 +360,7 @@ test('Elasticsearch instrumentation', (t) => {
       t.equal(resultsB?.hits?.length, 10, 'second search should return ten results')
       t.ok(transaction, 'transaction should still be visible after search')
       const trace = transaction.trace
-      t.ok(trace?.root?.children?.[0], 'trace, trace root, and first child should exist')
-      const firstChild = trace.root.children[0]
+      const [firstChild] = trace.getChildren(trace.root.id)
       t.match(
         firstChild.name,
         'timers.setTimeout',
@@ -455,7 +447,7 @@ test('Elasticsearch instrumentation', (t) => {
         ...documentProp
       })
 
-      const createSegment = transaction.trace.root.children[0]
+      const [createSegment] = transaction.trace.getChildren(transaction.trace.root.id)
       const attributes = createSegment.getAttributes()
       t.equal(attributes.host, undefined, 'should not have host attribute')
       t.equal(attributes.port_path_or_id, undefined, 'should not have port attribute')
@@ -477,7 +469,7 @@ test('Elasticsearch instrumentation', (t) => {
       } catch (e) {
         t.ok(e, 'should not be able to create an index named _search')
       }
-      const firstChild = transaction?.trace?.root?.children[0]
+      const [firstChild] = transaction.trace.getChildren(transaction.trace.root.id)
       t.equal(
         firstChild.name,
         'Datastore/statement/ElasticSearch/_search/index.create',
diff --git a/test/versioned/elastic/elasticsearchNoop.tap.js b/test/versioned/elastic/elasticsearchNoop.tap.js
index 5899aee370..fefd48972b 100644
--- a/test/versioned/elastic/elasticsearchNoop.tap.js
+++ b/test/versioned/elastic/elasticsearchNoop.tap.js
@@ -49,7 +49,7 @@ test('Elasticsearch instrumentation', (t) => {
       } catch (e) {
         t.notOk(e, 'should not error')
       }
-      const firstChild = transaction?.trace?.root?.children[0]
+      const [firstChild] = transaction.trace.getChildren(transaction.trace.root.id)
       t.equal(
         firstChild.name,
         `External/localhost:9200/${DB_INDEX}`,
diff --git a/test/versioned/express-esm/segments.tap.mjs b/test/versioned/express-esm/segments.tap.mjs
index f497e87f7c..8242fb4304 100644
--- a/test/versioned/express-esm/segments.tap.mjs
+++ b/test/versioned/express-esm/segments.tap.mjs
@@ -43,6 +43,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']],
       assertSegmentsOptions
@@ -73,6 +74,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']],
       assertSegmentsOptions
@@ -90,6 +92,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']],
       assertSegmentsOptions
@@ -107,6 +110,7 @@ test('transaction segments tests', (t) => {
 
     const { transaction } = await runTest({ app, t, endpoint: '/test/1' })
     const routeSegment = findSegment(
+      transaction.trace,
       transaction.trace.root,
       NAMES.EXPRESS.MIDDLEWARE + 'handler//test/:id'
     )
@@ -135,6 +139,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Route Path: /test',
@@ -161,6 +166,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint: '/router1/test' })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Router: /router1',
@@ -194,6 +200,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Router: /',
@@ -230,6 +237,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         `Expressjs/Route Path: ${segmentPath}`,
@@ -261,6 +269,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint: '/router1/test' })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Router: /router1',
@@ -294,6 +303,7 @@ test('transaction segments tests', (t) => {
       : 'Expressjs/Mounted App: /subapp1'
 
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [firstSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']]],
       assertSegmentsOptions
@@ -333,6 +343,7 @@ test('transaction segments tests', (t) => {
       : 'Expressjs/Mounted App: /subapp1'
 
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         firstSegment,
@@ -371,6 +382,7 @@ test('transaction segments tests', (t) => {
       : 'Expressjs/Mounted App: /subapp1'
 
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [firstSegment, ['Expressjs/Route Path: /:app', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']]],
       assertSegmentsOptions
@@ -406,6 +418,7 @@ test('transaction segments tests', (t) => {
       : 'Expressjs/Mounted App: /subapp1'
 
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Router: /router1',
@@ -431,6 +444,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [NAMES.EXPRESS.MIDDLEWARE + 'myHandler//test'],
       assertSegmentsOptions
@@ -452,6 +466,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Route Path: /test',
@@ -491,6 +506,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Router: /router',
@@ -535,6 +551,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Router: /router1',
@@ -579,6 +596,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Router: /router',
@@ -620,6 +638,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [
         'Expressjs/Router: /router1',
@@ -652,6 +671,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint: '/a/b' })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       ['Expressjs/Route Path: /:foo/:bar', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']],
       assertSegmentsOptions
@@ -675,6 +695,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint: '/abcd' })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       [`Expressjs/Route Path: ${path}`, [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']],
       assertSegmentsOptions
@@ -692,6 +713,7 @@ test('transaction segments tests', (t) => {
 
     const { rootSegment, transaction } = await runTest({ app, t, endpoint: '/a' })
     t.assertSegments(
+      transaction.trace,
       rootSegment,
       ['Expressjs/Route Path: /a/', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']],
       assertSegmentsOptions
@@ -702,7 +724,7 @@ test('transaction segments tests', (t) => {
 
   async function runTest({ t, app, endpoint = '/test', errors = 0 }) {
     const transaction = await makeRequestAndFinishTransaction({ t, app, agent, endpoint })
-    const rootSegment = transaction.trace.root.children[0]
+    const [rootSegment] = transaction.trace.getChildren(transaction.trace.root.id)
 
     t.equal(agent.errors.traceAggregator.errors.length, errors, `should have ${errors} errors`)
     return { rootSegment, transaction }
diff --git a/test/versioned/express-esm/transaction-naming.tap.mjs b/test/versioned/express-esm/transaction-naming.tap.mjs
index aef6bce861..15bf0f23fd 100644
--- a/test/versioned/express-esm/transaction-naming.tap.mjs
+++ b/test/versioned/express-esm/transaction-naming.tap.mjs
@@ -431,7 +431,8 @@ test('transaction naming tests', (t) => {
     const promise = new Promise((resolve) => {
       transactionHandler = function (tx) {
         const expected = 'WebTransaction/Expressjs/GET//test'
-        t.equal(tx.trace.root.children[0].name, expected)
+        const [baseSegment] = tx.trace.getChildren(tx.trace.root.id)
+        t.equal(baseSegment.name, expected)
         resolve()
       }
     })
diff --git a/test/versioned/express/async-handlers.test.js b/test/versioned/express/async-handlers.test.js
index f350d38a19..f2bba1fa6f 100644
--- a/test/versioned/express/async-handlers.test.js
+++ b/test/versioned/express/async-handlers.test.js
@@ -36,8 +36,8 @@ test('async handlers', { skip: !isExpress5() }, async (t) => {
     })
 
     const tx = await runTest(t, '/test')
-    const [children] = tx.trace.root.children
-    const [mw, handler] = children.children
+    const [child] = tx.trace.getChildren(tx.trace.root.id)
+    const [mw, handler] = tx.trace.getChildren(child.id)
     assert.ok(
       Math.ceil(mw.getDurationInMillis()) >= mwTimeout,
       `should be at least ${mwTimeout} for middleware segment`
diff --git a/test/versioned/express/bare-router.test.js b/test/versioned/express/bare-router.test.js
index 6620487144..1f0789dad8 100644
--- a/test/versioned/express/bare-router.test.js
+++ b/test/versioned/express/bare-router.test.js
@@ -35,7 +35,7 @@ test('Express router introspection', async function (t) {
     plan.equal(transaction.verb, 'GET', 'HTTP method is GET')
     plan.ok(transaction.trace, 'transaction has trace')
 
-    const web = transaction.trace.root.children[0]
+    const [web] = transaction.trace.getChildren(transaction.trace.root.id)
     plan.ok(web, 'trace has web segment')
     plan.equal(web.name, transaction.name, 'segment name and transaction name match')
 
diff --git a/test/versioned/express/client-disconnect.test.js b/test/versioned/express/client-disconnect.test.js
index 8505abe891..94fadf510f 100644
--- a/test/versioned/express/client-disconnect.test.js
+++ b/test/versioned/express/client-disconnect.test.js
@@ -44,6 +44,7 @@ test('Client Premature Disconnection', { timeout: 3000 }, (t, end) => {
 
   agent.on('transactionFinished', (transaction) => {
     assertSegments(
+      transaction.trace,
       transaction.trace.root,
       [
         'WebTransaction/Expressjs/POST//test',
diff --git a/test/versioned/express/router-params.test.js b/test/versioned/express/router-params.test.js
index 775fa507a2..8a0d0403b1 100644
--- a/test/versioned/express/router-params.test.js
+++ b/test/versioned/express/router-params.test.js
@@ -46,7 +46,7 @@ test('Express router introspection', async function (t) {
     plan.equal(transaction.verb, 'GET', 'HTTP method is GET')
     plan.ok(transaction.trace, 'transaction has trace')
 
-    const web = transaction.trace.root.children[0]
+    const [web] = transaction.trace.getChildren(transaction.trace.root.id)
     plan.ok(web, 'trace has web segment')
     plan.equal(web.name, transaction.name, 'segment name and transaction name match')
     plan.equal(
diff --git a/test/versioned/express/segments.test.js b/test/versioned/express/segments.test.js
index 8ebef17990..a2f48872ad 100644
--- a/test/versioned/express/segments.test.js
+++ b/test/versioned/express/segments.test.js
@@ -36,10 +36,11 @@ test('first two segments are built-in Express middlewares', function (t, end) {
     res.end()
   })
 
-  runTest(t, function (segments, transaction) {
+  runTest(t, function (root, transaction) {
     // TODO: check for different HTTP methods
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']],
       assertSegmentsOptions
     )
@@ -59,7 +60,7 @@ test('middleware with child segment gets named correctly', function (t, end) {
     }, 1)
   })
 
-  runTest(t, function (segments, transaction) {
+  runTest(t, function (root, transaction) {
     checkMetrics(transaction.metrics, [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>//test'])
 
     end()
@@ -73,9 +74,10 @@ test('segments for route handler', function (t, end) {
     res.end()
   })
 
-  runTest(t, function (segments, transaction) {
+  runTest(t, function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']],
       assertSegmentsOptions
     )
@@ -93,9 +95,10 @@ test('route function names are in segment names', function (t, end) {
     res.end()
   })
 
-  runTest(t, function (segments, transaction) {
+  runTest(t, function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']],
       assertSegmentsOptions
     )
@@ -113,9 +116,10 @@ test('middleware mounted on a path should produce correct names', function (t, e
     res.send()
   })
 
-  runTest(t, '/test/1', function (segments, transaction) {
+  runTest(t, '/test/1', function (root, transaction) {
     const segment = findSegment(
-      transaction.trace.root,
+      transaction.trace,
+      root,
       NAMES.EXPRESS.MIDDLEWARE + 'handler//test/:id'
     )
     assert.ok(segment)
@@ -139,9 +143,10 @@ test('each handler in route has its own segment', function (t, end) {
     }
   )
 
-  runTest(t, function (segments, transaction) {
+  runTest(t, function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [
         'Expressjs/Route Path: /test',
         [NAMES.EXPRESS.MIDDLEWARE + 'handler1', NAMES.EXPRESS.MIDDLEWARE + 'handler2']
@@ -168,9 +173,10 @@ test('segments for routers', function (t, end) {
 
   app.use('/router1', router)
 
-  runTest(t, '/router1/test', function (segments, transaction) {
+  runTest(t, '/router1/test', function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [
         'Expressjs/Router: /router1',
         ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']]
@@ -203,9 +209,10 @@ test('two root routers', function (t, end) {
   })
   app.use('/', router2)
 
-  runTest(t, '/test', function (segments, transaction) {
+  runTest(t, '/test', function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [
         'Expressjs/Router: /',
         'Expressjs/Router: /',
@@ -241,9 +248,10 @@ test('router mounted as a route handler', function (t, end) {
   }
   app.get(path, router1)
 
-  runTest(t, '/test', function (segments, transaction) {
+  runTest(t, '/test', function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [
         `Expressjs/Route Path: ${segmentPath}`,
         [
@@ -274,9 +282,10 @@ test('segments for routers', function (t, end) {
 
   app.use('/router1', router)
 
-  runTest(t, '/router1/test', function (segments, transaction) {
+  runTest(t, '/router1/test', function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [
         'Expressjs/Router: /router1',
         ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']]
@@ -304,14 +313,15 @@ test('segments for sub-app', function (t, end) {
 
   app.use('/subapp1', subapp)
 
-  runTest(t, '/subapp1/test', function (segments, transaction) {
+  runTest(t, '/subapp1/test', function (root, transaction) {
     // express 5 no longer handles child routers as mounted applications
     const firstSegment = isExpress5
       ? NAMES.EXPRESS.MIDDLEWARE + 'app//subapp1'
       : 'Expressjs/Mounted App: /subapp1'
 
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [firstSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']]],
       assertSegmentsOptions
     )
@@ -345,13 +355,14 @@ test('segments for sub-app router', function (t, end) {
 
   app.use('/subapp1', subapp)
 
-  runTest(t, '/subapp1/test', function (segments, transaction) {
+  runTest(t, '/subapp1/test', function (root, transaction) {
     // express 5 no longer handles child routers as mounted applications
     const firstSegment = isExpress5
       ? NAMES.EXPRESS.MIDDLEWARE + 'app//subapp1'
       : 'Expressjs/Mounted App: /subapp1'
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [
         firstSegment,
         [
@@ -384,13 +395,14 @@ test('segments for wildcard', function (t, end) {
 
   app.use('/subapp1', subapp)
 
-  runTest(t, '/subapp1/test', function (segments, transaction) {
+  runTest(t, '/subapp1/test', function (root, transaction) {
     // express 5 no longer handles child routers as mounted applications
     const firstSegment = isExpress5
       ? NAMES.EXPRESS.MIDDLEWARE + 'app//subapp1'
       : 'Expressjs/Mounted App: /subapp1'
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [firstSegment, ['Expressjs/Route Path: /:app', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']]],
       assertSegmentsOptions
     )
@@ -416,13 +428,14 @@ test('router with subapp', function (t, end) {
   router.use('/subapp1', subapp)
   app.use('/router1', router)
 
-  runTest(t, '/router1/subapp1/test', function (segments, transaction) {
+  runTest(t, '/router1/subapp1/test', function (root, transaction) {
     // express 5 no longer handles child routers as mounted applications
     const subAppSegment = isExpress5
       ? NAMES.EXPRESS.MIDDLEWARE + 'app//subapp1'
       : 'Expressjs/Mounted App: /subapp1'
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [
         'Expressjs/Router: /router1',
         [subAppSegment, ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']]]
@@ -447,9 +460,10 @@ test('mounted middleware', function (t, end) {
     res.end()
   })
 
-  runTest(t, function (segments, transaction) {
+  runTest(t, function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [NAMES.EXPRESS.MIDDLEWARE + 'myHandler//test'],
       assertSegmentsOptions
     )
@@ -471,9 +485,10 @@ test('error middleware', function (t, end) {
     res.end()
   })
 
-  runTest(t, function (segments, transaction) {
+  runTest(t, function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       [
         'Expressjs/Route Path: /test',
         [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>'],
@@ -515,12 +530,13 @@ test('error handler in router', function (t, end) {
   runTest(
     t,
     {
-      endpoint: endpoint,
+      endpoint,
       errors: 0
     },
-    function (segments, transaction) {
+    function (root, transaction) {
       assertSegments(
-        transaction.trace.root.children[0],
+        transaction.trace,
+        root,
         [
           'Expressjs/Router: /router',
           [
@@ -571,9 +587,10 @@ test('error handler in second router', function (t, end) {
       endpoint: endpoint,
       errors: 0
     },
-    function (segments, transaction) {
+    function (root, transaction) {
       assertSegments(
-        transaction.trace.root.children[0],
+        transaction.trace,
+        root,
         [
           'Expressjs/Router: /router1',
           [
@@ -624,9 +641,10 @@ test('error handler outside of router', function (t, end) {
       endpoint: endpoint,
       errors: 0
     },
-    function (segments, transaction) {
+    function (root, transaction) {
       assertSegments(
-        transaction.trace.root.children[0],
+        transaction.trace,
+        root,
         [
           'Expressjs/Router: /router',
           ['Expressjs/Route Path: /test', [NAMES.EXPRESS.MIDDLEWARE + '<anonymous>']],
@@ -674,9 +692,10 @@ test('error handler outside of two routers', function (t, end) {
       endpoint: endpoint,
       errors: 0
     },
-    function (segments, transaction) {
+    function (root, transaction) {
       assertSegments(
-        transaction.trace.root.children[0],
+        transaction.trace,
+        root,
         [
           'Expressjs/Router: /router1',
           [
@@ -709,9 +728,10 @@ test('when using a route variable', function (t, end) {
     res.end()
   })
 
-  runTest(t, '/a/b', function (segments, transaction) {
+  runTest(t, '/a/b', function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       ['Expressjs/Route Path: /:foo/:bar', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']],
       assertSegmentsOptions
     )
@@ -734,9 +754,10 @@ test('when using a string pattern in path', function (t, end) {
     res.end()
   })
 
-  runTest(t, '/abcd', function (segments, transaction) {
+  runTest(t, '/abcd', function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       ['Expressjs/Route Path: ' + path, [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']],
       assertSegmentsOptions
     )
@@ -754,9 +775,10 @@ test('when using a regular expression in path', function (t, end) {
     res.end()
   })
 
-  runTest(t, '/a', function (segments, transaction) {
+  runTest(t, '/a', function (root, transaction) {
     assertSegments(
-      transaction.trace.root.children[0],
+      transaction.trace,
+      root,
       ['Expressjs/Route Path: /a/', [NAMES.EXPRESS.MIDDLEWARE + 'myHandler']],
       assertSegmentsOptions
     )
@@ -785,9 +807,15 @@ for (const enabled of codeLevelMetrics) {
       res.end()
     })
 
-    runTest(t, '/chained', function (segments, transaction) {
-      const routeSegment = findSegment(transaction.trace.root, 'Expressjs/Route Path: /chained')
-      const [mw1Segment, mw2Segment, handlerSegment] = routeSegment.children
+    runTest(t, '/chained', function (root, transaction) {
+      const routeSegment = findSegment(
+        transaction.trace,
+        transaction.trace.root,
+        'Expressjs/Route Path: /chained'
+      )
+      const [mw1Segment, mw2Segment, handlerSegment] = transaction.trace.getChildren(
+        routeSegment.id
+      )
       const defaultPath = 'test/versioned/express/segments.test.js'
       assertCLMAttrs({
         segments: [
@@ -832,11 +860,11 @@ function runTest(t, options, callback) {
   }
 
   agent.on('transactionFinished', function (tx) {
-    const baseSegment = tx.trace.root.children[0]
+    const [baseSegment] = tx.trace.getChildren(tx.trace.root.id)
 
     assert.equal(agent.errors.traceAggregator.errors.length, errors, 'should have errors')
 
-    callback(baseSegment.children, tx)
+    callback(baseSegment, tx)
   })
 
   makeRequest(port, endpoint, function (response) {
diff --git a/test/versioned/express/transaction-naming.test.js b/test/versioned/express/transaction-naming.test.js
index eca3baf371..c64a3d4437 100644
--- a/test/versioned/express/transaction-naming.test.js
+++ b/test/versioned/express/transaction-naming.test.js
@@ -379,7 +379,8 @@ test('Express transaction names are unaffected by errorware', async function (t)
 
   agent.on('transactionFinished', function (tx) {
     const expected = 'WebTransaction/Expressjs/GET//test'
-    plan.equal(tx.trace.root.children[0].name, expected)
+    const [baseSegment] = tx.trace.getChildren(tx.trace.root.id)
+    plan.equal(baseSegment.name, expected)
   })
 
   app.use('/test', function () {
diff --git a/test/versioned/fastify/add-hook.test.js b/test/versioned/fastify/add-hook.test.js
index cecf24da28..1dccf563fd 100644
--- a/test/versioned/fastify/add-hook.test.js
+++ b/test/versioned/fastify/add-hook.test.js
@@ -108,7 +108,7 @@ test('non-error hooks', async (t) => {
         ]
       ]
     }
-    assertSegments(transaction.trace.root, expectedSegments)
+    assertSegments(transaction.trace, transaction.trace.root, expectedSegments)
 
     txPassed = true
   })
@@ -168,7 +168,7 @@ test('error hook', async function errorHookTest(t) {
       ]
     }
 
-    assertSegments(transaction.trace.root, expectedSegments)
+    assertSegments(transaction.trace, transaction.trace.root, expectedSegments)
 
     txPassed = true
   })
diff --git a/test/versioned/fastify/code-level-metrics-hooks.test.js b/test/versioned/fastify/code-level-metrics-hooks.test.js
index 31ad174960..cafcf13d00 100644
--- a/test/versioned/fastify/code-level-metrics-hooks.test.js
+++ b/test/versioned/fastify/code-level-metrics-hooks.test.js
@@ -43,11 +43,12 @@ async function performTest(t) {
 
   let txPassed = false
   agent.on('transactionFinished', (transaction) => {
-    const baseSegment = transaction.trace.root.children
-    const [onRequestSegment, handlerSegment] = helper.isSecurityAgentEnabled(agent)
-      ? baseSegment[0].children[0].children
-      : baseSegment[0].children
-    const onSendSegment = handlerSegment.children[0]
+    const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id)
+    let [onRequestSegment, handlerSegment] = transaction.trace.getChildren(baseSegment.id)
+    if (helper.isSecurityAgentEnabled(agent)) {
+      ;[onRequestSegment, handlerSegment] = transaction.trace.getChildren(onRequestSegment.id)
+    }
+    const [onSendSegment] = transaction.trace.getChildren(handlerSegment.id)
     assertCLMAttrs({
       segments: [
         {
diff --git a/test/versioned/fastify/code-level-metrics-middleware.test.js b/test/versioned/fastify/code-level-metrics-middleware.test.js
index 0449d88310..b112ce2c2a 100644
--- a/test/versioned/fastify/code-level-metrics-middleware.test.js
+++ b/test/versioned/fastify/code-level-metrics-middleware.test.js
@@ -53,9 +53,12 @@ async function setup(t, config) {
   }
 }
 
-function assertSegments(testContext, baseSegment, isCLMEnabled) {
-  const { agent } = testContext.nr
-  const { children } = helper.isSecurityAgentEnabled(agent) ? baseSegment.children[0] : baseSegment
+function assertSegments({ t, trace, baseSegment, isCLMEnabled }) {
+  const { agent } = t.nr
+  let children = trace.getChildren(baseSegment.id)
+  if (helper.isSecurityAgentEnabled(agent)) {
+    children = trace.getChildren(children[0].id)
+  }
   // TODO: once we drop v2 support, this function can be removed and assert inline in test below
   if (semver.satisfies(pkgVersion, '>=3')) {
     const [middieSegment, handlerSegment] = children
@@ -105,7 +108,13 @@ async function performTest(t) {
 
   agent.on('transactionFinished', (transaction) => {
     calls.test++
-    assertSegments(t, transaction.trace.root.children[0], agent.config.code_level_metrics.enabled)
+    const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id)
+    assertSegments({
+      t,
+      baseSegment,
+      trace: transaction.trace,
+      isCLMEnabled: agent.config.code_level_metrics.enabled
+    })
   })
 
   await fastify.listen({ port: 0 })
diff --git a/test/versioned/fastify/naming-common.js b/test/versioned/fastify/naming-common.js
index d7ef4a51ad..efb21e6da7 100644
--- a/test/versioned/fastify/naming-common.js
+++ b/test/versioned/fastify/naming-common.js
@@ -43,7 +43,7 @@ module.exports = async function runTests(t, getExpectedSegments) {
           ]
         }
 
-        assertSegments(transaction.trace.root, expectedSegments)
+        assertSegments(transaction.trace, transaction.trace.root, expectedSegments)
       })
 
       await fastify.listen({ port: 0 })
diff --git a/test/versioned/grpc/util.cjs b/test/versioned/grpc/util.cjs
index 1a743da252..c735816fdd 100644
--- a/test/versioned/grpc/util.cjs
+++ b/test/versioned/grpc/util.cjs
@@ -142,8 +142,8 @@ util.assertExternalSegment = function assertExternalSegment({
 }) {
   const methodName = util.getRPCName(fnName)
   const segmentName = `${EXTERNAL.PREFIX}${CLIENT_ADDR}:${port}${methodName}`
-  t.assertSegments(tx.trace.root, [segmentName], { exact: false })
-  const segment = metricsHelpers.findSegment(tx.trace.root, segmentName)
+  t.assertSegments(tx.trace, tx.trace.root, [segmentName], { exact: false })
+  const segment = metricsHelpers.findSegment(tx.trace, tx.trace.root, segmentName)
   const attributes = segment.getAttributes()
   t.equal(
     attributes.url,
diff --git a/test/versioned/hapi/render.tap.js b/test/versioned/hapi/render.tap.js
index 8fafbbf004..7030b3d95c 100644
--- a/test/versioned/hapi/render.tap.js
+++ b/test/versioned/hapi/render.tap.js
@@ -99,10 +99,11 @@ tap.test('agent instrumentation of Hapi', function (t) {
     })
 
     function verifyEnded(root, tx) {
-      for (let i = 0, len = root.children.length; i < len; i++) {
-        const segment = root.children[i]
+      const children = tx.trace.getChildren(root.id)
+      for (let i = 0, len = children.length; i < len; i++) {
+        const segment = children[i]
         t.ok(segment.timer.hasEnd(), util.format('verify %s (%s) has ended', segment.name, tx.id))
-        if (segment.children) {
+        if (tx.trace.getChildren(segment.id)) {
           verifyEnded(segment, tx)
         }
       }
diff --git a/test/versioned/hapi/router.tap.js b/test/versioned/hapi/router.tap.js
index 2870b1d101..c8ff8a1635 100644
--- a/test/versioned/hapi/router.tap.js
+++ b/test/versioned/hapi/router.tap.js
@@ -232,8 +232,9 @@ tap.test('Hapi router introspection', function (t) {
 
   t.test('404 transaction is named correctly', function (t) {
     agent.on('transactionFinished', function (tx) {
+      const [segment] = tx.trace.getChildren(tx.trace.root.id)
       t.equal(
-        tx.trace.root.children[0].name,
+        segment.name,
         'WebTransaction/Nodejs/GET/(not found)',
         '404 segment has standardized name'
       )
@@ -359,7 +360,7 @@ function verifier(t, verb) {
     t.equal(transaction.verb, verb, 'HTTP method is ' + verb)
     t.ok(transaction.trace, 'transaction has trace')
 
-    const web = transaction.trace.root.children[0]
+    const [web] = transaction.trace.getChildren(transaction.trace.root.id)
     t.ok(web, 'trace has web segment')
     t.equal(web.name, transaction.name, 'segment name and transaction name match')
 
diff --git a/test/versioned/hapi/segments.tap.js b/test/versioned/hapi/segments.tap.js
index 90d745f893..feba5c6bd0 100644
--- a/test/versioned/hapi/segments.tap.js
+++ b/test/versioned/hapi/segments.tap.js
@@ -39,11 +39,9 @@ tap.test('Hapi segments', function (t) {
       }
     })
 
-    runTest(t, function (segments, transaction) {
+    runTest(t, function (baseSegment, transaction) {
       checkMetrics(t, transaction.metrics, [NAMES.HAPI.MIDDLEWARE + 'myHandler//test'])
-      t.assertSegments(transaction.trace.root.children[0], [
-        NAMES.HAPI.MIDDLEWARE + 'myHandler//test'
-      ])
+      t.assertSegments(transaction.trace, baseSegment, [NAMES.HAPI.MIDDLEWARE + 'myHandler//test'])
       t.end()
     })
   })
@@ -61,9 +59,9 @@ tap.test('Hapi segments', function (t) {
       handler: { customHandler: { key1: 'val1' } }
     })
 
-    runTest(t, function (segments, transaction) {
+    runTest(t, function (baseSegment, transaction) {
       checkMetrics(t, transaction.metrics, [NAMES.HAPI.MIDDLEWARE + 'customHandler//test'])
-      t.assertSegments(transaction.trace.root.children[0], [
+      t.assertSegments(transaction.trace, baseSegment, [
         NAMES.HAPI.MIDDLEWARE + 'customHandler//test'
       ])
       t.end()
@@ -83,12 +81,12 @@ tap.test('Hapi segments', function (t) {
       }
     })
 
-    runTest(t, function (segments, transaction) {
+    runTest(t, function (baseSegment, transaction) {
       checkMetrics(t, transaction.metrics, [
         NAMES.HAPI.MIDDLEWARE + '<anonymous>//onRequest',
         NAMES.HAPI.MIDDLEWARE + 'myHandler//test'
       ])
-      t.assertSegments(transaction.trace.root.children[0], [
+      t.assertSegments(transaction.trace, baseSegment, [
         NAMES.HAPI.MIDDLEWARE + '<anonymous>//onRequest',
         NAMES.HAPI.MIDDLEWARE + 'myHandler//test'
       ])
@@ -113,12 +111,12 @@ tap.test('Hapi segments', function (t) {
       handler: { customHandler: { key1: 'val1' } }
     })
 
-    runTest(t, function (segments, transaction) {
+    runTest(t, function (baseSegment, transaction) {
       checkMetrics(t, transaction.metrics, [
         NAMES.HAPI.MIDDLEWARE + '<anonymous>//onRequest',
         NAMES.HAPI.MIDDLEWARE + 'customHandler//test'
       ])
-      t.assertSegments(transaction.trace.root.children[0], [
+      t.assertSegments(transaction.trace, baseSegment, [
         NAMES.HAPI.MIDDLEWARE + '<anonymous>//onRequest',
         NAMES.HAPI.MIDDLEWARE + 'customHandler//test'
       ])
@@ -149,8 +147,8 @@ tap.test('Hapi segments', function (t) {
           }
         })
 
-        runTest(t, function (segments) {
-          const [onRequestSegment, handlerSegment] = segments
+        runTest(t, function (baseSegment, transaction) {
+          const [onRequestSegment, handlerSegment] = transaction.trace.getChildren(baseSegment.id)
           t.clmAttrs({
             segments: [
               {
@@ -191,7 +189,8 @@ tap.test('Hapi segments', function (t) {
           handler: { customHandler: { key1: 'val1' } }
         })
 
-        runTest(t, function ([customHandlerSegment]) {
+        runTest(t, function (baseSegment, transaction) {
+          const [customHandlerSegment] = transaction.trace.getChildren(baseSegment.id)
           t.clmAttrs({
             segments: [
               {
@@ -229,7 +228,8 @@ tap.test('Hapi segments', function (t) {
         }
 
         server.register(plugin).then(() => {
-          runTest(t, function ([pluginHandlerSegment]) {
+          runTest(t, function (baseSegment, transaction) {
+            const [pluginHandlerSegment] = transaction.trace.getChildren(baseSegment.id)
             t.clmAttrs({
               segments: [
                 {
@@ -250,8 +250,8 @@ tap.test('Hapi segments', function (t) {
 
 function runTest(t, callback) {
   agent.on('transactionFinished', function (tx) {
-    const baseSegment = tx.trace.root.children[0]
-    callback(baseSegment.children, tx)
+    const [baseSegment] = tx.trace.getChildren(tx.trace.root.id)
+    callback(baseSegment, tx)
   })
 
   server.start().then(function () {
diff --git a/test/versioned/ioredis/ioredis.tap.js b/test/versioned/ioredis/ioredis.tap.js
index f854d219cd..aba0014963 100644
--- a/test/versioned/ioredis/ioredis.tap.js
+++ b/test/versioned/ioredis/ioredis.tap.js
@@ -76,16 +76,17 @@ tap.test('ioredis instrumentation', (t) => {
 
     agent.on('transactionFinished', function (tx) {
       const root = tx.trace.root
-      t.equal(root.children.length, 2, 'root has two children')
+      const children = tx.trace.getChildren(root.id)
+      t.equal(children.length, 2, 'root has two children')
 
-      const setSegment = root.children[0]
+      const [setSegment, getSegment] = children
       t.equal(setSegment.name, 'Datastore/operation/Redis/set')
 
       // ioredis operations return promise, any 'then' callbacks will be sibling segments
       // of the original redis call
-      const getSegment = root.children[1]
       t.equal(getSegment.name, 'Datastore/operation/Redis/get')
-      t.equal(getSegment.children.length, 0, 'should not contain any segments')
+      const getChildren = tx.trace.getChildren(getSegment.id)
+      t.equal(getChildren.length, 0, 'should not contain any segments')
 
       t.end()
     })
diff --git a/test/versioned/kafkajs/kafka.tap.js b/test/versioned/kafkajs/kafka.tap.js
index d585bfafe8..59744629aa 100644
--- a/test/versioned/kafkajs/kafka.tap.js
+++ b/test/versioned/kafkajs/kafka.tap.js
@@ -63,8 +63,9 @@ tap.test('send records correctly', (t) => {
     if (tx.name === expectedName) {
       const name = `MessageBroker/Kafka/Topic/Produce/Named/${topic}`
       const segment = tx.agent.tracer.getSegment()
+      const children = tx.trace.getChildren(segment.id)
 
-      const foundSegment = segment.children.find((s) => s.name.endsWith(topic))
+      const foundSegment = children.find((s) => s.name.endsWith(topic))
       t.equal(foundSegment.name, name)
 
       const metric = tx.metrics.getMetric(name)
@@ -191,8 +192,9 @@ tap.test('sendBatch records correctly', (t) => {
     if (tx.name === expectedName) {
       const name = `MessageBroker/Kafka/Topic/Produce/Named/${topic}`
       const segment = tx.agent.tracer.getSegment()
+      const children = tx.trace.getChildren(segment.id)
 
-      const foundSegment = segment.children.find((s) => s.name.endsWith(topic))
+      const foundSegment = children.find((s) => s.name.endsWith(topic))
       t.equal(foundSegment.name, name)
 
       const metric = tx.metrics.getMetric(name)
@@ -301,9 +303,14 @@ tap.test('consume inside of a transaction', async (t) => {
     agent.on('transactionFinished', (tx) => {
       txCount++
       if (tx.name === expectedName) {
-        t.assertSegments(tx.trace.root, [`${SEGMENT_PREFIX}subscribe`, `${SEGMENT_PREFIX}run`], {
-          exact: false
-        })
+        t.assertSegments(
+          tx.trace,
+          tx.trace.root,
+          [`${SEGMENT_PREFIX}subscribe`, `${SEGMENT_PREFIX}run`],
+          {
+            exact: false
+          }
+        )
       } else {
         utils.verifyConsumeTransaction({ t, tx, topic, clientId })
       }
@@ -349,9 +356,14 @@ tap.test('consume batch inside of a transaction', async (t) => {
 
   const txPromise = new Promise((resolve) => {
     agent.on('transactionFinished', (tx) => {
-      t.assertSegments(tx.trace.root, [`${SEGMENT_PREFIX}subscribe`, `${SEGMENT_PREFIX}run`], {
-        exact: false
-      })
+      t.assertSegments(
+        tx.trace,
+        tx.trace.root,
+        [`${SEGMENT_PREFIX}subscribe`, `${SEGMENT_PREFIX}run`],
+        {
+          exact: false
+        }
+      )
       resolve()
     })
   })
diff --git a/test/versioned/kafkajs/utils.js b/test/versioned/kafkajs/utils.js
index 9fd9c58c82..f350883523 100644
--- a/test/versioned/kafkajs/utils.js
+++ b/test/versioned/kafkajs/utils.js
@@ -92,7 +92,7 @@ utils.verifyConsumeTransaction = ({ t, tx, topic, clientId }) => {
   )
 
   t.equal(tx.getFullName(), expectedName)
-  const consume = metrics.findSegment(tx.trace.root, expectedName)
+  const consume = metrics.findSegment(tx.trace, tx.trace.root, expectedName)
   t.equal(consume, tx.baseSegment)
 
   const attributes = tx.trace.attributes.get(DESTINATIONS.TRANS_SCOPE)
@@ -109,7 +109,7 @@ utils.verifyConsumeTransaction = ({ t, tx, topic, clientId }) => {
  */
 utils.verifyDistributedTrace = ({ t, consumeTxs, produceTx }) => {
   t.ok(produceTx.isDistributedTrace, 'should mark producer as distributed')
-  const produceSegment = produceTx.trace.root.children[3]
+  const [, , , produceSegment] = produceTx.trace.getChildren(produceTx.trace.root.id)
   consumeTxs.forEach((consumeTx) => {
     t.ok(consumeTx.isDistributedTrace, 'should mark consumer as distributed')
     t.equal(consumeTx.incomingCatId, null, 'should not set old CAT properties')
diff --git a/test/versioned/koa/code-level-metrics.tap.js b/test/versioned/koa/code-level-metrics.tap.js
index 889c3fdbb4..db69578b29 100644
--- a/test/versioned/koa/code-level-metrics.tap.js
+++ b/test/versioned/koa/code-level-metrics.tap.js
@@ -110,16 +110,18 @@ tap.test('Vanilla koa, no router', (t) => {
       })
 
       agent.on('transactionFinished', (transaction) => {
-        const baseSegment = transaction.trace.root.children[0]
+        const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id)
+        const [one] = transaction.trace.getChildren(baseSegment.id)
+        const [two] = transaction.trace.getChildren(one.id)
         t.clmAttrs({
           segments: [
             {
-              segment: baseSegment.children[0],
+              segment: one,
               name: 'one',
               filepath: 'code-level-metrics.tap.js'
             },
             {
-              segment: baseSegment.children[0].children[0],
+              segment: two,
               name: 'two',
               filepath: 'code-level-metrics.tap.js'
             }
@@ -166,22 +168,25 @@ tap.test('Using koa-router', { skip: !koaRouterAvailable }, (t) => {
       app.use(router.routes())
 
       agent.on('transactionFinished', (transaction) => {
-        const baseSegment = transaction.trace.root.children[0]
+        const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id)
+        const [dispatch] = transaction.trace.getChildren(baseSegment.id)
+        const [appLevel] = transaction.trace.getChildren(dispatch.id)
+        const [secondMw] = transaction.trace.getChildren(appLevel.id)
 
         t.clmAttrs({
           segments: [
             {
-              segment: baseSegment.children[0],
+              segment: dispatch,
               name: 'dispatch',
               filepath: 'koa-router/lib/router.js'
             },
             {
-              segment: baseSegment.children[0].children[0],
+              segment: appLevel,
               name: 'appLevelMiddleware',
               filepath: 'code-level-metrics.tap.js'
             },
             {
-              segment: baseSegment.children[0].children[0].children[0],
+              segment: secondMw,
               name: 'secondMiddleware',
               filepath: 'code-level-metrics.tap.js'
             }
@@ -226,22 +231,25 @@ tap.test('Using @koa/router', { skip: !atKoaRouterAvailable }, (t) => {
       app.use(router.routes())
 
       agent.on('transactionFinished', (transaction) => {
-        const baseSegment = transaction.trace.root.children[0]
+        const [baseSegment] = transaction.trace.getChildren(transaction.trace.root.id)
+        const [dispatch] = transaction.trace.getChildren(baseSegment.id)
+        const [appLevel] = transaction.trace.getChildren(dispatch.id)
+        const [secondMw] = transaction.trace.getChildren(appLevel.id)
 
         t.clmAttrs({
           segments: [
             {
-              segment: baseSegment.children[0],
+              segment: dispatch,
               name: 'dispatch',
               filepath: '@koa/router/lib/router.js'
             },
             {
-              segment: baseSegment.children[0].children[0],
+              segment: appLevel,
               name: 'appLevelMiddleware',
               filepath: 'code-level-metrics.tap.js'
             },
             {
-              segment: baseSegment.children[0].children[0].children[0],
+              segment: secondMw,
               name: 'secondMiddleware',
               filepath: 'code-level-metrics.tap.js'
             }
diff --git a/test/versioned/koa/koa-route.tap.js b/test/versioned/koa/koa-route.tap.js
index 9048776692..4bcee18864 100644
--- a/test/versioned/koa/koa-route.tap.js
+++ b/test/versioned/koa/koa-route.tap.js
@@ -30,7 +30,7 @@ tap.test('koa-route instrumentation', function (t) {
     })
     app.use(first)
     agent.on('transactionFinished', function (tx) {
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'WebTransaction/WebFrameworkUri/Koa/GET//resource',
         ['Nodejs/Middleware/Koa/firstMiddleware//resource']
       ])
@@ -56,7 +56,7 @@ tap.test('koa-route instrumentation', function (t) {
     app.use(first)
     app.use(second)
     agent.on('transactionFinished', function (tx) {
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'WebTransaction/WebFrameworkUri/Koa/GET//:second',
         [
           'Nodejs/Middleware/Koa/firstMiddleware//:first',
@@ -86,7 +86,7 @@ tap.test('koa-route instrumentation', function (t) {
     app.use(first)
     app.use(second)
     agent.on('transactionFinished', function (tx) {
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'WebTransaction/WebFrameworkUri/Koa/GET//:first',
         [
           'Nodejs/Middleware/Koa/firstMiddleware//:first',
@@ -115,7 +115,7 @@ tap.test('koa-route instrumentation', function (t) {
     app.use(first)
     app.use(second)
     agent.on('transactionFinished', function (tx) {
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'WebTransaction/WebFrameworkUri/Koa/GET//:first',
         ['Nodejs/Middleware/Koa/firstMiddleware//:first']
       ])
@@ -140,7 +140,7 @@ tap.test('koa-route instrumentation', function (t) {
     app.use(first)
     app.use(second)
     agent.on('transactionFinished', function (tx) {
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'WebTransaction/WebFrameworkUri/Koa/GET//:second',
         [
           'Nodejs/Middleware/Koa/firstMiddleware//:first',
@@ -173,7 +173,7 @@ tap.test('koa-route instrumentation', function (t) {
     app.use(second)
     app.use(third)
     agent.on('transactionFinished', function (tx) {
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'WebTransaction/WebFrameworkUri/Koa/GET//resource',
         [
           'Nodejs/Middleware/Koa/firstMiddleware',
diff --git a/test/versioned/koa/koa.tap.js b/test/versioned/koa/koa.tap.js
index de00947b94..b3e7cb2846 100644
--- a/test/versioned/koa/koa.tap.js
+++ b/test/versioned/koa/koa.tap.js
@@ -228,7 +228,7 @@ tap.test('Koa instrumentation', (t) => {
     })
 
     agent.on('transactionFinished', (tx) => {
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'WebTransaction/WebFrameworkUri/Koa/GET//',
         [
           'Nodejs/Middleware/Koa/one',
@@ -284,7 +284,7 @@ tap.test('Koa instrumentation', (t) => {
     })
 
     agent.on('transactionFinished', function (txn) {
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         txn.name,
         [
           'Nodejs/Middleware/Koa/one',
@@ -400,7 +400,7 @@ tap.test('Koa instrumentation', (t) => {
 })
 
 function checkSegments(t, tx) {
-  t.assertSegments(tx.trace.root, [
+  t.assertSegments(tx.trace, tx.trace.root, [
     // Until koa-router is instrumented and transaction naming is addressed,
     // names will be inconsistent depending on whether there is an error.
     tx.name,
diff --git a/test/versioned/koa/router-common.js b/test/versioned/koa/router-common.js
index 9c2ef19f98..21faa78538 100644
--- a/test/versioned/koa/router-common.js
+++ b/test/versioned/koa/router-common.js
@@ -92,7 +92,7 @@ module.exports = (pkg) => {
 
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first',
             [
               'Koa/Router: /',
@@ -119,7 +119,7 @@ module.exports = (pkg) => {
         })
         app.use(router.middleware())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first',
             ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//:first']]
           ])
@@ -154,7 +154,7 @@ module.exports = (pkg) => {
         })
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//.*rst$',
             ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//.*rst$/']]
           ])
@@ -179,7 +179,7 @@ module.exports = (pkg) => {
         })
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             `WebTransaction/WebFrameworkUri/Koa/GET//:first/${path}`,
             ['Koa/Router: /', [`Nodejs/Middleware/Koa/firstMiddleware//:first/${path}`]]
           ])
@@ -204,7 +204,7 @@ module.exports = (pkg) => {
         })
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first',
             [
               'Koa/Router: /',
@@ -236,7 +236,7 @@ module.exports = (pkg) => {
 
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first',
             [
               'Koa/Router: /',
@@ -272,7 +272,7 @@ module.exports = (pkg) => {
 
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:second',
             [
               'Koa/Router: /',
@@ -306,7 +306,7 @@ module.exports = (pkg) => {
 
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first',
             [
               'Nodejs/Middleware/Koa/errorHandler',
@@ -337,7 +337,7 @@ module.exports = (pkg) => {
 
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first',
             [
               'Nodejs/Middleware/Koa/errorHandler',
@@ -380,7 +380,7 @@ module.exports = (pkg) => {
             ]
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:second',
             ['Koa/Router: /', segmentTree]
           ])
@@ -404,7 +404,7 @@ module.exports = (pkg) => {
         })
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET/(not found)',
             ['Koa/Router: /']
           ])
@@ -433,7 +433,7 @@ module.exports = (pkg) => {
           })
           app.use(router.routes())
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/WebFrameworkUri/Koa/GET/(not found)',
               ['Nodejs/Middleware/Koa/baseMiddleware', ['Koa/Router: /']]
             ])
@@ -474,7 +474,7 @@ module.exports = (pkg) => {
           // the dispatch function blocking its returned promise on the
           // resolution of a recursively returned promise.
           // https://github.com/koajs/compose/blob/e754ca3c13e9248b3f453d98ea0b618e09578e2d/index.js#L42-L44
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:second',
             [
               'Koa/Router: /',
@@ -512,7 +512,7 @@ module.exports = (pkg) => {
           // the dispatch function blocking its returned promise on the
           // resolution of a recursively returned promise.
           // https://github.com/koajs/compose/blob/e754ca3c13e9248b3f453d98ea0b618e09578e2d/index.js#L42-L44
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:second',
             [
               'Koa/Router: /',
@@ -547,7 +547,7 @@ module.exports = (pkg) => {
         router.use('/:first', router2.routes())
         app.use(router.routes())
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second',
             ['Koa/Router: /', [getNestedSpanName('secondMiddleware')]]
           ])
@@ -577,7 +577,7 @@ module.exports = (pkg) => {
         app.use(router.routes())
 
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second',
             [
               'Nodejs/Middleware/Koa/appLevelMiddleware',
@@ -609,7 +609,7 @@ module.exports = (pkg) => {
         app.use(router.routes())
 
         agent.on('transactionFinished', (tx) => {
-          t.assertSegments(tx.trace.root, [
+          t.assertSegments(tx.trace, tx.trace.root, [
             'WebTransaction/WebFrameworkUri/Koa/GET//:first/:second',
             [
               'Nodejs/Middleware/Koa/appLevelMiddleware',
@@ -646,7 +646,7 @@ module.exports = (pkg) => {
           app.use(router.routes())
           app.use(router.allowedMethods({ throw: true }))
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)',
               ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]]
             ])
@@ -669,7 +669,7 @@ module.exports = (pkg) => {
           app.use(router.routes())
           app.use(router.allowedMethods({ throw: true }))
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)',
               ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]]
             ])
@@ -705,7 +705,7 @@ module.exports = (pkg) => {
           app.use(router.allowedMethods({ throw: true }))
 
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/NormalizedUri/*',
               [
                 'Nodejs/Middleware/Koa/errorHandler',
@@ -744,7 +744,7 @@ module.exports = (pkg) => {
             app.use(router.allowedMethods({ throw: true }))
 
             agent.on('transactionFinished', (tx) => {
-              t.assertSegments(tx.trace.root, [
+              t.assertSegments(tx.trace, tx.trace.root, [
                 'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)',
                 [
                   'Nodejs/Middleware/Koa/baseMiddleware',
@@ -777,7 +777,7 @@ module.exports = (pkg) => {
           app.use(router.routes())
           app.use(router.allowedMethods())
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)',
               ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]]
             ])
@@ -801,7 +801,7 @@ module.exports = (pkg) => {
           app.use(router.routes())
           app.use(router.allowedMethods())
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)',
               ['Koa/Router: /', [`Nodejs/Middleware/Koa/${allowedMethodsFnName}`]]
             ])
@@ -833,7 +833,7 @@ module.exports = (pkg) => {
           app.use(router.allowedMethods())
 
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/WebFrameworkUri/Koa/GET/(method not allowed)',
               [
                 'Nodejs/Middleware/Koa/appLevelMiddleware',
@@ -867,7 +867,7 @@ module.exports = (pkg) => {
           app.use(router.allowedMethods())
 
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/WebFrameworkUri/Koa/GET/(not implemented)',
               [
                 'Nodejs/Middleware/Koa/appLevelMiddleware',
@@ -893,7 +893,7 @@ module.exports = (pkg) => {
           app.use(router.routes())
           app.use(router.allowedMethods())
           agent.on('transactionFinished', (tx) => {
-            t.assertSegments(tx.trace.root, [
+            t.assertSegments(tx.trace, tx.trace.root, [
               'WebTransaction/WebFrameworkUri/Koa/GET//:first',
               ['Koa/Router: /', ['Nodejs/Middleware/Koa/firstMiddleware//:first']]
             ])
diff --git a/test/versioned/langchain/common.js b/test/versioned/langchain/common.js
index f03ab6f8ed..0e246968c8 100644
--- a/test/versioned/langchain/common.js
+++ b/test/versioned/langchain/common.js
@@ -22,10 +22,11 @@ function filterLangchainEventsByType(events, msgType) {
 }
 
 function assertLangChainVectorSearch({ tx, vectorSearch, responseDocumentSize }) {
+  const [segment] = tx.trace.getChildren(tx.trace.root.id)
   const expectedSearch = {
     'id': /[a-f0-9]{36}/,
     'appName': 'New Relic for Node.js tests',
-    'span_id': tx.trace.root.children[0].id,
+    'span_id': segment.id,
     'trace_id': tx.traceId,
     'request.k': 1,
     'request.query': 'This is an embedding test.',
@@ -33,7 +34,7 @@ function assertLangChainVectorSearch({ tx, vectorSearch, responseDocumentSize })
     'vendor': 'langchain',
     'virtual_llm': true,
     ['response.number_of_documents']: responseDocumentSize,
-    'duration': tx.trace.root.children[0].getDurationInMillis()
+    'duration': segment.getDurationInMillis()
   }
 
   this.equal(vectorSearch[0].type, 'LlmVectorSearch')
@@ -41,11 +42,12 @@ function assertLangChainVectorSearch({ tx, vectorSearch, responseDocumentSize })
 }
 
 function assertLangChainVectorSearchResult({ tx, vectorSearchResult, vectorSearchId }) {
+  const [segment] = tx.trace.getChildren(tx.trace.root.id)
   const baseSearchResult = {
     'id': /[a-f0-9]{36}/,
     'search_id': vectorSearchId,
     'appName': 'New Relic for Node.js tests',
-    'span_id': tx.trace.root.children[0].id,
+    'span_id': segment.id,
     'trace_id': tx.traceId,
     'ingest_source': 'Node',
     'vendor': 'langchain',
@@ -69,10 +71,11 @@ function assertLangChainVectorSearchResult({ tx, vectorSearchResult, vectorSearc
 }
 
 function assertLangChainChatCompletionSummary({ tx, chatSummary, withCallback }) {
+  const [segment] = tx.trace.getChildren(tx.trace.root.id)
   const expectedSummary = {
     'id': /[a-f0-9]{36}/,
     'appName': 'New Relic for Node.js tests',
-    'span_id': tx.trace.root.children[0].id,
+    'span_id': segment.id,
     'trace_id': tx.traceId,
     'request_id': undefined,
     'ingest_source': 'Node',
@@ -82,7 +85,7 @@ function assertLangChainChatCompletionSummary({ tx, chatSummary, withCallback })
     'tags': 'tag1,tag2',
     'virtual_llm': true,
     ['response.number_of_messages']: 1,
-    'duration': tx.trace.root.children[0].getDurationInMillis()
+    'duration': segment.getDurationInMillis()
   }
 
   if (withCallback) {
@@ -102,10 +105,11 @@ function assertLangChainChatCompletionMessages({
   input = '{"topic":"scientist"}',
   output = '212 degrees Fahrenheit is equal to 100 degrees Celsius.'
 }) {
+  const [segment] = tx.trace.getChildren(tx.trace.root.id)
   const baseMsg = {
     id: /[a-f0-9]{36}/,
     appName: 'New Relic for Node.js tests',
-    span_id: tx.trace.root.children[0].id,
+    span_id: segment.id,
     trace_id: tx.traceId,
     ingest_source: 'Node',
     vendor: 'langchain',
diff --git a/test/versioned/langchain/runnables-streaming.tap.js b/test/versioned/langchain/runnables-streaming.tap.js
index 118c32e6a4..0159cb983e 100644
--- a/test/versioned/langchain/runnables-streaming.tap.js
+++ b/test/versioned/langchain/runnables-streaming.tap.js
@@ -396,7 +396,7 @@ tap.test('Langchain instrumentation - chain streaming', (t) => {
         // no-op
       }
 
-      t.assertSegments(tx.trace.root, ['Llm/chain/Langchain/stream'], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, ['Llm/chain/Langchain/stream'], { exact: false })
 
       tx.end()
       t.end()
diff --git a/test/versioned/langchain/runnables.tap.js b/test/versioned/langchain/runnables.tap.js
index a2d3aacc7f..3c7c064632 100644
--- a/test/versioned/langchain/runnables.tap.js
+++ b/test/versioned/langchain/runnables.tap.js
@@ -363,7 +363,7 @@ tap.test('Langchain instrumentation - runnable sequence', (t) => {
       const result = await chain.invoke(input, options)
 
       t.ok(result)
-      t.assertSegments(tx.trace.root, ['Llm/chain/Langchain/invoke'], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, ['Llm/chain/Langchain/invoke'], { exact: false })
 
       tx.end()
       t.end()
diff --git a/test/versioned/langchain/tools.tap.js b/test/versioned/langchain/tools.tap.js
index baf24f7343..ddf1c67caf 100644
--- a/test/versioned/langchain/tools.tap.js
+++ b/test/versioned/langchain/tools.tap.js
@@ -42,7 +42,9 @@ tap.test('Langchain instrumentation - tools', (t) => {
     helper.runInTransaction(agent, async (tx) => {
       const result = await tool.call(input)
       t.ok(result)
-      t.assertSegments(tx.trace.root, ['Llm/tool/Langchain/node-agent-test-tool'], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, ['Llm/tool/Langchain/node-agent-test-tool'], {
+        exact: false
+      })
       tx.end()
       t.end()
     })
@@ -73,10 +75,11 @@ tap.test('Langchain instrumentation - tools', (t) => {
       t.equal(events.length, 1, 'should create a LlmTool event')
       const [[{ type }, toolEvent]] = events
       t.equal(type, 'LlmTool')
+      const [segment] = tx.trace.getChildren(tx.trace.root.id)
       t.match(toolEvent, {
         'id': /[a-f0-9]{36}/,
         'appName': 'New Relic for Node.js tests',
-        'span_id': tx.trace.root.children[0].id,
+        'span_id': segment.id,
         'trace_id': tx.traceId,
         'ingest_source': 'Node',
         'vendor': 'langchain',
@@ -87,7 +90,7 @@ tap.test('Langchain instrumentation - tools', (t) => {
         'output': tool.fakeData[input],
         'name': tool.name,
         'description': tool.description,
-        'duration': tx.trace.root.children[0].getDurationInMillis(),
+        'duration': segment.getDurationInMillis(),
         'run_id': undefined
       })
       tx.end()
diff --git a/test/versioned/langchain/vectorstore.tap.js b/test/versioned/langchain/vectorstore.tap.js
index 9d0a3520fb..2704f6afdd 100644
--- a/test/versioned/langchain/vectorstore.tap.js
+++ b/test/versioned/langchain/vectorstore.tap.js
@@ -101,7 +101,7 @@ tap.test('Langchain instrumentation - vectorstore', (t) => {
     helper.runInTransaction(agent, async (tx) => {
       const result = await vs.similaritySearch('This is an embedding test.', 1)
       t.ok(result)
-      t.assertSegments(tx.trace.root, ['Llm/vectorstore/Langchain/similaritySearch'], {
+      t.assertSegments(tx.trace, tx.trace.root, ['Llm/vectorstore/Langchain/similaritySearch'], {
         exact: false
       })
       tx.end()
diff --git a/test/versioned/memcached/memcached.tap.js b/test/versioned/memcached/memcached.tap.js
index bca04ef074..8052d5d7a7 100644
--- a/test/versioned/memcached/memcached.tap.js
+++ b/test/versioned/memcached/memcached.tap.js
@@ -9,8 +9,7 @@ const tap = require('tap')
 const test = tap.test
 const helper = require('../../lib/agent_helper')
 const params = require('../../lib/params')
-const findSegment = require('../../lib/metrics_helper').findSegment
-const getMetricHostName = require('../../lib/metrics_helper').getMetricHostName
+const { findSegment, getMetricHostName } = require('../../lib/metrics_helper')
 const util = require('util')
 
 const METRICS_ASSERTIONS = 10
@@ -63,7 +62,9 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, ['Datastore/operation/Memcache/touch'])
+          verifySegments(t, transaction.trace, transaction.trace.root, [
+            'Datastore/operation/Memcache/touch'
+          ])
 
           verifyMetrics(t, transaction.metrics, {
             'Datastore/all': 1,
@@ -85,7 +86,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, [
+          verifySegments(t, transaction.trace, transaction.trace.root, [
             'Datastore/operation/Memcache/get',
             ['Truncated/Callback: <anonymous>']
           ])
@@ -110,7 +111,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, [
+          verifySegments(t, transaction.trace, transaction.trace.root, [
             'Datastore/operation/Memcache/gets',
             ['Truncated/Callback: <anonymous>']
           ])
@@ -135,7 +136,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, [
+          verifySegments(t, transaction.trace, transaction.trace.root, [
             'Datastore/operation/Memcache/get',
             ['Truncated/Callback: handle']
           ])
@@ -160,7 +161,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, [
+          verifySegments(t, transaction.trace, transaction.trace.root, [
             'Datastore/operation/Memcache/set',
             ['Truncated/Callback: <anonymous>']
           ])
@@ -188,7 +189,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
             t.ok(agent.getTransaction(), 'transaction should still be visible')
 
             transaction.end()
-            verifySegments(t, transaction.trace.root, [
+            verifySegments(t, transaction.trace, transaction.trace.root, [
               'Datastore/operation/Memcache/replace',
               ['Truncated/Callback: <anonymous>']
             ])
@@ -214,7 +215,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, [
+          verifySegments(t, transaction.trace, transaction.trace.root, [
             'Datastore/operation/Memcache/add',
             ['Truncated/Callback: <anonymous>']
           ])
@@ -245,7 +246,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
               t.ok(agent.getTransaction(), 'transaction should still be visible')
 
               transaction.end()
-              verifySegments(t, transaction.trace.root, [
+              verifySegments(t, transaction.trace, transaction.trace.root, [
                 'Datastore/operation/Memcache/cas',
                 ['Truncated/Callback: <anonymous>']
               ])
@@ -273,7 +274,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
             t.error(err)
             t.ok(agent.getTransaction(), 'transaction should still be visible')
             transaction.end()
-            verifySegments(t, transaction.trace.root, [
+            verifySegments(t, transaction.trace, transaction.trace.root, [
               'Datastore/operation/Memcache/append',
               ['Truncated/Callback: <anonymous>']
             ])
@@ -300,7 +301,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
             t.error(err)
             t.ok(agent.getTransaction(), 'transaction should still be visible')
             transaction.end()
-            verifySegments(t, transaction.trace.root, [
+            verifySegments(t, transaction.trace, transaction.trace.root, [
               'Datastore/operation/Memcache/prepend',
               ['Truncated/Callback: <anonymous>']
             ])
@@ -327,7 +328,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
             t.error(err)
             t.ok(agent.getTransaction(), 'transaction should still be visible')
             transaction.end()
-            verifySegments(t, transaction.trace.root, [
+            verifySegments(t, transaction.trace, transaction.trace.root, [
               'Datastore/operation/Memcache/delete',
               ['Truncated/Callback: <anonymous>']
             ])
@@ -353,7 +354,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, [
+          verifySegments(t, transaction.trace, transaction.trace.root, [
             'Datastore/operation/Memcache/incr',
             ['Truncated/Callback: <anonymous>']
           ])
@@ -378,7 +379,9 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, ['Datastore/operation/Memcache/decr'])
+          verifySegments(t, transaction.trace, transaction.trace.root, [
+            'Datastore/operation/Memcache/decr'
+          ])
 
           verifyMetrics(t, transaction.metrics, {
             'Datastore/all': 1,
@@ -403,7 +406,9 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.ok(agent.getTransaction(), 'transaction should still be visible')
 
           transaction.end()
-          verifySegments(t, transaction.trace.root, ['Datastore/operation/Memcache/version'])
+          verifySegments(t, transaction.trace, transaction.trace.root, [
+            'Datastore/operation/Memcache/version'
+          ])
 
           verifyMetrics(t, transaction.metrics, {
             'Datastore/all': 1,
@@ -443,7 +448,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.notOk(err, 'should not throw an error')
 
           transaction.end()
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           t.equal(segment.getAttributes().key, '"foo"', 'should have the get key as a parameter')
         })
       })
@@ -458,7 +463,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.error(err)
 
           transaction.end()
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           t.notOk(segment.getAttributes().key, 'should not have any attributes')
         })
       })
@@ -472,7 +477,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.notOk(err, 'should not throw an error')
 
           transaction.end()
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           t.equal(
             segment.getAttributes().key,
             '["foo","bar"]',
@@ -490,7 +495,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.notOk(err, 'should not throw an error')
 
           transaction.end()
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           t.equal(segment.getAttributes().key, '"foo"', 'should have the set key as a parameter')
         })
       })
@@ -520,7 +525,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.notOk(err, 'should not throw an error')
 
           transaction.end()
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           const attributes = segment.getAttributes()
           t.equal(
             attributes.host,
@@ -548,7 +553,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.notOk(err, 'should not throw an error')
 
           transaction.end()
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           const attributes = segment.getAttributes()
           t.equal(
             attributes.host,
@@ -595,7 +600,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.notOk(err, 'should not throw an error')
 
           transaction.end()
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           const attributes = segment.getAttributes()
           t.equal(attributes.host, undefined, 'should not have host instance parameter')
           t.equal(
@@ -621,7 +626,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           t.notOk(err, 'should not throw an error')
 
           transaction.end()
-          const segment = transaction.trace.root.children[0]
+          const [segment] = transaction.trace.getChildren(transaction.trace.root.id)
           const attributes = segment.getAttributes()
           t.equal(attributes.host, undefined, 'should not have host instance parameter')
           t.equal(
@@ -685,13 +690,13 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
           if (!t.error(err)) {
             return t.end()
           }
-          const firstSegment = agent.tracer.getSegment().parent
+          const firstSegment = transaction.trace.getParent(agent.tracer.getSegment().parentId)
 
           memcached.get('bar', function (err) {
             if (!t.error(err)) {
               return t.end()
             }
-            end(firstSegment, agent.tracer.getSegment().parent)
+            end(firstSegment, transaction.trace.getParent(agent.tracer.getSegment().parentId))
           })
         })
 
@@ -715,8 +720,7 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
             return t.end()
           }
 
-          const firstGet = transaction.trace.root.children[0]
-          const secondGet = transaction.trace.root.children[1]
+          const [firstGet, secondGet] = transaction.trace.getChildren(transaction.trace.root.id)
           if (firstGet.getAttributes().host === 'server1') {
             t.comment('first get is server 1')
             checkParams(firstGet, 'server1', '1111')
@@ -733,12 +737,12 @@ test('memcached instrumentation', { timeout: 5000 }, function (t) {
   })
 })
 
-function verifySegments(t, rootSegment, expected) {
+function verifySegments(t, trace, rootSegment, expected) {
   let previous
   for (let i = 0; i < expected.length; i++) {
     const child = expected[i]
     if (typeof child === 'string') {
-      const childSegment = findSegment(rootSegment, child)
+      const childSegment = findSegment(trace, rootSegment, child)
       if (!childSegment) {
         previous = null
         t.fail(util.format('Segment %s does not have child %s', rootSegment.name, child))
@@ -746,7 +750,7 @@ function verifySegments(t, rootSegment, expected) {
         previous = childSegment
       }
     } else if (child && Array.isArray(child)) {
-      verifySegments(t, previous, child)
+      verifySegments(t, trace, previous, child)
     }
   }
 }
diff --git a/test/versioned/mongodb-esm/db.test.mjs b/test/versioned/mongodb-esm/db.test.mjs
index c9da524aea..455c99ab31 100644
--- a/test/versioned/mongodb-esm/db.test.mjs
+++ b/test/versioned/mongodb-esm/db.test.mjs
@@ -461,11 +461,12 @@ function verifyMongoSegments({ t, tx, expectedSegments }) {
   let current = tx.trace.root
 
   for (let i = 0, l = expectedSegments.length; i < l; i += 1) {
+    let children = tx.trace.getChildren(current.id)
     // Filter out net.createConnection segments as they could occur during
     // execution, and we don't need to verify them.
-    current.children = current.children.filter((c) => c.name !== 'net.createConnection')
-    assert.equal(current.children.length, 1, 'should have one child segment')
-    current = current.children[0]
+    children = children.filter((c) => c.name !== 'net.createConnection')
+    assert.equal(children.length, 1, 'should have one child segment')
+    current = children[0]
     assert.equal(
       current.name,
       expectedSegments[i],
diff --git a/test/versioned/mongodb-esm/test-assertions.mjs b/test/versioned/mongodb-esm/test-assertions.mjs
index 75b85690b0..6de238537f 100644
--- a/test/versioned/mongodb-esm/test-assertions.mjs
+++ b/test/versioned/mongodb-esm/test-assertions.mjs
@@ -19,6 +19,7 @@ function getValidatorCallback({ t, tx, segments, metrics, end, childrenLength =
 
     const segment = agent.tracer.getSegment()
     let current = tx.trace.root
+    let children = tx.trace.getChildren(current.id)
 
     if (childrenLength === 2) {
       // This block is for testing `collection.aggregate`. The `aggregate`
@@ -29,27 +30,29 @@ function getValidatorCallback({ t, tx, segments, metrics, end, childrenLength =
       // on the trace root. We also added a strict flag for `aggregate` because,
       // depending on the version, there is an extra segment for the callback
       // of our test which we do not need to assert.
-      assert.equal(current.children.length, childrenLength, 'should have two children')
+      assert.equal(children.length, childrenLength, 'should have two children')
       for (const [i, expectedSegment] of segments.entries()) {
-        const child = current.children[i]
+        const child = children[i]
+        const childChildren = tx.trace.getChildren(child.id)
         assert.equal(child.name, expectedSegment, `child should be named ${expectedSegment}`)
         if (common.MONGO_SEGMENT_RE.test(child.name) === true) {
           checkSegmentParams(child, METRIC_HOST_NAME, METRIC_HOST_PORT)
           assert.equal(child.ignore, false, 'should not ignore segment')
         }
-        assert.equal(child.children.length, 0, 'should have no more children')
+        assert.equal(childChildren.length, 0, 'should have no more children')
       }
     } else {
       for (let i = 0, l = segments.length; i < l; ++i) {
-        assert.equal(current.children.length, 1, 'should have one child')
-        current = current.children[0]
+        assert.equal(children.length, 1, 'should have one child')
+        current = children[0]
+        children = tx.trace.getChildren(current.id)
         assert.equal(current.name, segments[i], 'child should be named ' + segments[i])
         if (common.MONGO_SEGMENT_RE.test(current.name) === true) {
           checkSegmentParams(current, METRIC_HOST_NAME, METRIC_HOST_PORT)
           assert.equal(current.ignore, false, 'should not ignore segment')
         }
       }
-      assert.equal(current.children.length, 0, 'should have no more children')
+      assert.equal(children.length, 0, 'should have no more children')
     }
     assert.equal(current === segment, true, 'should test to the current segment')
 
diff --git a/test/versioned/mongodb/collection-common.js b/test/versioned/mongodb/collection-common.js
index 91b35483c5..d6c772c782 100644
--- a/test/versioned/mongodb/collection-common.js
+++ b/test/versioned/mongodb/collection-common.js
@@ -78,6 +78,7 @@ function collectionTest(name, run) {
               t.equal(agent.getTransaction().id, transaction.id, 'should not change transactions')
               const segment = agent.tracer.getSegment()
               let current = transaction.trace.root
+              const children = transaction.trace.getChildren(current.id)
 
               // this logic is just for the collection.aggregate.
               // aggregate no longer returns a callback with cursor
@@ -90,10 +91,11 @@ function collectionTest(name, run) {
               // there is an extra segment for the callback of our test which we do not care
               // to assert
               if (childrenLength === 2) {
-                t.equal(current.children.length, childrenLength, 'should have one child')
+                t.equal(children.length, childrenLength, 'should have one child')
 
                 segments.forEach((expectedSegment, i) => {
-                  const child = current.children[i]
+                  const child = children[i]
+                  const childChildren = transaction.trace.getChildren(child.id)
 
                   t.equal(child.name, expectedSegment, `child should be named ${expectedSegment}`)
                   if (common.MONGO_SEGMENT_RE.test(child.name)) {
@@ -102,13 +104,15 @@ function collectionTest(name, run) {
                   }
 
                   if (strict) {
-                    t.equal(child.children.length, 0, 'should have no more children')
+                    t.equal(childChildren.length, 0, 'should have no more children')
                   }
                 })
               } else {
+                let currentChildren
                 for (let i = 0, l = segments.length; i < l; ++i) {
-                  t.equal(current.children.length, childrenLength, 'should have one child')
-                  current = current.children[0]
+                  t.equal(children.length, childrenLength, 'should have one child')
+                  current = children[0]
+                  currentChildren = transaction.trace.getChildren(current.id)
                   t.equal(current.name, segments[i], 'child should be named ' + segments[i])
                   if (common.MONGO_SEGMENT_RE.test(current.name)) {
                     checkSegmentParams(t, current)
@@ -117,7 +121,7 @@ function collectionTest(name, run) {
                 }
 
                 if (strict) {
-                  t.equal(current.children.length, 0, 'should have no more children')
+                  t.equal(currentChildren.length, 0, 'should have no more children')
                 }
               }
 
@@ -157,7 +161,7 @@ function collectionTest(name, run) {
                 t.ok(attributes.database_name, 'should have database name attribute')
                 t.ok(attributes.product, 'should have product attribute')
               }
-              current = current.children[0]
+              ;[current] = tx.trace.getChildren(current.id)
             }
             t.end()
           })
@@ -182,7 +186,7 @@ function collectionTest(name, run) {
                 t.notOk(attributes.database_name, 'should not have database name attribute')
                 t.ok(attributes.product, 'should have product attribute')
               }
-              current = current.children[0]
+              ;[current] = tx.trace.getChildren(current.id)
             }
             t.end()
           })
@@ -229,6 +233,7 @@ function collectionTest(name, run) {
               t.equal(agent.getTransaction().id, transaction.id, 'should not change transactions')
               const segment = agent.tracer.getSegment()
               let current = transaction.trace.root
+              const children = transaction.trace.getChildren(current.id)
 
               // this logic is just for the collection.aggregate.
               // aggregate no longer returns a callback with cursor
@@ -241,10 +246,11 @@ function collectionTest(name, run) {
               // there is an extra segment for the callback of our test which we do not care
               // to assert
               if (childrenLength === 2) {
-                t.equal(current.children.length, childrenLength, 'should have one child')
+                t.equal(children.length, childrenLength, 'should have one child')
 
                 segments.forEach((expectedSegment, i) => {
-                  const child = current.children[i]
+                  const child = children[i]
+                  const childChildren = transaction.trace.getChildren(child.id)
 
                   t.equal(child.name, expectedSegment, `child should be named ${expectedSegment}`)
                   if (common.MONGO_SEGMENT_RE.test(child.name)) {
@@ -253,13 +259,15 @@ function collectionTest(name, run) {
                   }
 
                   if (strict) {
-                    t.equal(child.children.length, 0, 'should have no more children')
+                    t.equal(childChildren.length, 0, 'should have no more children')
                   }
                 })
               } else {
+                let currentChildren
                 for (let i = 0, l = segments.length; i < l; ++i) {
-                  t.equal(current.children.length, childrenLength, 'should have one child')
-                  current = current.children[0]
+                  t.equal(children.length, childrenLength, 'should have one child')
+                  current = children[0]
+                  currentChildren = transaction.trace.getChildren(current.id)
                   t.equal(current.name, segments[i], 'child should be named ' + segments[i])
                   if (common.MONGO_SEGMENT_RE.test(current.name)) {
                     checkSegmentParams(t, current)
@@ -268,7 +276,7 @@ function collectionTest(name, run) {
                 }
 
                 if (strict) {
-                  t.equal(current.children.length, 0, 'should have no more children')
+                  t.equal(currentChildren.length, 0, 'should have no more children')
                 }
               }
 
diff --git a/test/versioned/mongodb/db-common.js b/test/versioned/mongodb/db-common.js
index ff8a474b42..5662ccf251 100644
--- a/test/versioned/mongodb/db-common.js
+++ b/test/versioned/mongodb/db-common.js
@@ -80,15 +80,16 @@ function verifyMongoSegments(t, agent, transaction, names, opts) {
   let child
 
   for (let i = 0, l = names.length; i < l; ++i) {
+    let children = transaction.trace.getChildren(current.id)
     if (opts.legacy) {
       // Filter out net.createConnection segments as they could occur during execution, which is fine
       // but breaks out assertion function
-      current.children = current.children.filter((c) => c.name !== 'net.createConnection')
-      t.equal(current.children.length, 1, 'should have one child segment')
-      child = current.children[0]
-      current = current.children[0]
+      children = children.filter((c) => c.name !== 'net.createConnection')
+      t.equal(children.length, 1, 'should have one child segment')
+      child = children[0]
+      current = children[0]
     } else {
-      child = current.children[i]
+      child = children[i]
     }
     t.equal(child.name, names[i], 'segment should be named ' + names[i])
 
diff --git a/test/versioned/mysql/basic-pool.tap.js b/test/versioned/mysql/basic-pool.tap.js
index a103ae30fe..0da777cdfa 100644
--- a/test/versioned/mysql/basic-pool.tap.js
+++ b/test/versioned/mysql/basic-pool.tap.js
@@ -121,10 +121,12 @@ tap.test('mysql built-in connection pools', function (t) {
   t.test('ensure host and port are set on segment', function (t) {
     helper.runInTransaction(agent, function transactionInScope(txn) {
       pool.query('SELECT 1 + 1 AS solution', function (err) {
-        let seg = txn.trace.root.children[0].children[1]
+        const [firstChild] = txn.trace.getChildren(txn.trace.root.id)
+        const children = txn.trace.getChildren(firstChild.id)
+        let seg = children[1]
         // 2.16 introduced an extra segment
         if (seg && seg.name === 'timers.setTimeout') {
-          seg = txn.trace.root.children[0].children[2]
+          seg = children[2]
         }
         const attributes = seg.getAttributes()
         t.error(err, 'should not error')
@@ -147,7 +149,7 @@ tap.test('mysql built-in connection pools', function (t) {
     helper.runInTransaction(agent, function transactionInScope(txn) {
       agent.config.datastore_tracer.instance_reporting.enabled = false
       pool.query('SELECT 1 + 1 AS solution', function (err) {
-        const seg = getDatastoreSegment(agent.tracer.getSegment())
+        const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() })
         t.error(err, 'should not error making query')
         t.ok(seg, 'should have a segment')
 
@@ -167,7 +169,7 @@ tap.test('mysql built-in connection pools', function (t) {
     helper.runInTransaction(agent, function transactionInScope(txn) {
       agent.config.datastore_tracer.database_name_reporting.enabled = false
       pool.query('SELECT 1 + 1 AS solution', function (err) {
-        const seg = getDatastoreSegment(agent.tracer.getSegment())
+        const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() })
         const attributes = seg.getAttributes()
         t.notOk(err, 'no errors')
         t.ok(seg, 'there is a segment')
@@ -198,7 +200,7 @@ tap.test('mysql built-in connection pools', function (t) {
         // In the case where you don't have a server running on
         // localhost the data will still be correctly associated
         // with the query.
-        const seg = getDatastoreSegment(agent.tracer.getSegment())
+        const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() })
         const attributes = seg.getAttributes()
         t.ok(seg, 'there is a segment')
         t.equal(attributes.host, agent.config.getHostnameSafe(), 'set host')
@@ -218,7 +220,7 @@ tap.test('mysql built-in connection pools', function (t) {
     const defaultPool = mysql.createPool(defaultConfig)
     helper.runInTransaction(agent, function transactionInScope(txn) {
       defaultPool.query('SELECT 1 + 1 AS solution', function (err) {
-        const seg = getDatastoreSegment(agent.tracer.getSegment())
+        const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() })
         const attributes = seg.getAttributes()
 
         t.error(err, 'should not error making query')
@@ -260,7 +262,7 @@ tap.test('mysql built-in connection pools', function (t) {
     helper.runInTransaction(agent, function transactionInScope(txn) {
       pool.query('SELECT 1 + 1 AS solution123123123123', function (err) {
         const transaction = agent.getTransaction()
-        const segment = agent.tracer.getSegment().parent
+        const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
 
         t.error(err, 'no error occurred')
         t.ok(transaction, 'transaction should exist')
@@ -281,7 +283,7 @@ tap.test('mysql built-in connection pools', function (t) {
         t.error(err)
         t.ok(transaction, 'should not lose transaction')
         if (transaction) {
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -305,7 +307,7 @@ tap.test('mysql built-in connection pools', function (t) {
 
         connection.query('SELECT 1 + 1 AS solution', function (err) {
           const transaction = agent.getTransaction()
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
 
           t.error(err, 'no error occurred')
           t.ok(transaction, 'transaction should exist')
@@ -334,7 +336,7 @@ tap.test('mysql built-in connection pools', function (t) {
           t.error(err)
           t.ok(transaction, 'should not lose transaction')
           if (transaction) {
-            const segment = agent.tracer.getSegment().parent
+            const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
             t.ok(segment, 'segment should exist')
             t.ok(segment.timer.start > 0, 'starts at a positive time')
             t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -365,7 +367,10 @@ tap.test('mysql built-in connection pools', function (t) {
           socketPool.query('SELECT 1 + 1 AS solution', function (err) {
             t.error(err, 'should not error making query')
 
-            const seg = getDatastoreSegment(agent.tracer.getSegment())
+            const seg = getDatastoreSegment({
+              trace: txn.trace,
+              segment: agent.tracer.getSegment()
+            })
             const attributes = seg.getAttributes()
 
             // In the case where you don't have a server running on localhost
@@ -452,7 +457,7 @@ tap.test('poolCluster', function (t) {
           const transaction = agent.getTransaction()
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -488,7 +493,7 @@ tap.test('poolCluster', function (t) {
           const transaction = agent.getTransaction()
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -524,7 +529,7 @@ tap.test('poolCluster', function (t) {
           const transaction = agent.getTransaction()
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -559,7 +564,7 @@ tap.test('poolCluster', function (t) {
           const transaction = agent.getTransaction()
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -596,7 +601,7 @@ tap.test('poolCluster', function (t) {
           const currentTransaction = agent.getTransaction()
           t.ok(currentTransaction, 'transaction should exist')
           t.equal(currentTransaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -622,7 +627,7 @@ tap.test('poolCluster', function (t) {
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction, txn, 'transaction must be same')
 
-          let segment = agent.tracer.getSegment().parent
+          let segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -642,7 +647,7 @@ tap.test('poolCluster', function (t) {
             t.ok(transaction, 'transaction should exist')
             t.equal(transaction, txn, 'transaction must be same')
 
-            segment = agent.tracer.getSegment().parent
+            segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
             t.ok(segment, 'segment should exist')
             t.ok(segment.timer.start > 0, 'starts at a positive time')
             t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -679,8 +684,8 @@ function getDomainSocketPath(callback) {
   })
 }
 
-function getDatastoreSegment(segment) {
-  return segment.parent.children.filter(function (s) {
+function getDatastoreSegment({ segment, trace }) {
+  return trace.getChildren(trace.getParent(segment.parentId).id).filter(function (s) {
     return /^Datastore/.test(s && s.name)
   })[0]
 }
diff --git a/test/versioned/mysql/basic.tap.js b/test/versioned/mysql/basic.tap.js
index b57dc9efdc..f8b3df2db1 100644
--- a/test/versioned/mysql/basic.tap.js
+++ b/test/versioned/mysql/basic.tap.js
@@ -13,6 +13,7 @@ const helper = require('../../lib/agent_helper')
 const urltils = require('../../../lib/util/urltils')
 const params = require('../../lib/params')
 const setup = require('./setup')
+const { findSegment } = require('../../lib/metrics_helper')
 
 tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, function (t) {
   t.autoend()
@@ -176,7 +177,7 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
 
   t.test('ensure database name changes with a use statement', function (t) {
     t.notOk(agent.getTransaction(), 'no transaction should be in play yet')
-    helper.runInTransaction(agent, function transactionInScope() {
+    helper.runInTransaction(agent, function transactionInScope(tx) {
       t.ok(agent.getTransaction(), 'we should be in a transaction')
       withRetry.getClient(function (err, client) {
         if (err) {
@@ -189,7 +190,7 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
             t.error(err, 'should not fail to set database')
 
             client.query('SELECT 1 + 1 AS solution', function (err) {
-              const seg = agent.tracer.getSegment().parent
+              const seg = tx.trace.getParent(agent.tracer.getSegment().parentId)
               const attributes = seg.getAttributes()
 
               t.notOk(err, 'no errors')
@@ -256,7 +257,11 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
           t.ok(results && ended, 'result and end events should occur')
           const traceRoot = transaction.trace.root
           const traceRootDuration = traceRoot.timer.getDurationInMillis()
-          const segment = findSegment(traceRoot, 'Datastore/statement/MySQL/unknown/select')
+          const segment = findSegment(
+            transaction.trace,
+            traceRoot,
+            'Datastore/statement/MySQL/unknown/select'
+          )
           const queryNodeDuration = segment.timer.getDurationInMillis()
 
           t.ok(
@@ -303,12 +308,13 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
             const transaction = agent.getTransaction().end()
             withRetry.release(client)
             const traceRoot = transaction.trace.root
-            const querySegment = traceRoot.children[0]
-            t.equal(querySegment.children.length, 2, 'the query segment should have two children')
+            const [querySegment] = transaction.trace.getChildren(traceRoot.id)
+            const queryChildren = transaction.trace.getChildren(querySegment.id)
+            t.equal(queryChildren.length, 2, 'the query segment should have two children')
 
-            const childSegment = querySegment.children[1]
+            const childSegment = queryChildren[1]
             t.equal(childSegment.name, 'Callback: endCallback', 'children should be callbacks')
-            const grandChildSegment = childSegment.children[0]
+            const [grandChildSegment] = transaction.trace.getChildren(childSegment.id)
             t.equal(grandChildSegment.name, 'timers.setTimeout', 'grand children should be timers')
             t.end()
           }, 100)
@@ -383,7 +389,7 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
           client.query('use test_db;', function (err) {
             t.error(err)
             client.query('SELECT 1 + 1 AS solution', function (err) {
-              const seg = agent.tracer.getSegment().parent
+              const seg = txn.trace.getParent(agent.tracer.getSegment().parentId)
               const attributes = seg.getAttributes()
               t.error(err)
               if (t.ok(seg, 'should have a segment')) {
@@ -409,12 +415,3 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
     })
   })
 })
-
-function findSegment(root, segmentName) {
-  for (let i = 0; i < root.children.length; i++) {
-    const segment = root.children[i]
-    if (segment.name === segmentName) {
-      return segment
-    }
-  }
-}
diff --git a/test/versioned/mysql/pooling.tap.js b/test/versioned/mysql/pooling.tap.js
index da59aaabd6..0838616c33 100644
--- a/test/versioned/mysql/pooling.tap.js
+++ b/test/versioned/mysql/pooling.tap.js
@@ -112,9 +112,10 @@ tap.test('MySQL instrumentation with a connection pool', { timeout: 30000 }, fun
       const trace = transaction.trace
       t.ok(trace, 'trace should exist')
       t.ok(trace.root, 'root element should exist.')
-      t.equal(trace.root.children.length, 1, 'There should be only one child.')
+      const children = trace.getChildren(trace.root.id)
+      t.equal(children.length, 1, 'There should be only one child.')
 
-      const selectSegment = trace.root.children[0]
+      const selectSegment = children[0]
       t.ok(selectSegment, 'trace segment for first SELECT should exist')
 
       t.equal(
@@ -123,10 +124,13 @@ tap.test('MySQL instrumentation with a connection pool', { timeout: 30000 }, fun
         'should register as SELECT'
       )
 
-      t.equal(selectSegment.children.length, 1, 'should only have a callback segment')
-      t.equal(selectSegment.children[0].name, 'Callback: <anonymous>')
+      const selectChildren = trace.getChildren(selectSegment.id)
+      t.equal(selectChildren.length, 1, 'should only have a callback segment')
+      const cb = selectChildren[0]
+      t.equal(cb.name, 'Callback: <anonymous>')
 
-      selectSegment.children[0].children
+      const cbChildren = trace.getChildren(cb.id)
+      cbChildren
         .map(function (segment) {
           return segment.name
         })
diff --git a/test/versioned/mysql2/basic-pool.tap.js b/test/versioned/mysql2/basic-pool.tap.js
index a13c7c824d..6a192d0597 100644
--- a/test/versioned/mysql2/basic-pool.tap.js
+++ b/test/versioned/mysql2/basic-pool.tap.js
@@ -123,7 +123,9 @@ tap.test('mysql2 built-in connection pools', function (t) {
       pool.query('SELECT 1 + 1 AS solution', function (err) {
         // depending on the minor version of mysql2,
         // relevant segment is either first or second index
-        const seg = txn.trace.root.children[0].children.filter(function (trace) {
+        const [parent] = txn.trace.getChildren(txn.trace.root.id)
+        const children = txn.trace.getChildren(parent.id)
+        const seg = children.filter(function (trace) {
           return /Datastore\/statement\/MySQL/.test(trace.name)
         })[0]
         const attributes = seg.getAttributes()
@@ -146,7 +148,7 @@ tap.test('mysql2 built-in connection pools', function (t) {
     helper.runInTransaction(agent, function transactionInScope(txn) {
       agent.config.datastore_tracer.instance_reporting.enabled = false
       pool.query('SELECT 1 + 1 AS solution', function (err) {
-        const seg = getDatastoreSegment(agent.tracer.getSegment())
+        const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() })
         t.error(err, 'should not error making query')
         t.ok(seg, 'should have a segment')
 
@@ -165,7 +167,7 @@ tap.test('mysql2 built-in connection pools', function (t) {
     helper.runInTransaction(agent, function transactionInScope(txn) {
       agent.config.datastore_tracer.database_name_reporting.enabled = false
       pool.query('SELECT 1 + 1 AS solution', function (err) {
-        const seg = getDatastoreSegment(agent.tracer.getSegment())
+        const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() })
         const attributes = seg.getAttributes()
         t.notOk(err, 'no errors')
         t.ok(seg, 'there is a segment')
@@ -195,7 +197,7 @@ tap.test('mysql2 built-in connection pools', function (t) {
         // In the case where you don't have a server running on
         // localhost the data will still be correctly associated
         // with the query.
-        const seg = getDatastoreSegment(agent.tracer.getSegment())
+        const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() })
         const attributes = seg.getAttributes()
         t.ok(seg, 'there is a segment')
         t.equal(attributes.host, agent.config.getHostnameSafe(), 'set host')
@@ -214,7 +216,7 @@ tap.test('mysql2 built-in connection pools', function (t) {
     const defaultPool = mysql.createPool(defaultConfig)
     helper.runInTransaction(agent, function transactionInScope(txn) {
       defaultPool.query('SELECT 1 + 1 AS solution', function (err) {
-        const seg = getDatastoreSegment(agent.tracer.getSegment())
+        const seg = getDatastoreSegment({ trace: txn.trace, segment: agent.tracer.getSegment() })
         const attributes = seg.getAttributes()
 
         t.error(err, 'should not error making query')
@@ -258,7 +260,7 @@ tap.test('mysql2 built-in connection pools', function (t) {
     helper.runInTransaction(agent, function transactionInScope(txn) {
       pool.query('SELECT 1 + 1 AS solution123123123123', function (err) {
         const transaction = agent.getTransaction()
-        const segment = agent.tracer.getSegment().parent
+        const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
 
         t.error(err, 'no error occurred')
         t.ok(transaction, 'transaction should exist')
@@ -279,7 +281,7 @@ tap.test('mysql2 built-in connection pools', function (t) {
         t.error(err)
         t.ok(transaction, 'should not lose transaction')
         if (transaction) {
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -303,7 +305,7 @@ tap.test('mysql2 built-in connection pools', function (t) {
 
         connection.query('SELECT 1 + 1 AS solution', function (err) {
           const transaction = agent.getTransaction()
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
 
           t.error(err, 'no error occurred')
           t.ok(transaction, 'transaction should exist')
@@ -332,7 +334,7 @@ tap.test('mysql2 built-in connection pools', function (t) {
           t.error(err)
           t.ok(transaction, 'should not lose transaction')
           if (transaction) {
-            const segment = agent.tracer.getSegment().parent
+            const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
             t.ok(segment, 'segment should exist')
             t.ok(segment.timer.start > 0, 'starts at a positive time')
             t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -363,7 +365,10 @@ tap.test('mysql2 built-in connection pools', function (t) {
           socketPool.query('SELECT 1 + 1 AS solution', function (err) {
             t.error(err, 'should not error making query')
 
-            const seg = getDatastoreSegment(agent.tracer.getSegment())
+            const seg = getDatastoreSegment({
+              trace: txn.trace,
+              segment: agent.tracer.getSegment()
+            })
             const attributes = seg.getAttributes()
 
             // In the case where you don't have a server running on localhost
@@ -447,7 +452,7 @@ tap.test('poolCluster', function (t) {
           const transaction = agent.getTransaction()
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -483,7 +488,7 @@ tap.test('poolCluster', function (t) {
           const transaction = agent.getTransaction()
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -519,7 +524,7 @@ tap.test('poolCluster', function (t) {
           const transaction = agent.getTransaction()
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -554,7 +559,7 @@ tap.test('poolCluster', function (t) {
           const transaction = agent.getTransaction()
           t.ok(transaction, 'transaction should exist')
           t.equal(transaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -591,7 +596,7 @@ tap.test('poolCluster', function (t) {
           const currentTransaction = agent.getTransaction()
           t.ok(currentTransaction, 'transaction should exist')
           t.equal(currentTransaction.id, txn.id, 'transaction must be same')
-          const segment = agent.tracer.getSegment().parent
+          const segment = txn.trace.getParent(agent.tracer.getSegment().parentId)
           t.ok(segment, 'segment should exist')
           t.ok(segment.timer.start > 0, 'starts at a positive time')
           t.ok(segment.timer.start <= Date.now(), 'starts in past')
@@ -619,8 +624,8 @@ function getDomainSocketPath(callback) {
   })
 }
 
-function getDatastoreSegment(segment) {
-  return segment.parent.children.filter(function (s) {
+function getDatastoreSegment({ trace, segment }) {
+  return trace.getChildren(trace.getParent(segment.parentId).id).filter(function (s) {
     return /^Datastore/.test(s && s.name)
   })[0]
 }
diff --git a/test/versioned/mysql2/basic.tap.js b/test/versioned/mysql2/basic.tap.js
index 26da21b8d4..7f5e3f18f1 100644
--- a/test/versioned/mysql2/basic.tap.js
+++ b/test/versioned/mysql2/basic.tap.js
@@ -13,6 +13,7 @@ const helper = require('../../lib/agent_helper')
 const urltils = require('../../../lib/util/urltils')
 const params = require('../../lib/params')
 const setup = require('./setup')
+const { findSegment } = require('../../lib/metrics_helper')
 
 tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, function (t) {
   t.autoend()
@@ -176,7 +177,7 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
 
   t.test('ensure database name changes with a use statement', function (t) {
     t.notOk(agent.getTransaction(), 'no transaction should be in play yet')
-    helper.runInTransaction(agent, function transactionInScope() {
+    helper.runInTransaction(agent, function transactionInScope(txn) {
       t.ok(agent.getTransaction(), 'we should be in a transaction')
       withRetry.getClient(function (err, client) {
         if (err) {
@@ -189,7 +190,7 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
             t.error(err, 'should not fail to set database')
 
             client.query('SELECT 1 + 1 AS solution', function (err) {
-              const seg = agent.tracer.getSegment().parent
+              const seg = txn.trace.getParent(agent.tracer.getSegment().parentId)
               const attributes = seg.getAttributes()
 
               t.notOk(err, 'no errors')
@@ -286,7 +287,11 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
           t.ok(results && ended, 'result and end events should occur')
           const traceRoot = transaction.trace.root
           const traceRootDuration = traceRoot.timer.getDurationInMillis()
-          const segment = findSegment(traceRoot, 'Datastore/statement/MySQL/unknown/select')
+          const segment = findSegment(
+            transaction.trace,
+            traceRoot,
+            'Datastore/statement/MySQL/unknown/select'
+          )
           const queryNodeDuration = segment.timer.getDurationInMillis()
 
           t.ok(
@@ -333,12 +338,13 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
             const transaction = agent.getTransaction().end()
             withRetry.release(client)
             const traceRoot = transaction.trace.root
-            const querySegment = traceRoot.children[0]
-            t.equal(querySegment.children.length, 2, 'the query segment should have two children')
+            const [querySegment] = transaction.trace.getChildren(traceRoot.id)
+            const children = transaction.trace.getChildren(querySegment.id)
+            t.equal(children.length, 2, 'the query segment should have two children')
 
-            const childSegment = querySegment.children[1]
+            const childSegment = children[1]
             t.equal(childSegment.name, 'Callback: endCallback', 'children should be callbacks')
-            const grandChildSegment = childSegment.children[0]
+            const [grandChildSegment] = transaction.trace.getChildren(childSegment.id)
             t.equal(grandChildSegment.name, 'timers.setTimeout', 'grand children should be timers')
             t.end()
           }, 100)
@@ -413,7 +419,7 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
           client.query('use test_db;', function (err) {
             t.error(err)
             client.query('SELECT 1 + 1 AS solution', function (err) {
-              const seg = agent.tracer.getSegment().parent
+              const seg = txn.trace.getParent(agent.tracer.getSegment().parentId)
               const attributes = seg.getAttributes()
               t.error(err)
               if (t.ok(seg, 'should have a segment')) {
@@ -440,12 +446,3 @@ tap.test('Basic run through mysql functionality', { timeout: 30 * 1000 }, functi
     })
   })
 })
-
-function findSegment(root, segmentName) {
-  for (let i = 0; i < root.children.length; i++) {
-    const segment = root.children[i]
-    if (segment.name === segmentName) {
-      return segment
-    }
-  }
-}
diff --git a/test/versioned/mysql2/pooling.tap.js b/test/versioned/mysql2/pooling.tap.js
index 5966ecb9f8..c0179cfa9f 100644
--- a/test/versioned/mysql2/pooling.tap.js
+++ b/test/versioned/mysql2/pooling.tap.js
@@ -109,9 +109,10 @@ tap.test('MySQL2 instrumentation with a connection pool', { timeout: 60000 }, fu
       const trace = transaction.trace
       t.ok(trace, 'trace should exist')
       t.ok(trace.root, 'root element should exist.')
-      t.equal(trace.root.children.length, 1, 'There should be only one child.')
+      const children = trace.getChildren(trace.root.id)
+      t.equal(children.length, 1, 'There should be only one child.')
 
-      const selectSegment = trace.root.children[0]
+      const selectSegment = children[0]
       t.ok(selectSegment, 'trace segment for first SELECT should exist')
       t.equal(
         selectSegment.name,
@@ -119,10 +120,13 @@ tap.test('MySQL2 instrumentation with a connection pool', { timeout: 60000 }, fu
         'should register as SELECT'
       )
 
-      t.equal(selectSegment.children.length, 1, 'should only have a callback segment')
-      t.equal(selectSegment.children[0].name, 'Callback: <anonymous>')
+      const selectChildren = trace.getChildren(selectSegment.id)
+      t.equal(selectChildren.length, 1, 'should only have a callback segment')
+      const cb = selectChildren[0]
+      t.equal(cb.name, 'Callback: <anonymous>')
+      const cbChildren = trace.getChildren(cb.id)
 
-      selectSegment.children[0].children
+      cbChildren
         .map(function (segment) {
           return segment.name
         })
diff --git a/test/versioned/mysql2/promises.tap.js b/test/versioned/mysql2/promises.tap.js
index adf9d340d7..c8b3010862 100644
--- a/test/versioned/mysql2/promises.tap.js
+++ b/test/versioned/mysql2/promises.tap.js
@@ -85,7 +85,7 @@ tap.test('mysql2 promises', { timeout: 30000 }, (t) => {
             const activeTx = agent.getTransaction()
             t.equal(tx.name, activeTx.name)
 
-            const segment = agent.getTransaction().trace.root.children[2]
+            const [, , segment] = tx.trace.getChildren(tx.trace.root.id)
             const attributes = segment.getAttributes()
             t.equal(
               attributes.host,
diff --git a/test/versioned/nextjs/attributes.tap.js b/test/versioned/nextjs/attributes.tap.js
index 9678abef78..ec5be4d122 100644
--- a/test/versioned/nextjs/attributes.tap.js
+++ b/test/versioned/nextjs/attributes.tap.js
@@ -9,8 +9,7 @@ const tap = require('tap')
 const helpers = require('./helpers')
 const nextPkg = require('next/package.json')
 const {
-  isMiddlewareInstrumentationSupported,
-  getServerSidePropsSegment
+  isMiddlewareInstrumentationSupported
 } = require('../../../lib/instrumentation/nextjs/utils')
 const middlewareSupported = isMiddlewareInstrumentationSupported(nextPkg.version)
 const agentHelper = require('../../lib/agent_helper')
@@ -196,16 +195,18 @@ tap.test('Next.js', (t) => {
         await helpers.makeRequest('/api/person/2?queryParam=queryValue')
         const [tx] = await txPromise
         const rootSegment = tx.trace.root
+        const [handler] = tx.trace.getChildren(rootSegment.id)
         const segments = [
           {
-            segment: rootSegment.children[0],
+            segment: handler,
             name: 'handler',
             filepath: 'pages/api/person/[id]'
           }
         ]
         if (middlewareSupported) {
+          const [middleware] = tx.trace.getChildren(handler.id)
           segments.push({
-            segment: rootSegment.children[0].children[0],
+            segment: middleware,
             name: 'middleware',
             filepath: 'middleware'
           })
@@ -226,20 +227,22 @@ tap.test('Next.js', (t) => {
       const [tx] = await txPromise
       const rootSegment = tx.trace.root
       const segments = []
+      const [first] = tx.trace.getChildren(rootSegment.id)
       if (middlewareSupported) {
+        const [middleware, getServerSideProps] = tx.trace.getChildren(first.id)
         segments.push({
-          segment: rootSegment.children[0].children[0],
+          segment: middleware,
           name: 'middleware',
           filepath: 'middleware'
         })
         segments.push({
-          segment: rootSegment.children[0].children[1],
+          segment: getServerSideProps,
           name: 'getServerSideProps',
           filepath: 'pages/ssr/people'
         })
       } else {
         segments.push({
-          segment: getServerSidePropsSegment(rootSegment),
+          segment: helpers.getServerSidePropsSegment(tx.trace),
           name: 'getServerSideProps',
           filepath: 'pages/ssr/people'
         })
@@ -259,21 +262,23 @@ tap.test('Next.js', (t) => {
       await helpers.makeRequest('/static/dynamic/testing?queryParam=queryValue')
       const [tx] = await txPromise
       const rootSegment = tx.trace.root
+      const [root] = tx.trace.getChildren(rootSegment.id)
 
       // The segment that names the static page will not contain CLM regardless of the
       // configuration flag
       t.clmAttrs({
-        segments: [{ segment: rootSegment.children[0] }],
+        segments: [{ segment: root }],
         enabled: false,
         skipFull: true
       })
 
       if (middlewareSupported) {
+        const [middleware] = tx.trace.getChildren(root.id)
         // this will exist when CLM is enabled
         t.clmAttrs({
           segments: [
             {
-              segment: rootSegment.children[0].children[0],
+              segment: middleware,
               name: 'middleware',
               filepath: 'middleware'
             }
diff --git a/test/versioned/nextjs/helpers.js b/test/versioned/nextjs/helpers.js
index eef6970d38..eca27b7e70 100644
--- a/test/versioned/nextjs/helpers.js
+++ b/test/versioned/nextjs/helpers.js
@@ -17,6 +17,7 @@ const noServerClose = semver.gte(nextPkg.version, '13.4.15')
 // just emit SIGTERM after 14.1.0
 const closeEvent = semver.gte(nextPkg.version, '14.1.0') ? 'SIGTERM' : 'exit'
 const { DESTINATIONS } = require('../../../lib/config/attribute-filter')
+const { findSegment } = require('../../lib/metrics_helper')
 
 /**
  * Builds a Next.js app
@@ -105,22 +106,6 @@ helpers.registerInstrumentation = function (agent) {
   hooks.forEach(agent.registerInstrumentation)
 }
 
-helpers.findSegmentByName = function (root, name) {
-  if (root.name === name) {
-    return root
-  } else if (root.children && root.children.length) {
-    for (let i = 0; i < root.children.length; i++) {
-      const child = root.children[i]
-      const found = helpers.findSegmentByName(child, name)
-      if (found) {
-        return found
-      }
-    }
-  }
-
-  return null
-}
-
 helpers.getTransactionEventAgentAttributes = function getTransactionEventAgentAttributes(
   transaction
 ) {
@@ -134,7 +119,7 @@ helpers.getTransactionIntrinsicAttributes = function getTransactionIntrinsicAttr
 }
 
 helpers.getSegmentAgentAttributes = function getSegmentAgentAttributes(transaction, name) {
-  const segment = helpers.findSegmentByName(transaction.trace.root, name)
+  const segment = findSegment(transaction.trace, transaction.trace.root, name)
   if (segment) {
     return segment.attributes.get(DESTINATIONS.SPAN_EVENT)
   }
@@ -166,3 +151,9 @@ helpers.setupTransactionHandler = function setupTransactionHandler({
     })
   })
 }
+
+helpers.getServerSidePropsSegment = function getServerSidePropsSegment(trace) {
+  const [first] = trace.getChildren(trace.root.id)
+  const children = trace.getChildren(first.id)
+  return children.find((segment) => segment.name.includes('getServerSideProps'))
+}
diff --git a/test/versioned/nextjs/segments.tap.js b/test/versioned/nextjs/segments.tap.js
index 6f224240ae..d5b5775aba 100644
--- a/test/versioned/nextjs/segments.tap.js
+++ b/test/versioned/nextjs/segments.tap.js
@@ -72,7 +72,7 @@ tap.test('Next.js', (t) => {
         children: getChildSegments(URI)
       }
     ]
-    t.assertSegments(tx.trace.root, expectedSegments, { exact: false })
+    t.assertSegments(tx.trace, tx.trace.root, expectedSegments, { exact: false })
   })
 
   t.test('should properly name getServerSideProps segments on dynamic pages', async (t) => {
@@ -91,7 +91,7 @@ tap.test('Next.js', (t) => {
         children: getChildSegments(EXPECTED_URI)
       }
     ]
-    t.assertSegments(tx.trace.root, expectedSegments, { exact: false })
+    t.assertSegments(tx.trace, tx.trace.root, expectedSegments, { exact: false })
   })
 
   t.test(
@@ -121,7 +121,7 @@ tap.test('Next.js', (t) => {
         ]
       }
 
-      t.assertSegments(tx.trace.root, expectedSegments, { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, expectedSegments, { exact: false })
     }
   )
 })
diff --git a/test/versioned/openai/chat-completions.tap.js b/test/versioned/openai/chat-completions.tap.js
index 322691bebb..ebcc238a9b 100644
--- a/test/versioned/openai/chat-completions.tap.js
+++ b/test/versioned/openai/chat-completions.tap.js
@@ -48,6 +48,7 @@ tap.test('OpenAI instrumentation - chat completions', (t) => {
       test.equal(results.choices[0].message.content, '1 plus 2 is 3.')
 
       test.assertSegments(
+        tx.trace,
         tx.trace.root,
         [OPENAI.COMPLETION, [`External/${host}:${port}/chat/completions`]],
         { exact: false }
@@ -128,6 +129,7 @@ tap.test('OpenAI instrumentation - chat completions', (t) => {
         test.equal(chunk.choices[0].message.content, res)
 
         test.assertSegments(
+          tx.trace,
           tx.trace.root,
           [OPENAI.COMPLETION, [`External/${host}:${port}/chat/completions`]],
           { exact: false }
@@ -342,7 +344,7 @@ tap.test('OpenAI instrumentation - chat completions', (t) => {
         const events = agent.customEventAggregator.events.toArray()
         test.equal(events.length, 0)
         // we will still record the external segment but not the chat completion
-        test.assertSegments(tx.trace.root, [
+        test.assertSegments(tx.trace, tx.trace.root, [
           'timers.setTimeout',
           `External/${host}:${port}/chat/completions`
         ])
diff --git a/test/versioned/openai/common.js b/test/versioned/openai/common.js
index 935b4e64f5..e8bbab7bb3 100644
--- a/test/versioned/openai/common.js
+++ b/test/versioned/openai/common.js
@@ -48,11 +48,12 @@ function assertChatCompletionMessages({
   resContent,
   tokenUsage
 }) {
+  const [segment] = tx.trace.getChildren(tx.trace.root.id)
   const baseMsg = {
     'appName': 'New Relic for Node.js tests',
     'request_id': '49dbbffbd3c3f4612aa48def69059aad',
     'trace_id': tx.traceId,
-    'span_id': tx.trace.root.children[0].id,
+    'span_id': segment.id,
     'response.model': model,
     'vendor': 'openai',
     'ingest_source': 'Node',
@@ -94,17 +95,18 @@ function assertChatCompletionMessages({
 }
 
 function assertChatCompletionSummary({ tx, model, chatSummary, error = false }) {
+  const [segment] = tx.trace.getChildren(tx.trace.root.id)
   const expectedChatSummary = {
     'id': /[a-f0-9]{36}/,
     'appName': 'New Relic for Node.js tests',
     'request_id': '49dbbffbd3c3f4612aa48def69059aad',
     'trace_id': tx.traceId,
-    'span_id': tx.trace.root.children[0].id,
+    'span_id': segment.id,
     'response.model': model,
     'vendor': 'openai',
     'ingest_source': 'Node',
     'request.model': model,
-    'duration': tx.trace.root.children[0].getDurationInMillis(),
+    'duration': segment.getDurationInMillis(),
     'response.organization': 'new-relic-nkmd8b',
     'response.headers.llmVersion': '2020-10-01',
     'response.headers.ratelimitLimitRequests': '200',
diff --git a/test/versioned/openai/embeddings.tap.js b/test/versioned/openai/embeddings.tap.js
index e5e7c05eeb..78b0b964c9 100644
--- a/test/versioned/openai/embeddings.tap.js
+++ b/test/versioned/openai/embeddings.tap.js
@@ -46,6 +46,7 @@ tap.test('OpenAI instrumentation - embedding', (t) => {
       test.equal(results.model, 'text-embedding-ada-002-v2')
 
       test.assertSegments(
+        tx.trace,
         tx.trace.root,
         [OPENAI.EMBEDDING, [`External/${host}:${port}/embeddings`]],
         {
@@ -85,17 +86,18 @@ tap.test('OpenAI instrumentation - embedding', (t) => {
       const events = agent.customEventAggregator.events.toArray()
       test.equal(events.length, 1, 'should create a chat completion message and summary event')
       const [embedding] = events
+      const [segment] = tx.trace.getChildren(tx.trace.root.id)
       const expectedEmbedding = {
         'id': /[a-f0-9]{36}/,
         'appName': 'New Relic for Node.js tests',
         'request_id': 'c70828b2293314366a76a2b1dcb20688',
         'trace_id': tx.traceId,
-        'span_id': tx.trace.root.children[0].id,
+        'span_id': segment.id,
         'response.model': 'text-embedding-ada-002-v2',
         'vendor': 'openai',
         'ingest_source': 'Node',
         'request.model': 'text-embedding-ada-002',
-        'duration': tx.trace.root.children[0].getDurationInMillis(),
+        'duration': segment.getDurationInMillis(),
         'response.organization': 'new-relic-nkmd8b',
         'token_count': undefined,
         'response.headers.llmVersion': '2020-10-01',
diff --git a/test/versioned/pg-esm/pg.common.mjs b/test/versioned/pg-esm/pg.common.mjs
index 07ff9798ad..8eabfa2996 100644
--- a/test/versioned/pg-esm/pg.common.mjs
+++ b/test/versioned/pg-esm/pg.common.mjs
@@ -121,9 +121,14 @@ export default function runTests(name, clientFactory) {
     t.ok(trace, 'trace should exist')
     t.ok(trace.root, 'root element should exist')
 
-    const setSegment = findSegment(trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert')
+    const setSegment = findSegment(
+      trace,
+      trace.root,
+      'Datastore/statement/Postgres/' + TABLE + '/insert'
+    )
 
     const getSegment = findSegment(
+      trace,
       trace.root,
       'Datastore/statement/Postgres/' + selectTable + '/select'
     )
@@ -148,7 +153,11 @@ export default function runTests(name, clientFactory) {
     const agent = transaction.agent
     const trace = transaction.trace
 
-    const setSegment = findSegment(trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert')
+    const setSegment = findSegment(
+      trace,
+      trace.root,
+      'Datastore/statement/Postgres/' + TABLE + '/insert'
+    )
     const attributes = setSegment.getAttributes()
 
     const metricHostName = getMetricHostName(agent, params.postgres_host)
@@ -576,6 +585,7 @@ export default function runTests(name, clientFactory) {
             }
 
             const segment = findSegment(
+              transaction.trace,
               transaction.trace.root,
               'Datastore/statement/Postgres/' + TABLE + '/insert'
             )
diff --git a/test/versioned/pg/pg.common.js b/test/versioned/pg/pg.common.js
index 1d769b25d9..f2c351160c 100644
--- a/test/versioned/pg/pg.common.js
+++ b/test/versioned/pg/pg.common.js
@@ -8,9 +8,8 @@
 const tap = require('tap')
 const params = require('../../lib/params')
 const helper = require('../../lib/agent_helper')
-const findSegment = require('../../lib/metrics_helper').findSegment
+const { findSegment, getMetricHostName } = require('../../lib/metrics_helper')
 const test = tap.test
-const getMetricHostName = require('../../lib/metrics_helper').getMetricHostName
 
 function runCommand(client, cmd) {
   return new Promise((resolve, reject) => {
@@ -123,9 +122,14 @@ module.exports = function runTests(name, clientFactory) {
     t.ok(trace, 'trace should exist')
     t.ok(trace.root, 'root element should exist')
 
-    const setSegment = findSegment(trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert')
+    const setSegment = findSegment(
+      trace,
+      trace.root,
+      'Datastore/statement/Postgres/' + TABLE + '/insert'
+    )
 
     const getSegment = findSegment(
+      trace,
       trace.root,
       'Datastore/statement/Postgres/' + selectTable + '/select'
     )
@@ -150,7 +154,11 @@ module.exports = function runTests(name, clientFactory) {
     const agent = transaction.agent
     const trace = transaction.trace
 
-    const setSegment = findSegment(trace.root, 'Datastore/statement/Postgres/' + TABLE + '/insert')
+    const setSegment = findSegment(
+      trace,
+      trace.root,
+      'Datastore/statement/Postgres/' + TABLE + '/insert'
+    )
     const attributes = setSegment.getAttributes()
 
     const metricHostName = getMetricHostName(agent, params.postgres_host)
@@ -578,6 +586,7 @@ module.exports = function runTests(name, clientFactory) {
             }
 
             const segment = findSegment(
+              transaction.trace,
               transaction.trace.root,
               'Datastore/statement/Postgres/' + TABLE + '/insert'
             )
diff --git a/test/versioned/prisma/prisma.tap.js b/test/versioned/prisma/prisma.tap.js
index 8f51c086d6..1f07109d3d 100644
--- a/test/versioned/prisma/prisma.tap.js
+++ b/test/versioned/prisma/prisma.tap.js
@@ -57,7 +57,7 @@ tap.test('Basic run through prisma functionality', { timeout: 30 * 1000 }, (t) =
     helper.runInTransaction(agent, async (tx) => {
       const users = await upsertUsers(prisma)
       t.equal(users.length, 2, 'should get two users')
-      const findManySegment = findSegment(tx.trace.root, findMany)
+      const findManySegment = findSegment(tx.trace, tx.trace.root, findMany)
       const attributes = findManySegment.getAttributes()
       t.notOk(attributes.host, 'should not have a host set')
       t.notOk(attributes.port_path_or_id, 'should not have a port set')
@@ -77,7 +77,7 @@ tap.test('Basic run through prisma functionality', { timeout: 30 * 1000 }, (t) =
         const users = await query
         t.equal(users.length, 2, 'should get two users')
         tx.end()
-        const rawSegment = findSegment(tx.trace.root, raw)
+        const rawSegment = findSegment(tx.trace, tx.trace.root, raw)
         t.ok(rawSegment, `segment named ${raw} should exist`)
       })
     }
@@ -94,7 +94,7 @@ tap.test('Basic run through prisma functionality', { timeout: 30 * 1000 }, (t) =
         const count = await query
         t.equal(count, 2, 'should modify two users')
         tx.end()
-        const rawSegment = findSegment(tx.trace.root, rawUpdate)
+        const rawSegment = findSegment(tx.trace, tx.trace.root, rawUpdate)
         t.ok(rawSegment, `segment named ${rawUpdate} should exist`)
       })
     }
diff --git a/test/versioned/prisma/utils.js b/test/versioned/prisma/utils.js
index 215f939f4b..4687664dc4 100644
--- a/test/versioned/prisma/utils.js
+++ b/test/versioned/prisma/utils.js
@@ -59,10 +59,10 @@ function verifyTraces(t, agent, transaction) {
   t.ok(trace, 'trace should exist')
   t.ok(trace.root, 'root element should exist')
 
-  t.assertSegments(trace.root, [findMany, update, update, findMany], { exact: true })
-  const findManySegment = findSegment(trace.root, findMany)
+  t.assertSegments(trace, trace.root, [findMany, update, update, findMany], { exact: true })
+  const findManySegment = findSegment(trace, trace.root, findMany)
   t.ok(findManySegment.timer.hrDuration, 'findMany segment should have ended')
-  const updateSegment = findSegment(trace.root, update)
+  const updateSegment = findSegment(trace, trace.root, update)
   t.ok(updateSegment.timer.hrDuration, 'update segment should have ended')
   for (const segment of [findManySegment, updateSegment]) {
     const attributes = segment.getAttributes()
diff --git a/test/versioned/q/q.tap.js b/test/versioned/q/q.tap.js
index b6df1c8379..00b138191d 100644
--- a/test/versioned/q/q.tap.js
+++ b/test/versioned/q/q.tap.js
@@ -15,8 +15,10 @@ function QContext(t, agent) {
 }
 
 QContext.prototype.assertTransaction = function assertTransaction(transaction) {
-  this.test.equal(this.agent.getTransaction(), transaction)
-  this.test.equal(this.agent.getTransaction().trace.root.children.length, 0)
+  const tx = this.agent.getTransaction()
+  this.test.equal(tx, transaction)
+  const children = tx.trace.getChildren(tx.trace.root.id)
+  this.test.equal(children.length, 0)
 }
 
 test('q.ninvoke', function testQNInvoke(t) {
diff --git a/test/versioned/redis/redis-v4-legacy-mode.tap.js b/test/versioned/redis/redis-v4-legacy-mode.tap.js
index abacc5a95f..8a9a520535 100644
--- a/test/versioned/redis/redis-v4-legacy-mode.tap.js
+++ b/test/versioned/redis/redis-v4-legacy-mode.tap.js
@@ -77,16 +77,17 @@ test('Redis instrumentation', function (t) {
       const trace = transaction.trace
       t.ok(trace, 'trace should exist')
       t.ok(trace.root, 'root element should exist')
-      t.equal(trace.root.children.length, 2, 'there should be only two children of the root')
+      const children = trace.getChildren(trace.root.id)
+      t.equal(children.length, 2, 'there should be only two children of the root')
 
-      const setSegment = trace.root.children[0]
+      const [setSegment, getSegment] = children
       const setAttributes = setSegment.getAttributes()
       t.ok(setSegment, 'trace segment for set should exist')
       t.equal(setSegment.name, 'Datastore/operation/Redis/set', 'should register the set')
       t.equal(setAttributes.key, '"testkey"', 'should have the set key as a attribute')
-      t.equal(setSegment.children.length, 0, 'set should have no children')
+      const setSegmentChildren = trace.getChildren(setSegment.id)
+      t.equal(setSegmentChildren.length, 0, 'set should have no children')
 
-      const getSegment = trace.root.children[1]
       const getAttributes = getSegment.getAttributes()
       t.ok(getSegment, 'trace segment for get should exist')
 
@@ -124,10 +125,10 @@ test('Redis instrumentation', function (t) {
     t.notOk(agent.getTransaction(), 'no transaction should be in play')
     agent.config.attributes.enabled = true
 
-    helper.runInTransaction(agent, async function () {
+    helper.runInTransaction(agent, async function (tx) {
       await client.v4.set('saveme', 'foobar')
 
-      const segment = agent.tracer.getSegment().children[0]
+      const [segment] = tx.trace.getChildren(agent.tracer.getSegment().id)
       t.equal(segment.getAttributes().key, '"saveme"', 'should have `key` attribute')
       t.end()
     })
@@ -137,10 +138,10 @@ test('Redis instrumentation', function (t) {
     t.notOk(agent.getTransaction(), 'no transaction should be in play')
     agent.config.attributes.enabled = false
 
-    helper.runInTransaction(agent, async function () {
+    helper.runInTransaction(agent, async function (tx) {
       await client.v4.set('saveme', 'foobar')
 
-      const segment = agent.tracer.getSegment().children[0]
+      const [segment] = tx.trace.getChildren(agent.tracer.getSegment().id)
       t.notOk(segment.getAttributes().key, 'should not have `key` attribute')
       t.end()
     })
@@ -157,7 +158,7 @@ test('Redis instrumentation', function (t) {
       await client.v4.set('testkey', 'arglbargle')
 
       const trace = transaction.trace
-      const setSegment = trace.root.children[0]
+      const [setSegment] = trace.getChildren(trace.root.id)
       const attributes = setSegment.getAttributes()
       t.equal(attributes.host, METRIC_HOST_NAME, 'should have host as attribute')
       t.equal(
@@ -181,7 +182,7 @@ test('Redis instrumentation', function (t) {
       const transaction = agent.getTransaction()
       await client.v4.set('testkey', 'arglbargle')
 
-      const setSegment = transaction.trace.root.children[0]
+      const [setSegment] = transaction.trace.getChildren(transaction.trace.root.id)
       const attributes = setSegment.getAttributes()
       t.equal(attributes.host, undefined, 'should not have host attribute')
       t.equal(attributes.port_path_or_id, undefined, 'should not have port attribute')
@@ -218,9 +219,9 @@ test('Redis instrumentation', function (t) {
     })
 
     function verify() {
-      const setSegment1 = transaction.trace.root.children[0]
-      const selectSegment = transaction.trace.root.children[1]
-      const setSegment2 = transaction.trace.root.children[2]
+      const [setSegment1, selectSegment, setSegment2] = transaction.trace.getChildren(
+        transaction.trace.root.id
+      )
 
       t.equal(setSegment1.name, 'Datastore/operation/Redis/set', 'should register the first set')
       t.equal(
diff --git a/test/versioned/redis/redis-v4.tap.js b/test/versioned/redis/redis-v4.tap.js
index 5b43c4225e..12719ec4cf 100644
--- a/test/versioned/redis/redis-v4.tap.js
+++ b/test/versioned/redis/redis-v4.tap.js
@@ -73,16 +73,17 @@ test('Redis instrumentation', function (t) {
       const trace = transaction.trace
       t.ok(trace, 'trace should exist')
       t.ok(trace.root, 'root element should exist')
-      t.equal(trace.root.children.length, 2, 'there should be only two children of the root')
+      const children = trace.getChildren(trace.root.id)
+      t.equal(children.length, 2, 'there should be only two children of the root')
 
-      const setSegment = trace.root.children[0]
+      const [setSegment, getSegment] = children
       const setAttributes = setSegment.getAttributes()
       t.ok(setSegment, 'trace segment for set should exist')
       t.equal(setSegment.name, 'Datastore/operation/Redis/set', 'should register the set')
       t.equal(setAttributes.key, '"testkey"', 'should have the set key as a attribute')
-      t.equal(setSegment.children.length, 0, 'set should have no children')
+      const setSegmentChildren = trace.getChildren(setSegment.id)
+      t.equal(setSegmentChildren.length, 0, 'set should have no children')
 
-      const getSegment = trace.root.children[1]
       const getAttributes = getSegment.getAttributes()
       t.ok(getSegment, 'trace segment for get should exist')
 
@@ -144,10 +145,10 @@ test('Redis instrumentation', function (t) {
     t.notOk(agent.getTransaction(), 'no transaction should be in play')
     agent.config.attributes.enabled = true
 
-    helper.runInTransaction(agent, async function () {
+    helper.runInTransaction(agent, async function (tx) {
       await client.set('saveme', 'foobar')
 
-      const segment = agent.tracer.getSegment().children[0]
+      const [segment] = tx.trace.getChildren(agent.tracer.getSegment().id)
       t.equal(segment.getAttributes().key, '"saveme"', 'should have `key` attribute')
       t.end()
     })
@@ -157,10 +158,10 @@ test('Redis instrumentation', function (t) {
     t.notOk(agent.getTransaction(), 'no transaction should be in play')
     agent.config.attributes.enabled = false
 
-    helper.runInTransaction(agent, async function () {
+    helper.runInTransaction(agent, async function (tx) {
       await client.set('saveme', 'foobar')
 
-      const segment = agent.tracer.getSegment().children[0]
+      const [segment] = tx.trace.getChildren(agent.tracer.getSegment().id)
       t.notOk(segment.getAttributes().key, 'should not have `key` attribute')
       t.end()
     })
@@ -177,7 +178,7 @@ test('Redis instrumentation', function (t) {
       await client.set('testkey', 'arglbargle')
 
       const trace = transaction.trace
-      const setSegment = trace.root.children[0]
+      const [setSegment] = trace.getChildren(trace.root.id)
       const attributes = setSegment.getAttributes()
       t.equal(attributes.host, METRIC_HOST_NAME, 'should have host as attribute')
       t.equal(
@@ -201,7 +202,7 @@ test('Redis instrumentation', function (t) {
       const transaction = agent.getTransaction()
       await client.set('testkey', 'arglbargle')
 
-      const setSegment = transaction.trace.root.children[0]
+      const [setSegment] = transaction.trace.getChildren(transaction.trace.root.id)
       const attributes = setSegment.getAttributes()
       t.equal(attributes.host, undefined, 'should not have host attribute')
       t.equal(attributes.port_path_or_id, undefined, 'should not have port attribute')
@@ -238,10 +239,9 @@ test('Redis instrumentation', function (t) {
     })
 
     function verify() {
-      const setSegment1 = transaction.trace.root.children[0]
-      const selectSegment = transaction.trace.root.children[1]
-      const setSegment2 = transaction.trace.root.children[2]
-
+      const [setSegment1, selectSegment, setSegment2] = transaction.trace.getChildren(
+        transaction.trace.root.id
+      )
       t.equal(setSegment1.name, 'Datastore/operation/Redis/set', 'should register the first set')
       t.equal(
         setSegment1.getAttributes().database_name,
diff --git a/test/versioned/redis/redis.tap.js b/test/versioned/redis/redis.tap.js
index 99d3641e2a..d7678e3858 100644
--- a/test/versioned/redis/redis.tap.js
+++ b/test/versioned/redis/redis.tap.js
@@ -79,16 +79,19 @@ test('Redis instrumentation', { timeout: 20000 }, function (t) {
           const trace = transaction.trace
           t.ok(trace, 'trace should exist')
           t.ok(trace.root, 'root element should exist')
-          t.equal(trace.root.children.length, 1, 'there should be only one child of the root')
+          const children = trace.getChildren(trace.root.id)
+          t.equal(children.length, 1, 'there should be only one child of the root')
 
-          const setSegment = trace.root.children[0]
+          const [setSegment] = children
           const setAttributes = setSegment.getAttributes()
+          const setChildren = trace.getChildren(setSegment.id)
           t.ok(setSegment, 'trace segment for set should exist')
           t.equal(setSegment.name, 'Datastore/operation/Redis/set', 'should register the set')
           t.equal(setAttributes.key, '"testkey"', 'should have the set key as a attribute')
-          t.equal(setSegment.children.length, 1, 'set should have an only child')
+          t.equal(setChildren.length, 1, 'set should have an only child')
 
-          const getSegment = setSegment.children[0].children[0]
+          const [getSegment] = trace.getChildren(setChildren[0].id)
+          const getChildren = trace.getChildren(getSegment.id)
           const getAttributes = getSegment.getAttributes()
           t.ok(getSegment, 'trace segment for get should exist')
 
@@ -96,7 +99,7 @@ test('Redis instrumentation', { timeout: 20000 }, function (t) {
 
           t.equal(getAttributes.key, '"testkey"', 'should have the get key as a attribute')
 
-          t.ok(getSegment.children.length >= 1, 'get should have a callback segment')
+          t.ok(getChildren.length >= 1, 'get should have a callback segment')
 
           t.ok(getSegment.timer.hrDuration, 'trace segment should have ended')
         })
@@ -148,9 +151,10 @@ test('Redis instrumentation', { timeout: 20000 }, function (t) {
     })
 
     agent.on('transactionFinished', function (tx) {
-      const redSeg = tx.trace.root.children[0]
+      const [redSeg] = tx.trace.getChildren(tx.trace.root.id)
       t.equal(redSeg.name, 'Datastore/operation/Redis/set', 'should have untruncated redis segment')
-      t.equal(redSeg.children.length, 0, 'should have no children for redis segment')
+      const redChildren = tx.trace.getChildren(redSeg.id)
+      t.equal(redChildren.length, 0, 'should have no children for redis segment')
     })
   })
 
@@ -218,12 +222,12 @@ test('Redis instrumentation', { timeout: 20000 }, function (t) {
   t.test('should add `key` attribute to trace segment', function (t) {
     agent.config.attributes.enabled = true
 
-    helper.runInTransaction(agent, function () {
+    helper.runInTransaction(agent, function (tx) {
       client.set('saveme', 'foobar', function (error) {
         // Regardless of error, key should still be captured.
         t.error(error)
 
-        const segment = agent.tracer.getSegment().parent
+        const segment = tx.trace.getParent(agent.tracer.getSegment().parentId)
         t.equal(segment.getAttributes().key, '"saveme"', 'should have `key` attribute')
         t.end()
       })
@@ -233,12 +237,12 @@ test('Redis instrumentation', { timeout: 20000 }, function (t) {
   t.test('should not add `key` attribute to trace segment', function (t) {
     agent.config.attributes.enabled = false
 
-    helper.runInTransaction(agent, function () {
+    helper.runInTransaction(agent, function (tx) {
       client.set('saveme', 'foobar', function (error) {
         // Regardless of error, key should still be captured.
         t.error(error)
 
-        const segment = agent.tracer.getSegment().parent
+        const segment = tx.trace.getParent(agent.tracer.getSegment().parentId)
         t.notOk(segment.getAttributes().key, 'should not have `key` attribute')
         t.end()
       })
@@ -260,7 +264,7 @@ test('Redis instrumentation', { timeout: 20000 }, function (t) {
         }
 
         const trace = transaction.trace
-        const setSegment = trace.root.children[0]
+        const [setSegment] = trace.getChildren(trace.root.id)
         const attributes = setSegment.getAttributes()
         t.equal(attributes.host, METRIC_HOST_NAME, 'should have host as attribute')
         t.equal(
@@ -288,7 +292,7 @@ test('Redis instrumentation', { timeout: 20000 }, function (t) {
           return t.end()
         }
 
-        const setSegment = transaction.trace.root.children[0]
+        const [setSegment] = transaction.trace.getChildren(transaction.trace.root.id)
         const attributes = setSegment.getAttributes()
         t.equal(attributes.host, undefined, 'should not have host attribute')
         t.equal(attributes.port_path_or_id, undefined, 'should not have port attribute')
@@ -330,9 +334,13 @@ test('Redis instrumentation', { timeout: 20000 }, function (t) {
     })
 
     function verify() {
-      const setSegment1 = transaction.trace.root.children[0]
-      const selectSegment = setSegment1.children[0].children[0]
-      const setSegment2 = selectSegment.children[0].children[0]
+      const [setSegment1] = transaction.trace.getChildren(transaction.trace.root.id)
+      const [selectSegment] = transaction.trace.getChildren(
+        transaction.trace.getChildren(setSegment1.id)[0].id
+      )
+      const [setSegment2] = transaction.trace.getChildren(
+        transaction.trace.getChildren(selectSegment.id)[0].id
+      )
 
       t.equal(setSegment1.name, 'Datastore/operation/Redis/set', 'should register the first set')
       t.equal(
diff --git a/test/versioned/restify/router.tap.js b/test/versioned/restify/router.tap.js
index 0f80d8d391..5ae13c9e25 100644
--- a/test/versioned/restify/router.tap.js
+++ b/test/versioned/restify/router.tap.js
@@ -54,7 +54,7 @@ tap.test('Restify router', function (t) {
       t.equal(transaction.verb, 'GET', 'HTTP method is GET')
       t.ok(transaction.trace, 'transaction has trace')
 
-      const web = transaction.trace.root.children[0]
+      const [web] = transaction.trace.getChildren(transaction.trace.root.id)
       t.ok(web, 'trace has web segment')
       t.equal(web.name, transaction.name, 'segment name and transaction name match')
       t.equal(web.partialName, 'Restify/GET//test/:id', 'should have partial name for apdex')
diff --git a/test/versioned/superagent/async-await.tap.js b/test/versioned/superagent/async-await.tap.js
index a7811df08e..105d745c14 100644
--- a/test/versioned/superagent/async-await.tap.js
+++ b/test/versioned/superagent/async-await.tap.js
@@ -36,11 +36,12 @@ tap.test('SuperAgent instrumentation with async/await', (t) => {
       const { request } = t.context
       await request.get(address)
 
-      const mainSegment = tx.trace.root.children[0]
+      const [mainSegment] = tx.trace.getChildren(tx.trace.root.id)
       t.ok(mainSegment)
       t.match(mainSegment.name, EXTERNAL_NAME, 'has segment matching request')
+      const mainChildren = tx.trace.getChildren(mainSegment.id)
       t.equal(
-        mainSegment.children.filter((c) => c.name === 'Callback: <anonymous>').length,
+        mainChildren.filter((c) => c.name === 'Callback: <anonymous>').length,
         1,
         'CB created by superagent is present'
       )
diff --git a/test/versioned/superagent/superagent.tap.js b/test/versioned/superagent/superagent.tap.js
index d46df1abde..ba29171d31 100644
--- a/test/versioned/superagent/superagent.tap.js
+++ b/test/versioned/superagent/superagent.tap.js
@@ -35,11 +35,12 @@ tap.test('SuperAgent instrumentation', (t) => {
       request.get(address, function testCallback() {
         t.ok(tx)
 
-        const mainSegment = tx.trace.root.children[0]
+        const [mainSegment] = tx.trace.getChildren(tx.trace.root.id)
         t.ok(mainSegment)
         t.match(mainSegment.name, EXTERNAL_NAME, 'has segment matching request')
+        const mainChildren = tx.trace.getChildren(mainSegment.id)
         t.equal(
-          mainSegment.children.filter((c) => c.name === 'Callback: testCallback').length,
+          mainChildren.filter((c) => c.name === 'Callback: testCallback').length,
           1,
           'has segment matching callback'
         )
@@ -63,11 +64,12 @@ tap.test('SuperAgent instrumentation', (t) => {
       request.get(address).then(function testThen() {
         t.ok(tx)
 
-        const mainSegment = tx.trace.root.children[0]
+        const [mainSegment] = tx.trace.getChildren(tx.trace.root.id)
         t.ok(mainSegment)
         t.match(mainSegment.name, EXTERNAL_NAME, 'has segment matching request')
+        const mainChildren = tx.trace.getChildren(mainSegment.id)
         t.equal(
-          mainSegment.children.filter((c) => c.name === 'Callback: <anonymous>').length,
+          mainChildren.filter((c) => c.name === 'Callback: <anonymous>').length,
           1,
           'has segment matching callback'
         )
diff --git a/test/versioned/undici/requests.tap.js b/test/versioned/undici/requests.tap.js
index ba38bd905e..1b5aa12445 100644
--- a/test/versioned/undici/requests.tap.js
+++ b/test/versioned/undici/requests.tap.js
@@ -93,7 +93,7 @@ tap.test('Undici request tests', (t) => {
       })
       t.equal(statusCode, 200)
 
-      t.assertSegments(tx.trace.root, [`External/${HOST}/post`], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/post`], { exact: false })
       tx.end()
       t.end()
     })
@@ -127,7 +127,7 @@ tap.test('Undici request tests', (t) => {
 
       await client.request({ path: '/', method: 'GET' })
 
-      t.assertSegments(transaction.trace.root, [`External/localhost:${port}/`], {
+      t.assertSegments(transaction.trace, transaction.trace.root, [`External/localhost:${port}/`], {
         exact: false
       })
 
@@ -142,7 +142,7 @@ tap.test('Undici request tests', (t) => {
         method: 'GET'
       })
       t.equal(statusCode, 200)
-      const segment = metrics.findSegment(tx.trace.root, `External/${HOST}/get`)
+      const segment = metrics.findSegment(tx.trace, tx.trace.root, `External/${HOST}/get`)
       const attrs = segment.getAttributes()
       t.equal(attrs.url, `${REQUEST_URL}/get`)
       t.equal(attrs.procedure, 'GET')
@@ -209,7 +209,7 @@ tap.test('Undici request tests', (t) => {
       const [{ statusCode }, { statusCode: statusCode2 }] = await Promise.all([req1, req2])
       t.equal(statusCode, 200)
       t.equal(statusCode2, 200)
-      t.assertSegments(tx.trace.root, [`External/${HOST}/post`, `External/${HOST}/put`], {
+      t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/post`, `External/${HOST}/put`], {
         exact: false
       })
       tx.end()
@@ -226,7 +226,7 @@ tap.test('Undici request tests', (t) => {
         })
       } catch (err) {
         t.ok(err)
-        t.assertSegments(tx.trace.root, ['External/invalidurl/foo'], { exact: false })
+        t.assertSegments(tx.trace, tx.trace.root, ['External/invalidurl/foo'], { exact: false })
         t.equal(tx.exceptions.length, 1)
         tx.end()
         t.end()
@@ -247,7 +247,7 @@ tap.test('Undici request tests', (t) => {
         }, 100)
         await req
       } catch (err) {
-        t.assertSegments(tx.trace.root, [`External/${HOST}/delay/1000`], { exact: false })
+        t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/delay/1000`], { exact: false })
         t.equal(tx.exceptions.length, 1)
         const expectedErrMsg = semver.gte(pkgVersion, '6.3.0')
           ? 'This operation was aborted'
@@ -277,11 +277,16 @@ tap.test('Undici request tests', (t) => {
       try {
         await req
       } catch (error) {
-        t.assertSegments(transaction.trace.root, [`External/localhost:${port}/`], {
-          exact: false
-        })
+        t.assertSegments(
+          transaction.trace,
+          transaction.trace.root,
+          [`External/localhost:${port}/`],
+          {
+            exact: false
+          }
+        )
 
-        const segments = transaction.trace.root.children
+        const segments = transaction.trace.getChildren(transaction.trace.root.id)
         const segment = segments[segments.length - 1]
 
         t.ok(segment.timer.start, 'should have started')
@@ -301,7 +306,7 @@ tap.test('Undici request tests', (t) => {
         method: 'GET'
       })
       t.equal(statusCode, 400)
-      t.assertSegments(tx.trace.root, [`External/${HOST}/status/400`], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/status/400`], { exact: false })
       tx.end()
       t.end()
     })
@@ -311,7 +316,7 @@ tap.test('Undici request tests', (t) => {
     helper.runInTransaction(agent, async (tx) => {
       const res = await undici.fetch(REQUEST_URL)
       t.equal(res.status, 200)
-      t.assertSegments(tx.trace.root, [`External/${HOST}/`], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/`], { exact: false })
       tx.end()
       t.end()
     })
@@ -334,7 +339,7 @@ tap.test('Undici request tests', (t) => {
           })
         }
       )
-      t.assertSegments(tx.trace.root, [`External/${HOST}/get`], { exact: false })
+      t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/get`], { exact: false })
       tx.end()
       t.end()
     })
@@ -370,7 +375,7 @@ tap.test('Undici request tests', (t) => {
         }),
         (err) => {
           t.error(err)
-          t.assertSegments(tx.trace.root, [`External/${HOST}/get`], { exact: false })
+          t.assertSegments(tx.trace, tx.trace.root, [`External/${HOST}/get`], { exact: false })
           tx.end()
           t.end()
         }
diff --git a/test/versioned/when/legacy-promise-segments.js b/test/versioned/when/legacy-promise-segments.js
index 4d957985e9..8558cc2cc7 100644
--- a/test/versioned/when/legacy-promise-segments.js
+++ b/test/versioned/when/legacy-promise-segments.js
@@ -49,9 +49,10 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = true
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'doSomeWork',
         ['Promise startSomeWork', ['Promise#then <anonymous>', ['someChildSegment']]]
       ])
@@ -83,8 +84,9 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = false
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
-      t.assertSegments(tx.trace.root, ['doWork1', ['doWork2', ['secondThen']]])
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
+      t.assertSegments(tx.trace, tx.trace.root, ['doWork1', ['doWork2', ['secondThen']]])
 
       t.end()
     })
@@ -108,9 +110,10 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = true
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'doWork1',
         ['Promise startSomeWork', ['Promise#then firstThen', ['Promise#then secondThen']]]
       ])
@@ -133,9 +136,10 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = true
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, [
+      t.assertSegments(tx.trace, tx.trace.root, [
         'doWork1',
         ['Promise startSomeWork', ['Promise#catch catchHandler']]
       ])
@@ -161,9 +165,10 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = false
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, ['doWork1', ['doWork2', ['catchHandler']]])
+      t.assertSegments(tx.trace, tx.trace.root, ['doWork1', ['doWork2', ['catchHandler']]])
 
       t.end()
     })
@@ -193,9 +198,11 @@ function segmentsEnabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = true
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 2)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 2)
 
       t.assertSegments(
+        tx.trace,
         tx.trace.root,
         ['Promise startSomeWork', ['Promise#then myThen'], 'doSomeWork'],
         true
@@ -234,9 +241,10 @@ function segmentsDisabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = false
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, ['doSomeWork', ['someChildSegment']])
+      t.assertSegments(tx.trace, tx.trace.root, ['doSomeWork', ['someChildSegment']])
 
       t.end()
     })
@@ -262,9 +270,10 @@ function segmentsDisabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = false
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, ['doWork1'])
+      t.assertSegments(tx.trace, tx.trace.root, ['doWork1'])
 
       t.end()
     })
@@ -286,9 +295,10 @@ function segmentsDisabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = false
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, ['doWork1'])
+      t.assertSegments(tx.trace, tx.trace.root, ['doWork1'])
 
       t.end()
     })
@@ -308,9 +318,10 @@ function segmentsDisabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = false
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, ['doWork1'])
+      t.assertSegments(tx.trace, tx.trace.root, ['doWork1'])
 
       t.end()
     })
@@ -330,9 +341,10 @@ function segmentsDisabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = false
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, ['doWork1', ['doWork2']])
+      t.assertSegments(tx.trace, tx.trace.root, ['doWork1', ['doWork2']])
 
       t.end()
     })
@@ -353,9 +365,10 @@ function segmentsDisabledTests(t, agent, Promise, doSomeWork) {
     agent.config.feature_flag.promise_segments = false
 
     agent.once('transactionFinished', function (tx) {
-      t.equal(tx.trace.root.children.length, 1)
+      const children = tx.trace.getChildren(tx.trace.root.id)
+      t.equal(children.length, 1)
 
-      t.assertSegments(tx.trace.root, ['doSomeWork'], true)
+      t.assertSegments(tx.trace, tx.trace.root, ['doSomeWork'], true)
 
       t.end()
     })

From 3fe43457a790c059d307388d0df22bd1da0d2ca7 Mon Sep 17 00:00:00 2001
From: Bob Evans <robert.evans25@gmail.com>
Date: Thu, 31 Oct 2024 14:38:15 -0400
Subject: [PATCH 2/2] chore: added more tests and jsdoc

---
 lib/transaction/trace/index.js                | 19 ++++++++++++
 .../api-start-background-transaction.test.js  | 25 +++++++++++++++
 test/unit/api/api-start-segment.test.js       |  4 +++
 test/unit/transaction/trace/index.test.js     | 31 +++++++++++++++++++
 4 files changed, 79 insertions(+)

diff --git a/lib/transaction/trace/index.js b/lib/transaction/trace/index.js
index 168832efc3..da4c1dceb2 100644
--- a/lib/transaction/trace/index.js
+++ b/lib/transaction/trace/index.js
@@ -339,16 +339,35 @@ Trace.prototype._getRequestUri = function _getRequestUri() {
   return requestUri
 }
 
+/**
+ * Gets all children of a segment.
+ *
+ * @param {number} id of segment
+ * @returns {Array.<TraceSegment>} list of all segments that have the parentId of the segment
+ */
 Trace.prototype.getChildren = function getChildren(id) {
   return this.segments.filter((segment) => segment.parentId === id)
 }
 
+/**
+ * Gets all children of a segment that should be collected and not ignored.
+ *
+ * @param {number} id of segment
+ * @returns {Array.<TraceSegment>} list of all segments that have the parentId of the segment
+ */
 Trace.prototype.getCollectedChildren = function getCollectedChildren(id) {
   return this.segments.filter(
     (segment) => segment.parentId === id && segment._collect && !segment.ignore
   )
 }
 
+/**
+ * Gets the parent segment from list of segments on trace by passing in the `parentId`
+ * and matching on the `segment.id`
+ *
+ * @param {number} parentId id of parent segment you want to retrieve
+ * @returns {TraceSegment} parent segment
+ */
 Trace.prototype.getParent = function getParent(parentId) {
   return this.segments.filter((segment) => segment.id === parentId)[0]
 }
diff --git a/test/unit/api/api-start-background-transaction.test.js b/test/unit/api/api-start-background-transaction.test.js
index fd075f6c32..2479e4fc1e 100644
--- a/test/unit/api/api-start-background-transaction.test.js
+++ b/test/unit/api/api-start-background-transaction.test.js
@@ -182,6 +182,31 @@ test('Agent API - startBackgroundTransaction', async (t) => {
     })
   })
 
+  await t.test('should record metrics', (t, end) => {
+    const { agent, api } = t.nr
+    let transaction
+    api.startBackgroundTransaction('test', function () {
+      transaction = agent.tracer.getTransaction()
+    })
+
+    transaction.end()
+    const metrics = transaction.metrics.unscoped
+    ;[
+      'OtherTransaction/Nodejs/test',
+      'OtherTransactionTotalTime/Nodejs/test',
+      'OtherTransaction/all',
+      'OtherTransactionTotalTime',
+      'OtherTransactionTotalTime',
+      'DurationByCaller/Unknown/Unknown/Unknown/Unknown/all',
+      'DurationByCaller/Unknown/Unknown/Unknown/Unknown/allOther'
+    ].forEach((metric) => {
+      assert.ok(metrics[metric].total, `${metric} has total`)
+      assert.ok(metrics[metric].totalExclusive, `${metric} has totalExclusive`)
+    })
+
+    end()
+  })
+
   await t.test('should not throw when no handler is supplied', (t, end) => {
     const { api } = t.nr
     assert.doesNotThrow(() => api.startBackgroundTransaction('test'))
diff --git a/test/unit/api/api-start-segment.test.js b/test/unit/api/api-start-segment.test.js
index b8efac74b4..503ccf8300 100644
--- a/test/unit/api/api-start-segment.test.js
+++ b/test/unit/api/api-start-segment.test.js
@@ -88,9 +88,13 @@ test('Agent API - startSegment', async (t) => {
 
       const transactionScopedCustomMetric = transactionNameMetric['Custom/foobar']
       assert.ok(transactionScopedCustomMetric)
+      assert.ok(transactionScopedCustomMetric.total)
+      assert.ok(transactionScopedCustomMetric.totalExclusive)
 
       const unscopedCustomMetric = tx.metrics.unscoped['Custom/foobar']
       assert.ok(unscopedCustomMetric)
+      assert.ok(unscopedCustomMetric.total)
+      assert.ok(unscopedCustomMetric.totalExclusive)
 
       end()
     })
diff --git a/test/unit/transaction/trace/index.test.js b/test/unit/transaction/trace/index.test.js
index ed503c5d30..ef98084175 100644
--- a/test/unit/transaction/trace/index.test.js
+++ b/test/unit/transaction/trace/index.test.js
@@ -766,6 +766,37 @@ test('when inserting segments', async (t) => {
       trace.toJSON()
     })
   })
+
+  await t.test('should get all children for a segment', (t) => {
+    const { trace } = t.nr
+    assert.deepEqual(trace.segments, [])
+    const segment = trace.add('base')
+    const segment2 = trace.add('1', null, segment)
+    const segment3 = trace.add('2', null, segment)
+    const children = trace.getChildren(segment.id)
+    assert.deepEqual(children, [segment2, segment3])
+  })
+
+  await t.test('should get all collected children for a segment', (t) => {
+    const { trace } = t.nr
+    const segment = trace.add('base')
+    const segment2 = trace.add('1', null, segment)
+    const segment3 = trace.add('2', null, segment)
+    const segment4 = trace.add('3', null, segment)
+    segment4._collect = false
+    const segment5 = trace.add('4', null, segment)
+    segment5.ignore = true
+    const children = trace.getCollectedChildren(segment.id)
+    assert.deepEqual(children, [segment2, segment3])
+  })
+
+  await t.test('should get parent segment for a segment', (t) => {
+    const { trace } = t.nr
+    const segment = trace.add('base')
+    const segment2 = trace.add('1', null, segment)
+    const parent = trace.getParent(segment2.parentId)
+    assert.equal(parent, segment)
+  })
 })
 
 test('should set URI to null when request.uri attribute is excluded globally', async (t) => {