diff --git a/src/storage/v2/indices.cpp b/src/storage/v2/indices.cpp
index c3ce80211..6a25cec64 100644
--- a/src/storage/v2/indices.cpp
+++ b/src/storage/v2/indices.cpp
@@ -295,23 +295,42 @@ bool CurrentVersionHasLabelProperty(Vertex *vertex, LabelId label,
 
 void LabelIndex::UpdateOnAddLabel(LabelId label, Vertex *vertex,
                                   const Transaction &tx) {
-  GetOrCreateStorage(label)->access().insert(Entry{vertex, tx.start_timestamp});
+  auto it = index_.find(label);
+  if (it == index_.end()) return;
+  it->second.access().insert(Entry{vertex, tx.start_timestamp});
+}
+
+bool LabelIndex::CreateIndex(LabelId label,
+                             utils::SkipList<Vertex>::Accessor vertices) {
+  auto [it, emplaced] =
+      index_.emplace(std::piecewise_construct, std::forward_as_tuple(label),
+                     std::forward_as_tuple());
+  if (!emplaced) {
+    // Index already exists.
+    return false;
+  }
+  auto acc = it->second.access();
+  for (Vertex &vertex : vertices) {
+    if (vertex.deleted || !utils::Contains(vertex.labels, label)) {
+      continue;
+    }
+    acc.insert(Entry{&vertex, 0});
+  }
+  return true;
 }
 
 std::vector<LabelId> LabelIndex::ListIndices() const {
   std::vector<LabelId> ret;
   ret.reserve(index_.size());
-  auto acc = index_.access();
-  for (const auto &item : acc) {
-    ret.push_back(item.label);
+  for (const auto &item : index_) {
+    ret.push_back(item.first);
   }
   return ret;
 }
 
 void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
-  auto index_acc = index_.access();
-  for (auto &label_storage : index_acc) {
-    auto vertices_acc = label_storage.vertices.access();
+  for (auto &label_storage : index_) {
+    auto vertices_acc = label_storage.second.access();
     for (auto it = vertices_acc.begin(); it != vertices_acc.end();) {
       auto next_it = it;
       ++next_it;
@@ -322,7 +341,7 @@ void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
       }
 
       if ((next_it != vertices_acc.end() && it->vertex == next_it->vertex) ||
-          !AnyVersionHasLabel(it->vertex, label_storage.label,
+          !AnyVersionHasLabel(it->vertex, label_storage.first,
                               oldest_active_start_timestamp)) {
         vertices_acc.remove(*it);
       }
@@ -332,18 +351,6 @@ void LabelIndex::RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp) {
   }
 }
 
-utils::SkipList<LabelIndex::Entry> *LabelIndex::GetOrCreateStorage(
-    LabelId label) {
-  auto acc = index_.access();
-  auto it = acc.find(label);
-  if (it == acc.end()) {
-    LabelStorage label_storage{.label = label,
-                               .vertices = utils::SkipList<Entry>()};
-    it = acc.insert(std::move(label_storage)).first;
-  }
-  return &it->vertices;
-}
-
 LabelIndex::Iterable::Iterator::Iterator(
     Iterable *self, utils::SkipList<Entry>::Iterator index_iterator)
     : self_(self),
diff --git a/src/storage/v2/indices.hpp b/src/storage/v2/indices.hpp
index c11f6138c..3b3e5cc73 100644
--- a/src/storage/v2/indices.hpp
+++ b/src/storage/v2/indices.hpp
@@ -45,6 +45,15 @@ class LabelIndex {
   /// @throw std::bad_alloc
   void UpdateOnAddLabel(LabelId label, Vertex *vertex, const Transaction &tx);
 
+  /// @throw std::bad_alloc
+  bool CreateIndex(LabelId label, utils::SkipList<Vertex>::Accessor vertices);
+
+  bool DropIndex(LabelId label) { return index_.erase(label) > 0; }
+
+  bool IndexExists(LabelId label) const {
+    return index_.find(label) != index_.end();
+  }
+
   std::vector<LabelId> ListIndices() const;
 
   void RemoveObsoleteEntries(uint64_t oldest_active_start_timestamp);
@@ -90,22 +99,23 @@ class LabelIndex {
   };
 
   /// Returns an self with vertices visible from the given transaction.
-  /// @throw std::bad_alloc
   Iterable Vertices(LabelId label, View view, Transaction *transaction) {
-    return Iterable(GetOrCreateStorage(label)->access(), label, view,
-                    transaction, indices_);
+    auto it = index_.find(label);
+    CHECK(it != index_.end())
+        << "Index for label " << label.AsUint() << " doesn't exist";
+    return Iterable(it->second.access(), label, view, transaction, indices_);
   }
 
   int64_t ApproximateVertexCount(LabelId label) {
-    return GetOrCreateStorage(label)->size();
+    auto it = index_.find(label);
+    CHECK(it != index_.end())
+        << "Index for label " << label.AsUint() << " doesn't exist";
+    return it->second.size();
   }
 
  private:
-  utils::SkipList<LabelStorage> index_;
   Indices *indices_;
-
-  /// @throw std::bad_alloc
-  utils::SkipList<Entry> *GetOrCreateStorage(LabelId label);
+  std::map<LabelId, utils::SkipList<Entry>> index_;
 };
 
 class LabelPropertyIndex {
@@ -140,7 +150,7 @@ class LabelPropertyIndex {
     return index_.erase({label, property}) > 0;
   }
 
-  bool IndexExists(LabelId label, PropertyId property) {
+  bool IndexExists(LabelId label, PropertyId property) const {
     return index_.find({label, property}) != index_.end();
   }
 
diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp
index 42d21a580..4e2f60e1f 100644
--- a/src/storage/v2/storage.hpp
+++ b/src/storage/v2/storage.hpp
@@ -182,7 +182,6 @@ class Storage final {
                                                   &storage_->indices_));
     }
 
-    /// @throw std::bad_alloc raised in Index::Vertices
     VerticesIterable Vertices(LabelId label, View view);
 
     /// @throw std::bad_alloc raised in Index::Vertices
@@ -297,6 +296,12 @@ class Storage final {
   /// @throw std::bad_alloc if unable to insert a new mapping
   EdgeTypeId NameToEdgeType(const std::string &name);
 
+  /// @throw std::bad_alloc
+  bool CreateIndex(LabelId label) {
+    std::unique_lock<utils::RWLock> storage_guard(main_lock_);
+    return indices_.label_index.CreateIndex(label, vertices_.access());
+  }
+
   /// @throw std::bad_alloc
   bool CreateIndex(LabelId label, PropertyId property) {
     std::unique_lock<utils::RWLock> storage_guard(main_lock_);
@@ -304,12 +309,21 @@ class Storage final {
                                                      vertices_.access());
   }
 
+  bool DropIndex(LabelId label) {
+    std::unique_lock<utils::RWLock> storage_guard(main_lock_);
+    return indices_.label_index.DropIndex(label);
+  }
+
   bool DropIndex(LabelId label, PropertyId property) {
     std::unique_lock<utils::RWLock> storage_guard(main_lock_);
     return indices_.label_property_index.DropIndex(label, property);
   }
 
-  bool LabelPropertyIndexExists(LabelId label, PropertyId property) {
+  bool LabelIndexExists(LabelId label) const {
+    return indices_.label_index.IndexExists(label);
+  }
+
+  bool LabelPropertyIndexExists(LabelId label, PropertyId property) const {
     return indices_.label_property_index.IndexExists(label, property);
   }
 
diff --git a/tests/unit/query_plan_v2_create_set_remove_delete.cpp b/tests/unit/query_plan_v2_create_set_remove_delete.cpp
index 2975d5be0..9c0722cf3 100644
--- a/tests/unit/query_plan_v2_create_set_remove_delete.cpp
+++ b/tests/unit/query_plan_v2_create_set_remove_delete.cpp
@@ -93,6 +93,7 @@ TEST(QueryPlan, ScanAll) {
 TEST(QueryPlan, ScanAllByLabel) {
   storage::Storage db;
   auto label = db.NameToLabel("label");
+  ASSERT_TRUE(db.CreateIndex(label));
   {
     auto dba = db.Access();
     // Add some unlabeled vertices
diff --git a/tests/unit/storage_v2_gc.cpp b/tests/unit/storage_v2_gc.cpp
index e34317be0..df7d20612 100644
--- a/tests/unit/storage_v2_gc.cpp
+++ b/tests/unit/storage_v2_gc.cpp
@@ -167,6 +167,8 @@ TEST(StorageV2Gc, Indices) {
       storage::Config{.gc = {.type = storage::Config::Gc::Type::PERIODIC,
                              .interval = std::chrono::milliseconds(100)}});
 
+  ASSERT_TRUE(storage.CreateIndex(storage.NameToLabel("label")));
+
   {
     auto acc0 = storage.Access();
     for (uint64_t i = 0; i < 1000; ++i) {
diff --git a/tests/unit/storage_v2_indices.cpp b/tests/unit/storage_v2_indices.cpp
index 3e64e5839..9097fb4c4 100644
--- a/tests/unit/storage_v2_indices.cpp
+++ b/tests/unit/storage_v2_indices.cpp
@@ -48,6 +48,154 @@ class IndexTest : public testing::Test {
   int vertex_id;
 };
 
+// NOLINTNEXTLINE(hicpp-special-member-functions)
+TEST_F(IndexTest, LabelIndexCreate) {
+  EXPECT_FALSE(storage.LabelIndexExists(label1));
+  EXPECT_EQ(storage.ListAllIndices().label.size(), 0);
+
+  {
+    auto acc = storage.Access();
+    for (int i = 0; i < 10; ++i) {
+      auto vertex = CreateVertex(&acc);
+      ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2));
+    }
+    ASSERT_NO_ERROR(acc.Commit());
+  }
+
+  EXPECT_TRUE(storage.CreateIndex(label1));
+
+  {
+    auto acc = storage.Access();
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
+                UnorderedElementsAre(1, 3, 5, 7, 9));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9));
+  }
+
+  {
+    auto acc = storage.Access();
+    for (int i = 10; i < 20; ++i) {
+      auto vertex = CreateVertex(&acc);
+      ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2));
+    }
+
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
+                UnorderedElementsAre(1, 3, 5, 7, 9));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19));
+
+    acc.AdvanceCommand();
+
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19));
+
+    acc.Abort();
+  }
+
+  {
+    auto acc = storage.Access();
+    for (int i = 10; i < 20; ++i) {
+      auto vertex = CreateVertex(&acc);
+      ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2));
+    }
+
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
+                UnorderedElementsAre(1, 3, 5, 7, 9));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29));
+
+    acc.AdvanceCommand();
+
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29));
+
+    ASSERT_NO_ERROR(acc.Commit());
+  }
+
+  {
+    auto acc = storage.Access();
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29));
+
+    acc.AdvanceCommand();
+
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 21, 23, 25, 27, 29));
+
+    ASSERT_NO_ERROR(acc.Commit());
+  }
+}
+
+// NOLINTNEXTLINE(hicpp-special-member-functions)
+TEST_F(IndexTest, LabelIndexDrop) {
+  EXPECT_FALSE(storage.LabelIndexExists(label1));
+  EXPECT_EQ(storage.ListAllIndices().label.size(), 0);
+
+  {
+    auto acc = storage.Access();
+    for (int i = 0; i < 10; ++i) {
+      auto vertex = CreateVertex(&acc);
+      ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2));
+    }
+    ASSERT_NO_ERROR(acc.Commit());
+  }
+
+  EXPECT_TRUE(storage.CreateIndex(label1));
+
+  {
+    auto acc = storage.Access();
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
+                UnorderedElementsAre(1, 3, 5, 7, 9));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9));
+  }
+
+  EXPECT_TRUE(storage.DropIndex(label1));
+  EXPECT_FALSE(storage.LabelIndexExists(label1));
+  EXPECT_EQ(storage.ListAllIndices().label.size(), 0);
+
+  EXPECT_FALSE(storage.DropIndex(label1));
+  EXPECT_FALSE(storage.LabelIndexExists(label1));
+  EXPECT_EQ(storage.ListAllIndices().label.size(), 0);
+
+  {
+    auto acc = storage.Access();
+    for (int i = 10; i < 20; ++i) {
+      auto vertex = CreateVertex(&acc);
+      ASSERT_NO_ERROR(vertex.AddLabel(i % 2 ? label1 : label2));
+    }
+    ASSERT_NO_ERROR(acc.Commit());
+  }
+
+  EXPECT_TRUE(storage.CreateIndex(label1));
+  EXPECT_TRUE(storage.LabelIndexExists(label1));
+  EXPECT_THAT(storage.ListAllIndices().label, UnorderedElementsAre(label1));
+
+  {
+    auto acc = storage.Access();
+
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19));
+
+    acc.AdvanceCommand();
+
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19));
+    EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW),
+                UnorderedElementsAre(1, 3, 5, 7, 9, 11, 13, 15, 17, 19));
+  }
+}
+
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_F(IndexTest, LabelIndexBasic) {
   // The following steps are performed and index correctness is validated after
@@ -57,14 +205,16 @@ TEST_F(IndexTest, LabelIndexBasic) {
   // 3. Remove Label1 from odd numbered vertices, and add it to even numbered
   //    vertices.
   // 4. Delete even numbered vertices.
+  EXPECT_TRUE(storage.CreateIndex(label1));
+  EXPECT_TRUE(storage.CreateIndex(label2));
+
   auto acc = storage.Access();
-  EXPECT_EQ(storage.ListAllIndices().label.size(), 0);
+  EXPECT_THAT(storage.ListAllIndices().label,
+              UnorderedElementsAre(label1, label2));
   EXPECT_THAT(GetIds(acc.Vertices(label1, View::OLD), View::OLD), IsEmpty());
   EXPECT_THAT(GetIds(acc.Vertices(label2, View::OLD), View::OLD), IsEmpty());
   EXPECT_THAT(GetIds(acc.Vertices(label1, View::NEW), View::NEW), IsEmpty());
   EXPECT_THAT(GetIds(acc.Vertices(label2, View::NEW), View::NEW), IsEmpty());
-  EXPECT_THAT(storage.ListAllIndices().label,
-              UnorderedElementsAre(label1, label2));
 
   for (int i = 0; i < 10; ++i) {
     auto vertex = CreateVertex(&acc);
@@ -133,6 +283,9 @@ TEST_F(IndexTest, LabelIndexDuplicateVersions) {
   // By removing labels and adding them again we create duplicate entries for
   // the same vertex in the index (they only differ by the timestamp). This test
   // checks that duplicates are properly filtered out.
+  EXPECT_TRUE(storage.CreateIndex(label1));
+  EXPECT_TRUE(storage.CreateIndex(label2));
+
   {
     auto acc = storage.Access();
     for (int i = 0; i < 5; ++i) {
@@ -172,6 +325,9 @@ TEST_F(IndexTest, LabelIndexDuplicateVersions) {
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_F(IndexTest, LabelIndexTransactionalIsolation) {
   // Check that transactions only see entries they are supposed to see.
+  EXPECT_TRUE(storage.CreateIndex(label1));
+  EXPECT_TRUE(storage.CreateIndex(label2));
+
   auto acc_before = storage.Access();
   auto acc = storage.Access();
   auto acc_after = storage.Access();
@@ -202,6 +358,9 @@ TEST_F(IndexTest, LabelIndexTransactionalIsolation) {
 
 // NOLINTNEXTLINE(hicpp-special-member-functions)
 TEST_F(IndexTest, LabelIndexCountEstimate) {
+  EXPECT_TRUE(storage.CreateIndex(label1));
+  EXPECT_TRUE(storage.CreateIndex(label2));
+
   auto acc = storage.Access();
   for (int i = 0; i < 20; ++i) {
     auto vertex = CreateVertex(&acc);