Page MenuHomePhabricator

Suggested investigations: Create database tables
Closed, ResolvedPublic2 Estimated Story Points

Description

Summary

For the Suggested investigations feature we need to have database tables that store a list of accounts in investigations, their connection to each other, and what makes them suspicious. This task tracks creating the database tables for this feature.

Background

  • We need to track the following things:
    • Which accounts are in a suggested investigation
    • Which accounts are related to other accounts
    • What made the software suggest the investigation
  • The database table needs to support the following operations
    • Creating a suggested investigation based on some signal that matched to the accounts
    • Seeing a list of suggested investigations
      • This includes filtering out suggested investigations which have been reviewed
    • Marking a suggested investigation as resolved or invalid, including giving a reason for this
    • Adding additional users to an existing suggested investigations which are yet to be closed as resolved or invalid
    • Checking if a user is any suggested investigations

Technical notes & database schema

Table schema layout (as a diagram):

databaseSchema.drawio (10).png (1,511×475 px, 128 KB)

Output of DESCRIBE and SHOW INDEXES FROM
cusi_case
describe cusi_case;
+-----------------------+---------------------+------+-----+---------+----------------+
| Field                 | Type                | Null | Key | Default | Extra          |
+-----------------------+---------------------+------+-----+---------+----------------+
| sic_id                | int(10) unsigned    | NO   | PRI | NULL    | auto_increment |
| sic_status            | tinyint(3) unsigned | NO   | MUL | 0       |                |
| sic_status_reason_id  | bigint(20) unsigned | NO   |     | 0       |                |
| sic_created_timestamp | binary(14)          | NO   | MUL | NULL    |                |
+-----------------------+---------------------+------+-----+---------+----------------+
show indexes from cusi_case;
+-----------+------------+---------------------------------+--------------+-----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| Table     | Non_unique | Key_name                        | Seq_in_index | Column_name           | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Ignored |
+-----------+------------+---------------------------------+--------------+-----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| cusi_case |          0 | PRIMARY                         |            1 | sic_id                | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_case |          0 | sic_status_created_timestamp_id |            1 | sic_status            | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_case |          0 | sic_status_created_timestamp_id |            2 | sic_created_timestamp | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_case |          0 | sic_status_created_timestamp_id |            3 | sic_id                | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_case |          0 | sic_created_timestamp_id        |            1 | sic_created_timestamp | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_case |          0 | sic_created_timestamp_id        |            2 | sic_id                | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
+-----------+------------+---------------------------------+--------------+-----------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
cusi_user
describe cusi_user;
+-------------+------------------+------+-----+---------+-------+
| Field       | Type             | Null | Key | Default | Extra |
+-------------+------------------+------+-----+---------+-------+
| siu_user_id | int(10) unsigned | NO   | PRI | NULL    |       |
| siu_sic_id  | int(10) unsigned | NO   | PRI | NULL    |       |
+-------------+------------------+------+-----+---------+-------+
show indexes from cusi_user;
+-----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| Table     | Non_unique | Key_name    | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Ignored |
+-----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| cusi_user |          0 | PRIMARY     |            1 | siu_sic_id  | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_user |          0 | PRIMARY     |            2 | siu_user_id | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_user |          1 | siu_user_id |            1 | siu_user_id | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
+-----------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
cusi_signal
describe cusi_signal;
+------------+------------------+------+-----+---------+----------------+
| Field      | Type             | Null | Key | Default | Extra          |
+------------+------------------+------+-----+---------+----------------+
| sis_sic_id | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| sis_name   | varbinary(255)   | NO   | PRI | NULL    |                |
| sis_value  | varbinary(255)   | NO   | PRI | NULL    |                |
+------------+------------------+------+-----+---------+----------------+
show indexes from cusi_signal;
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| Table       | Non_unique | Key_name   | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Ignored |
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+
| cusi_signal |          0 | PRIMARY    |            1 | sis_name    | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_signal |          0 | PRIMARY    |            2 | sis_value   | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_signal |          0 | PRIMARY    |            3 | sis_sic_id  | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
| cusi_signal |          1 | sis_sic_id |            1 | sis_sic_id  | A         |           0 |     NULL | NULL   |      | BTREE      |         |               | NO      |
+-------------+------------+------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+

