Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

YSQL: DDL command raises unexpected error "Transaction for catalog table write operation 'pg_type' not found" #9645

Closed
d-uspenskiy opened this issue Aug 10, 2021 · 0 comments
Assignees
Labels
kind/bug This issue is a bug

Comments

@d-uspenskiy
Copy link
Contributor

The following test case

@RunWith(YBTestRunnerNonTsanOnly.class)
public class ConcurrentDDL extends BasePgSQLTest {
  private static final Logger LOG = LoggerFactory.getLogger(ConcurrentDDL.class);

  @Test
  public void testConcurrency() throws Exception {
    AtomicBoolean stop = new AtomicBoolean(false);
    AtomicBoolean errorDetected = new AtomicBoolean(false);
    final int threadCount = 20;
    ExecutorService exec = Executors.newFixedThreadPool(20);
    for (int i = 0; i < threadCount; ++i) {
      final Integer idx = i;
      exec.submit(() -> {
        try(Connection c = getConnectionBuilder().withTServer(idx % 3).connect();
            Statement s = c.createStatement()) {
          while (!stop.get()) {
            helper(s, String.format("CREATE TABLE IF NOT EXISTS tt_%d( k int PRIMARY KEY, v int)", idx));
            helper(s, String.format("CREATE INDEX IF NOT EXISTS tt_%d_v ON tt_%d(v)", idx, idx));
            helper(s, String.format("DROP TABLE IF EXISTS tt_%d", idx));
          }
        } catch (Exception e) {
          LOG.error("Unexpected exception", e);
          errorDetected.set(true);
        }
      });
    }
    try(Connection c = getConnectionBuilder().withTServer(0).connect();
      Statement s = c.createStatement()) {
      final int count = 100;
      for (int i = 0; i < count; ++i) {
        helper(s, "CREATE TABLE IF NOT EXISTS t (a INT)");
        helper(s, "DROP TABLE IF EXISTS t");
      }
    }
    stop.set(true);
    exec.shutdown();
    exec.awaitTermination(1, TimeUnit.MINUTES);
    assertFalse(errorDetected.get());
  }

  @Override
  protected Map<String, String> getTServerFlags() {
    Map<String, String> flagMap = super.getTServerFlags();
    flagMap.put("ysql_log_statement", "all");
    return flagMap;
  }

  private static void helper(Statement s, String query) throws SQLException {
    try {
      s.execute(query);
    } catch(SQLException e) {
      final String msg = e.getMessage();
      if (!(msg.contains("Catalog Version Mismatch") || msg.contains("Try again"))) {
        throw e;
      }
    }
  }
}

fails with error

ts1|pid326976|:27271|http://127.237.244.27:12097 2021-08-10 15:46:36.693 MSK [327400] ERROR:  Illegal state: Transaction for catalog table write operation 'pg_type' not found
ts1|pid326976|:27271|http://127.237.244.27:12097 2021-08-10 15:46:36.693 MSK [327400] STATEMENT:  DROP TABLE IF EXISTS tt_12
2021-08-10 15:46:36,694 (pool-4-thread-13) [ERROR - org.yb.pgsql.ConcurrentDDL.lambda$testConcurrency$0(ConcurrentDDL.java:41)] Unexpected exception
org.postgresql.util.PSQLException: ERROR: Illegal state: Transaction for catalog table write operation 'pg_type' not found
        at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2440)
        at org.postgresql.core.v3.QueryExecutorImpl.processResults(QueryExecutorImpl.java:2183)
        at org.postgresql.core.v3.QueryExecutorImpl.execute(QueryExecutorImpl.java:308)
        at org.postgresql.jdbc.PgStatement.executeInternal(PgStatement.java:441)
        at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:365)
        at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:307)
        at org.postgresql.jdbc.PgStatement.executeCachedSql(PgStatement.java:293)
        at org.postgresql.jdbc.PgStatement.executeWithFlags(PgStatement.java:270)
        at org.postgresql.jdbc.PgStatement.execute(PgStatement.java:266)
        at org.yb.pgsql.ConcurrentDDL.helper(ConcurrentDDL.java:69)
        at org.yb.pgsql.ConcurrentDDL.lambda$testConcurrency$0(ConcurrentDDL.java:38)
        at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)
@d-uspenskiy d-uspenskiy added the kind/bug This issue is a bug label Aug 10, 2021
@d-uspenskiy d-uspenskiy self-assigned this Aug 10, 2021
d-uspenskiy added a commit that referenced this issue Oct 25, 2021
Summary:
DDL transaction state is controlled by the `ddl_nesting_level` counter.
When `ddl_nesting_level` is changing from 0 to 1 by the calling of the `YBIncrementDdlNestingLevel` function new DDL transaction is started. When `ddl_nesting_level` is changing from 1 to 0 by the calling of the `YBDecrementDdlNestingLevel` function current DDL transaction is committed.

In some scenarios in case of failure the `ddl_nesting_level` counter may have inappropriate value, it is required to reset them and DDL transaction state as well.

One of such scenario is the index creation procedure. By design index creation commits current DDL transaction at the middle and starts a new one immediately. For this purpose the `YBDecrementDdlNestingLevel`/`YBIncrementDdlNestingLevel` functions are called from the `DefineIndex` function. But in case current DDL is failed to commit or new DDL transaction can't be started for some reason (due to catalog version change etc) the `YBIncrementDdlNestingLevel` function may not be called or may return error. In this case `ddl_nesting_level` will have wrong value. As a result current SQL session will not be able to start/commit further DDLs at the proper time.

Solution is to reset DDL transaction state in case current DDL query finished with an error.

Note: As far as DDL transaction state is reset in case of error it is not necessary to have `success` parameter in the `YBDecrementDdlNestingLevel` function (it is called only in case of success)

Test Plan:
New unit test was introduced

```
./yb_build.sh --gtest_filter PgDDLConcurrencyTest.IndexCreation
```

Reviewers: mihnea, jason

Reviewed By: jason

Subscribers: rsami, yql

Differential Revision: https://phabricator.dev.yugabyte.com/D12609
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/bug This issue is a bug
Projects
None yet
Development

No branches or pull requests

1 participant