You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by am...@apache.org on 2022/01/28 17:15:20 UTC

[ignite] 02/03: Add tests when affinity index tree shadowed by a user index and 1. got broken after user index dropped. 2. got broken affinity index on a newly created cache with same name, after droppong the current one.

This is an automated email from the ASF dual-hosted git repository.

amashenkov pushed a commit to branch ignite-16426
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit 337f51f654fd39527f07abe4f927075442f288b7
Author: Andrew Mashenkov <an...@gmail.com>
AuthorDate: Fri Jan 28 18:17:05 2022 +0300

    Add tests when affinity index tree shadowed by a user index and
    1. got broken after user index dropped.
    2. got broken affinity index on a newly created cache with same name, after droppong the current one.
---
 .../cache/index/AffinityIndexShadowingTest.java    | 286 +++++++++++++++++++++
 1 file changed, 286 insertions(+)

diff --git a/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/AffinityIndexShadowingTest.java b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/AffinityIndexShadowingTest.java
new file mode 100644
index 0000000..660f9fd
--- /dev/null
+++ b/modules/indexing/src/test/java/org/apache/ignite/internal/processors/cache/index/AffinityIndexShadowingTest.java
@@ -0,0 +1,286 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Copyright 2021 GridGain Systems, Inc. and Contributors.
+ *
+ * Licensed under the GridGain Community Edition License (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.gridgain.com/products/software/community-edition/gridgain-community-edition-license
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.processors.cache.index;
+
+import java.util.List;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.cache.affinity.AffinityKeyMapped;
+import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction;
+import org.apache.ignite.cache.query.SqlFieldsQuery;
+import org.apache.ignite.cache.query.annotations.QuerySqlField;
+import org.apache.ignite.client.Person;
+import org.apache.ignite.configuration.CacheConfiguration;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.IgniteEx;
+import org.apache.ignite.internal.IgniteInternalFuture;
+import org.apache.ignite.internal.cache.query.index.Index;
+import org.apache.ignite.internal.processors.query.h2.H2TableDescriptor;
+import org.apache.ignite.internal.util.typedef.internal.CU;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+import static org.apache.ignite.testframework.GridTestUtils.runAsync;
+
+/**
+ * Class for testing forced rebuilding of indexes.
+ */
+public class AffinityIndexShadowingTest extends AbstractRebuildIndexTest {
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        return super.getConfiguration(igniteInstanceName)
+            .setCacheConfiguration(
+                cacheConfig(DEFAULT_CACHE_NAME),
+                // Add one more cache to keep CacheGroup non-empty when the first cache will be destroyed during test.
+                cacheConfig(DEFAULT_CACHE_NAME + 2)
+            );
+    }
+
+    /** */
+    private CacheConfiguration<Object, Object> cacheConfig(String cacheName) {
+        return new CacheConfiguration<>(cacheName).setGroupName("GRP")
+            .setIndexedTypes(PersonKey.class, Person.class)
+            .setAffinity(new RendezvousAffinityFunction(false, 1));
+    }
+
+    /**
+     * Checks that a new dynamic index shadows default affinity index correctly.
+     * Expected that shadowed affinity index is consistent after user index was dropped.
+     *
+     * Scenario:
+     * <li> Create cache. Key object must have dedicated affinity field.
+     * <li> Populate caches with some data. Affinity index will holds the links to the data.
+     * <li> Create new index 'IDX0' that will have AffinityKey as first column and wait all data be indexed.
+     * <li> Restart the grid. Now, system affinity index will be shadowed by the user index 'IDX0'.
+     * <li> Clear cache and populate once again.
+     * TODO: actually, the system affinity index will not be involved here and contains broken links.
+     * <li> Drop the index 'IDX0'.
+     * <li> Restart the grid. The affinity index will be restored'.
+     * <li> It is expected affinity index is wither ok or re-indexed.
+     * TODO: actually, the system affinity index is broken and next put or index rebuild operation will fail.
+     *
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testAffinityIndexShadowing() throws Exception {
+        final String cacheName = DEFAULT_CACHE_NAME;
+        final int cacheSize = 1_000;
+        IgniteH2IndexingEx.prepareBeforeNodeStart();
+
+        IgniteEx n = startGrid(0);
+        populateCache(n.cache(cacheName), cacheSize);
+
+        assertNotNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+
+        // Create user index that duplicates affinity-index.
+        String idxName = "IDX0";
+        createIdxAsync(n.cache(cacheName), idxName).get();
+
+        assertNotNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+        assertNotNull(index(n, n.cache(cacheName), idxName));
+
+        assertEquals(cacheSize, selectPersonByName(n.cache(cacheName)).size());
+
+        // Restart.
+        stopGrid(0);
+        n = startGrid(0);
+
+        // Invalidate old data. Expects, indices will be cleaned consistently.
+        n.cache(cacheName).clear();
+        populateCache(n.cache(cacheName), cacheSize);
+
+        // Affinity index shadowed.
+        assertNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+        assertNotNull(index(n, n.cache(cacheName), idxName));
+
+        // Drop user index.
+        dropIdx(n.cache(cacheName), idxName);
+
+        assertNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+        assertNull(index(n, n.cache(cacheName), idxName));
+
+        assertEquals(cacheSize, selectPersonByName(n.cache(cacheName)).size());
+
+        // Restart.
+        stopGrid(0);
+        n = startGrid(0);
+
+        IgniteInternalFuture<?> fut = indexRebuildFuture(n, CU.cacheId(cacheName));
+        if (fut != null)
+            fut.get(getTestTimeout()); // <-- Rebuild will fails due to broken Affinity index.
+
+        assertNull(index(n, n.cache(cacheName), idxName));
+        assertNotNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+        assertEquals(cacheSize, selectPersonByName(n.cache(cacheName)).size());
+    }
+
+    /**
+     * Checks that a new dynamic index shadows default affinity index correctly.
+     * The shadowed affinity index must be dropped together with the cache.
+     *
+     * Scenario:
+     * <li> Create 2 caches in the same cache group. Key object must have dedicated affinity field.
+     * (The second one is required to prevent whole group destroy when the first one will be dropped.)
+     * <li> Populate caches with some data. Affinity index will holds the links to the data.
+     * <li> Create new index 'IDX0' that will have AffinityKey as first column and wait all data be indexed.
+     * <li> Restart the grid. Now, system affinity index will be shadowed by the user index 'IDX0'.
+     * <li> Populate cache once again.
+     * TODO: actually, the system affinity index will not be involved here and contains broken links.
+     * <li> Drop the cache. 'IDX0' will be destroyed as well.
+     * <li> Create cache with the same name and populate with the data.
+     *  It is expected new cache will be operable.
+     * TODO: actually, the system affinity index tree wasn't destroyed. Now it is resurrected and contains broken links.
+     * @throws Exception If failed.
+     */
+    @Test
+    public void testAffinityIndexShadowing2() throws Exception {
+        final String cacheName = DEFAULT_CACHE_NAME;
+        final int cacheSize = 1_000;
+        IgniteH2IndexingEx.prepareBeforeNodeStart();
+
+        IgniteEx n = startGrid(0);
+        populateCache(n.cache(cacheName), cacheSize);
+
+        assertNotNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+
+        // Create user index that duplicates affinity-index.
+        String idxName = "IDX0";
+        createIdxAsync(n.cache(cacheName), idxName).get();
+
+        assertNotNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+        assertNotNull(index(n, n.cache(cacheName), idxName));
+
+        assertEquals(cacheSize, selectPersonByName(n.cache(cacheName)).size());
+
+        // Restart.
+        stopGrid(0);
+        n = startGrid(0);
+
+        assertEquals(cacheSize, selectPersonByName(n.cache(cacheName)).size());
+        populateCache(n.cache(cacheName), cacheSize);
+
+        // Affinity index shadowed.
+        assertNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+        assertNotNull(index(n, n.cache(cacheName), idxName));
+
+        // Recreate cache.
+        n.destroyCache(cacheName);
+        n.createCache(cacheConfig(cacheName));
+
+        populateCache(n.cache(cacheName), cacheSize); // <-- Face corrupted affinity index here!
+
+        assertNull(index(n, n.cache(cacheName), idxName));
+        assertNotNull(index(n, n.cache(cacheName), H2TableDescriptor.AFFINITY_KEY_IDX_NAME));
+
+        assertEquals(cacheSize, selectPersonByName(n.cache(cacheName)).size());
+    }
+
+    /**
+     * Selection of all {@link Person} by name.
+     * SQL: SELECT * FROM Person where name LIKE 'name_%';
+     *
+     * @param cache Cache.
+     * @return List containing all query results.
+     */
+    private List<List<?>> selectPersonByName(IgniteCache<Integer, Person> cache) {
+        return cache.query(new SqlFieldsQuery("SELECT * FROM Person where affId >= 0;")).getAll();
+    }
+
+    /**
+     * Asynchronous creation of a new index for the cache of {@link Person}.
+     * SQL: CREATE INDEX " + idxName + " ON Person(name)
+     *
+     * @param cache Cache.
+     * @param idxName Index name.
+     * @return Index creation future.
+     */
+    private IgniteInternalFuture<List<List<?>>> createIdxAsync(IgniteCache<Integer, Person> cache, String idxName) {
+        return runAsync(() -> {
+            String sql = "CREATE INDEX " + idxName + " ON Person(affId)";
+
+            return cache.query(new SqlFieldsQuery(sql)).getAll();
+        });
+    }
+
+    /**
+     * Drop of an index for the cache of{@link Person}.
+     * SQL: DROP INDEX " + idxName
+     *
+     * @param cache Cache.
+     * @param idxName Index name.
+     * @return Index creation future.
+     */
+    private List<List<?>> dropIdx(IgniteCache<Integer, Person> cache, String idxName) {
+        return cache.query(new SqlFieldsQuery("DROP INDEX " + idxName)).getAll();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected @Nullable Index index(IgniteEx n, IgniteCache<Integer, Person> cache, String idxName) {
+        try {
+            return super.index(n, cache, idxName);
+        }
+        catch (Exception e) {
+            return null;
+        }
+    }
+
+    /**
+     * Populate cache with {@link Person} sequentially.
+     *
+     * @param cache Cache.
+     * @param cnt Entry count.
+     */
+    private void populateCache(IgniteCache<PersonKey, Person> cache, int cnt) {
+        for (int i = 0; i < cnt; i++)
+            cache.put(new PersonKey(i * 2, i), new Person(i, "name_" + i));
+    }
+
+    /**
+     * Key class.
+     */
+    static class PersonKey {
+        /** ID. */
+        @QuerySqlField
+        long keyId;
+
+        /** Affinity key. */
+        @AffinityKeyMapped
+        @QuerySqlField
+        long affId;
+
+        PersonKey(long keyId, long affId) {
+            this.keyId = keyId;
+            this.affId = affId;
+        }
+    }
+}