Acceptance criteria

  • The database tables to support this feature are created

Related Objects

StatusSubtypeAssignedTask
Resolvedmszwarc
ResolvedDreamy_Jazz
ResolvedDreamy_Jazz
Resolvedmszwarc
ResolvedDreamy_Jazz
Resolvedmszwarc
ResolvedDreamy_Jazz
ResolvedDreamy_Jazz
Resolved hector.arroyo
ResolvedDreamy_Jazz
ResolvedDreamy_Jazz
Resolvedmszwarc
ResolvedDreamy_Jazz
Resolved hector.arroyo
Resolvedsgrabarczuk
ResolvedDreamy_Jazz
Resolvedmszwarc
ResolvedDreamy_Jazz
Resolvedmszwarc
Resolvedmszwarc
Resolvedmszwarc
ResolvedDreamy_Jazz
ResolvedDreamy_Jazz
ResolvedDreamy_Jazz

Event Timeline

Dreamy_Jazz set the point value for this task to 2.
Dreamy_Jazz moved this task from Unsorted to Add / Create on the Schema-change board.

I will tag DBA for review once have a gerrit patch and pasted the proposed schema to this task.

Change #1180816 had a related patch set uploaded (by Dreamy Jazz; author: Dreamy Jazz):

[mediawiki/extensions/CheckUser@master] [WIP] Add database tables for suspicious accounts feature

https://gerrit.wikimedia.org/r/1180816

Dreamy_Jazz renamed this task from Suspicious accounts: Create database tables to Suggested investigations: Create database tables.Aug 21 2025, 1:39 PM
Dreamy_Jazz updated the task description. (Show Details)

Change #1180909 had a related patch set uploaded (by Dreamy Jazz; author: Dreamy Jazz):

[mediawiki/extensions/CheckUser@master] [WIP] Add the cusi_case table

https://gerrit.wikimedia.org/r/1180909

Change #1180910 had a related patch set uploaded (by Dreamy Jazz; author: Dreamy Jazz):

[mediawiki/extensions/CheckUser@master] [WIP] Add the cusi_user table

https://gerrit.wikimedia.org/r/1180910

Change #1180911 had a related patch set uploaded (by Dreamy Jazz; author: Dreamy Jazz):

[mediawiki/extensions/CheckUser@master] [WIP] Add the cusi_signal table

https://gerrit.wikimedia.org/r/1180911

Change #1180912 had a related patch set uploaded (by Dreamy Jazz; author: Dreamy Jazz):

[mediawiki/extensions/CheckUser@master] [WIP] Add the cusi_signal_map table

https://gerrit.wikimedia.org/r/1180912

Change #1180816 abandoned by Dreamy Jazz:

[mediawiki/extensions/CheckUser@master] [WIP] Add database tables for suspicious accounts feature

https://gerrit.wikimedia.org/r/1180816

Adding DBA to request feedback on the new tables. There are 4 tables and I will shortly add the schema for these tables to the task description.

Additionally we would like to ask whether we can not store these on the extension1 cluster because we would like the ability to join to tables like the user table. However, I am aware that adding 4 tables to the main DB cluster per-wiki could be a big ask for DB stability (so we are open to compromise solutions)

Hi, The workload these days is a bit much. It might take me a bit to properly investigate the schema and the architecture of the storage. In the mean time, would you mind posting responses to the questions of https://wikitech.wikimedia.org/wiki/Creating_new_tables#Preparation ? plus some example data for me to understand it a bit better.

Thanks. I'm away until Tuesday lunchtime, so won't be able to post those answers until then.

