Merge "stdlib: fix _get_frame_table_with_id function failure" into main
diff --git a/Android.bp b/Android.bp
index 9af453a..f1d91c9 100644
--- a/Android.bp
+++ b/Android.bp
@@ -12955,7 +12955,7 @@
         "src/trace_processor/metrics/sql/android/unsymbolized_frames.sql",
         "src/trace_processor/metrics/sql/android/wattson_app_startup.sql",
         "src/trace_processor/metrics/sql/android/wattson_rail_relations.sql",
-        "src/trace_processor/metrics/sql/android/wattson_trace_estimate.sql",
+        "src/trace_processor/metrics/sql/android/wattson_trace_rails.sql",
         "src/trace_processor/metrics/sql/chrome/actual_power_by_category.sql",
         "src/trace_processor/metrics/sql/chrome/actual_power_by_rail_mode.sql",
         "src/trace_processor/metrics/sql/chrome/chrome_args_class_names.sql",
@@ -13511,6 +13511,7 @@
 filegroup {
     name: "perfetto_src_trace_processor_tables_tables",
     srcs: [
+        "src/trace_processor/tables/macros_internal.cc",
         "src/trace_processor/tables/table_destructors.cc",
     ],
 }
diff --git a/BUILD b/BUILD
index 277a5de..c5ec15a 100644
--- a/BUILD
+++ b/BUILD
@@ -2207,7 +2207,7 @@
         "src/trace_processor/metrics/sql/android/unsymbolized_frames.sql",
         "src/trace_processor/metrics/sql/android/wattson_app_startup.sql",
         "src/trace_processor/metrics/sql/android/wattson_rail_relations.sql",
-        "src/trace_processor/metrics/sql/android/wattson_trace_estimate.sql",
+        "src/trace_processor/metrics/sql/android/wattson_trace_rails.sql",
     ],
 )
 
@@ -3038,6 +3038,7 @@
 perfetto_filegroup(
     name = "src_trace_processor_tables_tables",
     srcs = [
+        "src/trace_processor/tables/macros_internal.cc",
         "src/trace_processor/tables/macros_internal.h",
         "src/trace_processor/tables/table_destructors.cc",
     ],
diff --git a/protos/perfetto/config/android/windowmanager_config.proto b/protos/perfetto/config/android/windowmanager_config.proto
index 1b16890..b5ea810 100644
--- a/protos/perfetto/config/android/windowmanager_config.proto
+++ b/protos/perfetto/config/android/windowmanager_config.proto
@@ -28,6 +28,9 @@
 
     // Trace state snapshots every time a transaction is committed.
     LOG_FREQUENCY_TRANSACTION = 2;
+
+    // Trace single state snapshots when the data source is started.
+    LOG_FREQUENCY_SINGLE_DUMP = 3;
   }
   optional LogFrequency log_frequency = 1;
 
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index ef84765..702cb98 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -830,6 +830,9 @@
 
     // Trace state snapshots every time a transaction is committed.
     LOG_FREQUENCY_TRANSACTION = 2;
+
+    // Trace single state snapshots when the data source is started.
+    LOG_FREQUENCY_SINGLE_DUMP = 3;
   }
   optional LogFrequency log_frequency = 1;
 
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index 4d6477f..f6d53b8 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -321,8 +321,8 @@
   // Android Wattson app startup metrics.
   optional AndroidWattsonTimePeriodMetric wattson_app_startup = 69;
 
-  // Android Wattson estimate for entire trace metrics.
-  optional AndroidWattsonTimePeriodMetric wattson_trace_estimate = 70;
+  // Android Wattson rail estimate for duration of entire trace.
+  optional AndroidWattsonTimePeriodMetric wattson_trace_rails = 70;
 
   // Android Anomaly metric
   optional AndroidAnomalyMetric android_anomaly = 71;
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index cb01f51..3d91e72 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -3174,8 +3174,8 @@
   // Android Wattson app startup metrics.
   optional AndroidWattsonTimePeriodMetric wattson_app_startup = 69;
 
-  // Android Wattson estimate for entire trace metrics.
-  optional AndroidWattsonTimePeriodMetric wattson_trace_estimate = 70;
+  // Android Wattson rail estimate for duration of entire trace.
+  optional AndroidWattsonTimePeriodMetric wattson_trace_rails = 70;
 
   // Android Anomaly metric
   optional AndroidAnomalyMetric android_anomaly = 71;
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index 9d0d775..eeeacc5 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -830,6 +830,9 @@
 
     // Trace state snapshots every time a transaction is committed.
     LOG_FREQUENCY_TRANSACTION = 2;
+
+    // Trace single state snapshots when the data source is started.
+    LOG_FREQUENCY_SINGLE_DUMP = 3;
   }
   optional LogFrequency log_frequency = 1;
 
@@ -12465,7 +12468,6 @@
     GESTURE_SCROLL_END = 4;
     GESTURE_TAP = 5;
     GESTURE_TAP_CANCEL = 6;
-    INERTIAL_GESTURE_SCROLL_UPDATE = 7;
   }
 
   // The type of input corresponding to this `ChromeLatencyInfo`.
diff --git a/protos/perfetto/trace/track_event/chrome_latency_info.proto b/protos/perfetto/trace/track_event/chrome_latency_info.proto
index 276abea..9f791f5 100644
--- a/protos/perfetto/trace/track_event/chrome_latency_info.proto
+++ b/protos/perfetto/trace/track_event/chrome_latency_info.proto
@@ -94,7 +94,6 @@
     GESTURE_SCROLL_END = 4;
     GESTURE_TAP = 5;
     GESTURE_TAP_CANCEL = 6;
-    INERTIAL_GESTURE_SCROLL_UPDATE = 7;
   }
 
   // The type of input corresponding to this `ChromeLatencyInfo`.
diff --git a/python/generators/trace_processor_table/serialize.py b/python/generators/trace_processor_table/serialize.py
index 24c243b..c347685 100644
--- a/python/generators/trace_processor_table/serialize.py
+++ b/python/generators/trace_processor_table/serialize.py
@@ -80,7 +80,7 @@
 
   def const_row_ref_getter(self) -> Optional[str]:
     return f'''ColumnType::{self.name}::type {self.name}() const {{
-      return table_->{self.name}()[row_number_];
+      return table()->{self.name}()[row_number_];
     }}'''
 
   def row_ref_getter(self) -> Optional[str]:
@@ -169,7 +169,7 @@
     name = self.name
     return f'''
     ColumnType::{self.name}::type {name}() const {{
-      const auto& col = table_->{name}();
+      const auto& col = table()->{name}();
       return col.GetAtIdx(
         iterator_.StorageIndexForColumn(col.index_in_table()));
     }}
@@ -385,7 +385,7 @@
 
    private:
     {self.table_name}* mutable_table() const {{
-      return const_cast<{self.table_name}*>(table_);
+      return const_cast<{self.table_name}*>(table());
     }}
   }};
   static_assert(std::is_trivially_destructible_v<RowReference>,
@@ -495,17 +495,14 @@
   class Iterator : public ConstIterator {{
     public:
      RowReference row_reference() const {{
-       return RowReference(mutable_table_, CurrentRowNumber());
+       return {{const_cast<{self.table_name}*>(table()), CurrentRowNumber()}};
      }}
 
     private:
      friend class {self.table_name};
 
      explicit Iterator({self.table_name}* table, Table::Iterator iterator)
-        : ConstIterator(table, std::move(iterator)),
-          mutable_table_(table) {{}}
-
-     {self.table_name}* mutable_table_ = nullptr;
+        : ConstIterator(table, std::move(iterator)) {{}}
   }};
       '''
 
@@ -518,15 +515,15 @@
         ColumnSerializer.extend_parent_param_arg, delimiter=', ')
     delim = ',' if params else ''
     return f'''
-  static std::unique_ptr<Table> ExtendParent(
+  static std::unique_ptr<{self.table_name}> ExtendParent(
       const {self.parent_class_name}& parent{delim}
       {params}) {{
-    return std::unique_ptr<Table>(new {self.table_name}(
+    return std::unique_ptr<{self.table_name}>(new {self.table_name}(
         parent.string_pool(), parent, RowMap(0, parent.row_count()){delim}
         {args}));
   }}
 
-  static std::unique_ptr<Table> SelectAndExtendParent(
+  static std::unique_ptr<{self.table_name}> SelectAndExtendParent(
       const {self.parent_class_name}& parent,
       std::vector<{self.parent_class_name}::RowNumber> parent_overlay{delim}
       {params}) {{
@@ -534,7 +531,7 @@
     for (uint32_t i = 0; i < parent_overlay.size(); ++i) {{
       prs_untyped[i] = parent_overlay[i].row_number();
     }}
-    return std::unique_ptr<Table>(new {self.table_name}(
+    return std::unique_ptr<{self.table_name}>(new {self.table_name}(
         parent.string_pool(), parent, RowMap(std::move(prs_untyped)){delim}
         {args}));
   }}
@@ -660,18 +657,28 @@
   Iterator IterateRows() {{ return Iterator(this, Table::IterateRows()); }}
 
   ConstIterator FilterToIterator(const Query& q) const {{
-    return ConstIterator(
-      this, ApplyAndIterateRows(QueryToRowMap(q)));
+    return ConstIterator(this, QueryToIterator(q));
   }}
 
   Iterator FilterToIterator(const Query& q) {{
-    return Iterator(this, ApplyAndIterateRows(QueryToRowMap(q)));
+    return Iterator(this, QueryToIterator(q));
   }}
 
   void ShrinkToFit() {{
     {self.foreach_col(ColumnSerializer.shrink_to_fit)}
   }}
 
+  ConstRowReference operator[](uint32_t r) const {{
+    return ConstRowReference(this, r);
+  }}
+  RowReference operator[](uint32_t r) {{ return RowReference(this, r); }}
+  ConstRowReference operator[](RowNumber r) const {{
+    return ConstRowReference(this, r.row_number());
+  }}
+  RowReference operator[](RowNumber r) {{
+    return RowReference(this, r.row_number());
+  }}
+
   std::optional<ConstRowReference> FindById(Id find_id) const {{
     std::optional<uint32_t> row = id().IndexOf(find_id);
     return row ? std::make_optional(ConstRowReference(this, *row))
diff --git a/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
index cd4ad17..1e2da15 100644
--- a/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/src/trace_processor/BUILD.gn b/src/trace_processor/BUILD.gn
index aba2613..53e8a1c 100644
--- a/src/trace_processor/BUILD.gn
+++ b/src/trace_processor/BUILD.gn
@@ -56,9 +56,12 @@
     "../../gn:default_deps",
     "../../include/perfetto/ext/trace_processor:export_json",
     "../base",
+    "containers",
     "importers/json:minimal",
     "storage",
+    "tables",
     "types",
+    "util",
   ]
   public_deps = [ "../../include/perfetto/ext/trace_processor:export_json" ]
 }
@@ -238,6 +241,18 @@
       ]
     }
   }
+
+  # Shell target which does not link all the extra libraryes linked by
+  # trace processor shell (e.g. httpd, libprotobuf etc.). Use for binary size
+  # analysis of the trace processor library.
+  executable("trace_processor_minimal_shell") {
+    deps = [
+      ":lib",
+      "../../gn:default_deps",
+      "util",
+    ]
+    sources = [ "minimal_shell.cc" ]
+  }
 }  # if (enable_perfetto_trace_processor_sqlite)
 
 perfetto_unittest_source_set("top_level_unittests") {
@@ -268,6 +283,7 @@
       "importers/common",
       "importers/proto:minimal",
       "storage",
+      "tables",
       "types",
     ]
   }
diff --git a/src/trace_processor/db/column.h b/src/trace_processor/db/column.h
index 0bbd929..1e3016c 100644
--- a/src/trace_processor/db/column.h
+++ b/src/trace_processor/db/column.h
@@ -214,62 +214,6 @@
   // Gets the value of the Column at the given |row|.
   SqlValue Get(uint32_t row) const { return GetAtIdx(overlay().Get(row)); }
 
-  // Returns the row containing the given value in the Column.
-  std::optional<uint32_t> IndexOf(SqlValue value) const {
-    switch (type_) {
-      // TODO(lalitm): investigate whether we could make this more efficient
-      // by first checking the type of the column and comparing explicitly
-      // based on that type.
-      case ColumnType::kInt32:
-      case ColumnType::kUint32:
-      case ColumnType::kInt64:
-      case ColumnType::kDouble:
-      case ColumnType::kString: {
-        for (uint32_t i = 0; i < overlay().size(); i++) {
-          if (compare::SqlValue(Get(i), value) == 0)
-            return i;
-        }
-        return std::nullopt;
-      }
-      case ColumnType::kId: {
-        if (value.type != SqlValue::Type::kLong)
-          return std::nullopt;
-        return overlay().RowOf(static_cast<uint32_t>(value.long_value));
-      }
-      case ColumnType::kDummy:
-        PERFETTO_FATAL("IndexOf not allowed on dummy column");
-    }
-    PERFETTO_FATAL("For GCC");
-  }
-
-  // Returns the minimum value in this column. Returns std::nullopt if this
-  // column is empty.
-  std::optional<SqlValue> Min() const {
-    if (overlay().empty())
-      return std::nullopt;
-
-    if (IsSorted())
-      return Get(0);
-
-    Iterator b(this, 0);
-    Iterator e(this, overlay().size());
-    return *std::min_element(b, e, &compare::SqlValueComparator);
-  }
-
-  // Returns the minimum value in this column. Returns std::nullopt if this
-  // column is empty.
-  std::optional<SqlValue> Max() const {
-    if (overlay().empty())
-      return std::nullopt;
-
-    if (IsSorted())
-      return Get(overlay().size() - 1);
-
-    Iterator b(this, 0);
-    Iterator e(this, overlay().size());
-    return *std::max_element(b, e, &compare::SqlValueComparator);
-  }
-
   // Returns the backing RowMap for this Column.
   // This function is defined out of line because of a circular dependency
   // between |Table| and |Column|.
diff --git a/src/trace_processor/db/query_executor_benchmark.cc b/src/trace_processor/db/query_executor_benchmark.cc
index 95b64ac..5fe039a 100644
--- a/src/trace_processor/db/query_executor_benchmark.cc
+++ b/src/trace_processor/db/query_executor_benchmark.cc
@@ -103,6 +103,14 @@
   return base::SplitString(table_csv, "\n");
 }
 
+template <typename It>
+double CountRows(It it) {
+  double i = 0;
+  for (; it; ++it, ++i) {
+  }
+  return i;
+}
+
 StringPool::Id StripAndIntern(StringPool& pool, const std::string& data) {
   std::string res = base::StripSuffix(base::StripPrefix(data, "\""), "\"");
   return pool.InternString(base::StringView(res));
@@ -237,16 +245,16 @@
   Query q;
   q.constraints = c;
   for (auto _ : state) {
-    benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
+    benchmark::DoNotOptimize(table.table_.FilterToIterator(q));
   }
   state.counters["s/row"] =
       benchmark::Counter(static_cast<double>(table.table_.row_count()),
                          benchmark::Counter::kIsIterationInvariantRate |
                              benchmark::Counter::kInvert);
-  state.counters["s/out"] = benchmark::Counter(
-      static_cast<double>(table.table_.QueryToRowMap(q).size()),
-      benchmark::Counter::kIsIterationInvariantRate |
-          benchmark::Counter::kInvert);
+  state.counters["s/out"] =
+      benchmark::Counter(CountRows(table.table_.FilterToIterator(q)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
 }
 
 void BenchmarkSliceTableSort(benchmark::State& state,
@@ -266,32 +274,32 @@
     ExpectedFrameTimelineTableForBenchmark& table,
     Query q) {
   for (auto _ : state) {
-    benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
+    benchmark::DoNotOptimize(table.table_.FilterToIterator(q));
   }
   state.counters["s/row"] =
       benchmark::Counter(static_cast<double>(table.table_.row_count()),
                          benchmark::Counter::kIsIterationInvariantRate |
                              benchmark::Counter::kInvert);
-  state.counters["s/out"] = benchmark::Counter(
-      static_cast<double>(table.table_.QueryToRowMap(q).size()),
-      benchmark::Counter::kIsIterationInvariantRate |
-          benchmark::Counter::kInvert);
+  state.counters["s/out"] =
+      benchmark::Counter(CountRows(table.table_.FilterToIterator(q)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
 }
 
 void BenchmarkFtraceEventTableQuery(benchmark::State& state,
                                     FtraceEventTableForBenchmark& table,
                                     Query q) {
   for (auto _ : state) {
-    benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
+    benchmark::DoNotOptimize(table.table_.FilterToIterator(q));
   }
   state.counters["s/row"] =
       benchmark::Counter(static_cast<double>(table.table_.row_count()),
                          benchmark::Counter::kIsIterationInvariantRate |
                              benchmark::Counter::kInvert);
-  state.counters["s/out"] = benchmark::Counter(
-      static_cast<double>(table.table_.QueryToRowMap(q).size()),
-      benchmark::Counter::kIsIterationInvariantRate |
-          benchmark::Counter::kInvert);
+  state.counters["s/out"] =
+      benchmark::Counter(CountRows(table.table_.FilterToIterator(q)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
 }
 
 void BenchmarkFtraceEventTableSort(benchmark::State& state,
@@ -441,16 +449,16 @@
   Query q;
   q.constraints = {c};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
+    benchmark::DoNotOptimize(table.table_.FilterToIterator(q));
   }
   state.counters["s/row"] =
       benchmark::Counter(static_cast<double>(table.table_.row_count()),
                          benchmark::Counter::kIsIterationInvariantRate |
                              benchmark::Counter::kInvert);
-  state.counters["s/out"] = benchmark::Counter(
-      static_cast<double>(table.table_.QueryToRowMap(q).size()),
-      benchmark::Counter::kIsIterationInvariantRate |
-          benchmark::Counter::kInvert);
+  state.counters["s/out"] =
+      benchmark::Counter(CountRows(table.table_.FilterToIterator(q)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
 }
 BENCHMARK(BM_QEDenseNullFilter);
 
@@ -461,16 +469,16 @@
   Query q;
   q.constraints = {c};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(table.table_.QueryToRowMap(q));
+    benchmark::DoNotOptimize(table.table_.FilterToIterator(q));
   }
   state.counters["s/row"] =
       benchmark::Counter(static_cast<double>(table.table_.row_count()),
                          benchmark::Counter::kIsIterationInvariantRate |
                              benchmark::Counter::kInvert);
-  state.counters["s/out"] = benchmark::Counter(
-      static_cast<double>(table.table_.QueryToRowMap(q).size()),
-      benchmark::Counter::kIsIterationInvariantRate |
-          benchmark::Counter::kInvert);
+  state.counters["s/out"] =
+      benchmark::Counter(CountRows(table.table_.FilterToIterator(q)),
+                         benchmark::Counter::kIsIterationInvariantRate |
+                             benchmark::Counter::kInvert);
 }
 BENCHMARK(BM_QEDenseNullFilterIsNull);
 
diff --git a/src/trace_processor/db/table.cc b/src/trace_processor/db/table.cc
index 50f383a..d581bc0 100644
--- a/src/trace_processor/db/table.cc
+++ b/src/trace_processor/db/table.cc
@@ -20,10 +20,12 @@
 #include <cstdint>
 #include <memory>
 #include <optional>
+#include <string>
 #include <utility>
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/public/compiler.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/ref_counted.h"
@@ -140,84 +142,6 @@
   return {string_pool_, row_count_, std::move(cols), {}};
 }
 
-RowMap Table::TryApplyIndex(const std::vector<Constraint>& c_vec,
-                            uint32_t& cs_offset) const {
-  RowMap rm(0, row_count());
-
-  // Prework - use indexes if possible and decide which one.
-  std::vector<uint32_t> maybe_idx_cols;
-  for (const auto& c : c_vec) {
-    // Id columns shouldn't use index.
-    if (columns()[c.col_idx].IsId()) {
-      break;
-    }
-    // The operation has to support sorting.
-    if (!IsSortingOp(c.op)) {
-      break;
-    }
-    maybe_idx_cols.push_back(c.col_idx);
-
-    // For the next col to be able to use index, all previous constraints have
-    // to be equality.
-    if (c.op != FilterOp::kEq) {
-      break;
-    }
-  }
-
-  OrderedIndices o_idxs;
-  while (!maybe_idx_cols.empty()) {
-    if (auto maybe_idx = GetIndex(maybe_idx_cols)) {
-      o_idxs = *maybe_idx;
-      break;
-    }
-    maybe_idx_cols.pop_back();
-  }
-
-  // If we can't use the index just apply constraints in a standard way.
-  if (maybe_idx_cols.empty()) {
-    return rm;
-  }
-
-  for (uint32_t i = 0; i < maybe_idx_cols.size(); i++) {
-    const Constraint& c = c_vec[i];
-    Range r =
-        ChainForColumn(c.col_idx).OrderedIndexSearch(c.op, c.value, o_idxs);
-    o_idxs.data += r.start;
-    o_idxs.size = r.size();
-  }
-  cs_offset = static_cast<uint32_t>(maybe_idx_cols.size());
-
-  std::vector<uint32_t> res_vec(o_idxs.data, o_idxs.data + o_idxs.size);
-  if (res_vec.size() < kIndexVectorThreshold) {
-    std::sort(res_vec.begin(), res_vec.end());
-    return RowMap(std::move(res_vec));
-  }
-  return RowMap(BitVector::FromUnsortedIndexVector(res_vec));
-}
-
-RowMap Table::ApplyIdJoinConstraints(const std::vector<Constraint>& cs,
-                                     uint32_t& cs_offset) const {
-  uint32_t i = 1;
-  uint32_t row = static_cast<uint32_t>(cs.front().value.AsLong());
-  if (row >= row_count()) {
-    return RowMap();
-  }
-  for (; i < cs.size(); i++) {
-    const Constraint& c = cs[i];
-    switch (ChainForColumn(c.col_idx).SingleSearch(c.op, c.value, row)) {
-      case SingleSearchResult::kNoMatch:
-        return RowMap();
-      case SingleSearchResult::kMatch:
-        continue;
-      case SingleSearchResult::kNeedsFullSearch:
-        cs_offset = i;
-        return RowMap(row, row + 1);
-    }
-  }
-  cs_offset = static_cast<uint32_t>(cs.size());
-  return RowMap(row, row + 1);
-}
-
 RowMap Table::QueryToRowMap(const Query& q) const {
   // We need to delay creation of the chains to this point because of Chrome
   // does not want the binary size overhead of including the chain
@@ -361,6 +285,41 @@
   }
 }
 
+base::Status Table::CreateIndex(const std::string& name,
+                                std::vector<uint32_t> col_idxs,
+                                bool replace) {
+  Query q;
+  for (const auto& c : col_idxs) {
+    q.orders.push_back({c});
+  }
+  std::vector<uint32_t> index = QueryToRowMap(q).TakeAsIndexVector();
+
+  auto it = std::find_if(
+      indexes_.begin(), indexes_.end(),
+      [&name](const ColumnIndex& idx) { return idx.name == name; });
+  if (it == indexes_.end()) {
+    indexes_.push_back({name, std::move(col_idxs), std::move(index)});
+    return base::OkStatus();
+  }
+  if (replace) {
+    it->columns = std::move(col_idxs);
+    it->index = std::move(index);
+    return base::OkStatus();
+  }
+  return base::ErrStatus("Index of this name already exists on this table.");
+}
+
+base::Status Table::DropIndex(const std::string& name) {
+  auto it = std::find_if(
+      indexes_.begin(), indexes_.end(),
+      [&name](const ColumnIndex& idx) { return idx.name == name; });
+  if (it == indexes_.end()) {
+    return base::ErrStatus("Index '%s' not found.", name.c_str());
+  }
+  indexes_.erase(it);
+  return base::OkStatus();
+}
+
 void Table::ApplyDistinct(const Query& q, RowMap* rm) const {
   const auto& ob = q.orders;
   PERFETTO_DCHECK(!ob.empty());
@@ -415,4 +374,82 @@
   *rm = RowMap(std::move(idx));
 }
 
+RowMap Table::TryApplyIndex(const std::vector<Constraint>& c_vec,
+                            uint32_t& cs_offset) const {
+  RowMap rm(0, row_count());
+
+  // Prework - use indexes if possible and decide which one.
+  std::vector<uint32_t> maybe_idx_cols;
+  for (const auto& c : c_vec) {
+    // Id columns shouldn't use index.
+    if (columns()[c.col_idx].IsId()) {
+      break;
+    }
+    // The operation has to support sorting.
+    if (!IsSortingOp(c.op)) {
+      break;
+    }
+    maybe_idx_cols.push_back(c.col_idx);
+
+    // For the next col to be able to use index, all previous constraints have
+    // to be equality.
+    if (c.op != FilterOp::kEq) {
+      break;
+    }
+  }
+
+  OrderedIndices o_idxs;
+  while (!maybe_idx_cols.empty()) {
+    if (auto maybe_idx = GetIndex(maybe_idx_cols)) {
+      o_idxs = *maybe_idx;
+      break;
+    }
+    maybe_idx_cols.pop_back();
+  }
+
+  // If we can't use the index just apply constraints in a standard way.
+  if (maybe_idx_cols.empty()) {
+    return rm;
+  }
+
+  for (uint32_t i = 0; i < maybe_idx_cols.size(); i++) {
+    const Constraint& c = c_vec[i];
+    Range r =
+        ChainForColumn(c.col_idx).OrderedIndexSearch(c.op, c.value, o_idxs);
+    o_idxs.data += r.start;
+    o_idxs.size = r.size();
+  }
+  cs_offset = static_cast<uint32_t>(maybe_idx_cols.size());
+
+  std::vector<uint32_t> res_vec(o_idxs.data, o_idxs.data + o_idxs.size);
+  if (res_vec.size() < kIndexVectorThreshold) {
+    std::sort(res_vec.begin(), res_vec.end());
+    return RowMap(std::move(res_vec));
+  }
+  return RowMap(BitVector::FromUnsortedIndexVector(res_vec));
+}
+
+RowMap Table::ApplyIdJoinConstraints(const std::vector<Constraint>& cs,
+                                     uint32_t& cs_offset) const {
+  uint32_t i = 1;
+  uint32_t row = static_cast<uint32_t>(cs.front().value.AsLong());
+  if (row >= row_count()) {
+    return RowMap();
+  }
+  for (; i < cs.size(); i++) {
+    const Constraint& c = cs[i];
+    switch (ChainForColumn(c.col_idx).SingleSearch(c.op, c.value, row)) {
+      case SingleSearchResult::kNoMatch:
+        return RowMap();
+      case SingleSearchResult::kMatch:
+        continue;
+      case SingleSearchResult::kNeedsFullSearch:
+        cs_offset = i;
+        return RowMap(row, row + 1);
+    }
+  }
+  cs_offset = static_cast<uint32_t>(cs.size());
+  return RowMap(row, row + 1);
+}
+
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/db/table.h b/src/trace_processor/db/table.h
index 44ec7d7..1b6e848 100644
--- a/src/trace_processor/db/table.h
+++ b/src/trace_processor/db/table.h
@@ -141,17 +141,18 @@
   Table(Table&& other) noexcept { *this = std::move(other); }
   Table& operator=(Table&& other) noexcept;
 
-  // Return a chain corresponding to a given column.
-  const column::DataLayerChain& ChainForColumn(uint32_t col_idx) const {
-    return *chains_[col_idx];
-  }
-
   // Filters and sorts the tables with the arguments specified, returning the
   // result as a RowMap.
   RowMap QueryToRowMap(const Query&) const;
 
   // Applies the RowMap |rm| onto this table and returns an iterator over the
   // resulting rows.
+  Iterator QueryToIterator(const Query& q) const {
+    return ApplyAndIterateRows(QueryToRowMap(q));
+  }
+
+  // Do not add any further uses.
+  // TODO(lalitm): make this private.
   Iterator ApplyAndIterateRows(RowMap rm) const {
     return Iterator(this, std::move(rm));
   }
@@ -171,37 +172,13 @@
 
   // Adds an index onto column.
   // Returns an error if index already exists and `!replace`.
-  base::Status SetIndex(const std::string& name,
-                        std::vector<uint32_t> col_idxs,
-                        std::vector<uint32_t> index,
-                        bool replace = false) {
-    auto it = std::find_if(
-        indexes_.begin(), indexes_.end(),
-        [&name](const ColumnIndex& idx) { return idx.name == name; });
-    if (it == indexes_.end()) {
-      indexes_.push_back({name, std::move(col_idxs), std::move(index)});
-      return base::OkStatus();
-    }
-    if (replace) {
-      it->columns = std::move(col_idxs);
-      it->index = std::move(index);
-      return base::OkStatus();
-    }
-    return base::ErrStatus("Index of this name already exists on this table.");
-  }
+  base::Status CreateIndex(const std::string& name,
+                           std::vector<uint32_t> col_idxs,
+                           bool replace);
 
   // Removes index from the table.
   // Returns an error if index doesn't exist.
-  base::Status DropIndex(const std::string& name) {
-    auto it = std::find_if(
-        indexes_.begin(), indexes_.end(),
-        [&name](const ColumnIndex& idx) { return idx.name == name; });
-    if (it == indexes_.end()) {
-      return base::ErrStatus("Index '%s' not found.", name.c_str());
-    }
-    indexes_.erase(it);
-    return base::OkStatus();
-  }
+  base::Status DropIndex(const std::string& name);
 
   // Sorts the table using the specified order by constraints.
   Table Sort(const std::vector<Order>&) const;
@@ -224,8 +201,9 @@
   }
 
   uint32_t row_count() const { return row_count_; }
-  StringPool* string_pool() const { return string_pool_; }
   const std::vector<ColumnLegacy>& columns() const { return columns_; }
+  StringPool* string_pool() const { return string_pool_; }
+
   const std::vector<RefPtr<column::StorageLayer>>& storage_layers() const {
     return storage_layers_;
   }
@@ -233,8 +211,6 @@
     return null_layers_;
   }
 
-  bool HasNullOrOverlayLayer(uint32_t col_idx) const;
-
  protected:
   Table(StringPool*,
         uint32_t row_count,
@@ -271,6 +247,7 @@
 
  private:
   friend class ColumnLegacy;
+  friend class QueryExecutor;
 
   struct ColumnIndex {
     std::string name;
@@ -278,6 +255,8 @@
     std::vector<uint32_t> index;
   };
 
+  bool HasNullOrOverlayLayer(uint32_t col_idx) const;
+
   void CreateChains() const;
 
   Table CopyExceptOverlays() const;
@@ -290,6 +269,10 @@
   RowMap ApplyIdJoinConstraints(const std::vector<Constraint>&,
                                 uint32_t& cs_offset) const;
 
+  const column::DataLayerChain& ChainForColumn(uint32_t col_idx) const {
+    return *chains_[col_idx];
+  }
+
   StringPool* string_pool_ = nullptr;
   uint32_t row_count_ = 0;
   std::vector<ColumnStorageOverlay> overlays_;
diff --git a/src/trace_processor/db/typed_column.h b/src/trace_processor/db/typed_column.h
index 1f6c280..dd5bd13 100644
--- a/src/trace_processor/db/typed_column.h
+++ b/src/trace_processor/db/typed_column.h
@@ -17,11 +17,20 @@
 #ifndef SRC_TRACE_PROCESSOR_DB_TYPED_COLUMN_H_
 #define SRC_TRACE_PROCESSOR_DB_TYPED_COLUMN_H_
 
+#include <cstdint>
+#include <optional>
+#include <type_traits>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
 #include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/column/types.h"
+#include "src/trace_processor/db/column_storage.h"
 #include "src/trace_processor/db/typed_column_internal.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // TypedColumn<T>
 //
@@ -81,22 +90,18 @@
   template <bool is_string = TH::is_string>
   typename std::enable_if<is_string, NullTermStringView>::type GetString(
       uint32_t row) const {
-    return GetStringAtIdx(overlay().Get(row));
+    return string_pool().Get(storage().Get((overlay().Get(row))));
   }
 
   // Sets the data in the column at index |row|.
   void Set(uint32_t row, non_optional_type v) {
-    SetAtIdx(overlay().Get(row), v);
+    auto serialized = Serializer::Serialize(v);
+    mutable_storage()->Set(overlay().Get(row), serialized);
   }
 
   // Inserts the value at the end of the column.
   void Append(T v) { mutable_storage()->Append(Serializer::Serialize(v)); }
 
-  // Returns the row containing the given value in the Column.
-  std::optional<uint32_t> IndexOf(sql_value_type v) const {
-    return ColumnLegacy::IndexOf(ToSqlValue(v));
-  }
-
   std::vector<T> ToVectorForTesting() const {
     std::vector<T> result(overlay().size());
     for (uint32_t i = 0; i < overlay().size(); ++i)
@@ -131,36 +136,6 @@
 
   // Cast a Column to TypedColumn or crash if that is unsafe.
   static TypedColumn<T>* FromColumn(ColumnLegacy* column) {
-    return FromColumnInternal<TypedColumn<T>>(column);
-  }
-
-  // Cast a Column to TypedColumn or crash if that is unsafe.
-  static const TypedColumn<T>* FromColumn(const ColumnLegacy* column) {
-    return FromColumnInternal<const TypedColumn<T>>(column);
-  }
-
-  // Public for use by macro tables.
-  void SetAtIdx(uint32_t idx, non_optional_type v) {
-    auto serialized = Serializer::Serialize(v);
-    mutable_storage()->Set(idx, serialized);
-  }
-
-  // Public for use by macro tables.
-  T GetAtIdx(uint32_t idx) const {
-    return Serializer::Deserialize(TH::Get(storage(), idx));
-  }
-
-  template <bool is_string = TH::is_string>
-  typename std::enable_if<is_string, NullTermStringView>::type GetStringAtIdx(
-      uint32_t idx) const {
-    return string_pool().Get(storage().Get(idx));
-  }
-
- private:
-  friend class Table;
-
-  template <typename Output, typename Input>
-  static Output* FromColumnInternal(Input* column) {
     // While casting from a base to derived without constructing as a derived is
     // technically UB, in practice, this is at the heart of protozero (see
     // Message::BeginNestedMessage) so we use it here.
@@ -169,13 +144,21 @@
 
     if (column->template IsColumnType<stored_type>() &&
         (column->IsNullable() == TH::is_optional) && !column->IsId()) {
-      return static_cast<Output*>(column);
+      return static_cast<TypedColumn<T>*>(column);
     } else {
       PERFETTO_FATAL("Unsafe to convert Column TypedColumn (%s)",
                      column->name());
     }
   }
 
+  // Public for use by macro tables.
+  T GetAtIdx(uint32_t idx) const {
+    return Serializer::Deserialize(TH::Get(storage(), idx));
+  }
+
+ private:
+  friend class Table;
+
   const ColumnStorage<stored_type>& storage() const {
     return ColumnLegacy::storage<stored_type>();
   }
@@ -203,23 +186,6 @@
   // Public for use by macro tables.
   Id GetAtIdx(uint32_t idx) const { return Id(idx); }
 
-  // Static cast a Column to IdColumn or crash if that is likely to be
-  // unsafe.
-  static const IdColumn<Id>* FromColumn(const ColumnLegacy* column) {
-    // While casting from a base to derived without constructing as a derived is
-    // technically UB, in practice, this is at the heart of protozero (see
-    // Message::BeginNestedMessage) so we use it here.
-    static_assert(sizeof(IdColumn<Id>) == sizeof(ColumnLegacy),
-                  "TypedColumn cannot introduce extra state.");
-
-    if (column->IsId()) {
-      return static_cast<const IdColumn<Id>*>(column);
-    } else {
-      PERFETTO_FATAL("Unsafe to convert Column to IdColumn (%s)",
-                     column->name());
-    }
-  }
-
   // Helper functions to create constraints for the given value.
   Constraint eq(uint32_t v) const { return eq_value(SqlValue::Long(v)); }
   Constraint gt(uint32_t v) const { return gt_value(SqlValue::Long(v)); }
@@ -232,7 +198,6 @@
   friend class Table;
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_DB_TYPED_COLUMN_H_
diff --git a/src/trace_processor/export_json.cc b/src/trace_processor/export_json.cc
index 3cd3a5d..f0a0479 100644
--- a/src/trace_processor/export_json.cc
+++ b/src/trace_processor/export_json.cc
@@ -15,50 +15,65 @@
  */
 
 #include "perfetto/ext/trace_processor/export_json.h"
-#include "src/trace_processor/export_json.h"
-
-#include <stdio.h>
-#include <sstream>
 
 #include <algorithm>
-#include <cinttypes>
 #include <cmath>
+#include <cstdint>
+#include <cstdio>
 #include <cstring>
 #include <deque>
 #include <limits>
+#include <map>
 #include <memory>
+#include <optional>
+#include <sstream>
+#include <string>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include <vector>
 
 #include "perfetto/base/build_config.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
-#include "src/trace_processor/importers/json/json_utils.h"
+#include "perfetto/public/compiler.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
+#include "src/trace_processor/export_json.h"
 #include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
 #include "src/trace_processor/trace_processor_storage_impl.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
+#include "src/trace_processor/util/status_macros.h"
 
 #if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
+#include <json/config.h>
 #include <json/reader.h>
+#include <json/value.h>
 #include <json/writer.h>
 #endif
 
-namespace perfetto {
-namespace trace_processor {
-namespace json {
+namespace perfetto::trace_processor::json {
 
 namespace {
 
 class FileWriter : public OutputWriter {
  public:
-  FileWriter(FILE* file) : file_(file) {}
+  explicit FileWriter(FILE* file) : file_(file) {}
   ~FileWriter() override { fflush(file_); }
 
-  util::Status AppendString(const std::string& s) override {
+  base::Status AppendString(const std::string& s) override {
     size_t written =
         fwrite(s.data(), sizeof(std::string::value_type), s.size(), file_);
     if (written != s.size())
-      return util::ErrStatus("Error writing to file: %d", ferror(file_));
-    return util::OkStatus();
+      return base::ErrStatus("Error writing to file: %d", ferror(file_));
+    return base::OkStatus();
   }
 
  private:
@@ -101,54 +116,24 @@
                LabelFilterPredicate label_filter)
       : storage_(storage),
         args_builder_(storage_),
-        writer_(output, argument_filter, metadata_filter, label_filter) {}
+        writer_(output,
+                std::move(argument_filter),
+                std::move(metadata_filter),
+                std::move(label_filter)) {}
 
-  util::Status Export() {
-    util::Status status = MapUniquePidsAndTids();
-    if (!status.ok())
-      return status;
-
-    status = ExportThreadNames();
-    if (!status.ok())
-      return status;
-
-    status = ExportProcessNames();
-    if (!status.ok())
-      return status;
-
-    status = ExportProcessUptimes();
-    if (!status.ok())
-      return status;
-
-    status = ExportSlices();
-    if (!status.ok())
-      return status;
-
-    status = ExportFlows();
-    if (!status.ok())
-      return status;
-
-    status = ExportRawEvents();
-    if (!status.ok())
-      return status;
-
-    status = ExportCpuProfileSamples();
-    if (!status.ok())
-      return status;
-
-    status = ExportMetadata();
-    if (!status.ok())
-      return status;
-
-    status = ExportStats();
-    if (!status.ok())
-      return status;
-
-    status = ExportMemorySnapshots();
-    if (!status.ok())
-      return status;
-
-    return util::OkStatus();
+  base::Status Export() {
+    RETURN_IF_ERROR(MapUniquePidsAndTids());
+    RETURN_IF_ERROR(ExportThreadNames());
+    RETURN_IF_ERROR(ExportProcessNames());
+    RETURN_IF_ERROR(ExportProcessUptimes());
+    RETURN_IF_ERROR(ExportSlices());
+    RETURN_IF_ERROR(ExportFlows());
+    RETURN_IF_ERROR(ExportRawEvents());
+    RETURN_IF_ERROR(ExportCpuProfileSamples());
+    RETURN_IF_ERROR(ExportMetadata());
+    RETURN_IF_ERROR(ExportStats());
+    RETURN_IF_ERROR(ExportMemorySnapshots());
+    return base::OkStatus();
   }
 
  private:
@@ -159,9 +144,9 @@
                       MetadataFilterPredicate metadata_filter,
                       LabelFilterPredicate label_filter)
         : output_(output),
-          argument_filter_(argument_filter),
-          metadata_filter_(metadata_filter),
-          label_filter_(label_filter),
+          argument_filter_(std::move(argument_filter)),
+          metadata_filter_(std::move(metadata_filter)),
+          label_filter_(std::move(label_filter)),
           first_event_(true) {
       Json::StreamWriterBuilder b;
       b.settings_["indentation"] = "";
@@ -520,12 +505,12 @@
         args_sets_.resize(1, empty_value_);
         return;
       }
-      args_sets_.resize(arg_table.arg_set_id()[count - 1] + 1, empty_value_);
+      args_sets_.resize(arg_table[count - 1].arg_set_id() + 1, empty_value_);
 
-      for (uint32_t i = 0; i < count; ++i) {
-        ArgSetId set_id = arg_table.arg_set_id()[i];
-        const char* key = arg_table.key().GetString(i).c_str();
-        Variadic value = storage_->GetArgValue(i);
+      for (auto it = arg_table.IterateRows(); it; ++it) {
+        ArgSetId set_id = it.arg_set_id();
+        const char* key = storage->GetString(it.key()).c_str();
+        Variadic value = storage_->GetArgValue(it.row_number().row_number());
         AppendArg(set_id, key, VariadicToJson(value));
       }
       PostprocessArgs();
@@ -591,11 +576,11 @@
         }
         std::string key_part = parts.cur_token();
         size_t bracketpos = key_part.find('[');
-        if (bracketpos == key_part.npos) {  // A single item
+        if (bracketpos == std::string::npos) {  // A single item
           target = &(*target)[key_part];
         } else {  // A list item
           target = &(*target)[key_part.substr(0, bracketpos)];
-          while (bracketpos != key_part.npos) {
+          while (bracketpos != std::string::npos) {
             // We constructed this string from an int earlier in trace_processor
             // so it shouldn't be possible for this (or the StringToUInt32
             // below) to fail.
@@ -667,10 +652,11 @@
     const Json::Value neg_inf_value_;
   };
 
-  util::Status MapUniquePidsAndTids() {
+  base::Status MapUniquePidsAndTids() {
     const auto& process_table = storage_->process_table();
-    for (UniquePid upid = 0; upid < process_table.row_count(); upid++) {
-      uint32_t exported_pid = process_table.pid()[upid];
+    for (auto it = process_table.IterateRows(); it; ++it) {
+      UniquePid upid = it.id().value;
+      uint32_t exported_pid = it.pid();
       auto it_and_inserted =
           exported_pids_to_upids_.emplace(exported_pid, upid);
       if (!it_and_inserted.second) {
@@ -681,16 +667,18 @@
     }
 
     const auto& thread_table = storage_->thread_table();
-    for (UniqueTid utid = 0; utid < thread_table.row_count(); utid++) {
+    for (auto it = thread_table.IterateRows(); it; ++it) {
+      UniqueTid utid = it.id().value;
+
       uint32_t exported_pid = 0;
-      std::optional<UniquePid> upid = thread_table.upid()[utid];
+      std::optional<UniquePid> upid = it.upid();
       if (upid) {
         auto exported_pid_it = upids_to_exported_pids_.find(*upid);
         PERFETTO_DCHECK(exported_pid_it != upids_to_exported_pids_.end());
         exported_pid = exported_pid_it->second;
       }
 
-      uint32_t exported_tid = thread_table.tid()[utid];
+      uint32_t exported_tid = it.tid();
       auto it_and_inserted = exported_pids_and_tids_to_utids_.emplace(
           std::make_pair(exported_pid, exported_tid), utid);
       if (!it_and_inserted.second) {
@@ -701,81 +689,76 @@
       utids_to_exported_pids_and_tids_.emplace(
           utid, std::make_pair(exported_pid, exported_tid));
     }
-
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ExportThreadNames() {
+  base::Status ExportThreadNames() {
     const auto& thread_table = storage_->thread_table();
-    for (UniqueTid utid = 0; utid < thread_table.row_count(); ++utid) {
-      auto opt_name = thread_table.name()[utid];
+    for (auto it = thread_table.IterateRows(); it; ++it) {
+      auto opt_name = it.name();
       if (opt_name.has_value()) {
+        UniqueTid utid = it.id().value;
         const char* thread_name = GetNonNullString(storage_, opt_name);
         auto pid_and_tid = UtidToPidAndTid(utid);
         writer_.WriteMetadataEvent("thread_name", "name", thread_name,
                                    pid_and_tid.first, pid_and_tid.second);
       }
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ExportProcessNames() {
+  base::Status ExportProcessNames() {
     const auto& process_table = storage_->process_table();
-    for (UniquePid upid = 0; upid < process_table.row_count(); ++upid) {
-      auto opt_name = process_table.name()[upid];
+    for (auto it = process_table.IterateRows(); it; ++it) {
+      auto opt_name = it.name();
       if (opt_name.has_value()) {
+        UniquePid upid = it.id().value;
         const char* process_name = GetNonNullString(storage_, opt_name);
         writer_.WriteMetadataEvent("process_name", "name", process_name,
                                    UpidToPid(upid), /*tid=*/0);
       }
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   // For each process it writes an approximate uptime, based on the process'
   // start time and the last slice in the entire trace. This same last slice is
   // used with all processes, so the process could have ended earlier.
-  util::Status ExportProcessUptimes() {
+  base::Status ExportProcessUptimes() {
     int64_t last_timestamp_ns = FindLastSliceTimestamp();
     if (last_timestamp_ns <= 0)
-      return util::OkStatus();
+      return base::OkStatus();
 
     const auto& process_table = storage_->process_table();
-    for (UniquePid upid = 0; upid < process_table.row_count(); ++upid) {
-      std::optional<int64_t> start_timestamp_ns =
-          process_table.start_ts()[upid];
-      if (!start_timestamp_ns.has_value())
+    for (auto it = process_table.IterateRows(); it; ++it) {
+      std::optional<int64_t> start_timestamp_ns = it.start_ts();
+      if (!start_timestamp_ns.has_value()) {
         continue;
+      }
 
+      UniquePid upid = it.id().value;
       int64_t process_uptime_seconds =
           (last_timestamp_ns - start_timestamp_ns.value()) /
-          (1000 * 1000 * 1000);
-
+          (1000l * 1000 * 1000);
       writer_.WriteMetadataEvent("process_uptime_seconds", "uptime",
                                  std::to_string(process_uptime_seconds).c_str(),
                                  UpidToPid(upid), /*tid=*/0);
     }
 
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   // Returns the last slice's end timestamp for the entire trace. If no slices
   // are found 0 is returned.
   int64_t FindLastSliceTimestamp() {
     int64_t last_ts = 0;
-    const auto& slices = storage_->slice_table();
-    for (uint32_t i = 0; i < slices.row_count(); ++i) {
-      int64_t duration_ns = slices.dur()[i];
-      int64_t timestamp_ns = slices.ts()[i];
-
-      if (duration_ns + timestamp_ns > last_ts) {
-        last_ts = duration_ns + timestamp_ns;
-      }
+    for (auto it = storage_->slice_table().IterateRows(); it; ++it) {
+      last_ts = std::max(last_ts, it.ts() + it.dur());
     }
     return last_ts;
   }
 
-  util::Status ExportSlices() {
+  base::Status ExportSlices() {
     const auto& slices = storage_->slice_table();
     for (auto it = slices.IterateRows(); it; ++it) {
       // Skip slices with empty category - these are ftrace/system slices that
@@ -865,11 +848,10 @@
         }
       }
 
-      auto opt_thread_track_row = thread_track.id().IndexOf(TrackId{track_id});
-
-      if (opt_thread_track_row && !is_child_track) {
+      auto tt_rr = thread_track.FindById(track_id);
+      if (tt_rr && !is_child_track) {
         // Synchronous (thread) slice or instant event.
-        UniqueTid utid = thread_track.utid()[*opt_thread_track_row];
+        UniqueTid utid = tt_rr->utid();
         auto pid_and_tid = UtidToPidAndTid(utid);
         event["pid"] = Json::Int(pid_and_tid.first);
         event["tid"] = Json::Int(pid_and_tid.second);
@@ -915,12 +897,12 @@
       } else if (is_child_track ||
                  (legacy_chrome_track && track_args->isMember("trace_id"))) {
         // Async event slice.
-        auto opt_process_row = process_track.id().IndexOf(TrackId{track_id});
+        auto pt_rr = process_track.FindById(track_id);
         if (legacy_chrome_track) {
           // Legacy async tracks are always process-associated and have args.
-          PERFETTO_DCHECK(opt_process_row);
+          PERFETTO_DCHECK(pt_rr);
           PERFETTO_DCHECK(track_args);
-          uint32_t upid = process_track.upid()[*opt_process_row];
+          UniquePid upid = pt_rr->upid();
           uint32_t exported_pid = UpidToPid(upid);
           event["pid"] = Json::Int(exported_pid);
           event["tid"] =
@@ -949,14 +931,14 @@
             event["id"] = base::Uint64ToHexString(trace_id);
           }
         } else {
-          if (opt_thread_track_row) {
-            UniqueTid utid = thread_track.utid()[*opt_thread_track_row];
+          if (tt_rr) {
+            UniqueTid utid = tt_rr->utid();
             auto pid_and_tid = UtidToPidAndTid(utid);
             event["pid"] = Json::Int(pid_and_tid.first);
             event["tid"] = Json::Int(pid_and_tid.second);
             event["id2"]["local"] = base::Uint64ToHexString(track_id.value);
-          } else if (opt_process_row) {
-            uint32_t upid = process_track.upid()[*opt_process_row];
+          } else if (pt_rr) {
+            uint32_t upid = pt_rr->upid();
             uint32_t exported_pid = UpidToPid(upid);
             event["pid"] = Json::Int(exported_pid);
             event["tid"] =
@@ -1035,9 +1017,9 @@
             event["ph"] = legacy_phase;
           }
 
-          auto opt_process_row = process_track.id().IndexOf(TrackId{track_id});
-          if (opt_process_row.has_value()) {
-            uint32_t upid = process_track.upid()[*opt_process_row];
+          auto pt_rr = process_track.FindById(track_id);
+          if (pt_rr.has_value()) {
+            UniquePid upid = pt_rr->upid();
             uint32_t exported_pid = UpidToPid(upid);
             event["pid"] = Json::Int(exported_pid);
             event["tid"] =
@@ -1051,31 +1033,30 @@
         }
       }
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   std::optional<Json::Value> CreateFlowEventV1(uint32_t flow_id,
                                                SliceId slice_id,
-                                               std::string name,
-                                               std::string cat,
+                                               const std::string& name,
+                                               const std::string& cat,
                                                Json::Value args,
                                                bool flow_begin) {
     const auto& slices = storage_->slice_table();
     const auto& thread_tracks = storage_->thread_track_table();
 
-    auto opt_slice_idx = slices.id().IndexOf(slice_id);
-    if (!opt_slice_idx)
+    auto opt_slice_rr = slices.FindById(slice_id);
+    if (!opt_slice_rr)
       return std::nullopt;
-    uint32_t slice_idx = opt_slice_idx.value();
+    auto slice_rr = opt_slice_rr.value();
 
-    TrackId track_id = storage_->slice_table().track_id()[slice_idx];
-    auto opt_thread_track_idx = thread_tracks.id().IndexOf(track_id);
+    TrackId track_id = slice_rr.track_id();
+    auto opt_ttrr = thread_tracks.FindById(track_id);
     // catapult only supports flow events attached to thread-track slices
-    if (!opt_thread_track_idx)
+    if (!opt_ttrr)
       return std::nullopt;
 
-    UniqueTid utid = thread_tracks.utid()[opt_thread_track_idx.value()];
-    auto pid_and_tid = UtidToPidAndTid(utid);
+    auto pid_and_tid = UtidToPidAndTid(opt_ttrr->utid());
     Json::Value event;
     event["id"] = flow_id;
     event["pid"] = Json::Int(pid_and_tid.first);
@@ -1083,7 +1064,7 @@
     event["cat"] = cat;
     event["name"] = name;
     event["ph"] = (flow_begin ? "s" : "f");
-    event["ts"] = Json::Int64(slices.ts()[slice_idx] / 1000);
+    event["ts"] = Json::Int64(slice_rr.ts() / 1000);
     if (!flow_begin) {
       event["bp"] = "e";
     }
@@ -1091,13 +1072,13 @@
     return std::move(event);
   }
 
-  util::Status ExportFlows() {
+  base::Status ExportFlows() {
     const auto& flow_table = storage_->flow_table();
     const auto& slice_table = storage_->slice_table();
-    for (uint32_t i = 0; i < flow_table.row_count(); i++) {
-      SliceId slice_out = flow_table.slice_out()[i];
-      SliceId slice_in = flow_table.slice_in()[i];
-      uint32_t arg_set_id = flow_table.arg_set_id()[i];
+    for (auto it = flow_table.IterateRows(); it; ++it) {
+      SliceId slice_out = it.slice_out();
+      SliceId slice_in = it.slice_in();
+      uint32_t arg_set_id = it.arg_set_id();
 
       std::string cat;
       std::string name;
@@ -1110,16 +1091,13 @@
         args.removeMember("name");
         args.removeMember("cat");
       } else {
-        auto opt_slice_out_idx = slice_table.id().IndexOf(slice_out);
-        PERFETTO_DCHECK(opt_slice_out_idx.has_value());
-        std::optional<StringId> cat_id =
-            slice_table.category()[opt_slice_out_idx.value()];
-        std::optional<StringId> name_id =
-            slice_table.name()[opt_slice_out_idx.value()];
-        cat = GetNonNullString(storage_, cat_id);
-        name = GetNonNullString(storage_, name_id);
+        auto rr = slice_table.FindById(slice_out);
+        PERFETTO_DCHECK(rr.has_value());
+        cat = GetNonNullString(storage_, rr->category());
+        name = GetNonNullString(storage_, rr->name());
       }
 
+      uint32_t i = it.row_number().row_number();
       auto out_event = CreateFlowEventV1(i, slice_out, name, cat, args,
                                          /* flow_begin = */ true);
       auto in_event = CreateFlowEventV1(i, slice_in, name, cat, std::move(args),
@@ -1130,23 +1108,22 @@
         writer_.WriteCommonEvent(in_event.value());
       }
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  Json::Value ConvertLegacyRawEventToJson(uint32_t index) {
-    const auto& events = storage_->raw_table();
-
+  Json::Value ConvertLegacyRawEventToJson(
+      const tables::RawTable::ConstIterator& it) {
     Json::Value event;
-    event["ts"] = Json::Int64(events.ts()[index] / 1000);
+    event["ts"] = Json::Int64(it.ts() / 1000);
 
-    UniqueTid utid = static_cast<UniqueTid>(events.utid()[index]);
+    UniqueTid utid = static_cast<UniqueTid>(it.utid());
     auto pid_and_tid = UtidToPidAndTid(utid);
     event["pid"] = Json::Int(pid_and_tid.first);
     event["tid"] = Json::Int(pid_and_tid.second);
 
     // Raw legacy events store all other params in the arg set. Make a copy of
     // the converted args here, parse, and then remove the legacy params.
-    event["args"] = args_builder_.GetArgs(events.arg_set_id()[index]);
+    event["args"] = args_builder_.GetArgs(it.arg_set_id());
     const Json::Value& legacy_args = event["args"][kLegacyEventArgsKey];
 
     PERFETTO_DCHECK(legacy_args.isMember(kLegacyEventCategoryKey));
@@ -1210,7 +1187,7 @@
     return event;
   }
 
-  util::Status ExportRawEvents() {
+  base::Status ExportRawEvents() {
     std::optional<StringId> raw_legacy_event_key_id =
         storage_->string_pool().GetId("track_event.legacy_event");
     std::optional<StringId> raw_legacy_system_trace_event_id =
@@ -1221,34 +1198,40 @@
         storage_->string_pool().GetId("chrome_event.metadata");
 
     const auto& events = storage_->raw_table();
-    for (uint32_t i = 0; i < events.row_count(); ++i) {
-      if (raw_legacy_event_key_id &&
-          events.name()[i] == *raw_legacy_event_key_id) {
-        Json::Value event = ConvertLegacyRawEventToJson(i);
+    for (auto it = events.IterateRows(); it; ++it) {
+      if (raw_legacy_event_key_id && it.name() == *raw_legacy_event_key_id) {
+        Json::Value event = ConvertLegacyRawEventToJson(it);
         writer_.WriteCommonEvent(event);
       } else if (raw_legacy_system_trace_event_id &&
-                 events.name()[i] == *raw_legacy_system_trace_event_id) {
-        Json::Value args = args_builder_.GetArgs(events.arg_set_id()[i]);
+                 it.name() == *raw_legacy_system_trace_event_id) {
+        Json::Value args = args_builder_.GetArgs(it.arg_set_id());
         PERFETTO_DCHECK(args.isMember("data"));
         writer_.AddSystemTraceData(args["data"].asString());
       } else if (raw_legacy_user_trace_event_id &&
-                 events.name()[i] == *raw_legacy_user_trace_event_id) {
-        Json::Value args = args_builder_.GetArgs(events.arg_set_id()[i]);
+                 it.name() == *raw_legacy_user_trace_event_id) {
+        Json::Value args = args_builder_.GetArgs(it.arg_set_id());
         PERFETTO_DCHECK(args.isMember("data"));
         writer_.AddUserTraceData(args["data"].asString());
       } else if (raw_chrome_metadata_event_id &&
-                 events.name()[i] == *raw_chrome_metadata_event_id) {
-        Json::Value args = args_builder_.GetArgs(events.arg_set_id()[i]);
+                 it.name() == *raw_chrome_metadata_event_id) {
+        Json::Value args = args_builder_.GetArgs(it.arg_set_id());
         writer_.MergeMetadata(args);
       }
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   class MergedProfileSamplesEmitter {
    public:
     // The TraceFormatWriter must outlive this instance.
-    MergedProfileSamplesEmitter(TraceFormatWriter& writer) : writer_(writer) {}
+    explicit MergedProfileSamplesEmitter(TraceFormatWriter& writer)
+        : writer_(writer) {}
+
+    MergedProfileSamplesEmitter(const MergedProfileSamplesEmitter&) = delete;
+    MergedProfileSamplesEmitter& operator=(const MergedProfileSamplesEmitter&) =
+        delete;
+    MergedProfileSamplesEmitter& operator=(
+        MergedProfileSamplesEmitter&& value) = delete;
 
     uint64_t AddEventForUtid(UniqueTid utid,
                              int64_t ts,
@@ -1290,15 +1273,21 @@
       Sample(TraceFormatWriter& writer,
              CallsiteId callsite_id,
              int64_t ts,
-             const Json::Value& event)
+             Json::Value event)
           : writer_(writer),
             callsite_id_(callsite_id),
             begin_ts_(ts),
             end_ts_(ts),
-            event_(event),
+            event_(std::move(event)),
             event_id_(MergedProfileSamplesEmitter::GenerateNewEventId()),
             sample_count_(1) {}
 
+      Sample(const Sample&) = delete;
+      Sample& operator=(const Sample&) = delete;
+
+      Sample(Sample&&) = delete;
+      Sample& operator=(Sample&& value) = delete;
+
       ~Sample() {
         // No point writing a merged event if we only got a single sample
         // as ExportCpuProfileSamples will already be writing the instant event.
@@ -1341,11 +1330,6 @@
       uint64_t event_id() const { return event_id_; }
       CallsiteId callsite_id() const { return callsite_id_; }
 
-     public:
-      Sample(const Sample&) = delete;
-      Sample& operator=(const Sample&) = delete;
-      Sample& operator=(Sample&& value) = delete;
-
       TraceFormatWriter& writer_;
       CallsiteId callsite_id_;
       int64_t begin_ts_;
@@ -1355,26 +1339,20 @@
       size_t sample_count_;
     };
 
-    MergedProfileSamplesEmitter(const MergedProfileSamplesEmitter&) = delete;
-    MergedProfileSamplesEmitter& operator=(const MergedProfileSamplesEmitter&) =
-        delete;
-    MergedProfileSamplesEmitter& operator=(
-        MergedProfileSamplesEmitter&& value) = delete;
-
     std::unordered_map<UniqueTid, Sample> current_events_;
     TraceFormatWriter& writer_;
   };
 
-  util::Status ExportCpuProfileSamples() {
+  base::Status ExportCpuProfileSamples() {
     MergedProfileSamplesEmitter merged_sample_emitter(writer_);
 
     const tables::CpuProfileStackSampleTable& samples =
         storage_->cpu_profile_stack_sample_table();
-    for (uint32_t i = 0; i < samples.row_count(); ++i) {
+    for (auto it = samples.IterateRows(); it; ++it) {
       Json::Value event;
-      event["ts"] = Json::Int64(samples.ts()[i] / 1000);
+      event["ts"] = Json::Int64(it.ts() / 1000);
 
-      UniqueTid utid = static_cast<UniqueTid>(samples.utid()[i]);
+      UniqueTid utid = static_cast<UniqueTid>(it.utid());
       auto pid_and_tid = UtidToPidAndTid(utid);
       event["pid"] = Json::Int(pid_and_tid.first);
       event["tid"] = Json::Int(pid_and_tid.second);
@@ -1394,38 +1372,38 @@
       const auto& mappings = storage_->stack_profile_mapping_table();
 
       std::vector<std::string> callstack;
-      std::optional<CallsiteId> opt_callsite_id = samples.callsite_id()[i];
+      std::optional<CallsiteId> opt_callsite_id = it.callsite_id();
 
       while (opt_callsite_id) {
         CallsiteId callsite_id = *opt_callsite_id;
-        uint32_t callsite_row = *callsites.id().IndexOf(callsite_id);
+        auto callsite_row = *callsites.FindById(callsite_id);
 
-        FrameId frame_id = callsites.frame_id()[callsite_row];
-        uint32_t frame_row = *frames.id().IndexOf(frame_id);
+        FrameId frame_id = callsite_row.frame_id();
+        auto frame_row = *frames.FindById(frame_id);
 
-        MappingId mapping_id = frames.mapping()[frame_row];
-        uint32_t mapping_row = *mappings.id().IndexOf(mapping_id);
+        MappingId mapping_id = frame_row.mapping();
+        auto mapping_row = *mappings.FindById(mapping_id);
 
         NullTermStringView symbol_name;
-        auto opt_symbol_set_id = frames.symbol_set_id()[frame_row];
+        auto opt_symbol_set_id = frame_row.symbol_set_id();
         if (opt_symbol_set_id) {
           symbol_name = storage_->GetString(
-              storage_->symbol_table().name()[*opt_symbol_set_id]);
+              storage_->symbol_table()[*opt_symbol_set_id].name());
         }
 
         base::StackString<1024> frame_entry(
             "%s - %s [%s]\n",
             (symbol_name.empty()
                  ? base::Uint64ToHexString(
-                       static_cast<uint64_t>(frames.rel_pc()[frame_row]))
+                       static_cast<uint64_t>(frame_row.rel_pc()))
                        .c_str()
                  : symbol_name.c_str()),
-            GetNonNullString(storage_, mappings.name()[mapping_row]),
-            GetNonNullString(storage_, mappings.build_id()[mapping_row]));
+            GetNonNullString(storage_, mapping_row.name()),
+            GetNonNullString(storage_, mapping_row.build_id()));
 
         callstack.emplace_back(frame_entry.ToStdString());
 
-        opt_callsite_id = callsites.parent_id()[callsite_row];
+        opt_callsite_id = callsite_row.parent_id();
       }
 
       std::string merged_callstack;
@@ -1435,7 +1413,7 @@
       }
 
       event["args"]["frames"] = merged_callstack;
-      event["args"]["process_priority"] = samples.process_priority()[i];
+      event["args"]["process_priority"] = it.process_priority();
 
       // TODO(oysteine): Used for backwards compatibility with the memlog
       // pipeline, should remove once we've switched to looking directly at the
@@ -1446,12 +1424,11 @@
       // For now, only do this when the trace has already been symbolized i.e.
       // are not directly output by Chrome, to avoid interfering with other
       // processing pipelines.
-      std::optional<CallsiteId> opt_current_callsite_id =
-          samples.callsite_id()[i];
+      std::optional<CallsiteId> opt_current_callsite_id = it.callsite_id();
 
       if (opt_current_callsite_id && storage_->symbol_table().row_count() > 0) {
         uint64_t parent_event_id = merged_sample_emitter.AddEventForUtid(
-            utid, samples.ts()[i], *opt_current_callsite_id, event);
+            utid, it.ts(), *opt_current_callsite_id, event);
         event["id"] = base::Uint64ToHexString(parent_event_id);
       } else {
         event["id"] = base::Uint64ToHexString(
@@ -1461,14 +1438,11 @@
       writer_.WriteCommonEvent(event);
     }
 
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ExportMetadata() {
+  base::Status ExportMetadata() {
     const auto& trace_metadata = storage_->metadata_table();
-    const auto& keys = trace_metadata.name();
-    const auto& int_values = trace_metadata.int_value();
-    const auto& str_values = trace_metadata.str_value();
 
     // Create a mapping from key string ids to keys.
     std::unordered_map<StringId, metadata::KeyId> key_map;
@@ -1477,8 +1451,8 @@
       key_map[id] = static_cast<metadata::KeyId>(i);
     }
 
-    for (uint32_t pos = 0; pos < trace_metadata.row_count(); pos++) {
-      auto key_it = key_map.find(keys[pos]);
+    for (auto it = trace_metadata.IterateRows(); it; ++it) {
+      auto key_it = key_map.find(it.name());
       // Skip exporting dynamic entries; the cr-xxx entries that come from
       // the ChromeMetadata proto message are already exported from the raw
       // table.
@@ -1491,45 +1465,48 @@
       switch (static_cast<size_t>(key)) {
         case metadata::benchmark_description:
           writer_.AppendTelemetryMetadataString(
-              "benchmarkDescriptions", str_values.GetString(pos).c_str());
+              "benchmarkDescriptions",
+              storage_->string_pool().Get(*it.str_value()).c_str());
           break;
 
         case metadata::benchmark_name:
           writer_.AppendTelemetryMetadataString(
-              "benchmarks", str_values.GetString(pos).c_str());
+              "benchmarks",
+              storage_->string_pool().Get(*it.str_value()).c_str());
           break;
 
         case metadata::benchmark_start_time_us:
           writer_.SetTelemetryMetadataTimestamp("benchmarkStart",
-                                                *int_values[pos]);
+                                                *it.int_value());
           break;
 
         case metadata::benchmark_had_failures:
-          writer_.AppendTelemetryMetadataBool("hadFailures", *int_values[pos]);
+          writer_.AppendTelemetryMetadataBool("hadFailures", *it.int_value());
           break;
 
         case metadata::benchmark_label:
           writer_.AppendTelemetryMetadataString(
-              "labels", str_values.GetString(pos).c_str());
+              "labels", storage_->string_pool().Get(*it.str_value()).c_str());
           break;
 
         case metadata::benchmark_story_name:
           writer_.AppendTelemetryMetadataString(
-              "stories", str_values.GetString(pos).c_str());
+              "stories", storage_->string_pool().Get(*it.str_value()).c_str());
           break;
 
         case metadata::benchmark_story_run_index:
           writer_.AppendTelemetryMetadataInt("storysetRepeats",
-                                             *int_values[pos]);
+                                             *it.int_value());
           break;
 
         case metadata::benchmark_story_run_time_us:
-          writer_.SetTelemetryMetadataTimestamp("traceStart", *int_values[pos]);
+          writer_.SetTelemetryMetadataTimestamp("traceStart", *it.int_value());
           break;
 
         case metadata::benchmark_story_tags:  // repeated
           writer_.AppendTelemetryMetadataString(
-              "storyTags", str_values.GetString(pos).c_str());
+              "storyTags",
+              storage_->string_pool().Get(*it.str_value()).c_str());
           break;
 
         default:
@@ -1537,10 +1514,10 @@
           break;
       }
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ExportStats() {
+  base::Status ExportStats() {
     const auto& stats = storage_->stats();
 
     for (size_t idx = 0; idx < stats::kNumKeys; idx++) {
@@ -1552,59 +1529,54 @@
       }
     }
 
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ExportMemorySnapshots() {
+  base::Status ExportMemorySnapshots() {
     const auto& memory_snapshots = storage_->memory_snapshot_table();
     std::optional<StringId> private_footprint_id =
         storage_->string_pool().GetId("chrome.private_footprint_kb");
     std::optional<StringId> peak_resident_set_id =
         storage_->string_pool().GetId("chrome.peak_resident_set_kb");
 
-    for (uint32_t memory_index = 0; memory_index < memory_snapshots.row_count();
-         ++memory_index) {
+    for (auto sit = memory_snapshots.IterateRows(); sit; ++sit) {
       Json::Value event_base;
 
       event_base["ph"] = "v";
       event_base["cat"] = "disabled-by-default-memory-infra";
-      auto snapshot_id = memory_snapshots.id()[memory_index].value;
-      event_base["id"] = base::Uint64ToHexString(snapshot_id);
-      int64_t snapshot_ts = memory_snapshots.timestamp()[memory_index];
+      auto snapshot_id = sit.id();
+      event_base["id"] = base::Uint64ToHexString(snapshot_id.value);
+      int64_t snapshot_ts = sit.timestamp();
       event_base["ts"] = Json::Int64(snapshot_ts / 1000);
       // TODO(crbug:1116359): Add dump type to the snapshot proto
       // to properly fill event_base["name"]
       event_base["name"] = "periodic_interval";
-      event_base["args"]["dumps"]["level_of_detail"] = GetNonNullString(
-          storage_, memory_snapshots.detail_level()[memory_index]);
+      event_base["args"]["dumps"]["level_of_detail"] =
+          GetNonNullString(storage_, sit.detail_level());
 
       // Export OS dump events for processes with relevant data.
       const auto& process_table = storage_->process_table();
-      for (UniquePid upid = 0; upid < process_table.row_count(); ++upid) {
-        Json::Value event =
-            FillInProcessEventDetails(event_base, process_table.pid()[upid]);
+      for (auto pit = process_table.IterateRows(); pit; ++pit) {
+        Json::Value event = FillInProcessEventDetails(event_base, pit.pid());
         Json::Value& totals = event["args"]["dumps"]["process_totals"];
 
         const auto& process_counters = storage_->process_counter_track_table();
 
-        for (uint32_t counter_index = 0;
-             counter_index < process_counters.row_count(); ++counter_index) {
-          if (process_counters.upid()[counter_index] != upid)
+        for (auto it = process_counters.IterateRows(); it; ++it) {
+          if (it.upid() != pit.id().value)
             continue;
-          TrackId track_id = process_counters.id()[counter_index];
-          if (private_footprint_id && (process_counters.name()[counter_index] ==
-                                       private_footprint_id)) {
+          TrackId track_id = it.id();
+          if (private_footprint_id && (it.name() == private_footprint_id)) {
             totals["private_footprint_bytes"] = base::Uint64ToHexStringNoPrefix(
                 GetCounterValue(track_id, snapshot_ts));
           } else if (peak_resident_set_id &&
-                     (process_counters.name()[counter_index] ==
-                      peak_resident_set_id)) {
+                     (it.name() == peak_resident_set_id)) {
             totals["peak_resident_set_size"] = base::Uint64ToHexStringNoPrefix(
                 GetCounterValue(track_id, snapshot_ts));
           }
         }
 
-        auto process_args_id = process_table.arg_set_id()[upid];
+        auto process_args_id = pit.arg_set_id();
         if (process_args_id) {
           const Json::Value* process_args =
               &args_builder_.GetArgs(process_args_id);
@@ -1621,49 +1593,33 @@
             smaps_table.row_count() > 0
                 ? &event["args"]["dumps"]["process_mmaps"]["vm_regions"]
                 : nullptr;
-        for (uint32_t smaps_index = 0; smaps_index < smaps_table.row_count();
-             ++smaps_index) {
-          if (smaps_table.upid()[smaps_index] != upid)
+        for (auto it = smaps_table.IterateRows(); it; ++it) {
+          if (it.upid() != pit.id().value)
             continue;
-          if (smaps_table.ts()[smaps_index] != snapshot_ts)
+          if (it.ts() != snapshot_ts)
             continue;
           Json::Value region;
-          region["mf"] =
-              GetNonNullString(storage_, smaps_table.file_name()[smaps_index]);
-          region["pf"] =
-              Json::Int64(smaps_table.protection_flags()[smaps_index]);
+          region["mf"] = GetNonNullString(storage_, it.file_name());
+          region["pf"] = Json::Int64(it.protection_flags());
           region["sa"] = base::Uint64ToHexStringNoPrefix(
-              static_cast<uint64_t>(smaps_table.start_address()[smaps_index]));
+              static_cast<uint64_t>(it.start_address()));
           region["sz"] = base::Uint64ToHexStringNoPrefix(
-              static_cast<uint64_t>(smaps_table.size_kb()[smaps_index]) * 1024);
-          region["ts"] =
-              Json::Int64(smaps_table.module_timestamp()[smaps_index]);
-          region["id"] = GetNonNullString(
-              storage_, smaps_table.module_debugid()[smaps_index]);
-          region["df"] = GetNonNullString(
-              storage_, smaps_table.module_debug_path()[smaps_index]);
+              static_cast<uint64_t>(it.size_kb()) * 1024);
+          region["ts"] = Json::Int64(it.module_timestamp());
+          region["id"] = GetNonNullString(storage_, it.module_debugid());
+          region["df"] = GetNonNullString(storage_, it.module_debug_path());
           region["bs"]["pc"] = base::Uint64ToHexStringNoPrefix(
-              static_cast<uint64_t>(
-                  smaps_table.private_clean_resident_kb()[smaps_index]) *
-              1024);
+              static_cast<uint64_t>(it.private_clean_resident_kb()) * 1024);
           region["bs"]["pd"] = base::Uint64ToHexStringNoPrefix(
-              static_cast<uint64_t>(
-                  smaps_table.private_dirty_kb()[smaps_index]) *
-              1024);
+              static_cast<uint64_t>(it.private_dirty_kb()) * 1024);
           region["bs"]["pss"] = base::Uint64ToHexStringNoPrefix(
-              static_cast<uint64_t>(
-                  smaps_table.proportional_resident_kb()[smaps_index]) *
-              1024);
+              static_cast<uint64_t>(it.proportional_resident_kb()) * 1024);
           region["bs"]["sc"] = base::Uint64ToHexStringNoPrefix(
-              static_cast<uint64_t>(
-                  smaps_table.shared_clean_resident_kb()[smaps_index]) *
-              1024);
+              static_cast<uint64_t>(it.shared_clean_resident_kb()) * 1024);
           region["bs"]["sd"] = base::Uint64ToHexStringNoPrefix(
-              static_cast<uint64_t>(
-                  smaps_table.shared_dirty_resident_kb()[smaps_index]) *
-              1024);
+              static_cast<uint64_t>(it.shared_dirty_resident_kb()) * 1024);
           region["bs"]["sw"] = base::Uint64ToHexStringNoPrefix(
-              static_cast<uint64_t>(smaps_table.swap_kb()[smaps_index]) * 1024);
+              static_cast<uint64_t>(it.swap_kb()) * 1024);
           smaps->append(region);
         }
 
@@ -1675,23 +1631,22 @@
       // snapshot.
       const auto& process_snapshots = storage_->process_memory_snapshot_table();
 
-      for (uint32_t process_index = 0;
-           process_index < process_snapshots.row_count(); ++process_index) {
-        if (process_snapshots.snapshot_id()[process_index].value != snapshot_id)
+      for (auto psit = process_snapshots.IterateRows(); psit; ++psit) {
+        if (psit.snapshot_id() != snapshot_id)
           continue;
 
-        auto process_snapshot_id = process_snapshots.id()[process_index].value;
-        uint32_t pid = UpidToPid(process_snapshots.upid()[process_index]);
+        auto process_snapshot_id = psit.id();
+        uint32_t pid = UpidToPid(psit.upid());
 
         // Shared memory nodes are imported into a fake process with pid 0.
         // Catapult expects them to be associated with one of the real processes
         // of the snapshot, so we choose the first one we can find and replace
         // the pid.
         if (pid == 0) {
-          for (uint32_t i = 0; i < process_snapshots.row_count(); ++i) {
-            if (process_snapshots.snapshot_id()[i].value != snapshot_id)
+          for (auto iit = process_snapshots.IterateRows(); iit; ++iit) {
+            if (iit.snapshot_id() != snapshot_id)
               continue;
-            uint32_t new_pid = UpidToPid(process_snapshots.upid()[i]);
+            uint32_t new_pid = UpidToPid(iit.upid());
             if (new_pid != 0) {
               pid = new_pid;
               break;
@@ -1701,31 +1656,25 @@
 
         Json::Value event = FillInProcessEventDetails(event_base, pid);
 
-        const auto& snapshot_nodes = storage_->memory_snapshot_node_table();
+        const auto& sn = storage_->memory_snapshot_node_table();
 
-        for (uint32_t node_index = 0; node_index < snapshot_nodes.row_count();
-             ++node_index) {
-          if (snapshot_nodes.process_snapshot_id()[node_index].value !=
-              process_snapshot_id) {
+        for (auto it = sn.IterateRows(); it; ++it) {
+          if (it.process_snapshot_id() != process_snapshot_id) {
             continue;
           }
-          const char* path =
-              GetNonNullString(storage_, snapshot_nodes.path()[node_index]);
+          const char* path = GetNonNullString(storage_, it.path());
           event["args"]["dumps"]["allocators"][path]["guid"] =
               base::Uint64ToHexStringNoPrefix(
-                  static_cast<uint64_t>(snapshot_nodes.id()[node_index].value));
-          if (snapshot_nodes.size()[node_index]) {
-            AddAttributeToMemoryNode(&event, path, "size",
-                                     snapshot_nodes.size()[node_index],
-                                     "bytes");
+                  static_cast<uint64_t>(it.id().value));
+          if (it.size()) {
+            AddAttributeToMemoryNode(&event, path, "size", it.size(), "bytes");
           }
-          if (snapshot_nodes.effective_size()[node_index]) {
-            AddAttributeToMemoryNode(
-                &event, path, "effective_size",
-                snapshot_nodes.effective_size()[node_index], "bytes");
+          if (it.effective_size()) {
+            AddAttributeToMemoryNode(&event, path, "effective_size",
+                                     it.effective_size(), "bytes");
           }
 
-          auto node_args_id = snapshot_nodes.arg_set_id()[node_index];
+          auto node_args_id = it.arg_set_id();
           if (!node_args_id)
             continue;
           const Json::Value* node_args =
@@ -1748,32 +1697,26 @@
         }
 
         const auto& snapshot_edges = storage_->memory_snapshot_edge_table();
+        for (auto it = snapshot_edges.IterateRows(); it; ++it) {
+          SnapshotNodeId source_node_id = it.source_node_id();
+          auto source_node_rr = *sn.FindById(source_node_id);
 
-        for (uint32_t edge_index = 0; edge_index < snapshot_edges.row_count();
-             ++edge_index) {
-          SnapshotNodeId source_node_id =
-              snapshot_edges.source_node_id()[edge_index];
-          uint32_t source_node_row =
-              *snapshot_nodes.id().IndexOf(source_node_id);
-
-          if (snapshot_nodes.process_snapshot_id()[source_node_row].value !=
-              process_snapshot_id) {
+          if (source_node_rr.process_snapshot_id() != process_snapshot_id) {
             continue;
           }
           Json::Value edge;
-          edge["source"] = base::Uint64ToHexStringNoPrefix(
-              snapshot_edges.source_node_id()[edge_index].value);
-          edge["target"] = base::Uint64ToHexStringNoPrefix(
-              snapshot_edges.target_node_id()[edge_index].value);
-          edge["importance"] =
-              Json::Int(snapshot_edges.importance()[edge_index]);
+          edge["source"] =
+              base::Uint64ToHexStringNoPrefix(it.source_node_id().value);
+          edge["target"] =
+              base::Uint64ToHexStringNoPrefix(it.target_node_id().value);
+          edge["importance"] = Json::Int(it.importance());
           edge["type"] = "ownership";
           event["args"]["dumps"]["allocators_graph"].append(edge);
         }
         writer_.WriteCommonEvent(event);
       }
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   uint32_t UpidToPid(UniquePid upid) {
@@ -1798,33 +1741,32 @@
 
   bool IsValidPidOrTid(uint32_t pid_or_tid) {
     const auto& process_table = storage_->process_table();
-    for (UniquePid upid = 0; upid < process_table.row_count(); upid++) {
-      if (process_table.pid()[upid] == pid_or_tid)
+    for (auto it = process_table.IterateRows(); it; ++it) {
+      if (it.pid() == pid_or_tid)
         return true;
     }
 
     const auto& thread_table = storage_->thread_table();
-    for (UniqueTid utid = 0; utid < thread_table.row_count(); utid++) {
-      if (thread_table.tid()[utid] == pid_or_tid)
+    for (auto it = thread_table.IterateRows(); it; ++it) {
+      if (it.tid() == pid_or_tid)
         return true;
     }
-
     return false;
   }
 
-  Json::Value FillInProcessEventDetails(const Json::Value& event,
-                                        uint32_t pid) {
+  static Json::Value FillInProcessEventDetails(const Json::Value& event,
+                                               uint32_t pid) {
     Json::Value output = event;
     output["pid"] = Json::Int(pid);
     output["tid"] = Json::Int(-1);
     return output;
   }
 
-  void AddAttributeToMemoryNode(Json::Value* event,
-                                const std::string& path,
-                                const std::string& key,
-                                int64_t value,
-                                const std::string& units) {
+  static void AddAttributeToMemoryNode(Json::Value* event,
+                                       const std::string& path,
+                                       const std::string& key,
+                                       int64_t value,
+                                       const std::string& units) {
     (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["value"] =
         base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(value));
     (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["type"] =
@@ -1833,11 +1775,11 @@
         units;
   }
 
-  void AddAttributeToMemoryNode(Json::Value* event,
-                                const std::string& path,
-                                const std::string& key,
-                                const std::string& value,
-                                const std::string& units = "") {
+  static void AddAttributeToMemoryNode(Json::Value* event,
+                                       const std::string& path,
+                                       const std::string& key,
+                                       const std::string& value,
+                                       const std::string& units = "") {
     (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["value"] =
         value;
     (*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["type"] =
@@ -1862,8 +1804,9 @@
     for (; it < end; ++it) {
       if ((*it).AsLong() != ts)
         break;
-      if (counter_table.track_id()[it.row()].value == track_id.value)
-        return static_cast<uint64_t>(counter_table.value()[it.row()]);
+      if (auto rr = counter_table[it.row()]; rr.track_id() == track_id) {
+        return static_cast<uint64_t>(rr.value());
+      }
     }
     return 0;
   }
@@ -1894,7 +1837,7 @@
 OutputWriter::OutputWriter() = default;
 OutputWriter::~OutputWriter() = default;
 
-util::Status ExportJson(const TraceStorage* storage,
+base::Status ExportJson(const TraceStorage* storage,
                         OutputWriter* output,
                         ArgumentFilterPredicate argument_filter,
                         MetadataFilterPredicate metadata_filter,
@@ -1909,11 +1852,11 @@
   perfetto::base::ignore_result(argument_filter);
   perfetto::base::ignore_result(metadata_filter);
   perfetto::base::ignore_result(label_filter);
-  return util::ErrStatus("JSON support is not compiled in this build");
+  return base::ErrStatus("JSON support is not compiled in this build");
 #endif  // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
 }
 
-util::Status ExportJson(TraceProcessorStorage* tp,
+base::Status ExportJson(TraceProcessorStorage* tp,
                         OutputWriter* output,
                         ArgumentFilterPredicate argument_filter,
                         MetadataFilterPredicate metadata_filter,
@@ -1921,15 +1864,13 @@
   const TraceStorage* storage = reinterpret_cast<TraceProcessorStorageImpl*>(tp)
                                     ->context()
                                     ->storage.get();
-  return ExportJson(storage, output, argument_filter, metadata_filter,
-                    label_filter);
+  return ExportJson(storage, output, std::move(argument_filter),
+                    std::move(metadata_filter), std::move(label_filter));
 }
 
-util::Status ExportJson(const TraceStorage* storage, FILE* output) {
+base::Status ExportJson(const TraceStorage* storage, FILE* output) {
   FileWriter writer(output);
   return ExportJson(storage, &writer, nullptr, nullptr, nullptr);
 }
 
-}  // namespace json
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor::json
diff --git a/src/trace_processor/export_json_unittest.cc b/src/trace_processor/export_json_unittest.cc
index 90677c2..5a77efd 100644
--- a/src/trace_processor/export_json_unittest.cc
+++ b/src/trace_processor/export_json_unittest.cc
@@ -15,14 +15,24 @@
  */
 
 #include "perfetto/ext/trace_processor/export_json.h"
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/export_json.h"
 
-#include <string.h>
-
-#include <limits>
-
+#include <json/config.h>
 #include <json/reader.h>
 #include <json/value.h>
+#include <array>
+#include <cstdint>
+#include <cstdio>
+#include <cstring>
+#include <limits>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
 
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/temp_file.h"
@@ -35,14 +45,15 @@
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/proto/track_event_tracker.h"
+#include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
-
+#include "src/trace_processor/types/variadic.h"
 #include "test/gtest_and_gmock.h"
 
-namespace perfetto {
-namespace trace_processor {
-namespace json {
+namespace perfetto::trace_processor::json {
 namespace {
 
 std::string ReadFile(FILE* input) {
@@ -51,7 +62,7 @@
   char buffer[kBufSize];
   size_t ret = fread(buffer, sizeof(char), kBufSize, input);
   EXPECT_GT(ret, 0u);
-  return std::string(buffer, ret);
+  return {buffer, ret};
 }
 
 class StringOutputWriter : public OutputWriter {
@@ -59,9 +70,9 @@
   StringOutputWriter() { str_.reserve(1024); }
   ~StringOutputWriter() override {}
 
-  util::Status AppendString(const std::string& str) override {
+  base::Status AppendString(const std::string& str) override {
     str_ += str;
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   std::string TakeStr() { return std::move(str_); }
@@ -90,16 +101,16 @@
 
   std::string ToJson(ArgumentFilterPredicate argument_filter = nullptr,
                      MetadataFilterPredicate metadata_filter = nullptr,
-                     LabelFilterPredicate label_filter = nullptr) {
+                     LabelFilterPredicate label_filter = nullptr) const {
     StringOutputWriter writer;
-    util::Status status =
-        ExportJson(context_.storage.get(), &writer, argument_filter,
-                   metadata_filter, label_filter);
+    base::Status status =
+        ExportJson(context_.storage.get(), &writer, std::move(argument_filter),
+                   std::move(metadata_filter), std::move(label_filter));
     EXPECT_TRUE(status.ok());
     return writer.TakeStr();
   }
 
-  Json::Value ToJsonValue(const std::string& json) {
+  static Json::Value ToJsonValue(const std::string& json) {
     Json::CharReaderBuilder b;
     auto reader = std::unique_ptr<Json::CharReader>(b.newCharReader());
     Json::Value result;
@@ -115,8 +126,8 @@
 
 TEST_F(ExportJsonTest, EmptyStorage) {
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -147,8 +158,8 @@
        kThreadInstructionDelta});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -192,8 +203,8 @@
        kThreadInstructionDelta});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -224,8 +235,8 @@
   context_.storage->mutable_thread_table()->Insert(row);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -251,8 +262,8 @@
       {0, 0, track, cat_id, name_id, 0, 0, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -320,8 +331,8 @@
   context_.metadata_tracker->SetDynamicMetadata(dynamic_key_id, had_failures);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -372,8 +383,8 @@
                                     kFtraceBegin);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
   EXPECT_TRUE(status.ok());
 
   Json::Value result = ToJsonValue(ReadFile(output));
@@ -414,8 +425,8 @@
   context_.args_tracker->Flush();
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(storage, output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(storage, output);
   EXPECT_TRUE(status.ok());
 
   Json::Value result = ToJsonValue(ReadFile(output));
@@ -449,11 +460,12 @@
   arg.key = arg_key_id;
   arg.value = Variadic::String(arg_value_id);
   ArgSetId args = context_.global_args_tracker->AddArgSet({arg}, 0, 1);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(args);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -487,8 +499,8 @@
   storage->mutable_flow_table()->Insert({id1, id2, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(storage, output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(storage, output);
 
   EXPECT_TRUE(status.ok());
 
@@ -547,11 +559,12 @@
   arg1.key = arg_key1_id;
   arg1.value = Variadic::Real(kValues[1]);
   ArgSetId args = context_.global_args_tracker->AddArgSet({arg0, arg1}, 0, 2);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(args);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -593,11 +606,12 @@
   arg1.key = arg_key1_id;
   arg1.value = Variadic::Pointer(kValue1);
   ArgSetId args = context_.global_args_tracker->AddArgSet({arg0, arg1}, 0, 2);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(args);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -639,11 +653,12 @@
   arg1.key = arg_key1_id;
   arg1.value = Variadic::Integer(kValues[1]);
   ArgSetId args = context_.global_args_tracker->AddArgSet({arg0, arg1}, 0, 2);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(args);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -686,11 +701,12 @@
   arg1.key = arg_key1_id;
   arg1.value = Variadic::Integer(kValues[1]);
   ArgSetId args = context_.global_args_tracker->AddArgSet({arg0, arg1}, 0, 2);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(args);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -726,11 +742,12 @@
   arg.key = arg_key_id;
   arg.value = Variadic::Json(arg_value_id);
   ArgSetId args = context_.global_args_tracker->AddArgSet({arg}, 0, 1);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(args);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -774,8 +791,8 @@
       {kTimestamp3, 0, track3, cat_id, name_id, 0, 0, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -819,8 +836,8 @@
       {kTimestamp, 0, track, cat_id, name_id, 0, 0, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -858,11 +875,12 @@
   ASSERT_NE(utid1b, utid2b);
   ASSERT_NE(utid1c, utid2b);
 
-  ASSERT_EQ(upid1, *context_.storage->thread_table().upid()[utid1a]);
-  ASSERT_EQ(upid1, *context_.storage->thread_table().upid()[utid1b]);
-  ASSERT_EQ(upid1, *context_.storage->thread_table().upid()[utid1c]);
-  ASSERT_EQ(upid2, *context_.storage->thread_table().upid()[utid2a]);
-  ASSERT_EQ(upid2, *context_.storage->thread_table().upid()[utid2b]);
+  const auto& thread_table = context_.storage->thread_table();
+  ASSERT_EQ(upid1, *thread_table[utid1a].upid());
+  ASSERT_EQ(upid1, *thread_table[utid1b].upid());
+  ASSERT_EQ(upid1, *thread_table[utid1c].upid());
+  ASSERT_EQ(upid2, *thread_table[utid2a].upid());
+  ASSERT_EQ(upid2, *thread_table[utid2b].upid());
 
   TrackId track1a = context_.track_tracker->InternThreadTrack(utid1a);
   TrackId track1b = context_.track_tracker->InternThreadTrack(utid1b);
@@ -895,8 +913,8 @@
       {50000, 1000, track2b, cat_id, name2b_id, 0, 0, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -965,11 +983,11 @@
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
-      name_id, upid, kSourceId, /*source_id_is_process_scoped=*/true,
+      name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
       /*source_scope=*/kNullStringId);
   constexpr int64_t kSourceId2 = 236;
   TrackId track2 = context_.track_tracker->InternLegacyChromeAsyncTrack(
-      name3_id, upid, kSourceId2, /*source_id_is_process_scoped=*/true,
+      name3_id, upid, kSourceId2, /*trace_id_is_process_scoped=*/true,
       /*source_scope=*/kNullStringId);
   context_.args_tracker->Flush();  // Flush track args.
 
@@ -982,7 +1000,8 @@
   arg.key = arg_key_id;
   arg.value = Variadic::Integer(kArgValue);
   ArgSetId args = context_.global_args_tracker->AddArgSet({arg}, 0, 1);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(args);
 
   // Child event with same timestamps as first one.
   context_.storage->mutable_slice_table()->Insert(
@@ -993,8 +1012,8 @@
       {kTimestamp3, kDuration3, track2, cat_id, name3_id, 0, 0, 0});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1111,11 +1130,11 @@
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
-      name_id, upid, kSourceId, /*source_id_is_process_scoped=*/true,
+      name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
       /*source_scope=*/kNullStringId);
   constexpr int64_t kSourceId2 = 236;
   TrackId track2 = context_.track_tracker->InternLegacyChromeAsyncTrack(
-      name3_id, upid, kSourceId2, /*source_id_is_process_scoped=*/true,
+      name3_id, upid, kSourceId2, /*trace_id_is_process_scoped=*/true,
       /*source_scope=*/kNullStringId);
   context_.args_tracker->Flush();  // Flush track args.
 
@@ -1125,8 +1144,8 @@
   arg_inserter("arg1", "value1", args1);
   arg_inserter("legacy_event.phase", "S", args1);
   ArgSetId arg_id1 = context_.global_args_tracker->AddArgSet(args1, 0, 2);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0,
-                                                                     arg_id1);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(arg_id1);
 
   // Step event with first event as parent.
   context_.storage->mutable_slice_table()->Insert(
@@ -1136,8 +1155,7 @@
   arg_inserter("legacy_event.phase", "T", step_args);
   arg_inserter("debug.step", "Step1", step_args);
   ArgSetId arg_id2 = context_.global_args_tracker->AddArgSet(step_args, 0, 3);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(1,
-                                                                     arg_id2);
+  slice[1].set_arg_set_id(arg_id2);
 
   // Another overlapping async event on a different track.
   context_.storage->mutable_slice_table()->Insert(
@@ -1145,12 +1163,11 @@
   std::vector<Arg> args3;
   arg_inserter("legacy_event.phase", "S", args3);
   ArgSetId arg_id3 = context_.global_args_tracker->AddArgSet(args3, 0, 1);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(2,
-                                                                     arg_id3);
+  slice[2].set_arg_set_id(arg_id3);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1236,7 +1253,7 @@
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
-      name_id, upid, kSourceId, /*source_id_is_process_scoped=*/true,
+      name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
       /*source_scope=*/kNullStringId);
   context_.args_tracker->Flush();  // Flush track args.
 
@@ -1247,8 +1264,8 @@
       id_and_row.id, kThreadTimestamp, kThreadDuration, 0, 0);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1292,7 +1309,7 @@
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
-      name_id, upid, kSourceId, /*source_id_is_process_scoped=*/true,
+      name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
       /*source_scope=*/kNullStringId);
   context_.args_tracker->Flush();  // Flush track args.
 
@@ -1304,8 +1321,8 @@
       slice_id, kThreadTimestamp, kThreadDuration, 0, 0);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1337,7 +1354,7 @@
 
   constexpr int64_t kSourceId = 235;
   TrackId track = context_.track_tracker->InternLegacyChromeAsyncTrack(
-      name_id, upid, kSourceId, /*source_id_is_process_scoped=*/true,
+      name_id, upid, kSourceId, /*trace_id_is_process_scoped=*/true,
       /*source_scope=*/kNullStringId);
   context_.args_tracker->Flush();  // Flush track args.
 
@@ -1350,11 +1367,12 @@
   arg.key = arg_key_id;
   arg.value = Variadic::Integer(kArgValue);
   ArgSetId args = context_.global_args_tracker->AddArgSet({arg}, 0, 1);
-  context_.storage->mutable_slice_table()->mutable_arg_set_id()->Set(0, args);
+  auto& slice = *context_.storage->mutable_slice_table();
+  slice[0].set_arg_set_id(args);
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1394,7 +1412,9 @@
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(kThreadID);
   UniquePid upid = context_.process_tracker->GetOrCreateProcess(kProcessID);
-  context_.storage->mutable_thread_table()->mutable_upid()->Set(utid, upid);
+
+  auto& tt = *context_.storage->mutable_thread_table();
+  tt[utid].set_upid(upid);
 
   auto ucpu = context_.cpu_tracker->GetOrCreateCpu(0);
   auto id_and_row = storage->mutable_raw_table()->Insert(
@@ -1437,8 +1457,8 @@
   context_.args_tracker->Flush();
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(storage, output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(storage, output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1493,8 +1513,8 @@
   context_.args_tracker->Flush();
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(storage, output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(storage, output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1516,10 +1536,12 @@
 
   UniqueTid utid = context_.process_tracker->GetOrCreateThread(kThreadID);
   UniquePid upid = context_.process_tracker->GetOrCreateProcess(kProcessID);
-  context_.storage->mutable_thread_table()->mutable_upid()->Set(utid, upid);
+
+  auto& tt = *context_.storage->mutable_thread_table();
+  tt[utid].set_upid(upid);
 
   auto* mappings = storage->mutable_stack_profile_mapping_table();
-  auto* frames = storage->mutable_stack_profile_frame_table();
+  auto& frames = *storage->mutable_stack_profile_frame_table();
   auto* callsites = storage->mutable_stack_profile_callsite_table();
 
   auto module_1 =
@@ -1534,22 +1556,22 @@
   // stack_profile_frame.symbol_set_id remove this hack
   storage->mutable_symbol_table()->Insert({0, kNullStringId, kNullStringId, 0});
 
-  auto frame_1 = frames->Insert({/*name_id=*/kNullStringId, module_1.id, 0x42});
+  auto frame_1 = frames.Insert({/*in_name=*/kNullStringId, module_1.id, 0x42});
 
   uint32_t symbol_set_id = storage->symbol_table().row_count();
   storage->mutable_symbol_table()->Insert(
       {symbol_set_id, storage->InternString("foo_func"),
        storage->InternString("foo_file"), 66});
-  frames->mutable_symbol_set_id()->Set(frame_1.row, symbol_set_id);
+  frames[frame_1.row].set_symbol_set_id(symbol_set_id);
 
   auto frame_2 =
-      frames->Insert({/*name_id=*/kNullStringId, module_2.id, 0x4242});
+      frames.Insert({/*in_name=*/kNullStringId, module_2.id, 0x4242});
 
   symbol_set_id = storage->symbol_table().row_count();
   storage->mutable_symbol_table()->Insert(
       {symbol_set_id, storage->InternString("bar_func"),
        storage->InternString("bar_file"), 77});
-  frames->mutable_symbol_set_id()->Set(frame_2.row, symbol_set_id);
+  frames[frame_2.row].set_symbol_set_id(symbol_set_id);
 
   auto frame_callsite_1 = callsites->Insert({0, std::nullopt, frame_1.id});
 
@@ -1566,8 +1588,8 @@
       {kTimestamp + 20000, frame_callsite_1.id, utid, kProcessPriority});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(storage, output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(storage, output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1647,7 +1669,8 @@
     if (strcmp(event_name, "name1") == 0) {
       // Filter all args for name1.
       return false;
-    } else if (strcmp(event_name, "name2") == 0) {
+    }
+    if (strcmp(event_name, "name2") == 0) {
       // Filter only the second arg for name2.
       *arg_name_filter = [](const char* arg_name) {
         if (strcmp(arg_name, "arg1") == 0) {
@@ -1821,8 +1844,8 @@
        0, kProportionalResidentKb});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -1947,8 +1970,8 @@
       {node1_id, node2_id, kImportance});
 
   base::TempFile temp_file = base::TempFile::Create();
-  FILE* output = fopen(temp_file.path().c_str(), "w+");
-  util::Status status = ExportJson(context_.storage.get(), output);
+  FILE* output = fopen(temp_file.path().c_str(), "w+e");
+  base::Status status = ExportJson(context_.storage.get(), output);
 
   EXPECT_TRUE(status.ok());
 
@@ -2016,6 +2039,4 @@
 }
 
 }  // namespace
-}  // namespace json
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor::json
diff --git a/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc b/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc
index 48d9135..2dd2e63 100644
--- a/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc
+++ b/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.cc
@@ -16,7 +16,15 @@
 
 #include "src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h"
 
+#include <cstddef>
+#include <cstdint>
+#include <utility>
+#include <vector>
+
 #include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_view.h"
+#include "src/trace_processor/importers/android_bugreport/android_log_reader.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/util/status_macros.h"
 
@@ -26,12 +34,11 @@
     TraceProcessorContext* context,
     int32_t year,
     std::vector<TimestampedAndroidLogEvent> logcat_events)
-    : context_(context),
-      log_reader_(context, std::move(year), std::move(logcat_events)) {}
+    : context_(context), log_reader_(context, year, std::move(logcat_events)) {}
 
 AndroidDumpstateReader::~AndroidDumpstateReader() = default;
 
-util::Status AndroidDumpstateReader::ParseLine(base::StringView line) {
+base::Status AndroidDumpstateReader::ParseLine(base::StringView line) {
   // Dumpstate is organized in a two level hierarchy, beautifully flattened into
   // one text file with load bearing ----- markers:
   // 1. Various dumpstate sections, examples:
diff --git a/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h b/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h
index 32abd85..54a19db 100644
--- a/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h
+++ b/src/trace_processor/importers/android_bugreport/android_dumpstate_reader.h
@@ -35,7 +35,7 @@
                          std::vector<TimestampedAndroidLogEvent> logcat_events);
   ~AndroidDumpstateReader() override;
 
-  util::Status ParseLine(base::StringView line) override;
+  base::Status ParseLine(base::StringView line) override;
   void EndOfStream(base::StringView leftovers) override;
 
  private:
diff --git a/src/trace_processor/importers/common/BUILD.gn b/src/trace_processor/importers/common/BUILD.gn
index 1a22292..c2f61da 100644
--- a/src/trace_processor/importers/common/BUILD.gn
+++ b/src/trace_processor/importers/common/BUILD.gn
@@ -141,6 +141,7 @@
     "../../../../protos/perfetto/trace:zero",
     "../../../base",
     "../../storage",
+    "../../tables",
     "../../types",
   ]
 }
diff --git a/src/trace_processor/importers/common/args_tracker.cc b/src/trace_processor/importers/common/args_tracker.cc
index d02d9ce..2454728 100644
--- a/src/trace_processor/importers/common/args_tracker.cc
+++ b/src/trace_processor/importers/common/args_tracker.cc
@@ -17,9 +17,15 @@
 #include "src/trace_processor/importers/common/args_tracker.h"
 
 #include <algorithm>
+#include <cstddef>
 #include <cstdint>
+#include <optional>
+#include <tuple>
 
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/small_vector.h"
 #include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/typed_column.h"
 #include "src/trace_processor/importers/common/args_translation_table.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
@@ -113,8 +119,8 @@
 
   // Apply permutation of entries[].index to args.
   base::SmallVector<Arg, 16> sorted_args;
-  for (uint32_t i = 0; i < entries.size(); i++) {
-    sorted_args.emplace_back(args_[entries[i].index]);
+  for (auto& entry : entries) {
+    sorted_args.emplace_back(args_[entry.index]);
   }
 
   // Insert args.
@@ -130,8 +136,8 @@
       next_rid_idx++;
     }
 
-    ArgSetId set_id = context_->global_args_tracker->AddArgSet(&sorted_args[0],
-                                                               i, next_rid_idx);
+    ArgSetId set_id = context_->global_args_tracker->AddArgSet(
+        sorted_args.data(), i, next_rid_idx);
     if (col->IsNullable()) {
       TypedColumn<std::optional<uint32_t>>::FromColumn(col)->Set(row, set_id);
     } else {
diff --git a/src/trace_processor/importers/common/args_tracker.h b/src/trace_processor/importers/common/args_tracker.h
index 81d19f6..912d574 100644
--- a/src/trace_processor/importers/common/args_tracker.h
+++ b/src/trace_processor/importers/common/args_tracker.h
@@ -17,6 +17,7 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ARGS_TRACKER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_ARGS_TRACKER_H_
 
+#include <cstdint>
 #include "perfetto/ext/base/small_vector.h"
 #include "src/trace_processor/importers/common/global_args_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
@@ -169,8 +170,7 @@
   }
 
   BoundInserter AddArgsTo(tables::AndroidKeyEventsTable::Id id) {
-    return AddArgsTo(context_->storage->mutable_android_key_events_table(),
-                     id);
+    return AddArgsTo(context_->storage->mutable_android_key_events_table(), id);
   }
 
   BoundInserter AddArgsTo(tables::AndroidMotionEventsTable::Id id) {
@@ -185,13 +185,13 @@
 
   BoundInserter AddArgsTo(MetadataId id) {
     auto* table = context_->storage->mutable_metadata_table();
-    uint32_t row = *table->id().IndexOf(id);
+    uint32_t row = table->FindById(id)->ToRowNumber().row_number();
     return BoundInserter(this, table->mutable_int_value(), row);
   }
 
   BoundInserter AddArgsTo(TrackId id) {
     auto* table = context_->storage->mutable_track_table();
-    uint32_t row = *table->id().IndexOf(id);
+    uint32_t row = table->FindById(id)->ToRowNumber().row_number();
     return BoundInserter(this, table->mutable_source_arg_set_id(), row);
   }
 
@@ -232,7 +232,7 @@
  private:
   template <typename Table>
   BoundInserter AddArgsTo(Table* table, typename Table::Id id) {
-    uint32_t row = *table->id().IndexOf(id);
+    uint32_t row = table->FindById(id)->ToRowNumber().row_number();
     return BoundInserter(this, table->mutable_arg_set_id(), row);
   }
 
diff --git a/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc b/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc
index 173fd89..49c625b 100644
--- a/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/async_track_set_tracker_unittest.cc
@@ -69,19 +69,19 @@
 
   ASSERT_EQ(begin, end);
 
-  uint32_t row = *storage_->process_track_table().id().IndexOf(begin);
-  ASSERT_EQ(storage_->process_track_table().upid()[row], 1u);
-  ASSERT_EQ(storage_->process_track_table().name()[row],
-            storage_->InternString("test"));
+  const auto& process = storage_->process_track_table();
+  auto rr = *process.FindById(begin);
+  ASSERT_EQ(rr.upid(), 1u);
+  ASSERT_EQ(rr.name(), storage_->string_pool().GetId("test"));
 }
 
 TEST_F(AsyncTrackSetTrackerUnittest, EndFirst) {
   auto end = tracker_->End(nestable_id_, 1);
 
-  uint32_t row = *storage_->process_track_table().id().IndexOf(end);
-  ASSERT_EQ(storage_->process_track_table().upid()[row], 1u);
-  ASSERT_EQ(storage_->process_track_table().name()[row],
-            storage_->InternString("test"));
+  const auto& process = storage_->process_track_table();
+  auto rr = *process.FindById(end);
+  ASSERT_EQ(rr.upid(), 1u);
+  ASSERT_EQ(rr.name(), storage_->string_pool().GetId("test"));
 }
 
 TEST_F(AsyncTrackSetTrackerUnittest, LegacySaturating) {
diff --git a/src/trace_processor/importers/common/cpu_tracker.h b/src/trace_processor/importers/common/cpu_tracker.h
index 3e2edc5..466fa92 100644
--- a/src/trace_processor/importers/common/cpu_tracker.h
+++ b/src/trace_processor/importers/common/cpu_tracker.h
@@ -18,7 +18,12 @@
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_CPU_TRACKER_H_
 
 #include <bitset>
+#include <cstdint>
+#include <optional>
 
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/public/compiler.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
@@ -46,10 +51,11 @@
     auto ucpu = ucpu_offset_ + cpu;
     if (PERFETTO_LIKELY(cpu_ids_[cpu]))
       return tables::CpuTable::Id(ucpu);
-
     cpu_ids_.set(cpu);
+
     // Populate the optional |cpu| column.
-    context_->storage->mutable_cpu_table()->mutable_cpu()->Set(ucpu, cpu);
+    auto& cpu_table = *context_->storage->mutable_cpu_table();
+    cpu_table[ucpu].set_cpu(cpu);
     return tables::CpuTable::Id(ucpu);
   }
 
diff --git a/src/trace_processor/importers/common/event_tracker.cc b/src/trace_processor/importers/common/event_tracker.cc
index a38d776..4b7e817 100644
--- a/src/trace_processor/importers/common/event_tracker.cc
+++ b/src/trace_processor/importers/common/event_tracker.cc
@@ -39,10 +39,11 @@
     double value,
     StringId name_id,
     UniqueTid utid) {
+  const auto& counter = context_->storage->counter_table();
   auto opt_id = PushCounter(timestamp, value, kInvalidTrackId);
   if (opt_id) {
     PendingUpidResolutionCounter pending;
-    pending.row = *context_->storage->counter_table().id().IndexOf(*opt_id);
+    pending.row = counter.FindById(*opt_id)->ToRowNumber().row_number();
     pending.utid = utid;
     pending.name_id = name_id;
     pending_upid_resolution_counter_.emplace_back(pending);
@@ -83,7 +84,7 @@
   const auto& thread_table = context_->storage->thread_table();
   for (const auto& pending_counter : pending_upid_resolution_counter_) {
     UniqueTid utid = pending_counter.utid;
-    std::optional<UniquePid> upid = thread_table.upid()[utid];
+    std::optional<UniquePid> upid = thread_table[utid].upid();
 
     TrackId track_id = kInvalidTrackId;
     if (upid.has_value()) {
@@ -96,8 +97,8 @@
       track_id = context_->track_tracker->InternThreadCounterTrack(
           pending_counter.name_id, utid);
     }
-    context_->storage->mutable_counter_table()->mutable_track_id()->Set(
-        pending_counter.row, track_id);
+    auto& counter = *context_->storage->mutable_counter_table();
+    counter[pending_counter.row].set_track_id(track_id);
   }
   pending_upid_resolution_counter_.clear();
 }
diff --git a/src/trace_processor/importers/common/event_tracker_unittest.cc b/src/trace_processor/importers/common/event_tracker_unittest.cc
index da559b0..64a3bdc 100644
--- a/src/trace_processor/importers/common/event_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/event_tracker_unittest.cc
@@ -63,15 +63,20 @@
 
   ASSERT_EQ(context.storage->counter_track_table().row_count(), 1ul);
 
-  ASSERT_EQ(context.storage->counter_table().row_count(), 4ul);
-  ASSERT_EQ(context.storage->counter_table().ts()[0], timestamp);
-  ASSERT_DOUBLE_EQ(context.storage->counter_table().value()[0], 1000);
+  const auto& counter = context.storage->counter_table();
+  ASSERT_EQ(counter.row_count(), 4ul);
 
-  ASSERT_EQ(context.storage->counter_table().ts()[1], timestamp + 1);
-  ASSERT_DOUBLE_EQ(context.storage->counter_table().value()[1], 4000);
+  auto rr = counter[0];
+  ASSERT_EQ(rr.ts(), timestamp);
+  ASSERT_DOUBLE_EQ(rr.value(), 1000);
 
-  ASSERT_EQ(context.storage->counter_table().ts()[2], timestamp + 3);
-  ASSERT_DOUBLE_EQ(context.storage->counter_table().value()[2], 5000);
+  rr = counter[1];
+  ASSERT_EQ(rr.ts(), timestamp + 1);
+  ASSERT_DOUBLE_EQ(rr.value(), 4000);
+
+  rr = counter[2];
+  ASSERT_EQ(rr.ts(), timestamp + 3);
+  ASSERT_DOUBLE_EQ(rr.value(), 5000);
 }
 
 }  // namespace
diff --git a/src/trace_processor/importers/common/flow_tracker_unittest.cc b/src/trace_processor/importers/common/flow_tracker_unittest.cc
index 9040734..82bbf40 100644
--- a/src/trace_processor/importers/common/flow_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/flow_tracker_unittest.cc
@@ -70,8 +70,10 @@
 
   const auto& flows = context_.storage->flow_table();
   EXPECT_EQ(flows.row_count(), 1u);
-  EXPECT_EQ(flows.slice_out()[0], out_slice_id);
-  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+
+  auto f = flows[0];
+  EXPECT_EQ(f.slice_out(), out_slice_id);
+  EXPECT_EQ(f.slice_in(), in_slice_id);
 }
 
 TEST_F(FlowTrackerTest, SingleFlowEventWaitForNextSlice) {
@@ -103,8 +105,10 @@
   slice_tracker->End(160, track_2, StringId::Raw(2), StringId::Raw(2));
 
   EXPECT_EQ(flows.row_count(), 1u);
-  EXPECT_EQ(flows.slice_out()[0], out_slice_id);
-  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+
+  auto f = flows[0];
+  EXPECT_EQ(f.slice_out(), out_slice_id);
+  EXPECT_EQ(f.slice_in(), in_slice_id);
 }
 
 TEST_F(FlowTrackerTest, SingleFlowEventWaitForNextSliceScoped) {
@@ -135,8 +139,10 @@
   SliceId in_slice_id = slice_tracker->GetTopmostSliceOnTrack(track_2).value();
 
   EXPECT_EQ(flows.row_count(), 1u);
-  EXPECT_EQ(flows.slice_out()[0], out_slice_id);
-  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
+
+  auto f = flows[0];
+  EXPECT_EQ(f.slice_out(), out_slice_id);
+  EXPECT_EQ(f.slice_in(), in_slice_id);
 }
 
 TEST_F(FlowTrackerTest, TwoFlowEventsWaitForNextSlice) {
@@ -180,10 +186,14 @@
   slice_tracker->End(170, track_2, StringId::Raw(3), StringId::Raw(3));
 
   EXPECT_EQ(flows.row_count(), 2u);
-  EXPECT_EQ(flows.slice_out()[0], out_slice1_id);
-  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
-  EXPECT_EQ(flows.slice_out()[1], out_slice2_id);
-  EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+
+  auto f = flows[0];
+  EXPECT_EQ(f.slice_out(), out_slice1_id);
+  EXPECT_EQ(f.slice_in(), in_slice_id);
+
+  f = flows[1];
+  EXPECT_EQ(f.slice_out(), out_slice2_id);
+  EXPECT_EQ(f.slice_in(), in_slice_id);
 }
 
 TEST_F(FlowTrackerTest, TwoFlowEventsSliceInSlice) {
@@ -227,10 +237,14 @@
 
   const auto& flows = context_.storage->flow_table();
   EXPECT_EQ(flows.row_count(), 2u);
-  EXPECT_EQ(flows.slice_out()[0], out_slice2_id);
-  EXPECT_EQ(flows.slice_in()[0], in_slice_id);
-  EXPECT_EQ(flows.slice_out()[1], out_slice1_id);
-  EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+
+  auto f = flows[0];
+  EXPECT_EQ(f.slice_out(), out_slice2_id);
+  EXPECT_EQ(f.slice_in(), in_slice_id);
+
+  f = flows[1];
+  EXPECT_EQ(f.slice_out(), out_slice1_id);
+  EXPECT_EQ(f.slice_in(), in_slice_id);
 }
 
 TEST_F(FlowTrackerTest, FlowEventsWithStep) {
@@ -268,10 +282,14 @@
 
   const auto& flows = context_.storage->flow_table();
   EXPECT_EQ(flows.row_count(), 2u);
-  EXPECT_EQ(flows.slice_out()[0], out_slice1_id);
-  EXPECT_EQ(flows.slice_in()[0], inout_slice2_id);
-  EXPECT_EQ(flows.slice_out()[1], inout_slice2_id);
-  EXPECT_EQ(flows.slice_in()[1], in_slice_id);
+
+  auto f = flows[0];
+  EXPECT_EQ(f.slice_out(), out_slice1_id);
+  EXPECT_EQ(f.slice_in(), inout_slice2_id);
+
+  f = flows[1];
+  EXPECT_EQ(f.slice_out(), inout_slice2_id);
+  EXPECT_EQ(f.slice_in(), in_slice_id);
 }
 
 }  // namespace
diff --git a/src/trace_processor/importers/common/global_args_tracker.h b/src/trace_processor/importers/common/global_args_tracker.h
index f94d2de..78bae43 100644
--- a/src/trace_processor/importers/common/global_args_tracker.h
+++ b/src/trace_processor/importers/common/global_args_tracker.h
@@ -17,10 +17,15 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_GLOBAL_ARGS_TRACKER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_GLOBAL_ARGS_TRACKER_H_
 
+#include <cstdint>
+#include <type_traits>
+#include <vector>
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/hash.h"
 #include "perfetto/ext/base/small_vector.h"
+#include "src/trace_processor/db/column.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/types/variadic.h"
 
 namespace perfetto {
@@ -123,19 +128,19 @@
       hash.Update(ArgHasher()(args[i]));
     }
 
-    auto* arg_table = storage_->mutable_arg_table();
+    auto& arg_table = *storage_->mutable_arg_table();
 
     ArgSetHash digest = hash.digest();
     auto it_and_inserted =
-        arg_row_for_hash_.Insert(digest, arg_table->row_count());
+        arg_row_for_hash_.Insert(digest, arg_table.row_count());
     if (!it_and_inserted.second) {
       // Already inserted.
-      return arg_table->arg_set_id()[*it_and_inserted.first];
+      return arg_table[*it_and_inserted.first].arg_set_id();
     }
 
     // Taking size() after the Insert() ensures that nothing has an id == 0
     // (0 == kInvalidArgSetId).
-    ArgSetId id = static_cast<uint32_t>(arg_row_for_hash_.size());
+    auto id = static_cast<uint32_t>(arg_row_for_hash_.size());
     for (uint32_t i : valid_indexes) {
       const auto& arg = args[i];
 
@@ -169,7 +174,7 @@
           break;
       }
       row.value_type = storage_->GetIdForVariadicType(arg.value.type);
-      arg_table->Insert(row);
+      arg_table.Insert(row);
     }
     return id;
   }
diff --git a/src/trace_processor/importers/common/metadata_tracker.cc b/src/trace_processor/importers/common/metadata_tracker.cc
index 743e115..9ff55e5 100644
--- a/src/trace_processor/importers/common/metadata_tracker.cc
+++ b/src/trace_processor/importers/common/metadata_tracker.cc
@@ -15,18 +15,23 @@
  */
 
 #include "src/trace_processor/importers/common/metadata_tracker.h"
+#include <cstddef>
+#include <cstdint>
+#include <optional>
 
+#include "perfetto/base/logging.h"
 #include "perfetto/ext/base/crash_keys.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 namespace {
-
 base::CrashKey g_crash_key_uuid("trace_uuid");
-
 }
 
 MetadataTracker::MetadataTracker(TraceStorage* storage) : storage_(storage) {
@@ -49,20 +54,23 @@
     g_crash_key_uuid.Set(uuid_string_view);
   }
 
-  auto* metadata_table = storage_->mutable_metadata_table();
-  uint32_t key_idx = static_cast<uint32_t>(key);
-  std::optional<uint32_t> opt_row =
-      metadata_table->name().IndexOf(metadata::kNames[key_idx]);
-  if (opt_row) {
-    WriteValue(*opt_row, value);
-    return metadata_table->id()[*opt_row];
+  auto& metadata_table = *storage_->mutable_metadata_table();
+  auto key_idx = static_cast<uint32_t>(key);
+  auto name_id = storage_->string_pool().GetId(metadata::kNames[key_idx]);
+  if (name_id) {
+    for (auto it = metadata_table.IterateRows(); it; ++it) {
+      if (it.name() == *name_id) {
+        WriteValue(it.row_number().row_number(), value);
+        return it.id();
+      }
+    }
   }
 
   tables::MetadataTable::Row row;
   row.name = key_ids_[key_idx];
   row.key_type = key_type_ids_[static_cast<size_t>(metadata::KeyType::kSingle)];
 
-  auto id_and_row = metadata_table->Insert(row);
+  auto id_and_row = metadata_table.Insert(row);
   WriteValue(id_and_row.row, value);
   return id_and_row.id;
 }
@@ -71,19 +79,32 @@
   // KeyType::kMulti not yet supported by this method:
   PERFETTO_CHECK(metadata::kKeyTypes[key] == metadata::KeyType::kSingle);
 
-  auto* metadata_table = storage_->mutable_metadata_table();
-  uint32_t key_idx = static_cast<uint32_t>(key);
-  std::optional<uint32_t> row =
-      metadata_table->name().IndexOf(metadata::kNames[key_idx]);
-  if (!row.has_value())
+  auto& metadata_table = *storage_->mutable_metadata_table();
+  auto key_idx = static_cast<uint32_t>(key);
+
+  auto key_id = storage_->string_pool().GetId(metadata::kNames[key_idx]);
+  if (!key_id) {
+    return std::nullopt;
+  }
+
+  std::optional<tables::MetadataTable::RowReference> row;
+  for (auto it = metadata_table.IterateRows(); it; ++it) {
+    if (key_id == it.name()) {
+      row = it.row_reference();
+      break;
+    }
+  }
+  if (!row.has_value()) {
     return {};
+  }
 
   auto value_type = metadata::kValueTypes[key];
   switch (value_type) {
-    case Variadic::kInt:
-      return metadata_table->mutable_int_value()->Get(*row);
+    case Variadic::kInt: {
+      return SqlValue::Long(*row->int_value());
+    }
     case Variadic::kString:
-      return metadata_table->mutable_str_value()->Get(*row);
+      return SqlValue::String(storage_->GetString(*row->str_value()).c_str());
     case Variadic::kNull:
       return SqlValue();
     case Variadic::kJson:
@@ -125,16 +146,17 @@
 }
 
 void MetadataTracker::WriteValue(uint32_t row, Variadic value) {
-  auto* metadata_table = storage_->mutable_metadata_table();
+  auto& metadata_table = *storage_->mutable_metadata_table();
+  auto rr = metadata_table[row];
   switch (value.type) {
     case Variadic::Type::kInt:
-      metadata_table->mutable_int_value()->Set(row, value.int_value);
+      rr.set_int_value(value.int_value);
       break;
     case Variadic::Type::kString:
-      metadata_table->mutable_str_value()->Set(row, value.string_value);
+      rr.set_str_value(value.string_value);
       break;
     case Variadic::Type::kJson:
-      metadata_table->mutable_str_value()->Set(row, value.json_value);
+      rr.set_str_value(value.json_value);
       break;
     case Variadic::Type::kBool:
     case Variadic::Type::kPointer:
@@ -145,5 +167,4 @@
   }
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/process_tracker.cc b/src/trace_processor/importers/common/process_tracker.cc
index e292801..e1f9c6d 100644
--- a/src/trace_processor/importers/common/process_tracker.cc
+++ b/src/trace_processor/importers/common/process_tracker.cc
@@ -90,8 +90,8 @@
 }
 
 void ProcessTracker::EndThread(int64_t timestamp, uint32_t tid) {
-  auto* thread_table = context_->storage->mutable_thread_table();
-  auto* process_table = context_->storage->mutable_process_table();
+  auto& thread_table = *context_->storage->mutable_thread_table();
+  auto& process_table = *context_->storage->mutable_process_table();
 
   // Don't bother creating a new thread if we're just going to
   // end it straight away.
@@ -106,21 +106,28 @@
     return;
 
   UniqueTid utid = *opt_utid;
-  thread_table->mutable_end_ts()->Set(utid, timestamp);
+
+  auto td = thread_table[utid];
+  td.set_end_ts(timestamp);
 
   // Remove the thread from the list of threads being tracked as any event after
   // this one should be ignored.
   auto& vector = tids_[tid];
   vector.erase(std::remove(vector.begin(), vector.end(), utid), vector.end());
 
-  auto opt_upid = thread_table->upid()[utid];
-  if (!opt_upid.has_value() || process_table->pid()[*opt_upid] != tid)
+  auto opt_upid = td.upid();
+  if (!opt_upid) {
     return;
+  }
+  auto ps = process_table[*opt_upid];
+  if (ps.pid() != tid) {
+    return;
+  }
 
   // If the process pid and thread tid are equal then, as is the main thread
   // of the process, we should also finish the process itself.
-  PERFETTO_DCHECK(thread_table->is_main_thread()[utid].value());
-  process_table->mutable_end_ts()->Set(*opt_upid, timestamp);
+  PERFETTO_DCHECK(*td.is_main_thread());
+  ps.set_end_ts(timestamp);
   pids_.Erase(tid);
 }
 
@@ -129,16 +136,17 @@
   if (!opt_utid)
     return std::nullopt;
 
-  auto* threads = context_->storage->mutable_thread_table();
+  auto& threads = *context_->storage->mutable_thread_table();
   UniqueTid utid = *opt_utid;
+  auto rr = threads[utid];
 
   // Ensure that the tid matches the tid we were looking for.
-  PERFETTO_DCHECK(threads->tid()[utid] == tid);
+  PERFETTO_DCHECK(rr.tid() == tid);
   // Ensure that the thread's machine ID matches the context's machine ID.
-  PERFETTO_DCHECK(threads->machine_id()[utid] == context_->machine_id());
+  PERFETTO_DCHECK(rr.machine_id() == context_->machine_id());
   // If the thread is being tracked by the process tracker, it should not be
   // known to have ended.
-  PERFETTO_DCHECK(!threads->end_ts()[utid].has_value());
+  PERFETTO_DCHECK(!rr.end_ts().has_value());
 
   return utid;
 }
@@ -162,7 +170,7 @@
   if (thread_name_id.is_null())
     return;
 
-  auto* thread_table = context_->storage->mutable_thread_table();
+  auto& thread_table = *context_->storage->mutable_thread_table();
   if (PERFETTO_UNLIKELY(thread_name_priorities_.size() <= utid)) {
     // This condition can happen in a multi-machine tracing session:
     // Machine 1 gets utid 0, 1
@@ -171,31 +179,33 @@
     thread_name_priorities_.resize(utid + 1);
   }
   if (priority >= thread_name_priorities_[utid]) {
-    thread_table->mutable_name()->Set(utid, thread_name_id);
+    thread_table[utid].set_name(thread_name_id);
     thread_name_priorities_[utid] = priority;
   }
 }
 
 bool ProcessTracker::IsThreadAlive(UniqueTid utid) {
-  auto* threads = context_->storage->mutable_thread_table();
-  auto* processes = context_->storage->mutable_process_table();
+  auto& threads = *context_->storage->mutable_thread_table();
+  auto& processes = *context_->storage->mutable_process_table();
 
   // If the thread has an end ts, it's certainly dead.
-  if (threads->end_ts()[utid].has_value())
+  auto rr = threads[utid];
+  if (rr.end_ts().has_value())
     return false;
 
   // If we don't know the parent process, we have to consider this thread alive.
-  auto opt_current_upid = threads->upid()[utid];
+  auto opt_current_upid = rr.upid();
   if (!opt_current_upid)
     return true;
 
   // If the process is already dead, the thread can't be alive.
   UniquePid current_upid = *opt_current_upid;
-  if (processes->end_ts()[current_upid].has_value())
+  auto prr = processes[current_upid];
+  if (prr.end_ts().has_value())
     return false;
 
   // If the process has been replaced in |pids_|, this thread is dead.
-  uint32_t current_pid = processes->pid()[current_upid];
+  uint32_t current_pid = prr.pid();
   auto* pid_it = pids_.Find(current_pid);
   return !pid_it || *pid_it == current_upid;
 }
@@ -203,8 +213,8 @@
 std::optional<UniqueTid> ProcessTracker::GetThreadOrNull(
     uint32_t tid,
     std::optional<uint32_t> pid) {
-  auto* threads = context_->storage->mutable_thread_table();
-  auto* processes = context_->storage->mutable_process_table();
+  auto& threads = *context_->storage->mutable_thread_table();
+  auto& processes = *context_->storage->mutable_process_table();
 
   auto* vector_it = tids_.Find(tid);
   if (!vector_it)
@@ -215,22 +225,24 @@
   const auto& vector = *vector_it;
   for (auto it = vector.rbegin(); it != vector.rend(); it++) {
     UniqueTid current_utid = *it;
+    auto rr = threads[current_utid];
 
     // If we finished this thread, we should have removed it from the vector
     // entirely.
-    PERFETTO_DCHECK(!threads->end_ts()[current_utid].has_value());
+    PERFETTO_DCHECK(!rr.end_ts().has_value());
 
     // If the thread is dead, ignore it.
     if (!IsThreadAlive(current_utid))
       continue;
 
     // If we don't know the parent process, we have to choose this thread.
-    auto opt_current_upid = threads->upid()[current_utid];
+    auto opt_current_upid = rr.upid();
     if (!opt_current_upid)
       return current_utid;
 
     // We found a thread that matches both the tid and its parent pid.
-    uint32_t current_pid = processes->pid()[*opt_current_upid];
+    auto prr = processes[*opt_current_upid];
+    uint32_t current_pid = prr.pid();
     if (!pid || current_pid == *pid)
       return current_utid;
   }
@@ -238,24 +250,23 @@
 }
 
 UniqueTid ProcessTracker::UpdateThread(uint32_t tid, uint32_t pid) {
-  auto* thread_table = context_->storage->mutable_thread_table();
+  auto& thread_table = *context_->storage->mutable_thread_table();
 
   // Try looking for a thread that matches both tid and thread group id (pid).
   std::optional<UniqueTid> opt_utid = GetThreadOrNull(tid, pid);
 
   // If no matching thread was found, create a new one.
   UniqueTid utid = opt_utid ? *opt_utid : StartNewThread(std::nullopt, tid);
-  PERFETTO_DCHECK(thread_table->tid()[utid] == tid);
+  auto rr = thread_table[utid];
+  PERFETTO_DCHECK(rr.tid() == tid);
   // Ensure that the thread's machine ID matches the context's machine ID.
-  PERFETTO_DCHECK(thread_table->machine_id()[utid] == context_->machine_id());
+  PERFETTO_DCHECK(rr.machine_id() == context_->machine_id());
 
   // Find matching process or create new one.
-  if (!thread_table->upid()[utid].has_value()) {
+  if (!rr.upid().has_value()) {
     AssociateThreadToProcess(utid, GetOrCreateProcess(pid));
   }
-
-  ResolvePendingAssociations(utid, *thread_table->upid()[utid]);
-
+  ResolvePendingAssociations(utid, *rr.upid());
   return utid;
 }
 
@@ -321,22 +332,23 @@
   // process.
   UniquePid upid = GetOrCreateProcess(pid);
 
-  auto* process_table = context_->storage->mutable_process_table();
-  auto* thread_table = context_->storage->mutable_thread_table();
+  auto& process_table = *context_->storage->mutable_process_table();
+  auto& thread_table = *context_->storage->mutable_thread_table();
 
-  PERFETTO_DCHECK(!process_table->name()[upid].has_value());
-  PERFETTO_DCHECK(!process_table->start_ts()[upid].has_value());
+  auto prr = process_table[upid];
+  PERFETTO_DCHECK(!prr.name().has_value());
+  PERFETTO_DCHECK(!prr.start_ts().has_value());
 
   if (timestamp) {
-    process_table->mutable_start_ts()->Set(upid, *timestamp);
+    prr.set_start_ts(*timestamp);
   }
-  process_table->mutable_name()->Set(upid, main_thread_name);
+  prr.set_name(main_thread_name);
 
   if (parent_tid) {
     UniqueTid parent_utid = GetOrCreateThread(*parent_tid);
-    auto opt_parent_upid = thread_table->upid()[parent_utid];
+    auto opt_parent_upid = thread_table[parent_utid].upid();
     if (opt_parent_upid.has_value()) {
-      process_table->mutable_parent_upid()->Set(upid, *opt_parent_upid);
+      prr.set_parent_upid(*opt_parent_upid);
     } else {
       pending_parent_assocs_.emplace_back(parent_utid, upid);
     }
@@ -354,13 +366,13 @@
   }
 
   UniquePid upid = GetOrCreateProcess(pid);
-  auto* process_table = context_->storage->mutable_process_table();
+  auto& process_table = *context_->storage->mutable_process_table();
 
   // If we both know the previous and current parent pid and the two are not
   // matching, we must have died and restarted: create a new process.
+  auto prr = process_table[upid];
   if (pupid) {
-    std::optional<UniquePid> prev_parent_upid =
-        process_table->parent_upid()[upid];
+    std::optional<UniquePid> prev_parent_upid = prr.parent_upid();
     if (prev_parent_upid && prev_parent_upid != pupid) {
       upid = StartNewProcess(std::nullopt, ppid, pid, kNullStringId,
                              ThreadNamePriority::kOther);
@@ -368,61 +380,68 @@
   }
 
   StringId proc_name_id = context_->storage->InternString(name);
-  process_table->mutable_name()->Set(upid, proc_name_id);
-  process_table->mutable_cmdline()->Set(
-      upid, context_->storage->InternString(cmdline));
-  if (pupid)
-    process_table->mutable_parent_upid()->Set(upid, *pupid);
-
+  prr.set_name(proc_name_id);
+  prr.set_cmdline(context_->storage->InternString(cmdline));
+  if (pupid) {
+    prr.set_parent_upid(*pupid);
+  }
   return upid;
 }
 
 void ProcessTracker::SetProcessUid(UniquePid upid, uint32_t uid) {
-  auto* process_table = context_->storage->mutable_process_table();
-  process_table->mutable_uid()->Set(upid, uid);
+  auto& process_table = *context_->storage->mutable_process_table();
+  auto rr = process_table[upid];
+  rr.set_uid(uid);
 
   // The notion of the app ID (as derived from the uid) is defined in
   // frameworks/base/core/java/android/os/UserHandle.java
-  process_table->mutable_android_appid()->Set(upid, uid % 100000);
+  rr.set_android_appid(uid % 100000);
 }
 
 void ProcessTracker::SetProcessNameIfUnset(UniquePid upid,
                                            StringId process_name_id) {
-  auto* process_table = context_->storage->mutable_process_table();
-  if (!process_table->name()[upid].has_value())
-    process_table->mutable_name()->Set(upid, process_name_id);
+  auto& pt = *context_->storage->mutable_process_table();
+  if (auto rr = pt[upid]; !rr.name().has_value()) {
+    rr.set_name(process_name_id);
+  }
 }
 
 void ProcessTracker::SetStartTsIfUnset(UniquePid upid,
                                        int64_t start_ts_nanoseconds) {
-  auto* process_table = context_->storage->mutable_process_table();
-  if (!process_table->start_ts()[upid].has_value())
-    process_table->mutable_start_ts()->Set(upid, start_ts_nanoseconds);
+  auto& pt = *context_->storage->mutable_process_table();
+  if (auto rr = pt[upid]; !rr.start_ts().has_value()) {
+    rr.set_start_ts(start_ts_nanoseconds);
+  }
 }
 
 void ProcessTracker::UpdateThreadNameAndMaybeProcessName(
     uint32_t tid,
     StringId thread_name,
     ThreadNamePriority priority) {
-  auto* thread_table = context_->storage->mutable_thread_table();
-  auto* process_table = context_->storage->mutable_process_table();
+  auto& tt = *context_->storage->mutable_thread_table();
+  auto& pt = *context_->storage->mutable_process_table();
 
   UniqueTid utid = UpdateThreadName(tid, thread_name, priority);
-  std::optional<UniquePid> opt_upid = thread_table->upid()[utid];
-  if (opt_upid.has_value() && process_table->pid()[*opt_upid] == tid) {
-    PERFETTO_DCHECK(thread_table->is_main_thread()[utid]);
-    process_table->mutable_name()->Set(*opt_upid, thread_name);
+  auto trr = tt[utid];
+  std::optional<UniquePid> opt_upid = trr.upid();
+  if (!opt_upid.has_value()) {
+    return;
+  }
+  auto prr = pt[*opt_upid];
+  if (prr.pid() == tid) {
+    PERFETTO_DCHECK(trr.is_main_thread());
+    prr.set_name(thread_name);
   }
 }
 
 UniquePid ProcessTracker::GetOrCreateProcess(uint32_t pid) {
-  auto* process_table = context_->storage->mutable_process_table();
+  auto& process_table = *context_->storage->mutable_process_table();
 
   // If the insertion succeeds, we'll fill the upid below.
   auto it_and_ins = pids_.Insert(pid, UniquePid{0});
   if (!it_and_ins.second) {
     // Ensure that the process has not ended.
-    PERFETTO_DCHECK(!process_table->end_ts()[*it_and_ins.first].has_value());
+    PERFETTO_DCHECK(!process_table[*it_and_ins.first].end_ts().has_value());
     return *it_and_ins.first;
   }
 
@@ -430,7 +449,7 @@
   row.pid = pid;
   row.machine_id = context_->machine_id();
 
-  UniquePid upid = process_table->Insert(row).row;
+  UniquePid upid = process_table.Insert(row).row;
   *it_and_ins.first = upid;  // Update the newly inserted hashmap entry.
 
   // Create an entry for the main thread.
@@ -442,14 +461,16 @@
 }
 
 void ProcessTracker::AssociateThreads(UniqueTid utid1, UniqueTid utid2) {
-  auto* tt = context_->storage->mutable_thread_table();
+  auto& tt = *context_->storage->mutable_thread_table();
 
   // First of all check if one of the two threads is already bound to a process.
   // If that is the case, map the other thread to the same process and resolve
   // recursively any associations pending on the other thread.
 
-  auto opt_upid1 = tt->upid()[utid1];
-  auto opt_upid2 = tt->upid()[utid2];
+  auto rr1 = tt[utid1];
+  auto rr2 = tt[utid2];
+  auto opt_upid1 = rr1.upid();
+  auto opt_upid2 = rr2.upid();
 
   if (opt_upid1.has_value() && !opt_upid2.has_value()) {
     AssociateThreadToProcess(utid2, *opt_upid1);
@@ -466,7 +487,7 @@
   if (opt_upid1.has_value() && opt_upid1 != opt_upid2) {
     // Cannot associate two threads that belong to two different processes.
     PERFETTO_ELOG("Process tracker failure. Cannot associate threads %u, %u",
-                  tt->tid()[utid1], tt->tid()[utid2]);
+                  rr1.tid(), rr2.tid());
     context_->storage->IncrementStats(stats::process_tracker_errors);
     return;
   }
@@ -476,9 +497,11 @@
 
 void ProcessTracker::ResolvePendingAssociations(UniqueTid utid_arg,
                                                 UniquePid upid) {
-  auto* tt = context_->storage->mutable_thread_table();
-  auto* pt = context_->storage->mutable_process_table();
-  PERFETTO_DCHECK(tt->upid()[utid_arg] == upid);
+  auto& tt = *context_->storage->mutable_thread_table();
+  auto& pt = *context_->storage->mutable_process_table();
+
+  auto trr = tt[utid_arg];
+  PERFETTO_DCHECK(trr.upid() == upid);
 
   std::vector<UniqueTid> resolved_utids;
   resolved_utids.emplace_back(utid_arg);
@@ -498,9 +521,9 @@
       PERFETTO_DCHECK(child_upid != upid);
 
       // Set the parent pid of the other process
-      PERFETTO_DCHECK(!pt->parent_upid()[child_upid] ||
-                      pt->parent_upid()[child_upid] == upid);
-      pt->mutable_parent_upid()->Set(child_upid, upid);
+      auto crr = pt[child_upid];
+      PERFETTO_DCHECK(!crr.parent_upid() || crr.parent_upid() == upid);
+      crr.set_parent_upid(upid);
 
       // Erase the pair. The |pending_parent_assocs_| vector is not sorted and
       // swapping a std::pair<uint32_t, uint32_t> is cheap.
@@ -523,8 +546,8 @@
       PERFETTO_DCHECK(other_utid != utid);
 
       // Update the other thread and associated it to the same process.
-      PERFETTO_DCHECK(!tt->upid()[other_utid] ||
-                      tt->upid()[other_utid] == upid);
+      auto orr = tt[other_utid];
+      PERFETTO_DCHECK(!orr.upid() || orr.upid() == upid);
       AssociateThreadToProcess(other_utid, upid);
 
       // Swap the current element to the end of the list and move the end
@@ -545,11 +568,13 @@
 }
 
 void ProcessTracker::AssociateThreadToProcess(UniqueTid utid, UniquePid upid) {
-  auto* thread_table = context_->storage->mutable_thread_table();
-  thread_table->mutable_upid()->Set(utid, upid);
-  auto* process_table = context_->storage->mutable_process_table();
-  bool main_thread = thread_table->tid()[utid] == process_table->pid()[upid];
-  thread_table->mutable_is_main_thread()->Set(utid, main_thread);
+  auto& thread_table = *context_->storage->mutable_thread_table();
+  auto& process_table = *context_->storage->mutable_process_table();
+
+  auto trr = thread_table[utid];
+  auto prr = process_table[upid];
+  trr.set_upid(upid);
+  trr.set_is_main_thread(trr.tid() == prr.pid());
 }
 
 void ProcessTracker::SetPidZeroIsUpidZeroIdleProcess() {
diff --git a/src/trace_processor/importers/common/process_tracker_unittest.cc b/src/trace_processor/importers/common/process_tracker_unittest.cc
index e4f06ce..361d388 100644
--- a/src/trace_processor/importers/common/process_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/process_tracker_unittest.cc
@@ -16,15 +16,17 @@
 
 #include "src/trace_processor/importers/common/process_tracker.h"
 
+#include <memory>
 #include <optional>
 
-#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "test/gtest_and_gmock.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
 
 using ::testing::_;
@@ -34,12 +36,12 @@
 class ProcessTrackerTest : public ::testing::Test {
  public:
   ProcessTrackerTest() {
-    context.storage.reset(new TraceStorage());
-    context.global_args_tracker.reset(
-        new GlobalArgsTracker(context.storage.get()));
-    context.args_tracker.reset(new ArgsTracker(&context));
-    context.process_tracker.reset(new ProcessTracker(&context));
-    context.event_tracker.reset(new EventTracker(&context));
+    context.storage = std::make_shared<TraceStorage>();
+    context.global_args_tracker =
+        std::make_unique<GlobalArgsTracker>(context.storage.get());
+    context.args_tracker = std::make_unique<ArgsTracker>(&context);
+    context.process_tracker = std::make_unique<ProcessTracker>(&context);
+    context.event_tracker = std::make_unique<EventTracker>(&context);
   }
 
  protected:
@@ -62,7 +64,7 @@
   auto upid = context.process_tracker->StartNewProcess(
       1000, 0u, 123, kNullStringId, ThreadNamePriority::kFtrace);
   ASSERT_EQ(context.process_tracker->GetOrCreateProcess(123), upid);
-  ASSERT_EQ(context.storage->process_table().start_ts()[upid], 1000);
+  ASSERT_EQ(context.storage->process_table()[upid].start_ts(), 1000);
 }
 
 TEST_F(ProcessTrackerTest, PushTwoProcessEntries_SamePidAndName) {
@@ -88,9 +90,8 @@
 TEST_F(ProcessTrackerTest, AddProcessEntry_CorrectName) {
   context.process_tracker->SetProcessMetadata(1, std::nullopt, "test",
                                               base::StringView());
-  auto name = context.storage->process_table().name().GetString(1);
-
-  ASSERT_EQ(name, "test");
+  auto name = context.storage->process_table()[1].name();
+  ASSERT_EQ(context.storage->GetString(*name), "test");
 }
 
 TEST_F(ProcessTrackerTest, UpdateThreadCreate) {
@@ -101,7 +102,7 @@
 
   auto tid_it = context.process_tracker->UtidsForTidForTesting(12);
   ASSERT_NE(tid_it.first, tid_it.second);
-  ASSERT_EQ(context.storage->thread_table().upid()[1].value(), 1u);
+  ASSERT_EQ(context.storage->thread_table()[1].upid().value(), 1u);
   auto opt_upid = context.process_tracker->UpidForPidForTesting(2);
   ASSERT_TRUE(opt_upid.has_value());
   ASSERT_EQ(context.storage->process_table().row_count(), 2u);
@@ -130,8 +131,8 @@
 TEST_F(ProcessTrackerTest, Cmdline) {
   UniquePid upid = context.process_tracker->SetProcessMetadata(
       1, std::nullopt, "test", "cmdline blah");
-  ASSERT_EQ(context.storage->process_table().cmdline().GetString(upid),
-            "cmdline blah");
+  auto cmdline = *context.storage->process_table()[upid].cmdline();
+  ASSERT_EQ(context.storage->GetString(cmdline), "cmdline blah");
 }
 
 TEST_F(ProcessTrackerTest, UpdateThreadName) {
@@ -142,19 +143,19 @@
   context.process_tracker->UpdateThreadName(1, name1,
                                             ThreadNamePriority::kFtrace);
   ASSERT_EQ(context.storage->thread_table().row_count(), 2u);
-  ASSERT_EQ(context.storage->thread_table().name()[1], name1);
+  ASSERT_EQ(context.storage->thread_table()[1].name(), name1);
 
   context.process_tracker->UpdateThreadName(1, name2,
                                             ThreadNamePriority::kProcessTree);
   // The priority is higher: the name should change.
   ASSERT_EQ(context.storage->thread_table().row_count(), 2u);
-  ASSERT_EQ(context.storage->thread_table().name()[1], name2);
+  ASSERT_EQ(context.storage->thread_table()[1].name(), name2);
 
   context.process_tracker->UpdateThreadName(1, name3,
                                             ThreadNamePriority::kFtrace);
   // The priority is lower: the name should stay the same.
   ASSERT_EQ(context.storage->thread_table().row_count(), 2u);
-  ASSERT_EQ(context.storage->thread_table().name()[1], name2);
+  ASSERT_EQ(context.storage->thread_table()[1].name(), name2);
 }
 
 TEST_F(ProcessTrackerTest, SetStartTsIfUnset) {
@@ -162,10 +163,10 @@
       /*timestamp=*/std::nullopt, 0u, 123, kNullStringId,
       ThreadNamePriority::kFtrace);
   context.process_tracker->SetStartTsIfUnset(upid, 1000);
-  ASSERT_EQ(context.storage->process_table().start_ts()[upid], 1000);
+  ASSERT_EQ(context.storage->process_table()[upid].start_ts(), 1000);
 
   context.process_tracker->SetStartTsIfUnset(upid, 3000);
-  ASSERT_EQ(context.storage->process_table().start_ts()[upid], 1000);
+  ASSERT_EQ(context.storage->process_table()[upid].start_ts(), 1000);
 }
 
 TEST_F(ProcessTrackerTest, PidReuseAfterExplicitEnd) {
@@ -264,5 +265,4 @@
 }
 
 }  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/sched_event_tracker.h b/src/trace_processor/importers/common/sched_event_tracker.h
index ff4f537..7a632eb 100644
--- a/src/trace_processor/importers/common/sched_event_tracker.h
+++ b/src/trace_processor/importers/common/sched_event_tracker.h
@@ -17,15 +17,17 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
 
-#include "src/trace_processor/importers/common/event_tracker.h"
+#include <cstdint>
 
+#include "perfetto/base/logging.h"
+#include "perfetto/public/compiler.h"
 #include "src/trace_processor/importers/common/cpu_tracker.h"
+#include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/destructible.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 // Tracks per-cpu scheduling events, storing them as slices in the |sched|
 // table.
@@ -51,7 +53,7 @@
     auto row_and_id = sched->Insert(
         {ts, /* duration */ -1, next_utid, kNullStringId, next_prio, ucpu});
     SchedId sched_id = row_and_id.id;
-    return *sched->id().IndexOf(sched_id);
+    return sched->FindById(sched_id)->ToRowNumber().row_number();
   }
 
   PERFETTO_ALWAYS_INLINE
@@ -75,21 +77,15 @@
                          int64_t ts,
                          StringId prev_state) {
     auto* slices = context_->storage->mutable_sched_slice_table();
-
-    int64_t duration = ts - slices->ts()[pending_slice_idx];
-    slices->mutable_dur()->Set(pending_slice_idx, duration);
-
-    // We store the state as a uint16 as we only consider values up to 2048
-    // when unpacking the information inside; this allows savings of 48 bits
-    // per slice.
-    slices->mutable_end_state()->Set(pending_slice_idx, prev_state);
+    auto r = (*slices)[pending_slice_idx];
+    r.set_dur(ts - r.ts());
+    r.set_end_state(prev_state);
   }
 
  private:
   TraceProcessorContext* const context_;
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_COMMON_SCHED_EVENT_TRACKER_H_
diff --git a/src/trace_processor/importers/common/slice_tracker_unittest.cc b/src/trace_processor/importers/common/slice_tracker_unittest.cc
index 73fa8bf..b999ca1 100644
--- a/src/trace_processor/importers/common/slice_tracker_unittest.cc
+++ b/src/trace_processor/importers/common/slice_tracker_unittest.cc
@@ -14,7 +14,11 @@
  * limitations under the License.
  */
 
+#include <cstdint>
 #include <memory>
+#include <optional>
+#include <ostream>
+#include <tuple>
 #include <vector>
 
 #include "src/trace_processor/importers/common/args_tracker.h"
@@ -22,11 +26,12 @@
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/slice_translation_table.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/slice_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
 #include "test/gtest_and_gmock.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
 
 using ::testing::ElementsAre;
@@ -47,8 +52,8 @@
 
 std::vector<SliceInfo> ToSliceInfo(const tables::SliceTable& slices) {
   std::vector<SliceInfo> infos;
-  for (uint32_t i = 0; i < slices.row_count(); i++) {
-    infos.emplace_back(SliceInfo{slices.ts()[i], slices.dur()[i]});
+  for (auto it = slices.IterateRows(); it; ++it) {
+    infos.emplace_back(SliceInfo{it.ts(), it.dur()});
   }
   return infos;
 }
@@ -80,13 +85,13 @@
 
   const auto& slices = context_.storage->slice_table();
   EXPECT_EQ(slices.row_count(), 1u);
-  EXPECT_EQ(slices.ts()[0], 2);
-  EXPECT_EQ(slices.dur()[0], 8);
-  EXPECT_EQ(slices.track_id()[0], track);
-  EXPECT_EQ(slices.category()[0].value_or(kNullStringId).raw_id(), 0u);
-  EXPECT_EQ(slices.name()[0].value_or(kNullStringId).raw_id(), 1u);
-  EXPECT_EQ(slices.depth()[0], 0u);
-  EXPECT_EQ(slices.arg_set_id()[0], kInvalidArgSetId);
+  EXPECT_EQ(slices[0].ts(), 2);
+  EXPECT_EQ(slices[0].dur(), 8);
+  EXPECT_EQ(slices[0].track_id(), track);
+  EXPECT_EQ(slices[0].category().value_or(kNullStringId).raw_id(), 0u);
+  EXPECT_EQ(slices[0].name().value_or(kNullStringId).raw_id(), 1u);
+  EXPECT_EQ(slices[0].depth(), 0u);
+  EXPECT_EQ(slices[0].arg_set_id(), kInvalidArgSetId);
 }
 
 TEST_F(SliceTrackerTest, OneSliceDetailedWithTranslatedName) {
@@ -103,14 +108,14 @@
 
   const auto& slices = context_.storage->slice_table();
   EXPECT_EQ(slices.row_count(), 1u);
-  EXPECT_EQ(slices.ts()[0], 2);
-  EXPECT_EQ(slices.dur()[0], 8);
-  EXPECT_EQ(slices.track_id()[0], track);
-  EXPECT_EQ(slices.category()[0].value_or(kNullStringId).raw_id(), 0u);
-  EXPECT_EQ(slices.name()[0].value_or(kNullStringId).raw_id(),
+  EXPECT_EQ(slices[0].ts(), 2);
+  EXPECT_EQ(slices[0].dur(), 8);
+  EXPECT_EQ(slices[0].track_id(), track);
+  EXPECT_EQ(slices[0].category().value_or(kNullStringId).raw_id(), 0u);
+  EXPECT_EQ(slices[0].name().value_or(kNullStringId).raw_id(),
             mapped_name.raw_id());
-  EXPECT_EQ(slices.depth()[0], 0u);
-  EXPECT_EQ(slices.arg_set_id()[0], kInvalidArgSetId);
+  EXPECT_EQ(slices[0].depth(), 0u);
+  EXPECT_EQ(slices[0].arg_set_id(), kInvalidArgSetId);
 }
 
 TEST_F(SliceTrackerTest, NegativeTimestamps) {
@@ -124,13 +129,15 @@
 
   const auto& slices = context_.storage->slice_table();
   EXPECT_EQ(slices.row_count(), 1u);
-  EXPECT_EQ(slices.ts()[0], -1000);
-  EXPECT_EQ(slices.dur()[0], 499);
-  EXPECT_EQ(slices.track_id()[0], track);
-  EXPECT_EQ(slices.category()[0].value_or(kNullStringId).raw_id(), 0u);
-  EXPECT_EQ(slices.name()[0].value_or(kNullStringId).raw_id(), 1u);
-  EXPECT_EQ(slices.depth()[0], 0u);
-  EXPECT_EQ(slices.arg_set_id()[0], kInvalidArgSetId);
+
+  auto rr = slices[0];
+  EXPECT_EQ(rr.ts(), -1000);
+  EXPECT_EQ(rr.dur(), 499);
+  EXPECT_EQ(rr.track_id(), track);
+  EXPECT_EQ(rr.category().value_or(kNullStringId).raw_id(), 0u);
+  EXPECT_EQ(rr.name().value_or(kNullStringId).raw_id(), 1u);
+  EXPECT_EQ(rr.depth(), 0u);
+  EXPECT_EQ(rr.arg_set_id(), kInvalidArgSetId);
 }
 
 TEST_F(SliceTrackerTest, OneSliceWithArgs) {
@@ -142,35 +149,39 @@
                 [](ArgsTracker::BoundInserter* inserter) {
                   inserter->AddArg(/*flat_key=*/StringId::Raw(1),
                                    /*key=*/StringId::Raw(2),
-                                   /*value=*/Variadic::Integer(10));
+                                   /*v=*/Variadic::Integer(10));
                 });
   tracker.End(10 /*ts*/, track, kNullStringId /*cat*/,
               StringId::Raw(1) /*name*/,
               [](ArgsTracker::BoundInserter* inserter) {
                 inserter->AddArg(/*flat_key=*/StringId::Raw(3),
                                  /*key=*/StringId::Raw(4),
-                                 /*value=*/Variadic::Integer(20));
+                                 /*v=*/Variadic::Integer(20));
               });
 
   const auto& slices = context_.storage->slice_table();
   EXPECT_EQ(slices.row_count(), 1u);
-  EXPECT_EQ(slices.ts()[0], 2);
-  EXPECT_EQ(slices.dur()[0], 8);
-  EXPECT_EQ(slices.track_id()[0], track);
-  EXPECT_EQ(slices.category()[0].value_or(kNullStringId).raw_id(), 0u);
-  EXPECT_EQ(slices.name()[0].value_or(kNullStringId).raw_id(), 1u);
-  EXPECT_EQ(slices.depth()[0], 0u);
-  auto set_id = slices.arg_set_id()[0];
+
+  auto sr = slices[0];
+  EXPECT_EQ(sr.ts(), 2);
+  EXPECT_EQ(sr.dur(), 8);
+  EXPECT_EQ(sr.track_id(), track);
+  EXPECT_EQ(sr.category().value_or(kNullStringId).raw_id(), 0u);
+  EXPECT_EQ(sr.name().value_or(kNullStringId).raw_id(), 1u);
+  EXPECT_EQ(sr.depth(), 0u);
+  auto set_id = sr.arg_set_id();
 
   const auto& args = context_.storage->arg_table();
-  EXPECT_EQ(args.arg_set_id()[0], set_id);
-  EXPECT_EQ(args.flat_key()[0].raw_id(), 1u);
-  EXPECT_EQ(args.key()[0].raw_id(), 2u);
-  EXPECT_EQ(args.int_value()[0], 10);
-  EXPECT_EQ(args.arg_set_id()[1], set_id);
-  EXPECT_EQ(args.flat_key()[1].raw_id(), 3u);
-  EXPECT_EQ(args.key()[1].raw_id(), 4u);
-  EXPECT_EQ(args.int_value()[1], 20);
+  auto ar0 = args[0];
+  auto ar1 = args[1];
+  EXPECT_EQ(ar0.arg_set_id(), set_id);
+  EXPECT_EQ(ar0.flat_key().raw_id(), 1u);
+  EXPECT_EQ(ar0.key().raw_id(), 2u);
+  EXPECT_EQ(ar0.int_value(), 10);
+  EXPECT_EQ(ar1.arg_set_id(), set_id);
+  EXPECT_EQ(ar1.flat_key().raw_id(), 3u);
+  EXPECT_EQ(ar1.key().raw_id(), 4u);
+  EXPECT_EQ(ar1.int_value(), 20);
 }
 
 TEST_F(SliceTrackerTest, OneSliceWithArgsWithTranslatedName) {
@@ -186,35 +197,38 @@
                 [](ArgsTracker::BoundInserter* inserter) {
                   inserter->AddArg(/*flat_key=*/StringId::Raw(1),
                                    /*key=*/StringId::Raw(2),
-                                   /*value=*/Variadic::Integer(10));
+                                   /*v=*/Variadic::Integer(10));
                 });
   tracker.End(10 /*ts*/, track, kNullStringId /*cat*/, raw_name /*name*/,
               [](ArgsTracker::BoundInserter* inserter) {
                 inserter->AddArg(/*flat_key=*/StringId::Raw(3),
                                  /*key=*/StringId::Raw(4),
-                                 /*value=*/Variadic::Integer(20));
+                                 /*v=*/Variadic::Integer(20));
               });
 
   const auto& slices = context_.storage->slice_table();
   EXPECT_EQ(slices.row_count(), 1u);
-  EXPECT_EQ(slices.ts()[0], 2);
-  EXPECT_EQ(slices.dur()[0], 8);
-  EXPECT_EQ(slices.track_id()[0], track);
-  EXPECT_EQ(slices.category()[0].value_or(kNullStringId).raw_id(), 0u);
-  EXPECT_EQ(slices.name()[0].value_or(kNullStringId).raw_id(),
-            mapped_name.raw_id());
-  EXPECT_EQ(slices.depth()[0], 0u);
-  auto set_id = slices.arg_set_id()[0];
+
+  auto sr = slices[0];
+  EXPECT_EQ(sr.ts(), 2);
+  EXPECT_EQ(sr.dur(), 8);
+  EXPECT_EQ(sr.track_id(), track);
+  EXPECT_EQ(sr.category().value_or(kNullStringId).raw_id(), 0u);
+  EXPECT_EQ(sr.name().value_or(kNullStringId).raw_id(), mapped_name.raw_id());
+  EXPECT_EQ(sr.depth(), 0u);
+  auto set_id = sr.arg_set_id();
 
   const auto& args = context_.storage->arg_table();
-  EXPECT_EQ(args.arg_set_id()[0], set_id);
-  EXPECT_EQ(args.flat_key()[0].raw_id(), 1u);
-  EXPECT_EQ(args.key()[0].raw_id(), 2u);
-  EXPECT_EQ(args.int_value()[0], 10);
-  EXPECT_EQ(args.arg_set_id()[1], set_id);
-  EXPECT_EQ(args.flat_key()[1].raw_id(), 3u);
-  EXPECT_EQ(args.key()[1].raw_id(), 4u);
-  EXPECT_EQ(args.int_value()[1], 20);
+  auto ar0 = args[0];
+  auto ar1 = args[1];
+  EXPECT_EQ(ar0.arg_set_id(), set_id);
+  EXPECT_EQ(ar0.flat_key().raw_id(), 1u);
+  EXPECT_EQ(ar0.key().raw_id(), 2u);
+  EXPECT_EQ(ar0.int_value(), 10);
+  EXPECT_EQ(ar1.arg_set_id(), set_id);
+  EXPECT_EQ(ar1.flat_key().raw_id(), 3u);
+  EXPECT_EQ(ar1.key().raw_id(), 4u);
+  EXPECT_EQ(ar1.int_value(), 20);
 }
 
 TEST_F(SliceTrackerTest, TwoSliceDetailed) {
@@ -232,24 +246,25 @@
 
   EXPECT_EQ(slices.row_count(), 2u);
 
-  uint32_t idx = 0;
-  EXPECT_EQ(slices.ts()[idx], 2);
-  EXPECT_EQ(slices.dur()[idx], 8);
-  EXPECT_EQ(slices.track_id()[idx], track);
-  EXPECT_EQ(slices.category()[idx].value_or(kNullStringId).raw_id(), 0u);
-  EXPECT_EQ(slices.name()[idx].value_or(kNullStringId).raw_id(), 1u);
-  EXPECT_EQ(slices.depth()[idx++], 0u);
+  auto sr0 = slices[0];
+  EXPECT_EQ(sr0.ts(), 2);
+  EXPECT_EQ(sr0.dur(), 8);
+  EXPECT_EQ(sr0.track_id(), track);
+  EXPECT_EQ(sr0.category().value_or(kNullStringId).raw_id(), 0u);
+  EXPECT_EQ(sr0.name().value_or(kNullStringId).raw_id(), 1u);
+  EXPECT_EQ(sr0.depth(), 0u);
+  EXPECT_EQ(sr0.parent_stack_id(), 0);
 
-  EXPECT_EQ(slices.ts()[idx], 3);
-  EXPECT_EQ(slices.dur()[idx], 2);
-  EXPECT_EQ(slices.track_id()[idx], track);
-  EXPECT_EQ(slices.category()[idx].value_or(kNullStringId).raw_id(), 0u);
-  EXPECT_EQ(slices.name()[idx].value_or(kNullStringId).raw_id(), 2u);
-  EXPECT_EQ(slices.depth()[idx], 1u);
+  auto sr1 = slices[1];
+  EXPECT_EQ(sr1.ts(), 3);
+  EXPECT_EQ(sr1.dur(), 2);
+  EXPECT_EQ(sr1.track_id(), track);
+  EXPECT_EQ(sr1.category().value_or(kNullStringId).raw_id(), 0u);
+  EXPECT_EQ(sr1.name().value_or(kNullStringId).raw_id(), 2u);
+  EXPECT_EQ(sr1.depth(), 1u);
+  EXPECT_NE(sr1.stack_id(), 0);
 
-  EXPECT_EQ(slices.parent_stack_id()[0], 0);
-  EXPECT_EQ(slices.stack_id()[0], slices.parent_stack_id()[1]);
-  EXPECT_NE(slices.stack_id()[1], 0);
+  EXPECT_EQ(sr0.stack_id(), sr1.parent_stack_id());
 }
 
 TEST_F(SliceTrackerTest, Scoped) {
@@ -297,8 +312,8 @@
   tracker.End(150, track);
   tracker.End(200, track);
 
-  SliceId parent = context_.storage->slice_table().id()[0];
-  SliceId child = context_.storage->slice_table().id()[1];
+  SliceId parent = context_.storage->slice_table()[0].id();
+  SliceId child = context_.storage->slice_table()[1].id();
   EXPECT_THAT(context_.storage->slice_table().parent_id().ToVectorForTesting(),
               ElementsAre(std::nullopt, parent, child));
 }
@@ -352,16 +367,17 @@
   tracker.End(10 /*ts*/, track_a);
   tracker.FlushPendingSlices();
 
-  auto slices = ToSliceInfo(context_.storage->slice_table());
+  const auto& table = context_.storage->slice_table();
+  auto slices = ToSliceInfo(table);
   EXPECT_THAT(slices,
               ElementsAre(SliceInfo{0, 10}, SliceInfo{2, 6}, SliceInfo{3, 4}));
 
-  EXPECT_EQ(context_.storage->slice_table().track_id()[0], track_a);
-  EXPECT_EQ(context_.storage->slice_table().track_id()[1], track_b);
-  EXPECT_EQ(context_.storage->slice_table().track_id()[2], track_b);
-  EXPECT_EQ(context_.storage->slice_table().depth()[0], 0u);
-  EXPECT_EQ(context_.storage->slice_table().depth()[1], 0u);
-  EXPECT_EQ(context_.storage->slice_table().depth()[2], 1u);
+  EXPECT_EQ(table[0].track_id(), track_a);
+  EXPECT_EQ(table[1].track_id(), track_b);
+  EXPECT_EQ(table[2].track_id(), track_b);
+  EXPECT_EQ(table[0].depth(), 0u);
+  EXPECT_EQ(table[1].depth(), 0u);
+  EXPECT_EQ(table[2].depth(), 1u);
 }
 
 TEST_F(SliceTrackerTest, EndEventOutOfOrder) {
@@ -401,15 +417,16 @@
 
   tracker.FlushPendingSlices();
 
-  auto slices = ToSliceInfo(context_.storage->slice_table());
+  const auto& st = context_.storage->slice_table();
+  auto slices = ToSliceInfo(st);
   EXPECT_THAT(slices, ElementsAre(SliceInfo{50, 100}, SliceInfo{100, 50},
                                   SliceInfo{450, 100}, SliceInfo{800, 200},
                                   SliceInfo{1100, -1}, SliceInfo{1300, 0 - 1}));
 
-  EXPECT_EQ(context_.storage->slice_table().depth()[0], 0u);
-  EXPECT_EQ(context_.storage->slice_table().depth()[1], 1u);
-  EXPECT_EQ(context_.storage->slice_table().depth()[2], 0u);
-  EXPECT_EQ(context_.storage->slice_table().depth()[3], 0u);
+  EXPECT_EQ(st[0].depth(), 0u);
+  EXPECT_EQ(st[1].depth(), 1u);
+  EXPECT_EQ(st[2].depth(), 0u);
+  EXPECT_EQ(st[3].depth(), 0u);
 }
 
 TEST_F(SliceTrackerTest, GetTopmostSliceOnTrack) {
@@ -421,12 +438,12 @@
   EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track), std::nullopt);
 
   tracker.Begin(100, track, StringId::Raw(11), StringId::Raw(11));
-  SliceId slice1 = context_.storage->slice_table().id()[0];
+  SliceId slice1 = context_.storage->slice_table()[0].id();
 
   EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track).value(), slice1);
 
   tracker.Begin(120, track, StringId::Raw(22), StringId::Raw(22));
-  SliceId slice2 = context_.storage->slice_table().id()[1];
+  SliceId slice2 = context_.storage->slice_table()[1].id();
 
   EXPECT_EQ(tracker.GetTopmostSliceOnTrack(track).value(), slice2);
 
@@ -458,22 +475,21 @@
   EXPECT_TRUE(slice_records.empty());
 
   tracker.Begin(100, track1, StringId::Raw(11), StringId::Raw(11));
-  SliceId slice1 = context_.storage->slice_table().id()[0];
+  SliceId slice1 = context_.storage->slice_table()[0].id();
   EXPECT_THAT(track_records, ElementsAre(TrackId{1u}));
   EXPECT_THAT(slice_records, ElementsAre(slice1));
 
   tracker.Begin(120, track2, StringId::Raw(22), StringId::Raw(22));
-  SliceId slice2 = context_.storage->slice_table().id()[1];
+  SliceId slice2 = context_.storage->slice_table()[1].id();
   EXPECT_THAT(track_records, ElementsAre(TrackId{1u}, TrackId{2u}));
   EXPECT_THAT(slice_records, ElementsAre(slice1, slice2));
 
   tracker.Begin(330, track1, StringId::Raw(33), StringId::Raw(33));
-  SliceId slice3 = context_.storage->slice_table().id()[2];
+  SliceId slice3 = context_.storage->slice_table()[2].id();
   EXPECT_THAT(track_records,
               ElementsAre(TrackId{1u}, TrackId{2u}, TrackId{1u}));
   EXPECT_THAT(slice_records, ElementsAre(slice1, slice2, slice3));
 }
 
 }  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/common/track_tracker.cc b/src/trace_processor/importers/common/track_tracker.cc
index 5f74ad5..9d32e04 100644
--- a/src/trace_processor/importers/common/track_tracker.cc
+++ b/src/trace_processor/importers/common/track_tracker.cc
@@ -16,14 +16,18 @@
 
 #include "src/trace_processor/importers/common/track_tracker.h"
 
+#include <cstdint>
 #include <optional>
+#include <utility>
 
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/cpu_tracker.h"
 #include "src/trace_processor/importers/common/process_track_translation_table.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
 
 namespace perfetto {
 namespace trace_processor {
@@ -164,10 +168,11 @@
     if (name != kNullStringId) {
       // The track may have been created for an end event without name. In that
       // case, update it with this event's name.
-      auto* tracks = context_->storage->mutable_track_table();
-      uint32_t track_row = *tracks->id().IndexOf(it->second);
-      if (tracks->name()[track_row] == kNullStringId)
-        tracks->mutable_name()->Set(track_row, name);
+      auto& tracks = *context_->storage->mutable_track_table();
+      auto rr = *tracks.FindById(it->second);
+      if (rr.name() == kNullStringId) {
+        rr.set_name(name);
+      }
     }
     return it->second;
   }
diff --git a/src/trace_processor/importers/ftrace/binder_tracker.cc b/src/trace_processor/importers/ftrace/binder_tracker.cc
index 48cdf58..831d466 100644
--- a/src/trace_processor/importers/ftrace/binder_tracker.cc
+++ b/src/trace_processor/importers/ftrace/binder_tracker.cc
@@ -15,14 +15,20 @@
  */
 
 #include "src/trace_processor/importers/ftrace/binder_tracker.h"
+#include <cstdint>
+#include <optional>
+#include <string>
+#include <utility>
 #include "perfetto/base/compiler.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
 
 // Binder tracker: displays slices for binder transactions and other operations.
 // =============================================================================
@@ -268,7 +274,7 @@
     if (is_reply) {
       UniqueTid utid = context_->process_tracker->GetOrCreateThread(
           static_cast<uint32_t>(dest_tid));
-      auto dest_thread_name = context_->storage->thread_table().name()[utid];
+      auto dest_thread_name = context_->storage->thread_table()[utid].name();
       auto dest_args_inserter = [this, dest_tid, &dest_thread_name](
                                     ArgsTracker::BoundInserter* inserter) {
         inserter->AddArg(dest_thread_, Variadic::Integer(dest_tid));
@@ -363,7 +369,7 @@
       auto args_inserter = [this, utid,
                             pid](ArgsTracker::BoundInserter* inserter) {
         inserter->AddArg(dest_thread_, Variadic::UnsignedInteger(pid));
-        auto dest_thread_name = context_->storage->thread_table().name()[utid];
+        auto dest_thread_name = context_->storage->thread_table()[utid].name();
         if (dest_thread_name.has_value()) {
           inserter->AddArg(dest_name_, Variadic::String(*dest_thread_name));
         }
diff --git a/src/trace_processor/importers/ftrace/binder_tracker_unittest.cc b/src/trace_processor/importers/ftrace/binder_tracker_unittest.cc
index 7fab0e5..16b57b4 100644
--- a/src/trace_processor/importers/ftrace/binder_tracker_unittest.cc
+++ b/src/trace_processor/importers/ftrace/binder_tracker_unittest.cc
@@ -16,11 +16,13 @@
 
 #include "src/trace_processor/importers/ftrace/binder_tracker.h"
 
-#include "perfetto/base/logging.h"
+#include <cstdint>
+
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/args_translation_table.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/slice_translation_table.h"
@@ -28,8 +30,7 @@
 #include "src/trace_processor/storage/trace_storage.h"
 #include "test/gtest_and_gmock.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
 constexpr int kOneWay = 0x01;
 
@@ -83,22 +84,21 @@
   ASSERT_EQ(slice.row_count(), 2u);
 
   auto tid_for_slice = [&](uint32_t row) {
-    TrackId track_id = slice.track_id()[row];
-    UniqueTid utid = track.utid()[*track.id().IndexOf(track_id)];
-    return thread.tid()[utid];
+    auto rr = track.FindById(slice[row].track_id());
+    return thread[rr->utid()].tid();
   };
 
-  ASSERT_EQ(slice.ts()[0], req_ts);
-  ASSERT_EQ(slice.dur()[0], rep_recv_ts - req_ts);
+  ASSERT_EQ(slice[0].ts(), req_ts);
+  ASSERT_EQ(slice[0].dur(), rep_recv_ts - req_ts);
   ASSERT_EQ(tid_for_slice(0), req_tid);
 
-  ASSERT_EQ(slice.ts()[1], req_recv_ts);
-  ASSERT_EQ(slice.dur()[1], rep_ts - req_recv_ts);
+  ASSERT_EQ(slice[1].ts(), req_recv_ts);
+  ASSERT_EQ(slice[1].dur(), rep_ts - req_recv_ts);
   ASSERT_EQ(tid_for_slice(1), rep_tid);
 
   ASSERT_EQ(flow.row_count(), 1u);
-  ASSERT_EQ(flow.slice_out()[0], slice.id()[0]);
-  ASSERT_EQ(flow.slice_in()[0], slice.id()[1]);
+  ASSERT_EQ(flow[0].slice_out(), slice[0].id());
+  ASSERT_EQ(flow[0].slice_in(), slice[1].id());
 
   EXPECT_TRUE(binder_tracker->utid_stacks_empty());
 }
@@ -123,22 +123,22 @@
   ASSERT_EQ(slice.row_count(), 2u);
 
   auto tid_for_slice = [&](uint32_t row) {
-    TrackId track_id = slice.track_id()[row];
-    UniqueTid utid = track.utid()[*track.id().IndexOf(track_id)];
-    return thread.tid()[utid];
+    TrackId track_id = slice[row].track_id();
+    auto rr = track.FindById(track_id);
+    return thread[rr->utid()].tid();
   };
 
-  ASSERT_EQ(slice.ts()[0], sen_ts);
-  ASSERT_EQ(slice.dur()[0], 0);
+  ASSERT_EQ(slice[0].ts(), sen_ts);
+  ASSERT_EQ(slice[0].dur(), 0);
   ASSERT_EQ(tid_for_slice(0), sen_tid);
 
-  ASSERT_EQ(slice.ts()[1], rec_ts);
-  ASSERT_EQ(slice.dur()[1], 0);
+  ASSERT_EQ(slice[1].ts(), rec_ts);
+  ASSERT_EQ(slice[1].dur(), 0);
   ASSERT_EQ(tid_for_slice(1), rec_tid);
 
   ASSERT_EQ(flow.row_count(), 1u);
-  ASSERT_EQ(flow.slice_out()[0], slice.id()[0]);
-  ASSERT_EQ(flow.slice_in()[0], slice.id()[1]);
+  ASSERT_EQ(flow[0].slice_out(), slice[0].id());
+  ASSERT_EQ(flow[0].slice_in(), slice[1].id());
 
   EXPECT_TRUE(binder_tracker->utid_stacks_empty());
 }
@@ -170,8 +170,8 @@
 
   const auto& slice = context.storage->slice_table();
   ASSERT_EQ(slice.row_count(), 2u);
-  EXPECT_NE(slice.dur()[0], -1);
-  EXPECT_NE(slice.dur()[1], -1);
+  EXPECT_NE(slice[0].dur(), -1);
+  EXPECT_NE(slice[1].dur(), -1);
 
   EXPECT_TRUE(binder_tracker->utid_stacks_empty());
 }
@@ -207,7 +207,7 @@
 
   const auto& slice = context.storage->slice_table();
   ASSERT_EQ(slice.row_count(), 1u);
-  EXPECT_NE(slice.dur()[0], -1);
+  EXPECT_NE(slice[0].dur(), -1);
 
   EXPECT_TRUE(binder_tracker->utid_stacks_empty());
 }
@@ -236,8 +236,8 @@
 
   const auto& slice = context.storage->slice_table();
   ASSERT_EQ(slice.row_count(), 2u);
-  EXPECT_NE(slice.dur()[0], -1);
-  EXPECT_NE(slice.dur()[1], -1);
+  EXPECT_NE(slice[0].dur(), -1);
+  EXPECT_NE(slice[1].dur(), -1);
 
   EXPECT_TRUE(binder_tracker->utid_stacks_empty());
 }
@@ -269,8 +269,8 @@
 
   const auto& slice = context.storage->slice_table();
   ASSERT_EQ(slice.row_count(), 2u);
-  EXPECT_NE(slice.dur()[0], -1);
-  EXPECT_NE(slice.dur()[1], -1);
+  EXPECT_NE(slice[0].dur(), -1);
+  EXPECT_NE(slice[1].dur(), -1);
 
   EXPECT_TRUE(binder_tracker->utid_stacks_empty());
 }
@@ -294,8 +294,8 @@
 
   const auto& slice = context.storage->slice_table();
   ASSERT_EQ(slice.row_count(), 2u);
-  EXPECT_EQ(slice.dur()[0], 0);
-  EXPECT_EQ(slice.dur()[1], 0);
+  EXPECT_EQ(slice[0].dur(), 0);
+  EXPECT_EQ(slice[1].dur(), 0);
 
   EXPECT_TRUE(binder_tracker->utid_stacks_empty());
 }
@@ -331,11 +331,10 @@
 
   const auto& slice = context.storage->slice_table();
   ASSERT_EQ(slice.row_count(), 1u);
-  EXPECT_EQ(slice.dur()[0], 0);
+  EXPECT_EQ(slice[0].dur(), 0);
 
   EXPECT_TRUE(binder_tracker->utid_stacks_empty());
 }
 
 }  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/ftrace/ftrace_parser.cc b/src/trace_processor/importers/ftrace/ftrace_parser.cc
index 40d1730..1ef8874 100644
--- a/src/trace_processor/importers/ftrace/ftrace_parser.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_parser.cc
@@ -15,6 +15,7 @@
  */
 
 #include "src/trace_processor/importers/ftrace/ftrace_parser.h"
+#include <optional>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
@@ -711,7 +712,7 @@
   if (PERFETTO_UNLIKELY(ts < drop_ftrace_data_before_ts_)) {
     context_->storage->IncrementStats(
         stats::ftrace_packet_before_tracing_start);
-    return util::OkStatus();
+    return base::OkStatus();
   }
   using protos::pbzero::FtraceEvent;
   const TraceBlobView& event = data.packet;
@@ -736,7 +737,7 @@
     // not associated with any pid. The rest of trace parsing logic for
     // hypervisor events will use the pid 0.
     if (no_pid && !PkvmHypervisorCpuTracker::IsPkvmHypervisorEvent(fld.id())) {
-      return util::ErrStatus("Pid field not found in ftrace packet");
+      return base::ErrStatus("Pid field not found in ftrace packet");
     }
 
     ConstBytes fld_bytes = fld.as_bytes();
@@ -1305,7 +1306,7 @@
   }
 
   PERFETTO_DCHECK(!decoder.bytes_left());
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 base::Status FtraceParser::ParseInlineSchedSwitch(
@@ -1319,7 +1320,7 @@
     if (ts < drop_ftrace_data_before_ts_) {
       context_->storage->IncrementStats(
           stats::ftrace_packet_before_tracing_start);
-      return util::OkStatus();
+      return base::OkStatus();
     }
   }
 
@@ -1329,7 +1330,7 @@
   ftrace_sched_tracker->PushSchedSwitchCompact(
       cpu, ts, data.prev_state, static_cast<uint32_t>(data.next_pid),
       data.next_prio, data.next_comm, parse_only_into_raw);
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 base::Status FtraceParser::ParseInlineSchedWaking(
@@ -1343,7 +1344,7 @@
     if (ts < drop_ftrace_data_before_ts_) {
       context_->storage->IncrementStats(
           stats::ftrace_packet_before_tracing_start);
-      return util::OkStatus();
+      return base::OkStatus();
     }
   }
 
@@ -1353,7 +1354,7 @@
   ftrace_sched_tracker->PushSchedWakingCompact(
       cpu, ts, static_cast<uint32_t>(data.pid), data.target_cpu, data.prio,
       data.comm, data.common_flags, parse_only_into_raw);
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 void FtraceParser::MaybeOnFirstFtraceEvent() {
@@ -1895,9 +1896,7 @@
   // id
   TrackId track = context_->track_tracker->InternGlobalCounterTrack(
       TrackTracker::Group::kBatteryMitigation, bcl_irq_id_);
-  context_->event_tracker->PushCounter(ts,
-                                       throttle ? bcl.id() : -1,
-                                       track);
+  context_->event_tracker->PushCounter(ts, throttle ? bcl.id() : -1, track);
   // throttle
   track = context_->track_tracker->InternGlobalCounterTrack(
       TrackTracker::Group::kBatteryMitigation, bcl_irq_throttle_);
@@ -1905,32 +1904,27 @@
   // cpu0_limit
   track = context_->track_tracker->InternGlobalCounterTrack(
       TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu0_);
-  context_->event_tracker->PushCounter(ts,
-                                       throttle ? bcl.cpu0_limit() : 0,
+  context_->event_tracker->PushCounter(ts, throttle ? bcl.cpu0_limit() : 0,
                                        track);
   // cpu1_limit
   track = context_->track_tracker->InternGlobalCounterTrack(
       TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu1_);
-  context_->event_tracker->PushCounter(ts,
-                                       throttle ? bcl.cpu1_limit() : 0,
+  context_->event_tracker->PushCounter(ts, throttle ? bcl.cpu1_limit() : 0,
                                        track);
   // cpu2_limit
   track = context_->track_tracker->InternGlobalCounterTrack(
       TrackTracker::Group::kBatteryMitigation, bcl_irq_cpu2_);
-  context_->event_tracker->PushCounter(ts,
-                                       throttle ? bcl.cpu2_limit() : 0,
+  context_->event_tracker->PushCounter(ts, throttle ? bcl.cpu2_limit() : 0,
                                        track);
   // tpu_limit
   track = context_->track_tracker->InternGlobalCounterTrack(
       TrackTracker::Group::kBatteryMitigation, bcl_irq_tpu_);
-  context_->event_tracker->PushCounter(ts,
-                                       throttle ? bcl.tpu_limit(): 0,
+  context_->event_tracker->PushCounter(ts, throttle ? bcl.tpu_limit() : 0,
                                        track);
   // gpu_limit
   track = context_->track_tracker->InternGlobalCounterTrack(
       TrackTracker::Group::kBatteryMitigation, bcl_irq_gpu_);
-  context_->event_tracker->PushCounter(ts,
-                                       throttle ? bcl.gpu_limit() : 0,
+  context_->event_tracker->PushCounter(ts, throttle ? bcl.gpu_limit() : 0,
                                        track);
   // voltage
   track = context_->track_tracker->InternGlobalCounterTrack(
@@ -2584,8 +2578,8 @@
     PERFETTO_DCHECK(updated_utid == *opt_utid);
 
     // UpdateThread above should ensure this is always set.
-    UniquePid upid = *context_->storage->thread_table().upid()[*opt_utid];
-    PERFETTO_DCHECK(context_->storage->process_table().pid()[upid] == pid);
+    UniquePid upid = *context_->storage->thread_table()[*opt_utid].upid();
+    PERFETTO_DCHECK(context_->storage->process_table()[upid].pid() == pid);
 
     track = context_->track_tracker->InternProcessCounterTrack(
         gpu_mem_total_name_id_, upid, gpu_mem_total_unit_id_,
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
index feb616c..832ca7f 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.cc
@@ -28,6 +28,7 @@
 #include "src/trace_processor/importers/common/thread_state_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
 #include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/task_state.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/types/variadic.h"
@@ -66,14 +67,14 @@
 FtraceSchedEventTracker::~FtraceSchedEventTracker() = default;
 
 void FtraceSchedEventTracker::PushSchedSwitch(uint32_t cpu,
-                                        int64_t ts,
-                                        uint32_t prev_pid,
-                                        base::StringView prev_comm,
-                                        int32_t prev_prio,
-                                        int64_t prev_state,
-                                        uint32_t next_pid,
-                                        base::StringView next_comm,
-                                        int32_t next_prio) {
+                                              int64_t ts,
+                                              uint32_t prev_pid,
+                                              base::StringView prev_comm,
+                                              int32_t prev_prio,
+                                              int64_t prev_state,
+                                              uint32_t next_pid,
+                                              base::StringView next_comm,
+                                              int32_t next_prio) {
   if (!context_->sched_event_tracker->UpdateEventTrackerTimestamp(
           ts, "sched_switch", stats::sched_switch_out_of_order)) {
     return;
@@ -95,7 +96,7 @@
     prev_pid_match_prev_next_pid = prev_pid == pending_sched->last_pid;
     if (PERFETTO_LIKELY(prev_pid_match_prev_next_pid)) {
       context_->sched_event_tracker->ClosePendingSlice(pending_slice_idx, ts,
-          prev_state_string_id);
+                                                       prev_state_string_id);
     } else {
       // If the pids are not consistent, make a note of this.
       context_->storage->IncrementStats(stats::mismatched_sched_switch_tids);
@@ -112,8 +113,8 @@
   AddRawSchedSwitchEvent(cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio,
                          prev_state, next_pid, next_comm_id, next_prio);
 
-  auto new_slice_idx = context_->sched_event_tracker
-                           ->AddStartSlice(cpu, ts, next_utid, next_prio);
+  auto new_slice_idx = context_->sched_event_tracker->AddStartSlice(
+      cpu, ts, next_utid, next_prio);
 
   // Finally, update the info for the next sched switch on this CPU.
   pending_sched->pending_slice_storage_idx = new_slice_idx;
@@ -179,7 +180,7 @@
   // Do a fresh task name lookup in case it was updated by a task_rename while
   // scheduled.
   StringId prev_comm_id =
-      context_->storage->thread_table().name()[prev_utid].value_or(
+      context_->storage->thread_table()[prev_utid].name().value_or(
           kNullStringId);
 
   AddRawSchedSwitchEvent(cpu, ts, prev_utid, prev_pid, prev_comm_id, prev_prio,
diff --git a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc
index 8becd2e..afb4793 100644
--- a/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc
+++ b/src/trace_processor/importers/ftrace/ftrace_sched_event_tracker_unittest.cc
@@ -15,14 +15,19 @@
  */
 
 #include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
+#include <cstdint>
+#include <memory>
+#include <optional>
 
 #include "perfetto/base/logging.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/cpu_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
 #include "src/trace_processor/importers/common/machine_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/sched_event_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "test/gtest_and_gmock.h"
 
 namespace perfetto {
@@ -36,15 +41,15 @@
 class SchedEventTrackerTest : public ::testing::Test {
  public:
   SchedEventTrackerTest() {
-    context.storage.reset(new TraceStorage());
-    context.global_args_tracker.reset(
-        new GlobalArgsTracker(context.storage.get()));
-    context.args_tracker.reset(new ArgsTracker(&context));
-    context.event_tracker.reset(new EventTracker(&context));
-    context.process_tracker.reset(new ProcessTracker(&context));
-    context.machine_tracker.reset(new MachineTracker(&context, 0));
-    context.cpu_tracker.reset(new CpuTracker(&context));
-    context.sched_event_tracker.reset(new SchedEventTracker(&context));
+    context.storage = std::make_shared<TraceStorage>();
+    context.global_args_tracker =
+        std::make_unique<GlobalArgsTracker>(context.storage.get());
+    context.args_tracker = std::make_unique<ArgsTracker>(&context);
+    context.event_tracker = std::make_unique<EventTracker>(&context);
+    context.process_tracker = std::make_unique<ProcessTracker>(&context);
+    context.machine_tracker = std::make_unique<MachineTracker>(&context, 0);
+    context.cpu_tracker = std::make_unique<CpuTracker>(&context);
+    context.sched_event_tracker = std::make_unique<SchedEventTracker>(&context);
     sched_tracker = FtraceSchedEventTracker::GetOrCreate(&context);
   }
 
@@ -72,14 +77,15 @@
 
   ASSERT_EQ(context.storage->sched_slice_table().row_count(), 2ul);
 
-  const auto& timestamps = context.storage->sched_slice_table().ts();
-  ASSERT_EQ(timestamps[0], timestamp);
-  ASSERT_EQ(context.storage->thread_table().start_ts()[1], std::nullopt);
+  const auto& sched = context.storage->sched_slice_table();
+  ASSERT_EQ(sched[0].ts(), timestamp);
+  ASSERT_EQ(context.storage->thread_table()[1].start_ts(), std::nullopt);
 
-  auto name = context.storage->thread_table().name().GetString(1);
+  auto name =
+      context.storage->GetString(*context.storage->thread_table()[1].name());
   ASSERT_STREQ(name.c_str(), kCommProc1);
-  ASSERT_EQ(context.storage->sched_slice_table().utid()[0], 1u);
-  ASSERT_EQ(context.storage->sched_slice_table().dur()[0], 1);
+  ASSERT_EQ(context.storage->sched_slice_table()[0].utid(), 1u);
+  ASSERT_EQ(context.storage->sched_slice_table()[0].dur(), 1);
 }
 
 TEST_F(SchedEventTrackerTest, InsertThirdSched_SameThread) {
@@ -90,30 +96,29 @@
   static const char kCommProc2[] = "process2";
   int32_t prio = 1024;
 
-  sched_tracker->PushSchedSwitch(cpu, timestamp, /*tid=*/4, kCommProc2, prio,
-                                 prev_state,
+  sched_tracker->PushSchedSwitch(cpu, timestamp, /*prev_pid=*/4, kCommProc2,
+                                 prio, prev_state,
                                  /*tid=*/2, kCommProc1, prio);
   ASSERT_EQ(context.storage->sched_slice_table().row_count(), 1u);
 
-  sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*tid=*/2, kCommProc1,
+  sched_tracker->PushSchedSwitch(cpu, timestamp + 1, /*prev_pid=*/2, kCommProc1,
                                  prio, prev_state,
                                  /*tid=*/4, kCommProc2, prio);
-  sched_tracker->PushSchedSwitch(cpu, timestamp + 11, /*tid=*/4, kCommProc2,
-                                 prio, prev_state,
+  sched_tracker->PushSchedSwitch(cpu, timestamp + 11, /*prev_pid=*/4,
+                                 kCommProc2, prio, prev_state,
                                  /*tid=*/2, kCommProc1, prio);
   sched_tracker->PushSchedSwitch(cpu, timestamp + 31, /*tid=*/2, kCommProc1,
                                  prio, prev_state,
                                  /*tid=*/4, kCommProc2, prio);
   ASSERT_EQ(context.storage->sched_slice_table().row_count(), 4ul);
 
-  const auto& timestamps = context.storage->sched_slice_table().ts();
-  ASSERT_EQ(timestamps[0], timestamp);
-  ASSERT_EQ(context.storage->thread_table().start_ts()[1], std::nullopt);
-  ASSERT_EQ(context.storage->sched_slice_table().dur()[0], 1u);
-  ASSERT_EQ(context.storage->sched_slice_table().dur()[1], 11u - 1u);
-  ASSERT_EQ(context.storage->sched_slice_table().dur()[2], 31u - 11u);
-  ASSERT_EQ(context.storage->sched_slice_table().utid()[0],
-            context.storage->sched_slice_table().utid()[2]);
+  ASSERT_EQ(context.storage->sched_slice_table()[0].ts(), timestamp);
+  ASSERT_EQ(context.storage->thread_table()[1].start_ts(), std::nullopt);
+  ASSERT_EQ(context.storage->sched_slice_table()[0].dur(), 1u);
+  ASSERT_EQ(context.storage->sched_slice_table()[1].dur(), 11u - 1u);
+  ASSERT_EQ(context.storage->sched_slice_table()[2].dur(), 31u - 11u);
+  ASSERT_EQ(context.storage->sched_slice_table()[0].utid(),
+            context.storage->sched_slice_table()[2].utid());
 }
 
 TEST_F(SchedEventTrackerTest, UpdateThreadMatch) {
@@ -135,10 +140,10 @@
                                               base::StringView());
   context.process_tracker->UpdateThread(4, 2);
 
-  ASSERT_EQ(context.storage->thread_table().tid()[1], 4u);
-  ASSERT_EQ(context.storage->thread_table().upid()[1].value(), 1u);
-  ASSERT_EQ(context.storage->process_table().pid()[1], 2u);
-  ASSERT_EQ(context.storage->process_table().start_ts()[1], std::nullopt);
+  ASSERT_EQ(context.storage->thread_table()[1].tid(), 4u);
+  ASSERT_EQ(context.storage->thread_table()[1].upid().value(), 1u);
+  ASSERT_EQ(context.storage->process_table()[1].pid(), 2u);
+  ASSERT_EQ(context.storage->process_table()[1].start_ts(), std::nullopt);
 }
 
 }  // namespace
diff --git a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc
index f01f232..4ffb9a2 100644
--- a/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc
+++ b/src/trace_processor/importers/fuchsia/fuchsia_trace_tokenizer.cc
@@ -626,7 +626,8 @@
 
           UniqueTid utid = procs->UpdateThread(static_cast<uint32_t>(obj_id),
                                                static_cast<uint32_t>(pid));
-          storage->mutable_thread_table()->mutable_name()->Set(utid, name);
+          auto& tt = *storage->mutable_thread_table();
+          tt[utid].set_name(name);
           break;
         }
         default: {
diff --git a/src/trace_processor/importers/json/json_trace_parser_impl.cc b/src/trace_processor/importers/json/json_trace_parser_impl.cc
index dc7a261..37e56c8 100644
--- a/src/trace_processor/importers/json/json_trace_parser_impl.cc
+++ b/src/trace_processor/importers/json/json_trace_parser_impl.cc
@@ -181,11 +181,9 @@
       auto opt_tts = json::CoerceToTs(value["tts"]);
       if (opt_slice_id.has_value() && opt_tts) {
         auto* slice = storage->mutable_slice_table();
-        auto maybe_row = slice->id().IndexOf(*opt_slice_id);
-        PERFETTO_DCHECK(maybe_row.has_value());
-        auto start_tts = slice->thread_ts()[*maybe_row];
-        if (start_tts) {
-          slice->mutable_thread_dur()->Set(*maybe_row, *opt_tts - *start_tts);
+        auto rr = *slice->FindById(*opt_slice_id);
+        if (auto start_tts = rr.thread_ts(); start_tts) {
+          rr.set_thread_dur(*opt_tts - *start_tts);
         }
       }
       break;
diff --git a/src/trace_processor/importers/ninja/ninja_log_parser.cc b/src/trace_processor/importers/ninja/ninja_log_parser.cc
index 1fdeee7..ba81882 100644
--- a/src/trace_processor/importers/ninja/ninja_log_parser.cc
+++ b/src/trace_processor/importers/ninja/ninja_log_parser.cc
@@ -16,24 +16,31 @@
 
 #include "src/trace_processor/importers/ninja/ninja_log_parser.h"
 
+#include <stdio.h>
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
-#include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 using base::StringSplitter;
 
 NinjaLogParser::NinjaLogParser(TraceProcessorContext* ctx) : ctx_(ctx) {}
 NinjaLogParser::~NinjaLogParser() = default;
 
-util::Status NinjaLogParser::Parse(TraceBlobView blob) {
+base::Status NinjaLogParser::Parse(TraceBlobView blob) {
   // A trace is read in chunks of arbitrary size (for http fetch() pipeliniing),
   // not necessarily aligned on a line boundary.
   // Here we push everything into a vector and, on each call, consume only
@@ -50,11 +57,11 @@
     static const char kHeader[] = "# ninja log v";
     if (!header_parsed_) {
       if (!base::StartsWith(line.cur_token(), kHeader))
-        return util::ErrStatus("Failed to parse ninja log header");
+        return base::ErrStatus("Failed to parse ninja log header");
       header_parsed_ = true;
       auto version = base::CStringToUInt32(line.cur_token() + strlen(kHeader));
       if (!version || *version != 5)
-        return util::ErrStatus("Unsupported ninja log version");
+        return base::ErrStatus("Unsupported ninja log version");
       continue;
     }
 
@@ -108,7 +115,7 @@
     jobs_.emplace_back(*t_start, *t_end, *cmdhash, name);
   }
   log_.erase(log_.begin(), log_.begin() + static_cast<ssize_t>(valid_size));
-  return util::OkStatus();
+  return base::OkStatus();
 }
 
 // This is called after the last Parse() call. At this point all |jobs_| have
@@ -169,7 +176,7 @@
       worker = &workers.back();
     }
 
-    static constexpr int64_t kMsToNs = 1000 * 1000;
+    static constexpr int64_t kMsToNs = 1000ul * 1000;
     const int64_t start_ns = job.start_ms * kMsToNs;
     const int64_t dur_ns = (job.end_ms - job.start_ms) * kMsToNs;
     StringId name_id = ctx_->storage->InternString(base::StringView(job.names));
@@ -179,5 +186,4 @@
   return base::OkStatus();
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/BUILD.gn b/src/trace_processor/importers/proto/BUILD.gn
index 467e624..f7ebbe7 100644
--- a/src/trace_processor/importers/proto/BUILD.gn
+++ b/src/trace_processor/importers/proto/BUILD.gn
@@ -301,6 +301,8 @@
     "../../../../protos/perfetto/trace/sys_stats:zero",
     "../../../../protos/perfetto/trace/track_event:zero",
     "../../../protozero",
+    "../../containers",
+    "../../db/column",
     "../../sorter",
     "../../storage",
     "../../tables",
diff --git a/src/trace_processor/importers/proto/gpu_event_parser.cc b/src/trace_processor/importers/proto/gpu_event_parser.cc
index 5664fba..49f7dbd 100644
--- a/src/trace_processor/importers/proto/gpu_event_parser.cc
+++ b/src/trace_processor/importers/proto/gpu_event_parser.cc
@@ -17,33 +17,47 @@
 #include "src/trace_processor/importers/proto/gpu_event_parser.h"
 
 #include <cinttypes>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <optional>
+#include <string>
 
-#include "perfetto/ext/base/utils.h"
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/string_writer.h"
 #include "perfetto/protozero/field.h"
+#include "protos/perfetto/trace/android/gpu_mem_event.pbzero.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+#include "src/trace_processor/importers/proto/vulkan_memory_tracker.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
+#include "src/trace_processor/tables/slice_tables_py.h"
+#include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
 #include "protos/perfetto/common/gpu_counter_descriptor.pbzero.h"
-#include "protos/perfetto/trace/android/graphics_frame_event.pbzero.h"
 #include "protos/perfetto/trace/gpu/gpu_counter_event.pbzero.h"
 #include "protos/perfetto/trace/gpu/gpu_log.pbzero.h"
 #include "protos/perfetto/trace/gpu/gpu_render_stage_event.pbzero.h"
 #include "protos/perfetto/trace/gpu/vulkan_api_event.pbzero.h"
 #include "protos/perfetto/trace/gpu/vulkan_memory_event.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
+#include "src/trace_processor/types/variadic.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 namespace {
 
 // https://www.khronos.org/registry/vulkan/specs/1.1-extensions/man/html/VkObjectType.html
-typedef enum VkObjectType {
+enum VkObjectType {
   VK_OBJECT_TYPE_UNKNOWN = 0,
   VK_OBJECT_TYPE_INSTANCE = 1,
   VK_OBJECT_TYPE_PHYSICAL_DEVICE = 2,
@@ -88,7 +102,9 @@
   VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION_KHR =
       VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION,
   VK_OBJECT_TYPE_MAX_ENUM = 0x7FFFFFFF
-} VkObjectType;
+};
+
+using protos::pbzero::VulkanMemoryEvent;
 
 }  // anonymous namespace
 
@@ -209,7 +225,7 @@
   }
 }
 
-const StringId GpuEventParser::GetFullStageName(
+StringId GpuEventParser::GetFullStageName(
     PacketSequenceStateGeneration* sequence_state,
     const protos::pbzero::GpuRenderStageEvent_Decoder& event) const {
   StringId stage_name;
@@ -223,8 +239,7 @@
     }
     stage_name = context_->storage->InternString(decoder->name());
   } else {
-    uint64_t stage_id = static_cast<uint64_t>(event.stage_id());
-
+    auto stage_id = static_cast<uint64_t>(event.stage_id());
     if (stage_id < gpu_render_stage_ids_.size()) {
       stage_name = gpu_render_stage_ids_[static_cast<size_t>(stage_id)].first;
     } else {
@@ -263,14 +278,11 @@
     // description.
     auto track_id = gpu_hw_queue_ids_[gpu_hw_queue_counter_];
     if (track_id.has_value()) {
-      auto row = context_->storage->mutable_gpu_track_table()
-                     ->id()
-                     .IndexOf(track_id.value())
-                     .value();
-      context_->storage->mutable_gpu_track_table()->mutable_name()->Set(
-          row, track_name);
-      context_->storage->mutable_gpu_track_table()->mutable_description()->Set(
-          row, context_->storage->InternString(hw_queue.description()));
+      auto rr = *context_->storage->mutable_gpu_track_table()->FindById(
+          track_id.value());
+      rr.set_name(track_name);
+      rr.set_description(
+          context_->storage->InternString(hw_queue.description()));
     } else {
       tables::GpuTrackTable::Row track(track_name);
       track.scope = gpu_render_stage_scope_id_;
@@ -291,12 +303,11 @@
   auto name = map->second.find(vk_handle);
   if (name == map->second.end()) {
     return std::nullopt;
-  } else {
-    return name->second;
   }
+  return name->second;
 }
 
-const StringId GpuEventParser::ParseRenderSubpasses(
+StringId GpuEventParser::ParseRenderSubpasses(
     const protos::pbzero::GpuRenderStageEvent_Decoder& event) const {
   if (!event.has_render_subpass_index_mask()) {
     return kNullStringId;
@@ -345,9 +356,9 @@
       protos::pbzero::GpuRenderStageEvent_Specifications_Description::Decoder
           stage(*it);
       if (stage.has_name()) {
-        gpu_render_stage_ids_.emplace_back(std::make_pair(
+        gpu_render_stage_ids_.emplace_back(
             context_->storage->InternString(stage.name()),
-            context_->storage->InternString(stage.description())));
+            context_->storage->InternString(stage.description()));
       }
     }
     if (spec.has_context_spec()) {
@@ -759,7 +770,7 @@
   } else {
     // Process emitting the packet can be different from the pid in the event.
     UniqueTid utid = context_->process_tracker->UpdateThread(pid, pid);
-    UniquePid upid = context_->storage->thread_table().upid()[utid].value_or(0);
+    UniquePid upid = context_->storage->thread_table()[utid].upid().value_or(0);
     track = context_->track_tracker->InternProcessCounterTrack(
         gpu_mem_total_name_id_, upid, gpu_mem_total_unit_id_,
         gpu_mem_total_proc_desc_id_);
@@ -768,5 +779,4 @@
       ts, static_cast<double>(gpu_mem_total.size()), track);
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/gpu_event_parser.h b/src/trace_processor/importers/proto/gpu_event_parser.h
index 6ca6527..ccab42a 100644
--- a/src/trace_processor/importers/proto/gpu_event_parser.h
+++ b/src/trace_processor/importers/proto/gpu_event_parser.h
@@ -17,14 +17,18 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_GPU_EVENT_PARSER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_GPU_EVENT_PARSER_H_
 
+#include <array>
+#include <cstddef>
+#include <cstdint>
 #include <optional>
+#include <string>
+#include <unordered_map>
+#include <utility>
 #include <vector>
 
-#include "perfetto/ext/base/string_writer.h"
 #include "perfetto/protozero/field.h"
-#include "protos/perfetto/trace/android/gpu_mem_event.pbzero.h"
 #include "protos/perfetto/trace/gpu/gpu_render_stage_event.pbzero.h"
-#include "src/trace_processor/importers/common/args_tracker.h"
+#include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
 #include "src/trace_processor/importers/proto/vulkan_memory_tracker.h"
 #include "src/trace_processor/storage/trace_storage.h"
 
@@ -32,13 +36,9 @@
 
 namespace perfetto {
 
-namespace protos {
-namespace pbzero {
-
+namespace protos::pbzero {
 class GpuRenderStageEvent_Decoder;
-
-}  // namespace pbzero
-}  // namespace protos
+}  // namespace protos::pbzero
 
 namespace trace_processor {
 
@@ -55,8 +55,9 @@
 class GpuEventParser {
  public:
   using ConstBytes = protozero::ConstBytes;
-  using VulkanMemoryEventSource = VulkanMemoryEvent::Source;
-  using VulkanMemoryEventOperation = VulkanMemoryEvent::Operation;
+  using VulkanMemoryEventSource = protos::pbzero::VulkanMemoryEvent::Source;
+  using VulkanMemoryEventOperation =
+      protos::pbzero::VulkanMemoryEvent::Operation;
   explicit GpuEventParser(TraceProcessorContext*);
 
   void ParseGpuCounterEvent(int64_t ts, ConstBytes);
@@ -67,15 +68,16 @@
   void ParseGpuLog(int64_t ts, ConstBytes);
 
   void ParseVulkanMemoryEvent(PacketSequenceStateGeneration*, ConstBytes);
-  void UpdateVulkanMemoryAllocationCounters(UniquePid,
-                                            const VulkanMemoryEvent::Decoder&);
+  void UpdateVulkanMemoryAllocationCounters(
+      UniquePid,
+      const protos::pbzero::VulkanMemoryEvent::Decoder&);
 
   void ParseVulkanApiEvent(int64_t, ConstBytes);
 
   void ParseGpuMemTotalEvent(int64_t, ConstBytes);
 
  private:
-  const StringId GetFullStageName(
+  StringId GetFullStageName(
       PacketSequenceStateGeneration* sequence_state,
       const protos::pbzero::GpuRenderStageEvent_Decoder& event) const;
   void InsertGpuTrack(
@@ -83,7 +85,7 @@
           GpuRenderStageEvent_Specifications_Description_Decoder& hw_queue);
   std::optional<std::string> FindDebugName(int32_t vk_object_type,
                                            uint64_t vk_handle) const;
-  const StringId ParseRenderSubpasses(
+  StringId ParseRenderSubpasses(
       const protos::pbzero::GpuRenderStageEvent_Decoder& event) const;
 
   TraceProcessorContext* const context_;
@@ -98,7 +100,7 @@
   // Map of stage ID -> pair(stage name, stage description)
   std::vector<std::pair<StringId, StringId>> gpu_render_stage_ids_;
   // For VulkanMemoryEvent
-  std::unordered_map<VulkanMemoryEvent::AllocationScope,
+  std::unordered_map<protos::pbzero::VulkanMemoryEvent::AllocationScope,
                      int64_t /*counter_value*/,
                      ProtoEnumHasher>
       vulkan_driver_memory_counters_;
diff --git a/src/trace_processor/importers/proto/graphics_frame_event_parser.cc b/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
index 11808a4..d75c617 100644
--- a/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
+++ b/src/trace_processor/importers/proto/graphics_frame_event_parser.cc
@@ -16,22 +16,26 @@
 
 #include "src/trace_processor/importers/proto/graphics_frame_event_parser.h"
 
-#include <cinttypes>
+#include <algorithm>
+#include <cstddef>
+#include <cstdint>
+#include <optional>
 
+#include "perfetto/base/logging.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/string_writer.h"
 #include "perfetto/ext/base/utils.h"
-#include "perfetto/protozero/field.h"
-#include "src/trace_processor/importers/common/args_tracker.h"
+#include "protos/perfetto/trace/android/graphics_frame_event.pbzero.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
-#include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/slice_tables_py.h"
+#include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
-#include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
-
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 constexpr char kQueueLostMessage[] =
     "Missing queue event. The slice is now a bit extended than it might "
@@ -163,11 +167,8 @@
     } else if (event.type() == GraphicsFrameEvent::QUEUE) {
       auto it = dequeue_slice_ids_.find(event_key);
       if (it != dequeue_slice_ids_.end()) {
-        auto dequeue_slice_id = it->second;
-        uint32_t row_idx =
-            *graphics_frame_slice_table->id().IndexOf(dequeue_slice_id);
-        graphics_frame_slice_table->mutable_frame_number()->Set(row_idx,
-                                                                frame_number);
+        auto rr = graphics_frame_slice_table->FindById(it->second);
+        rr->set_frame_number(frame_number);
       }
     }
   }
@@ -182,19 +183,19 @@
   if (opt_slice_id) {
     auto* graphics_frame_slice_table =
         context_->storage->mutable_graphics_frame_slice_table();
-    uint32_t row_idx = *graphics_frame_slice_table->id().IndexOf(*opt_slice_id);
+    auto rr = *graphics_frame_slice_table->FindById(*opt_slice_id);
     if (reset_name) {
       // Set the name (frame_number) to be 0 since there is no frame number
       // associated, example : dequeue event.
       StringId frame_name_id = context_->storage->InternString("0");
-      graphics_frame_slice_table->mutable_name()->Set(row_idx, frame_name_id);
-      graphics_frame_slice_table->mutable_frame_number()->Set(row_idx, 0);
+      rr.set_name(frame_name_id);
+      rr.set_frame_number(0);
     }
 
     // Set the duration to -1 so that this slice will be ignored by the
     // UI. Setting any other duration results in wrong data which we want
     // to avoid at all costs.
-    graphics_frame_slice_table->mutable_dur()->Set(row_idx, -1);
+    rr.set_dur(-1);
   }
 }
 
@@ -277,14 +278,10 @@
               context_->storage->mutable_graphics_frame_slice_table();
           // Set the name of the slice to be the frame number since dequeue did
           // not have a frame number at that time.
-          uint32_t row_idx =
-              *graphics_frame_slice_table->id().IndexOf(*opt_slice_id);
-          StringId frame_name_id =
-              context_->storage->InternString(slice_name.GetStringView());
-          graphics_frame_slice_table->mutable_name()->Set(row_idx,
-                                                          frame_name_id);
-          graphics_frame_slice_table->mutable_frame_number()->Set(row_idx,
-                                                                  frame_number);
+          auto rr = *graphics_frame_slice_table->FindById(*opt_slice_id);
+          rr.set_name(
+              context_->storage->InternString(slice_name.GetStringView()));
+          rr.set_frame_number(frame_number);
           dequeue_map_.erase(dequeue_time);
         }
       }
@@ -394,19 +391,17 @@
 
 void GraphicsFrameEventParser::ParseGraphicsFrameEvent(int64_t timestamp,
                                                        ConstBytes blob) {
-  protos::pbzero::GraphicsFrameEvent_Decoder frame_event(blob.data, blob.size);
+  protos::pbzero::GraphicsFrameEvent::Decoder frame_event(blob);
   if (!frame_event.has_buffer_event()) {
     return;
   }
 
-  ConstBytes bufferBlob = frame_event.buffer_event();
-  protos::pbzero::GraphicsFrameEvent_BufferEvent_Decoder event(bufferBlob.data,
-                                                               bufferBlob.size);
+  protos::pbzero::GraphicsFrameEvent::BufferEvent::Decoder event(
+      frame_event.buffer_event());
   if (CreateBufferEvent(timestamp, event)) {
     // Create a phase event only if the buffer event finishes successfully
     CreatePhaseEvent(timestamp, event);
   }
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker.cc b/src/trace_processor/importers/proto/heap_graph_tracker.cc
index 097f398..89f7977 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker.cc
@@ -586,13 +586,14 @@
       }
     }
     if (!class_package) {
-      auto app_id = storage_->process_table()
-                        .android_appid()[sequence_state.current_upid];
+      auto app_id = storage_->process_table()[sequence_state.current_upid]
+                        .android_appid();
       if (app_id) {
-        auto pkg_row = storage_->package_list_table().uid().IndexOf(*app_id);
-        if (pkg_row) {
-          class_package =
-              storage_->package_list_table().package_name()[*pkg_row];
+        for (auto it = storage_->package_list_table().IterateRows(); it; ++it) {
+          if (it.uid() == *app_id) {
+            class_package = it.package_name();
+            break;
+          }
         }
       }
     }
@@ -736,18 +737,18 @@
 
   auto* classes_tbl = storage_->mutable_heap_graph_class_table();
   std::map<ClassDescriptor, ClassTable::Id> class_to_id;
-  for (uint32_t idx = 0; idx < classes_tbl->row_count(); ++idx) {
-    class_to_id[{classes_tbl->name()[idx], classes_tbl->location()[idx]}] =
-        classes_tbl->id()[idx];
+  for (auto it = classes_tbl->IterateRows(); it; ++it) {
+    class_to_id[{it.name(), it.location()}] = it.id();
   }
 
   // Iterate through the classes table and annotate with superclasses.
   // We iterate all rows on the classes table (even though the superclass
   // mapping was generated on the current sequence) - if we cannot identify
   // a superclass we will just skip.
-  for (uint32_t idx = 0; idx < classes_tbl->row_count(); ++idx) {
-    auto name = storage_->GetString(classes_tbl->name()[idx]);
-    auto location = classes_tbl->location()[idx];
+  for (uint32_t i = 0; i < classes_tbl->row_count(); ++i) {
+    auto rr = (*classes_tbl)[i];
+    auto name = storage_->GetString(rr.name());
+    auto location = rr.location();
     auto normalized = GetNormalizedType(name);
     if (normalized.is_static_class || normalized.number_of_arrays > 0)
       continue;
@@ -766,9 +767,7 @@
       // instances would not appear here).
       continue;
     }
-    auto superclass_id = superclass_it->second;
-    // Mutate the superclass column
-    classes_tbl->mutable_superclass_id()->Set(idx, superclass_id);
+    rr.set_superclass_id(superclass_it->second);
   }
 }
 
diff --git a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
index ebc2a5e..70c8af5 100644
--- a/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/heap_graph_tracker_unittest.cc
@@ -16,8 +16,18 @@
 
 #include "src/trace_processor/importers/proto/heap_graph_tracker.h"
 
-#include "perfetto/base/logging.h"
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <utility>
+
+#include "perfetto/ext/base/string_view.h"
+#include "protos/perfetto/trace/profiling/heap_graph.pbzero.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/profiler_tables_py.h"
 #include "src/trace_processor/util/profiler_util.h"
 #include "test/gtest_and_gmock.h"
 
@@ -61,8 +71,8 @@
   constexpr int64_t kTimestamp = 1;
 
   TraceProcessorContext context;
-  context.storage.reset(new TraceStorage());
-  context.process_tracker.reset(new ProcessTracker(&context));
+  context.storage = std::make_shared<TraceStorage>();
+  context.process_tracker = std::make_unique<ProcessTracker>(&context);
   context.process_tracker->GetOrCreateProcess(kPid);
 
   HeapGraphTracker tracker(context.storage.get());
@@ -171,19 +181,16 @@
   const auto& objs_table = context.storage->heap_graph_object_table();
   const auto& class_table = context.storage->heap_graph_class_table();
   size_t count_bitmaps = 0;
-  for (uint32_t obj_row = 0; obj_row < objs_table.row_count(); ++obj_row) {
-    std::optional<uint32_t> class_row =
-        class_table.id().IndexOf(objs_table.type_id()[obj_row]);
+  for (auto it = objs_table.IterateRows(); it; ++it) {
+    auto class_row = class_table.FindById(it.type_id());
     ASSERT_TRUE(class_row.has_value());
-    if (context.storage->string_pool().Get(class_table.name()[*class_row]) ==
+    if (context.storage->string_pool().Get(class_row->name()) ==
         "android.graphics.Bitmap") {
-      EXPECT_EQ(objs_table.native_size()[obj_row], 24242);
+      EXPECT_EQ(it.native_size(), 24242);
       count_bitmaps++;
     } else {
-      EXPECT_EQ(objs_table.native_size()[obj_row], 0)
-          << context.storage->string_pool()
-                 .Get(class_table.name()[*class_row])
-                 .c_str()
+      EXPECT_EQ(it.native_size(), 0)
+          << context.storage->string_pool().Get(class_row->name()).c_str()
           << " has non zero native_size";
     }
   }
@@ -216,7 +223,7 @@
   constexpr uint64_t kA = 3;
   constexpr uint64_t kB = 4;
 
-  base::StringView field = base::StringView("foo");
+  auto field = base::StringView("foo");
   StringPool::Id x = context.storage->InternString("X");
   StringPool::Id y = context.storage->InternString("Y");
   StringPool::Id a = context.storage->InternString("A");
@@ -424,12 +431,12 @@
   EXPECT_THAT(counts, UnorderedElementsAre(1, 1));
 }
 
-static const char kArray[] = "X[]";
-static const char kDoubleArray[] = "X[][]";
-static const char kNoArray[] = "X";
-static const char kLongNoArray[] = "ABCDE";
-static const char kStaticClassNoArray[] = "java.lang.Class<abc>";
-static const char kStaticClassArray[] = "java.lang.Class<abc[]>";
+constexpr char kArray[] = "X[]";
+constexpr char kDoubleArray[] = "X[][]";
+constexpr char kNoArray[] = "X";
+constexpr char kLongNoArray[] = "ABCDE";
+constexpr char kStaticClassNoArray[] = "java.lang.Class<abc>";
+constexpr char kStaticClassArray[] = "java.lang.Class<abc[]>";
 
 TEST(HeapGraphTrackerTest, NormalizeTypeName) {
   // sizeof(...) - 1 below to get rid of the null-byte.
diff --git a/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc b/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc
index c4eeeff..dc96491 100644
--- a/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc
+++ b/src/trace_processor/importers/proto/memory_tracker_snapshot_parser.cc
@@ -275,8 +275,7 @@
           .id;
 
   auto* node_table = context_->storage->mutable_memory_snapshot_node_table();
-  uint32_t node_row_index =
-      static_cast<uint32_t>(*node_table->id().IndexOf(node_row_id));
+  auto rr = *node_table->FindById(node_row_id);
   ArgsTracker::BoundInserter args =
       context_->args_tracker->AddArgsTo(node_row_id);
 
@@ -286,9 +285,9 @@
         int64_t value_int = static_cast<int64_t>(entry.second.value_uint64);
 
         if (entry.first == "size") {
-          node_table->mutable_size()->Set(node_row_index, value_int);
+          rr.set_size(value_int);
         } else if (entry.first == "effective_size") {
-          node_table->mutable_effective_size()->Set(node_row_index, value_int);
+          rr.set_effective_size(value_int);
         } else {
           args.AddArg(context_->storage->InternString(
                           base::StringView(entry.first + ".value")),
diff --git a/src/trace_processor/importers/proto/network_trace_module_unittest.cc b/src/trace_processor/importers/proto/network_trace_module_unittest.cc
index 317120e..36ede9a 100644
--- a/src/trace_processor/importers/proto/network_trace_module_unittest.cc
+++ b/src/trace_processor/importers/proto/network_trace_module_unittest.cc
@@ -14,8 +14,21 @@
  * limitations under the License.
  */
 
+#include <cstdint>
+#include <memory>
+#include <vector>
+
 #include "src/trace_processor/importers/proto/network_trace_module.h"
 
+#include "perfetto/base/status.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/protozero/packed_repeated_fields.h"
+#include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "protos/perfetto/trace/android/network_trace.pbzero.h"
+#include "protos/perfetto/trace/trace.pbzero.h"
+#include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/args_translation_table.h"
 #include "src/trace_processor/importers/common/async_track_set_tracker.h"
@@ -27,35 +40,42 @@
 #include "src/trace_processor/importers/proto/proto_trace_parser_impl.h"
 #include "src/trace_processor/importers/proto/proto_trace_reader.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
 #include "test/gtest_and_gmock.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
+
 using ::perfetto::protos::pbzero::TrafficDirection;
 
 class NetworkTraceModuleTest : public testing::Test {
  public:
   NetworkTraceModuleTest() {
-    context_.storage.reset(new TraceStorage());
+    context_.storage = std::make_shared<TraceStorage>();
     storage_ = context_.storage.get();
 
-    context_.track_tracker.reset(new TrackTracker(&context_));
-    context_.slice_tracker.reset(new SliceTracker(&context_));
-    context_.args_tracker.reset(new ArgsTracker(&context_));
-    context_.global_args_tracker.reset(new GlobalArgsTracker(storage_));
-    context_.slice_translation_table.reset(new SliceTranslationTable(storage_));
-    context_.process_track_translation_table.reset(
-        new ProcessTrackTranslationTable(storage_));
-    context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
-    context_.async_track_set_tracker.reset(new AsyncTrackSetTracker(&context_));
-    context_.proto_trace_parser.reset(new ProtoTraceParserImpl(&context_));
-    context_.sorter.reset(
-        new TraceSorter(&context_, TraceSorter::SortingMode::kFullSort));
+    context_.track_tracker = std::make_unique<TrackTracker>(&context_);
+    context_.slice_tracker = std::make_unique<SliceTracker>(&context_);
+    context_.args_tracker = std::make_unique<ArgsTracker>(&context_);
+    context_.global_args_tracker =
+        std::make_unique<GlobalArgsTracker>(storage_);
+    context_.slice_translation_table =
+        std::make_unique<SliceTranslationTable>(storage_);
+    context_.process_track_translation_table =
+        std::make_unique<ProcessTrackTranslationTable>(storage_);
+    context_.args_translation_table =
+        std::make_unique<ArgsTranslationTable>(storage_);
+    context_.async_track_set_tracker =
+        std::make_unique<AsyncTrackSetTracker>(&context_);
+    context_.proto_trace_parser =
+        std::make_unique<ProtoTraceParserImpl>(&context_);
+    context_.sorter = std::make_shared<TraceSorter>(
+        &context_, TraceSorter::SortingMode::kFullSort);
   }
 
-  util::Status TokenizeAndParse() {
+  base::Status TokenizeAndParse() {
     context_.chunk_readers.push_back(
         std::make_unique<ProtoTraceReader>(&context_));
 
@@ -114,7 +134,7 @@
 
   const auto& slices = storage_->slice_table();
   ASSERT_EQ(slices.row_count(), 1u);
-  EXPECT_EQ(slices.ts()[0], 123);
+  EXPECT_EQ(slices[0].ts(), 123);
 
   EXPECT_TRUE(HasArg(1u, "packet_length", Variadic::Integer(72)));
   EXPECT_TRUE(HasArg(1u, "socket_uid", Variadic::Integer(1010)));
@@ -153,8 +173,8 @@
 
   const auto& slices = storage_->slice_table();
   ASSERT_EQ(slices.row_count(), 2u);
-  EXPECT_EQ(slices.ts()[0], 123);
-  EXPECT_EQ(slices.ts()[1], 133);
+  EXPECT_EQ(slices[0].ts(), 123);
+  EXPECT_EQ(slices[1].ts(), 133);
 
   EXPECT_TRUE(HasArg(1u, "packet_length", Variadic::Integer(72)));
   EXPECT_TRUE(HasArg(2u, "packet_length", Variadic::Integer(100)));
@@ -178,13 +198,12 @@
 
   const auto& slices = storage_->slice_table();
   ASSERT_EQ(slices.row_count(), 1u);
-  EXPECT_EQ(slices.ts()[0], 123);
-  EXPECT_EQ(slices.dur()[0], 10);
+  EXPECT_EQ(slices[0].ts(), 123);
+  EXPECT_EQ(slices[0].dur(), 10);
 
   EXPECT_TRUE(HasArg(1u, "packet_length", Variadic::Integer(172)));
   EXPECT_TRUE(HasArg(1u, "packet_count", Variadic::Integer(2)));
 }
 
 }  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc b/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc
index df1fba0..86336a7 100644
--- a/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc
+++ b/src/trace_processor/importers/proto/perf_sample_tracker_unittest.cc
@@ -15,9 +15,12 @@
  */
 
 #include "src/trace_processor/importers/proto/perf_sample_tracker.h"
+#include <cstdint>
+#include <string>
 
 #include "perfetto/base/logging.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "test/gtest_and_gmock.h"
 
 #include "protos/perfetto/common/perf_events.gen.h"
@@ -81,17 +84,16 @@
 
   TrackId track_id = stream.timebase_track_id;
   const auto& track_table = context.storage->perf_counter_track_table();
-  auto row_id = track_table.id().IndexOf(track_id);
+  auto rr = track_table.FindById(track_id);
 
   // track exists and looks sensible
-  ASSERT_TRUE(row_id.has_value());
-  EXPECT_EQ(track_table.perf_session_id()[*row_id], stream.perf_session_id);
-  EXPECT_EQ(track_table.cpu()[*row_id], cpu0);
-  EXPECT_TRUE(track_table.is_timebase()[*row_id]);
+  ASSERT_TRUE(rr.has_value());
+  EXPECT_EQ(rr->perf_session_id(), stream.perf_session_id);
+  EXPECT_EQ(rr->cpu(), cpu0);
+  EXPECT_TRUE(rr->is_timebase());
 
   // Name derived from the timebase.
-  std::string track_name =
-      context.storage->GetString(track_table.name()[*row_id]).ToStdString();
+  std::string track_name = context.storage->GetString(rr->name()).ToStdString();
   ASSERT_EQ(track_name, "page-faults");
 }
 
@@ -112,17 +114,16 @@
 
   TrackId track_id = stream.timebase_track_id;
   const auto& track_table = context.storage->perf_counter_track_table();
-  auto row_id = track_table.id().IndexOf(track_id);
+  auto rr = track_table.FindById(track_id);
 
   // track exists and looks sensible
-  ASSERT_TRUE(row_id.has_value());
-  EXPECT_EQ(track_table.perf_session_id()[*row_id], stream.perf_session_id);
-  EXPECT_EQ(track_table.cpu()[*row_id], cpu0);
-  EXPECT_TRUE(track_table.is_timebase()[*row_id]);
+  ASSERT_TRUE(rr.has_value());
+  EXPECT_EQ(rr->perf_session_id(), stream.perf_session_id);
+  EXPECT_EQ(rr->cpu(), cpu0);
+  EXPECT_TRUE(rr->is_timebase());
 
   // Name derived from the timebase.
-  std::string track_name =
-      context.storage->GetString(track_table.name()[*row_id]).ToStdString();
+  std::string track_name = context.storage->GetString(rr->name()).ToStdString();
   ASSERT_EQ(track_name, "sched:sched_switch");
 }
 
@@ -135,18 +136,17 @@
 
   TrackId track_id = stream.timebase_track_id;
   const auto& track_table = context.storage->perf_counter_track_table();
-  auto row_id = track_table.id().IndexOf(track_id);
+  auto rr = track_table.FindById(track_id);
 
   // track exists and looks sensible
-  ASSERT_TRUE(row_id.has_value());
-  EXPECT_EQ(track_table.perf_session_id()[*row_id], stream.perf_session_id);
-  EXPECT_EQ(track_table.cpu()[*row_id], cpu0);
-  EXPECT_TRUE(track_table.is_timebase()[*row_id]);
+  ASSERT_TRUE(rr.has_value());
+  EXPECT_EQ(rr->perf_session_id(), stream.perf_session_id);
+  EXPECT_EQ(rr->cpu(), cpu0);
+  EXPECT_TRUE(rr->is_timebase());
 
   // If the trace doesn't have a PerfSampleDefaults describing the timebase
   // counter, we assume cpu-clock.
-  std::string track_name =
-      context.storage->GetString(track_table.name()[*row_id]).ToStdString();
+  std::string track_name = context.storage->GetString(rr->name()).ToStdString();
   ASSERT_EQ(track_name, "cpu-clock");
 }
 
@@ -170,17 +170,16 @@
 
   TrackId track_id = stream.timebase_track_id;
   const auto& track_table = context.storage->perf_counter_track_table();
-  auto row_id = track_table.id().IndexOf(track_id);
+  auto rr = track_table.FindById(track_id);
 
   // track exists and looks sensible
-  ASSERT_TRUE(row_id.has_value());
-  EXPECT_EQ(track_table.perf_session_id()[*row_id], stream.perf_session_id);
-  EXPECT_EQ(track_table.cpu()[*row_id], cpu0);
-  EXPECT_TRUE(track_table.is_timebase()[*row_id]);
+  ASSERT_TRUE(rr.has_value());
+  EXPECT_EQ(rr->perf_session_id(), stream.perf_session_id);
+  EXPECT_EQ(rr->cpu(), cpu0);
+  EXPECT_TRUE(rr->is_timebase());
 
   // Using the config-supplied name for the track.
-  std::string track_name =
-      context.storage->GetString(track_table.name()[*row_id]).ToStdString();
+  std::string track_name = context.storage->GetString(rr->name()).ToStdString();
   ASSERT_EQ(track_name, "test-name");
 }
 
diff --git a/src/trace_processor/importers/proto/profile_module.cc b/src/trace_processor/importers/proto/profile_module.cc
index a5e3672..813fa09 100644
--- a/src/trace_processor/importers/proto/profile_module.cc
+++ b/src/trace_processor/importers/proto/profile_module.cc
@@ -21,6 +21,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/flat_hash_map.h"
 #include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
 #include "src/trace_processor/importers/common/args_translation_table.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/deobfuscation_mapping_table.h"
@@ -480,8 +481,8 @@
 
       for (const FrameId frame_id : frame_ids) {
         auto* frames = context_->storage->mutable_stack_profile_frame_table();
-        uint32_t frame_row = *frames->id().IndexOf(frame_id);
-        frames->mutable_symbol_set_id()->Set(frame_row, symbol_set_id);
+        auto rr = *frames->FindById(frame_id);
+        rr.set_symbol_set_id(symbol_set_id);
         frame_found = true;
       }
     }
@@ -542,10 +543,9 @@
       for (tables::StackProfileFrameTable::Id frame_id : frames) {
         auto* frames_tbl =
             context_->storage->mutable_stack_profile_frame_table();
-        frames_tbl->mutable_deobfuscated_name()->Set(
-            *frames_tbl->id().IndexOf(frame_id),
-            context_->storage->InternString(
-                base::StringView(merged_deobfuscated)));
+        auto rr = *frames_tbl->FindById(frame_id);
+        rr.set_deobfuscated_name(context_->storage->InternString(
+            base::StringView(merged_deobfuscated)));
       }
       obfuscated_to_deobfuscated_members[context_->storage->InternString(
           member.obfuscated_name())] =
diff --git a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc b/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
index ddaaa85..2022952 100644
--- a/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
+++ b/src/trace_processor/importers/proto/profile_packet_sequence_state_unittest.cc
@@ -16,11 +16,14 @@
 
 #include "src/trace_processor/importers/proto/profile_packet_sequence_state.h"
 
+#include <cstdint>
 #include <memory>
+#include <optional>
 
 #include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 #include "test/gtest_and_gmock.h"
 
@@ -127,19 +130,19 @@
   InsertMapping(kSecondPacket);
   profile_packet_sequence_state().FinalizeProfile();
 
-  EXPECT_THAT(context.storage->stack_profile_mapping_table().build_id()[0],
+  EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].build_id(),
               context.storage->InternString({kBuildIDHexName}));
-  EXPECT_THAT(context.storage->stack_profile_mapping_table().exact_offset()[0],
+  EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].exact_offset(),
               kMappingExactOffset);
-  EXPECT_THAT(context.storage->stack_profile_mapping_table().start_offset()[0],
+  EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].start_offset(),
               kMappingStartOffset);
-  EXPECT_THAT(context.storage->stack_profile_mapping_table().start()[0],
+  EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].start(),
               kMappingStart);
-  EXPECT_THAT(context.storage->stack_profile_mapping_table().end()[0],
+  EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].end(),
               kMappingEnd);
-  EXPECT_THAT(context.storage->stack_profile_mapping_table().load_bias()[0],
+  EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].load_bias(),
               kMappingLoadBias);
-  EXPECT_THAT(context.storage->stack_profile_mapping_table().name()[0],
+  EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].name(),
               fully_qualified_mapping_name);
 }
 
@@ -152,9 +155,9 @@
   profile_packet_sequence_state().FinalizeProfile();
 
   const auto& frames = context.storage->stack_profile_frame_table();
-  EXPECT_THAT(frames.name()[0], frame_name);
-  EXPECT_THAT(frames.mapping()[0], MappingId{0});
-  EXPECT_THAT(frames.rel_pc()[0], kFrameRelPc);
+  EXPECT_THAT(frames[0].name(), frame_name);
+  EXPECT_THAT(frames[0].mapping(), MappingId{0});
+  EXPECT_THAT(frames[0].rel_pc(), kFrameRelPc);
 }
 
 // Insert the same callstack from two different packets, assert it is only
@@ -166,18 +169,15 @@
   profile_packet_sequence_state().FinalizeProfile();
 
   const auto& callsite_table = context.storage->stack_profile_callsite_table();
-  const auto& depth = callsite_table.depth();
-  const auto& parent_id = callsite_table.parent_id();
-  const auto& frame_id = callsite_table.frame_id();
 
-  EXPECT_EQ(depth[0], 0u);
-  EXPECT_EQ(depth[1], 1u);
+  EXPECT_EQ(callsite_table[0].depth(), 0u);
+  EXPECT_EQ(callsite_table[1].depth(), 1u);
 
-  EXPECT_EQ(parent_id[0], std::nullopt);
-  EXPECT_EQ(parent_id[1], CallsiteId{0});
+  EXPECT_EQ(callsite_table[0].parent_id(), std::nullopt);
+  EXPECT_EQ(callsite_table[1].parent_id(), CallsiteId{0});
 
-  EXPECT_EQ(frame_id[0], FrameId{0});
-  EXPECT_EQ(frame_id[1], FrameId{0});
+  EXPECT_EQ(callsite_table[0].frame_id(), FrameId{0});
+  EXPECT_EQ(callsite_table[1].frame_id(), FrameId{0});
 }
 
 std::optional<CallsiteId> FindCallstack(const TraceStorage& storage,
@@ -185,10 +185,10 @@
                                         std::optional<CallsiteId> parent,
                                         FrameId frame_id) {
   const auto& callsites = storage.stack_profile_callsite_table();
-  for (uint32_t i = 0; i < callsites.row_count(); ++i) {
-    if (callsites.depth()[i] == depth && callsites.parent_id()[i] == parent &&
-        callsites.frame_id()[i] == frame_id) {
-      return callsites.id()[i];
+  for (auto it = callsites.IterateRows(); it; ++it) {
+    if (it.depth() == depth && it.parent_id() == parent &&
+        it.frame_id() == frame_id) {
+      return it.id();
     }
   }
   return std::nullopt;
@@ -223,7 +223,7 @@
   ppss.CommitAllocations();
   auto foo_bar_id = context.storage->string_pool().GetId("/foo/bar");
   ASSERT_NE(foo_bar_id, std::nullopt);
-  EXPECT_THAT(context.storage->stack_profile_mapping_table().name()[0],
+  EXPECT_THAT(context.storage->stack_profile_mapping_table()[0].name(),
               *foo_bar_id);
 }
 
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
index efa53c9..cbff477 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_impl_unittest.cc
@@ -14,33 +14,51 @@
  * limitations under the License.
  */
 
-#include "src/trace_processor/importers/proto/proto_trace_reader.h"
+#include "src/trace_processor/importers/proto/proto_trace_parser_impl.h"
 
-#include "perfetto/base/logging.h"
+#include <cmath>
+#include <cstdint>
+#include <cstring>
+#include <functional>
+#include <memory>
+#include <optional>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/status.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/trace_blob.h"
+#include "perfetto/trace_processor/trace_blob_view.h"
+#include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/args_translation_table.h"
 #include "src/trace_processor/importers/common/clock_tracker.h"
 #include "src/trace_processor/importers/common/cpu_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/global_args_tracker.h"
 #include "src/trace_processor/importers/common/machine_tracker.h"
 #include "src/trace_processor/importers/common/mapping_tracker.h"
 #include "src/trace_processor/importers/common/metadata_tracker.h"
 #include "src/trace_processor/importers/common/process_track_translation_table.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/slice_tracker.h"
+#include "src/trace_processor/importers/common/slice_translation_table.h"
 #include "src/trace_processor/importers/common/stack_profile_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_sched_event_tracker.h"
 #include "src/trace_processor/importers/proto/additional_modules.h"
 #include "src/trace_processor/importers/proto/default_modules.h"
-#include "src/trace_processor/importers/proto/proto_trace_parser_impl.h"
+#include "src/trace_processor/importers/proto/proto_trace_reader.h"
 #include "src/trace_processor/sorter/trace_sorter.h"
 #include "src/trace_processor/storage/metadata.h"
+#include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
+#include "src/trace_processor/types/variadic.h"
 #include "src/trace_processor/util/descriptors.h"
 #include "test/gtest_and_gmock.h"
 
@@ -77,10 +95,10 @@
 #include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 namespace {
 
+using ::std::make_pair;
 using ::testing::_;
 using ::testing::Args;
 using ::testing::AtLeast;
@@ -253,12 +271,13 @@
   ProtoTraceParserTest() {
     storage_ = new TraceStorage();
     context_.storage.reset(storage_);
-    context_.track_tracker.reset(new TrackTracker(&context_));
-    context_.global_args_tracker.reset(
-        new GlobalArgsTracker(context_.storage.get()));
+    context_.track_tracker = std::make_unique<TrackTracker>(&context_);
+    context_.global_args_tracker =
+        std::make_unique<GlobalArgsTracker>(context_.storage.get());
     context_.mapping_tracker.reset(new MappingTracker(&context_));
-    context_.stack_profile_tracker.reset(new StackProfileTracker(&context_));
-    context_.args_tracker.reset(new ArgsTracker(&context_));
+    context_.stack_profile_tracker =
+        std::make_unique<StackProfileTracker>(&context_);
+    context_.args_tracker = std::make_unique<ArgsTracker>(&context_);
     context_.args_translation_table.reset(new ArgsTranslationTable(storage_));
     context_.metadata_tracker.reset(
         new MetadataTracker(context_.storage.get()));
@@ -274,14 +293,16 @@
         new ProcessTrackTranslationTable(storage_));
     slice_ = new NiceMock<MockSliceTracker>(&context_);
     context_.slice_tracker.reset(slice_);
-    context_.slice_translation_table.reset(new SliceTranslationTable(storage_));
+    context_.slice_translation_table =
+        std::make_unique<SliceTranslationTable>(storage_);
     clock_ = new ClockTracker(&context_);
     context_.clock_tracker.reset(clock_);
-    context_.flow_tracker.reset(new FlowTracker(&context_));
-    context_.proto_trace_parser.reset(new ProtoTraceParserImpl(&context_));
-    context_.sorter.reset(
-        new TraceSorter(&context_, TraceSorter::SortingMode::kFullSort));
-    context_.descriptor_pool_.reset(new DescriptorPool());
+    context_.flow_tracker = std::make_unique<FlowTracker>(&context_);
+    context_.proto_trace_parser =
+        std::make_unique<ProtoTraceParserImpl>(&context_);
+    context_.sorter = std::make_shared<TraceSorter>(
+        &context_, TraceSorter::SortingMode::kFullSort);
+    context_.descriptor_pool_ = std::make_unique<DescriptorPool>();
 
     RegisterDefaultModules(&context_);
     RegisterAdditionalModules(&context_);
@@ -291,7 +312,7 @@
 
   void SetUp() override { ResetTraceBuffers(); }
 
-  util::Status Tokenize() {
+  base::Status Tokenize() {
     trace_->Finalize();
     std::vector<uint8_t> trace_bytes = trace_.SerializeAsArray();
     std::unique_ptr<uint8_t[]> raw_trace(new uint8_t[trace_bytes.size()]);
@@ -309,12 +330,12 @@
     const auto& args = storage_->arg_table();
     Query q;
     q.constraints = {args.arg_set_id().eq(set_id)};
-    RowMap rm = args.QueryToRowMap(q);
+
     bool found = false;
-    for (auto it = rm.IterateRows(); it; it.Next()) {
-      if (args.key()[it.index()] == key_id) {
-        EXPECT_EQ(args.flat_key()[it.index()], key_id);
-        if (storage_->GetArgValue(it.index()) == value) {
+    for (auto it = args.FilterToIterator(q); it; ++it) {
+      if (it.key() == key_id) {
+        EXPECT_EQ(it.flat_key(), key_id);
+        if (storage_->GetArgValue(it.row_number().row_number()) == value) {
           found = true;
           break;
         }
@@ -401,17 +422,17 @@
   std::vector<std::string> expected_keys;
   for (uint32_t i = 0; i < args.row_count(); i++) {
     expected_keys.push_back(
-        context_.storage->GetString(args.key()[i]).ToStdString());
+        context_.storage->GetString(args[i].key()).ToStdString());
   }
   ASSERT_THAT(expected_keys,
               testing::ElementsAre("pid", "comm", "clone_flags",
                                    "oom_score_adj", "ip", "buf"));
-  ASSERT_EQ(args.int_value()[0], 123);
-  ASSERT_STREQ(args.string_value().GetString(1).c_str(), task_newtask);
-  ASSERT_EQ(args.int_value()[2], 12);
-  ASSERT_EQ(args.int_value()[3], 15);
-  ASSERT_EQ(args.int_value()[4], 20);
-  ASSERT_STREQ(args.string_value().GetString(5).c_str(), buf_value);
+  ASSERT_EQ(args[0].int_value(), 123);
+  ASSERT_EQ(context_.storage->GetString(*args[1].string_value()), task_newtask);
+  ASSERT_EQ(args[2].int_value(), 12);
+  ASSERT_EQ(args[3].int_value(), 15);
+  ASSERT_EQ(args[4].int_value(), 20);
+  ASSERT_EQ(context_.storage->GetString(*args[5].string_value()), buf_value);
 
   // TODO(hjd): Add test ftrace event with all field types
   // and test here.
@@ -449,28 +470,31 @@
   const auto& raw = storage_->raw_table();
 
   ASSERT_EQ(raw.row_count(), 1u);
-  ASSERT_EQ(raw.ts()[raw.row_count() - 1], 100);
-  ASSERT_EQ(storage_->thread_table().tid()[raw.utid()[raw.row_count() - 1]],
+  ASSERT_EQ(raw[raw.row_count() - 1].ts(), 100);
+  ASSERT_EQ(storage_->thread_table()[raw[raw.row_count() - 1].utid()].tid(),
             10u);
-  ASSERT_EQ(raw.name().GetString(raw.row_count() - 1), "Test");
+  ASSERT_EQ(storage_->GetString(raw[raw.row_count() - 1].name()), "Test");
 
-  auto set_id = raw.arg_set_id()[raw.row_count() - 1];
+  auto set_id = raw[raw.row_count() - 1].arg_set_id();
 
   const auto& args = storage_->arg_table();
   Query q;
   q.constraints = {args.arg_set_id().eq(set_id)};
-  RowMap rm = args.QueryToRowMap(q);
 
-  auto row = rm.Get(0);
+  auto it = args.FilterToIterator(q);
+  ASSERT_TRUE(it);
 
-  ASSERT_EQ(args.key().GetString(row), "meta1");
-  ASSERT_EQ(args.string_value().GetString(row++), "value1");
+  ASSERT_EQ(storage_->GetString(it.key()), "meta1");
+  ASSERT_EQ(storage_->GetString(*it.string_value()), "value1");
+  ASSERT_TRUE(++it);
 
-  ASSERT_EQ(args.key().GetString(row), "meta2");
-  ASSERT_EQ(args.int_value()[row++], -2);
+  ASSERT_EQ(storage_->GetString(it.key()), "meta2");
+  ASSERT_EQ(it.int_value(), -2);
+  ASSERT_TRUE(++it);
 
-  ASSERT_EQ(args.key().GetString(row), "meta3");
-  ASSERT_EQ(args.int_value()[row++], 3);
+  ASSERT_EQ(storage_->GetString(it.key()), "meta3");
+  ASSERT_EQ(it.int_value(), 3);
+  ASSERT_FALSE(++it);
 }
 
 TEST_F(ProtoTraceParserTest, LoadMultipleEvents) {
@@ -620,7 +644,7 @@
   Tokenize();
   context_.sorter->ExtractEventsForced();
 
-  EXPECT_EQ(context_.storage->cpu_counter_track_table().ucpu()[0].value, 10u);
+  EXPECT_EQ(context_.storage->cpu_counter_track_table()[0].ucpu().value, 10u);
 }
 
 TEST_F(ProtoTraceParserTest, LoadCpuFreqKHz) {
@@ -947,14 +971,14 @@
   context_.sorter->ExtractEventsForced();
 
   EXPECT_EQ(storage_->slice_table().row_count(), 2u);
-  auto id_0 = storage_->slice_table().id().IndexOf(SliceId(0u));
-  EXPECT_TRUE(id_0);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_0], 2003000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_0], 12000);
-  auto id_1 = storage_->slice_table().id().IndexOf(SliceId(1u));
-  EXPECT_TRUE(id_1);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_1], 2005000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_1], 5000);
+  auto rr_0 = storage_->slice_table().FindById(SliceId(0u));
+  EXPECT_TRUE(rr_0);
+  EXPECT_EQ(rr_0->thread_ts(), 2003000);
+  EXPECT_EQ(rr_0->thread_dur(), 12000);
+  auto rr_1 = storage_->slice_table().FindById(SliceId(1u));
+  EXPECT_TRUE(rr_1);
+  EXPECT_EQ(rr_1->thread_ts(), 2005000);
+  EXPECT_EQ(rr_1->thread_dur(), 5000);
 }
 
 TEST_F(ProtoTraceParserTest, TrackEventWithoutInternedDataWithTypes) {
@@ -1036,14 +1060,14 @@
   context_.sorter->ExtractEventsForced();
 
   EXPECT_EQ(storage_->slice_table().row_count(), 2u);
-  auto id_0 = storage_->slice_table().id().IndexOf(SliceId(0u));
-  EXPECT_TRUE(id_0);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_0], 2005000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_0], 5000);
-  auto id_1 = storage_->slice_table().id().IndexOf(SliceId(1u));
-  EXPECT_TRUE(id_1);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_1], 2007000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_1], 0);
+  auto rr_0 = storage_->slice_table().FindById(SliceId(0u));
+  EXPECT_TRUE(rr_0);
+  EXPECT_EQ(rr_0->thread_ts(), 2005000);
+  EXPECT_EQ(rr_0->thread_dur(), 5000);
+  auto rr_1 = storage_->slice_table().FindById(SliceId(1u));
+  EXPECT_TRUE(rr_1);
+  EXPECT_EQ(rr_1->thread_ts(), 2007000);
+  EXPECT_EQ(rr_1->thread_dur(), 0);
 }
 
 TEST_F(ProtoTraceParserTest, TrackEventWithInternedData) {
@@ -1071,10 +1095,10 @@
     legacy_event->set_phase('B');
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev1");
   }
@@ -1134,13 +1158,13 @@
         protos::pbzero::TrackEvent::LegacyEvent::FLOW_OUT);
 
     auto* interned_data = packet->set_interned_data();
-    auto cat2 = interned_data->add_event_categories();
+    auto* cat2 = interned_data->add_event_categories();
     cat2->set_iid(2);
     cat2->set_name("cat2");
-    auto cat3 = interned_data->add_event_categories();
+    auto* cat3 = interned_data->add_event_categories();
     cat3->set_iid(3);
     cat3->set_name("cat3");
-    auto ev2 = interned_data->add_event_names();
+    auto* ev2 = interned_data->add_event_names();
     ev2->set_iid(4);
     ev2->set_name("ev2");
   }
@@ -1235,24 +1259,24 @@
   context_.sorter->ExtractEventsForced();
 
   EXPECT_EQ(storage_->slice_table().row_count(), 3u);
-  auto id_0 = storage_->slice_table().id().IndexOf(SliceId(0u));
-  EXPECT_TRUE(id_0);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_0], 2003000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_0], 12000);
-  EXPECT_EQ(storage_->slice_table().thread_instruction_count()[*id_0], 3010);
-  EXPECT_EQ(storage_->slice_table().thread_instruction_delta()[*id_0], 50);
-  auto id_1 = storage_->slice_table().id().IndexOf(SliceId(1u));
-  EXPECT_TRUE(id_1);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_1], 2005000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_1], 5000);
-  EXPECT_EQ(storage_->slice_table().thread_instruction_count()[*id_1], 3020);
-  EXPECT_EQ(storage_->slice_table().thread_instruction_delta()[*id_1], 20);
-  auto id_2 = storage_->slice_table().id().IndexOf(SliceId(2u));
-  EXPECT_TRUE(id_2);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_2], 2030000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_2], 0);
-  EXPECT_EQ(storage_->slice_table().thread_instruction_count()[*id_2], 3100);
-  EXPECT_EQ(storage_->slice_table().thread_instruction_delta()[*id_2], 0);
+  auto rr_0 = storage_->slice_table().FindById(SliceId(0u));
+  EXPECT_TRUE(rr_0);
+  EXPECT_EQ(rr_0->thread_ts(), 2003000);
+  EXPECT_EQ(rr_0->thread_dur(), 12000);
+  EXPECT_EQ(rr_0->thread_instruction_count(), 3010);
+  EXPECT_EQ(rr_0->thread_instruction_delta(), 50);
+  auto rr_1 = storage_->slice_table().FindById(SliceId(1u));
+  EXPECT_TRUE(rr_1);
+  EXPECT_EQ(rr_1->thread_ts(), 2005000);
+  EXPECT_EQ(rr_1->thread_dur(), 5000);
+  EXPECT_EQ(rr_1->thread_instruction_count(), 3020);
+  EXPECT_EQ(rr_1->thread_instruction_delta(), 20);
+  auto rr_2 = storage_->slice_table().FindById(SliceId(2u));
+  EXPECT_TRUE(rr_2);
+  EXPECT_EQ(rr_2->thread_ts(), 2030000);
+  EXPECT_EQ(rr_2->thread_dur(), 0);
+  EXPECT_EQ(rr_2->thread_instruction_count(), 3100);
+  EXPECT_EQ(rr_2->thread_instruction_delta(), 0);
 }
 
 TEST_F(ProtoTraceParserTest, TrackEventAsyncEvents) {
@@ -1282,10 +1306,10 @@
     legacy_event->set_use_async_tts(true);
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev1");
   }
@@ -1315,7 +1339,7 @@
     legacy_event->set_global_id(10);
 
     auto* interned_data = packet->set_interned_data();
-    auto ev2 = interned_data->add_event_names();
+    auto* ev2 = interned_data->add_event_names();
     ev2->set_iid(2);
     ev2->set_name("ev2");
   }
@@ -1332,7 +1356,7 @@
     legacy_event->set_global_id(15);
 
     auto* interned_data = packet->set_interned_data();
-    auto cat2 = interned_data->add_event_categories();
+    auto* cat2 = interned_data->add_event_categories();
     cat2->set_iid(2);
     cat2->set_name("cat2");
   }
@@ -1388,14 +1412,14 @@
   // First track is for the thread; second first async, third and fourth for
   // thread time and instruction count, others are the async event tracks.
   EXPECT_EQ(storage_->track_table().row_count(), 6u);
-  EXPECT_EQ(storage_->track_table().name()[1], ev_1);
-  EXPECT_EQ(storage_->track_table().name()[4], ev_2);
-  EXPECT_EQ(storage_->track_table().name()[5], ev_2);
+  EXPECT_EQ(storage_->track_table()[1].name(), ev_1);
+  EXPECT_EQ(storage_->track_table()[4].name(), ev_2);
+  EXPECT_EQ(storage_->track_table()[5].name(), ev_2);
 
   EXPECT_EQ(storage_->process_track_table().row_count(), 3u);
-  EXPECT_EQ(storage_->process_track_table().upid()[0], 1u);
-  EXPECT_EQ(storage_->process_track_table().upid()[1], 1u);
-  EXPECT_EQ(storage_->process_track_table().upid()[2], 1u);
+  EXPECT_EQ(storage_->process_track_table()[0].upid(), 1u);
+  EXPECT_EQ(storage_->process_track_table()[1].upid(), 1u);
+  EXPECT_EQ(storage_->process_track_table()[2].upid(), 1u);
 
   EXPECT_EQ(storage_->virtual_track_slices().slice_count(), 1u);
   EXPECT_EQ(storage_->virtual_track_slices().slice_ids()[0], SliceId(0u));
@@ -1449,10 +1473,10 @@
     legacy_event->set_use_async_tts(true);
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev1");
   }
@@ -1469,10 +1493,10 @@
     event->set_type(protos::pbzero::TrackEvent::TYPE_INSTANT);
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(2);
     cat1->set_name("cat2");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(2);
     ev1->set_name("ev2");
   }
@@ -1516,10 +1540,10 @@
     event->set_type(protos::pbzero::TrackEvent::TYPE_INSTANT);
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat3");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev3");
   }
@@ -1575,12 +1599,15 @@
   // default track (parent of async track), fourth is "Thread track 2", fifth &
   // sixth are thread time tracks for thread 1 and 2.
   EXPECT_EQ(storage_->track_table().row_count(), 5u);
-  EXPECT_EQ(storage_->track_table().name().GetString(0), "Thread track 1");
-  EXPECT_EQ(storage_->track_table().name().GetString(1), "Async track 1");
-  EXPECT_EQ(storage_->track_table().name().GetString(2), "Thread track 2");
+  EXPECT_EQ(storage_->GetString((storage_->track_table()[0].name())),
+            "Thread track 1");
+  EXPECT_EQ(storage_->GetString((storage_->track_table()[1].name())),
+            "Async track 1");
+  EXPECT_EQ(storage_->GetString((storage_->track_table()[2].name())),
+            "Thread track 2");
   EXPECT_EQ(storage_->thread_track_table().row_count(), 2u);
-  EXPECT_EQ(storage_->thread_track_table().utid()[0], 1u);
-  EXPECT_EQ(storage_->thread_track_table().utid()[1], 2u);
+  EXPECT_EQ(storage_->thread_track_table()[0].utid(), 1u);
+  EXPECT_EQ(storage_->thread_track_table()[1].utid(), 2u);
 
   EXPECT_EQ(storage_->virtual_track_slices().slice_count(), 1u);
   EXPECT_EQ(storage_->virtual_track_slices().slice_ids()[0], SliceId(2u));
@@ -1592,19 +1619,19 @@
             20);
 
   EXPECT_EQ(storage_->slice_table().row_count(), 2u);
-  auto id_0 = storage_->slice_table().id().IndexOf(SliceId(0u));
-  EXPECT_TRUE(id_0);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_0], 2007000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_0], 0);
+  auto rr_0 = storage_->slice_table().FindById(SliceId(0u));
+  EXPECT_TRUE(rr_0);
+  EXPECT_EQ(rr_0->thread_ts(), 2007000);
+  EXPECT_EQ(rr_0->thread_dur(), 0);
   // There was no thread instructions in the packets above.
-  EXPECT_FALSE(storage_->slice_table().thread_instruction_count()[*id_0]);
-  EXPECT_FALSE(storage_->slice_table().thread_instruction_delta()[*id_0]);
-  auto id_1 = storage_->slice_table().id().IndexOf(SliceId(1u));
-  EXPECT_TRUE(id_1);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_1], 2008000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_1], 0);
-  EXPECT_FALSE(storage_->slice_table().thread_instruction_count()[*id_1]);
-  EXPECT_FALSE(storage_->slice_table().thread_instruction_delta()[*id_1]);
+  EXPECT_FALSE(rr_0->thread_instruction_count());
+  EXPECT_FALSE(rr_0->thread_instruction_delta());
+  auto rr_1 = storage_->slice_table().FindById(SliceId(1u));
+  EXPECT_TRUE(rr_1);
+  EXPECT_EQ(rr_1->thread_ts(), 2008000);
+  EXPECT_EQ(rr_1->thread_dur(), 0);
+  EXPECT_FALSE(rr_1->thread_instruction_count());
+  EXPECT_FALSE(rr_1->thread_instruction_delta());
 }
 
 TEST_F(ProtoTraceParserTest, TrackEventWithResortedCounterDescriptor) {
@@ -1697,14 +1724,14 @@
   // First track is thread time track, second is "t1".
   EXPECT_EQ(storage_->track_table().row_count(), 2u);
   EXPECT_EQ(storage_->thread_track_table().row_count(), 1u);
-  EXPECT_EQ(storage_->thread_track_table().utid()[0], 1u);
+  EXPECT_EQ(storage_->thread_track_table()[0].utid(), 1u);
 
   // Counter values should also be imported into thread slices.
   EXPECT_EQ(storage_->slice_table().row_count(), 1u);
-  auto id_0 = storage_->slice_table().id().IndexOf(SliceId(0u));
-  EXPECT_TRUE(id_0);
-  EXPECT_EQ(storage_->slice_table().thread_ts()[*id_0], 1000000);
-  EXPECT_EQ(storage_->slice_table().thread_dur()[*id_0], 10000);
+  auto rr_0 = storage_->slice_table().FindById(SliceId(0u));
+  EXPECT_TRUE(rr_0);
+  EXPECT_EQ(rr_0->thread_ts(), 1000000);
+  EXPECT_EQ(rr_0->thread_dur(), 10000);
 }
 
 TEST_F(ProtoTraceParserTest, TrackEventWithoutIncrementalStateReset) {
@@ -1916,10 +1943,10 @@
     legacy_event->set_phase('B');
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev1");
   }
@@ -1943,10 +1970,10 @@
     legacy_event->set_phase('B');
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
-    auto ev2 = interned_data->add_event_names();
+    auto* ev2 = interned_data->add_event_names();
     ev2->set_iid(1);
     ev2->set_name("ev2");
   }
@@ -2051,16 +2078,16 @@
     legacy_event->set_phase('B');
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev1");
-    auto an1 = interned_data->add_debug_annotation_names();
+    auto* an1 = interned_data->add_debug_annotation_names();
     an1->set_iid(1);
     an1->set_name("an1");
-    auto an2 = interned_data->add_debug_annotation_names();
+    auto* an2 = interned_data->add_debug_annotation_names();
     an2->set_iid(2);
     an2->set_name("an2");
   }
@@ -2097,25 +2124,25 @@
     legacy_event->set_phase('E');
 
     auto* interned_data = packet->set_interned_data();
-    auto an3 = interned_data->add_debug_annotation_names();
+    auto* an3 = interned_data->add_debug_annotation_names();
     an3->set_iid(3);
     an3->set_name("an3");
-    auto an4 = interned_data->add_debug_annotation_names();
+    auto* an4 = interned_data->add_debug_annotation_names();
     an4->set_iid(4);
     an4->set_name("an4");
-    auto an5 = interned_data->add_debug_annotation_names();
+    auto* an5 = interned_data->add_debug_annotation_names();
     an5->set_iid(5);
     an5->set_name("an5");
-    auto an6 = interned_data->add_debug_annotation_names();
+    auto* an6 = interned_data->add_debug_annotation_names();
     an6->set_iid(6);
     an6->set_name("an6");
-    auto an7 = interned_data->add_debug_annotation_names();
+    auto* an7 = interned_data->add_debug_annotation_names();
     an7->set_iid(7);
     an7->set_name("an7");
-    auto an8 = interned_data->add_debug_annotation_names();
+    auto* an8 = interned_data->add_debug_annotation_names();
     an8->set_iid(8);
     an8->set_name("an8");
-    auto an9 = interned_data->add_debug_annotation_names();
+    auto* an9 = interned_data->add_debug_annotation_names();
     an9->set_iid(9);
     an9->set_name("an8.foo");
   }
@@ -2224,13 +2251,13 @@
     legacy_event->set_phase('B');
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev1");
-    auto loc1 = interned_data->add_source_locations();
+    auto* loc1 = interned_data->add_source_locations();
     loc1->set_iid(1);
     loc1->set_file_name("file1");
     loc1->set_function_name("func1");
@@ -2289,19 +2316,19 @@
     legacy_event->set_phase('I');
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
 
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev1");
 
-    auto body = interned_data->add_log_message_body();
+    auto* body = interned_data->add_log_message_body();
     body->set_iid(1);
     body->set_body("body1");
 
-    auto loc1 = interned_data->add_source_locations();
+    auto* loc1 = interned_data->add_source_locations();
     loc1->set_iid(1);
     loc1->set_file_name("file1");
     loc1->set_function_name("func1");
@@ -2338,9 +2365,9 @@
   context_.sorter->ExtractEventsForced();
 
   EXPECT_GT(context_.storage->android_log_table().row_count(), 0u);
-  EXPECT_EQ(context_.storage->android_log_table().ts()[0], 1010000);
-  EXPECT_EQ(context_.storage->android_log_table().msg()[0], body_1);
-  EXPECT_EQ(context_.storage->android_log_table().tag()[0], source_location_id);
+  EXPECT_EQ(context_.storage->android_log_table()[0].ts(), 1010000);
+  EXPECT_EQ(context_.storage->android_log_table()[0].msg(), body_1);
+  EXPECT_EQ(context_.storage->android_log_table()[0].tag(), source_location_id);
 }
 
 TEST_F(ProtoTraceParserTest, TrackEventParseLegacyEventIntoRawTable) {
@@ -2377,13 +2404,13 @@
     annotation1->set_uint_value(10u);
 
     auto* interned_data = packet->set_interned_data();
-    auto cat1 = interned_data->add_event_categories();
+    auto* cat1 = interned_data->add_event_categories();
     cat1->set_iid(1);
     cat1->set_name("cat1");
-    auto ev1 = interned_data->add_event_names();
+    auto* ev1 = interned_data->add_event_names();
     ev1->set_iid(1);
     ev1->set_name("ev1");
-    auto an1 = interned_data->add_debug_annotation_names();
+    auto* an1 = interned_data->add_debug_annotation_names();
     an1->set_iid(1);
     an1->set_name("an1");
   }
@@ -2412,14 +2439,14 @@
   // Verify raw_table and args contents.
   const auto& raw_table = storage_->raw_table();
   EXPECT_EQ(raw_table.row_count(), 1u);
-  EXPECT_EQ(raw_table.ts()[0], 1010000);
-  EXPECT_EQ(raw_table.name()[0],
+  EXPECT_EQ(raw_table[0].ts(), 1010000);
+  EXPECT_EQ(raw_table[0].name(),
             storage_->InternString("track_event.legacy_event"));
-  auto ucpu = raw_table.ucpu()[0];
+  auto ucpu = raw_table[0].ucpu();
   const auto& cpu_table = storage_->cpu_table();
-  EXPECT_EQ(cpu_table.cpu()[ucpu.value], 0u);
-  EXPECT_EQ(raw_table.utid()[0], 1u);
-  EXPECT_EQ(raw_table.arg_set_id()[0], 1u);
+  EXPECT_EQ(cpu_table[ucpu.value].cpu(), 0u);
+  EXPECT_EQ(raw_table[0].utid(), 1u);
+  EXPECT_EQ(raw_table[0].arg_set_id(), 1u);
 
   EXPECT_GE(storage_->arg_table().row_count(), 10u);
 
@@ -2499,7 +2526,7 @@
     metadata->set_int_value(23);
   }
 
-  util::Status status = Tokenize();
+  base::Status status = Tokenize();
   EXPECT_TRUE(status.ok());
   context_.sorter->ExtractEventsForced();
 
@@ -2534,9 +2561,9 @@
   // Verify raw_table and args contents.
   const auto& raw_table = storage_->raw_table();
   EXPECT_EQ(raw_table.row_count(), 1u);
-  EXPECT_EQ(raw_table.name()[0],
+  EXPECT_EQ(raw_table[0].name(),
             storage_->InternString("chrome_event.metadata"));
-  EXPECT_EQ(raw_table.arg_set_id()[0], 1u);
+  EXPECT_EQ(raw_table[0].arg_set_id(), 1u);
 
   EXPECT_EQ(storage_->arg_table().row_count(), 2u);
   EXPECT_TRUE(HasArg(1u, storage_->InternString(kStringName),
@@ -2565,9 +2592,9 @@
   // Verify raw_table and args contents.
   const auto& raw_table = storage_->raw_table();
   EXPECT_EQ(raw_table.row_count(), 1u);
-  EXPECT_EQ(raw_table.name()[0],
+  EXPECT_EQ(raw_table[0].name(),
             storage_->InternString("chrome_event.legacy_system_trace"));
-  EXPECT_EQ(raw_table.arg_set_id()[0], 1u);
+  EXPECT_EQ(raw_table[0].arg_set_id(), 1u);
 
   EXPECT_EQ(storage_->arg_table().row_count(), 1u);
   EXPECT_TRUE(HasArg(1u, storage_->InternString("data"),
@@ -2593,9 +2620,9 @@
   // Verify raw_table and args contents.
   const auto& raw_table = storage_->raw_table();
   EXPECT_EQ(raw_table.row_count(), 1u);
-  EXPECT_EQ(raw_table.name()[0],
+  EXPECT_EQ(raw_table[0].name(),
             storage_->InternString("chrome_event.legacy_user_trace"));
-  EXPECT_EQ(raw_table.arg_set_id()[0], 1u);
+  EXPECT_EQ(raw_table[0].arg_set_id(), 1u);
 
   EXPECT_EQ(storage_->arg_table().row_count(), 1u);
   EXPECT_TRUE(
@@ -2619,18 +2646,15 @@
   base::StringView tags = metadata::kNames[metadata::benchmark_story_tags];
 
   context_.sorter->ExtractEventsForced();
-
-  const auto& meta_keys = storage_->metadata_table().name();
-  const auto& meta_values = storage_->metadata_table().str_value();
   EXPECT_EQ(storage_->metadata_table().row_count(), 3u);
 
   std::vector<std::pair<base::StringView, base::StringView>> meta_entries;
-  for (uint32_t i = 0; i < storage_->metadata_table().row_count(); i++) {
-    meta_entries.emplace_back(
-        std::make_pair(meta_keys.GetString(i), meta_values.GetString(i)));
+  for (auto it = storage_->metadata_table().IterateRows(); it; ++it) {
+    meta_entries.emplace_back(storage_->GetString(it.name()),
+                              storage_->GetString(*it.str_value()));
   }
   EXPECT_THAT(meta_entries,
-              UnorderedElementsAreArray({std::make_pair(benchmark, kName),
+              UnorderedElementsAreArray({make_pair(benchmark, kName),
                                          std::make_pair(tags, kTag1),
                                          std::make_pair(tags, kTag2)}));
 }
@@ -2666,17 +2690,18 @@
 
   const auto& metadata = storage_->metadata_table();
 
-  EXPECT_STREQ(metadata.name().GetString(0).c_str(), "cr-str_name");
-  EXPECT_STREQ(metadata.str_value().GetString(0).c_str(), "foostr");
+  EXPECT_STREQ(storage_->GetString(metadata[0].name()).c_str(), "cr-str_name");
+  EXPECT_STREQ(storage_->GetString(*metadata[0].str_value()).c_str(), "foostr");
 
-  EXPECT_STREQ(metadata.name().GetString(1).c_str(), "cr-int_name");
-  EXPECT_EQ(metadata.int_value()[1], 42);
+  EXPECT_STREQ(storage_->GetString(metadata[1].name()).c_str(), "cr-int_name");
+  EXPECT_EQ(metadata[1].int_value(), 42);
 
-  EXPECT_STREQ(metadata.name().GetString(2).c_str(), "cr-bool_name");
-  EXPECT_EQ(metadata.int_value()[2], 1);
+  EXPECT_STREQ(storage_->GetString(metadata[2].name()).c_str(), "cr-bool_name");
+  EXPECT_EQ(metadata[2].int_value(), 1);
 
-  EXPECT_STREQ(metadata.name().GetString(3).c_str(), "cr-json_name");
-  EXPECT_STREQ(metadata.str_value().GetString(3).c_str(), "{key: value}");
+  EXPECT_STREQ(storage_->GetString(metadata[3].name()).c_str(), "cr-json_name");
+  EXPECT_STREQ(storage_->GetString(*metadata[3].str_value()).c_str(),
+               "{key: value}");
 }
 
 TEST_F(ProtoTraceParserTest, AndroidPackagesList) {
@@ -2717,19 +2742,19 @@
   const auto& package_list = context_.storage->package_list_table();
   ASSERT_EQ(package_list.row_count(), 2u);
 
-  EXPECT_STREQ(storage_->GetString(package_list.package_name()[0]).c_str(),
+  EXPECT_STREQ(storage_->GetString(package_list[0].package_name()).c_str(),
                "com.test.app");
-  EXPECT_EQ(package_list.uid()[0], 1000u);
-  EXPECT_EQ(package_list.debuggable()[0], false);
-  EXPECT_EQ(package_list.profileable_from_shell()[0], true);
-  EXPECT_EQ(package_list.version_code()[0], 42);
+  EXPECT_EQ(package_list[0].uid(), 1000u);
+  EXPECT_EQ(package_list[0].debuggable(), false);
+  EXPECT_EQ(package_list[0].profileable_from_shell(), true);
+  EXPECT_EQ(package_list[0].version_code(), 42);
 
-  EXPECT_STREQ(storage_->GetString(package_list.package_name()[1]).c_str(),
+  EXPECT_STREQ(storage_->GetString(package_list[1].package_name()).c_str(),
                "com.test.app2");
-  EXPECT_EQ(package_list.uid()[1], 1001u);
-  EXPECT_EQ(package_list.debuggable()[1], false);
-  EXPECT_EQ(package_list.profileable_from_shell()[1], false);
-  EXPECT_EQ(package_list.version_code()[1], 43);
+  EXPECT_EQ(package_list[1].uid(), 1001u);
+  EXPECT_EQ(package_list[1].debuggable(), false);
+  EXPECT_EQ(package_list[1].profileable_from_shell(), false);
+  EXPECT_EQ(package_list[1].version_code(), 43);
 }
 
 TEST_F(ProtoTraceParserTest, AndroidPackagesListDuplicate) {
@@ -2770,12 +2795,12 @@
   const auto& package_list = context_.storage->package_list_table();
   ASSERT_EQ(package_list.row_count(), 1u);
 
-  EXPECT_STREQ(storage_->GetString(package_list.package_name()[0]).c_str(),
+  EXPECT_STREQ(storage_->GetString(package_list[0].package_name()).c_str(),
                "com.test.app");
-  EXPECT_EQ(package_list.uid()[0], 1000u);
-  EXPECT_EQ(package_list.debuggable()[0], false);
-  EXPECT_EQ(package_list.profileable_from_shell()[0], true);
-  EXPECT_EQ(package_list.version_code()[0], 42);
+  EXPECT_EQ(package_list[0].uid(), 1000u);
+  EXPECT_EQ(package_list[0].debuggable(), false);
+  EXPECT_EQ(package_list[0].profileable_from_shell(), true);
+  EXPECT_EQ(package_list[0].version_code(), 42);
 }
 
 TEST_F(ProtoTraceParserTest, ParseCPUProfileSamplesIntoTable) {
@@ -2792,29 +2817,29 @@
 
     auto* interned_data = packet->set_interned_data();
 
-    auto mapping = interned_data->add_mappings();
+    auto* mapping = interned_data->add_mappings();
     mapping->set_iid(1);
     mapping->set_build_id(1);
 
-    auto build_id = interned_data->add_build_ids();
+    auto* build_id = interned_data->add_build_ids();
     build_id->set_iid(1);
     build_id->set_str("3BBCFBD372448A727265C3E7C4D954F91");
 
-    auto frame = interned_data->add_frames();
+    auto* frame = interned_data->add_frames();
     frame->set_iid(1);
     frame->set_rel_pc(0x42);
     frame->set_mapping_id(1);
 
-    auto frame2 = interned_data->add_frames();
+    auto* frame2 = interned_data->add_frames();
     frame2->set_iid(2);
     frame2->set_rel_pc(0x4242);
     frame2->set_mapping_id(1);
 
-    auto callstack = interned_data->add_callstacks();
+    auto* callstack = interned_data->add_callstacks();
     callstack->set_iid(1);
     callstack->add_frame_ids(1);
 
-    auto callstack2 = interned_data->add_callstacks();
+    auto* callstack2 = interned_data->add_callstacks();
     callstack2->set_iid(42);
     callstack2->add_frame_ids(2);
   }
@@ -2851,25 +2876,25 @@
   const auto& samples = storage_->cpu_profile_stack_sample_table();
   EXPECT_EQ(samples.row_count(), 3u);
 
-  EXPECT_EQ(samples.ts()[0], 11000);
-  EXPECT_EQ(samples.callsite_id()[0], CallsiteId{0});
-  EXPECT_EQ(samples.utid()[0], 1u);
-  EXPECT_EQ(samples.process_priority()[0], 20);
+  EXPECT_EQ(samples[0].ts(), 11000);
+  EXPECT_EQ(samples[0].callsite_id(), CallsiteId{0});
+  EXPECT_EQ(samples[0].utid(), 1u);
+  EXPECT_EQ(samples[0].process_priority(), 20);
 
-  EXPECT_EQ(samples.ts()[1], 26000);
-  EXPECT_EQ(samples.callsite_id()[1], CallsiteId{1});
-  EXPECT_EQ(samples.utid()[1], 1u);
-  EXPECT_EQ(samples.process_priority()[1], 20);
+  EXPECT_EQ(samples[1].ts(), 26000);
+  EXPECT_EQ(samples[1].callsite_id(), CallsiteId{1});
+  EXPECT_EQ(samples[1].utid(), 1u);
+  EXPECT_EQ(samples[1].process_priority(), 20);
 
-  EXPECT_EQ(samples.ts()[2], 68000);
-  EXPECT_EQ(samples.callsite_id()[2], CallsiteId{0});
-  EXPECT_EQ(samples.utid()[2], 1u);
-  EXPECT_EQ(samples.process_priority()[2], 30);
+  EXPECT_EQ(samples[2].ts(), 68000);
+  EXPECT_EQ(samples[2].callsite_id(), CallsiteId{0});
+  EXPECT_EQ(samples[2].utid(), 1u);
+  EXPECT_EQ(samples[2].process_priority(), 30);
 
   // Breakpad build_ids should not be modified/mangled.
   ASSERT_STREQ(
       context_.storage
-          ->GetString(storage_->stack_profile_mapping_table().build_id()[0])
+          ->GetString(storage_->stack_profile_mapping_table()[0].build_id())
           .c_str(),
       "3BBCFBD372448A727265C3E7C4D954F91");
 }
@@ -2902,20 +2927,20 @@
 
     auto* interned_data = packet->set_interned_data();
 
-    auto mapping = interned_data->add_mappings();
+    auto* mapping = interned_data->add_mappings();
     mapping->set_iid(1);
     mapping->set_build_id(1);
 
-    auto build_id = interned_data->add_build_ids();
+    auto* build_id = interned_data->add_build_ids();
     build_id->set_iid(1);
     build_id->set_str("3BBCFBD372448A727265C3E7C4D954F91");
 
-    auto frame = interned_data->add_frames();
+    auto* frame = interned_data->add_frames();
     frame->set_iid(1);
     frame->set_rel_pc(0x42);
     frame->set_mapping_id(1);
 
-    auto callstack = interned_data->add_callstacks();
+    auto* callstack = interned_data->add_callstacks();
     callstack->set_iid(1);
     callstack->add_frame_ids(1);
   }
@@ -2938,9 +2963,9 @@
   EXPECT_EQ(samples.row_count(), 1u);
 
   // Should have been translated to boottime, i.e. 10015 us absolute.
-  EXPECT_EQ(samples.ts()[0], 10015000);
-  EXPECT_EQ(samples.callsite_id()[0], CallsiteId{0});
-  EXPECT_EQ(samples.utid()[0], 1u);
+  EXPECT_EQ(samples[0].ts(), 10015000);
+  EXPECT_EQ(samples[0].callsite_id(), CallsiteId{0});
+  EXPECT_EQ(samples[0].utid(), 1u);
 }
 
 TEST_F(ProtoTraceParserTest, ConfigUuid) {
@@ -3005,5 +3030,4 @@
 }
 
 }  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/track_event_parser.cc b/src/trace_processor/importers/proto/track_event_parser.cc
index b9509a0..01b5313 100644
--- a/src/trace_processor/importers/proto/track_event_parser.cc
+++ b/src/trace_processor/importers/proto/track_event_parser.cc
@@ -16,41 +16,51 @@
 
 #include "src/trace_processor/importers/proto/track_event_parser.h"
 
-#include <iostream>
+#include <cstddef>
+#include <cstdint>
 #include <optional>
 #include <string>
+#include <utility>
+#include <vector>
 
 #include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
-#include "perfetto/ext/base/base64.h"
+#include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/string_writer.h"
+#include "perfetto/protozero/field.h"
+#include "perfetto/protozero/proto_decoder.h"
+#include "perfetto/public/compiler.h"
+#include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/status.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
 #include "src/trace_processor/importers/common/args_translation_table.h"
 #include "src/trace_processor/importers/common/cpu_tracker.h"
 #include "src/trace_processor/importers/common/event_tracker.h"
 #include "src/trace_processor/importers/common/flow_tracker.h"
+#include "src/trace_processor/importers/common/parser_types.h"
 #include "src/trace_processor/importers/common/process_track_translation_table.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
 #include "src/trace_processor/importers/common/virtual_memory_mapping.h"
-#include "src/trace_processor/importers/json/json_utils.h"
 #include "src/trace_processor/importers/proto/args_parser.h"
 #include "src/trace_processor/importers/proto/packet_analyzer.h"
-#include "src/trace_processor/importers/proto/profile_packet_utils.h"
 #include "src/trace_processor/importers/proto/stack_profile_sequence_state.h"
 #include "src/trace_processor/importers/proto/track_event_tracker.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/slice_tables_py.h"
+#include "src/trace_processor/types/variadic.h"
 #include "src/trace_processor/util/debug_annotation_parser.h"
 #include "src/trace_processor/util/proto_to_args_parser.h"
 #include "src/trace_processor/util/status_macros.h"
 
 #include "protos/perfetto/common/android_log_constants.pbzero.h"
-#include "protos/perfetto/trace/extension_descriptor.pbzero.h"
 #include "protos/perfetto/trace/interned_data/interned_data.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_active_processes.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_compositor_scheduler_state.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_histogram_sample.pbzero.h"
-#include "protos/perfetto/trace/track_event/chrome_legacy_ipc.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_process_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/chrome_thread_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/counter_descriptor.pbzero.h"
@@ -63,8 +73,7 @@
 #include "protos/perfetto/trace/track_event/track_descriptor.pbzero.h"
 #include "protos/perfetto/trace/track_event/track_event.pbzero.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 namespace {
 using BoundInserter = ArgsTracker::BoundInserter;
@@ -90,7 +99,7 @@
 }
 
 std::optional<base::Status> MaybeParseUnsymbolizedSourceLocation(
-    std::string prefix,
+    const std::string& prefix,
     const protozero::Field& field,
     util::ProtoToArgsParser::Delegate& delegate) {
   auto* decoder = delegate.GetInternedMessage(
@@ -103,9 +112,9 @@
   }
   // Interned mapping_id loses it's meaning when the sequence ends. So we need
   // to get an id from stack_profile_mapping table.
-  auto mapping = delegate.seq_state()
-                     ->GetCustomState<StackProfileSequenceState>()
-                     ->FindOrInsertMapping(decoder->mapping_id());
+  auto* mapping = delegate.seq_state()
+                      ->GetCustomState<StackProfileSequenceState>()
+                      ->FindOrInsertMapping(decoder->mapping_id());
   if (!mapping) {
     return std::nullopt;
   }
@@ -118,7 +127,7 @@
 }
 
 std::optional<base::Status> MaybeParseSourceLocation(
-    std::string prefix,
+    const std::string& prefix,
     const protozero::Field& field,
     util::ProtoToArgsParser::Delegate& delegate) {
   auto* decoder = delegate.GetInternedMessage(
@@ -137,7 +146,6 @@
     delegate.AddInteger(util::ProtoToArgsParser::Key(prefix + ".line_number"),
                         decoder->line_number());
   }
-
   return base::OkStatus();
 }
 
@@ -181,7 +189,7 @@
         ts_(ts),
         event_data_(event_data),
         sequence_state_(event_data->trace_packet_data.sequence_state.get()),
-        blob_(std::move(blob)),
+        blob_(blob),
         event_(blob_),
         legacy_event_(event_.legacy_event()),
         defaults_(event_data->trace_packet_data.sequence_state
@@ -190,11 +198,11 @@
         thread_instruction_count_(event_data->thread_instruction_count),
         packet_sequence_id_(packet_sequence_id) {}
 
-  util::Status Import() {
+  base::Status Import() {
     // TODO(eseckler): This legacy event field will eventually be replaced by
     // fields in TrackEvent itself.
     if (PERFETTO_UNLIKELY(!event_.type() && !legacy_event_.has_phase()))
-      return util::ErrStatus("TrackEvent without type or phase");
+      return base::ErrStatus("TrackEvent without type or phase");
 
     category_id_ = ParseTrackEventCategory();
     name_id_ = ParseTrackEventName();
@@ -213,7 +221,7 @@
     // CounterDescriptor instead). All they have is a |{double_,}counter_value|.
     if (event_.type() == TrackEvent::TYPE_COUNTER) {
       ParseCounterEvent();
-      return util::OkStatus();
+      return base::OkStatus();
     }
 
     // If we have legacy thread time / instruction count fields, also parse them
@@ -341,7 +349,7 @@
     return kNullStringId;
   }
 
-  util::Status ParseTrackAssociation() {
+  base::Status ParseTrackAssociation() {
     TrackTracker* track_tracker = context_->track_tracker.get();
     ProcessTracker* procs = context_->process_tracker.get();
 
@@ -373,35 +381,34 @@
       }
       track_id_ = *opt_track_id;
 
-      auto thread_track_row =
-          storage_->thread_track_table().id().IndexOf(track_id_);
-      if (thread_track_row) {
-        utid_ = storage_->thread_track_table().utid()[*thread_track_row];
-        upid_ = storage_->thread_table().upid()[*utid_];
+      auto tt_rr = storage_->thread_track_table().FindById(track_id_);
+      if (tt_rr) {
+        utid_ = tt_rr->utid();
+        upid_ = storage_->thread_table()[*utid_].upid();
       } else {
-        auto process_track_row =
-            storage_->process_track_table().id().IndexOf(track_id_);
-        if (process_track_row) {
-          upid_ = storage_->process_track_table().upid()[*process_track_row];
+        auto pt_rr = storage_->process_track_table().FindById(track_id_);
+        if (pt_rr) {
+          upid_ = pt_rr->upid();
           if (sequence_state_->pid_and_tid_valid()) {
-            uint32_t pid = static_cast<uint32_t>(sequence_state_->pid());
-            uint32_t tid = static_cast<uint32_t>(sequence_state_->tid());
+            auto pid = static_cast<uint32_t>(sequence_state_->pid());
+            auto tid = static_cast<uint32_t>(sequence_state_->tid());
             UniqueTid utid_candidate = procs->UpdateThread(tid, pid);
-            if (storage_->thread_table().upid()[utid_candidate] == upid_)
+            if (storage_->thread_table()[utid_candidate].upid() == upid_) {
               legacy_passthrough_utid_ = utid_candidate;
+            }
           }
         } else {
           auto* tracks = context_->storage->mutable_track_table();
-          auto track_index = tracks->id().IndexOf(track_id_);
-          if (track_index) {
-            const StringPool::Id& id = tracks->name()[*track_index];
-            if (id.is_null())
-              tracks->mutable_name()->Set(*track_index, name_id_);
+          auto t_rr = tracks->FindById(track_id_);
+          if (t_rr) {
+            StringPool::Id id = t_rr->name();
+            if (id.is_null()) {
+              t_rr->set_name(name_id_);
+            }
           }
-
           if (sequence_state_->pid_and_tid_valid()) {
-            uint32_t pid = static_cast<uint32_t>(sequence_state_->pid());
-            uint32_t tid = static_cast<uint32_t>(sequence_state_->tid());
+            auto pid = static_cast<uint32_t>(sequence_state_->pid());
+            auto tid = static_cast<uint32_t>(sequence_state_->tid());
             legacy_passthrough_utid_ = procs->UpdateThread(tid, pid);
           }
         }
@@ -426,8 +433,8 @@
           legacy_event_.has_tid_override() && pid_tid_state_valid;
 
       if (fallback_to_legacy_pid_tid_tracks) {
-        uint32_t pid = static_cast<uint32_t>(sequence_state_->pid());
-        uint32_t tid = static_cast<uint32_t>(sequence_state_->tid());
+        auto pid = static_cast<uint32_t>(sequence_state_->pid());
+        auto tid = static_cast<uint32_t>(sequence_state_->tid());
         if (legacy_event_.has_pid_override()) {
           pid = static_cast<uint32_t>(legacy_event_.pid_override());
           tid = static_cast<uint32_t>(-1);
@@ -436,7 +443,7 @@
           tid = static_cast<uint32_t>(legacy_event_.tid_override());
 
         utid_ = procs->UpdateThread(tid, pid);
-        upid_ = storage_->thread_table().upid()[*utid_];
+        upid_ = storage_->thread_table()[*utid_].upid();
         track_id_ = track_tracker->InternThreadTrack(*utid_);
       } else {
         track_id_ = track_event_tracker_->GetOrCreateDefaultDescriptorTrack();
@@ -444,7 +451,7 @@
     }
 
     if (!legacy_event_.has_phase())
-      return util::OkStatus();
+      return base::OkStatus();
 
     // Legacy phases may imply a different track than the one specified by
     // the fallback (or default track uuid) above.
@@ -465,14 +472,14 @@
           source_id = static_cast<int64_t>(legacy_event_.global_id());
         } else if (legacy_event_.has_local_id()) {
           if (!upid_) {
-            return util::ErrStatus(
+            return base::ErrStatus(
                 "TrackEvent with local_id without process association");
           }
 
           source_id = static_cast<int64_t>(legacy_event_.local_id());
           source_id_is_process_scoped = true;
         } else {
-          return util::ErrStatus("Async LegacyEvent without ID");
+          return base::ErrStatus("Async LegacyEvent without ID");
         }
 
         // Catapult treats nestable async events of different categories with
@@ -504,7 +511,7 @@
             // Thread-scoped legacy instant events already have the right
             // track based on the tid/pid of the sequence.
             if (!utid_) {
-              return util::ErrStatus(
+              return base::ErrStatus(
                   "Thread-scoped instant event without thread association");
             }
             break;
@@ -516,7 +523,7 @@
             break;
           case LegacyEvent::SCOPE_PROCESS:
             if (!upid_) {
-              return util::ErrStatus(
+              return base::ErrStatus(
                   "Process-scoped instant event without process association");
             }
 
@@ -533,7 +540,7 @@
         break;
     }
 
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   int32_t ParsePhaseOrType() {
@@ -556,7 +563,7 @@
   void ParseCounterEvent() {
     // Tokenizer ensures that TYPE_COUNTER events are associated with counter
     // tracks and have values.
-    PERFETTO_DCHECK(storage_->counter_track_table().id().IndexOf(track_id_));
+    PERFETTO_DCHECK(storage_->counter_track_table().FindById(track_id_));
     PERFETTO_DCHECK(event_.has_counter_value() ||
                     event_.has_double_counter_value());
 
@@ -630,16 +637,14 @@
 
     std::optional<TrackId> track_id = track_event_tracker_->GetDescriptorTrack(
         *track_uuid_it, kNullStringId, packet_sequence_id_);
-    std::optional<uint32_t> counter_row =
-        storage_->counter_track_table().id().IndexOf(*track_id);
+    auto counter_row = storage_->counter_track_table().FindById(*track_id);
 
     double value = event_data_->extra_counter_values[index];
     context_->event_tracker->PushCounter(ts_, value, *track_id);
 
     // Also import thread_time and thread_instruction_count counters into
     // slice columns to simplify JSON export.
-    StringId counter_name =
-        storage_->counter_track_table().name()[*counter_row];
+    StringId counter_name = counter_row->name();
     if (counter_name == parser_->counter_name_thread_time_id_) {
       thread_timestamp_ = static_cast<int64_t>(value);
     } else if (counter_name ==
@@ -648,9 +653,9 @@
     }
   }
 
-  util::Status ParseThreadBeginEvent() {
+  base::Status ParseThreadBeginEvent() {
     if (!utid_) {
-      return util::ErrStatus(
+      return base::ErrStatus(
           "TrackEvent with phase B without thread association");
     }
 
@@ -662,12 +667,12 @@
     if (opt_slice_id.has_value()) {
       MaybeParseFlowEvents(opt_slice_id.value());
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseThreadEndEvent() {
+  base::Status ParseThreadEndEvent() {
     if (!utid_) {
-      return util::ErrStatus(
+      return base::ErrStatus(
           "TrackEvent with phase E without thread association");
     }
     auto opt_slice_id = context_->slice_tracker->End(
@@ -699,18 +704,18 @@
       slice_ref.set_thread_instruction_delta(
           *event_data_->thread_instruction_count - *tic);
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseThreadCompleteEvent() {
+  base::Status ParseThreadCompleteEvent() {
     if (!utid_) {
-      return util::ErrStatus(
+      return base::ErrStatus(
           "TrackEvent with phase X without thread association");
     }
 
     auto duration_ns = legacy_event_.duration_us() * 1000;
     if (duration_ns < 0)
-      return util::ErrStatus("TrackEvent with phase X with negative duration");
+      return base::ErrStatus("TrackEvent with phase X with negative duration");
 
     auto* thread_slices = storage_->mutable_slice_table();
     tables::SliceTable::Row row = MakeThreadSliceRow();
@@ -722,13 +727,13 @@
       row.thread_instruction_delta = legacy_event_.thread_instruction_delta();
     }
     auto opt_slice_id = context_->slice_tracker->ScopedTyped(
-        thread_slices, std::move(row),
+        thread_slices, row,
         [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); });
 
     if (opt_slice_id.has_value()) {
       MaybeParseFlowEvents(opt_slice_id.value());
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   std::optional<uint64_t> GetLegacyEventId() {
@@ -742,11 +747,11 @@
     return std::nullopt;
   }
 
-  util::Status ParseFlowEventV1(char phase) {
+  base::Status ParseFlowEventV1(char phase) {
     auto opt_source_id = GetLegacyEventId();
     if (!opt_source_id) {
       storage_->IncrementStats(stats::flow_invalid_id);
-      return util::ErrStatus("Invalid id for flow event v1");
+      return base::ErrStatus("Invalid id for flow event v1");
     }
     FlowId flow_id = context_->flow_tracker->GetFlowIdForV1Event(
         opt_source_id.value(), category_id_, name_id_);
@@ -763,7 +768,7 @@
                                     /* close_flow = */ false);
         break;
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   void MaybeParseTrackEventFlows(SliceId slice_id) {
@@ -828,7 +833,7 @@
     MaybeParseTrackEventFlows(slice_id);
   }
 
-  util::Status ParseThreadInstantEvent(char phase) {
+  base::Status ParseThreadInstantEvent(char phase) {
     // Handle instant events as slices with zero duration, so that they end
     // up nested underneath their parent slices.
     int64_t duration_ns = 0;
@@ -862,13 +867,13 @@
           std::move(args_inserter));
     }
     if (!opt_slice_id.has_value()) {
-      return util::OkStatus();
+      return base::OkStatus();
     }
     MaybeParseFlowEvents(opt_slice_id.value());
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseAsyncBeginEvent(char phase) {
+  base::Status ParseAsyncBeginEvent(char phase) {
     auto args_inserter = [this, phase](BoundInserter* inserter) {
       ParseTrackEventArgs(inserter);
 
@@ -884,7 +889,7 @@
     auto opt_slice_id = context_->slice_tracker->Begin(
         ts_, track_id_, category_id_, name_id_, args_inserter);
     if (!opt_slice_id.has_value()) {
-      return util::OkStatus();
+      return base::OkStatus();
     }
     MaybeParseFlowEvents(opt_slice_id.value());
     // For the time being, we only create vtrack slice rows if we need to
@@ -899,10 +904,10 @@
                                           kPendingThreadDuration, tic,
                                           kPendingThreadInstructionDelta);
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseAsyncEndEvent() {
+  base::Status ParseAsyncEndEvent() {
     auto opt_slice_id = context_->slice_tracker->End(
         ts_, track_id_, category_id_, name_id_,
         [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); });
@@ -916,10 +921,10 @@
       int64_t tic = event_data_->thread_instruction_count.value_or(0);
       vtrack_slices->UpdateThreadDeltasForSliceId(*opt_slice_id, tts, tic);
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseAsyncStepEvent(char phase) {
+  base::Status ParseAsyncStepEvent(char phase) {
     // Parse step events as instant events. Reconstructing the begin/end times
     // of the child slice would be too complicated, see b/178540838. For JSON
     // export, we still record the original step's phase in an arg.
@@ -937,10 +942,10 @@
         });
     // Step events don't support thread timestamps, so no need to add a row to
     // virtual_track_slices.
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseAsyncInstantEvent() {
+  base::Status ParseAsyncInstantEvent() {
     // Handle instant events as slices with zero duration, so that they end
     // up nested underneath their parent slices.
     int64_t duration_ns = 0;
@@ -949,7 +954,7 @@
         ts_, track_id_, category_id_, name_id_, duration_ns,
         [this](BoundInserter* inserter) { ParseTrackEventArgs(inserter); });
     if (!opt_slice_id.has_value()) {
-      return util::OkStatus();
+      return base::OkStatus();
     }
     MaybeParseFlowEvents(opt_slice_id.value());
     if (legacy_event_.use_async_tts()) {
@@ -961,68 +966,68 @@
       vtrack_slices->AddVirtualTrackSlice(opt_slice_id.value(), tts,
                                           duration_ns, tic, tidelta);
     }
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseMetadataEvent() {
+  base::Status ParseMetadataEvent() {
     ProcessTracker* procs = context_->process_tracker.get();
 
     if (name_id_ == kNullStringId)
-      return util::ErrStatus("Metadata event without name");
+      return base::ErrStatus("Metadata event without name");
 
     // Parse process and thread names from correspondingly named events.
     NullTermStringView event_name = storage_->GetString(name_id_);
     PERFETTO_DCHECK(event_name.data());
-    if (strcmp(event_name.c_str(), "thread_name") == 0) {
+    if (event_name == "thread_name") {
       if (!utid_) {
-        return util::ErrStatus(
+        return base::ErrStatus(
             "thread_name metadata event without thread association");
       }
 
       auto it = event_.debug_annotations();
       if (!it) {
-        return util::ErrStatus(
+        return base::ErrStatus(
             "thread_name metadata event without debug annotations");
       }
       protos::pbzero::DebugAnnotation::Decoder annotation(*it);
       auto thread_name = annotation.string_value();
       if (!thread_name.size)
-        return util::OkStatus();
+        return base::OkStatus();
       auto thread_name_id = storage_->InternString(thread_name);
       procs->UpdateThreadNameByUtid(
           *utid_, thread_name_id,
           ThreadNamePriority::kTrackDescriptorThreadType);
-      return util::OkStatus();
+      return base::OkStatus();
     }
-    if (strcmp(event_name.c_str(), "process_name") == 0) {
+    if (event_name == "process_name") {
       if (!upid_) {
-        return util::ErrStatus(
+        return base::ErrStatus(
             "process_name metadata event without process association");
       }
 
       auto it = event_.debug_annotations();
       if (!it) {
-        return util::ErrStatus(
+        return base::ErrStatus(
             "process_name metadata event without debug annotations");
       }
       protos::pbzero::DebugAnnotation::Decoder annotation(*it);
       auto process_name = annotation.string_value();
       if (!process_name.size)
-        return util::OkStatus();
+        return base::OkStatus();
       auto process_name_id =
           storage_->InternString(base::StringView(process_name));
       // Don't override system-provided names.
       procs->SetProcessNameIfUnset(*upid_, process_name_id);
-      return util::OkStatus();
+      return base::OkStatus();
     }
     // Other metadata events are proxied via the raw table for JSON export.
     ParseLegacyEventAsRawEvent();
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseLegacyEventAsRawEvent() {
+  base::Status ParseLegacyEventAsRawEvent() {
     if (!utid_)
-      return util::ErrStatus("raw legacy event without thread association");
+      return base::ErrStatus("raw legacy event without thread association");
 
     auto ucpu = context_->cpu_tracker->GetOrCreateCpu(0);
     RawId id =
@@ -1099,11 +1104,11 @@
     // instant events into the slice table.
 
     ParseTrackEventArgs(&inserter);
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   void ParseTrackEventArgs(BoundInserter* inserter) {
-    auto log_errors = [this](util::Status status) {
+    auto log_errors = [this](const base::Status& status) {
       if (status.ok())
         return;
       // Log error but continue parsing the other args.
@@ -1159,18 +1164,18 @@
     }
   }
 
-  util::Status ParseTaskExecutionArgs(ConstBytes task_execution,
+  base::Status ParseTaskExecutionArgs(ConstBytes task_execution,
                                       BoundInserter* inserter) {
     protos::pbzero::TaskExecution::Decoder task(task_execution);
     uint64_t iid = task.posted_from_iid();
     if (!iid)
-      return util::ErrStatus("TaskExecution with invalid posted_from_iid");
+      return base::ErrStatus("TaskExecution with invalid posted_from_iid");
 
     auto* decoder = sequence_state_->LookupInternedMessage<
         protos::pbzero::InternedData::kSourceLocationsFieldNumber,
         protos::pbzero::SourceLocation>(iid);
     if (!decoder)
-      return util::ErrStatus("TaskExecution with invalid posted_from_iid");
+      return base::ErrStatus("TaskExecution with invalid posted_from_iid");
 
     StringId file_name_id = kNullStringId;
     StringId function_name_id = kNullStringId;
@@ -1187,18 +1192,18 @@
                      Variadic::String(function_name_id));
     inserter->AddArg(parser_->task_line_number_args_key_id_,
                      Variadic::UnsignedInteger(line_number));
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status AddSourceLocationArgs(uint64_t iid, BoundInserter* inserter) {
+  base::Status AddSourceLocationArgs(uint64_t iid, BoundInserter* inserter) {
     if (!iid)
-      return util::ErrStatus("SourceLocation with invalid iid");
+      return base::ErrStatus("SourceLocation with invalid iid");
 
     auto* decoder = sequence_state_->LookupInternedMessage<
         protos::pbzero::InternedData::kSourceLocationsFieldNumber,
         protos::pbzero::SourceLocation>(iid);
     if (!decoder)
-      return util::ErrStatus("SourceLocation with invalid iid");
+      return base::ErrStatus("SourceLocation with invalid iid");
 
     StringId file_name_id = kNullStringId;
     StringId function_name_id = kNullStringId;
@@ -1215,12 +1220,12 @@
                      Variadic::String(function_name_id));
     inserter->AddArg(parser_->source_location_line_number_key_id_,
                      Variadic::UnsignedInteger(line_number));
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseLogMessage(ConstBytes blob, BoundInserter* inserter) {
+  base::Status ParseLogMessage(ConstBytes blob, BoundInserter* inserter) {
     if (!utid_)
-      return util::ErrStatus("LogMessage without thread association");
+      return base::ErrStatus("LogMessage without thread association");
 
     protos::pbzero::LogMessage::Decoder message(blob);
 
@@ -1228,7 +1233,7 @@
         protos::pbzero::InternedData::kLogMessageBodyFieldNumber,
         protos::pbzero::LogMessageBody>(message.body_iid());
     if (!body_decoder)
-      return util::ErrStatus("LogMessage with invalid body_iid");
+      return base::ErrStatus("LogMessage with invalid body_iid");
 
     const StringId log_message_id =
         storage_->InternString(body_decoder->body());
@@ -1241,7 +1246,7 @@
           protos::pbzero::InternedData::kSourceLocationsFieldNumber,
           protos::pbzero::SourceLocation>(message.source_location_iid());
       if (!source_location_decoder)
-        return util::ErrStatus("LogMessage with invalid source_location_iid");
+        return base::ErrStatus("LogMessage with invalid source_location_iid");
       const std::string source_location =
           source_location_decoder->file_name().ToStdString() + ":" +
           std::to_string(source_location_decoder->line_number());
@@ -1276,16 +1281,16 @@
          /*priority*/ static_cast<uint32_t>(priority),
          /*tag_id*/ source_location_id, log_message_id});
 
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
-  util::Status ParseHistogramName(ConstBytes blob, BoundInserter* inserter) {
+  base::Status ParseHistogramName(ConstBytes blob, BoundInserter* inserter) {
     protos::pbzero::ChromeHistogramSample::Decoder sample(blob);
     if (!sample.has_name_iid())
-      return util::OkStatus();
+      return base::OkStatus();
 
     if (sample.has_name()) {
-      return util::ErrStatus(
+      return base::ErrStatus(
           "name is already set for ChromeHistogramSample: only one of name and "
           "name_iid can be set.");
     }
@@ -1294,11 +1299,11 @@
         protos::pbzero::InternedData::kHistogramNamesFieldNumber,
         protos::pbzero::HistogramName>(sample.name_iid());
     if (!decoder)
-      return util::ErrStatus("HistogramName with invalid name_iid");
+      return base::ErrStatus("HistogramName with invalid name_iid");
 
     inserter->AddArg(parser_->histogram_name_key_id_,
                      Variadic::String(storage_->InternString(decoder->name())));
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   tables::SliceTable::Row MakeThreadSliceRow() {
@@ -1347,7 +1352,7 @@
 
 TrackEventParser::TrackEventParser(TraceProcessorContext* context,
                                    TrackEventTracker* track_event_tracker)
-    : args_parser_(*context->descriptor_pool_.get()),
+    : args_parser_(*context->descriptor_pool_),
       context_(context),
       track_event_tracker_(track_event_tracker),
       counter_name_thread_time_id_(
@@ -1535,7 +1540,7 @@
         decoder.has_name() ? decoder.name() : decoder.static_name());
     const StringId name_id =
         context_->process_track_translation_table->TranslateName(raw_name_id);
-    tracks->mutable_name()->Set(*tracks->id().IndexOf(track_id), name_id);
+    tracks->FindById(track_id)->set_name(name_id);
   }
 }
 
@@ -1645,30 +1650,26 @@
   if (unit_index >= counter_unit_ids_.size())
     unit_index = CounterDescriptor::UNIT_UNSPECIFIED;
 
-  auto opt_track_idx = counter_tracks->id().IndexOf(track_id);
-  if (!opt_track_idx) {
+  auto opt_rr = counter_tracks->FindById(track_id);
+  if (!opt_rr) {
     context_->storage->IncrementStats(stats::track_event_parser_errors);
     return;
   }
 
-  auto track_idx = *opt_track_idx;
-
+  auto& rr = *opt_rr;
   switch (decoder.type()) {
     case CounterDescriptor::COUNTER_UNSPECIFIED:
       break;
     case CounterDescriptor::COUNTER_THREAD_TIME_NS:
       unit_index = CounterDescriptor::UNIT_TIME_NS;
-      counter_tracks->mutable_name()->Set(track_idx,
-                                          counter_name_thread_time_id_);
+      rr.set_name(counter_name_thread_time_id_);
       break;
     case CounterDescriptor::COUNTER_THREAD_INSTRUCTION_COUNT:
       unit_index = CounterDescriptor::UNIT_COUNT;
-      counter_tracks->mutable_name()->Set(
-          track_idx, counter_name_thread_instruction_count_id_);
+      rr.set_name(counter_name_thread_instruction_count_id_);
       break;
   }
-
-  counter_tracks->mutable_unit()->Set(track_idx, counter_unit_ids_[unit_index]);
+  rr.set_unit(counter_unit_ids_[unit_index]);
 }
 
 void TrackEventParser::ParseTrackEvent(int64_t ts,
@@ -1686,9 +1687,8 @@
         stats::track_event_dropped_packets_outside_of_range_of_interest);
     return;
   }
-  util::Status status =
-      EventImporter(this, ts, event_data, std::move(blob), packet_sequence_id)
-          .Import();
+  base::Status status =
+      EventImporter(this, ts, event_data, blob, packet_sequence_id).Import();
   if (!status.ok()) {
     context_->storage->IncrementStats(stats::track_event_parser_errors);
     PERFETTO_DLOG("ParseTrackEvent error: %s", status.c_message());
@@ -1706,5 +1706,4 @@
   active_chrome_processes_tracker_.NotifyEndOfFile();
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/track_event_tracker.cc b/src/trace_processor/importers/proto/track_event_tracker.cc
index e758b9d..a336b60 100644
--- a/src/trace_processor/importers/proto/track_event_tracker.cc
+++ b/src/trace_processor/importers/proto/track_event_tracker.cc
@@ -16,15 +16,29 @@
 
 #include "src/trace_processor/importers/proto/track_event_tracker.h"
 
+#include <algorithm>
+#include <cinttypes>
+#include <cstddef>
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <optional>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
 #include "src/trace_processor/importers/common/args_tracker.h"
-#include "src/trace_processor/importers/common/args_translation_table.h"
 #include "src/trace_processor/importers/common/process_track_translation_table.h"
 #include "src/trace_processor/importers/common/process_tracker.h"
 #include "src/trace_processor/importers/common/track_tracker.h"
+#include "src/trace_processor/storage/stats.h"
+#include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/tables/track_tables_py.h"
+#include "src/trace_processor/types/trace_processor_context.h"
+#include "src/trace_processor/types/variadic.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 TrackEventTracker::TrackEventTracker(TraceProcessorContext* context)
     : source_key_(context->storage->InternString("source")),
@@ -64,7 +78,6 @@
     context_->storage->IncrementStats(stats::track_event_tokenizer_errors);
     return;
   }
-
   it->second.min_timestamp = std::min(it->second.min_timestamp, timestamp);
 }
 
@@ -190,9 +203,10 @@
   // Update the name of the track if unset and the track is not the primary
   // track of a process/thread or a counter track.
   auto* tracks = context_->storage->mutable_track_table();
-  uint32_t row = *tracks->id().IndexOf(*track_id);
-  if (!tracks->name()[row].is_null())
+  auto rr = *tracks->FindById(*track_id);
+  if (!rr.name().is_null()) {
     return track_id;
+  }
 
   // Check reservation for track type.
   auto reservation_it = reserved_descriptor_tracks_.find(uuid);
@@ -202,9 +216,8 @@
       reservation_it->second.is_counter) {
     return track_id;
   }
-  const StringId track_name =
-      context_->process_track_translation_table->TranslateName(event_name);
-  tracks->mutable_name()->Set(row, track_name);
+  rr.set_name(
+      context_->process_track_translation_table->TranslateName(event_name));
   return track_id;
 }
 
@@ -385,7 +398,7 @@
     // seen in the recursion.
     std::unique_ptr<std::vector<uint64_t>> owned_descendent_uuids;
     if (!descendent_uuids) {
-      owned_descendent_uuids.reset(new std::vector<uint64_t>());
+      owned_descendent_uuids = std::make_unique<std::vector<uint64_t>>();
       descendent_uuids = owned_descendent_uuids.get();
     }
     descendent_uuids->push_back(uuid);
@@ -633,5 +646,4 @@
   return track;
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/importers/proto/vulkan_memory_tracker.h b/src/trace_processor/importers/proto/vulkan_memory_tracker.h
index dd44a89..cb770cc 100644
--- a/src/trace_processor/importers/proto/vulkan_memory_tracker.h
+++ b/src/trace_processor/importers/proto/vulkan_memory_tracker.h
@@ -17,6 +17,11 @@
 #ifndef SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_VULKAN_MEMORY_TRACKER_H_
 #define SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_VULKAN_MEMORY_TRACKER_H_
 
+#include <cstdint>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
 #include "src/trace_processor/importers/proto/packet_sequence_state_generation.h"
 #include "src/trace_processor/storage/trace_storage.h"
 #include "src/trace_processor/types/trace_processor_context.h"
@@ -24,13 +29,12 @@
 #include "protos/perfetto/trace/gpu/vulkan_memory_event.pbzero.h"
 #include "protos/perfetto/trace/profiling/profile_common.pbzero.h"
 
-namespace perfetto {
-namespace trace_processor {
-
-using protos::pbzero::VulkanMemoryEvent;
+namespace perfetto::trace_processor {
 
 class VulkanMemoryTracker {
  public:
+  using VulkanMemoryEvent = protos::pbzero::VulkanMemoryEvent;
+
   enum class DeviceCounterType {
     kAllocationCounter = 0,
     kBindCounter = 1,
@@ -76,7 +80,6 @@
   void SetupSourceAndTypeInternedStrings();
 };
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
 
 #endif  // SRC_TRACE_PROCESSOR_IMPORTERS_PROTO_VULKAN_MEMORY_TRACKER_H_
diff --git a/src/trace_processor/metrics/sql/android/BUILD.gn b/src/trace_processor/metrics/sql/android/BUILD.gn
index ea029b0..ba8666e 100644
--- a/src/trace_processor/metrics/sql/android/BUILD.gn
+++ b/src/trace_processor/metrics/sql/android/BUILD.gn
@@ -145,6 +145,6 @@
     "unsymbolized_frames.sql",
     "wattson_app_startup.sql",
     "wattson_rail_relations.sql",
-    "wattson_trace_estimate.sql",
+    "wattson_trace_rails.sql",
   ]
 }
diff --git a/src/trace_processor/metrics/sql/android/wattson_trace_estimate.sql b/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
similarity index 92%
rename from src/trace_processor/metrics/sql/android/wattson_trace_estimate.sql
rename to src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
index 865ad3d..ab035c0 100644
--- a/src/trace_processor/metrics/sql/android/wattson_trace_estimate.sql
+++ b/src/trace_processor/metrics/sql/android/wattson_trace_rails.sql
@@ -30,8 +30,8 @@
   'window_table', '_wattson_period_windows'
 );
 
-DROP VIEW IF EXISTS wattson_trace_estimate_output;
-CREATE PERFETTO VIEW wattson_trace_estimate_output AS
+DROP VIEW IF EXISTS wattson_trace_rails_output;
+CREATE PERFETTO VIEW wattson_trace_rails_output AS
 SELECT AndroidWattsonTimePeriodMetric(
   'metric_version', 2,
   'period_info', (
diff --git a/src/trace_processor/minimal_shell.cc b/src/trace_processor/minimal_shell.cc
new file mode 100644
index 0000000..0bbf6f7
--- /dev/null
+++ b/src/trace_processor/minimal_shell.cc
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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 <cinttypes>
+#include <cstdint>
+#include <cstdio>
+#include <memory>
+
+#include "perfetto/base/status.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "perfetto/trace_processor/read_trace.h"
+#include "perfetto/trace_processor/trace_processor.h"
+#include "src/trace_processor/util/status_macros.h"
+
+namespace perfetto::trace_processor {
+namespace {
+
+// This binary exists just for the purpose of debugging the binary size of
+// trace processor. To that end, we just run some basic trace processor
+// functions to ensure that the linker does not strip the TP symbols.
+base::Status MinimalMain(int, char**) {
+  std::unique_ptr<TraceProcessor> tp = TraceProcessor::CreateInstance({});
+  RETURN_IF_ERROR(tp->Parse(std::unique_ptr<uint8_t[]>(new uint8_t[0]), 0));
+  RETURN_IF_ERROR(tp->NotifyEndOfFile());
+
+  auto it = tp->ExecuteQuery("SELECT id FROM slice");
+  while (it.Next()) {
+    SqlValue value = it.Get(0);
+    fprintf(stderr, "%" PRId64, value.AsLong());
+  }
+  return it.Status();
+}
+
+}  // namespace
+}  // namespace perfetto::trace_processor
+
+int main(int argc, char** argv) {
+  auto status = perfetto::trace_processor::MinimalMain(argc, argv);
+  if (!status.ok()) {
+    fprintf(stderr, "%s\n", status.c_message());
+    return 1;
+  }
+  return 0;
+}
diff --git a/src/trace_processor/perfetto_sql/engine/BUILD.gn b/src/trace_processor/perfetto_sql/engine/BUILD.gn
index e297c2b..a721a51 100644
--- a/src/trace_processor/perfetto_sql/engine/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/engine/BUILD.gn
@@ -42,6 +42,7 @@
     "../../../base",
     "../../containers",
     "../../db",
+    "../../db/column",
     "../../perfetto_sql/intrinsics/functions:interface",
     "../../perfetto_sql/intrinsics/table_functions:interface",
     "../../sqlite",
diff --git a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
index 4302847..86bf583 100644
--- a/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
+++ b/src/trace_processor/perfetto_sql/engine/perfetto_sql_engine.cc
@@ -37,7 +37,10 @@
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/ext/base/string_view.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/db/runtime_table.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/perfetto_sql/engine/created_function.h"
@@ -709,8 +712,6 @@
     return base::ErrStatus("CREATE PERFETTO INDEX: Table '%s' not found",
                            index.table_name.c_str());
   }
-
-  std::vector<Order> obs;
   std::vector<uint32_t> col_idxs;
   for (const std::string& col_name : index.col_names) {
     const std::optional<uint32_t> opt_col = t->ColumnIdxFromName(col_name);
@@ -719,19 +720,10 @@
           "CREATE PERFETTO INDEX: Column '%s' not found in table '%s'",
           index.col_names.front().c_str(), index.table_name.c_str());
     }
-    Order o;
-    o.col_idx = *opt_col;
-    obs.push_back(o);
     col_idxs.push_back(*opt_col);
   }
-
-  Query q;
-  q.orders = obs;
-  RowMap sorted_rm = t->QueryToRowMap(q);
-
-  RETURN_IF_ERROR(t->SetIndex(index.name, std::move(col_idxs),
-                              std::move(sorted_rm).TakeAsIndexVector(),
-                              index.replace));
+  RETURN_IF_ERROR(
+      t->CreateIndex(index.name, std::move(col_idxs), index.replace));
   return base::OkStatus();
 }
 
@@ -742,7 +734,6 @@
     return base::ErrStatus("DROP PERFETTO INDEX: Table '%s' not found",
                            index.table_name.c_str());
   }
-
   RETURN_IF_ERROR(t->DropIndex(index.name));
   return base::OkStatus();
 }
diff --git a/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc b/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc
index f8ee59f..1127fd1 100644
--- a/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc
+++ b/src/trace_processor/perfetto_sql/engine/table_pointer_module.cc
@@ -172,18 +172,15 @@
       return sqlite::utils::SetError(c->pVtab, "Column name is not text");
     }
 
-    std::string_view tok(
-        reinterpret_cast<const char*>(sqlite3_value_text(argv[i])));
-    auto it = std::find_if(
-        c->table->columns().begin(), c->table->columns().end(),
-        [&tok](const ColumnLegacy& col) { return col.name() == tok; });
-    if (it == c->table->columns().end()) {
+    const char* tok =
+        reinterpret_cast<const char*>(sqlite3_value_text(argv[i]));
+    auto idx = c->table->ColumnIdxFromName(tok);
+    if (!idx) {
       base::StackString<128> err("column '%s' does not exist in table",
                                  sqlite3_value_text(argv[i]));
       return sqlite::utils::SetError(c->pVtab, err.c_str());
     }
-    c->bound_col_to_table_index[c->col_count++] =
-        static_cast<uint32_t>(std::distance(c->table->columns().begin(), it));
+    c->bound_col_to_table_index[c->col_count++] = *idx;
   }
   c->iterator = c->table->IterateRows();
   return SQLITE_OK;
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn b/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
index 53df4f7..863c2e6 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/BUILD.gn
@@ -76,6 +76,7 @@
     "../../../perfetto_sql/intrinsics/table_functions",
     "../../../sqlite",
     "../../../storage",
+    "../../../tables",
     "../../../types",
     "../../../util",
     "../../../util:profile_builder",
diff --git a/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc b/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
index efa7659..7898fb9 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.cc
@@ -16,16 +16,31 @@
 
 #include "src/trace_processor/perfetto_sql/intrinsics/functions/to_ftrace.h"
 
-#include "perfetto/base/compiler.h"
+#include <cstddef>
+#include <cstdint>
+#include <cstdlib>
+#include <functional>
+#include <optional>
+#include <vector>
+
+#include "perfetto/base/logging.h"
 #include "perfetto/base/status.h"
-#include "perfetto/ext/base/string_utils.h"
+#include "perfetto/ext/base/string_view.h"
+#include "perfetto/ext/base/string_writer.h"
+#include "perfetto/public/compiler.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
+#include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/importers/common/system_info_tracker.h"
 #include "src/trace_processor/importers/ftrace/ftrace_descriptors.h"
-#include "src/trace_processor/sqlite/sqlite_utils.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_type.h"
+#include "src/trace_processor/sqlite/bindings/sqlite_value.h"
+#include "src/trace_processor/storage/trace_storage.h"
+#include "src/trace_processor/tables/metadata_tables_py.h"
 #include "src/trace_processor/types/gfp_flags.h"
 #include "src/trace_processor/types/softirq_action.h"
 #include "src/trace_processor/types/task_state.h"
+#include "src/trace_processor/types/trace_processor_context.h"
 #include "src/trace_processor/types/variadic.h"
 
 #include "protos/perfetto/trace/ftrace/binder.pbzero.h"
@@ -43,9 +58,9 @@
 #include "protos/perfetto/trace/ftrace/samsung.pbzero.h"
 #include "protos/perfetto/trace/ftrace/sched.pbzero.h"
 #include "protos/perfetto/trace/ftrace/workqueue.pbzero.h"
+#include "src/trace_processor/types/version_number.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 namespace {
 
@@ -57,6 +72,12 @@
   const int64_t micros;
 };
 
+Query GetArgQuery(const tables::ArgTable& table, uint32_t arg_set_id) {
+  Query q;
+  q.constraints = {table.arg_set_id().eq(arg_set_id)};
+  return q;
+}
+
 class ArgsSerializer {
  public:
   ArgsSerializer(TraceProcessorContext*,
@@ -72,7 +93,7 @@
   using SerializerValueWriter = void (ArgsSerializer::*)(const Variadic&);
 
   // Arg writing functions.
-  void WriteArgForField(uint32_t field_id, ValueWriter writer) {
+  void WriteArgForField(uint32_t field_id, const ValueWriter& writer) {
     std::optional<uint32_t> row = FieldIdToRow(field_id);
     if (!row)
       return;
@@ -80,21 +101,23 @@
   }
   void WriteArgForField(uint32_t field_id,
                         base::StringView key,
-                        ValueWriter writer) {
+                        const ValueWriter& writer) {
     std::optional<uint32_t> row = FieldIdToRow(field_id);
     if (!row)
       return;
     WriteArg(key, storage_->GetArgValue(*row), writer);
   }
-  void WriteArgAtRow(uint32_t arg_row, ValueWriter writer) {
+  void WriteArgAtRow(uint32_t arg_row, const ValueWriter& writer) {
     const auto& args = storage_->arg_table();
     const auto& key = storage_->GetString(args.key()[arg_row]);
     WriteArg(key, storage_->GetArgValue(arg_row), writer);
   }
-  void WriteArg(base::StringView key, Variadic value, ValueWriter writer);
+  void WriteArg(base::StringView key,
+                Variadic value,
+                const ValueWriter& writer);
 
   // Value writing functions.
-  void WriteValueForField(uint32_t field_id, ValueWriter writer) {
+  void WriteValueForField(uint32_t field_id, const ValueWriter& writer) {
     std::optional<uint32_t> row = FieldIdToRow(field_id);
     if (!row)
       return;
@@ -109,7 +132,7 @@
       PERFETTO_DFATAL("Invalid field type %d", static_cast<int>(value.type));
     }
   }
-  void WriteValue(const Variadic& variadic);
+  void WriteValue(const Variadic&);
 
   // The default value writer which uses the |WriteValue| function.
   ValueWriter DVW() { return Wrap(&ArgsSerializer::WriteValue); }
@@ -130,11 +153,10 @@
 
   const TraceStorage* storage_ = nullptr;
   TraceProcessorContext* context_ = nullptr;
-  ArgSetId arg_set_id_ = kInvalidArgSetId;
   NullTermStringView event_name_;
   std::vector<std::optional<uint32_t>>* field_id_to_arg_index_;
 
-  RowMap row_map_;
+  tables::ArgTable::ConstIterator it_;
   uint32_t start_row_ = 0;
 
   base::StringWriter* writer_ = nullptr;
@@ -146,21 +168,16 @@
     NullTermStringView event_name,
     std::vector<std::optional<uint32_t>>* field_id_to_arg_index,
     base::StringWriter* writer)
-    : context_(context),
-      arg_set_id_(arg_set_id),
+    : storage_(context->storage.get()),
+      context_(context),
       event_name_(event_name),
       field_id_to_arg_index_(field_id_to_arg_index),
+      it_(context->storage->arg_table().FilterToIterator(
+          GetArgQuery(context->storage->arg_table(), arg_set_id))),
       writer_(writer) {
-  storage_ = context_->storage.get();
-  const auto& args = storage_->arg_table();
-  const auto& set_ids = args.arg_set_id();
-
   // We assume that the row map is a contiguous range (which is always the case
   // because arg_set_ids are contiguous by definition).
-  Query q;
-  q.constraints = {set_ids.eq(arg_set_id_)};
-  row_map_ = args.QueryToRowMap(q);
-  start_row_ = row_map_.empty() ? 0 : row_map_.Get(0);
+  start_row_ = it_ ? it_.row_number().row_number() : 0;
 
   // If the vector already has entries, we've previously cached the mapping
   // from field id to arg index.
@@ -185,11 +202,13 @@
   field_id_to_arg_index_->resize(max + 1);
 
   // Go through each field id and find the entry in the args table for that
-  for (uint32_t i = 1; i <= max; ++i) {
-    for (auto it = row_map_.IterateRows(); it; it.Next()) {
-      base::StringView key = args.key().GetString(it.index());
+  auto it = storage_->arg_table().FilterToIterator(
+      GetArgQuery(context->storage->arg_table(), arg_set_id));
+  for (uint32_t r = 0; it; ++it, ++r) {
+    for (uint32_t i = 1; i <= max; ++i) {
+      base::StringView key = context->storage->GetString(it.key());
       if (key == descriptor->fields[i].name) {
-        (*field_id_to_arg_index)[i] = it.row();
+        (*field_id_to_arg_index)[i] = r;
         break;
       }
     }
@@ -197,7 +216,7 @@
 }
 
 void ArgsSerializer::SerializeArgs() {
-  if (row_map_.empty())
+  if (!it_)
     return;
 
   if (event_name_ == "sched_switch") {
@@ -513,14 +532,14 @@
     WriteArgForField(CAT::kCommFieldNumber, DVW());
     return;
   }
-  for (auto it = row_map_.IterateRows(); it; it.Next()) {
-    WriteArgAtRow(it.index(), DVW());
+  for (; it_; ++it_) {
+    WriteArgAtRow(it_.row_number().row_number(), DVW());
   }
 }
 
 void ArgsSerializer::WriteArg(base::StringView key,
                               Variadic value,
-                              ValueWriter writer) {
+                              const ValueWriter& writer) {
   writer_->AppendChar(' ');
   writer_->AppendString(key.data(), key.size());
   writer_->AppendChar('=');
@@ -574,7 +593,7 @@
                            sqlite3_value** argv,
                            SqlValue& out,
                            Destructors& destructors) {
-  if (argc != 1 || sqlite3_value_type(argv[0]) != SQLITE_INTEGER) {
+  if (argc != 1 || sqlite::value::Type(argv[0]) != sqlite::Type::kInteger) {
     return base::ErrStatus("Usage: to_ftrace(id)");
   }
   uint32_t row = static_cast<uint32_t>(sqlite3_value_int64(argv[0]));
@@ -624,7 +643,7 @@
                             &writer);
   serializer.SerializeArgs();
 
-  return ScopedCString(writer.CreateStringCopy(), free);
+  return {writer.CreateStringCopy(), free};
 }
 
 void SystraceSerializer::SerializePrefix(uint32_t raw_row,
@@ -686,5 +705,4 @@
   writer->AppendChar(':');
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc
index ab9ea14..093afae 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/ancestor.cc
@@ -112,14 +112,17 @@
     // Nothing matches a null id so return an empty table.
     switch (type_) {
       case Type::kSlice:
-        return tables::AncestorSliceTable::SelectAndExtendParent(
-            storage_->slice_table(), {}, {});
+        return std::unique_ptr<Table>(
+            tables::AncestorSliceTable::SelectAndExtendParent(
+                storage_->slice_table(), {}, {}));
       case Type::kStackProfileCallsite:
-        return tables::AncestorStackProfileCallsiteTable::SelectAndExtendParent(
-            storage_->stack_profile_callsite_table(), {}, {});
+        return std::unique_ptr<Table>(
+            tables::AncestorStackProfileCallsiteTable::SelectAndExtendParent(
+                storage_->stack_profile_callsite_table(), {}, {}));
       case Type::kSliceByStack:
-        return tables::AncestorSliceByStackTable::SelectAndExtendParent(
-            storage_->slice_table(), {}, {});
+        return std::unique_ptr<Table>(
+            tables::AncestorSliceByStackTable::SelectAndExtendParent(
+                storage_->slice_table(), {}, {}));
     }
     return base::OkStatus();
   }
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc
index 4089036..12d0455 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/connected_flow.cc
@@ -30,6 +30,7 @@
 #include "perfetto/base/status.h"
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/db/column_storage.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/db/typed_column.h"
@@ -209,14 +210,15 @@
 
   if (arguments[0].type == SqlValue::Type::kNull) {
     // Nothing matches a null id so return an empty table.
-    return tables::ConnectedFlowTable::SelectAndExtendParent(flow, {}, {});
+    return std::unique_ptr<Table>(
+        tables::ConnectedFlowTable::SelectAndExtendParent(flow, {}, {}));
   }
   if (arguments[0].type != SqlValue::Type::kLong) {
     return base::ErrStatus("start id should be an integer.");
   }
 
   SliceId start_id{static_cast<uint32_t>(arguments[0].AsLong())};
-  if (!slice.id().IndexOf(start_id)) {
+  if (!slice.FindById(start_id)) {
     return base::ErrStatus("invalid slice id %" PRIu32 "",
                            static_cast<uint32_t>(start_id.value));
   }
@@ -243,8 +245,9 @@
   for (size_t i = 0; i < result_rows.size(); i++) {
     start_ids.Append(start_id.value);
   }
-  return tables::ConnectedFlowTable::SelectAndExtendParent(
-      flow, result_rows, std::move(start_ids));
+  return std::unique_ptr<Table>(
+      tables::ConnectedFlowTable::SelectAndExtendParent(flow, result_rows,
+                                                        std::move(start_ids)));
 }
 
 Table::Schema ConnectedFlow::CreateSchema() {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc
index 4d3ab12..62a5b94 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/descendant.cc
@@ -113,11 +113,13 @@
     // Nothing matches a null id so return an empty table.
     switch (type_) {
       case Type::kSlice:
-        return tables::DescendantSliceTable::SelectAndExtendParent(
-            storage_->slice_table(), {}, {});
+        return std::unique_ptr<Table>(
+            tables::DescendantSliceTable::SelectAndExtendParent(
+                storage_->slice_table(), {}, {}));
       case Type::kSliceByStack:
-        return tables::DescendantSliceByStackTable::SelectAndExtendParent(
-            storage_->slice_table(), {}, {});
+        return std::unique_ptr<Table>(
+            tables::DescendantSliceByStackTable::SelectAndExtendParent(
+                storage_->slice_table(), {}, {}));
     }
     PERFETTO_FATAL("For GCC");
   }
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.cc
index 98b1ab2..d2b7fa2 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_annotated_stack.cc
@@ -296,9 +296,10 @@
   for (uint32_t i = 0; i < cs_rows.size(); i++)
     start_id_vals.Append(start_id.value);
 
-  return tables::ExperimentalAnnotatedCallstackTable::SelectAndExtendParent(
-      cs_table, std::move(cs_rows), std::move(annotation_vals),
-      std::move(start_id_vals));
+  return std::unique_ptr<Table>(
+      tables::ExperimentalAnnotatedCallstackTable::SelectAndExtendParent(
+          cs_table, std::move(cs_rows), std::move(annotation_vals),
+          std::move(start_id_vals)));
 }
 
 uint32_t ExperimentalAnnotatedStack::EstimateRowCount() {
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
index b4d76cf..3f58dfd 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flamegraph.cc
@@ -28,6 +28,7 @@
 #include "perfetto/ext/base/status_or.h"
 #include "perfetto/ext/base/string_utils.h"
 #include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/db/table.h"
 #include "src/trace_processor/importers/proto/heap_graph_tracker.h"
@@ -194,34 +195,38 @@
 
 using tables::ExperimentalFlamegraphTable;
 std::vector<FocusedState> ComputeFocusedState(
+    const StringPool& pool,
     const ExperimentalFlamegraphTable& table,
     const Matcher& focus_matcher) {
   // Each row corresponds to a node in the flame chart tree with its parent
   // ptr. Root trees (no parents) will have a null parent ptr.
   std::vector<FocusedState> focused(table.row_count());
 
-  for (uint32_t i = 0; i < table.row_count(); ++i) {
-    auto parent_id = table.parent_id()[i];
+  for (auto it = table.IterateRows(); it; ++it) {
+    auto parent_id = it.parent_id();
     // Constraint: all descendants MUST come after their parents.
-    PERFETTO_DCHECK(!parent_id.has_value() || *parent_id < table.id()[i]);
+    PERFETTO_DCHECK(!parent_id.has_value() || *parent_id < it.id());
 
-    if (focus_matcher.matches(table.name().GetString(i).ToStdString())) {
+    auto i = it.row_number().row_number();
+    if (focus_matcher.matches(pool.Get(it.name()).ToStdString())) {
       // Mark as focused
       focused[i] = FocusedState::kFocusedPropagating;
       auto current = parent_id;
       // Mark all parent nodes as focused
       while (current.has_value()) {
-        auto current_idx = *table.id().IndexOf(*current);
+        auto c = *table.FindById(*current);
+        uint32_t current_idx = c.ToRowNumber().row_number();
         if (focused[current_idx] != FocusedState::kNotFocused) {
           // We have already visited these nodes, skip
           break;
         }
         focused[current_idx] = FocusedState::kFocusedNotPropagating;
-        current = table.parent_id()[current_idx];
+        current = c.parent_id();
       }
-    } else if (parent_id.has_value() &&
-               focused[*table.id().IndexOf(*parent_id)] ==
-                   FocusedState::kFocusedPropagating) {
+    } else if (parent_id.has_value() && focused[table.FindById(*parent_id)
+                                                    ->ToRowNumber()
+                                                    .row_number()] ==
+                                            FocusedState::kFocusedPropagating) {
       // Focus cascades downwards.
       focused[i] = FocusedState::kFocusedPropagating;
     } else {
@@ -245,7 +250,7 @@
     return in;
   }
   std::vector<FocusedState> focused_state =
-      ComputeFocusedState(*in, Matcher(focus_str));
+      ComputeFocusedState(storage->string_pool(), *in, Matcher(focus_str));
   std::unique_ptr<ExperimentalFlamegraphTable> tbl(
       new tables::ExperimentalFlamegraphTable(storage->mutable_string_pool()));
 
@@ -253,19 +258,21 @@
   std::vector<CumulativeCounts> node_to_cumulatives(in->row_count());
   for (int64_t idx = in->row_count() - 1; idx >= 0; --idx) {
     auto i = static_cast<uint32_t>(idx);
+    auto rr = (*in)[i];
     if (focused_state[i] == FocusedState::kNotFocused) {
       continue;
     }
     auto& cumulatives = node_to_cumulatives[i];
-    cumulatives.size += in->size()[i];
-    cumulatives.count += in->count()[i];
-    cumulatives.alloc_size += in->alloc_size()[i];
-    cumulatives.alloc_count += in->alloc_count()[i];
+    cumulatives.size += rr.size();
+    cumulatives.count += rr.count();
+    cumulatives.alloc_size += rr.alloc_size();
+    cumulatives.alloc_count += rr.alloc_count();
 
-    auto parent_id = in->parent_id()[i];
+    auto parent_id = rr.parent_id();
     if (parent_id.has_value()) {
-      auto& parent_cumulatives =
-          node_to_cumulatives[*in->id().IndexOf(*parent_id)];
+      uint32_t parent_row =
+          in->FindById(*parent_id)->ToRowNumber().row_number();
+      auto& parent_cumulatives = node_to_cumulatives[parent_row];
       parent_cumulatives.size += cumulatives.size;
       parent_cumulatives.count += cumulatives.count;
       parent_cumulatives.alloc_size += cumulatives.alloc_size;
@@ -275,7 +282,8 @@
 
   // Mapping between the old rows ('node') to the new identifiers.
   std::vector<ExperimentalFlamegraphTable::Id> node_to_id(in->row_count());
-  for (uint32_t i = 0; i < in->row_count(); ++i) {
+  for (auto it = in->IterateRows(); it; ++it) {
+    uint32_t i = it.row_number().row_number();
     if (focused_state[i] == FocusedState::kNotFocused) {
       continue;
     }
@@ -283,22 +291,23 @@
     tables::ExperimentalFlamegraphTable::Row alloc_row{};
     // We must reparent the rows as every insertion will get its own
     // identifier.
-    auto original_parent_id = in->parent_id()[i];
+    auto original_parent_id = it.parent_id();
     if (original_parent_id.has_value()) {
-      auto original_idx = *in->id().IndexOf(*original_parent_id);
+      auto original_idx =
+          in->FindById(*original_parent_id)->ToRowNumber().row_number();
       alloc_row.parent_id = node_to_id[original_idx];
     }
 
-    alloc_row.ts = in->ts()[i];
-    alloc_row.upid = in->upid()[i];
-    alloc_row.profile_type = in->profile_type()[i];
-    alloc_row.depth = in->depth()[i];
-    alloc_row.name = in->name()[i];
-    alloc_row.map_name = in->map_name()[i];
-    alloc_row.count = in->count()[i];
-    alloc_row.size = in->size()[i];
-    alloc_row.alloc_count = in->alloc_count()[i];
-    alloc_row.alloc_size = in->alloc_size()[i];
+    alloc_row.ts = it.ts();
+    alloc_row.upid = it.upid();
+    alloc_row.profile_type = it.profile_type();
+    alloc_row.depth = it.depth();
+    alloc_row.name = it.name();
+    alloc_row.map_name = it.map_name();
+    alloc_row.count = it.count();
+    alloc_row.size = it.size();
+    alloc_row.alloc_count = it.alloc_count();
+    alloc_row.alloc_size = it.alloc_size();
 
     const auto& cumulative = node_to_cumulatives[i];
     alloc_row.cumulative_count = cumulative.count;
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.cc
index 8e384e6..6d4ff85 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice.cc
@@ -36,8 +36,7 @@
 #include "src/trace_processor/tables/track_tables_py.h"
 #include "src/trace_processor/types/trace_processor_context.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 ExperimentalFlatSlice::ExperimentalFlatSlice(TraceProcessorContext* context)
     : context_(context) {}
@@ -67,14 +66,15 @@
 
   auto insert_slice = [&](uint32_t i, int64_t ts,
                           tables::TrackTable::Id track_id) {
+    auto rr = slice[i];
     tables::ExperimentalFlatSliceTable::Row row;
     row.ts = ts;
     row.dur = -1;
     row.track_id = track_id;
-    row.category = slice.category()[i];
-    row.name = slice.name()[i];
-    row.arg_set_id = slice.arg_set_id()[i];
-    row.source_id = slice.id()[i];
+    row.category = rr.category();
+    row.name = rr.name();
+    row.arg_set_id = rr.arg_set_id();
+    row.source_id = rr.id();
     row.start_bound = start_bound;
     row.end_bound = end_bound;
     return out->Insert(row).row;
@@ -94,9 +94,9 @@
   };
 
   auto terminate_slice = [&](uint32_t out_row, int64_t end_ts) {
-    PERFETTO_DCHECK(out->dur()[out_row] == -1);
-    int64_t out_ts = out->ts()[out_row];
-    out->mutable_dur()->Set(out_row, end_ts - out_ts);
+    auto rr = (*out)[out_row];
+    PERFETTO_DCHECK(rr.dur() == -1);
+    rr.set_dur(end_ts - rr.ts());
   };
 
   struct ActiveSlice {
@@ -113,8 +113,9 @@
   std::unordered_map<TrackId, Track> tracks;
 
   auto maybe_terminate_active_slice = [&](const Track& t, int64_t fin_ts) {
-    int64_t ts = slice.ts()[t.active.source_row.value()];
-    int64_t dur = slice.dur()[t.active.source_row.value()];
+    auto rr = slice[t.active.source_row.value()];
+    int64_t ts = rr.ts();
+    int64_t dur = rr.dur();
     if (dur == -1 || ts + dur > fin_ts)
       return false;
 
@@ -146,8 +147,9 @@
       uint32_t source_row = t.parents[static_cast<size_t>(i)];
       t.parents.pop_back();
 
-      int64_t active_ts = out->ts()[t.active.out_row];
-      int64_t active_dur = out->dur()[t.active.out_row];
+      auto rr = (*out)[t.active.out_row];
+      int64_t active_ts = rr.ts();
+      int64_t active_dur = rr.dur();
       PERFETTO_DCHECK(active_dur != -1);
 
       t.active.source_row = source_row;
@@ -165,8 +167,9 @@
     // should have caught it; all code only adds slices from source.
     PERFETTO_DCHECK(!t.active.is_sentinel());
 
-    int64_t ts = out->ts()[t.active.out_row];
-    int64_t dur = out->dur()[t.active.out_row];
+    auto rr = (*out)[t.active.out_row];
+    int64_t ts = rr.ts();
+    int64_t dur = rr.dur();
 
     // If the active slice is unfinshed, we return that for the caller to
     // terminate.
@@ -178,11 +181,11 @@
     t.active.out_row = insert_sentinel(ts + dur, track_id);
   };
 
-  for (uint32_t i = 0; i < slice.row_count(); ++i) {
+  for (auto it = slice.IterateRows(); it; ++it) {
     // TODO(lalitm): this can be optimized using a O(logn) lower bound/filter.
     // Not adding for now as a premature optimization but may be needed down the
     // line.
-    int64_t ts = slice.ts()[i];
+    int64_t ts = it.ts();
     if (ts < start_bound)
       continue;
 
@@ -190,15 +193,15 @@
       break;
 
     // Ignore instants as they don't factor into flat slice at all.
-    if (slice.dur()[i] == 0)
+    if (it.dur() == 0)
       continue;
 
-    TrackId track_id = slice.track_id()[i];
+    TrackId track_id = it.track_id();
     Track& track = tracks[track_id];
 
     // Initalize the track (if needed) by adding a sentinel slice starting at
     // start_bound.
-    bool is_root = slice.depth()[i] == 0;
+    bool is_root = it.depth() == 0;
     if (!track.initialized) {
       // If we are unintialized and our start box picks up slices mid way
       // through startup, wait until we reach a root slice.
@@ -221,10 +224,11 @@
 
     // The depth of our slice should also match the depth of the parent stack
     // (after adding the previous slice).
-    PERFETTO_DCHECK(track.parents.size() == slice.depth()[i]);
+    PERFETTO_DCHECK(track.parents.size() == it.depth());
 
-    track.active.source_row = i;
-    track.active.out_row = insert_slice(i, ts, track_id);
+    track.active.source_row = it.row_number().row_number();
+    track.active.out_row =
+        insert_slice(it.row_number().row_number(), ts, track_id);
   }
 
   for (const auto& track : tracks) {
@@ -254,5 +258,4 @@
   return context_->storage->slice_table().row_count();
 }
 
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc
index 3a107d2..e3a6450 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_flat_slice_unittest.cc
@@ -57,20 +57,20 @@
 
 class TableAsserter {
  public:
-  explicit TableAsserter(Table::Iterator it) : iterator_(std::move(it)) {}
+  explicit TableAsserter(tables::ExperimentalFlatSliceTable::Iterator it)
+      : iterator_(std::move(it)) {}
 
   void NextSlice(int64_t ts, int64_t dur) {
-    using CI = tables::ExperimentalFlatSliceTable::ColumnIndex;
     ASSERT_TRUE(HasMoreSlices());
-    ASSERT_EQ(iterator_.Get(CI::ts).AsLong(), ts);
-    ASSERT_EQ(iterator_.Get(CI::dur).AsLong(), dur);
+    ASSERT_EQ(iterator_.ts(), ts);
+    ASSERT_EQ(iterator_.dur(), dur);
     ++iterator_;
   }
 
   bool HasMoreSlices() { return bool(iterator_); }
 
  private:
-  Table::Iterator iterator_;
+  tables::ExperimentalFlatSliceTable::Iterator iterator_;
 };
 
 TEST(ExperimentalFlatSlice, Smoke) {
@@ -102,7 +102,7 @@
   auto out = ExperimentalFlatSlice::ComputeFlatSliceTable(table, &pool, 0, 400);
   Query q;
   q.orders = {out->track_id().ascending(), out->ts().ascending()};
-  auto it = out->ApplyAndIterateRows(out->QueryToRowMap(q));
+  auto it = out->FilterToIterator(q);
 
   TableAsserter asserter(std::move(it));
 
@@ -186,7 +186,7 @@
       ExperimentalFlatSlice::ComputeFlatSliceTable(table, &pool, start, end);
   Query q;
   q.orders = {out->track_id().ascending(), out->ts().ascending()};
-  auto it = out->ApplyAndIterateRows(out->QueryToRowMap(q));
+  auto it = out->FilterToIterator(q);
 
   TableAsserter asserter(std::move(it));
 
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout_unittest.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout_unittest.cc
index 5df44c3..5735a1d 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout_unittest.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/experimental_slice_layout_unittest.cc
@@ -42,9 +42,9 @@
   using CI = tables::ExperimentalSliceLayoutTable::ColumnIndex;
   std::vector<std::string> lines;
   for (auto it = table.IterateRows(); it; ++it) {
-    int64_t layout_depth = it.Get(CI::layout_depth).long_value;
-    int64_t ts = it.Get(CI::ts).long_value;
-    int64_t dur = it.Get(CI::dur).long_value;
+    int64_t layout_depth = it.Get(CI::layout_depth).AsLong();
+    int64_t ts = it.Get(CI::ts).AsLong();
+    int64_t dur = it.Get(CI::dur).AsLong();
     const char* filter_track_ids = it.Get(CI::filter_track_ids).AsString();
     if (std::string("") == filter_track_ids) {
       continue;
diff --git a/src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc b/src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc
index d47f834..9dd5564 100644
--- a/src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc
+++ b/src/trace_processor/perfetto_sql/intrinsics/table_functions/flamegraph_construction_algorithms.cc
@@ -283,15 +283,21 @@
 
 static std::unique_ptr<tables::ExperimentalFlamegraphTable>
 BuildFlamegraphTableCallstackSizeAndCount(
+    const tables::PerfSampleTable& table,
     std::unique_ptr<tables::ExperimentalFlamegraphTable> tbl,
     const std::vector<uint32_t>& callsite_to_merged_callsite,
-    Table::Iterator it) {
-  for (; it; ++it) {
-    int64_t callsite_id =
-        it.Get(tables::PerfSampleTable::ColumnIndex::callsite_id).long_value;
-    int64_t ts = it.Get(tables::PerfSampleTable::ColumnIndex::ts).long_value;
-    uint32_t merged_idx =
-        callsite_to_merged_callsite[static_cast<uint32_t>(callsite_id)];
+    std::vector<Constraint> constraints,
+    const std::unordered_set<uint32_t>& utids) {
+  Query q;
+  q.constraints = std::move(constraints);
+  for (auto it = table.FilterToIterator(q); it; ++it) {
+    if (utids.find(it.utid()) == utids.end()) {
+      continue;
+    }
+
+    uint32_t callsite_id = it.callsite_id().value_or(CallsiteId(0u)).value;
+    int64_t ts = it.ts();
+    uint32_t merged_idx = callsite_to_merged_callsite[callsite_id];
     tbl->mutable_size()->Set(merged_idx, tbl->size()[merged_idx] + 1);
     tbl->mutable_count()->Set(merged_idx, tbl->count()[merged_idx] + 1);
     tbl->mutable_ts()->Set(merged_idx, ts);
@@ -392,21 +398,6 @@
     cs.emplace_back(Constraint{tables::PerfSampleTable::ColumnIndex::ts, tc.op,
                                SqlValue::Long(tc.value)});
   }
-  std::vector<uint32_t> cs_rows;
-  {
-    Query q;
-    q.constraints = cs;
-    auto it = storage->perf_sample_table().FilterToIterator(q);
-    for (; it; ++it) {
-      if (utids.find(it.utid()) != utids.end()) {
-        cs_rows.push_back(it.row_number().row_number());
-      }
-    }
-  }
-  if (cs_rows.empty()) {
-    return std::make_unique<tables::ExperimentalFlamegraphTable>(
-        storage->mutable_string_pool());
-  }
 
   // The logic underneath is selecting a default timestamp to be used by all
   // frames which do not have a timestamp. The timestamp is taken from the
@@ -415,7 +406,7 @@
   // the table ExperimentalFlamegraphTable in this class.
   int64_t default_timestamp = 0;
   if (!time_constraints.empty()) {
-    auto& tc = time_constraints[0];
+    const auto& tc = time_constraints[0];
     if (tc.op == FilterOp::kGt) {
       default_timestamp = tc.value + 1;
     } else if (tc.op == FilterOp::kLt) {
@@ -431,10 +422,8 @@
                                         default_timestamp,
                                         storage->InternString("perf"));
   return BuildFlamegraphTableCallstackSizeAndCount(
-      std::move(table_and_callsites.tbl),
-      table_and_callsites.callsite_to_merged_callsite,
-      storage->perf_sample_table().ApplyAndIterateRows(
-          RowMap(std::move(cs_rows))));
+      storage->perf_sample_table(), std::move(table_and_callsites.tbl),
+      table_and_callsites.callsite_to_merged_callsite, std::move(cs), utids);
 }
 
 }  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql b/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql
index 2ed2851..08d9808 100644
--- a/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql
+++ b/src/trace_processor/perfetto_sql/stdlib/intervals/intersect.sql
@@ -16,7 +16,7 @@
 INCLUDE PERFETTO MODULE metasql.table_list;
 
 CREATE PERFETTO MACRO _ii_df_agg(x Expr, y Expr)
-RETURNS Expr AS __intrinsic_stringify!($x), $y;
+RETURNS Expr AS __intrinsic_stringify!($x), input.$y;
 
 CREATE PERFETTO MACRO _ii_df_bind(x Expr, y Expr)
 RETURNS Expr AS __intrinsic_table_ptr_bind($x, __intrinsic_stringify!($y));
@@ -33,22 +33,18 @@
 )
 RETURNS TableOrSubquery AS
 (
-  WITH sorted_tab AS (
-    SELECT * FROM $tab ORDER BY ts
-  )
-  SELECT
-    __intrinsic_interval_tree_intervals_agg(
-      id,
-      ts,
-      dur
-      __intrinsic_prefixed_token_zip_join!(
-        $agg_columns,
-        $agg_columns,
-        _ii_df_agg,
-        __intrinsic_token_comma!()
-      )
+  SELECT __intrinsic_interval_tree_intervals_agg(
+    input.id,
+    input.ts,
+    input.dur
+    __intrinsic_prefixed_token_zip_join!(
+      $agg_columns,
+      $agg_columns,
+      _ii_df_agg,
+      __intrinsic_token_comma!()
     )
-  FROM sorted_tab
+  )
+  FROM (SELECT * FROM $tab ORDER BY ts) input
 );
 
 CREATE PERFETTO MACRO _interval_intersect(
@@ -95,11 +91,11 @@
 
     -- Partition columns. Prefixed to handle case of no partitions.
     __intrinsic_prefixed_token_zip_join!(
-        (c7, c8, c9, c10),
-        $agg_columns,
-        _ii_df_bind,
-        AND
-      )
+      (c7, c8, c9, c10),
+      $agg_columns,
+      _ii_df_bind,
+      AND
+    )
 );
 
 CREATE PERFETTO MACRO _interval_intersect_single(
diff --git a/src/trace_processor/sqlite/db_sqlite_table.cc b/src/trace_processor/sqlite/db_sqlite_table.cc
index d1485bb..c119d99 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.cc
+++ b/src/trace_processor/sqlite/db_sqlite_table.cc
@@ -822,6 +822,7 @@
   SqlValue value = c->mode == Cursor::Mode::kSingleRow
                        ? source_table->columns()[idx].Get(*c->single_row)
                        : c->iterator->Get(idx);
+
   // We can say kSqliteStatic for strings because all strings are expected
   // to come from the string pool. Thus they will be valid for the lifetime
   // of trace processor. Similarily, for bytes, we can also use
diff --git a/src/trace_processor/sqlite/db_sqlite_table.h b/src/trace_processor/sqlite/db_sqlite_table.h
index 4085e7c..e438386 100644
--- a/src/trace_processor/sqlite/db_sqlite_table.h
+++ b/src/trace_processor/sqlite/db_sqlite_table.h
@@ -109,6 +109,7 @@
     Mode mode = Mode::kSingleRow;
 
     int last_idx_num = -1;
+
     Query query;
 
     std::vector<SqlValue> table_function_arguments;
diff --git a/src/trace_processor/storage/BUILD.gn b/src/trace_processor/storage/BUILD.gn
index 844d228..060e821 100644
--- a/src/trace_processor/storage/BUILD.gn
+++ b/src/trace_processor/storage/BUILD.gn
@@ -26,6 +26,8 @@
     "../../../include/perfetto/ext/base",
     "../../../include/perfetto/trace_processor",
     "../containers",
+    "../db:minimal",
+    "../db/column",
     "../tables",
     "../types",
   ]
diff --git a/src/trace_processor/storage/trace_storage.cc b/src/trace_processor/storage/trace_storage.cc
index ba53e3e..7f0c973 100644
--- a/src/trace_processor/storage/trace_storage.cc
+++ b/src/trace_processor/storage/trace_storage.cc
@@ -16,39 +16,21 @@
 
 #include "src/trace_processor/storage/trace_storage.h"
 
-#include <string.h>
-#include <algorithm>
-#include <limits>
+#include <cstdint>
+#include <cstring>
+#include <string>
+#include <vector>
 
+#include "perfetto/base/logging.h"
 #include "perfetto/ext/base/no_destructor.h"
+#include "perfetto/trace_processor/basic_types.h"
+#include "src/trace_processor/containers/null_term_string_view.h"
+#include "src/trace_processor/types/variadic.h"
 
-namespace perfetto {
-namespace trace_processor {
+namespace perfetto::trace_processor {
 
 namespace {
 
-void DbTableMaybeUpdateMinMax(const TypedColumn<int64_t>& ts_col,
-                              int64_t* min_value,
-                              int64_t* max_value,
-                              const TypedColumn<int64_t>* dur_col = nullptr) {
-  if (ts_col.overlay().empty())
-    return;
-
-  int64_t col_min = ts_col.Min()->AsLong();
-  int64_t col_max = ts_col.Max()->AsLong();
-
-  if (dur_col) {
-    PERFETTO_CHECK(ts_col.IsSorted());
-    PERFETTO_CHECK(dur_col->overlay().size() == ts_col.overlay().size());
-    for (uint32_t i = 0; i < dur_col->overlay().size(); i++) {
-      col_max = std::max(ts_col[i] + (*dur_col)[i], col_max);
-    }
-  }
-
-  *min_value = std::min(*min_value, col_min);
-  *max_value = std::max(*max_value, col_max);
-}
-
 std::vector<NullTermStringView> CreateRefTypeStringMap() {
   std::vector<NullTermStringView> map(static_cast<size_t>(RefType::kRefMax));
   map[static_cast<size_t>(RefType::kRefNoRef)] = NullTermStringView();
@@ -115,34 +97,4 @@
   times_ended_[queue_row] = time_ended;
 }
 
-std::pair<int64_t, int64_t> TraceStorage::GetTraceTimestampBoundsNs() const {
-  int64_t start_ns = std::numeric_limits<int64_t>::max();
-  int64_t end_ns = std::numeric_limits<int64_t>::min();
-
-  DbTableMaybeUpdateMinMax(raw_table_.ts(), &start_ns, &end_ns);
-  DbTableMaybeUpdateMinMax(sched_slice_table_.ts(), &start_ns, &end_ns,
-                           &sched_slice_table_.dur());
-  DbTableMaybeUpdateMinMax(counter_table_.ts(), &start_ns, &end_ns);
-  DbTableMaybeUpdateMinMax(slice_table_.ts(), &start_ns, &end_ns,
-                           &slice_table_.dur());
-  DbTableMaybeUpdateMinMax(heap_profile_allocation_table_.ts(), &start_ns,
-                           &end_ns);
-  DbTableMaybeUpdateMinMax(thread_state_table_.ts(), &start_ns, &end_ns);
-  DbTableMaybeUpdateMinMax(android_log_table_.ts(), &start_ns, &end_ns);
-  DbTableMaybeUpdateMinMax(heap_graph_object_table_.graph_sample_ts(),
-                           &start_ns, &end_ns);
-  DbTableMaybeUpdateMinMax(perf_sample_table_.ts(), &start_ns, &end_ns);
-  DbTableMaybeUpdateMinMax(cpu_profile_stack_sample_table_.ts(), &start_ns,
-                           &end_ns);
-
-  if (start_ns == std::numeric_limits<int64_t>::max()) {
-    return std::make_pair(0, 0);
-  }
-  if (start_ns == end_ns) {
-    end_ns += 1;
-  }
-  return std::make_pair(start_ns, end_ns);
-}
-
-}  // namespace trace_processor
-}  // namespace perfetto
+}  // namespace perfetto::trace_processor
diff --git a/src/trace_processor/storage/trace_storage.h b/src/trace_processor/storage/trace_storage.h
index 74862f1..649c28d 100644
--- a/src/trace_processor/storage/trace_storage.h
+++ b/src/trace_processor/storage/trace_storage.h
@@ -32,13 +32,15 @@
 #include <vector>
 
 #include "perfetto/base/logging.h"
+#include "perfetto/base/status.h"
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/trace_processor/basic_types.h"
 #include "perfetto/trace_processor/status.h"
 #include "src/trace_processor/containers/null_term_string_view.h"
-#include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/column/types.h"
+#include "src/trace_processor/db/typed_column_internal.h"
 #include "src/trace_processor/storage/stats.h"
 #include "src/trace_processor/tables/android_tables_py.h"
 #include "src/trace_processor/tables/counter_tables_py.h"
@@ -959,60 +961,57 @@
   // Number of interned strings in the pool. Includes the empty string w/ ID=0.
   size_t string_count() const { return string_pool_.size(); }
 
-  // Start / end ts (in nanoseconds) across the parsed trace events.
-  // Returns (0, 0) if the trace is empty.
-  std::pair<int64_t, int64_t> GetTraceTimestampBoundsNs() const;
-
-  util::Status ExtractArg(uint32_t arg_set_id,
+  base::Status ExtractArg(uint32_t arg_set_id,
                           const char* key,
                           std::optional<Variadic>* result) const {
     const auto& args = arg_table();
     Query q;
     q.constraints = {args.arg_set_id().eq(arg_set_id), args.key().eq(key)};
-    RowMap filtered = args.QueryToRowMap(q);
-    if (filtered.empty()) {
+    auto it = args.FilterToIterator(q);
+    if (!it) {
       *result = std::nullopt;
-      return util::OkStatus();
+      return base::OkStatus();
     }
-    if (filtered.size() > 1) {
-      return util::ErrStatus(
+    *result = GetArgValue(it.row_number().row_number());
+    if (++it) {
+      return base::ErrStatus(
           "EXTRACT_ARG: received multiple args matching arg set id and key");
     }
-    uint32_t idx = filtered.Get(0);
-    *result = GetArgValue(idx);
-    return util::OkStatus();
+    return base::OkStatus();
   }
 
   Variadic GetArgValue(uint32_t row) const {
+    auto rr = arg_table_[row];
+
     Variadic v;
-    v.type = *GetVariadicTypeForId(arg_table_.value_type()[row]);
+    v.type = *GetVariadicTypeForId(rr.value_type());
 
     // Force initialization of union to stop GCC complaining.
     v.int_value = 0;
 
     switch (v.type) {
       case Variadic::Type::kBool:
-        v.bool_value = static_cast<bool>(*arg_table_.int_value()[row]);
+        v.bool_value = static_cast<bool>(*rr.int_value());
         break;
       case Variadic::Type::kInt:
-        v.int_value = *arg_table_.int_value()[row];
+        v.int_value = *rr.int_value();
         break;
       case Variadic::Type::kUint:
-        v.uint_value = static_cast<uint64_t>(*arg_table_.int_value()[row]);
+        v.uint_value = static_cast<uint64_t>(*rr.int_value());
         break;
       case Variadic::Type::kString: {
-        auto opt_value = arg_table_.string_value()[row];
+        auto opt_value = rr.string_value();
         v.string_value = opt_value ? *opt_value : kNullStringId;
         break;
       }
       case Variadic::Type::kPointer:
-        v.pointer_value = static_cast<uint64_t>(*arg_table_.int_value()[row]);
+        v.pointer_value = static_cast<uint64_t>(*rr.int_value());
         break;
       case Variadic::Type::kReal:
-        v.real_value = *arg_table_.real_value()[row];
+        v.real_value = *rr.real_value();
         break;
       case Variadic::Type::kJson: {
-        auto opt_value = arg_table_.string_value()[row];
+        auto opt_value = rr.string_value();
         v.json_value = opt_value ? *opt_value : kNullStringId;
         break;
       }
@@ -1144,8 +1143,8 @@
 
   tables::AndroidKeyEventsTable android_key_events_table_{&string_pool_};
   tables::AndroidMotionEventsTable android_motion_events_table_{&string_pool_};
-  tables::AndroidInputEventDispatchTable
-      android_input_event_dispatch_table_{&string_pool_};
+  tables::AndroidInputEventDispatchTable android_input_event_dispatch_table_{
+      &string_pool_};
 
   tables::StackProfileMappingTable stack_profile_mapping_table_{&string_pool_};
   tables::StackProfileFrameTable stack_profile_frame_table_{&string_pool_};
diff --git a/src/trace_processor/tables/BUILD.gn b/src/trace_processor/tables/BUILD.gn
index 7cf8bb0..72796e5 100644
--- a/src/trace_processor/tables/BUILD.gn
+++ b/src/trace_processor/tables/BUILD.gn
@@ -36,6 +36,7 @@
 
 source_set("tables") {
   sources = [
+    "macros_internal.cc",
     "macros_internal.h",
     "table_destructors.cc",
   ]
@@ -64,6 +65,7 @@
     "../../../gn:gtest_and_gmock",
     "../containers",
     "../db",
+    "../db/column",
   ]
 }
 
diff --git a/src/trace_processor/tables/macros_internal.cc b/src/trace_processor/tables/macros_internal.cc
new file mode 100644
index 0000000..c2ce1bf
--- /dev/null
+++ b/src/trace_processor/tables/macros_internal.cc
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed 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 "src/trace_processor/tables/macros_internal.h"
+
+#include <cstdint>
+#include <initializer_list>
+#include <type_traits>
+#include <utility>
+#include <vector>
+
+#include "perfetto/base/logging.h"
+#include "perfetto/public/compiler.h"
+#include "perfetto/trace_processor/ref_counted.h"
+#include "src/trace_processor/containers/bit_vector.h"
+#include "src/trace_processor/containers/row_map.h"
+#include "src/trace_processor/containers/string_pool.h"
+#include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/column/overlay_layer.h"
+#include "src/trace_processor/db/column/selector_overlay.h"
+#include "src/trace_processor/db/column/storage_layer.h"
+#include "src/trace_processor/db/column_storage_overlay.h"
+
+namespace perfetto::trace_processor::macros_internal {
+
+PERFETTO_NO_INLINE MacroTable::MacroTable(StringPool* pool,
+                                          std::vector<ColumnLegacy> columns,
+                                          const MacroTable* parent)
+    : Table(pool, 0u, std::move(columns), EmptyOverlaysFromParent(parent)),
+      allow_inserts_(true),
+      parent_(parent) {}
+
+PERFETTO_NO_INLINE MacroTable::MacroTable(StringPool* pool,
+                                          std::vector<ColumnLegacy> columns,
+                                          const MacroTable& parent,
+                                          const RowMap& parent_overlay)
+    : Table(pool,
+            parent_overlay.size(),
+            std::move(columns),
+            SelectedOverlaysFromParent(parent, parent_overlay)),
+      allow_inserts_(false),
+      parent_(&parent) {}
+
+PERFETTO_NO_INLINE void MacroTable::UpdateOverlaysAfterParentInsert() {
+  CopyLastInsertFrom(parent_->overlays());
+}
+
+PERFETTO_NO_INLINE void MacroTable::UpdateSelfOverlayAfterInsert() {
+  IncrementRowCountAndAddToLastOverlay();
+}
+
+PERFETTO_NO_INLINE std::vector<ColumnLegacy>
+MacroTable::CopyColumnsFromParentOrAddRootColumns(MacroTable* self,
+                                                  const MacroTable* parent) {
+  std::vector<ColumnLegacy> columns;
+  if (parent) {
+    for (const ColumnLegacy& col : parent->columns()) {
+      columns.emplace_back(col, col.index_in_table(), col.overlay_index());
+    }
+  } else {
+    columns.emplace_back(ColumnLegacy::IdColumn(0, 0));
+    columns.emplace_back("type", &self->type_, ColumnLegacy::kNonNull, 1, 0);
+  }
+  return columns;
+}
+
+PERFETTO_NO_INLINE void MacroTable::OnConstructionCompletedRegularConstructor(
+    std::initializer_list<RefPtr<column::StorageLayer>> storage_layers,
+    std::initializer_list<RefPtr<column::OverlayLayer>> null_layers) {
+  std::vector<RefPtr<column::OverlayLayer>> overlay_layers(
+      OverlayCount(parent_) + 1);
+  for (uint32_t i = 0; i < overlay_layers.size() - 1; ++i) {
+    PERFETTO_CHECK(overlays()[i].row_map().IsBitVector());
+    overlay_layers[i].reset(
+        new column::SelectorOverlay(overlays()[i].row_map().GetIfBitVector()));
+  }
+  Table::OnConstructionCompleted(storage_layers, null_layers,
+                                 std::move(overlay_layers));
+}
+
+PERFETTO_NO_INLINE std::vector<ColumnStorageOverlay>
+MacroTable::EmptyOverlaysFromParent(const MacroTable* parent) {
+  std::vector<ColumnStorageOverlay> overlays(parent ? parent->overlays().size()
+                                                    : 0);
+  for (auto& overlay : overlays) {
+    overlay = ColumnStorageOverlay(BitVector());
+  }
+  overlays.emplace_back();
+  return overlays;
+}
+
+PERFETTO_NO_INLINE std::vector<ColumnStorageOverlay>
+MacroTable::SelectedOverlaysFromParent(
+    const macros_internal::MacroTable& parent,
+    const RowMap& rm) {
+  std::vector<ColumnStorageOverlay> overlays;
+  for (const auto& overlay : parent.overlays()) {
+    overlays.emplace_back(overlay.SelectRows(rm));
+    PERFETTO_DCHECK(overlays.back().size() == rm.size());
+  }
+  overlays.emplace_back(rm.size());
+  return overlays;
+}
+
+BaseConstIterator::BaseConstIterator(const MacroTable* table,
+                                     Table::Iterator iterator)
+    : iterator_(std::move(iterator)), table_(table) {
+  static_assert(std::is_base_of<Table, MacroTable>::value,
+                "Template param should be a subclass of Table.");
+}
+
+BaseConstIterator::operator bool() const {
+  return bool(iterator_);
+}
+
+BaseConstIterator& BaseConstIterator::operator++() {
+  ++iterator_;
+  return *this;
+}
+
+BaseRowReference::BaseRowReference(const MacroTable* table, uint32_t row_number)
+    : table_(table), row_number_(row_number) {}
+
+}  // namespace perfetto::trace_processor::macros_internal
diff --git a/src/trace_processor/tables/macros_internal.h b/src/trace_processor/tables/macros_internal.h
index 5f69c6c..994322d 100644
--- a/src/trace_processor/tables/macros_internal.h
+++ b/src/trace_processor/tables/macros_internal.h
@@ -24,16 +24,13 @@
 #include <utility>
 #include <vector>
 
-#include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
+#include "perfetto/public/compiler.h"
 #include "perfetto/trace_processor/ref_counted.h"
-#include "src/trace_processor/containers/bit_vector.h"
 #include "src/trace_processor/containers/row_map.h"
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/column.h"
-#include "src/trace_processor/db/column/data_layer.h"
 #include "src/trace_processor/db/column/overlay_layer.h"
-#include "src/trace_processor/db/column/selector_overlay.h"
 #include "src/trace_processor/db/column/storage_layer.h"
 #include "src/trace_processor/db/column_storage.h"
 #include "src/trace_processor/db/column_storage_overlay.h"
@@ -88,61 +85,27 @@
   // Constructors for tables created by the regular constructor.
   PERFETTO_NO_INLINE explicit MacroTable(StringPool* pool,
                                          std::vector<ColumnLegacy> columns,
-                                         const MacroTable* parent)
-      : Table(pool, 0u, std::move(columns), EmptyOverlaysFromParent(parent)),
-        allow_inserts_(true),
-        parent_(parent) {}
+                                         const MacroTable* parent);
 
   // Constructor for tables created by SelectAndExtendParent.
   MacroTable(StringPool* pool,
              std::vector<ColumnLegacy> columns,
              const MacroTable& parent,
-             const RowMap& parent_overlay)
-      : Table(pool,
-              parent_overlay.size(),
-              std::move(columns),
-              SelectedOverlaysFromParent(parent, parent_overlay)),
-        allow_inserts_(false),
-        parent_(&parent) {}
+             const RowMap& parent_overlay);
 
   ~MacroTable() override;
 
-  PERFETTO_NO_INLINE void UpdateOverlaysAfterParentInsert() {
-    CopyLastInsertFrom(parent_->overlays());
-  }
+  PERFETTO_NO_INLINE void UpdateOverlaysAfterParentInsert();
 
-  PERFETTO_NO_INLINE void UpdateSelfOverlayAfterInsert() {
-    IncrementRowCountAndAddToLastOverlay();
-  }
+  PERFETTO_NO_INLINE void UpdateSelfOverlayAfterInsert();
 
   PERFETTO_NO_INLINE static std::vector<ColumnLegacy>
   CopyColumnsFromParentOrAddRootColumns(MacroTable* self,
-                                        const MacroTable* parent) {
-    std::vector<ColumnLegacy> columns;
-    if (parent) {
-      for (const ColumnLegacy& col : parent->columns()) {
-        columns.emplace_back(col, col.index_in_table(), col.overlay_index());
-      }
-    } else {
-      columns.emplace_back(ColumnLegacy::IdColumn(0, 0));
-      columns.emplace_back("type", &self->type_, ColumnLegacy::kNonNull, 1, 0);
-    }
-    return columns;
-  }
+                                        const MacroTable* parent);
 
   PERFETTO_NO_INLINE void OnConstructionCompletedRegularConstructor(
       std::initializer_list<RefPtr<column::StorageLayer>> storage_layers,
-      std::initializer_list<RefPtr<column::OverlayLayer>> null_layers) {
-    std::vector<RefPtr<column::OverlayLayer>> overlay_layers(
-        OverlayCount(parent_) + 1);
-    for (uint32_t i = 0; i < overlay_layers.size() - 1; ++i) {
-      PERFETTO_CHECK(overlays()[i].row_map().IsBitVector());
-      overlay_layers[i].reset(new column::SelectorOverlay(
-          overlays()[i].row_map().GetIfBitVector()));
-    }
-    Table::OnConstructionCompleted(storage_layers, null_layers,
-                                   std::move(overlay_layers));
-  }
+      std::initializer_list<RefPtr<column::OverlayLayer>> null_layers);
 
   template <typename T>
   PERFETTO_NO_INLINE static void AddColumnToVector(
@@ -180,43 +143,36 @@
 
  private:
   PERFETTO_NO_INLINE static std::vector<ColumnStorageOverlay>
-  EmptyOverlaysFromParent(const MacroTable* parent) {
-    std::vector<ColumnStorageOverlay> overlays(
-        parent ? parent->overlays().size() : 0);
-    for (auto& overlay : overlays) {
-      overlay = ColumnStorageOverlay(BitVector());
-    }
-    overlays.emplace_back();
-    return overlays;
-  }
+  EmptyOverlaysFromParent(const MacroTable* parent);
   PERFETTO_NO_INLINE static std::vector<ColumnStorageOverlay>
   SelectedOverlaysFromParent(const macros_internal::MacroTable& parent,
-                             const RowMap& rm) {
-    std::vector<ColumnStorageOverlay> overlays;
-    for (const auto& overlay : parent.overlays()) {
-      overlays.emplace_back(overlay.SelectRows(rm));
-      PERFETTO_DCHECK(overlays.back().size() == rm.size());
-    }
-    overlays.emplace_back(rm.size());
-    return overlays;
-  }
+                             const RowMap& rm);
 
   const MacroTable* parent_ = nullptr;
 };
 
+class BaseConstIterator {
+ public:
+  explicit operator bool() const;
+  BaseConstIterator& operator++();
+
+ protected:
+  explicit BaseConstIterator(const MacroTable* table, Table::Iterator iterator);
+
+  Table::Iterator iterator_;
+  const MacroTable* table_;
+};
+
 // Abstract iterator class for macro tables.
 // Extracted to allow sharing with view code.
 template <typename Iterator,
           typename MacroTable,
           typename RowNumber,
           typename ConstRowReference>
-class AbstractConstIterator {
+class AbstractConstIterator : public BaseConstIterator {
  public:
-  explicit operator bool() const { return bool(iterator_); }
-
   Iterator& operator++() {
-    ++iterator_;
-    return *this_it();
+    return static_cast<Iterator&>(BaseConstIterator::operator++());
   }
 
   // Returns a RowNumber for the current row.
@@ -226,37 +182,50 @@
 
   // Returns a ConstRowReference to the current row.
   ConstRowReference row_reference() const {
-    return ConstRowReference(table_, this_it()->CurrentRowNumber());
+    return ConstRowReference(table(), this_it()->CurrentRowNumber());
   }
 
  protected:
   explicit AbstractConstIterator(const MacroTable* table,
                                  Table::Iterator iterator)
-      : iterator_(std::move(iterator)), table_(table) {
-    static_assert(std::is_base_of<Table, MacroTable>::value,
-                  "Template param should be a subclass of Table.");
-  }
+      : BaseConstIterator(table, std::move(iterator)) {}
 
-  Table::Iterator iterator_;
-  const MacroTable* table_;
+  const MacroTable* table() const {
+    return static_cast<const MacroTable*>(table_);
+  }
 
  private:
   Iterator* this_it() { return static_cast<Iterator*>(this); }
   const Iterator* this_it() const { return static_cast<const Iterator*>(this); }
 };
 
+class BaseRowNumber {
+ public:
+  // Converts this object to the underlying int value.
+  uint32_t row_number() const { return row_number_; }
+
+  // Allows sorting + storage in a map/set.
+  bool operator<(const BaseRowNumber& other) const {
+    return row_number_ < other.row_number_;
+  }
+
+ protected:
+  explicit BaseRowNumber(uint32_t row_number) : row_number_(row_number) {}
+
+  uint32_t row_number_ = 0;
+};
+
 // Abstract RowNumber class for macro tables.
 // Extracted to allow sharing with view code.
 template <typename MacroTable,
           typename ConstRowReference,
           typename RowReference = void>
-class AbstractRowNumber {
+class AbstractRowNumber : public BaseRowNumber {
  public:
   // Converts this RowNumber to a RowReference for the given |table|.
-  template <
-      typename RR = RowReference,
-      typename = typename std::enable_if<!std::is_same<RR, void>::value>::type>
+  template <typename RR = RowReference>
   RR ToRowReference(MacroTable* table) const {
+    static_assert(!std::is_same_v<RR, void>);
     return RR(table, row_number_);
   }
 
@@ -265,36 +234,34 @@
     return ConstRowReference(&table, row_number_);
   }
 
-  // Converts this object to the underlying int value.
-  uint32_t row_number() const { return row_number_; }
-
-  // Allows sorting + storage in a map/set.
-  bool operator<(const AbstractRowNumber& other) const {
-    return row_number_ < other.row_number_;
-  }
-
  protected:
-  explicit AbstractRowNumber(uint32_t row_number) : row_number_(row_number) {}
+  explicit AbstractRowNumber(uint32_t row_number) : BaseRowNumber(row_number) {}
+};
 
- private:
+class BaseRowReference {
+ protected:
+  BaseRowReference(const MacroTable* table, uint32_t row_number);
+
+  const MacroTable* table_ = nullptr;
   uint32_t row_number_ = 0;
 };
 
 // Abstract ConstRowReference class for macro tables.
 // Extracted to allow sharing with view code.
 template <typename MacroTable, typename RowNumber>
-class AbstractConstRowReference {
+class AbstractConstRowReference : public BaseRowReference {
  public:
   // Converts this RowReference to a RowNumber object which is more memory
   // efficient to store.
   RowNumber ToRowNumber() { return RowNumber(row_number_); }
 
  protected:
-  AbstractConstRowReference(const MacroTable* table, uint32_t row_number)
-      : table_(table), row_number_(row_number) {}
+  const MacroTable* table() const {
+    return static_cast<const MacroTable*>(table_);
+  }
 
-  const MacroTable* table_ = nullptr;
-  uint32_t row_number_ = 0;
+  AbstractConstRowReference(const MacroTable* table, uint32_t row_number)
+      : BaseRowReference(table, row_number) {}
 };
 
 }  // namespace perfetto::trace_processor::macros_internal
diff --git a/src/trace_processor/tables/macros_unittest.cc b/src/trace_processor/tables/macros_unittest.cc
deleted file mode 100644
index f6414cd..0000000
--- a/src/trace_processor/tables/macros_unittest.cc
+++ /dev/null
@@ -1,473 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed 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 "src/trace_processor/tables/macros.h"
-
-#include "test/gtest_and_gmock.h"
-
-namespace perfetto {
-namespace trace_processor {
-namespace {
-
-// @param arg_set_id {@joinable args.arg_set_id}
-#define PERFETTO_TP_TEST_EVENT_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestEventTable, "event")                           \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)            \
-  C(int64_t, ts, Column::Flag::kSorted)                   \
-  C(int64_t, arg_set_id)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_EVENT_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_COUNTER_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestCounterTable, "counter")                         \
-  PARENT(PERFETTO_TP_TEST_EVENT_TABLE_DEF, C)               \
-  C(std::optional<double>, value)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_COUNTER_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestSliceTable, "slice")                           \
-  PARENT(PERFETTO_TP_TEST_EVENT_TABLE_DEF, C)             \
-  C(std::optional<int64_t>, dur)                          \
-  C(int64_t, depth)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_SLICE_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_CPU_SLICE_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestCpuSliceTable, "cpu_slice")                        \
-  PARENT(PERFETTO_TP_TEST_SLICE_TABLE_DEF, C)                 \
-  C(int64_t, cpu)                                             \
-  C(int64_t, priority)                                        \
-  C(StringPool::Id, end_state)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_CPU_SLICE_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_ARGS_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestArgsTable, "args")                            \
-  PARENT(PERFETTO_TP_ROOT_TABLE_PARENT_DEF, C)           \
-  C(uint32_t, arg_set_id, Column::Flag::kSetId | Column::Flag::kSorted)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_ARGS_TABLE_DEF);
-
-#define PERFETTO_TP_TEST_ARGS_CHILD_TABLE_DEF(NAME, PARENT, C) \
-  NAME(TestArgsChildTable, "args_child")                       \
-  PARENT(PERFETTO_TP_TEST_ARGS_TABLE_DEF, C)                   \
-  C(uint32_t, child_col)
-PERFETTO_TP_TABLE(PERFETTO_TP_TEST_ARGS_CHILD_TABLE_DEF);
-
-TestEventTable::~TestEventTable() = default;
-TestCounterTable::~TestCounterTable() = default;
-TestSliceTable::~TestSliceTable() = default;
-TestCpuSliceTable::~TestCpuSliceTable() = default;
-TestArgsTable::~TestArgsTable() = default;
-TestArgsChildTable::~TestArgsChildTable() = default;
-
-class TableMacrosUnittest : public ::testing::Test {
- protected:
-  StringPool pool_;
-
-  TestEventTable event_{&pool_, nullptr};
-  TestCounterTable counter_{&pool_, &event_};
-  TestSliceTable slice_{&pool_, &event_};
-  TestCpuSliceTable cpu_slice_{&pool_, &slice_};
-  TestArgsTable args_{&pool_, nullptr};
-  TestArgsChildTable args_child_{&pool_, &args_};
-};
-
-TEST_F(TableMacrosUnittest, Name) {
-  ASSERT_STREQ(TestEventTable::Name(), "event");
-  ASSERT_STREQ(TestSliceTable::Name(), "slice");
-  ASSERT_STREQ(TestCpuSliceTable::Name(), "cpu_slice");
-}
-
-TEST_F(TableMacrosUnittest, InsertParent) {
-  auto id = event_.Insert(TestEventTable::Row(100, 0)).id;
-  ASSERT_EQ(id.value, 0u);
-  ASSERT_EQ(event_.type().GetString(0), "event");
-  ASSERT_EQ(event_.ts()[0], 100);
-  ASSERT_EQ(event_.arg_set_id()[0], 0);
-
-  id = slice_.Insert(TestSliceTable::Row(200, 123, 10, 0)).id;
-  ASSERT_EQ(id.value, 1u);
-
-  ASSERT_EQ(event_.type().GetString(1), "slice");
-  ASSERT_EQ(event_.ts()[1], 200);
-  ASSERT_EQ(event_.arg_set_id()[1], 123);
-  ASSERT_EQ(slice_.type().GetString(0), "slice");
-  ASSERT_EQ(slice_.ts()[0], 200);
-  ASSERT_EQ(slice_.arg_set_id()[0], 123);
-  ASSERT_EQ(slice_.dur()[0], 10);
-  ASSERT_EQ(slice_.depth()[0], 0);
-
-  id = slice_.Insert(TestSliceTable::Row(210, 456, std::nullopt, 0)).id;
-  ASSERT_EQ(id.value, 2u);
-
-  ASSERT_EQ(event_.type().GetString(2), "slice");
-  ASSERT_EQ(event_.ts()[2], 210);
-  ASSERT_EQ(event_.arg_set_id()[2], 456);
-  ASSERT_EQ(slice_.type().GetString(1), "slice");
-  ASSERT_EQ(slice_.ts()[1], 210);
-  ASSERT_EQ(slice_.arg_set_id()[1], 456);
-  ASSERT_EQ(slice_.dur()[1], std::nullopt);
-  ASSERT_EQ(slice_.depth()[1], 0);
-}
-
-TEST_F(TableMacrosUnittest, InsertChild) {
-  event_.Insert(TestEventTable::Row(100, 0));
-  slice_.Insert(TestSliceTable::Row(200, 123, 10, 0));
-
-  auto reason = pool_.InternString("R");
-  auto id =
-      cpu_slice_.Insert(TestCpuSliceTable::Row(205, 456, 5, 1, 4, 1024, reason))
-          .id;
-  ASSERT_EQ(id.value, 2u);
-  ASSERT_EQ(event_.type().GetString(2), "cpu_slice");
-  ASSERT_EQ(event_.ts()[2], 205);
-  ASSERT_EQ(event_.arg_set_id()[2], 456);
-
-  ASSERT_EQ(slice_.type().GetString(1), "cpu_slice");
-  ASSERT_EQ(slice_.ts()[1], 205);
-  ASSERT_EQ(slice_.arg_set_id()[1], 456);
-  ASSERT_EQ(slice_.dur()[1], 5);
-  ASSERT_EQ(slice_.depth()[1], 1);
-
-  ASSERT_EQ(cpu_slice_.type().GetString(0), "cpu_slice");
-  ASSERT_EQ(cpu_slice_.ts()[0], 205);
-  ASSERT_EQ(cpu_slice_.arg_set_id()[0], 456);
-  ASSERT_EQ(cpu_slice_.dur()[0], 5);
-  ASSERT_EQ(cpu_slice_.depth()[0], 1);
-  ASSERT_EQ(cpu_slice_.cpu()[0], 4);
-  ASSERT_EQ(cpu_slice_.priority()[0], 1024);
-  ASSERT_EQ(cpu_slice_.end_state()[0], reason);
-  ASSERT_EQ(cpu_slice_.end_state().GetString(0), "R");
-}
-
-TEST_F(TableMacrosUnittest, NullableLongComparision) {
-  slice_.Insert({});
-
-  TestSliceTable::Row row;
-  row.dur = 100;
-  slice_.Insert(row);
-
-  row.dur = 101;
-  slice_.Insert(row);
-
-  row.dur = 200;
-  slice_.Insert(row);
-
-  slice_.Insert({});
-
-  Table out = slice_.Filter({slice_.dur().is_null()});
-  const auto* dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_EQ(dur->Get(0).type, SqlValue::kNull);
-  ASSERT_EQ(dur->Get(1).type, SqlValue::kNull);
-
-  out = slice_.Filter({slice_.dur().is_not_null()});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 3u);
-  ASSERT_EQ(dur->Get(0).long_value, 100);
-  ASSERT_EQ(dur->Get(1).long_value, 101);
-  ASSERT_EQ(dur->Get(2).long_value, 200);
-
-  out = slice_.Filter({slice_.dur().lt(101)});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_EQ(dur->Get(0).long_value, 100);
-
-  out = slice_.Filter({slice_.dur().eq(101)});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_EQ(dur->Get(0).long_value, 101);
-
-  out = slice_.Filter({slice_.dur().gt(101)});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_EQ(dur->Get(0).long_value, 200);
-
-  out = slice_.Filter({slice_.dur().ne(100)});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_EQ(dur->Get(0).long_value, 101);
-  ASSERT_EQ(dur->Get(1).long_value, 200);
-
-  out = slice_.Filter({slice_.dur().le(101)});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_EQ(dur->Get(0).long_value, 100);
-  ASSERT_EQ(dur->Get(1).long_value, 101);
-
-  out = slice_.Filter({slice_.dur().ge(101)});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_EQ(dur->Get(0).long_value, 101);
-  ASSERT_EQ(dur->Get(1).long_value, 200);
-}
-
-TEST_F(TableMacrosUnittest, NullableLongCompareWithDouble) {
-  slice_.Insert({});
-
-  TestSliceTable::Row row;
-  row.dur = 100;
-  slice_.Insert(row);
-
-  row.dur = std::numeric_limits<int64_t>::max();
-  slice_.Insert(row);
-
-  row.dur = std::numeric_limits<int64_t>::min();
-  slice_.Insert(row);
-
-  Table out = slice_.Filter({slice_.dur().eq_value(SqlValue::Double(100.0))});
-  const Column* dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_EQ(dur->Get(0).long_value, 100);
-
-  out = slice_.Filter({slice_.dur().le_value(SqlValue::Double(99.9999))});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_EQ(dur->Get(0).long_value, std::numeric_limits<int64_t>::min());
-
-  out = slice_.Filter({slice_.dur().ge_value(SqlValue::Double(99.9999))});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_EQ(dur->Get(0).long_value, 100);
-  ASSERT_EQ(dur->Get(1).long_value, std::numeric_limits<int64_t>::max());
-
-  out = slice_.Filter({slice_.dur().eq_value(
-      SqlValue::Double(std::numeric_limits<int64_t>::min()))});
-  dur = out.GetColumnByName("dur");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_EQ(dur->Get(0).long_value, std::numeric_limits<int64_t>::min());
-}
-
-TEST_F(TableMacrosUnittest, NullableLongCompareWrongType) {
-  slice_.Insert({});
-
-  TestSliceTable::Row row;
-  row.dur = 100;
-  slice_.Insert(row);
-
-  row.dur = 101;
-  slice_.Insert(row);
-
-  row.dur = 200;
-  slice_.Insert(row);
-
-  slice_.Insert({});
-
-  Table out = slice_.Filter({slice_.dur().ne_value(SqlValue())});
-  ASSERT_EQ(out.row_count(), 0u);
-
-  out = slice_.Filter({slice_.dur().eq_value(SqlValue::String("100"))});
-  ASSERT_EQ(out.row_count(), 0u);
-}
-
-TEST_F(TableMacrosUnittest, NullableDoubleComparision) {
-  counter_.Insert({});
-
-  TestCounterTable::Row row;
-  row.value = 100.0;
-  counter_.Insert(row);
-
-  row.value = 101.0;
-  counter_.Insert(row);
-
-  row.value = 200.0;
-  counter_.Insert(row);
-
-  counter_.Insert({});
-
-  Table out = counter_.Filter({counter_.value().is_null()});
-  const auto* value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_EQ(value->Get(0).type, SqlValue::kNull);
-  ASSERT_EQ(value->Get(1).type, SqlValue::kNull);
-
-  out = counter_.Filter({counter_.value().is_not_null()});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 3u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 101);
-  ASSERT_DOUBLE_EQ(value->Get(2).double_value, 200);
-
-  out = counter_.Filter({counter_.value().lt(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
-
-  out = counter_.Filter({counter_.value().eq(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
-
-  out = counter_.Filter({counter_.value().gt(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 200);
-
-  out = counter_.Filter({counter_.value().ne(100)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 200);
-
-  out = counter_.Filter({counter_.value().le(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 101);
-
-  out = counter_.Filter({counter_.value().ge(101)});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 101);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value, 200);
-}
-
-TEST_F(TableMacrosUnittest, NullableDoubleCompareWithLong) {
-  counter_.Insert({});
-
-  TestCounterTable::Row row;
-  row.value = 100.0;
-  counter_.Insert(row);
-
-  row.value = 99.9999;
-  counter_.Insert(row);
-
-  row.value = static_cast<double>(std::numeric_limits<int64_t>::min());
-  counter_.Insert(row);
-
-  Table out = counter_.Filter({counter_.value().eq_value(SqlValue::Long(100))});
-  const Column* value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 100.0);
-
-  out = counter_.Filter({counter_.value().lt_value(SqlValue::Long(100))});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value, 99.9999);
-  ASSERT_DOUBLE_EQ(value->Get(1).double_value,
-                   std::numeric_limits<int64_t>::min());
-
-  out = counter_.Filter({counter_.value().eq_value(
-      SqlValue::Long(std::numeric_limits<int64_t>::min()))});
-  value = out.GetColumnByName("value");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_DOUBLE_EQ(value->Get(0).double_value,
-                   std::numeric_limits<int64_t>::min());
-}
-
-TEST_F(TableMacrosUnittest, StringComparision) {
-  cpu_slice_.Insert({});
-
-  TestCpuSliceTable::Row row;
-  row.end_state = pool_.InternString("R");
-  cpu_slice_.Insert(row);
-
-  row.end_state = pool_.InternString("D");
-  cpu_slice_.Insert(row);
-
-  cpu_slice_.Insert({});
-
-  Table out = cpu_slice_.Filter({cpu_slice_.end_state().is_null()});
-  const auto* end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_EQ(end_state->Get(0).type, SqlValue::kNull);
-  ASSERT_EQ(end_state->Get(1).type, SqlValue::kNull);
-
-  out = cpu_slice_.Filter({cpu_slice_.end_state().is_not_null()});
-  end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_STREQ(end_state->Get(0).string_value, "R");
-  ASSERT_STREQ(end_state->Get(1).string_value, "D");
-
-  out = cpu_slice_.Filter({cpu_slice_.end_state().lt("R")});
-  end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_STREQ(end_state->Get(0).string_value, "D");
-
-  out = cpu_slice_.Filter({cpu_slice_.end_state().eq("D")});
-  end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_STREQ(end_state->Get(0).string_value, "D");
-
-  out = cpu_slice_.Filter({cpu_slice_.end_state().gt("D")});
-  end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_STREQ(end_state->Get(0).string_value, "R");
-
-  out = cpu_slice_.Filter({cpu_slice_.end_state().ne("D")});
-  end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_STREQ(end_state->Get(0).string_value, "R");
-
-  out = cpu_slice_.Filter({cpu_slice_.end_state().le("R")});
-  end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_STREQ(end_state->Get(0).string_value, "R");
-  ASSERT_STREQ(end_state->Get(1).string_value, "D");
-
-  out = cpu_slice_.Filter({cpu_slice_.end_state().ge("D")});
-  end_state = out.GetColumnByName("end_state");
-  ASSERT_EQ(out.row_count(), 2u);
-  ASSERT_STREQ(end_state->Get(0).string_value, "R");
-  ASSERT_STREQ(end_state->Get(1).string_value, "D");
-}
-
-TEST_F(TableMacrosUnittest, FilterIdThenOther) {
-  TestCpuSliceTable::Row row;
-  row.cpu = 1;
-  row.end_state = pool_.InternString("D");
-
-  cpu_slice_.Insert(row);
-  cpu_slice_.Insert(row);
-  cpu_slice_.Insert(row);
-
-  auto out =
-      cpu_slice_.Filter({cpu_slice_.id().eq(0), cpu_slice_.end_state().eq("D"),
-                         cpu_slice_.cpu().eq(1)});
-  const auto* end_state = out.GetColumnByName("end_state");
-  const auto* cpu = out.GetColumnByName("cpu");
-
-  ASSERT_EQ(out.row_count(), 1u);
-  ASSERT_EQ(cpu->Get(0).long_value, 1u);
-  ASSERT_STREQ(end_state->Get(0).string_value, "D");
-}
-
-TEST_F(TableMacrosUnittest, Sort) {
-  ASSERT_TRUE(event_.ts().IsSorted());
-
-  event_.Insert(TestEventTable::Row(0 /* ts */, 100 /* arg_set_id */));
-  event_.Insert(TestEventTable::Row(1 /* ts */, 1 /* arg_set_id */));
-  event_.Insert(TestEventTable::Row(2 /* ts */, 3 /* arg_set_id */));
-
-  Table out = event_.Sort({event_.arg_set_id().ascending()});
-  const auto* ts = out.GetColumnByName("ts");
-  const auto* arg_set_id = out.GetColumnByName("arg_set_id");
-
-  ASSERT_FALSE(ts->IsSorted());
-  ASSERT_TRUE(arg_set_id->IsSorted());
-
-  ASSERT_EQ(arg_set_id->Get(0).long_value, 1);
-  ASSERT_EQ(arg_set_id->Get(1).long_value, 3);
-  ASSERT_EQ(arg_set_id->Get(2).long_value, 100);
-}
-
-TEST_F(TableMacrosUnittest, ChildDoesntInheritArgsSetFlag) {
-  ASSERT_FALSE(args_child_.arg_set_id().IsSetId());
-  ASSERT_FALSE(TestArgsChildTable::ComputeStaticSchema()
-                   .columns[args_child_.arg_set_id().index_in_table()]
-                   .is_set_id);
-}
-
-}  // namespace
-}  // namespace trace_processor
-}  // namespace perfetto
diff --git a/src/trace_processor/tables/py_tables_benchmark.cc b/src/trace_processor/tables/py_tables_benchmark.cc
index ed19da1..2491ca9 100644
--- a/src/trace_processor/tables/py_tables_benchmark.cc
+++ b/src/trace_processor/tables/py_tables_benchmark.cc
@@ -108,7 +108,7 @@
     root.Insert({});
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterRootId)->Apply(TableFilterArgs);
@@ -129,7 +129,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterRootIdAndOther)->Apply(TableFilterArgs);
@@ -148,7 +148,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterChildId)->Apply(TableFilterArgs);
@@ -172,7 +172,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterChildIdAndSortedInRoot)->Apply(TableFilterArgs);
@@ -193,7 +193,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterRootNonNullEqMatchMany)->Apply(TableFilterArgs);
@@ -216,7 +216,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterRootMultipleNonNull)->Apply(TableFilterArgs);
@@ -241,7 +241,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterRootNullableEqMatchMany)->Apply(TableFilterArgs);
@@ -265,7 +265,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterChildNonNullEqMatchMany)->Apply(TableFilterArgs);
@@ -292,7 +292,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterChildNullableEqMatchMany)->Apply(TableFilterArgs);
@@ -317,7 +317,7 @@
   }
 
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterChildNonNullEqMatchManyInParent)
@@ -343,7 +343,7 @@
   Query q;
   q.constraints = {child.root_nullable().eq(1)};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterChildNullableEqMatchManyInParent)
@@ -364,7 +364,7 @@
   Query q;
   q.constraints = {root.root_sorted().eq(22)};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterParentSortedEq)->Apply(TableFilterArgs);
@@ -391,7 +391,7 @@
   q.constraints = {root.root_sorted().eq(last_group),
                    root.root_non_null().eq(size - 1)};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterParentSortedAndOther)->Apply(TableFilterArgs);
@@ -413,7 +413,7 @@
   Query q;
   q.constraints = {child.child_sorted().eq(22)};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterChildSortedEq)->Apply(TableFilterArgs);
@@ -438,7 +438,7 @@
   Query q;
   q.constraints = {child.root_sorted().eq(22)};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableFilterChildSortedEqInParent)->Apply(TableFilterArgs);
@@ -461,7 +461,7 @@
   Query q;
   q.orders = {root.root_non_null().ascending()};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableSortRootNonNull)->Apply(TableSortArgs);
@@ -485,7 +485,7 @@
   Query q;
   q.orders = {root.root_nullable().ascending()};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(root.ApplyAndIterateRows(root.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(root.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableSortRootNullable)->Apply(TableSortArgs);
@@ -515,7 +515,7 @@
   Query q;
   q.orders = {child.root_non_null().ascending()};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableSortChildNonNullInParent)->Apply(TableSortArgs);
@@ -547,7 +547,7 @@
   Query q;
   q.orders = {child.root_nullable().ascending()};
   for (auto _ : state) {
-    benchmark::DoNotOptimize(child.ApplyAndIterateRows(child.QueryToRowMap(q)));
+    benchmark::DoNotOptimize(child.FilterToIterator(q));
   }
 }
 BENCHMARK(BM_TableSortChildNullableInParent)->Apply(TableSortArgs);
diff --git a/src/trace_processor/tables/py_tables_unittest.cc b/src/trace_processor/tables/py_tables_unittest.cc
index 1f4a4ef..344e17b 100644
--- a/src/trace_processor/tables/py_tables_unittest.cc
+++ b/src/trace_processor/tables/py_tables_unittest.cc
@@ -20,6 +20,7 @@
 
 #include "src/trace_processor/containers/string_pool.h"
 #include "src/trace_processor/db/column.h"
+#include "src/trace_processor/db/column/types.h"
 #include "src/trace_processor/db/column_storage.h"
 #include "src/trace_processor/tables/py_tables_unittest_py.h"
 
@@ -73,9 +74,9 @@
 TEST_F(PyTablesUnittest, InsertEvent) {
   event_.Insert(TestEventTable::Row(100, 0));
 
-  ASSERT_EQ(event_.type().GetString(0).ToStdString(), "event");
-  ASSERT_EQ(event_.ts()[0], 100);
-  ASSERT_EQ(event_.arg_set_id()[0], 0u);
+  ASSERT_EQ(pool_.Get(event_[0].type()).ToStdString(), "event");
+  ASSERT_EQ(event_[0].ts(), 100);
+  ASSERT_EQ(event_[0].arg_set_id(), 0u);
 }
 
 TEST_F(PyTablesUnittest, InsertEventSpecifyCols) {
@@ -84,16 +85,16 @@
   row.arg_set_id = 0;
   event_.Insert(row);
 
-  ASSERT_EQ(event_.type().GetString(0).ToStdString(), "event");
-  ASSERT_EQ(event_.ts()[0], 100);
-  ASSERT_EQ(event_.arg_set_id()[0], 0u);
+  ASSERT_EQ(pool_.Get(event_[0].type()).ToStdString(), "event");
+  ASSERT_EQ(event_[0].ts(), 100);
+  ASSERT_EQ(event_[0].arg_set_id(), 0u);
 }
 
 TEST_F(PyTablesUnittest, MutableColumn) {
   event_.Insert(TestEventTable::Row(100, 0));
 
-  ASSERT_EQ((*event_.mutable_ts())[0], 100);
-  ASSERT_EQ((*event_.mutable_arg_set_id())[0], 0u);
+  ASSERT_EQ(event_[0].ts(), 100);
+  ASSERT_EQ(event_[0].arg_set_id(), 0u);
 }
 
 TEST_F(PyTablesUnittest, ShrinkToFit) {
@@ -142,32 +143,32 @@
   slice_.Insert(TestSliceTable::Row(200, 3, 20));
 
   ASSERT_EQ(event_.row_count(), 4u);
-  ASSERT_EQ(event_.id()[0], TestEventTable::Id{0});
-  ASSERT_EQ(event_.type().GetString(0), "event");
-  ASSERT_EQ(event_.ts()[0], 50);
+  ASSERT_EQ(event_[0].id(), TestEventTable::Id{0});
+  ASSERT_EQ(pool_.Get(event_[0].type()), "event");
+  ASSERT_EQ(event_[0].ts(), 50);
 
-  ASSERT_EQ(event_.id()[1], TestEventTable::Id{1});
-  ASSERT_EQ(event_.type().GetString(1), "slice");
-  ASSERT_EQ(event_.ts()[1], 100);
+  ASSERT_EQ(event_[1].id(), TestEventTable::Id{1});
+  ASSERT_EQ(pool_.Get(event_[1].type()), "slice");
+  ASSERT_EQ(event_[1].ts(), 100);
 
-  ASSERT_EQ(event_.id()[2], TestEventTable::Id{2});
-  ASSERT_EQ(event_.type().GetString(2), "event");
-  ASSERT_EQ(event_.ts()[2], 150);
+  ASSERT_EQ(event_[2].id(), TestEventTable::Id{2});
+  ASSERT_EQ(pool_.Get(event_[2].type()), "event");
+  ASSERT_EQ(event_[2].ts(), 150);
 
-  ASSERT_EQ(event_.id()[3], TestEventTable::Id{3});
-  ASSERT_EQ(event_.type().GetString(3), "slice");
-  ASSERT_EQ(event_.ts()[3], 200);
+  ASSERT_EQ(event_[3].id(), TestEventTable::Id{3});
+  ASSERT_EQ(pool_.Get(event_[3].type()), "slice");
+  ASSERT_EQ(event_[3].ts(), 200);
 
   ASSERT_EQ(slice_.row_count(), 2u);
-  ASSERT_EQ(slice_.id()[0], TestEventTable::Id{1});
-  ASSERT_EQ(slice_.type().GetString(0), "slice");
-  ASSERT_EQ(slice_.ts()[0], 100);
-  ASSERT_EQ(slice_.dur()[0], 10);
+  ASSERT_EQ(slice_[0].id(), TestEventTable::Id{1});
+  ASSERT_EQ(pool_.Get(slice_[0].type()), "slice");
+  ASSERT_EQ(slice_[0].ts(), 100);
+  ASSERT_EQ(slice_[0].dur(), 10);
 
-  ASSERT_EQ(slice_.id()[1], TestEventTable::Id{3});
-  ASSERT_EQ(slice_.type().GetString(1), "slice");
-  ASSERT_EQ(slice_.ts()[1], 200);
-  ASSERT_EQ(slice_.dur()[1], 20);
+  ASSERT_EQ(slice_[1].id(), TestEventTable::Id{3});
+  ASSERT_EQ(pool_.Get(slice_[1].type()), "slice");
+  ASSERT_EQ(slice_[1].ts(), 200);
+  ASSERT_EQ(slice_[1].dur(), 20);
 }
 
 TEST_F(PyTablesUnittest, Extend) {
@@ -182,24 +183,12 @@
 
   auto slice_ext = TestSliceTable::ExtendParent(event_, std::move(dur));
   ASSERT_EQ(slice_ext->row_count(), 3u);
-  ASSERT_EQ(
-      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(0).AsLong(),
-      50);
-  ASSERT_EQ(
-      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(0).AsLong(),
-      512);
-  ASSERT_EQ(
-      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(1).AsLong(),
-      100);
-  ASSERT_EQ(
-      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(1).AsLong(),
-      1024);
-  ASSERT_EQ(
-      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(2).AsLong(),
-      150);
-  ASSERT_EQ(
-      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(2).AsLong(),
-      2048);
+  ASSERT_EQ((*slice_ext)[0].ts(), 50);
+  ASSERT_EQ((*slice_ext)[0].dur(), 512);
+  ASSERT_EQ((*slice_ext)[1].ts(), 100);
+  ASSERT_EQ((*slice_ext)[1].dur(), 1024);
+  ASSERT_EQ((*slice_ext)[2].ts(), 150);
+  ASSERT_EQ((*slice_ext)[2].dur(), 2048);
 }
 
 TEST_F(PyTablesUnittest, SelectAndExtend) {
@@ -215,12 +204,8 @@
   auto slice_ext = TestSliceTable::SelectAndExtendParent(
       event_, std::move(rows), std::move(dur));
   ASSERT_EQ(slice_ext->row_count(), 1u);
-  ASSERT_EQ(
-      slice_ext->columns()[TestSliceTable::ColumnIndex::ts].Get(0).AsLong(),
-      100);
-  ASSERT_EQ(
-      slice_ext->columns()[TestSliceTable::ColumnIndex::dur].Get(0).AsLong(),
-      1024);
+  ASSERT_EQ((*slice_ext)[0].ts(), 100);
+  ASSERT_EQ((*slice_ext)[0].dur(), 1024);
 }
 
 TEST_F(PyTablesUnittest, SetIdColumns) {
@@ -245,56 +230,47 @@
     static constexpr uint32_t kFilterArgSetId = 1;
     Query q;
     q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
-    auto res = table.QueryToRowMap(q);
-    ASSERT_TRUE(res.empty());
+    auto res = table.FilterToIterator(q);
+    ASSERT_TRUE(!res);
   }
   {
     static constexpr uint32_t kFilterArgSetId = 9;
     Query q;
     q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
-    auto res = table.QueryToRowMap(q);
-    ASSERT_TRUE(res.empty());
+    auto it = table.FilterToIterator(q);
+    ASSERT_TRUE(!it);
   }
 
-  auto arg_set_id_col_idx =
-      static_cast<uint32_t>(TestArgsTable::ColumnIndex::arg_set_id);
-
   // Verify that filtering equality for real arg set ids works as expected.
   {
     static constexpr uint32_t kFilterArgSetId = 4;
     Query q;
     q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
-    auto res = table.QueryToRowMap(q);
-    ASSERT_EQ(res.size(), 4u);
-    for (auto it = table.ApplyAndIterateRows(std::move(res)); it; ++it) {
-      auto arg_set_id =
-          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
-      ASSERT_EQ(arg_set_id, kFilterArgSetId);
+    uint32_t cnt = 0;
+    for (auto it = table.FilterToIterator(q); it; ++it, ++cnt) {
+      ASSERT_EQ(it.arg_set_id(), kFilterArgSetId);
     }
+    ASSERT_EQ(cnt, 4u);
   }
   {
     static constexpr uint32_t kFilterArgSetId = 0;
     Query q;
     q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
-    auto res = table.QueryToRowMap(q);
-    ASSERT_EQ(res.size(), 2u);
-    for (auto it = table.ApplyAndIterateRows(std::move(res)); it; ++it) {
-      auto arg_set_id =
-          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
-      ASSERT_EQ(arg_set_id, kFilterArgSetId);
+    uint32_t cnt = 0;
+    for (auto it = table.FilterToIterator(q); it; ++it, ++cnt) {
+      ASSERT_EQ(it.arg_set_id(), kFilterArgSetId);
     }
+    ASSERT_EQ(cnt, 2u);
   }
   {
     static constexpr uint32_t kFilterArgSetId = 8;
     Query q;
     q.constraints = {table.arg_set_id().eq(kFilterArgSetId)};
-    auto res = table.QueryToRowMap(q);
-    ASSERT_EQ(res.size(), 1u);
-    for (auto it = table.ApplyAndIterateRows(std::move(res)); it; ++it) {
-      auto arg_set_id =
-          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
-      ASSERT_EQ(arg_set_id, kFilterArgSetId);
+    uint32_t cnt = 0;
+    for (auto it = table.FilterToIterator(q); it; ++it, ++cnt) {
+      ASSERT_EQ(it.arg_set_id(), kFilterArgSetId);
     }
+    ASSERT_EQ(cnt, 1u);
   }
 
   // Verify that filtering equality for arg set ids after filtering another
@@ -304,13 +280,11 @@
     Query q;
     q.constraints = {table.int_value().eq(200),
                      table.arg_set_id().eq(kFilterArgSetId)};
-    auto res = table.QueryToRowMap(q);
-    ASSERT_EQ(res.size(), 2u);
-    for (auto it = table.ApplyAndIterateRows(std::move(res)); it; ++it) {
-      uint32_t arg_set_id =
-          static_cast<uint32_t>(it.Get(arg_set_id_col_idx).AsLong());
-      ASSERT_EQ(arg_set_id, kFilterArgSetId);
+    uint32_t cnt = 0;
+    for (auto it = table.FilterToIterator(q); it; ++it, ++cnt) {
+      ASSERT_EQ(it.arg_set_id(), kFilterArgSetId);
     }
+    ASSERT_EQ(cnt, 2u);
   }
 }
 
diff --git a/src/trace_processor/trace_processor_impl.cc b/src/trace_processor/trace_processor_impl.cc
index 2536e87..c4a1cb2 100644
--- a/src/trace_processor/trace_processor_impl.cc
+++ b/src/trace_processor/trace_processor_impl.cc
@@ -302,6 +302,61 @@
   return modules;
 }
 
+std::pair<int64_t, int64_t> GetTraceTimestampBoundsNs(
+    const TraceStorage& storage) {
+  int64_t start_ns = std::numeric_limits<int64_t>::max();
+  int64_t end_ns = std::numeric_limits<int64_t>::min();
+  for (auto it = storage.raw_table().IterateRows(); it; ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts(), end_ns);
+  }
+  for (auto it = storage.sched_slice_table().IterateRows(); it; ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts() + it.dur(), end_ns);
+  }
+  for (auto it = storage.counter_table().IterateRows(); it; ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts(), end_ns);
+  }
+  for (auto it = storage.slice_table().IterateRows(); it; ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts() + it.dur(), end_ns);
+  }
+  for (auto it = storage.heap_profile_allocation_table().IterateRows(); it;
+       ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts(), end_ns);
+  }
+  for (auto it = storage.thread_state_table().IterateRows(); it; ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts() + it.dur(), end_ns);
+  }
+  for (auto it = storage.android_log_table().IterateRows(); it; ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts(), end_ns);
+  }
+  for (auto it = storage.heap_graph_object_table().IterateRows(); it; ++it) {
+    start_ns = std::min(it.graph_sample_ts(), start_ns);
+    end_ns = std::max(it.graph_sample_ts(), end_ns);
+  }
+  for (auto it = storage.perf_sample_table().IterateRows(); it; ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts(), end_ns);
+  }
+  for (auto it = storage.cpu_profile_stack_sample_table().IterateRows(); it;
+       ++it) {
+    start_ns = std::min(it.ts(), start_ns);
+    end_ns = std::max(it.ts(), end_ns);
+  }
+  if (start_ns == std::numeric_limits<int64_t>::max()) {
+    return std::make_pair(0, 0);
+  }
+  if (start_ns == end_ns) {
+    end_ns += 1;
+  }
+  return std::make_pair(start_ns, end_ns);
+}
+
 }  // namespace
 
 TraceProcessorImpl::TraceProcessorImpl(const Config& cfg)
@@ -403,7 +458,7 @@
 void TraceProcessorImpl::Flush() {
   TraceProcessorStorageImpl::Flush();
   BuildBoundsTable(engine_->sqlite_engine()->db(),
-                   context_.storage->GetTraceTimestampBoundsNs());
+                   GetTraceTimestampBoundsNs(*context_.storage));
 }
 
 base::Status TraceProcessorImpl::NotifyEndOfFile() {
@@ -431,7 +486,7 @@
   // trace bounds: this is important for parsers like ninja which wait until
   // the end to flush all their data.
   BuildBoundsTable(engine_->sqlite_engine()->db(),
-                   context_.storage->GetTraceTimestampBoundsNs());
+                   GetTraceTimestampBoundsNs(*context_.storage));
 
   TraceProcessorStorageImpl::DestroyContext();
   return base::OkStatus();
@@ -972,7 +1027,7 @@
   }
 
   // Fill trace bounds table.
-  BuildBoundsTable(db, context_.storage->GetTraceTimestampBoundsNs());
+  BuildBoundsTable(db, GetTraceTimestampBoundsNs(*context_.storage));
 }
 
 namespace {
diff --git a/test/gtest_and_gmock.h b/test/gtest_and_gmock.h
index 24d8bed..69c112a 100644
--- a/test/gtest_and_gmock.h
+++ b/test/gtest_and_gmock.h
@@ -46,12 +46,14 @@
 
 #endif  // defined(__clang__)
 
-#include <gmock/gmock-matchers.h>       // IWYU pragma: export
-#include <gmock/gmock-more-matchers.h>  // IWYU pragma: export
-#include <gmock/gmock-spec-builders.h>  // IWYU pragma: export
-#include <gmock/gmock.h>                // IWYU pragma: export
-#include <gtest/gtest-matchers.h>       // IWYU pragma: export
-#include <gtest/gtest.h>                // IWYU pragma: export
+#include <gmock/gmock-actions.h>          // IWYU pragma: export
+#include <gmock/gmock-function-mocker.h>  // IWYU pragma: export
+#include <gmock/gmock-matchers.h>         // IWYU pragma: export
+#include <gmock/gmock-more-matchers.h>    // IWYU pragma: export
+#include <gmock/gmock-spec-builders.h>    // IWYU pragma: export
+#include <gmock/gmock.h>                  // IWYU pragma: export
+#include <gtest/gtest-matchers.h>         // IWYU pragma: export
+#include <gtest/gtest.h>                  // IWYU pragma: export
 
 #if defined(__GNUC__) || defined(__clang__)
 #pragma GCC diagnostic pop
diff --git a/test/trace_processor/diff_tests/metrics/android/tests.py b/test/trace_processor/diff_tests/metrics/android/tests.py
index 03619ec..76508c2 100644
--- a/test/trace_processor/diff_tests/metrics/android/tests.py
+++ b/test/trace_processor/diff_tests/metrics/android/tests.py
@@ -425,9 +425,9 @@
   def test_wattson_estimate_output(self):
     return DiffTestBlueprint(
         trace=DataPath('wattson_eos_suspend.pb'),
-        query=Metric("wattson_trace_estimate"),
+        query=Metric("wattson_trace_rails"),
         out=Csv("""
-        wattson_trace_estimate {
+        wattson_trace_rails {
           metric_version: 2
           period_info {
             period_id: 1
diff --git a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts
index 14065f3..f1c2b8b 100644
--- a/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts
+++ b/ui/src/plugins/dev.perfetto.PinAndroidPerfMetrics/handlers/pinCujScoped.ts
@@ -145,7 +145,7 @@
 
   private async findFirstJank(
     ctx: PluginContextTrace,
-  ): Promise<SliceIdentifier> {
+  ): Promise<SliceIdentifier | undefined> {
     const queryForFirstJankyFrame = `
       SELECT slice_id, track_id, ts, dur FROM slice
         WHERE type = "actual_frame_timeline_slice"
@@ -155,6 +155,9 @@
         AS VARCHAR(20) );
     `;
     const queryResult = await ctx.engine.query(queryForFirstJankyFrame);
+    if (queryResult.numRows() === 0) {
+      return undefined;
+    }
     const row = queryResult.firstRow({
       slice_id: NUM,
       track_id: NUM,
@@ -172,7 +175,9 @@
 
   private async focusOnFirstJank(ctx: PluginContextTrace) {
     const slice = await this.findFirstJank(ctx);
-    focusOnSlice(slice);
+    if (slice) {
+      focusOnSlice(slice);
+    }
   }
 }