HTRACE-209. Make span ID 128 bit to avoid collisions (cmccabe)
diff --git a/htrace-c/src/CMakeLists.txt b/htrace-c/src/CMakeLists.txt
index c34a7c2..20a0d51 100644
--- a/htrace-c/src/CMakeLists.txt
+++ b/htrace-c/src/CMakeLists.txt
@@ -75,6 +75,7 @@
     core/htracer.c
     core/scope.c
     core/span.c
+    core/span_id.c
     receiver/hrpc.c
     receiver/htraced.c
     receiver/local_file.c
@@ -198,8 +199,8 @@
     test/span-unit.c
 )
 
-add_utest(span_util-unit
-    test/span_util-unit.c
+add_utest(span_id-unit
+    test/span_id-unit.c
 )
 
 add_utest(string-unit
diff --git a/htrace-c/src/core/htrace.h b/htrace-c/src/core/htrace.h
index e7dc93b..03c02bb 100644
--- a/htrace-c/src/core/htrace.h
+++ b/htrace-c/src/core/htrace.h
@@ -329,16 +329,6 @@
                                              struct htrace_span *span);
 
     /**
-     * Get the span id of an HTrace scope.
-     *
-     * @param scope     The trace scope, or NULL.
-     *
-     * @return          The span ID of the trace span, or 0 if there is no trace
-     *                      span inside the scope, or if NULL was passed.
-     */
-    uint64_t htrace_scope_get_span_id(const struct htrace_scope *scope);
-
-    /**
      * Close a trace scope.
      *
      * This must be called from the same thread that the trace scope was created
diff --git a/htrace-c/src/core/htrace.hpp b/htrace-c/src/core/htrace.hpp
index e4a87cb..0378c57 100644
--- a/htrace-c/src/core/htrace.hpp
+++ b/htrace-c/src/core/htrace.hpp
@@ -221,10 +221,6 @@
       scope_ = NULL;
     }
 
-    uint64_t GetSpanId() {
-      return htrace_scope_get_span_id(scope_);
-    }
-
   private:
     friend class Tracer;
     Scope(htrace::Scope &other); // Can't copy
diff --git a/htrace-c/src/core/scope.c b/htrace-c/src/core/scope.c
index 93008cb..442bf52 100644
--- a/htrace-c/src/core/scope.c
+++ b/htrace-c/src/core/scope.c
@@ -43,7 +43,7 @@
 {
     struct htrace_scope *cur_scope, *scope = NULL, *pscope;
     struct htrace_span *span = NULL;
-    uint64_t span_id;
+    struct htrace_span_id span_id;
 
     // Validate the description string.  This ensures that it doesn't have
     // anything silly in it like embedded double quotes, backslashes, or control
@@ -54,15 +54,16 @@
         return NULL;
     }
     cur_scope = htracer_cur_scope(tracer);
-    if (!cur_scope) {
+    if ((!cur_scope) || (!cur_scope->span)) {
         if (!sampler->ty->next(sampler)) {
             return NULL;
         }
+        htrace_span_id_generate(&span_id, tracer->rnd, NULL);
+    } else {
+        htrace_span_id_generate(&span_id, tracer->rnd,
+                                &cur_scope->span->span_id);
     }
-    do {
-        span_id = random_u64(tracer->rnd);
-    } while (span_id == 0);
-    span = htrace_span_alloc(desc, now_ms(tracer->lg), span_id);
+    span = htrace_span_alloc(desc, now_ms(tracer->lg), &span_id);
     if (!span) {
         htrace_log(tracer->lg, "htrace_span_alloc(desc=%s): OOM\n", desc);
         return NULL;
@@ -112,12 +113,14 @@
                                          struct htrace_span *span)
 {
     struct htrace_scope *cur_scope, *scope = NULL;
+    char buf[HTRACE_SPAN_ID_STRING_LENGTH + 1];
 
     scope = malloc(sizeof(*scope));
     if (!scope) {
+        htrace_span_id_to_str(&span->span_id, buf, sizeof(buf));
+        htrace_log(tracer->lg, "htrace_start_span(desc=%s, parent_id=%s"
+                   "): OOM\n", span->desc, buf);
         htrace_span_free(span);
-        htrace_log(tracer->lg, "htrace_start_span(desc=%s, parent_id=%016"PRIx64
-                   "): OOM\n", span->desc, span->span_id);
         return NULL;
     }
     scope->tracer = tracer;
@@ -132,15 +135,21 @@
     return scope;
 }
 
-uint64_t htrace_scope_get_span_id(const struct htrace_scope *scope)
+void htrace_scope_get_span_id(const struct htrace_scope *scope,
+                              struct htrace_span_id *id)
 {
     struct htrace_span *span;
 
     if (!scope) {
-        return 0;
+        htrace_span_id_clear(id);
+        return;
     }
     span = scope->span;
-    return span ? span->span_id : 0;
+    if (!span) {
+        htrace_span_id_clear(id);
+        return;
+    }
+    htrace_span_id_copy(id, &span->span_id);
 }
 
 void htrace_scope_close(struct htrace_scope *scope)
diff --git a/htrace-c/src/core/scope.h b/htrace-c/src/core/scope.h
index f76cd42..9f00b13 100644
--- a/htrace-c/src/core/scope.h
+++ b/htrace-c/src/core/scope.h
@@ -27,6 +27,8 @@
  * This is an internal header, not intended for external use.
  */
 
+struct htrace_span_id;
+
 #include <stdint.h>
 
 /**
@@ -53,6 +55,17 @@
     struct htrace_span *span;
 };
 
+/**
+ * Get the span id of an HTrace scope.
+ *
+ * @param scope     The trace scope, or NULL.
+ * @param id        (out param) The htrace span ID object to modify.
+ *                      It will be set to the invalid span ID if the scope
+ *                      is null or has no span.
+ */
+void htrace_scope_get_span_id(const struct htrace_scope *scope,
+                              struct htrace_span_id *id);
+
 #endif
 
 // vim: ts=4:sw=4:et
diff --git a/htrace-c/src/core/span.c b/htrace-c/src/core/span.c
index b82f4f1..44f4e6c 100644
--- a/htrace-c/src/core/span.c
+++ b/htrace-c/src/core/span.c
@@ -38,7 +38,7 @@
  */
 
 struct htrace_span *htrace_span_alloc(const char *desc,
-                uint64_t begin_ms, uint64_t span_id)
+                uint64_t begin_ms, struct htrace_span_id *span_id)
 {
     struct htrace_span *span;
 
@@ -53,10 +53,10 @@
     }
     span->begin_ms = begin_ms;
     span->end_ms = 0;
-    span->span_id = span_id;
+    htrace_span_id_copy(&span->span_id, span_id);
     span->trid = NULL;
     span->num_parents = 0;
-    span->parent.single = 0;
+    htrace_span_id_clear(&span->parent.single);
     span->parent.list = NULL;
     return span;
 }
@@ -74,35 +74,26 @@
     free(span);
 }
 