Hi, The workload these days is a bit much. It might take me a bit to properly investigate the schema and the architecture of the storage. In the mean time, would you mind posting responses to the questions of https://wikitech.wikimedia.org/wiki/Creating_new_tables#Preparation ? plus some example data for me to understand it a bit better.

  1. Should this table be replicated to wiki replicas (does it not contain private data)?

No, none of these tables should be replicated to wiki replicas. They contains private data.

  1. Will you be doing cross-joins with the wiki metadata?

We would like to join to the user table and other wiki metadata (see T402391#11107938). Strictly speaking, we could get by without cross-joins for this quarter, but then would likely be coming back to request the ability to do so in the next quarter, so would rather start with this capability already present.

  1. Size of the table (number of rows expected). Expected growth per year (number of rows).

We are not clear yet on this yet because:

  • We are still developing the signals that indicate that a suggested investigation should be created
  • We will modify the thresholds for the signals in response to feedback and how many rows are created

We create rows when a user matches a signal on account creation. Therefore, we can (for now) correlate rows created to number of accounts created where a limited subset will cause inserts to the new tables.

Here are some ballpark numbers:

  • cusi_case is likely grow by tens of thousands of rows per year for large wikis, with smaller wikis likely seeing few to no rows being added per year.
  • cusi_signal initially will match the growth rate of cusi_case but the growth rate would increase if we continue development to add new signal types
  • cusi_signal_map initially will be at most double the number of rows in cusi_cae, but like cusi_signal the growth rate would increase if we continue development
  • cusi_user would grow at a rate more than cusi_case and initially we expect on average 3 to 5 times as many rows as cusi_user

We do not expect the growth rate of these tables to be much larger than this, as we will want CheckUsers to provide manual review for each cusi_case row. Therefore, a growth rate that is too high will be problematic.

  1. Expected amount of queries, both writes and reads (per minute, per hour...per day, any of those are ok).

As mentioned above, writes will occur on a limited subset of account creations (and in some other limited circumstances TBC that should have a very infrequent occurrence). Reads will occur during that same set of account creations and also when a CheckUser loads suggested investigations.

We expect:

  • cusi_case - writes of at least 27 per day, reads likely not exceeding 50 per day
  • cusi_user - writes of at least 150 per day, reads likely not exceeding 200 per day
  • cusi_signal - writes of at least 27 per day, reads likely not exceeding 100 per day - will increase if we add more signals
  • cusi_signal_map - writes of at least 50 per day, reads likely not exceeding 200 per day - will increase if we add more signals
  1. Examples of queries that will be using the table.
  • Queries used to create a new suggested investigation:
    • Create the cusi_case row - INSERT INTO cusi_case (sic_created_timestamp) VALUES (<timestamp of now>);
    • Search for pre-existing signal values (assumes that the "X or more users share the same email" signal has ID of 1) - SELECT sis_id FROM cusi_signal WHERE sis_name = 'test' AND sis_value = "test@test.com";
    • Insert missing signal values - INSERT INTO cusi_signal (sis_value, sis_name) VALUES ("test@test.com", 1);
    • Link the signal values to the investigation - INSERT INTO cusi_signal_map (sism_sis_id, sism_sic_id) VALUES (<cusi_signal row ID>, <cusi_case row ID>);
  • Add a user to a suggested investigation - INSERT INTO cusi_user (siu_user_id, siu_sic_id) values (<user id>, <cusi_case id>);
  • Search for suggested investigations that a user, that has just been created, may match:
    • Generate the signals for the user
    • Look for any matching signal values - SELECT sis_id FROM cusi_signal WHERE sis_name = '<name>' AND sis_value = <value>;
    • Find which investigations are associated with the found signal values (limiting this to open cases only) - SELECT DISTINCT sic_id FROM cusi_signal_map JOIN cusi_case on sism_sic_id = sic_id WHERE sism_sis_id IN (<sis_id value(s)>) AND sic_status = 0 ORDER BY sic_created_timestamp DESC LIMIT 500;
  • List investigations without any filters - SELECT sic_status, sic_status_timestamp, comment_text, sic_created_timestamp FROM cusi_case JOIN comment on sic_status_reason_id = comment_id ORDER BY sic_created_timestamp DESC;
  • List investigations filtering for closed investigations - SELECT sic_status, sic_status_timestamp, comment_text, sic_created_timestamp FROM cusi_case WHERE sic_status = 2 ORDER BY sic_status_timestamp DESC;
  • Get users associated with a given suggested investigation - SELECT siu_user_id FROM cusi_user WHERE siu_sic_id = <suggested investigations case ID>;
  • Close a suggested investigation as resolved - UPDATE cusi_case SET sic_status = 2, sic_status_timestamp = <current timestamp> WHERE sic_id = <suggested investigations case ID>;
  • Check if a user is associated with a given suggested investigation - SELECT siu_sic_id FROM cusi_user WHERE siu_user_id = <user ID>;
  1. The release plan for the feature (are there specific wikis you'd like to test first etc).

English Wikipedia, and 7 other larger Wikipedias (TBC). Happy with creating the database tables initially only on those wikis to avoid unnecessary tables on production. We may expand to other wikis at a later date.

Extremely preliminary review:

  • I would like to know why do you need to join with user (including your future usecase). The reason I'm asking is that we really can't have these tables in core dbs and moving things from x1 to core is quite a decent chunk of work so once it's in x1, I don't think there would be good idea to move it to core (and generally don't think it'd be a good idea in the future either, we really should move tables out of core, not the other way around)
    • it's not really about files of s3, it's about scalability of the infra.
  • If x1 is accepted, then infra-wise it's fine.
  • My first-skim view of the schema is that maybe it can be simplified a bit? Do we really need a map table?

Extremely preliminary review:

  • I would like to know why do you need to join with user (including your future usecase). The reason I'm asking is that we really can't have these tables in core dbs and moving things from x1 to core is quite a decent chunk of work so once it's in x1, I don't think there would be good idea to move it to core (and generally don't think it'd be a good idea in the future either, we really should move tables out of core, not the other way around)
    • it's not really about files of s3, it's about scalability of the infra.

We have several use cases for why we want to join to user (and other tables):

  • Filtering for suggested investigations which contain users in specific buckets of edit counts (for example, filtering for cases which have users with 100+ edits or have 0 edits)
  • Filtering for suggested investigations which contain users in specific user groups (for example, filtering for cases where an extended confirmed user is present)
  • Filtering for suggested investigations which contain blocked users

We want the filtering to allow paging, so for an efficient query we would need to be able to JOIN to user, user_groups, block and therefore keep all sorting and paging within the SQL query. If we can't join to those tables then we need to fetch batches of suggested investigations and then test if they meet the filter in PHP.

If we have to use extension1, then we may be able to work around some of the issues. However, it may need us to create new columns/tables as well as increasing the expected write rates.

  • My first-skim view of the schema is that maybe it can be simplified a bit? Do we really need a map table?

I'm proposing a map table because for hCaptcha signal (T402935) we will have only one value for the signal which is "considered suspicious". Therefore, it seemed better for de-duplication to have a map table (because we would only need one row in the cusi_signal table to represent hCaptcha suspicious). However, we don't specifically need to have a map table AFAICS if you think the additional space created by an index that contains a blob column is better.

Okay, I reviewed the schema a bit. Thanks for the test data. Definitely get rid of cusi_signal_map and merge it to cusi_signal. The tables are small enough and the space saved is negligible. Normalization adds overhead (e.g. files opened, maintaining consistency, ...) and it's making understanding the data model quite hard. The future engineers onboarding the team will thank you if you get rid of the map table.

Why would you need an index on blob. Why not indexing on signal name won't be enough? if you need the filter based on value too, you can do index on first X characters of a blob. That would work, it's common in mw.

Instead of normalizing that, you can simply turn sis_name into a smallint and define the values in code. Similar to what linter does: https://github.com/wikimedia/mediawiki-extensions-Linter/blob/master/extension.json#L160

Okay, I reviewed the schema a bit. Thanks for the test data. Definitely get rid of cusi_signal_map and merge it to cusi_signal. The tables are small enough and the space saved is negligible. Normalization adds overhead (e.g. files opened, maintaining consistency, ...) and it's making understanding the data model quite hard. The future engineers onboarding the team will thank you if you get rid of the map table.

Sure, we can do that.

Why would you need an index on blob. Why not indexing on signal name won't be enough? if you need the filter based on value too, you can do index on first X characters of a blob. That would work, it's common in mw.

We need to merge suggested investigation cases which have the same value for the signal.

For example, a new user creates an email which hashes to the same value. We want to add the user to existing suggested investigations which have this hash and it should be an exact match.

Instead of normalizing that, you can simply turn sis_name into a smallint and define the values in code. Similar to what linter does: https://github.com/wikimedia/mediawiki-extensions-Linter/blob/master/extension.json#L160

I had it as a smallint, but we needed to make it a string because we cannot fix the values in code (because we want to make it possible to define signals privately and in other extensions, so we can't easily enforce that no other signal uses the same integer as another one).

See https://gerrit.wikimedia.org/r/c/mediawiki/extensions/CheckUser/+/1182230/comment/61f90d13_ae70fbd1/ for extended details.

if you need the filter based on value too, you can do index on first X characters of a blob.

Could we consider making it a VARCHAR and limiting the length to 255, so that we can still index the full value? That should be enough for our purposes.

Change #1180912 abandoned by Dreamy Jazz:

[mediawiki/extensions/CheckUser@master] Add the cusi_signal_map table

Reason:

Per DBA feedback, we should get rid of the map table and have a `cusi_signal` row for each matching `cusi_case` row.

https://gerrit.wikimedia.org/r/1180912

Instead of normalizing that, you can simply turn sis_name into a smallint and define the values in code. Similar to what linter does: https://github.com/wikimedia/mediawiki-extensions-Linter/blob/master/extension.json#L160

I had it as a smallint, but we needed to make it a string because we cannot fix the values in code (because we want to make it possible to define signals privately and in other extensions, so we can't easily enforce that no other signal uses the same integer as another one).

See https://gerrit.wikimedia.org/r/c/mediawiki/extensions/CheckUser/+/1182230/comment/61f90d13_ae70fbd1/ for extended details.

ACK. Go for it.

if you need the filter based on value too, you can do index on first X characters of a blob.

Could we consider making it a VARCHAR and limiting the length to 255, so that we can still index the full value? That should be enough for our purposes.

Yea. I think that'd be great.

Thanks for the review. Given the dropping of cusi_signal_map, I've reanswered the some of the questions you posed above to reflect the changes.

In summary, the write and read rates to cusi_signal_map now apply to cusi_signal. Additionally, some of the example queries have changed but do not change in functionality.

  1. Size of the table (number of rows expected). Expected growth per year (number of rows).

We are not clear yet on this yet because:

  • We are still developing the signals that indicate that a suggested investigation should be created
  • We will modify the thresholds for the signals in response to feedback and how many rows are created

We create rows when a user matches a signal on account creation. Therefore, we can (for now) correlate rows created to number of accounts created where a limited subset will cause inserts to the new tables.

Here are some ballpark numbers:

  • cusi_case is likely grow by tens of thousands of rows per year for large wikis, with smaller wikis likely seeing few to no rows being added per year.
  • cusi_signal initially will at most grow at a rate 2 to 3 times as cusi_case, but the growth rate would increase if we continue development to add new signal types
  • cusi_user would grow at a rate more than cusi_case and initially we expect on average 3 to 5 times as many rows as cusi_user

We do not expect the growth rate of these tables to be much larger than this, as we will want CheckUsers to provide manual review for each cusi_case row. Therefore, a growth rate that is too high will be problematic.

  1. Expected amount of queries, both writes and reads (per minute, per hour...per day, any of those are ok).

As mentioned above, writes will occur on a limited subset of account creations (and in some other limited circumstances TBC that should have a very infrequent occurrence). Reads will occur during that same set of account creations and also when a CheckUser loads suggested investigations.

We expect:

  • cusi_case - writes of at least 27 per day, reads likely not exceeding 50 per day
  • cusi_user - writes of at least 150 per day, reads likely not exceeding 200 per day
  • cusi_signal - writes of at least 50 per day, reads likely not exceeding 200 per day - will increase if we add more signals
  1. Examples of queries that will be using the table.
  • Queries used to create a new suggested investigation:
    • Create the cusi_case row - INSERT INTO cusi_case (sic_created_timestamp) VALUES (<timestamp of now>);
    • Insert the signal values - INSERT INTO cusi_signal (sis_sic_id, sis_value, sis_name) VALUES (<cusi_case ID>, <signal value>, <signal name>);
  • Add a user to a suggested investigation - INSERT INTO cusi_user (siu_user_id, siu_sic_id) values (<user id>, <cusi_case id>);
  • Search for suggested investigations that a user, that has just been created, may match:
    • Generate the signals for the user
    • Look for any open cases which match the signal value - SELECT sic_id FROM cusi_case JOIN cusi_signal ON sis_sic_id = sic_id WHERE sis_name = '<name>' AND sis_value = <value>;
  • List investigations without any filters - SELECT sic_status, sic_status_timestamp, comment_text, sic_created_timestamp FROM cusi_case JOIN comment on sic_status_reason_id = comment_id ORDER BY sic_created_timestamp DESC;
  • List investigations filtering for closed investigations - SELECT sic_status, sic_status_timestamp, comment_text, sic_created_timestamp FROM cusi_case WHERE sic_status = 2 ORDER BY sic_status_timestamp DESC;
  • Get users associated with a given suggested investigation - SELECT siu_user_id FROM cusi_user WHERE siu_sic_id = <suggested investigations case ID>;
  • Close a suggested investigation as resolved - UPDATE cusi_case SET sic_status = 2, sic_status_timestamp = <current timestamp> WHERE sic_id = <suggested investigations case ID>;
  • Check if a user is associated with a given suggested investigation - SELECT siu_sic_id FROM cusi_user WHERE siu_user_id = <user ID>;

I will need to update the schema patches to use extension1 per our discussions. I may not get that done by the end of today, but should have it ready on Monday.

Would that be enough time to give a green light to merge the patches and follow-up on any smaller changes via follow-up patches?

I will need to update the schema patches to use extension1 per our discussions. I may not get that done by the end of today, but should have it ready on Monday.

Would that be enough time to give a green light to merge the patches and follow-up on any smaller changes via follow-up patches?

Yup. I think you could call it stable that if the need arises, we do schema changes.

I will need to update the schema patches to use extension1 per our discussions. I may not get that done by the end of today, but should have it ready on Monday.

Would that be enough time to give a green light to merge the patches and follow-up on any smaller changes via follow-up patches?

Yup. I think you could call it stable that if the need arises, we do schema changes.

Thanks!

Don't forget to catalog them.

Sure. I will do that when we have the tables merged.

Per T402391#11132565 we can merge the patches and update the database schema later if needed, so moving to 'Needs Review' as these patches are blocking some others which are mostly ready to be merged.

Change #1180909 merged by jenkins-bot:

[mediawiki/extensions/CheckUser@master] Add the cusi_case table

https://gerrit.wikimedia.org/r/1180909

Moving to 'Done' on our workboard. We can move back to 'In Progress' if there are additional suggestions regarding the database schema.

Change #1180910 merged by jenkins-bot:

[mediawiki/extensions/CheckUser@master] Add the cusi_user table

https://gerrit.wikimedia.org/r/1180910

Change #1180911 merged by jenkins-bot:

[mediawiki/extensions/CheckUser@master] Add the cusi_signal table

https://gerrit.wikimedia.org/r/1180911

Dreamy_Jazz updated the task description. (Show Details)