-static int compare_spanids(const void *va, const void *vb)
-{
-    uint64_t a = *((uint64_t*)va);
-    uint64_t b = *((uint64_t*)vb);
-    if (a < b) {
-        return -1;
-    } else if (a > b) {
-        return 1;
-    } else {
-        return 0;
-    }
-}
+typedef int (*qsort_fn_t)(const void *, const void *);
 
 void htrace_span_sort_and_dedupe_parents(struct htrace_span *span)
 {
     int i, j, num_parents = span->num_parents;
-    uint64_t prev;
+    struct htrace_span_id prev;
 
     if (num_parents <= 1) {
         return;
     }
-    qsort(span->parent.list, num_parents, sizeof(uint64_t), compare_spanids);
+    qsort(span->parent.list, num_parents, sizeof(struct htrace_span_id),
+          (qsort_fn_t)htrace_span_id_compare);
     prev = span->parent.list[0];
+    htrace_span_id_copy(&prev, &span->parent.list[0]);
     j = 1;
     for (i = 1; i < num_parents; i++) {
-        uint64_t id = span->parent.list[i];
-        if (id != prev) {
-            span->parent.list[j++] = span->parent.list[i];
-            prev = id;
+        if (htrace_span_id_compare(&prev, span->parent.list + i) != 0) {
+            htrace_span_id_copy(&prev, span->parent.list + i);
+            htrace_span_id_copy(span->parent.list + j, span->parent.list + i);
+            j++;
         }
     }
     span->num_parents = j;
@@ -114,7 +105,8 @@
     } else if (j != num_parents) {
         // After deduplication, there are now fewer entries.  Use realloc to
         // shrink the size of our dynamic allocation if possible.
-        uint64_t *nlist = realloc(span->parent.list, sizeof(uint64_t) * j);
+        struct htrace_span_id *nlist =
+            realloc(span->parent.list, sizeof(struct htrace_span_id) * j);
         if (nlist) {
             span->parent.list = nlist;
         }
@@ -141,13 +133,15 @@
 {
     int num_parents, i, ret = 0;
     const char *prefix = "";
+    char sbuf[HTRACE_SPAN_ID_STRING_LENGTH + 1];
 
     // Note that we have validated the description and process ID strings to
     // make sure they don't contain anything evil.  So we don't need to escape
     // them here.
 
-    ret += fwdprintf(&buf, &max, "{\"s\":\"%016" PRIx64 "\",\"b\":%" PRId64
-                 ",\"e\":%" PRId64",", span->span_id, span->begin_ms,
+    htrace_span_id_to_str(&span->span_id, sbuf, sizeof(sbuf));
+    ret += fwdprintf(&buf, &max, "{\"a\":\"%s\",\"b\":%" PRId64
+                 ",\"e\":%" PRId64",", sbuf, span->begin_ms,
                  span->end_ms);
     if (span->desc[0]) {
         ret += fwdprintf(&buf, &max, "\"d\":\"%s\",", span->desc);
@@ -159,13 +153,13 @@
     if (num_parents == 0) {
         ret += fwdprintf(&buf, &max, "\"p\":[]");
     } else if (num_parents == 1) {
-        ret += fwdprintf(&buf, &max, "\"p\":[\"%016"PRIx64"\"]",
-                         span->parent.single);
+        htrace_span_id_to_str(&span->parent.single, sbuf, sizeof(sbuf));
+        ret += fwdprintf(&buf, &max, "\"p\":[\"%s\"]", sbuf);
     } else if (num_parents > 1) {
         ret += fwdprintf(&buf, &max, "\"p\":[");
         for (i = 0; i < num_parents; i++) {
-            ret += fwdprintf(&buf, &max, "%s\"%016" PRIx64 "\"", prefix,
-                             span->parent.list[i]);
+            htrace_span_id_to_str(span->parent.list + i, sbuf, sizeof(sbuf));
+            ret += fwdprintf(&buf, &max, "%s\"%s\"", prefix, sbuf);
             prefix = ",";
         }
         ret += fwdprintf(&buf, &max, "]");
@@ -205,6 +199,12 @@
     if (!cmp_write_map16(ctx, map_size)) {
         return 0;
     }
+    if (!cmp_write_fixstr(ctx, "a", 1)) {
+        return 0;
+    }
+    if (!htrace_span_id_write_msgpack(&span->span_id, ctx)) {
+        return 0;
+    }
     if (!cmp_write_fixstr(ctx, "d", 1)) {
         return 0;
     }
@@ -223,12 +223,6 @@
     if (!cmp_write_u64(ctx, span->end_ms)) {
         return 0;
     }
-    if (!cmp_write_fixstr(ctx, "s", 1)) {
-        return 0;
-    }
-    if (!cmp_write_u64(ctx, span->span_id)) {
-        return 0;
-    }
     if (span->trid) {
         if (!cmp_write_fixstr(ctx, "r", 1)) {
             return 0;
@@ -245,12 +239,12 @@
             return 0;
         }
         if (num_parents == 1) {
-            if (!cmp_write_u64(ctx, span->parent.single)) {
+            if (!htrace_span_id_write_msgpack(&span->parent.single, ctx)) {
                 return 0;
             }
         } else {
             for (i = 0; i < num_parents; i++) {
-                if (!cmp_write_u64(ctx, span->parent.list[i])) {
+                if (!htrace_span_id_write_msgpack(span->parent.list + i, ctx)) {
                     return 0;
                 }
             }
diff --git a/htrace-c/src/core/span.h b/htrace-c/src/core/span.h
index 4d28e75..d6fbeb5 100644
--- a/htrace-c/src/core/span.h
+++ b/htrace-c/src/core/span.h
@@ -27,6 +27,8 @@
  * This is an internal header, not intended for external use.
  */
 
+#include "core/span_id.h"
+
 #include <stdint.h>
 
 struct cmp_ctx_s;
@@ -52,7 +54,7 @@
     /**
      * The span id.
      */
-    uint64_t span_id;
+    struct htrace_span_id span_id;
 
     /**
      * The tracer ID of this trace scope.
@@ -69,13 +71,13 @@
         /**
          * If there is 1 parent, this is the parent ID.
          */
-        uint64_t single;
+        struct htrace_span_id single;
 
         /**
          * If there are multiple parents, this is a pointer to a dynamically
          * allocated array of parent IDs.
          */
-        uint64_t *list;
+        struct htrace_span_id *list;
     } parent;
 };
 
@@ -89,7 +91,7 @@
  * @return              NULL on OOM; the span otherwise.
  */
 struct htrace_span *htrace_span_alloc(const char *desc,
-                uint64_t begin_ms, uint64_t span_id);
+                uint64_t begin_ms, struct htrace_span_id *span_id);
 
 /**
  * Free the memory associated with an htrace span.
diff --git a/htrace-c/src/core/span_id.c b/htrace-c/src/core/span_id.c
new file mode 100644
index 0000000..7ea23eb
--- /dev/null
+++ b/htrace-c/src/core/span_id.c
@@ -0,0 +1,221 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core/span_id.h"
+#include "util/cmp.h"
+#include "util/log.h"
+#include "util/rand.h"
+#include "util/string.h"
+#include "util/time.h"
+
+#include <errno.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+/**
+ * @file span_id.c
+ *
+ * Implementation of HTrace span IDs.
+ *
+ * Span IDs are 128 bits in total.  The upper 64 bits of a span ID is the same
+ * as the upper 64 bits of the first parent span, if there is one.  The lower 64
+ * bits are always random.
+ */
+
+const struct htrace_span_id INVALID_SPAN_ID;
+
+static uint64_t parse_hex_range(const char *str, int start, int end,
+                                char *err, size_t err_len)
+{
+    char *endptr = NULL;
+    uint64_t ret;
+    char substr[HTRACE_SPAN_ID_STRING_LENGTH + 1];
+
+    err[0] = '\0';
+    if (end - start >= HTRACE_SPAN_ID_STRING_LENGTH) {
+        snprintf(err, err_len, "parse_hex_range buffer too short.");
+        return 0;
+    }
+    memcpy(substr, str + start, end - start);
+    substr[end - start] = '\0';
+    errno = 0;
+    ret = strtoull(substr, &endptr, 16);
+    if (errno) {
+        int e = errno;
+        snprintf(err, err_len, "parse_hex_range error: %s", terror(e));
+        return 0;
+    }
+    while (1) {
+        char c = *endptr;
+        if (c == '\0') {
+            break;
+        }
+        if ((c != ' ') || (c != '\t')) {
+            snprintf(err, err_len, "parse_hex_range error: garbage at end "
+                     "of string.");
+            return 0;
+        }
+        endptr++;
+    }
+    return ret;
+}
+
+void htrace_span_id_parse(struct htrace_span_id *id, const char *str,
+                   char *err, size_t err_len)
+{
+    size_t len;
+
+    err[0] = '\0';
+    len = strlen(str);
+    if (len < HTRACE_SPAN_ID_STRING_LENGTH) {
+        snprintf(err, err_len, "too short: must be %d characters.",
+                 HTRACE_SPAN_ID_STRING_LENGTH);
+        return;
+    }
+    id->high = (parse_hex_range(str, 0, 8, err, err_len) << 32);
+    if (err[0]) {
+        return;
+    }
+    id->high |= (parse_hex_range(str, 8, 16, err, err_len));
+    if (err[0]) {
+        return;
+    }
+    id->low = (parse_hex_range(str, 16, 24, err, err_len) << 32);
+    if (err[0]) {
+        return;
+    }
+    id->low |= (parse_hex_range(str, 24, 32, err, err_len));
+    if (err[0]) {
+        return;
+    }
+}
+
+int htrace_span_id_to_str(const struct htrace_span_id *id,
+                          char *str, size_t len)
+{
+    int res = snprintf(str, len,
+        "%08"PRIx32"%08"PRIx32"%08"PRIx32"%08"PRIx32,
+        (uint32_t)(0xffffffffUL & (id->high >> 32)),
+        (uint32_t)(0xffffffffUL & id->high),
+        (uint32_t)(0xffffffffUL & (id->low >> 32)),
+        (uint32_t)(0xffffffffUL & id->low));
+    return (res == HTRACE_SPAN_ID_STRING_LENGTH);
+}
+
+void htrace_span_id_copy(struct htrace_span_id *dst,
+                         const struct htrace_span_id *src)
+{
+    memmove(dst, src, sizeof(*dst));
+}
+
+int htrace_span_id_write_msgpack(const struct htrace_span_id *id,
+                                 struct cmp_ctx_s *ctx)
+{
+    uint8_t buf[HTRACE_SPAN_ID_NUM_BYTES];
+    buf[0] = (id->high >> 56) & 0xff;
+    buf[1] = (id->high >> 48) & 0xff;
+    buf[2] = (id->high >> 40) & 0xff;
+    buf[3] = (id->high >> 32) & 0xff;
+    buf[4] = (id->high >> 24) & 0xff;
+    buf[5] = (id->high >> 16) & 0xff;
+    buf[6] = (id->high >> 8) & 0xff;
+    buf[7] = (id->high >> 0) & 0xff;
+    buf[8] = (id->low >> 56) & 0xff;
+    buf[9] = (id->low >> 48) & 0xff;
+    buf[10] = (id->low >> 40) & 0xff;
+    buf[11] = (id->low >> 32) & 0xff;
+    buf[12] = (id->low >> 24) & 0xff;
+    buf[13] = (id->low >> 16) & 0xff;
+    buf[14] = (id->low >> 8) & 0xff;
+    buf[15] = (id->low >> 0) & 0xff;
+    return cmp_write_bin(ctx, buf, HTRACE_SPAN_ID_NUM_BYTES);
+}
+
+int htrace_span_id_read_msgpack(struct htrace_span_id *id,
+                                struct cmp_ctx_s *ctx)
+{
+    uint8_t buf[HTRACE_SPAN_ID_NUM_BYTES];
+    uint32_t size = HTRACE_SPAN_ID_NUM_BYTES;
+
+    if (!cmp_read_bin(ctx, buf, &size)) {
+        return 0;
+    }
+    if (size != HTRACE_SPAN_ID_NUM_BYTES) {
+        return 0;
+    }
+    id->high =
+        (((uint64_t)buf[0]) << 56) |
+        (((uint64_t)buf[1]) << 48) |
+        (((uint64_t)buf[2]) << 40) |
+        (((uint64_t)buf[3]) << 32) |
+        (((uint64_t)buf[4]) << 24) |
+        (((uint64_t)buf[5]) << 16) |
+        (((uint64_t)buf[6]) << 8) |
+        (((uint64_t)buf[7]) << 0);
+    id->low =
+        (((uint64_t)buf[8]) << 56) |
+        (((uint64_t)buf[9]) << 48) |
+        (((uint64_t)buf[10]) << 40) |
+        (((uint64_t)buf[11]) << 32) |
+        (((uint64_t)buf[12]) << 24) |
+        (((uint64_t)buf[13]) << 16) |
+        (((uint64_t)buf[14]) << 8) |
+        (((uint64_t)buf[15]) << 0);
+    return 1;
+}
+
+void htrace_span_id_generate(struct htrace_span_id *id, struct random_src *rnd,
+                             const struct htrace_span_id *parent)
+{
+    if (parent) {
+        id->high = parent->high;
+    } else {
+        do {
+            id->high = random_u64(rnd);
+        } while (id->high == 0);
+    }
+    do {
+        id->low = random_u64(rnd);
+    } while (id->low == 0);
+}
+
+void htrace_span_id_clear(struct htrace_span_id *id)
+{
+    memset(id, 0, sizeof(*id));
+}
+
+int htrace_span_id_compare(const struct htrace_span_id *a,
+                           const struct htrace_span_id *b)
+{
+    if (a->high < b->high) {
+        return -1;
+    } else if (a->high > b->high) {
+        return 1;
+    } else if (a->low < b->low) {
+        return -1;
+    } else if (a->low > b->low) {
+        return 1;
+    } else {
+        return 0;
+    }
+}
+
+// vim:ts=4:sw=4:et
diff --git a/htrace-c/src/core/span_id.h b/htrace-c/src/core/span_id.h
new file mode 100644
index 0000000..af2e725
--- /dev/null
+++ b/htrace-c/src/core/span_id.h
@@ -0,0 +1,149 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef APACHE_HTRACE_SPAN_ID_H
+#define APACHE_HTRACE_SPAN_ID_H
+
+/**
+ * @file span_id.h
+ *
+ * Functions related to HTrace span IDs.
+ *
+ * This is an internal header, not intended for external use.
+ */
+
+#include <stdint.h> // for uint64_t
+#include <unistd.h> // for size_t
+
+struct cmp_ctx_s;
+struct random_src;
+
+/**
+ * Length of an HTrace span ID in hexadecimal string form.
+ */
+#define HTRACE_SPAN_ID_STRING_LENGTH 32
+
+/**
+ * The number of bytes in the HTrace span ID
+ */
+#define HTRACE_SPAN_ID_NUM_BYTES 16
+
+/**
+ * The invalid span ID, which is all zeroes.
+ */
+extern const struct htrace_span_id INVALID_SPAN_ID;
+
+/**
+ * The HTrace span id.
+ */
+struct htrace_span_id {
+    uint64_t high;
+    uint64_t low;
+};
+
+/**
+ * Parse a string containing an HTrace span ID.
+ *
+ * @param id            The HTrace span ID to fill in.
+ * @param str           The string to parse.
+ *
+ */
+void htrace_span_id_parse(struct htrace_span_id *id, const char *str,
+                         char *err, size_t err_len);
+
+/**
+ * Write an HTrace span ID to a string.
+ *
+ * @param id            The HTrace span ID.
+ * @param str           Where to put the string.
+ * @param len           The length of the string buffer.
+ * @param err           The error buffer to be set on failure.
+ * @param err_len       Length of the error buffer.
+ *
+ * @return              1 on success; 0 if the length was not long enough, or
+ *                          there was an internal snprintf error.
+ */
+int htrace_span_id_to_str(const struct htrace_span_id *id,
+                          char *str, size_t len);
+
+/**
+ * Copy an htrace span ID.
+ *
+ * dst and src can be the same.
+ *
+ * @param dst           The destination span ID.
+ * @param src           The source span ID.
+ */
+void htrace_span_id_copy(struct htrace_span_id *dst,
+                         const struct htrace_span_id *src);
+
+/**
+ * Write this span ID to the provided CMP context.
+ *
+ * @param span          The span.
+ * @param ctx           The CMP context.
+ *
+ * @return              0 on failure; 1 on success.
+ */
+int htrace_span_id_write_msgpack(const struct htrace_span_id *id,
+                                 struct cmp_ctx_s *ctx);
+
+/**
+ * Read this span ID from the provided CMP context.
+ *
+ * @param span          The span.
+ * @param ctx           The CMP context.
+ *
+ * @return              0 on failure; 1 on success.
+ */
+int htrace_span_id_read_msgpack(struct htrace_span_id *id,
+                                struct cmp_ctx_s *ctx);
+
+/**
+ * Generate a new span ID.
+ *
+ * @param id            The span ID to alter.
+ * @param rnd           The random source.
+ * @param parent        The parent span ID, or null if there is none.
+ */
+void htrace_span_id_generate(struct htrace_span_id *id, struct random_src *rnd,
+                             const struct htrace_span_id *parent);
+
+/**
+ * Set a span ID to the invalid span ID by clearing it.
+ *
+ * @param id            The span ID to clear.
+ */
+void htrace_span_id_clear(struct htrace_span_id *id);
+
+/**
+ * Compare two span IDs.
+ *
+ * @param a             The first span ID.
+ * @param b             The second span ID.
+ *
+ * @return              A number less than 0 if the first span ID is less;
+ *                      A number greater than 0 if the first span ID is greater;
+ *                      0 if the span IDs are equal.
+ */
+int htrace_span_id_compare(const struct htrace_span_id *a,
+                           const struct htrace_span_id *b);
+
+#endif
+
+// vim: ts=4:sw=4:et
diff --git a/htrace-c/src/test/cmp_util-unit.c b/htrace-c/src/test/cmp_util-unit.c
index 4675ca8..d272cae 100644
--- a/htrace-c/src/test/cmp_util-unit.c
+++ b/htrace-c/src/test/cmp_util-unit.c
@@ -39,27 +39,33 @@
     spans[0]->desc = xstrdup("FirstSpan");
     spans[0]->begin_ms = 1927;
     spans[0]->end_ms = 2000;
-    spans[0]->span_id = 1;
+    spans[0]->span_id.high = 0xface;
+    spans[0]->span_id.low = 1;
 
     spans[1] = xcalloc(sizeof(struct htrace_span));
     spans[1]->desc = xstrdup("SecondSpan");
     spans[1]->begin_ms = 1950;
     spans[1]->end_ms = 2000;
-    spans[1]->span_id = 0xffffffffffffffffULL;
+    spans[1]->span_id.high = 0xface;
+    spans[1]->span_id.low = 2;
     spans[1]->trid = xstrdup("SecondSpanProc");
     spans[1]->num_parents = 1;
-    spans[1]->parent.single = 1;
+    spans[1]->parent.single.high = 0xface;
+    spans[1]->parent.single.low = 1;
 
     spans[2] = xcalloc(sizeof(struct htrace_span));
     spans[2]->desc = xstrdup("ThirdSpan");
     spans[2]->begin_ms = 1969;
     spans[2]->end_ms = 1997;
-    spans[2]->span_id = 0xcfcfcfcfcfcfcfcfULL;
+    spans[1]->span_id.high = 0xface;
+    spans[1]->span_id.low = 0xcfcfcfcfcfcfcfcfULL;
     spans[2]->trid = xstrdup("ThirdSpanProc");
     spans[2]->num_parents = 2;
-    spans[2]->parent.list = xcalloc(sizeof(uint64_t) * 2);
-    spans[2]->parent.list[0] = 1;
-    spans[2]->parent.list[1] = 0xffffffffffffffffULL;
+    spans[2]->parent.list = xcalloc(sizeof(struct htrace_span_id) * 2);
+    spans[2]->parent.list[0].high = 0xface;
+    spans[2]->parent.list[0].low = 1;
+    spans[2]->parent.list[1].high = 0xface;
+    spans[2]->parent.list[1].low = 2;
 
     return spans;
 }
diff --git a/htrace-c/src/test/linkage-unit.c b/htrace-c/src/test/linkage-unit.c
index f3c5eba..bba73e1 100644
--- a/htrace-c/src/test/linkage-unit.c
+++ b/htrace-c/src/test/linkage-unit.c
@@ -45,7 +45,6 @@
     "htrace_sampler_to_str",
     "htrace_scope_close",
     "htrace_scope_detach",
-    "htrace_scope_get_span_id",
     "htrace_start_span",
     "htracer_create",
     "htracer_free",
diff --git a/htrace-c/src/test/rtest.c b/htrace-c/src/test/rtest.c
index ccae241..933315d 100644
--- a/htrace-c/src/test/rtest.c
+++ b/htrace-c/src/test/rtest.c
@@ -18,6 +18,7 @@
 
 #include "core/conf.h"
 #include "core/htrace.h"
+#include "core/scope.h"
 #include "core/span.h"
 #include "test/rtest.h"
 #include "test/span_table.h"
@@ -92,12 +93,17 @@
 static int doit(struct rtest_data *rdata)
 {
     struct htrace_scope *scope1, *scope2, *scope2_5;
+    struct htrace_span_id span_id;
 
     scope1 = htrace_start_span(rdata->tracer, NULL, "part1");
-    EXPECT_UINT64_GT(0L, htrace_scope_get_span_id(scope1));
+    htrace_scope_get_span_id(scope1, &span_id);
+    EXPECT_TRUE(0 !=
+        htrace_span_id_compare(&INVALID_SPAN_ID, &span_id));
     htrace_scope_close(scope1);
     scope2 = htrace_start_span(rdata->tracer, NULL, "part2");
-    EXPECT_UINT64_GT(0L, htrace_scope_get_span_id(scope2));
+    htrace_scope_get_span_id(scope2, &span_id);
+    EXPECT_TRUE(0 !=
+        htrace_span_id_compare(&INVALID_SPAN_ID, &span_id));
     scope2_5 = htrace_start_span(rdata->tracer, NULL, "part2.5");
     htrace_scope_close(scope2_5);
     htrace_scope_close(scope2);
@@ -122,27 +128,27 @@
 int rtest_simple_verify(struct rtest *rt, struct span_table *st)
 {
     struct htrace_span *span;
-    uint64_t doit_id, part2_id;
+    struct htrace_span_id doit_id, part2_id;
     char trid[128];
 
     EXPECT_INT_ZERO(rtest_verify_table_size(rt, st));
     get_receiver_test_trid(trid, sizeof(trid));
     EXPECT_INT_ZERO(span_table_get(st, &span, "doit", trid));
-    doit_id = span->span_id;
+    htrace_span_id_copy(&doit_id, &span->span_id);
     EXPECT_INT_ZERO(span->num_parents);
 
     EXPECT_INT_ZERO(span_table_get(st, &span, "part1", trid));
     EXPECT_INT_EQ(1, span->num_parents);
-    EXPECT_UINT64_EQ(doit_id, span->parent.single);
+    EXPECT_TRUE(0 == htrace_span_id_compare(&doit_id, &span->parent.single));
 
     EXPECT_INT_ZERO(span_table_get(st, &span, "part2", trid));
     EXPECT_INT_EQ(1, span->num_parents);
-    part2_id = span->span_id;
-    EXPECT_UINT64_EQ(doit_id, span->parent.single);
+    htrace_span_id_copy(&part2_id, &span->span_id);
+    EXPECT_TRUE(0 == htrace_span_id_compare(&doit_id, &span->parent.single));
 
     EXPECT_INT_ZERO(span_table_get(st, &span, "part2.5", trid));
     EXPECT_INT_EQ(1, span->num_parents);
-    EXPECT_UINT64_EQ(part2_id, span->parent.single);
+    EXPECT_TRUE(0 == htrace_span_id_compare(&part2_id, &span->parent.single));
 
     return EXIT_SUCCESS;
 }
diff --git a/htrace-c/src/test/rtestpp.cc b/htrace-c/src/test/rtestpp.cc
index 0cd7120..a5c2ac8 100644
--- a/htrace-c/src/test/rtestpp.cc
+++ b/htrace-c/src/test/rtestpp.cc
@@ -88,14 +88,11 @@
 {
     {
         htrace::Scope scope1(tdata.tracer_, "part1");
-        EXPECT_UINT64_GT(0L, scope1.GetSpanId());
     }
     {
         htrace::Scope scope2(tdata.tracer_, "part2");
-        EXPECT_UINT64_GT(0L, scope2.GetSpanId());
         {
             htrace::Scope scope2_5(tdata.tracer_, "part2.5");
-            EXPECT_UINT64_GT(0L, scope2_5.GetSpanId());
         }
     }
     return EXIT_SUCCESS;
@@ -115,27 +112,27 @@
 int rtestpp_simple_verify(struct rtest *rt, struct span_table *st)
 {
     struct htrace_span *span;
-    uint64_t doit_id, part2_id;
+    struct htrace_span_id doit_id, part2_id;
     char trid[128];
 
     EXPECT_INT_ZERO(rtest_generic_verify(rt, st));
     get_receiver_test_trid(trid, sizeof(trid));
     EXPECT_INT_ZERO(span_table_get(st, &span, "doit", trid));
-    doit_id = span->span_id;
+    htrace_span_id_copy(&doit_id, &span->span_id);
     EXPECT_INT_ZERO(span->num_parents);
 
     EXPECT_INT_ZERO(span_table_get(st, &span, "part1", trid));
     EXPECT_INT_EQ(1, span->num_parents);
-    EXPECT_UINT64_EQ(doit_id, span->parent.single);
+    EXPECT_TRUE(0 == htrace_span_id_compare(&doit_id, &span->parent.single));
 
     EXPECT_INT_ZERO(span_table_get(st, &span, "part2", trid));
     EXPECT_INT_EQ(1, span->num_parents);
-    part2_id = span->span_id;
-    EXPECT_UINT64_EQ(doit_id, span->parent.single);
+    htrace_span_id_copy(&part2_id, &span->span_id);
+    EXPECT_TRUE(0 == htrace_span_id_compare(&doit_id, &span->parent.single));
 
     EXPECT_INT_ZERO(span_table_get(st, &span, "part2.5", trid));
     EXPECT_INT_EQ(1, span->num_parents);
-    EXPECT_UINT64_EQ(part2_id, span->parent.single);
+    EXPECT_TRUE(0 == htrace_span_id_compare(&part2_id, &span->parent.single));
 
     return EXIT_SUCCESS;
 }
diff --git a/htrace-c/src/test/span-unit.c b/htrace-c/src/test/span-unit.c
index 5482b8d..652c9a7 100644
--- a/htrace-c/src/test/span-unit.c
+++ b/htrace-c/src/test/span-unit.c
@@ -20,153 +20,51 @@
 #include "core/htrace.h"
 #include "core/htracer.h"
 #include "core/span.h"
-#include "sampler/sampler.h"
 #include "test/span_util.h"
 #include "test/test.h"
-#include "util/htable.h"
-#include "util/log.h"
 
-#include <errno.h>
 #include <inttypes.h>
-#include <stdarg.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 
-#define MAX_SPAN_JSON_LEN 100000
-
-static struct htrace_conf *g_test_conf;
-
-static struct htrace_log *g_test_lg;
-
-static struct htracer *g_test_tracer;
-
-static struct htrace_span *create_span(const char *desc,
-        uint64_t begin_ms, uint64_t end_ms, uint64_t span_id,
-        const char *trid, ...) __attribute__((sentinel));
-
-static int test_span_to_json(const char *expected,
-                             struct htrace_span *span)
+static int test_span_round_trip(const char *str)
 {
-    int buf_len;
-    char *buf;
-    struct htrace_span *rspan = NULL;
-    char err[128];
+    char err[512], *json = NULL;
     size_t err_len = sizeof(err);
+    struct htrace_span *span = NULL;
+    int json_size;
 
-    htrace_span_sort_and_dedupe_parents(span);
-    buf_len = span_json_size(span);
-    if ((0 > buf_len) || (buf_len > MAX_SPAN_JSON_LEN)) {
-        fprintf(stderr, "invalid span_json_size %d.\n", buf_len);
-        return EXIT_FAILURE;
-    }
-    buf = malloc(buf_len);
-    EXPECT_NONNULL(buf);
-    span_json_sprintf(span, buf_len, buf);
-    EXPECT_STR_EQ(expected, buf);
-    span_json_parse(buf, &rspan, err, err_len);
-    if (err[0]) {
-        fprintf(stderr, "Failed to parse span json %s: %s\n", buf, err);
-        return EXIT_FAILURE;
-    }
-    EXPECT_NONNULL(rspan);
-    if (span_compare(span, rspan) != 0) {
-        htrace_span_free(rspan);
-        fprintf(stderr, "Failed to parse the span json back into a span "
-                "which was identical to the input.  JSON: %s\n", buf);
-        return EXIT_FAILURE;
-    }
-    free(buf);
-    htrace_span_free(rspan);
-    return EXIT_SUCCESS;
-}
-
-static struct htrace_span *create_span(const char *desc,
-        uint64_t begin_ms, uint64_t end_ms, uint64_t span_id,
-        const char *trid, ...)
-{
-    struct htrace_span* span = NULL;
-    uint64_t *parents, parent;
-    int i, num_parents = 0;
-    va_list ap, ap2;
-
-    va_start(ap, trid);
-    va_copy(ap2, ap);
-    while (1) {
-        parent = va_arg(ap2, uint64_t);
-        if (!parent) {
-            break;
-        }
-        num_parents++;
-    } while (parent);
-    va_end(ap2);
-    if (num_parents > 0) {
-        parents = xcalloc(sizeof(uint64_t) * num_parents);
-        for (i = 0; i < num_parents; i++) {
-            parents[i] = va_arg(ap, uint64_t);
-        }
-    }
-    va_end(ap);
-    span = htrace_span_alloc(desc, begin_ms, span_id);
-    span->end_ms = end_ms;
-    span->span_id = span_id;
-    span->trid = xstrdup(trid);
-    span->num_parents = num_parents;
-    if (num_parents == 1) {
-        span->parent.single = parents[0];
-        free(parents);
-    } else if (num_parents > 1) {
-        span->parent.list = parents;
-    }
-    return span;
-}
-
-static int test_spans_to_str(void)
-{
-    struct htrace_span *span;
-
-    span = create_span("foo", 123LLU, 456LLU, 789LLU, "span-unit",
-                        NULL);
-    EXPECT_INT_ZERO(test_span_to_json(
-        "{\"s\":\"0000000000000315\",\"b\":123,\"e\":456,"
-            "\"d\":\"foo\",\"r\":\"span-unit\""
-            ",\"p\":[]}", span));
+    err[0] = '\0';
+    span_json_parse(str, &span, err, err_len);
+    EXPECT_STR_EQ("", err);
+    json_size = span_json_size(span);
+    json = malloc(json_size);
+    EXPECT_NONNULL(json);
+    span_json_sprintf(span, json_size, json);
+    EXPECT_STR_EQ(str, json);
+    free(json);
     htrace_span_free(span);
 
-    span = create_span("myspan", 34359738368LLU,
-                        34359739368LLU, 68719476736LLU, "span-unit2",
-                        1LLU, 2LLU, 3LLU, NULL);
-    EXPECT_INT_ZERO(test_span_to_json(
-        "{\"s\":\"0000001000000000\",\"b\":34359738368,\"e\":34359739368,"
-        "\"d\":\"myspan\",\"r\":\"span-unit2\"," "\"p\":[\"0000000000000001\","
-        "\"0000000000000002\",\"0000000000000003\"]}", span));
-    htrace_span_free(span);
-
-    span = create_span("nextSpan", 14359739368LLU, 18719476736LLU,
-                       0x8000001000000000LLU, "span-unit3",
-                        1LLU, 1LLU, 1LLU, NULL);
-    EXPECT_INT_ZERO(test_span_to_json(
-        "{\"s\":\"8000001000000000\",\"b\":14359739368,\"e\":18719476736,"
-        "\"d\":\"nextSpan\",\"r\":\"span-unit3\"," "\"p\":[\"0000000000000001\"]"
-        "}", span));
-    htrace_span_free(span);
-    return EXIT_SUCCESS;
+    return 0;
 }
 
 int main(void)
 {
-    g_test_conf = htrace_conf_from_strs("", HTRACE_TRACER_ID"=span-unit");
-    EXPECT_NONNULL(g_test_conf);
-    g_test_lg = htrace_log_alloc(g_test_conf);
-    EXPECT_NONNULL(g_test_lg);
-    g_test_tracer = htracer_create("span-unit", g_test_conf);
-    EXPECT_NONNULL(g_test_tracer);
-
-    EXPECT_INT_ZERO(test_spans_to_str());
-
-    htracer_free(g_test_tracer);
-    htrace_log_free(g_test_lg);
-    htrace_conf_free(g_test_conf);
+    EXPECT_INT_ZERO(test_span_round_trip(
+        "{\"a\":\"ba85631c2ce111e5b345feff819cdc9f\",\"b\":34359738368,"
+        "\"e\":34359739368,\"d\":\"myspan\",\"r\":\"span-unit2\","
+        "\"p\":[\"1549e8d42ce411e5b345feff819cdc9f\","
+        "\"1b6a1d242ce411e5b345feff819cdc9f\","
+        "\"25ab73822ce411e5b345feff819cdc9f\"]}"));
+    EXPECT_INT_ZERO(test_span_round_trip(
+        "{\"a\":\"000000002ce111e5b345feff819cdc9f\",\"b\":0,"
+        "\"e\":0,\"d\":\"secondSpan\",\"r\":\"other-tracerid\","
+        "\"p\":[]}"));
+    EXPECT_INT_ZERO(test_span_round_trip(
+        "{\"a\":\"6baba3842ce411e5b345feff819cdc9f\",\"b\":999,"
+        "\"e\":1000,\"d\":\"thirdSpan\",\"r\":\"other-tracerid\","
+        "\"p\":[\"000000002ce111e5b345feff819cdc9f\"]}"));
     return EXIT_SUCCESS;
 }
 
diff --git a/htrace-c/src/test/span_id-unit.c b/htrace-c/src/test/span_id-unit.c
new file mode 100644
index 0000000..06c0320
--- /dev/null
+++ b/htrace-c/src/test/span_id-unit.c
@@ -0,0 +1,104 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "core/span_id.h"
+#include "test/span_util.h"
+#include "test/test.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static int test_span_id_round_trip(const char *str)
+{
+    struct htrace_span_id id;
+    char err[512], str2[HTRACE_SPAN_ID_STRING_LENGTH + 1];
+    size_t err_len = sizeof(err);
+
+    err[0] = '\0';
+    htrace_span_id_parse(&id, str, err, err_len);
+    EXPECT_STR_EQ("", err);
+    EXPECT_INT_EQ(1, htrace_span_id_to_str(&id, str2, sizeof(str2)));
+    EXPECT_STR_EQ(str, str2);
+    return 0;
+}
+
+static int test_span_id_compare(int isLess,
+                                const char *sa, const char *sb)
+{
+    struct htrace_span_id a, b;
+    char err[512];
+    size_t err_len = sizeof(err);
+    int cmp;
+
+    err[0] = '\0';
+
+    htrace_span_id_parse(&a, sa, err, err_len);
+    EXPECT_STR_EQ("", err);
+
+    htrace_span_id_parse(&b, sb, err, err_len);
+    EXPECT_STR_EQ("", err);
+
+    cmp = htrace_span_id_compare(&a, &b);
+    if (isLess) {
+        EXPECT_INT_GT(cmp, 0);
+    } else {
+        EXPECT_INT_ZERO(cmp);
+    }
+    cmp = htrace_span_id_compare(&b, &a);
+    if (isLess) {
+        EXPECT_INT_GT(0, cmp);
+    } else {
+        EXPECT_INT_ZERO(cmp);
+    }
+    return 0;
+}
+
+static int test_span_id_less(const char *sa, const char *sb)
+{
+    return test_span_id_compare(1, sa, sb);
+}
+
+static int test_span_id_eq(const char *sa, const char *sb)
+{
+    return test_span_id_compare(0, sa, sb);
+}
+
+int main(void)
+{
+    EXPECT_INT_ZERO(test_span_id_round_trip("0123456789abcdef0011223344556677"));
+    EXPECT_INT_ZERO(test_span_id_round_trip("a919f3d62ce111e5b345feff819cdc9f"));
+    EXPECT_INT_ZERO(test_span_id_round_trip("00000000000000000000000000000000"));
+    EXPECT_INT_ZERO(test_span_id_round_trip("ba85631c2ce111e5b345feff819cdc9f"));
+    EXPECT_INT_ZERO(test_span_id_round_trip("ffffffffffffffffffffffffffffffff"));
+    EXPECT_INT_ZERO(test_span_id_round_trip("ba85631c2ce111e5b345feff819cdc9f"));
+
+    EXPECT_INT_ZERO(test_span_id_less("a919f3d62ce111e5b345feff819cdc9e",
+                                      "a919f3d62ce111e5b345feff819cdc9f"));
+    EXPECT_INT_ZERO(test_span_id_eq("a919f3d62ce111e5b345feff819cdc9f",
+                                    "a919f3d62ce111e5b345feff819cdc9f"));
+    EXPECT_INT_ZERO(test_span_id_eq("ffffffff2ce111e5b345feff819cdc9f",
+                                    "ffffffff2ce111e5b345feff819cdc9f"));
+    EXPECT_INT_ZERO(test_span_id_less("1919f3d62ce111e5b345feff819cdc9f",
+                                      "f919f3d62ce111e5b345feff81900000"));
+    return EXIT_SUCCESS;
+}
+
+// vim: ts=4:sw=4:tw=79:et
diff --git a/htrace-c/src/test/span_table.c b/htrace-c/src/test/span_table.c
index 42ed5f6..b2b23fd 100644
--- a/htrace-c/src/test/span_table.c
+++ b/htrace-c/src/test/span_table.c
@@ -50,7 +50,8 @@
     EXPECT_NONNULL(span);
     EXPECT_STR_EQ(desc, span->desc);
     EXPECT_UINT64_GE(span->begin_ms, span->end_ms);
-    EXPECT_UINT64_GT(0L, span->span_id);
+    EXPECT_TRUE(0 !=
+        htrace_span_id_compare(&INVALID_SPAN_ID, &span->span_id));
     EXPECT_NONNULL(span->trid);
     EXPECT_STR_EQ(trid, span->trid);
     *out = span;
diff --git a/htrace-c/src/test/span_util-unit.c b/htrace-c/src/test/span_util-unit.c
deleted file mode 100644
index fc2a56c..0000000
--- a/htrace-c/src/test/span_util-unit.c
+++ /dev/null
@@ -1,74 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "test/span_util.h"
-#include "test/test.h"
-
-#include <inttypes.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-static int test_parse_hex_id_error(const char *in)
-{
-    char err[128];
-
-    err[0] = '\0';
-    parse_hex_id(in, err, sizeof(err));
-    if (!err[0]) {
-        fprintf(stderr, "test_parse_hex_id_error(%s): expected error, but "
-                "was successful.\n", in);
-        return EXIT_FAILURE;
-    }
-    return EXIT_SUCCESS;
-}
-
-static int test_parse_hex_id(uint64_t expected, const char *in)
-{
-    char err[128];
-    size_t err_len = sizeof(err);
-    uint64_t val;
-
-    err[0] = '\0';
-    val = parse_hex_id(in, err, err_len);
-    if (err[0]) {
-        fprintf(stderr, "test_parse_hex_id(%s): got error %s\n",
-                in, err);
-        return EXIT_FAILURE;
-    }
-    EXPECT_UINT64_EQ(expected, val);
-    return EXIT_SUCCESS;
-}
-
-int main(void)
-{
-    EXPECT_INT_ZERO(test_parse_hex_id_error(""));
-    EXPECT_INT_ZERO(test_parse_hex_id_error("z"));
-    EXPECT_INT_ZERO(test_parse_hex_id_error("achoo"));
-    EXPECT_INT_ZERO(test_parse_hex_id(1LLU, "00000000000000001"));
-    EXPECT_INT_ZERO(test_parse_hex_id(0xffffffffffffffffLLU,
-                                       "ffffffffffffffff"));
-    EXPECT_INT_ZERO(test_parse_hex_id(0x8000000000000000LLU,
-                                       "8000000000000000"));
-    EXPECT_INT_ZERO(test_parse_hex_id(0x6297421fe159345fLLU,
-                                       "6297421fe159345f"));
-    EXPECT_INT_ZERO(test_parse_hex_id_error("6297421fe159345fzoo"));
-    return EXIT_SUCCESS;
-}
-
-// vim: ts=4:sw=4:tw=79:et
diff --git a/htrace-c/src/test/span_util.c b/htrace-c/src/test/span_util.c
index 625ba86..04490a9 100644
--- a/htrace-c/src/test/span_util.c
+++ b/htrace-c/src/test/span_util.c
@@ -29,46 +29,15 @@
 #include <stdlib.h>
 #include <string.h>
 
-uint64_t parse_hex_id(const char *in, char *err, size_t err_len)
-{
-    char *endptr;
-    unsigned long long int ret;
-
-    err[0] = '\0';
-    errno = 0;
-    ret = strtoull(in, &endptr, 16);
-    if (errno) {
-        int e = errno;
-        snprintf(err, err_len, "parse_hex_id(%s) failed: error %s",
-                 in, terror(e));
-        return 0;
-    }
-    if (endptr == in) {
-        snprintf(err, err_len, "parse_hex_id(%s) failed: empty string "
-                 "found.", in);
-        return 0;
-    }
-    while (1) {
-        char c = *endptr++;
-        if (c == '\0') {
-            break;
-        }
-        if ((c != ' ') || (c != '\t')) {
-            snprintf(err, err_len, "parse_hex_id(%s) failed: garbage at end "
-                     "of string.", in);
-            return 0;
-        }
-    }
-    return ret;
-}
-
 static void span_json_parse_parents(struct json_object *root,
                     struct htrace_span *span, char *err, size_t err_len)
 {
     char err2[128];
+    size_t err2_len = sizeof(err2);
     struct json_object *p = NULL, *e = NULL;
     int i, np;
 
+    err2[0] = '\0';
     if (!json_object_object_get_ex(root, "p", &p)) {
         return; // no parents
     }
@@ -80,14 +49,14 @@
     if (np == 1) {
         span->num_parents = 1;
         e = json_object_array_get_idx(p, 0);
-        span->parent.single = parse_hex_id(json_object_get_string(e),
-                                            err2, sizeof(err2));
+        htrace_span_id_parse(&span->parent.single,
+                             json_object_get_string(e), err2, err2_len);
         if (err2[0]) {
             snprintf(err, err_len, "failed to parse parent ID 1/1: %s.", err2);
             return;
         }
     } else if (np > 1) {
-        span->parent.list = malloc(sizeof(uint64_t) * np);
+        span->parent.list = malloc(sizeof(struct htrace_span_id) * np);
         if (!span->parent.list) {
             snprintf(err, err_len, "failed to allocate parent ID array of "
                      "%d elements", np);
@@ -96,8 +65,8 @@
         span->num_parents = np;
         for (i = 0; i < np; i++) {
             e = json_object_array_get_idx(p, i);
-            span->parent.list[i] = parse_hex_id(json_object_get_string(e),
-                                            err2, sizeof(err2));
+            htrace_span_id_parse(span->parent.list + i,
+                                 json_object_get_string(e), err2, err2_len);
             if (err2[0]) {
                 snprintf(err, err_len, "failed to parse parent ID %d/%d: %s",
                          i + 1, np, err2);
@@ -115,6 +84,7 @@
     int res;
 
     err[0] = '\0';
+    err2[0] = '\0';
     if (!json_object_object_get_ex(root, "d", &d)) {
         d = NULL;
     }
@@ -141,8 +111,8 @@
             return;
         }
     }
-    if (json_object_object_get_ex(root, "s", &s)) {
-        span->span_id = parse_hex_id(json_object_get_string(s),
+    if (json_object_object_get_ex(root, "a", &s)) {
+        htrace_span_id_parse(&span->span_id, json_object_get_string(s),
                                      err2, sizeof(err2));
         if (err2[0]) {
             snprintf(err, err_len, "error parsing span_id: %s", err2);
@@ -226,7 +196,7 @@
 
 static int compare_parents(struct htrace_span *a, struct htrace_span *b)
 {
-    int na, nb, i;
+    int na, nb, i, cmp;
 
     htrace_span_sort_and_dedupe_parents(a);
     na = a->num_parents;
@@ -234,7 +204,7 @@
     nb = b->num_parents;
 
     for (i = 0; ; i++) {
-        uint64_t sa, sb;
+        struct htrace_span_id sa, sb;
 
         if (i >= na) {
             if (i >= nb) {
@@ -246,21 +216,18 @@
             return 1;
         }
         if ((i == 0) && (na == 1)) {
-            sa = a->parent.single;
+            htrace_span_id_copy(&sa, &a->parent.single);
         } else {
-            sa = a->parent.list[i];
+            htrace_span_id_copy(&sa, a->parent.list + i);
         }
         if ((i == 0) && (nb == 1)) {
-            sb = b->parent.single;
+            htrace_span_id_copy(&sb, &b->parent.single);
         } else {
-            sb = b->parent.list[i];
+            htrace_span_id_copy(&sb, b->parent.list + i);
         }
-        // Use explicit comparison rather than subtraction to avoid numeric
-        // overflow issues.
-        if (sa < sb) {
-            return -1;
-        } else if (sa > sb) {
-            return 1;
+        cmp = htrace_span_id_compare(&sa, &sb);
+        if (cmp) {
+            return cmp;
         }
     }
 }
@@ -269,7 +236,7 @@
 {
     int c;
 
-    c = uint64_cmp(a->span_id, b->span_id);
+    c = htrace_span_id_compare(&a->span_id, &b->span_id);
     if (c) {
         return c;
     }
@@ -355,7 +322,7 @@
         free(span->parent.list);
         span->parent.list = NULL;
     }
-    span->parent.single = 0;
+    htrace_span_id_clear(&span->parent.single);
     span->num_parents = 0;
     if (!cmp_read_array(ctx, &size)) {
         snprintf(err, err_len, "span_parse_msgpack_parents: cmp_read_array "
@@ -363,22 +330,22 @@
         return;
     }
     if (size == 1) {
-        if (!cmp_read_u64(ctx, &span->parent.single)) {
+        if (!htrace_span_id_read_msgpack(&span->parent.single, ctx)) {
             snprintf(err, err_len, "span_parse_msgpack_parents: cmp_read_u64 "
                      "for single child ID failed");
             return;
         }
     } else if (size > 1) {
-        span->parent.list = malloc(sizeof(uint64_t) * size);
+        span->parent.list = malloc(sizeof(struct htrace_span_id) * size);
         if (!span->parent.list) {
             snprintf(err, err_len, "span_parse_msgpack_parents: failed to "
                      "malloc %"PRId32"-entry parent array.", size);
             return;
         }
         for (i = 0; i < size; i++) {
-            if (!cmp_read_u64(ctx, &span->parent.list[i])) {
-                snprintf(err, err_len, "span_parse_msgpack_parents: cmp_read_u64 "
-                         "for child %d ID failed", i);
+            if (!htrace_span_id_read_msgpack(span->parent.list + i, ctx)) {
+                snprintf(err, err_len, "span_parse_msgpack_parents: "
+                    "htrace_span_id_read_msgpack for child %d ID failed", i);
                 free(span->parent.list);
                 span->parent.list = NULL;
                 return;
@@ -412,6 +379,13 @@
             goto error;
         }
         switch (key[0]) {
+        case 'a':
+            if (!htrace_span_id_read_msgpack(&span->span_id, ctx)) {
+                snprintf(err, err_len, "span_read_msgpack: "
+                    "htrace_span_id_read_msgpack failed for span->span_id");
+                goto error;
+            }
+            break;
         case 'd':
             if (span->desc) {
                 free(span->desc);
@@ -436,13 +410,6 @@
                 goto error;
             }
             break;
-        case 's':
-            if (!cmp_read_u64(ctx, &span->span_id)) {
-                snprintf(err, err_len, "span_read_msgpack: cmp_read_u64 "
-                         "failed for span->span_id");
-                goto error;
-            }
-            break;
         case 'r':
             if (span->trid) {
                 free(span->trid);
diff --git a/htrace-core/src/main/java/org/apache/htrace/Span.java b/htrace-core/src/main/java/org/apache/htrace/Span.java
index f41da30..0897ee9 100644
--- a/htrace-core/src/main/java/org/apache/htrace/Span.java
+++ b/htrace-core/src/main/java/org/apache/htrace/Span.java
@@ -75,13 +75,7 @@
    * The spanId is immutable and cannot be changed.  It is safe to access this
    * from multiple threads.
    */
-  long getSpanId();
-
-  /**
-   * A pseudo-unique (random) number assigned to the trace associated with this
-   * span
-   */
-  long getTraceId();
+  SpanId getSpanId();
 
   /**
    * Create a child span of this span with the given description
@@ -96,14 +90,14 @@
    *
    * The array will be empty if there are no parents.
    */
-  long[] getParents();
+  SpanId[] getParents();
 
   /**
    * Set the parents of this span.<p/>
    *
    * Any existing parents will be cleared by this call.
    */
-  void setParents(long[] parents);
+  void setParents(SpanId[] parents);
 
   /**
    * Add a data annotation associated with this span
@@ -151,11 +145,8 @@
     public void serialize(Span span, JsonGenerator jgen, SerializerProvider provider)
         throws IOException {
       jgen.writeStartObject();
-      if (span.getTraceId() != 0) {
-        jgen.writeStringField("i", String.format("%016x", span.getTraceId()));
-      }
-      if (span.getSpanId() != 0) {
-        jgen.writeStringField("s", String.format("%016x", span.getSpanId()));
+      if (span.getSpanId().isValid()) {
+        jgen.writeStringField("a", span.getSpanId().toString());
       }
       if (span.getStartTimeMillis() != 0) {
         jgen.writeNumberField("b", span.getStartTimeMillis());
@@ -171,8 +162,8 @@
         jgen.writeStringField("r", tracerId);
       }
       jgen.writeArrayFieldStart("p");
-      for (long parent : span.getParents()) {
-        jgen.writeString(String.format("%016x", parent));
+      for (SpanId parent : span.getParents()) {
+        jgen.writeString(parent.toString());
       }
       jgen.writeEndArray();
       Map<String, String> traceInfoMap = span.getKVAnnotations();
diff --git a/htrace-core/src/main/java/org/apache/htrace/SpanId.java b/htrace-core/src/main/java/org/apache/htrace/SpanId.java
new file mode 100644
index 0000000..25dc108
--- /dev/null
+++ b/htrace-core/src/main/java/org/apache/htrace/SpanId.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.htrace;
+
+import java.math.BigInteger;
+import java.lang.Void;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.Random;
+
+/**
+ * Uniquely identifies an HTrace span.
+ *
+ * Span IDs are 128 bits in total.  The upper 64 bits of a span ID is the same
+ * as the upper 64 bits of the parent span, if there is one.  The lower 64 bits
+ * are always random.
+ */
+public final class SpanId implements Comparable<SpanId> {
+  private static final int SPAN_ID_STRING_LENGTH = 32;
+  private final long high;
+  private final long low;
+
+  /**
+   * The invalid span ID, which is all zeroes.
+   *
+   * It is also the "least" span ID in the sense that it is considered
+   * smaller than any other span ID.
+   */
+  public static SpanId INVALID = new SpanId(0, 0);
+
+  private static long nonZeroRand64() {
+    while (true) {
+      long r = ThreadLocalRandom.current().nextLong();
+      if (r != 0) {
+        return r;
+      }
+    }
+  }
+
+  public static SpanId fromRandom() {
+    return new SpanId(nonZeroRand64(), nonZeroRand64());
+  }
+
+  public static SpanId fromString(String str) {
+    if (str.length() != SPAN_ID_STRING_LENGTH) {
+      throw new RuntimeException("Invalid SpanID string: length was not " +
+          SPAN_ID_STRING_LENGTH);
+    }
+    long high =
+      ((Long.parseLong(str.substring(0, 8), 16)) << 32) |
+      (Long.parseLong(str.substring(8, 16), 16));
+    long low =
+      ((Long.parseLong(str.substring(16, 24), 16)) << 32) |
+      (Long.parseLong(str.substring(24, 32), 16));
+    return new SpanId(high, low);
+  }
+
+  public SpanId(long high, long low) {
+    this.high = high;
+    this.low = low;
+  }
+
+  public long getHigh() {
+    return high;
+  }
+
+  public long getLow() {
+    return low;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (!(o instanceof SpanId)) {
+      return false;
+    }
+    SpanId other = (SpanId)o;
+    return ((other.high == high) && (other.low == low));
+  }
+
+  @Override
+  public int compareTo(SpanId other) {
+    int cmp = compareAsUnsigned(high, other.high);
+    if (cmp != 0) {
+      return cmp;
+    }
+    return compareAsUnsigned(low, other.low);
+  }
+
+  private static int compareAsUnsigned(long a, long b) {
+    boolean aSign = a < 0;
+    boolean bSign = b < 0;
+    if (aSign != bSign) {
+      if (aSign) {
+        return 1;
+      } else {
+        return -1;
+      }
+    }
+    if (aSign) {
+      a = -a;
+      b = -b;
+    }
+    if (a < b) {
+      return -1;
+    } else if (a > b) {
+      return 1;
+    } else {
+      return 0;
+    }
+  }
+
+  @Override
+  public int hashCode() {
+    return (int)((0xffffffff & (high >> 32))) ^
+           (int)((0xffffffff & (high >> 0))) ^
+           (int)((0xffffffff & (low >> 32))) ^
+           (int)((0xffffffff & (low >> 0)));
+  }
+
+  @Override
+  public String toString() {
+    return String.format("%08x%08x%08x%08x",
+        (0x00000000ffffffffL & (high >> 32)),
+        (0x00000000ffffffffL & high),
+        (0x00000000ffffffffL & (low >> 32)),
+        (0x00000000ffffffffL & low));
+  }
+
+  public boolean isValid() {
+    return (high != 0)  || (low != 0);
+  }
+
+  public SpanId newChildId() {
+    return new SpanId(high, nonZeroRand64());
+  }
+}
diff --git a/htrace-core/src/main/java/org/apache/htrace/Trace.java b/htrace-core/src/main/java/org/apache/htrace/Trace.java
index b9def45..e782309 100644
--- a/htrace-core/src/main/java/org/apache/htrace/Trace.java
+++ b/htrace-core/src/main/java/org/apache/htrace/Trace.java
@@ -75,15 +75,16 @@
     return startSpan(description, NeverSampler.INSTANCE);
   }
 
-  public static TraceScope startSpan(String description, TraceInfo tinfo) {
-    if (tinfo == null) return continueSpan(null);
+  public static TraceScope startSpan(String description, SpanId parentId) {
+    if (parentId == null) {
+      return continueSpan(null);
+    }
     Span newSpan = new MilliSpan.Builder().
         begin(System.currentTimeMillis()).
         end(0).
         description(description).
-        traceId(tinfo.traceId).
-        spanId(Tracer.nonZeroRandom64()).
-        parents(new long[] { tinfo.spanId }).
+        spanId(parentId.newChildId()).
+        parents(new SpanId[] { parentId }).
         build();
     return continueSpan(newSpan);
   }
diff --git a/htrace-core/src/main/java/org/apache/htrace/TraceInfo.java b/htrace-core/src/main/java/org/apache/htrace/TraceInfo.java
deleted file mode 100644
index 9e7d74a..0000000
--- a/htrace-core/src/main/java/org/apache/htrace/TraceInfo.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.htrace;
-
-
-public class TraceInfo {
-  public final long traceId;
-  public final long spanId;
-
-  public TraceInfo(long traceId, long spanId) {
-    this.traceId = traceId;
-    this.spanId = spanId;
-  }
-
-  @Override
-  public String toString() {
-    return "TraceInfo(traceId=" + traceId + ", spanId=" + spanId + ")";
-  }
-
-  public static TraceInfo fromSpan(Span s) {
-    if (s == null) return null;
-    return new TraceInfo(s.getTraceId(), s.getSpanId());
-  }
-}
diff --git a/htrace-core/src/main/java/org/apache/htrace/Tracer.java b/htrace-core/src/main/java/org/apache/htrace/Tracer.java
index b0ed451..d07e1a8 100644
--- a/htrace-core/src/main/java/org/apache/htrace/Tracer.java
+++ b/htrace-core/src/main/java/org/apache/htrace/Tracer.java
@@ -48,8 +48,7 @@
       return null;
     }
   };
-  public static final TraceInfo DONT_TRACE = new TraceInfo(-1, -1);
-  private static final long EMPTY_PARENT_ARRAY[] = new long[0];
+  private static final SpanId EMPTY_PARENT_ARRAY[] = new SpanId[0];
 
   /**
    * Log a client error, and throw an exception.
@@ -81,9 +80,8 @@
           begin(System.currentTimeMillis()).
           end(0).
           description(description).
-          traceId(nonZeroRandom64()).
           parents(EMPTY_PARENT_ARRAY).
-          spanId(nonZeroRandom64()).
+          spanId(SpanId.fromRandom()).
           build();
     } else {
       return parent.child(description);
diff --git a/htrace-core/src/main/java/org/apache/htrace/impl/MilliSpan.java b/htrace-core/src/main/java/org/apache/htrace/impl/MilliSpan.java
index 3f6e700..9d49cf9 100644
--- a/htrace-core/src/main/java/org/apache/htrace/impl/MilliSpan.java
+++ b/htrace-core/src/main/java/org/apache/htrace/impl/MilliSpan.java
@@ -26,6 +26,7 @@
 import com.fasterxml.jackson.databind.ObjectWriter;
 import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
 import org.apache.htrace.Span;
+import org.apache.htrace.SpanId;
 import org.apache.htrace.TimelineAnnotation;
 import org.apache.htrace.Tracer;
 
@@ -40,8 +41,6 @@
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
-import java.util.Random;
-import java.util.concurrent.ThreadLocalRandom;
 
 /**
  * A Span implementation that stores its information in milliseconds since the
@@ -52,37 +51,26 @@
   private static ObjectMapper OBJECT_MAPPER = new ObjectMapper();
   private static ObjectReader JSON_READER = OBJECT_MAPPER.reader(MilliSpan.class);
   private static ObjectWriter JSON_WRITER = OBJECT_MAPPER.writer();
-  private static final long EMPTY_PARENT_ARRAY[] = new long[0];
+  private static final SpanId EMPTY_PARENT_ARRAY[] = new SpanId[0];
   private static final String EMPTY_STRING = "";
 
   private long begin;
   private long end;
   private final String description;
-  private final long traceId;
-  private long parents[];
-  private final long spanId;
+  private SpanId parents[];
+  private final SpanId spanId;
   private Map<String, String> traceInfo = null;
   private String tracerId;
   private List<TimelineAnnotation> timeline = null;
 
-  private static long nonZeroRandom64() {
-    long id;
-    Random random = ThreadLocalRandom.current();
-    do {
-      id = random.nextLong();
-    } while (id == 0);
-    return id;
-  }
-
   @Override
   public Span child(String childDescription) {
     return new MilliSpan.Builder().
       begin(System.currentTimeMillis()).
       end(0).
       description(childDescription).
-      traceId(traceId).
-      parents(new long[] {spanId}).
-      spanId(nonZeroRandom64()).
+      parents(new SpanId[] {spanId}).
+      spanId(spanId.newChildId()).
       tracerId(tracerId).
       build();
   }
@@ -94,9 +82,8 @@
     private long begin;
     private long end;
     private String description = EMPTY_STRING;
-    private long traceId;
-    private long parents[] = EMPTY_PARENT_ARRAY;
-    private long spanId;
+    private SpanId parents[] = EMPTY_PARENT_ARRAY;
+    private SpanId spanId = SpanId.INVALID;
     private Map<String, String> traceInfo = null;
     private String tracerId = EMPTY_STRING;
     private List<TimelineAnnotation> timeline = null;
@@ -119,26 +106,21 @@
       return this;
     }
 
-    public Builder traceId(long traceId) {
-      this.traceId = traceId;
-      return this;
-    }
-
-    public Builder parents(long parents[]) {
+    public Builder parents(SpanId parents[]) {
       this.parents = parents;
       return this;
     }
 
-    public Builder parents(List<Long> parentList) {
-      long[] parents = new long[parentList.size()];
+    public Builder parents(List<SpanId> parentList) {
+      SpanId[] parents = new SpanId[parentList.size()];
       for (int i = 0; i < parentList.size(); i++) {
-        parents[i] = parentList.get(i).longValue();
+        parents[i] = parentList.get(i);
       }
       this.parents = parents;
       return this;
     }
 
-    public Builder spanId(long spanId) {
+    public Builder spanId(SpanId spanId) {
       this.spanId = spanId;
       return this;
     }
@@ -167,9 +149,8 @@
     this.begin = 0;
     this.end = 0;
     this.description = EMPTY_STRING;
-    this.traceId = 0;
     this.parents = EMPTY_PARENT_ARRAY;
-    this.spanId = 0;
+    this.spanId = SpanId.INVALID;
     this.traceInfo = null;
     this.tracerId = EMPTY_STRING;
     this.timeline = null;
@@ -179,7 +160,6 @@
     this.begin = builder.begin;
     this.end = builder.end;
     this.description = builder.description;
-    this.traceId = builder.traceId;
     this.parents = builder.parents;
     this.spanId = builder.spanId;
     this.traceInfo = builder.traceInfo;
@@ -227,26 +207,21 @@
   }
 
   @Override
-  public long getSpanId() {
+  public SpanId getSpanId() {
     return spanId;
   }
 
   @Override
-  public long[] getParents() {
+  public SpanId[] getParents() {
     return parents;
   }
 
   @Override
-  public void setParents(long[] parents) {
+  public void setParents(SpanId[] parents) {
     this.parents = parents;
   }
 
   @Override
-  public long getTraceId() {
-    return traceId;
-  }
-
-  @Override
   public long getStartTimeMillis() {
     return begin;
   }
@@ -308,10 +283,6 @@
     return writer.toString();
   }
 
-  private static long parseUnsignedHexLong(String s) {
-    return new BigInteger(s, 16).longValue();
-  }
-
   public static class MilliSpanDeserializer
         extends JsonDeserializer<MilliSpan> {
     @Override
@@ -331,25 +302,21 @@
       if (dNode != null) {
         builder.description(dNode.asText());
       }
-      JsonNode iNode = root.get("i");
-      if (iNode != null) {
-        builder.traceId(parseUnsignedHexLong(iNode.asText()));
-      }
-      JsonNode sNode = root.get("s");
+      JsonNode sNode = root.get("a");
       if (sNode != null) {
-        builder.spanId(parseUnsignedHexLong(sNode.asText()));
+        builder.spanId(SpanId.fromString(sNode.asText()));
       }
       JsonNode rNode = root.get("r");
       if (rNode != null) {
         builder.tracerId(rNode.asText());
       }
       JsonNode parentsNode = root.get("p");
-      LinkedList<Long> parents = new LinkedList<Long>();
+      LinkedList<SpanId> parents = new LinkedList<SpanId>();
       if (parentsNode != null) {
         for (Iterator<JsonNode> iter = parentsNode.elements();
              iter.hasNext(); ) {
           JsonNode parentIdNode = iter.next();
-          parents.add(parseUnsignedHexLong(parentIdNode.asText()));
+          parents.add(SpanId.fromString(parentIdNode.asText()));
         }
       }
       builder.parents(parents);
diff --git a/htrace-core/src/test/java/org/apache/htrace/TestBadClient.java b/htrace-core/src/test/java/org/apache/htrace/TestBadClient.java
index e13a0f8..868c0d0 100644
--- a/htrace-core/src/test/java/org/apache/htrace/TestBadClient.java
+++ b/htrace-core/src/test/java/org/apache/htrace/TestBadClient.java
@@ -24,8 +24,6 @@
 import org.apache.htrace.Span;
 import org.apache.htrace.SpanReceiver;
 import org.apache.htrace.Tracer;
-import org.apache.htrace.TraceTree.SpansByParent;
-import org.apache.htrace.TraceTree;
 import org.apache.htrace.impl.AlwaysSampler;
 import org.apache.htrace.impl.LocalFileSpanReceiver;
 import org.apache.htrace.impl.POJOSpanReceiver;
diff --git a/htrace-core/src/test/java/org/apache/htrace/TestHTrace.java b/htrace-core/src/test/java/org/apache/htrace/TestHTrace.java
index 13338e9..92f96c8 100644
--- a/htrace-core/src/test/java/org/apache/htrace/TestHTrace.java
+++ b/htrace-core/src/test/java/org/apache/htrace/TestHTrace.java
@@ -16,7 +16,7 @@
  */
 package org.apache.htrace;
 
-import org.apache.htrace.TraceTree.SpansByParent;
+import org.apache.htrace.TraceGraph.SpansByParent;
 import org.apache.htrace.impl.LocalFileSpanReceiver;
 import org.apache.htrace.impl.POJOSpanReceiver;
 import org.apache.htrace.impl.StandardOutSpanReceiver;
@@ -67,8 +67,8 @@
     traceCreator.addReceiver(new POJOSpanReceiver(HTraceConfiguration.EMPTY){
       @Override
       public void close() {
-        TraceTree traceTree = new TraceTree(getSpans());
-        Collection<Span> roots = traceTree.getSpansByParent().find(0);
+        TraceGraph traceGraph = new TraceGraph(getSpans());
+        Collection<Span> roots = traceGraph.getSpansByParent().find(SpanId.INVALID);
         Assert.assertTrue("Trace tree must have roots", !roots.isEmpty());
         Assert.assertEquals(numTraces, roots.size());
 
@@ -84,7 +84,7 @@
         Assert.assertTrue(descriptionToRootSpan.keySet().contains(
             TraceCreator.THREADED_TRACE_ROOT));
 
-        SpansByParent spansByParentId = traceTree.getSpansByParent();
+        SpansByParent spansByParentId = traceGraph.getSpansByParent();
         Span rpcTraceRoot = descriptionToRootSpan.get(TraceCreator.RPC_TRACE_ROOT);
         Assert.assertEquals(1, spansByParentId.find(rpcTraceRoot.getSpanId()).size());
 
@@ -109,11 +109,10 @@
 
   @Test(timeout=60000)
   public void testRootSpansHaveNonZeroSpanId() throws Exception {
-    TraceInfo traceInfo = new TraceInfo(100L, 200L);
-    TraceScope scope = Trace.startSpan("myRootSpan", traceInfo);
+    TraceScope scope = Trace.startSpan("myRootSpan", new SpanId(100L, 200L));
     Assert.assertNotNull(scope);
     Assert.assertEquals("myRootSpan", scope.getSpan().getDescription());
-    Assert.assertEquals(100L, scope.getSpan().getTraceId());
-    Assert.assertTrue(0 != scope.getSpan().getSpanId());
+    Assert.assertEquals(100L, scope.getSpan().getSpanId().getHigh());
+    Assert.assertTrue(scope.getSpan().getSpanId().isValid());
   }
 }
diff --git a/htrace-core/src/test/java/org/apache/htrace/TestSpanId.java b/htrace-core/src/test/java/org/apache/htrace/TestSpanId.java
new file mode 100644
index 0000000..10e6cca
--- /dev/null
+++ b/htrace-core/src/test/java/org/apache/htrace/TestSpanId.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.htrace;
+
+import java.util.Random;
+import org.apache.htrace.SpanId;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class TestSpanId {
+  private void testRoundTrip(SpanId id) throws Exception {
+    String str = id.toString();
+    SpanId id2 = SpanId.fromString(str);
+    Assert.assertEquals(id, id2);
+  }
+
+  @Test
+  public void testToStringAndFromString() throws Exception {
+    testRoundTrip(SpanId.INVALID);
+    testRoundTrip(new SpanId(0x1234567812345678L, 0x1234567812345678L));
+    testRoundTrip(new SpanId(0xf234567812345678L, 0xf234567812345678L));
+    testRoundTrip(new SpanId(0xffffffffffffffffL, 0xffffffffffffffffL));
+    Random rand = new Random(12345);
+    for (int i = 0; i < 100; i++) {
+      testRoundTrip(new SpanId(rand.nextLong(), rand.nextLong()));
+    }
+  }
+
+  @Test
+  public void testValidAndInvalidIds() throws Exception {
+    Assert.assertFalse(SpanId.INVALID.isValid());
+    Assert.assertTrue(
+        new SpanId(0x1234567812345678L, 0x1234567812345678L).isValid());
+    Assert.assertTrue(
+        new SpanId(0xf234567812345678L, 0xf234567812345678L).isValid());
+  }
+
+  private void expectLessThan(SpanId a, SpanId b) throws Exception {
+    int cmp = a.compareTo(b);
+    Assert.assertTrue("Expected " + a + " to be less than " + b,
+        (cmp < 0));
+    int cmp2 = b.compareTo(a);
+    Assert.assertTrue("Expected " + b + " to be greater than " + a,
+        (cmp2 > 0));
+  }
+
+  @Test
+  public void testIdComparisons() throws Exception {
+    expectLessThan(new SpanId(0x0000000000000001L, 0x0000000000000001L),
+                   new SpanId(0x0000000000000001L, 0x0000000000000002L));
+    expectLessThan(new SpanId(0x0000000000000001L, 0x0000000000000001L),
+                   new SpanId(0x0000000000000002L, 0x0000000000000000L));
+    expectLessThan(SpanId.INVALID,
+                   new SpanId(0xffffffffffffffffL, 0xffffffffffffffffL));
+    expectLessThan(new SpanId(0x1234567812345678L, 0x1234567812345678L),
+                   new SpanId(0x1234567812345678L, 0xf234567812345678L));
+  }
+}
diff --git a/htrace-core/src/main/java/org/apache/htrace/TraceTree.java b/htrace-core/src/test/java/org/apache/htrace/TraceGraph.java
similarity index 79%
rename from htrace-core/src/main/java/org/apache/htrace/TraceTree.java
rename to htrace-core/src/test/java/org/apache/htrace/TraceGraph.java
index db781cb..9004ea6 100644
--- a/htrace-core/src/main/java/org/apache/htrace/TraceTree.java
+++ b/htrace-core/src/test/java/org/apache/htrace/TraceGraph.java
@@ -35,7 +35,7 @@
 /**
  * Used to create the graph formed by spans.
  */
-public class TraceTree {
+public class TraceGraph {
   private static final Log LOG = LogFactory.getLog(Tracer.class);
 
 
@@ -47,38 +47,32 @@
         new Comparator<Span>() {
           @Override
           public int compare(Span a, Span b) {
-            if (a.getSpanId() < b.getSpanId()) {
-              return -1;
-            } else if (a.getSpanId() > b.getSpanId()) {
-              return 1;
-            } else {
-              return 0;
-            }
+            return a.getSpanId().compareTo(b.getSpanId());
           }
         };
 
     private final TreeSet<Span> treeSet;
 
-    private final HashMap<Long, LinkedList<Span>> parentToSpans;
+    private final HashMap<SpanId, LinkedList<Span>> parentToSpans;
 
     SpansByParent(Collection<Span> spans) {
       TreeSet<Span> treeSet = new TreeSet<Span>(COMPARATOR);
-      parentToSpans = new HashMap<Long, LinkedList<Span>>();
+      parentToSpans = new HashMap<SpanId, LinkedList<Span>>();
       for (Span span : spans) {
         treeSet.add(span);
-        for (long parent : span.getParents()) {
-          LinkedList<Span> list = parentToSpans.get(Long.valueOf(parent));
+        for (SpanId parent : span.getParents()) {
+          LinkedList<Span> list = parentToSpans.get(parent);
           if (list == null) {
             list = new LinkedList<Span>();
-            parentToSpans.put(Long.valueOf(parent), list);
+            parentToSpans.put(parent, list);
           }
           list.add(span);
         }
         if (span.getParents().length == 0) {
-          LinkedList<Span> list = parentToSpans.get(Long.valueOf(0L));
+          LinkedList<Span> list = parentToSpans.get(SpanId.INVALID);
           if (list == null) {
             list = new LinkedList<Span>();
-            parentToSpans.put(Long.valueOf(0L), list);
+            parentToSpans.put(SpanId.INVALID, list);
           }
           list.add(span);
         }
@@ -86,7 +80,7 @@
       this.treeSet = treeSet;
     }
 
-    public List<Span> find(long parentId) {
+    public List<Span> find(SpanId parentId) {
       LinkedList<Span> spans = parentToSpans.get(parentId);
       if (spans == null) {
         return new LinkedList<Span>();
@@ -110,13 +104,8 @@
             int cmp = a.getTracerId().compareTo(b.getTracerId());
             if (cmp != 0) {
               return cmp;
-            } else if (a.getSpanId() < b.getSpanId()) {
-              return -1;
-            } else if (a.getSpanId() > b.getSpanId()) {
-              return 1;
-            } else {
-              return 0;
             }
+            return a.getSpanId().compareTo(b.getSpanId());
           }
         };
 
@@ -133,8 +122,7 @@
     public List<Span> find(String tracerId) {
       List<Span> spans = new ArrayList<Span>();
       Span span = new MilliSpan.Builder().
-                    traceId(Long.MIN_VALUE).
-                    spanId(Long.MIN_VALUE).
+                    spanId(SpanId.INVALID).
                     tracerId(tracerId).
                     build();
       while (true) {
@@ -159,12 +147,12 @@
   private final SpansByTracerId spansByTracerId;
 
   /**
-   * Create a new TraceTree
+   * Create a new TraceGraph
    *
-   * @param spans The collection of spans to use to create this TraceTree. Should
+   * @param spans The collection of spans to use to create this TraceGraph. Should
    *              have at least one root span.
    */
-  public TraceTree(Collection<Span> spans) {
+  public TraceGraph(Collection<Span> spans) {
     this.spansByParent = new SpansByParent(spans);
     this.spansByTracerId = new SpansByTracerId(spans);
   }
diff --git a/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java b/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java
index 74ee562..9a0be4a 100644
--- a/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java
+++ b/htrace-core/src/test/java/org/apache/htrace/impl/TestMilliSpan.java
@@ -20,10 +20,10 @@
 import static org.junit.Assert.assertTrue;
 
 import org.apache.htrace.Span;
+import org.apache.htrace.SpanId;
 import org.apache.htrace.TimelineAnnotation;
 import org.junit.Test;
 
-import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -31,13 +31,13 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
 
 public class TestMilliSpan {
   private void compareSpans(Span expected, Span got) throws Exception {
     assertEquals(expected.getStartTimeMillis(), got.getStartTimeMillis());
     assertEquals(expected.getStopTimeMillis(), got.getStopTimeMillis());
     assertEquals(expected.getDescription(), got.getDescription());
-    assertEquals(expected.getTraceId(), got.getTraceId());
     assertEquals(expected.getSpanId(), got.getSpanId());
     assertEquals(expected.getTracerId(), got.getTracerId());
     assertTrue(Arrays.equals(expected.getParents(), got.getParents()));
@@ -74,10 +74,10 @@
         description("foospan").
         begin(123L).
         end(456L).
-        parents(new long[] { 7L }).
+        parents(new SpanId[] { new SpanId(7L, 7L) }).
         tracerId("b2404.halxg.com:8080").
-        spanId(989L).
-        traceId(444).build();
+        spanId(new SpanId(7L, 8L)).
+        build();
     String json = span.toJson();
     MilliSpan dspan = MilliSpan.fromJson(json);
     compareSpans(span, dspan);
@@ -89,10 +89,10 @@
         description("foospan").
         begin(-1L).
         end(-1L).
-        parents(new long[] { -1L }).
+        parents(new SpanId[] { new SpanId(-1L, -1L) }).
         tracerId("b2404.halxg.com:8080").
-        spanId(-1L).
-        traceId(-1L).build();
+        spanId(new SpanId(-1L, -2L)).
+        build();
     String json = span.toJson();
     MilliSpan dspan = MilliSpan.fromJson(json);
     compareSpans(span, dspan);
@@ -100,15 +100,15 @@
 
   @Test
   public void testJsonSerializationWithRandomLongValue() throws Exception {
-    Random random = new SecureRandom();
+    SpanId parentId = SpanId.fromRandom();
     MilliSpan span = new MilliSpan.Builder().
         description("foospan").
-        begin(random.nextLong()).
-        end(random.nextLong()).
-        parents(new long[] { random.nextLong() }).
+        begin(ThreadLocalRandom.current().nextLong()).
+        end(ThreadLocalRandom.current().nextLong()).
+        parents(new SpanId[] { parentId }).
         tracerId("b2404.halxg.com:8080").
-        spanId(random.nextLong()).
-        traceId(random.nextLong()).build();
+        spanId(parentId.newChildId()).
+        build();
     String json = span.toJson();
     MilliSpan dspan = MilliSpan.fromJson(json);
     compareSpans(span, dspan);
@@ -120,10 +120,9 @@
         description("foospan").
         begin(300).
         end(400).
-        parents(new long[] { }).
+        parents(new SpanId[] { }).
         tracerId("b2408.halxg.com:8080").
-        spanId(111111111L).
-        traceId(4443);
+        spanId(new SpanId(111111111L, 111111111L));
     Map<String, String> traceInfo = new HashMap<String, String>();
     traceInfo.put("abc", "123");
     traceInfo.put("def", "456");
diff --git a/htrace-flume/src/main/java/org/apache/htrace/impl/FlumeSpanReceiver.java b/htrace-flume/src/main/java/org/apache/htrace/impl/FlumeSpanReceiver.java
index a1352ab..f930c02 100644
--- a/htrace-flume/src/main/java/org/apache/htrace/impl/FlumeSpanReceiver.java
+++ b/htrace-flume/src/main/java/org/apache/htrace/impl/FlumeSpanReceiver.java
@@ -173,9 +173,8 @@
           for (Span span : dequeuedSpans) {
             // Headers allow Flume to filter
             Map<String, String> headers = new HashMap<String, String>();
-            headers.put("TraceId",      Long.toString(span.getTraceId()));
-            headers.put("SpanId",       Long.toString(span.getSpanId()));
-            headers.put("TracerId",    span.getTracerId());
+            headers.put("SpanId",       span.toString());
+            headers.put("TracerId",     span.getTracerId());
             headers.put("Description",  span.getDescription());
 
             String body = span.toJson();
diff --git a/htrace-flume/src/test/java/org/apache/htrace/impl/TestFlumeSpanReceiver.java b/htrace-flume/src/test/java/org/apache/htrace/impl/TestFlumeSpanReceiver.java
index 6743c85..d144b62 100644
--- a/htrace-flume/src/test/java/org/apache/htrace/impl/TestFlumeSpanReceiver.java
+++ b/htrace-flume/src/test/java/org/apache/htrace/impl/TestFlumeSpanReceiver.java
@@ -19,6 +19,7 @@
 
 import org.apache.htrace.HTraceConfiguration;
 import org.apache.htrace.Span;
+import org.apache.htrace.SpanId;
 import org.apache.htrace.TraceCreator;
 import org.junit.Assert;
 import org.junit.Rule;
@@ -42,8 +43,7 @@
 
     Span rootSpan = new MilliSpan.Builder().
         description("root").
-        traceId(1).
-        spanId(100).
+        spanId(new SpanId(100, 100)).
         tracerId("test").
         begin(System.currentTimeMillis()).
         build();
diff --git a/htrace-hbase/src/main/java/org/apache/htrace/impl/HBaseSpanReceiver.java b/htrace-hbase/src/main/java/org/apache/htrace/impl/HBaseSpanReceiver.java
index beb96b5..381010f 100644
--- a/htrace-hbase/src/main/java/org/apache/htrace/impl/HBaseSpanReceiver.java
+++ b/htrace-hbase/src/main/java/org/apache/htrace/impl/HBaseSpanReceiver.java
@@ -208,17 +208,17 @@
         try {
           for (Span span : dequeuedSpans) {
             sbuilder.clear()
-                    .setTraceId(span.getTraceId())
+                    .setTraceId(span.getSpanId().getHigh())
                     .setStart(span.getStartTimeMillis())
                     .setStop(span.getStopTimeMillis())
-                    .setSpanId(span.getSpanId())
+                    .setSpanId(span.getSpanId().getLow())
                     .setProcessId(span.getTracerId())
                     .setDescription(span.getDescription());
 
             if (span.getParents().length == 0) {
               sbuilder.setParentId(0);
             } else if (span.getParents().length > 0) {
-              sbuilder.setParentId(span.getParents()[0]);
+              sbuilder.setParentId(span.getParents()[0].getLow());
               if (span.getParents().length > 1) {
                 LOG.error("error: HBaseSpanReceiver does not support spans " +
                     "with multiple parents.  Ignoring multiple parents for " +
@@ -231,7 +231,7 @@
                                             .setMessage(ta.getMessage())
                                             .build());
             }
-            Put put = new Put(Bytes.toBytes(span.getTraceId()));
+            Put put = new Put(Bytes.toBytes(span.getSpanId().getHigh()));
             put.add(HBaseSpanReceiver.this.cf,
                     sbuilder.build().toByteArray(),
                     null);
@@ -360,7 +360,7 @@
     Trace.addReceiver(receiver);
     TraceScope parent = Trace.startSpan("HBaseSpanReceiver.main.parent", Sampler.ALWAYS);
     Thread.sleep(10);
-    long traceid = parent.getSpan().getTraceId();
+    long traceid = parent.getSpan().getSpanId().getHigh();
     TraceScope child1 = Trace.startSpan("HBaseSpanReceiver.main.child.1");
     Thread.sleep(10);
     TraceScope child2 = Trace.startSpan("HBaseSpanReceiver.main.child.2", parent.getSpan());
diff --git a/htrace-hbase/src/test/java/org/apache/htrace/impl/TestHBaseSpanReceiver.java b/htrace-hbase/src/test/java/org/apache/htrace/impl/TestHBaseSpanReceiver.java
index 5027891..2224599 100644
--- a/htrace-hbase/src/test/java/org/apache/htrace/impl/TestHBaseSpanReceiver.java
+++ b/htrace-hbase/src/test/java/org/apache/htrace/impl/TestHBaseSpanReceiver.java
@@ -38,11 +38,12 @@
 import org.apache.hadoop.hbase.client.Scan;
 import org.apache.hadoop.hbase.util.Bytes;
 import org.apache.htrace.Span;
+import org.apache.htrace.SpanId;
 import org.apache.htrace.SpanReceiver;
 import org.apache.htrace.TimelineAnnotation;
 import org.apache.htrace.TraceCreator;
-import org.apache.htrace.TraceTree.SpansByParent;
-import org.apache.htrace.TraceTree;
+import org.apache.htrace.TraceGraph.SpansByParent;
+import org.apache.htrace.TraceGraph;
 import org.apache.htrace.protobuf.generated.SpanProtos;
 import org.junit.AfterClass;
 import org.junit.Assert;
@@ -93,9 +94,9 @@
       Assert.fail("failed to get spans from HBase. " + e.getMessage());
     }
 
-    TraceTree traceTree = new TraceTree(spans);
+    TraceGraph traceGraph = new TraceGraph(spans);
     Collection<Span> roots =
-        traceTree.getSpansByParent().find(0);
+        traceGraph.getSpansByParent().find(SpanId.INVALID);
     Assert.assertTrue("Trace tree must have roots", !roots.isEmpty());
     Assert.assertEquals(3, roots.size());
 
@@ -107,7 +108,7 @@
     Assert.assertTrue(descs.keySet().contains(TraceCreator.SIMPLE_TRACE_ROOT));
     Assert.assertTrue(descs.keySet().contains(TraceCreator.THREADED_TRACE_ROOT));
 
-    SpansByParent spansByParentId = traceTree.getSpansByParent();
+    SpansByParent spansByParentId = traceGraph.getSpansByParent();
     Span rpcRoot = descs.get(TraceCreator.RPC_TRACE_ROOT);
     Assert.assertEquals(1, spansByParentId.find(rpcRoot.getSpanId()).size());
     Span rpcChild1 = spansByParentId.find(rpcRoot.getSpanId()).iterator().next();
@@ -144,19 +145,14 @@
     }
 
     @Override
-    public long getTraceId() {
-      return span.getTraceId();
-    }
-
-    @Override
-    public long[] getParents() {
+    public SpanId[] getParents() {
       return (span.getParentId() == 0L) ?
-        (new long[] {}) :
-        (new long[] { span.getParentId() });
+        (new SpanId[] {}) :
+        (new SpanId[] { new SpanId(span.getTraceId(), span.getParentId()) });
     }
 
     @Override
-    public void setParents(long[] parents) {
+    public void setParents(SpanId[] parents) {
       throw new UnsupportedOperationException();
     }
 
@@ -171,8 +167,8 @@
     }
 
     @Override
-    public long getSpanId() {
-      return span.getSpanId();
+    public SpanId getSpanId() {
+      return new SpanId(span.getTraceId(), span.getSpanId());
     }
 
     @Override
diff --git a/htrace-htraced/go/src/org/apache/htrace/client/client.go b/htrace-htraced/go/src/org/apache/htrace/client/client.go
index ef827e8..5051d94 100644
--- a/htrace-htraced/go/src/org/apache/htrace/client/client.go
+++ b/htrace-htraced/go/src/org/apache/htrace/client/client.go
@@ -69,7 +69,7 @@
 
 // Get information about a trace span.  Returns nil, nil if the span was not found.
 func (hcl *Client) FindSpan(sid common.SpanId) (*common.Span, error) {
-	buf, rc, err := hcl.makeGetRequest(fmt.Sprintf("span/%016x", uint64(sid)))
+	buf, rc, err := hcl.makeGetRequest(fmt.Sprintf("span/%s", sid.String()))
 	if err != nil {
 		if rc == http.StatusNoContent {
 			return nil, nil
@@ -133,8 +133,8 @@
 
 // Find the child IDs of a given span ID.
 func (hcl *Client) FindChildren(sid common.SpanId, lim int) ([]common.SpanId, error) {
-	buf, _, err := hcl.makeGetRequest(fmt.Sprintf("span/%016x/children?lim=%d",
-		uint64(sid), lim))
+	buf, _, err := hcl.makeGetRequest(fmt.Sprintf("span/%s/children?lim=%d",
+		sid.String(), lim))
 	if err != nil {
 		return nil, err
 	}
@@ -209,7 +209,7 @@
 	defer func() {
 		close(out)
 	}()
-	searchId := common.SpanId(0)
+	searchId := common.INVALID_SPAN_ID
 	for {
 		q := common.Query{
 			Lim: lim,
@@ -232,7 +232,7 @@
 		for i := range spans {
 			out <- &spans[i]
 		}
-		searchId = spans[len(spans)-1].Id + 1
+		searchId = spans[len(spans)-1].Id.Next()
 	}
 }
 
diff --git a/htrace-htraced/go/src/org/apache/htrace/common/span.go b/htrace-htraced/go/src/org/apache/htrace/common/span.go
index 720c4cd..1716c5a 100644
--- a/htrace-htraced/go/src/org/apache/htrace/common/span.go
+++ b/htrace-htraced/go/src/org/apache/htrace/common/span.go
@@ -20,10 +20,11 @@
 package common
 
 import (
+	"bytes"
 	"encoding/json"
 	"errors"
 	"fmt"
-	"strconv"
+	"hash/fnv"
 )
 
 //
@@ -43,18 +44,88 @@
 	Msg  string `json:"m"`
 }
 
-type SpanId uint64
+type SpanId []byte
+
+var INVALID_SPAN_ID SpanId = make([]byte, 16) // all zeroes
 
 func (id SpanId) String() string {
-	return fmt.Sprintf("%016x", uint64(id))
+	return fmt.Sprintf("%02x%02x%02x%02x"+
+		"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+		id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7], id[8],
+		id[9], id[10], id[11], id[12], id[13], id[14], id[15])
 }
 
-func (id SpanId) Val() uint64 {
-	return uint64(id)
+func (id SpanId) Val() []byte {
+	return []byte(id)
+}
+
+func (id SpanId) FindProblem() string {
+	if id == nil {
+		return "The span ID is nil"
+	}
+	if len(id) != 16 {
+		return "The span ID is not exactly 16 bytes."
+	}
+	if bytes.Equal(id.Val(), INVALID_SPAN_ID.Val()) {
+		return "The span ID is all zeros."
+	}
+	return ""
+}
+
+func (id SpanId) ToArray() [16]byte {
+	var ret [16]byte
+	copy(ret[:], id.Val()[:])
+	return ret
+}
+
+// Return the next ID in lexicographical order.  For the maximum ID,
+// returns the minimum.
+func (id SpanId) Next() SpanId {
+	next := make([]byte, 16)
+	copy(next, id)
+	for i := len(next) - 1; i >= 0; i-- {
+		if next[i] == 0xff {
+			next[i] = 0
+		} else {
+			next[i] = next[i] + 1
+			break
+		}
+	}
+	return next
+}
+
+// Return the previous ID in lexicographical order.  For the minimum ID,
+// returns the maximum ID.
+func (id SpanId) Prev() SpanId {
+	prev := make([]byte, 16)
+	copy(prev, id)
+	for i := len(prev) - 1; i >= 0; i-- {
+		if prev[i] == 0x00 {
+			prev[i] = 0xff
+		} else {
+			prev[i] = prev[i] - 1
+			break
+		}
+	}
+	return prev
 }
 
 func (id SpanId) MarshalJSON() ([]byte, error) {
-	return []byte(`"` + fmt.Sprintf("%016x", uint64(id)) + `"`), nil
+	return []byte(`"` + id.String() + `"`), nil
+}
+
+func (id SpanId) Compare(other SpanId) int {
+	return bytes.Compare(id.Val(), other.Val())
+}
+
+func (id SpanId) Equal(other SpanId) bool {
+	return bytes.Equal(id.Val(), other.Val())
+}
+
+func (id SpanId) Hash32() uint32 {
+	h := fnv.New32a()
+	h.Write(id.Val())
+	return h.Sum32()
 }
 
 type SpanSlice []*Span
@@ -64,7 +135,7 @@
 }
 
 func (s SpanSlice) Less(i, j int) bool {
-	return s[i].Id < s[j].Id
+	return s[i].Id.Compare(s[j].Id) < 0
 }
 
 func (s SpanSlice) Swap(i, j int) {
@@ -78,7 +149,7 @@
 }
 
 func (s SpanIdSlice) Less(i, j int) bool {
-	return s[i] < s[j]
+	return s[i].Compare(s[j]) < 0
 }
 
 func (s SpanIdSlice) Swap(i, j int) {
@@ -98,11 +169,18 @@
 }
 
 func (id *SpanId) FromString(str string) error {
-	v, err := strconv.ParseUint(str, 16, 64)
+	i := SpanId(make([]byte, 16))
+	n, err := fmt.Sscanf(str, "%02x%02x%02x%02x"+
+		"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
+		&i[0], &i[1], &i[2], &i[3], &i[4], &i[5], &i[6], &i[7], &i[8],
+		&i[9], &i[10], &i[11], &i[12], &i[13], &i[14], &i[15])
 	if err != nil {
 		return err
 	}
-	*id = SpanId(v)
+	if n != 16 {
+		return errors.New("Failed to find 16 hex digits in the SpanId")
+	}
+	*id = i
 	return nil
 }
 
@@ -110,7 +188,6 @@
 	Begin               int64                `json:"b"`
 	End                 int64                `json:"e"`
 	Description         string               `json:"d"`
-	TraceId             SpanId               `json:"i"`
 	Parents             []SpanId             `json:"p"`
 	Info                TraceInfoMap         `json:"n,omitempty"`
 	TracerId            string               `json:"r"`
@@ -118,7 +195,7 @@
 }
 
 type Span struct {
-	Id SpanId `json:"s"`
+	Id SpanId `json:"a"`
 	SpanData
 }
 
diff --git a/htrace-htraced/go/src/org/apache/htrace/common/span_test.go b/htrace-htraced/go/src/org/apache/htrace/common/span_test.go
index e3b44fe..9de7cee 100644
--- a/htrace-htraced/go/src/org/apache/htrace/common/span_test.go
+++ b/htrace-htraced/go/src/org/apache/htrace/common/span_test.go
@@ -20,33 +20,35 @@
 package common
 
 import (
+	"bytes"
+	"encoding/hex"
+	"fmt"
+	"github.com/ugorji/go/codec"
 	"testing"
 )
 
 func TestSpanToJson(t *testing.T) {
 	t.Parallel()
-	span := Span{Id: 2305843009213693952,
+	span := Span{Id: TestId("33f25a1a750a471db5bafa59309d7d6f"),
 		SpanData: SpanData{
 			Begin:       123,
 			End:         456,
 			Description: "getFileDescriptors",
-			TraceId:     999,
 			Parents:     []SpanId{},
 			TracerId:    "testTracerId",
 		}}
 	ExpectStrEqual(t,
-		`{"s":"2000000000000000","b":123,"e":456,"d":"getFileDescriptors","i":"00000000000003e7","p":[],"r":"testTracerId"}`,
+		`{"a":"33f25a1a750a471db5bafa59309d7d6f","b":123,"e":456,"d":"getFileDescriptors","p":[],"r":"testTracerId"}`,
 		string(span.ToJson()))
 }
 
 func TestAnnotatedSpanToJson(t *testing.T) {
 	t.Parallel()
-	span := Span{Id: 1305813009213693952,
+	span := Span{Id: TestId("11eace42e6404b40a7644214cb779a08"),
 		SpanData: SpanData{
 			Begin:       1234,
 			End:         4567,
 			Description: "getFileDescriptors2",
-			TraceId:     999,
 			Parents:     []SpanId{},
 			TracerId:    "testAnnotatedTracerId",
 			TimelineAnnotations: []TimelineAnnotation{
@@ -61,6 +63,54 @@
 			},
 		}}
 	ExpectStrEqual(t,
-		`{"s":"121f2e036d442000","b":1234,"e":4567,"d":"getFileDescriptors2","i":"00000000000003e7","p":[],"r":"testAnnotatedTracerId","t":[{"t":7777,"m":"contactedServer"},{"t":8888,"m":"passedFd"}]}`,
+		`{"a":"11eace42e6404b40a7644214cb779a08","b":1234,"e":4567,"d":"getFileDescriptors2","p":[],"r":"testAnnotatedTracerId","t":[{"t":7777,"m":"contactedServer"},{"t":8888,"m":"passedFd"}]}`,
 		string(span.ToJson()))
 }
+
+func TestSpanNext(t *testing.T) {
+	ExpectStrEqual(t, TestId("00000000000000000000000000000001").String(),
+		TestId("00000000000000000000000000000000").Next().String())
+	ExpectStrEqual(t, TestId("00000000000000000000000000f00000").String(),
+		TestId("00000000000000000000000000efffff").Next().String())
+	ExpectStrEqual(t, TestId("00000000000000000000000000000000").String(),
+		TestId("ffffffffffffffffffffffffffffffff").Next().String())
+}
+
+func TestSpanPrev(t *testing.T) {
+	ExpectStrEqual(t, TestId("00000000000000000000000000000000").String(),
+		TestId("00000000000000000000000000000001").Prev().String())
+	ExpectStrEqual(t, TestId("00000000000000000000000000efffff").String(),
+		TestId("00000000000000000000000000f00000").Prev().String())
+	ExpectStrEqual(t, TestId("ffffffffffffffffffffffffffffffff").String(),
+		TestId("00000000000000000000000000000000").Prev().String())
+}
+
+func TestSpanMsgPack(t *testing.T) {
+	span := Span{Id: TestId("33f25a1a750a471db5bafa59309d7d6f"),
+		SpanData: SpanData{
+			Begin:       1234,
+			End:         5678,
+			Description: "getFileDescriptors",
+			Parents:     []SpanId{},
+			TracerId:   "testTracerId",
+		}}
+	mh := new(codec.MsgpackHandle)
+	mh.WriteExt = true
+	w := bytes.NewBuffer(make([]byte, 0, 2048))
+	enc := codec.NewEncoder(w, mh)
+	err := enc.Encode(span)
+	if err != nil {
+		t.Fatal("Error encoding span as msgpack: " + err.Error())
+	}
+	buf := w.Bytes()
+	fmt.Printf("span: %s\n", hex.EncodeToString(buf))
+	mh = new(codec.MsgpackHandle)
+	mh.WriteExt = true
+	dec := codec.NewDecoder(bytes.NewReader(buf), mh)
+	var span2 Span
+	err = dec.Decode(&span2)
+	if err != nil {
+		t.Fatal("Failed to reverse msgpack encoding for " + span.String())
+	}
+	ExpectSpansEqual(t, &span, &span2)
+}
diff --git a/htrace-htraced/go/src/org/apache/htrace/common/test_util.go b/htrace-htraced/go/src/org/apache/htrace/common/test_util.go
index 871c847..ec9151b 100644
--- a/htrace-htraced/go/src/org/apache/htrace/common/test_util.go
+++ b/htrace-htraced/go/src/org/apache/htrace/common/test_util.go
@@ -72,3 +72,12 @@
 func ExpectSpansEqual(t *testing.T, spanA *Span, spanB *Span) {
 	ExpectStrEqual(t, string(spanA.ToJson()), string(spanB.ToJson()))
 }
+
+func TestId(str string) SpanId {
+	var spanId SpanId
+	err := spanId.FromString(str)
+	if err != nil {
+		panic(err.Error())
+	}
+	return spanId
+}
diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go b/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
index 38cdb58..8fd7067 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htrace/cmd.go
@@ -63,10 +63,10 @@
 	version := app.Command("version", "Print the version of this program.")
 	serverInfo := app.Command("serverInfo", "Print information retrieved from an htraced server.")
 	findSpan := app.Command("findSpan", "Print information about a trace span with a given ID.")
-	findSpanId := findSpan.Arg("id", "Span ID to find. Example: 0x123456789abcdef").Required().Uint64()
+	findSpanId := findSpan.Arg("id", "Span ID to find. Example: be305e54-4534-2110-a0b2-e06b9effe112").Required().String()
 	findChildren := app.Command("findChildren", "Print out the span IDs that are children of a given span ID.")
-	parentSpanId := findChildren.Arg("id", "Span ID to print children for. Example: 0x123456789abcdef").
-		Required().Uint64()
+	parentSpanId := findChildren.Arg("id", "Span ID to print children for. Example: be305e54-4534-2110-a0b2-e06b9effe112").
+		Required().String()
 	childLim := findChildren.Flag("lim", "Maximum number of child IDs to print.").Default("20").Int()
 	loadFile := app.Command("loadFile", "Write whitespace-separated JSON spans from a file to the server.")
 	loadFilePath := loadFile.Arg("path",
@@ -123,9 +123,13 @@
 	case serverInfo.FullCommand():
 		os.Exit(printServerInfo(hcl))
 	case findSpan.FullCommand():
-		os.Exit(doFindSpan(hcl, common.SpanId(*findSpanId)))
+		var id *common.SpanId
+		id.FromString(*findSpanId)
+		os.Exit(doFindSpan(hcl, *id))
 	case findChildren.FullCommand():
-		os.Exit(doFindChildren(hcl, common.SpanId(*parentSpanId), *childLim))
+		var id *common.SpanId
+		id.FromString(*parentSpanId)
+		os.Exit(doFindChildren(hcl, *id, *childLim))
 	case loadJson.FullCommand():
 		os.Exit(doLoadSpanJson(hcl, *loadJsonArg))
 	case loadFile.FullCommand():
diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/file_test.go b/htrace-htraced/go/src/org/apache/htrace/htrace/file_test.go
index 6fd6789..98e5e6c 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htrace/file_test.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htrace/file_test.go
@@ -25,7 +25,6 @@
 	"io/ioutil"
 	"org/apache/htrace/common"
 	"org/apache/htrace/conf"
-	"org/apache/htrace/test"
 	"os"
 	"strings"
 	"testing"
@@ -116,10 +115,10 @@
 }
 
 func TestReadSpans(t *testing.T) {
-	SPAN_TEST_STR := `{"i":"bdd6d4ee48de59bf","s":"c0681027d3ea4928",` +
+	SPAN_TEST_STR := `{"a":"b9f2a1e07b6e4f16b0c2b27303b20e79",` +
 		`"b":1424736225037,"e":1424736225901,"d":"ClientNamenodeProtocol#getFileInfo",` +
-		`"r":"FsShell","p":["60538dfb4df91418"]}
-{"i":"bdd6d4ee48de59bf","s":"60538dfb4df91418","b":1424736224969,` +
+		`"r":"FsShell","p":["3afebdc0a13f4feb811cc5c0e42d30b1"]}
+{"a":"3afebdc0a13f4feb811cc5c0e42d30b1","b":1424736224969,` +
 		`"e":1424736225960,"d":"getFileInfo","r":"FsShell","p":[],"n":{"path":"/"}}
 `
 	r := strings.NewReader(SPAN_TEST_STR)
@@ -129,20 +128,18 @@
 	}
 	SPAN_TEST_EXPECTED := common.SpanSlice{
 		&common.Span{
-			Id: test.SpanId("c0681027d3ea4928"),
+			Id: common.TestId("b9f2a1e07b6e4f16b0c2b27303b20e79"),
 			SpanData: common.SpanData{
-				TraceId:     test.SpanId("bdd6d4ee48de59bf"),
 				Begin:       1424736225037,
 				End:         1424736225901,
 				Description: "ClientNamenodeProtocol#getFileInfo",
 				TracerId:    "FsShell",
-				Parents:     []common.SpanId{test.SpanId("60538dfb4df91418")},
+				Parents:     []common.SpanId{common.TestId("3afebdc0a13f4feb811cc5c0e42d30b1")},
 			},
 		},
 		&common.Span{
-			Id: test.SpanId("60538dfb4df91418"),
+			Id: common.TestId("3afebdc0a13f4feb811cc5c0e42d30b1"),
 			SpanData: common.SpanData{
-				TraceId:     test.SpanId("bdd6d4ee48de59bf"),
 				Begin:       1424736224969,
 				End:         1424736225960,
 				Description: "getFileInfo",
diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/graph.go b/htrace-htraced/go/src/org/apache/htrace/htrace/graph.go
index dabf2df..024d973 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htrace/graph.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htrace/graph.go
@@ -64,32 +64,32 @@
 // Create output in dotfile format from a set of spans.
 func spansToDot(spans common.SpanSlice, writer io.Writer) error {
 	sort.Sort(spans)
-	idMap := make(map[common.SpanId]*common.Span)
+	idMap := make(map[[16]byte]*common.Span)
 	for i := range spans {
 		span := spans[i]
-		if idMap[span.Id] != nil {
+		if idMap[span.Id.ToArray()] != nil {
 			fmt.Fprintf(os.Stderr, "There were multiple spans listed which "+
 				"had ID %s.\nFirst:%s\nOther:%s\n", span.Id.String(),
-				idMap[span.Id].ToJson(), span.ToJson())
+				idMap[span.Id.ToArray()].ToJson(), span.ToJson())
 		} else {
-			idMap[span.Id] = span
+			idMap[span.Id.ToArray()] = span
 		}
 	}
-	childMap := make(map[common.SpanId]common.SpanSlice)
+	childMap := make(map[[16]byte]common.SpanSlice)
 	for i := range spans {
 		child := spans[i]
 		for j := range child.Parents {
-			parent := idMap[child.Parents[j]]
+			parent := idMap[child.Parents[j].ToArray()]
 			if parent == nil {
 				fmt.Fprintf(os.Stderr, "Can't find parent id %s for %s\n",
 					child.Parents[j].String(), child.ToJson())
 			} else {
-				children := childMap[parent.Id]
+				children := childMap[parent.Id.ToArray()]
 				if children == nil {
 					children = make(common.SpanSlice, 0)
 				}
 				children = append(children, child)
-				childMap[parent.Id] = children
+				childMap[parent.Id.ToArray()] = children
 			}
 		}
 	}
@@ -102,7 +102,7 @@
 	}
 	// Write out the edges between nodes... the parent/children relationships
 	for i := range spans {
-		children := childMap[spans[i].Id]
+		children := childMap[spans[i].Id.ToArray()]
 		sort.Sort(children)
 		if children != nil {
 			for c := range children {
diff --git a/htrace-htraced/go/src/org/apache/htrace/htrace/graph_test.go b/htrace-htraced/go/src/org/apache/htrace/htrace/graph_test.go
index e614cec..621b3dc 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htrace/graph_test.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htrace/graph_test.go
@@ -22,16 +22,14 @@
 import (
 	"bytes"
 	"org/apache/htrace/common"
-	"org/apache/htrace/test"
 	"testing"
 )
 
 func TestSpansToDot(t *testing.T) {
 	TEST_SPANS := common.SpanSlice{
 		&common.Span{
-			Id: test.SpanId("6af3cc058e5d829d"),
+			Id: common.TestId("814c8ee0e7984be3a8af00ac64adccb6"),
 			SpanData: common.SpanData{
-				TraceId:     test.SpanId("0e4716fe911244de"),
 				Begin:       1424813349020,
 				End:         1424813349134,
 				Description: "newDFSInputStream",
@@ -43,25 +41,23 @@
 			},
 		},
 		&common.Span{
-			Id: test.SpanId("75d16cc5b2c07d8a"),
+			Id: common.TestId("cf2d5de696454548bc055d1e6024054c"),
 			SpanData: common.SpanData{
-				TraceId:     test.SpanId("0e4716fe911244de"),
 				Begin:       1424813349025,
 				End:         1424813349133,
 				Description: "getBlockLocations",
 				TracerId:    "FsShell",
-				Parents:     []common.SpanId{test.SpanId("6af3cc058e5d829d")},
+				Parents:     []common.SpanId{common.TestId("814c8ee0e7984be3a8af00ac64adccb6")},
 			},
 		},
 		&common.Span{
-			Id: test.SpanId("e2c7273efb280a8c"),
+			Id: common.TestId("37623806f9c64483b834b8ea5d6b4827"),
 			SpanData: common.SpanData{
-				TraceId:     test.SpanId("0e4716fe911244de"),
 				Begin:       1424813349027,
 				End:         1424813349073,
 				Description: "ClientNamenodeProtocol#getBlockLocations",
 				TracerId:    "FsShell",
-				Parents:     []common.SpanId{test.SpanId("75d16cc5b2c07d8a")},
+				Parents:     []common.SpanId{common.TestId("cf2d5de696454548bc055d1e6024054c")},
 			},
 		},
 	}
@@ -71,11 +67,11 @@
 		t.Fatalf("spansToDot failed: error %s\n", err.Error())
 	}
 	EXPECTED_STR := `digraph spans {
-  "6af3cc058e5d829d" [label="newDFSInputStream"];
-  "75d16cc5b2c07d8a" [label="getBlockLocations"];
-  "e2c7273efb280a8c" [label="ClientNamenodeProtocol#getBlockLocations"];
-  "6af3cc058e5d829d" -> "75d16cc5b2c07d8a";
-  "75d16cc5b2c07d8a" -> "e2c7273efb280a8c";
+  "37623806f9c64483b834b8ea5d6b4827" [label="ClientNamenodeProtocol#getBlockLocations"];
+  "814c8ee0e7984be3a8af00ac64adccb6" [label="newDFSInputStream"];
+  "cf2d5de696454548bc055d1e6024054c" [label="getBlockLocations"];
+  "814c8ee0e7984be3a8af00ac64adccb6" -> "cf2d5de696454548bc055d1e6024054c";
+  "cf2d5de696454548bc055d1e6024054c" -> "37623806f9c64483b834b8ea5d6b4827";
 }
 `
 	if w.String() != EXPECTED_STR {
diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go b/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go
index 218c1c8..02a00f3 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htraced/client_test.go
@@ -121,7 +121,7 @@
 		t.Fatalf("FindChildren(%s) returned an invalid number of "+
 			"children: expected %d, got %d\n", parentId, 1, len(children))
 	}
-	if children[0] != childSpan.Id {
+	if !children[0].Equal(childSpan.Id) {
 		t.Fatalf("FindChildren(%s) returned an invalid child id: expected %s, "+
 			" got %s\n", parentId, childSpan.Id, children[0])
 	}
diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/datastore.go b/htrace-htraced/go/src/org/apache/htrace/htraced/datastore.go
index 48f02aa..5885168 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htraced/datastore.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htraced/datastore.go
@@ -22,6 +22,7 @@
 import (
 	"bytes"
 	"encoding/gob"
+	"encoding/hex"
 	"errors"
 	"fmt"
 	"github.com/jmhodges/levigo"
@@ -68,6 +69,7 @@
 var EMPTY_BYTE_BUF []byte = []byte{}
 
 const VERSION_KEY = 'v'
+
 const SPAN_ID_INDEX_PREFIX = 's'
 const BEGIN_TIME_INDEX_PREFIX = 'b'
 const END_TIME_INDEX_PREFIX = 'e'
@@ -90,56 +92,6 @@
 	}
 }
 
-// Translate an 8-byte value into a leveldb key.
-func makeKey(tag byte, val uint64) []byte {
-	return []byte{
-		tag,
-		byte(0xff & (val >> 56)),
-		byte(0xff & (val >> 48)),
-		byte(0xff & (val >> 40)),
-		byte(0xff & (val >> 32)),
-		byte(0xff & (val >> 24)),
-		byte(0xff & (val >> 16)),
-		byte(0xff & (val >> 8)),
-		byte(0xff & (val >> 0)),
-	}
-}
-
-func keyToInt(key []byte) uint64 {
-	var id uint64
-	id = (uint64(key[0]) << 56) |
-		(uint64(key[1]) << 48) |
-		(uint64(key[2]) << 40) |
-		(uint64(key[3]) << 32) |
-		(uint64(key[4]) << 24) |
-		(uint64(key[5]) << 16) |
-		(uint64(key[6]) << 8) |
-		(uint64(key[7]) << 0)
-	return id
-}
-
-func makeSecondaryKey(tag byte, fir uint64, sec uint64) []byte {
-	return []byte{
-		tag,
-		byte(0xff & (fir >> 56)),
-		byte(0xff & (fir >> 48)),
-		byte(0xff & (fir >> 40)),
-		byte(0xff & (fir >> 32)),
-		byte(0xff & (fir >> 24)),
-		byte(0xff & (fir >> 16)),
-		byte(0xff & (fir >> 8)),
-		byte(0xff & (fir >> 0)),
-		byte(0xff & (sec >> 56)),
-		byte(0xff & (sec >> 48)),
-		byte(0xff & (sec >> 40)),
-		byte(0xff & (sec >> 32)),
-		byte(0xff & (sec >> 24)),
-		byte(0xff & (sec >> 16)),
-		byte(0xff & (sec >> 8)),
-		byte(0xff & (sec >> 0)),
-	}
-}
-
 // A single directory containing a levelDB instance.
 type shard struct {
 	// The data store that this shard is part of
@@ -186,6 +138,18 @@
 	return ret
 }
 
+func u64toSlice(val uint64) []byte {
+	return []byte{
+		byte(0xff & (val >> 56)),
+		byte(0xff & (val >> 48)),
+		byte(0xff & (val >> 40)),
+		byte(0xff & (val >> 32)),
+		byte(0xff & (val >> 24)),
+		byte(0xff & (val >> 16)),
+		byte(0xff & (val >> 8)),
+		byte(0xff & (val >> 0))}
+}
+
 func (shd *shard) writeSpan(span *common.Span) error {
 	batch := levigo.NewWriteBatch()
 	defer batch.Close()
@@ -197,21 +161,27 @@
 	if err != nil {
 		return err
 	}
-	batch.Put(makeKey(SPAN_ID_INDEX_PREFIX, span.Id.Val()), spanDataBuf.Bytes())
+	primaryKey :=
+		append([]byte{SPAN_ID_INDEX_PREFIX}, span.Id.Val()...)
+	batch.Put(primaryKey, spanDataBuf.Bytes())
 
 	// Add this to the parent index.
 	for parentIdx := range span.Parents {
-		batch.Put(makeSecondaryKey(PARENT_ID_INDEX_PREFIX,
-			span.Parents[parentIdx].Val(), span.Id.Val()), EMPTY_BYTE_BUF)
+		key := append(append([]byte{PARENT_ID_INDEX_PREFIX},
+			span.Parents[parentIdx].Val()...), span.Id.Val()...)
+		batch.Put(key, EMPTY_BYTE_BUF)
 	}
 
 	// Add to the other secondary indices.
-	batch.Put(makeSecondaryKey(BEGIN_TIME_INDEX_PREFIX, s2u64(span.Begin),
-		span.Id.Val()), EMPTY_BYTE_BUF)
-	batch.Put(makeSecondaryKey(END_TIME_INDEX_PREFIX, s2u64(span.End),
-		span.Id.Val()), EMPTY_BYTE_BUF)
-	batch.Put(makeSecondaryKey(DURATION_INDEX_PREFIX, s2u64(span.Duration()),
-		span.Id.Val()), EMPTY_BYTE_BUF)
+	beginTimeKey := append(append([]byte{BEGIN_TIME_INDEX_PREFIX},
+		u64toSlice(s2u64(span.Begin))...), span.Id.Val()...)
+	batch.Put(beginTimeKey, EMPTY_BYTE_BUF)
+	endTimeKey := append(append([]byte{END_TIME_INDEX_PREFIX},
+		u64toSlice(s2u64(span.End))...), span.Id.Val()...)
+	batch.Put(endTimeKey, EMPTY_BYTE_BUF)
+	durationKey := append(append([]byte{DURATION_INDEX_PREFIX},
+		u64toSlice(s2u64(span.Duration()))...), span.Id.Val()...)
+	batch.Put(durationKey, EMPTY_BYTE_BUF)
 
 	err = shd.ldb.Write(shd.store.writeOpts, batch)
 	if err != nil {
@@ -226,7 +196,7 @@
 
 func (shd *shard) FindChildren(sid common.SpanId, childIds []common.SpanId,
 	lim int32) ([]common.SpanId, int32, error) {
-	searchKey := makeKey('p', sid.Val())
+	searchKey := append([]byte{PARENT_ID_INDEX_PREFIX}, sid.Val()...)
 	iter := shd.ldb.NewIterator(shd.store.readOpts)
 	defer iter.Close()
 	iter.Seek(searchKey)
@@ -241,7 +211,7 @@
 		if !bytes.HasPrefix(key, searchKey) {
 			break
 		}
-		id := common.SpanId(keyToInt(key[9:]))
+		id := common.SpanId(key[17:])
 		childIds = append(childIds, id)
 		lim--
 		iter.Next()
@@ -462,7 +432,7 @@
 
 // Get the index of the shard which stores the given spanId.
 func (store *dataStore) getShardIndex(sid common.SpanId) int {
-	return int(sid.Val() % uint64(len(store.shards)))
+	return int(sid.Hash32() % uint32(len(store.shards)))
 }
 
 func (store *dataStore) WriteSpan(span *common.Span) {
@@ -475,7 +445,8 @@
 
 func (shd *shard) FindSpan(sid common.SpanId) *common.Span {
 	lg := shd.store.lg
-	buf, err := shd.ldb.Get(shd.store.readOpts, makeKey('s', sid.Val()))
+	primaryKey := append([]byte{SPAN_ID_INDEX_PREFIX}, sid.Val()...)
+	buf, err := shd.ldb.Get(shd.store.readOpts, primaryKey)
 	if err != nil {
 		if strings.Index(err.Error(), "NotFound:") != -1 {
 			return nil
@@ -541,8 +512,7 @@
 
 type predicateData struct {
 	*common.Predicate
-	uintKey uint64
-	strKey  string
+	key []byte
 }
 
 func loadPredicateData(pred *common.Predicate) (*predicateData, error) {
@@ -558,11 +528,11 @@
 			return nil, errors.New(fmt.Sprintf("Unable to parse span id '%s': %s",
 				pred.Val, err.Error()))
 		}
-		p.uintKey = id.Val()
+		p.key = id.Val()
 		break
 	case common.DESCRIPTION:
 		// Any string is valid for a description.
-		p.strKey = pred.Val
+		p.key = []byte(pred.Val)
 		break
 	case common.BEGIN_TIME, common.END_TIME, common.DURATION:
 		// Parse a base-10 signed numeric field.
@@ -571,11 +541,11 @@
 			return nil, errors.New(fmt.Sprintf("Unable to parse %s '%s': %s",
 				pred.Field, pred.Val, err.Error()))
 		}
-		p.uintKey = s2u64(v)
+		p.key = u64toSlice(s2u64(v))
 		break
 	case common.TRACER_ID:
 		// Any string is valid for a tracer ID.
-		p.strKey = pred.Val
+		p.key = []byte(pred.Val)
 		break
 	default:
 		return nil, errors.New(fmt.Sprintf("Unknown field %s", pred.Field))
@@ -626,22 +596,22 @@
 }
 
 // Get the values that this predicate cares about for a given span.
-func (pred *predicateData) extractRelevantSpanData(span *common.Span) (uint64, string) {
+func (pred *predicateData) extractRelevantSpanData(span *common.Span) []byte {
 	switch pred.Field {
 	case common.SPAN_ID:
-		return span.Id.Val(), ""
+		return span.Id.Val()
 	case common.DESCRIPTION:
-		return 0, span.Description
+		return []byte(span.Description)
 	case common.BEGIN_TIME:
-		return s2u64(span.Begin), ""
+		return u64toSlice(s2u64(span.Begin))
 	case common.END_TIME:
-		return s2u64(span.End), ""
+		return u64toSlice(s2u64(span.End))
 	case common.DURATION:
-		return s2u64(span.Duration()), ""
+		return u64toSlice(s2u64(span.Duration()))
 	case common.TRACER_ID:
-		return 0, span.TracerId
+		return []byte(span.TracerId)
 	default:
-		panic(fmt.Sprintf("Field type %s isn't a 64-bit integer.", pred.Field))
+		panic(fmt.Sprintf("Unknown field type %s.", pred.Field))
 	}
 }
 
@@ -656,56 +626,33 @@
 		return true
 	}
 	// Compare the spans according to this predicate.
-	aInt, aStr := pred.extractRelevantSpanData(a)
-	bInt, bStr := pred.extractRelevantSpanData(b)
-	if pred.fieldIsNumeric() {
-		if pred.Op.IsDescending() {
-			return aInt > bInt
-		} else {
-			return aInt < bInt
-		}
+	aVal := pred.extractRelevantSpanData(a)
+	bVal := pred.extractRelevantSpanData(b)
+	cmp := bytes.Compare(aVal, bVal)
+	if pred.Op.IsDescending() {
+		return cmp > 0
 	} else {
-		if pred.Op.IsDescending() {
-			return aStr > bStr
-		} else {
-			return aStr < bStr
-		}
+		return cmp < 0
 	}
 }
 
 // Returns true if the predicate is satisfied by the given span.
 func (pred *predicateData) satisfiedBy(span *common.Span) bool {
-	intVal, strVal := pred.extractRelevantSpanData(span)
-	if pred.fieldIsNumeric() {
-		switch pred.Op {
-		case common.EQUALS:
-			return intVal == pred.uintKey
-		case common.LESS_THAN_OR_EQUALS:
-			return intVal <= pred.uintKey
-		case common.GREATER_THAN_OR_EQUALS:
-			return intVal >= pred.uintKey
-		case common.GREATER_THAN:
-			return intVal > pred.uintKey
-		default:
-			panic(fmt.Sprintf("unknown Op type %s should have been caught "+
-				"during normalization", pred.Op))
-		}
-	} else {
-		switch pred.Op {
-		case common.CONTAINS:
-			return strings.Contains(strVal, pred.strKey)
-		case common.EQUALS:
-			return strVal == pred.strKey
-		case common.LESS_THAN_OR_EQUALS:
-			return strVal <= pred.strKey
-		case common.GREATER_THAN_OR_EQUALS:
-			return strVal >= pred.strKey
-		case common.GREATER_THAN:
-			return strVal > pred.strKey
-		default:
-			panic(fmt.Sprintf("unknown Op type %s should have been caught "+
-				"during normalization", pred.Op))
-		}
+	val := pred.extractRelevantSpanData(span)
+	switch pred.Op {
+	case common.CONTAINS:
+		return bytes.Contains(val, pred.key)
+	case common.EQUALS:
+		return bytes.Equal(val, pred.key)
+	case common.LESS_THAN_OR_EQUALS:
+		return bytes.Compare(val, pred.key) <= 0
+	case common.GREATER_THAN_OR_EQUALS:
+		return bytes.Compare(val, pred.key) >= 0
+	case common.GREATER_THAN:
+		return bytes.Compare(val, pred.key) > 0
+	default:
+		panic(fmt.Sprintf("unknown Op type %s should have been caught "+
+			"during normalization", pred.Op))
 	}
 }
 
@@ -746,7 +693,7 @@
 		// organized as [type-code][8b-secondary-key][8b-span-id], elements
 		// with the same secondary index field are ordered by span ID.  So we
 		// create a 17-byte key incorporating the span ID from 'prev.'
-		var startId common.SpanId
+		startId := common.INVALID_SPAN_ID
 		switch pred.Op {
 		case common.EQUALS:
 			if pred.Field == common.SPAN_ID {
@@ -759,17 +706,17 @@
 				lg.Debugf("Attempted to use a continuation token with an EQUALS "+
 					"SPAN_ID query. %s.  Setting search id = 0",
 					pred.Predicate.String())
-				startId = 0
+				startId = common.INVALID_SPAN_ID
 			} else {
 				// When doing an EQUALS search on a secondary index, the
 				// results are sorted by span id.
-				startId = prev.Id + 1
+				startId = prev.Id.Next()
 			}
 		case common.LESS_THAN_OR_EQUALS:
 			// Subtract one from the previous span id.  Since the previous
 			// start ID will never be 0 (0 is an illegal span id), we'll never
 			// wrap around when doing this.
-			startId = prev.Id - 1
+			startId = prev.Id.Prev()
 		case common.GREATER_THAN_OR_EQUALS:
 			// We can't add one to the span id, since the previous span ID
 			// might be the maximum value.  So just switch over to using
@@ -785,21 +732,22 @@
 			panic(str)
 		}
 		if pred.Field == common.SPAN_ID {
-			pred.uintKey = uint64(startId)
-			searchKey = makeKey(src.keyPrefix, uint64(startId))
+			pred.key = startId.Val()
+			searchKey = append([]byte{src.keyPrefix}, startId.Val()...)
 		} else {
 			// Start where the previous query left off.  This means adjusting
 			// our uintKey.
-			pred.uintKey, _ = pred.extractRelevantSpanData(prev)
-			searchKey = makeSecondaryKey(src.keyPrefix, pred.uintKey, uint64(startId))
+			pred.key = pred.extractRelevantSpanData(prev)
+			searchKey = append(append([]byte{src.keyPrefix}, pred.key...),
+				startId.Val()...)
 		}
 		if lg.TraceEnabled() {
 			lg.Tracef("Handling continuation token %s for %s.  startId=%d, "+
-				"pred.uintKey=%d\n", prev, pred.Predicate.String(), startId,
-				pred.uintKey)
+				"pred.uintKey=%s\n", prev, pred.Predicate.String(), startId,
+				hex.EncodeToString(pred.key))
 		}
 	} else {
-		searchKey = makeKey(src.keyPrefix, pred.uintKey)
+		searchKey = append([]byte{src.keyPrefix}, pred.key...)
 	}
 	for i := range src.iters {
 		src.iters[i].Seek(searchKey)
@@ -866,7 +814,7 @@
 		var sid common.SpanId
 		if src.keyPrefix == SPAN_ID_INDEX_PREFIX {
 			// The span id maps to the span itself.
-			sid = common.SpanId(keyToInt(key[1:]))
+			sid = common.SpanId(key[1:17])
 			span, err = src.store.shards[shardIdx].decodeSpan(sid, iter.Value())
 			if err != nil {
 				lg.Debugf("Internal error decoding span %s in shard %d: %s\n",
@@ -875,7 +823,7 @@
 			}
 		} else {
 			// With a secondary index, we have to look up the span by id.
-			sid = common.SpanId(keyToInt(key[9:]))
+			sid = common.SpanId(key[9:25])
 			span = src.store.shards[shardIdx].FindSpan(sid)
 			if span == nil {
 				lg.Debugf("Internal error rehydrating span %s in shard %d\n",
@@ -948,7 +896,7 @@
 	// If there are no predicates that are indexed, read rows in order of span id.
 	spanIdPred := common.Predicate{Op: common.GREATER_THAN_OR_EQUALS,
 		Field: common.SPAN_ID,
-		Val:   "0000000000000000",
+		Val:   common.INVALID_SPAN_ID.String(),
 	}
 	spanIdPredData, err := loadPredicateData(&spanIdPred)
 	if err != nil {
diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/datastore_test.go b/htrace-htraced/go/src/org/apache/htrace/htraced/datastore_test.go
index 0c122fd..0caa509 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htraced/datastore_test.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htraced/datastore_test.go
@@ -45,31 +45,28 @@
 }
 
 var SIMPLE_TEST_SPANS []common.Span = []common.Span{
-	common.Span{Id: 1,
+	common.Span{Id: common.TestId("00000000000000000000000000000001"),
 		SpanData: common.SpanData{
 			Begin:       123,
 			End:         456,
 			Description: "getFileDescriptors",
-			TraceId:     999,
 			Parents:     []common.SpanId{},
 			TracerId:    "firstd",
 		}},
-	common.Span{Id: 2,
+	common.Span{Id: common.TestId("00000000000000000000000000000002"),
 		SpanData: common.SpanData{
 			Begin:       125,
 			End:         200,
 			Description: "openFd",
-			TraceId:     999,
-			Parents:     []common.SpanId{1},
+			Parents:     []common.SpanId{common.TestId("00000000000000000000000000000001")},
 			TracerId:    "secondd",
 		}},
-	common.Span{Id: 3,
+	common.Span{Id: common.TestId("00000000000000000000000000000003"),
 		SpanData: common.SpanData{
 			Begin:       200,
 			End:         456,
 			Description: "passFd",
-			TraceId:     999,
-			Parents:     []common.SpanId{1},
+			Parents:     []common.SpanId{common.TestId("00000000000000000000000000000001")},
 			TracerId:    "thirdd",
 		}},
 }
@@ -98,27 +95,27 @@
 	if ht.Store.GetStatistics().NumSpansWritten < uint64(len(SIMPLE_TEST_SPANS)) {
 		t.Fatal()
 	}
-	span := ht.Store.FindSpan(1)
+	span := ht.Store.FindSpan(common.TestId("00000000000000000000000000000001"))
 	if span == nil {
 		t.Fatal()
 	}
-	if span.Id != 1 {
+	if !span.Id.Equal(common.TestId("00000000000000000000000000000001")) {
 		t.Fatal()
 	}
 	common.ExpectSpansEqual(t, &SIMPLE_TEST_SPANS[0], span)
-	children := ht.Store.FindChildren(1, 1)
+	children := ht.Store.FindChildren(common.TestId("00000000000000000000000000000001"), 1)
 	if len(children) != 1 {
 		t.Fatalf("expected 1 child, but got %d\n", len(children))
 	}
-	children = ht.Store.FindChildren(1, 2)
+	children = ht.Store.FindChildren(common.TestId("00000000000000000000000000000001"), 2)
 	if len(children) != 2 {
 		t.Fatalf("expected 2 children, but got %d\n", len(children))
 	}
 	sort.Sort(common.SpanIdSlice(children))
-	if children[0] != 2 {
+	if !children[0].Equal(common.TestId("00000000000000000000000000000002")) {
 		t.Fatal()
 	}
-	if children[1] != 3 {
+	if !children[1].Equal(common.TestId("00000000000000000000000000000003")) {
 		t.Fatal()
 	}
 }
@@ -258,7 +255,7 @@
 			common.Predicate{
 				Op:    common.LESS_THAN_OR_EQUALS,
 				Field: common.SPAN_ID,
-				Val:   "0",
+				Val:   common.TestId("00000000000000000000000000000000").String(),
 			},
 		},
 		Lim: 200,
@@ -269,7 +266,7 @@
 			common.Predicate{
 				Op:    common.LESS_THAN_OR_EQUALS,
 				Field: common.SPAN_ID,
-				Val:   "2",
+				Val:   common.TestId("00000000000000000000000000000002").String(),
 			},
 		},
 		Lim: 200,
@@ -477,7 +474,7 @@
 			common.Predicate{
 				Op:    common.EQUALS,
 				Field: common.SPAN_ID,
-				Val:   "1",
+				Val:   common.TestId("00000000000000000000000000000001").String(),
 			},
 		},
 		Lim:  100,
@@ -491,7 +488,7 @@
 			common.Predicate{
 				Op:    common.LESS_THAN_OR_EQUALS,
 				Field: common.SPAN_ID,
-				Val:   "2",
+				Val:   common.TestId("00000000000000000000000000000002").String(),
 			},
 		},
 		Lim:  100,
diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/hrpc.go b/htrace-htraced/go/src/org/apache/htrace/htraced/hrpc.go
index 71b9625..354d064 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htraced/hrpc.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htraced/hrpc.go
@@ -193,6 +193,10 @@
 		"defaultTrid = %s\n", len(req.Spans), req.DefaultTrid)
 	for i := range req.Spans {
 		span := req.Spans[i]
+		spanIdProblem := span.Id.FindProblem()
+		if spanIdProblem != "" {
+			return errors.New(fmt.Sprintf("Invalid span ID: %s", spanIdProblem))
+		}
 		if span.TracerId == "" {
 			span.TracerId = req.DefaultTrid
 		}
diff --git a/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go b/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go
index cd90038..66f78f8 100644
--- a/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go
+++ b/htrace-htraced/go/src/org/apache/htrace/htraced/rest.go
@@ -76,14 +76,15 @@
 
 func (hand *dataStoreHandler) parseSid(w http.ResponseWriter,
 	str string) (common.SpanId, bool) {
-	val, err := strconv.ParseUint(str, 16, 64)
+	var id common.SpanId
+	err := id.FromString(str)
 	if err != nil {
 		writeError(hand.lg, w, http.StatusBadRequest,
 			fmt.Sprintf("Failed to parse span ID %s: %s", str, err.Error()))
 		w.Write([]byte("Error parsing : " + err.Error()))
-		return 0, false
+		return common.INVALID_SPAN_ID, false
 	}
-	return common.SpanId(val), true
+	return id, true
 }
 
 func (hand *dataStoreHandler) getReqField32(fieldName string, w http.ResponseWriter,
diff --git a/htrace-htraced/go/src/org/apache/htrace/test/random.go b/htrace-htraced/go/src/org/apache/htrace/test/random.go
index 96a3e8b..540ea14 100644
--- a/htrace-htraced/go/src/org/apache/htrace/test/random.go
+++ b/htrace-htraced/go/src/org/apache/htrace/test/random.go
@@ -38,6 +38,15 @@
 	}
 }
 
+func NonZeroRandSpanId(rnd *rand.Rand) common.SpanId {
+	var id common.SpanId
+	id = make([]byte, 16)
+	for i := 0; i < len(id); i++ {
+		id[i] = byte(rnd.Intn(0x100))
+	}
+	return id
+}
+
 func NonZeroRand32(rnd *rand.Rand) int32 {
 	for {
 		r := rnd.Int31()
@@ -60,12 +69,11 @@
 			parents = []common.SpanId{potentialParents[parentIdx].Id}
 		}
 	}
-	return &common.Span{Id: common.SpanId(NonZeroRand64(rnd)),
+	return &common.Span{Id: NonZeroRandSpanId(rnd),
 		SpanData: common.SpanData{
 			Begin:       NonZeroRand64(rnd),
 			End:         NonZeroRand64(rnd),
 			Description: "getFileDescriptors",
-			TraceId:     common.SpanId(NonZeroRand64(rnd)),
 			Parents:     parents,
 			TracerId:    fmt.Sprintf("tracer%d", NonZeroRand32(rnd)),
 		}}
diff --git a/htrace-htraced/go/src/org/apache/htrace/test/util.go b/htrace-htraced/go/src/org/apache/htrace/test/util.go
deleted file mode 100644
index cc058e0..0000000
--- a/htrace-htraced/go/src/org/apache/htrace/test/util.go
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package test
-
-import (
-	"org/apache/htrace/common"
-)
-
-func SpanId(str string) common.SpanId {
-	var spanId common.SpanId
-	err := spanId.FromString(str)
-	if err != nil {
-		panic(err.Error())
-	}
-	return spanId
-}
diff --git a/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedRESTReceiver.java b/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedRESTReceiver.java
index 35d1f09..99f2c50 100644
--- a/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedRESTReceiver.java
+++ b/htrace-htraced/src/test/java/org/apache/htrace/impl/TestHTracedRESTReceiver.java
@@ -28,6 +28,7 @@
 import org.apache.commons.logging.LogFactory;
 import org.apache.htrace.HTraceConfiguration;
 import org.apache.htrace.Span;
+import org.apache.htrace.SpanId;
 import org.apache.htrace.util.DataDir;
 import org.apache.htrace.util.HTracedProcess;
 import org.apache.htrace.util.TestUtil;
@@ -126,8 +127,8 @@
     Span spans[] = new Span[NUM_SPANS];
     for (int i = 0; i < NUM_SPANS; i++) {
       MilliSpan.Builder builder = new MilliSpan.Builder().
-          parents(new long[]{1L}).
-          spanId(i);
+          parents(new SpanId[] { new SpanId(1L, 1L) }).
+          spanId(new SpanId(1L, i));
       if (i == NUM_SPANS - 1) {
         builder.tracerId("specialTrid");
       }
@@ -150,7 +151,8 @@
             for (int i = 0; i < NUM_SPANS; i++) {
               // This is what the REST server expects when querying for a
               // span id.
-              String findSpan = String.format("span/%016x", i);
+              String findSpan = String.format("span/%s",
+                  new SpanId(1L, i).toString());
               ContentResponse response =
                   http.GET(restServerUrl + findSpan);
               String content = processGET(response);
@@ -160,7 +162,8 @@
               }
               LOG.info("Got " + content + " for span " + i);
               MilliSpan dspan = MilliSpan.fromJson(content);
-              assertEquals((long)i, dspan.getSpanId());
+              assertEquals(new SpanId(1, i).toString(),
+                dspan.getSpanId().toString());
               // Every span should have the tracer ID we set in the
               // configuration... except for the last span, which had
               // a custom value set.
diff --git a/htrace-webapp/src/main/web/app/span.js b/htrace-webapp/src/main/web/app/span.js
index b56a2c9..cd87543 100644
--- a/htrace-webapp/src/main/web/app/span.js
+++ b/htrace-webapp/src/main/web/app/span.js
@@ -20,7 +20,7 @@
 var htrace = htrace || {};
 
 // The invalid span ID, which is all zeroes.
-htrace.INVALID_SPAN_ID = "0000000000000000";
+htrace.INVALID_SPAN_ID = "00000000000000000000000000000000";
 
 // Convert an array of htrace.Span models into a comma-separated string.
 htrace.spanModelsToString = function(spans) {
@@ -81,8 +81,7 @@
   // forced to be numbers.
   parse: function(response, options) {
     var span = {};
-    this.set("spanId", response.s ? response.s : htrace.INVALID_SPAN_ID);
-    this.set("traceId", response.i ? response.i : htrace.INVALID_SPAN_ID);
+    this.set("spanId", response.a ? response.a : htrace.INVALID_SPAN_ID);
     this.set("tracerId", response.r ? response.r : "");
     this.set("parents", response.p ? response.p : []);
     this.set("description", response.d ? response.d : "");
@@ -120,10 +119,7 @@
   unparse: function() {
     var obj = { };
     if (!(this.get("spanId") === htrace.INVALID_SPAN_ID)) {
-      obj.s = this.get("spanId");
-    }
-    if (!(this.get("traceId") === htrace.INVALID_SPAN_ID)) {
-      obj.i = this.get("traceId");
+      obj.a = this.get("spanId");
     }
     if (!(this.get("tracerId") === "")) {
       obj.r = this.get("tracerId");
diff --git a/htrace-webapp/src/main/web/app/string.js b/htrace-webapp/src/main/web/app/string.js
index b0dfb74..c9c514b 100644
--- a/htrace-webapp/src/main/web/app/string.js
+++ b/htrace-webapp/src/main/web/app/string.js
@@ -47,16 +47,12 @@
   return moment.utc(val).format("YYYY-MM-DDTHH:mm:ss,SSS");
 };
 
-// Normalize a span ID into the format the server expects to see
-// (no leading 0x).
+// Normalize a span ID into the format the server expects to see--
+// i.e. something like 00000000000000000000000000000000.
 htrace.normalizeSpanId = function(str) {
-  // Strip off the 0x prefix, if there is one.
-  if (str.indexOf("0x") == 0) {
-    str = str.substring(2);
-  }
-  if (str.length != 16) {
+  if (str.length != 36) {
     throw "The length of '" + str + "' was " + str.length +
-      ", but span IDs must be 16 characters long.";
+      ", but span IDs must be 36 characters long.";
   }
   if (str.search(/[^0-9a-fA-F]/) != -1) {
     throw "Span IDs must contain only hexadecimal digits, but '" + str +
diff --git a/htrace-zipkin/src/main/java/org/apache/htrace/zipkin/HTraceToZipkinConverter.java b/htrace-zipkin/src/main/java/org/apache/htrace/zipkin/HTraceToZipkinConverter.java
index f41ec10..3921370 100644
--- a/htrace-zipkin/src/main/java/org/apache/htrace/zipkin/HTraceToZipkinConverter.java
+++ b/htrace-zipkin/src/main/java/org/apache/htrace/zipkin/HTraceToZipkinConverter.java
@@ -110,15 +110,15 @@
     Endpoint ep = new Endpoint(ipv4Address, (short) getPort(serviceName), serviceName);
     List<Annotation> annotationList = createZipkinAnnotations(hTraceSpan, ep);
     List<BinaryAnnotation> binaryAnnotationList = createZipkinBinaryAnnotations(hTraceSpan, ep);
-    zipkinSpan.setTrace_id(hTraceSpan.getTraceId());
+    zipkinSpan.setTrace_id(hTraceSpan.getSpanId().getHigh());
     if (hTraceSpan.getParents().length > 0) {
       if (hTraceSpan.getParents().length > 1) {
         LOG.error("zipkin doesn't support spans with multiple parents.  Omitting " +
             "other parents for " + hTraceSpan);
       }
-      zipkinSpan.setParent_id(hTraceSpan.getParents()[0]);
+      zipkinSpan.setParent_id(hTraceSpan.getParents()[0].getLow());
     }
-    zipkinSpan.setId(hTraceSpan.getSpanId());
+    zipkinSpan.setId(hTraceSpan.getSpanId().getLow());
     zipkinSpan.setName(hTraceSpan.getDescription());
     zipkinSpan.setAnnotations(annotationList);
     zipkinSpan.setBinary_annotations(binaryAnnotationList);
diff --git a/htrace-zipkin/src/test/java/org/apache/htrace/TestHTraceSpanToZipkinSpan.java b/htrace-zipkin/src/test/java/org/apache/htrace/TestHTraceSpanToZipkinSpan.java
index 915bb89..579b98d 100644
--- a/htrace-zipkin/src/test/java/org/apache/htrace/TestHTraceSpanToZipkinSpan.java
+++ b/htrace-zipkin/src/test/java/org/apache/htrace/TestHTraceSpanToZipkinSpan.java
@@ -21,6 +21,7 @@
 
 import org.apache.htrace.HTraceConfiguration;
 import org.apache.htrace.Span;
+import org.apache.htrace.SpanId;
 import org.apache.htrace.Trace;
 import org.apache.htrace.impl.MilliSpan;
 import org.apache.htrace.impl.POJOSpanReceiver;
@@ -48,9 +49,8 @@
 
     Span rootSpan = new MilliSpan.Builder().
             description(ROOT_SPAN_DESC).
-            traceId(1).
-            parents(new long[] { } ).
-            spanId(100).
+            parents(new SpanId[] { } ).
+            spanId(new SpanId(100, 100)).
             tracerId("test").
             begin(System.currentTimeMillis()).
             build();
@@ -72,11 +72,13 @@
   @Test
   public void testHTraceAnnotationTimestamp() throws IOException, InterruptedException {
 
-    String traceName = "testHTraceAnnotationTimestamp";
+    String tracerId = "testHTraceAnnotationTimestamp";
     long startTime = System.currentTimeMillis() * 1000;
     Span ms = new MilliSpan.Builder().
-        description(traceName).traceId(1).parents(new long[] { }).
-        spanId(2).tracerId(traceName).begin(System.currentTimeMillis()).
+        description(tracerId).parents(new SpanId[] { }).
+        spanId(new SpanId(2L, 2L)).
+        tracerId(tracerId).
+        begin(System.currentTimeMillis()).
         build();
 
     Thread.sleep(500);
@@ -117,19 +119,23 @@
   @Test
   public void testHTraceDefaultPort() throws IOException {
     MilliSpan ms = new MilliSpan.Builder().description("test").
-                      traceId(1).parents(new long[] { 2 }).
-                      spanId(3).tracerId("hmaster").
-                      begin(System.currentTimeMillis()).build();
+                      parents(new SpanId[] { new SpanId(2L, 2L) }).
+                      spanId(new SpanId(2L, 3L)).
+                      tracerId("hmaster").
+                      begin(System.currentTimeMillis()).
+                      build();
     com.twitter.zipkin.gen.Span zs = new HTraceToZipkinConverter(12345, (short) -1).convert(ms);
     for (com.twitter.zipkin.gen.Annotation annotation:zs.getAnnotations()) {
       assertEquals((short)60000, annotation.getHost().getPort());
     }
 
     // make sure it's all lower cased
-    ms = new MilliSpan.Builder().description("test").traceId(1).
-                      parents(new long[] {2}).spanId(3).
+    ms = new MilliSpan.Builder().description("test").
+                      parents(new SpanId[] {new SpanId(2, 2)}).
+                      spanId(new SpanId(2, 3)).
                       tracerId("HregIonServer").
-                      begin(System.currentTimeMillis()).build();
+                      begin(System.currentTimeMillis()).
+                      build();
     zs = new HTraceToZipkinConverter(12345, (short) -1).convert(ms);
     for (com.twitter.zipkin.gen.Annotation annotation:zs.getAnnotations()) {
       assertEquals((short)60020, annotation.getHost().getPort());
@@ -137,13 +143,12 @@
   }
 
   private void assertSpansAreEquivalent(Span s, com.twitter.zipkin.gen.Span zs) {
-    assertEquals(s.getTraceId(), zs.getTrace_id());
     assertTrue("zipkin doesn't support multiple parents to a single span.",
           s.getParents().length <= 1);
     if (s.getParents().length == 1) {
-      assertEquals(s.getParents()[0], zs.getParent_id());
+      assertEquals(s.getParents()[0].getLow(), zs.getParent_id());
     }
-    assertEquals(s.getSpanId(), zs.getId());
+    assertEquals(s.getSpanId().getLow(), zs.getId());
     Assert.assertNotNull(zs.getAnnotations());
     if (ROOT_SPAN_DESC.equals(zs.getName())) {
       assertEquals(5, zs.getAnnotations().size());// two start, two stop + one timeline annotation
diff --git a/src/main/site/markdown/index.md b/src/main/site/markdown/index.md
index 9b3034f..6198472 100644
--- a/src/main/site/markdown/index.md
+++ b/src/main/site/markdown/index.md
@@ -184,13 +184,11 @@
 a new trace in the current thread (it will be a
 `ProcessRootMilliSpan`). All of the other `startSpan()` methods take some
 parameter describing the parent span of the span to be created. The
-versions that take a `TraceInfo` or a `long traceId` and `long
-parentId` will mostly be used when continuing a trace over RPC. The
-receiver of the RPC will check the message for the additional two
-`longs` and will call `startSpan()` if they are attached.  The last
-`startSpan()` takes a `Span parent`.  The result of `parent.child()`
-will be used for the new span.  `Span.child()` simply returns a span
-that is a child of `this`.
+version that takes a parent id will mostly be used when continuing a trace over
+RPC. The receiver of the RPC will check the message for the 128-bit parent trace
+ID and will call `startSpan()` if it is attached.  The last `startSpan()` takes
+a `Span parent`.  The result of `parent.child()` will be used for the new span.
+`Span.child()` simply returns a span that is a child of `this`.
 
 ###Span Receivers
 In order to use the tracing information consisting of spans,
@@ -260,11 +258,6 @@
 one. The expectation now is that SpanReceiver implementations
 provide a constructor that takes a single parameter of HTraceConfiguration.
 
-HTRACE-16 refactors the TraceTree interface. The handy
-getRoots() method has been replaced with the less obvious
-getSpansByParent().find(Span.ROOT_SPAN_ID) and .getSpansByParentIdMap() is
-also an invocation of getSpansByParent().find().
-
 
 Publishing to Maven Central
 -------------------------------