Query Performance - Data Masking
Tags: anonymize, datamasking, database, dataswapping
Published: 2024-02-06 05:38:34

> **Note: This is Problem Statement Solution**


## Introduction
This case study revolves around the optimization of an anonymization process for a large database within our system. The original script was observed to be significantly time-consuming, taking approximately **22+** **hours** to execute on larger databases. This not only posed efficiency concerns but also introduced disruptions to other system processes.

The anonymization process is crucial for maintaining the privacy of sensitive data in our database tables and several associated tables in the database. The script is designed to replace real values with generated fake data, preserving the privacy of sensitive data while keeping the structure and functionality of our database intact for testing and development purposes.

## Background Analysis
The central focus of this case study is a script tasked with anonymizing personal data of people within a large database, and more specifically, improving the efficiency of this process. The motivation for this optimization effort was the significant amount of time the initial script was taking to execute - over **22 hours** to anonymize approximately **10 million** records.

This high volume of data is a result of the complex structure of the database, which comprises records not only in the `Employees` table, but also in several associated tables such as users, `Bookings`, `booking_actions`, `safeguards`, and `sensitive_values`. These tables hold different types of sensitive information related to employees. For instance, the users table stores personal data like email, first name, last name, etc., while the Bookings table logs data about employees seating arrangements.

The anonymization process is not independent for each table - they are interconnected. When an employee's data is anonymized in the Employee table, all corresponding records in other tables also need to be updated to maintain consistency across the database. This implies that every time an employee record gets anonymized, its associations with other records in different tables also have to be modified.

This intricate interrelation between tables amplifies the computation required for the anonymization process, which was a primary factor contributing to the initial script's long execution time. The challenge, therefore, was not only to anonymize the data but to do so in a manner that respects the relationships between tables, ensuring the integrity of the data as a whole, while significantly reducing the time required for the process to complete.
### **Schema**: 
The schema used for this case study involves several interconnected tables in a database system. The main tables involved include Employee, users, Bookings, booking_actions, safeguards, and sensitive_values.

 * **`sensitive_values`**: This table is used to store the old and new values of sensitive data for each employee during the anonymization process. Key columns include **old_email, new_email, new_first_name, new_last_name, old_first_name, old_last_name, employee_id, old_full_name, and new_full_name**.

The relationships among these tables form the basis for the anonymization process, where sensitive data from the Employee and associated tables are replaced with anonymized data, and the mappings between old and new data are stored in the `sensitive_values` table.

![Schema Diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g3h9hue68bin436nwi5q.png)

## Problem Analysis
In the context of this case study, we encountered two significant issues that heavily impacted the efficiency and performance of our anonymization process.

* **Impediment Due to Foreign Key Constraints**: The primary issue that was identified lies within the foreign key constraints in our database schema. These constraints ensure the consistency and integrity of the data across different tables. However, they also have a substantial impact on the performance of the queries. Whenever a record in a parent table (in this case, the Employee table) is updated, the DBMS has to check all related records in the child tables to maintain the foreign key constraint. This can be a very time-consuming process, especially when dealing with large datasets, as it requires a significant amount of processing power to perform these checks for every operation.

* **Inefficient Handling of Dependent Tables**: The second problem was related to the handling of dependent tables (tables with associations to the Employee table). The existing process executed individual queries for each dependent table when an employee record was anonymized. This resulted in a **1:N** ratio of queries, where N is the number of tables having a relationship with the Employee table and further for associated table M (matching records count with employee) queries instead of single query to update same info. This approach is inefficient, as it requires a separate database operation for each table, which can significantly slow down the overall process, particularly when there are many related tables or the tables have a large number of records.

These issues combined, resulted in a process that was not only time-consuming but also computationally intensive, posing a significant bottleneck for the anonymization of data. Consequently, these problems necessitated a reevaluation of the existing process and the exploration of more efficient ways to anonymize data while maintaining the integrity of relationships in the database.

## Solution Implementation
The execution of the anonymization process was optimized by implementing the following steps:

1. **Disabling Foreign Key Checks**: Initially, to overcome the query performance issues during the anonymization process, foreign key checks were disabled. This allowed for faster execution of queries, as the system didn't have to check for relational integrity during this process.

2. **Handling Dependent Tables**: A new temporary table was created to store both the sensitive data and its anonymized counterparts. This table served as a reference during the modification of associated dependent tables.

3. **Multithreading for Anonymization**: To accelerate the anonymization process, multi-threading was employed. The sensitive data in the main table was anonymized in parallel, and the necessary fields along with their masked values were stored in the previously created temporary table.

4. **Anonymizing Dependent Tables**: The sensitive data in the dependent tables was then anonymized by referring to the temporary table. This ensured consistent anonymization across all related records.

5. **Anonymizing Missed Records**: Any records that were not anonymized in the previous steps were dealt with at this stage. Synthetic data was used to anonymize these missed records, all in a single update query, to maintain efficiency.

6. **Cleanup**: After the anonymization process was complete, the temporary table containing sensitive and anonymized data was discarded, ensuring no sensitive data was preserved in the anonymized database. Foreign key checks were then re-enabled to restore relational integrity for future operations.

7. **Truncating Irrelevant Tables**: Finally, any tables that were irrelevant or not required were truncated. This helped in maintaining the cleanliness and efficiency of the database, ensuring only necessary data was retained.

### **Sequence Diagram of the Proposed Solution**:
![Sequence Diagram](https://github.com/HarpyTech/others/assets/77878864/9cf4a523-504c-4177-b884-86c07356475d)

### **Code Implementation**: 
#### SensitiveDataAnonymizer.java
```JAVA
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import com.github.javafaker.Faker;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import javax.transaction.Transactional;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

@Component
public class SensitiveDataAnonymizer {

    private final Faker faker = new Faker();

    @Autowired
    private JdbcTemplate jdbcTemplate;

    private String scriptUpdateTime;
    private ExecutorService executorService;
    private AnonymizeEmployeeAssociations employeeAssociation;

    public SensitiveDataAnonymizer() {
        this.scriptUpdateTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));

        this.employeeAssociation = new AnonymizeEmployeeAssociations(scriptUpdateTime);
        this.executorService = Executors.newFixedThreadPool(8);
    }

    @Transactional
    public void anonymize() {
        employeeAssociation.executeSql("SET foreign_key_checks = 0;");
        truncateTables();
        employeeAssociation.addIndexes();
        System.out.println("ADDED ADDITIONAL INDEXES TO Bookings");
        anonymizeTablesByEmployee();
        System.out.println("ANONYMIZE SENSITIVE INFORMATION ASSOCIATED WITH EMPLOYEES");
        employeeAssociation.anonymize();
        employeeAssociation.dropIndexes();
        truncateTable("sensitive_values");
        System.out.println("TRUNCATE sensitive_values TABLE");
        System.out.println("DROPPED ADDITIONAL INDEXES ON Bookings");
        employeeAssociation.executeSql("SET foreign_key_checks = 1;");

    }

    public void truncateTables() { // consider these tables data is no longer required
        jdbcTemplate.execute("TRUNCATE TABLE sensitive_values");
        jdbcTemplate.execute("TRUNCATE TABLE table_X");
        jdbcTemplate.execute("TRUNCATE TABLE table_Y");
    }

    public void dataInsert(String firstName, String lastName, String email, String fullName, 
                           String newFirstName, String newLastName, String newEmail, 
                           String newFullName, String employeeId) {
        String sql = "INSERT INTO sensitive_values (old_first_name, old_last_name, old_email, " +
                     "old_full_name, new_first_name, new_last_name, new_email, new_full_name, employee_id) " +
                     "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
        jdbcTemplate.update(sql, firstName, lastName, email, fullName, newFirstName, newLastName, 
                            newEmail, newFullName, employeeId);
    }

    public void anonymizeTablesByEmployee() {
        System.out.println("\nANONYMIZE EMPLOYEES SENSITIVE DATA AND STORE INTO TEMPORARY TABLE");
        System.out.println("\nEmployee Records Anonymization started at " + java.time.LocalTime.now());

        List<Map<String, Object>> employees = jdbcTemplate.queryForList("SELECT * FROM Employee");
        for (Map<String, Object> employee : employees) {
            executorService.submit(() -> {
                Map<String, String> anonymizedEmployee = employeeAttributes(employee.get("id").toString());
                String fullName = anonymizedEmployee.get("FirstName") + " " + anonymizedEmployee.get("LastName");

                dataInsert(employee.get("FirstName").toString(), employee.get("LastName").toString(), 
                           employee.get("Email").toString(), employee.get("full_name").toString(), 
                           anonymizedEmployee.get("FirstName"), anonymizedEmployee.get("LastName"), 
                           anonymizedEmployee.get("Email"), fullName, employee.get("id").toString());

                anonymizeTable("Employee", anonymizedEmployee, "id", employee.get("id").toString());
            });
        }

        executorService.shutdown();
        while (!executorService.isTerminated()) {
        }

        anonymizeMissingRows("Employee", 'updated_at', scriptUpdateTime)
        System.out.println("Completed Employee Records at " + java.time.LocalTime.now());

        anonymizeEmployeeDefaultValues();
        System.out.println("\nANONYMIZED EMPLOYEES SENSITIVE DATA AND STORED INTO TEMPORARY TABLE");
    }

    // This method will help to anonymize any table record
    public void anonymizeTable(String tableName, Map<String, String> data, String whereColumn, String whereValue) {
        StringBuilder sql = new StringBuilder("UPDATE " + tableName + " SET ");
    
        // Prepare the SET part of the SQL
        data.forEach((key, value) -> sql.append(key + " = '" + value + "', "));
        // Remove the last comma
        sql.deleteCharAt(sql.length() - 2);
    
        // Append the WHERE clause
        sql.append(" WHERE " + whereColumn + " = '" + whereValue + "'");
    
        // Execute the SQL
        jdbcTemplate.update(sql.toString());
    }

    public void anonymizeMissingRows(String tableName, String columnName, String time) {
        List<Map<String, Object>> rows = jdbcTemplate.queryForList("SELECT * FROM " + tableName + " WHERE ? > " + columnName, time);
        if (rows.size() > 0) {
            System.out.println(tableName + " of " + rows.size() + " Records Started At " + java.time.LocalTime.now());
            for (Map<String, Object> row : rows) {
                executorService.submit(() -> {
                    Map<String, String> anonymizedAttributes = employeeAttributes(row.get("id").toString()); // consider relevant data of the table
                    anonymizeTable(tableName, anonymizedAttributes, 'id' row.get("id"));
                });
            }
            executorService.shutdown();
            while (!executorService.isTerminated()) {
            }
            System.out.println(tableName + " Completed At " + java.time.LocalTime.now());
        }
    }

    public void anonymizeEmployeeDefaultValues() { // This method is used to nullify or set some default value on any sensitive informantion on a table
        System.out.println("Employee Images Started At " + java.time.LocalTime.now());
        jdbcTemplate.update("UPDATE Employee SET profile_Image = NULL");      
        jdbcTemplate.update("UPDATE users SET encrypted_password = 'dummy-password-hash', reset_password_token = NULL, reset_password_sent_at = NULL");

        System.out.println("Default Values Modification Completed At " + java.time.LocalTime.now());
    }

    public Map<String, String> employeeAttributes(String id) {
        Map<String, String> attributes = new HashMap<>();
        attributes.put("FirstName", faker.name().firstName());
        attributes.put("LastName", faker.name().lastName());
        attributes.put("WorkPhone", faker.phoneNumber().phoneNumber());
        attributes.put("Extension", faker.phoneNumber().extension());
        attributes.put("Photo", null);
        attributes.put("Bio", faker.lorem().sentence());
        attributes.put("Email", id + faker.internet().emailAddress());
        attributes.put("updated_at", String.valueOf(System.currentTimeMillis()));
        return attributes;
    }
}

```

The provided code is written in JAVA and it seems to be part of a larger system that anonymizes sensitive data in a database. The code uses the JPA library, which is a part of Spring boot, but it can also be used standalone. Java Persistence API (JPA) tool, which means it allows you to interact with your database, like you would with SQL. In other words, it's a way to create, retrieve, update, and delete records from your database, without having to write raw SQL statements. Meanwhile in a Java Spring Boot application you can use `JdbcTemplate` to interact with your database with RAW SQL queries.

Let's break down each function:

1. `anonymize`: This is the main function that orchestrates the anonymization process. It first disables foreign key checks, truncates certain tables, and adds indexes to improve performance. It then anonymizes tables associated with employees, adds more indexes, and truncates the sensitive_values table. It finally re-enables foreign key checks.

2. `anonymize_table`: This function anonymizes a specific table. It takes in a model class, updated attributes, a field, and a value. It first counts the number of records where the field equals the value. If there are any, it updates all those records with the provided attributes.

3. `data_insert`: This function inserts a new record which will have new values of senstive data and exisitng values of employees for the future reference into the `sensitive_values` table in the database.

4. `anonymize_missing_rows`: This function anonymizes rows that haven't been anonymized yet. It checks if the model instance has certain attributes and if it does, it updates those attributes.

5. `truncate_table`: This function truncates a specific table. If the environment is test, it deletes the records instead of truncating the table.

6. `employee_attributes`: This function returns a hash of anonymized employee attributes. It uses the Faker gem to generate fake data.

#### AnonymizeEmployeeAssociations.java

```JAVA
import com.github.javafaker.Faker;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.HashMap;
import java.util.Map;

public class AnonymizeEmployeeAssociations {

    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    @Autowired
    private JdbcTemplate jdbcTemplate;

    private final Faker faker = new Faker();
    private final Map<String, String> employee = new HashMap<>();
    private final Map<String, String> additionalIndexes = new HashMap<>();
    private final long scriptUpdateTime;
    private final String WHITE_LIST_EXPRESSION = "%@domain.com"; // to white list the users of product or support team credentials.

    public AnonymizeEmployeeAssociations(long time) {
        this.scriptUpdateTime = time;

        this.additionalIndexes.put("checkInByEmail", "Bookings");
        this.additionalIndexes.put("empSittingEmail", "Bookings");

        this.employee.put("first_name", faker.name().firstName());
        this.employee.put("last_name", faker.name().lastName());
        this.employee.put("email", faker.internet().emailAddress());
        this.employee.put("name", faker.name().fullName());
        this.employee.put("subject", faker.lorem().sentence());
        this.employee.put("content", faker.lorem().paragraph());
        this.employee.put("updated_at", String.valueOf(time));
    }

    public void anonymize() {
        updateUsers();
        System.out.println("Completed Users Records at " + new Date());
        updateBookings();
        System.out.println("Completed Booking Records at " + new Date());
        updateBookingActions();
        System.out.println("Completed BookingAction Records at " + new Date());
        updateSafeguards();
        System.out.println("Completed Safeguards Records at " + new Date());
    }

    public void addIndexes() {
        // These indexes are added to speed up the SQL query on joining tables
        additionalIndexes.forEach((column, table) -> {
            String indexExistsQuery = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE table_name = ? AND index_name = ?";
            int indexExists = jdbcTemplate.queryForObject(indexExistsQuery, new Object[]{table, column}, Integer.class);

            if (indexExists == 0) {
                String addIndexQuery = "CREATE INDEX " + column + " ON " + table + " (" + column + ")";
                jdbcTemplate.execute(addIndexQuery);
            }
        });
    }

    public void dropIndexes() {
        additionalIndexes.forEach((column, table) -> {
            String indexExistsQuery = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS WHERE table_name = ? AND index_name = ?";
            int indexExists = jdbcTemplate.queryForObject(indexExistsQuery, new Object[]{table, column}, Integer.class);

            if (indexExists > 0) {
                String removeIndexQuery = "DROP INDEX " + column + " ON " + table;
                jdbcTemplate.execute(removeIndexQuery);
            }
        });
    }

    public void updateUsers() {
        String queryWithEmployee = "UPDATE users " +
                "INNER JOIN sensitive_values ON email = sensitive_values.old_email " +
                "SET email = sensitive_values.new_email, " +
                "first_name = sensitive_values.new_first_name, " +
                "last_name = sensitive_values.new_last_name, " +
                "updated_at = :updated_at " +
                "WHERE (email NOT LIKE :whiteList)"; // its optional if don't want to exclude any users data.

        Map<String, Object> params = new HashMap<>();
        params.put("updated_at", scriptUpdateTime);
        params.put("whiteList", "%" + WHITE_LIST_EXPRESSION);

        namedParameterJdbcTemplate.update(queryWithEmployee, params);

        String queryWithoutEmployee = "UPDATE users " +
                "SET email = CONCAT(id, :email), " +
                "first_name = CONCAT(id, :first_name), " +
                "last_name = :last_name, " +
                "updated_at = :updated_at " +
                "WHERE (email NOT LIKE :whiteList AND updated_at < :updated_at)";

        params.put("email", "@anonymous.com");
        params.put("first_name", "Anonymous");
        params.put("last_name", "User");

        namedParameterJdbcTemplate.update(queryWithoutEmployee, params);
    }

    public void updateBookings() {
        String queryWithEmployee = "UPDATE Bookings " +
                "LEFT JOIN sensitive_values requestor ON checkInByEmail = requestor.old_email " +
                "LEFT JOIN sensitive_values occupant ON empSittingEmail = occupant.old_email " +
                "SET checkOutBy = occupant.new_full_name, " +
                "empSittingFirstName = occupant.new_first_name, " +
                "empSittingLastName = occupant.new_last_name, " +
                "empSittingEmail = occupant.new_email, " +
                "checkInBy = requestor.new_full_name, " +
                "checkInByEmail = requestor.new_email, " +
                "canceledBy = requestor.new_full_name, " +
                "updated_at = :updated_at " +
                "WHERE (checkInByEmail = requestor.old_email" +
                "OR empSittingEmail = occupant.old_email" +
                "OR checkOutBy = occupant.old_full_name)";

        String queryWithoutEmployee = "UPDATE Bookings " +
                "SET checkOutBy = CONCAT(id, :name), " +
                "empSittingFirstName = CONCAT(id, :first_name), " +
                "empSittingLastName = :last_name, " +
                "empSittingEmail = CONCAT(id, :email), " +
                "checkInBy = CONCAT(id, :name), " +
                "checkInByEmail = CONCAT(id, :email), " +
                "updatedBy = :name, " +
                "updated_at = :updated_at " +
                "WHERE (updated_at < :updated_at)";

        Map<String, Object> params = new HashMap<>();
        params.put("name", "Anonymous User");
        params.put("first_name", "Anonymous");
        params.put("last_name", "User");
        params.put("email", "@anonymous.com");
        params.put("updated_at", scriptUpdateTime);

        namedParameterJdbcTemplate.update(queryWithEmployee, params);
        namedParameterJdbcTemplate.update(queryWithoutEmployee, params);
    }

    public void updateBookingActions() {
        String queryWithEmployee = "UPDATE booking_actions " +
                "INNER JOIN sensitive_values ON performer_email = sensitive_values.old_email " +
                "SET performed_by =  sensitive_values.new_full_name," +
                "performer_email = sensitive_values.new_email, " +
                "updated_at = :updated_at " +
                "WHERE (performer_email = sensitive_values.old_email)";

        String queryWithoutEmployee = "UPDATE booking_actions " +
                "SET performed_by = CONCAT(id, :name), " +
                "performer_email = CONCAT(id, :email), " +
                "updated_at = :updated_at " +
                "WHERE (updated_at < :updated_at)";

        Map<String, Object> params = new HashMap<>();
        params.put("name", "Anonymous User");
        params.put("email", "@anonymous.com");
        params.put("updated_at", scriptUpdateTime);

        namedParameterJdbcTemplate.update(queryWithEmployee, params);
        namedParameterJdbcTemplate.update(queryWithoutEmployee, params);
    }

    public void updateSafeguards() {
        String queryWithEmployee = "UPDATE safeguards " +
                "INNER JOIN sensitive_values ON email = sensitive_values.old_email " +
                "SET email = sensitive_values.new_email, " +
                "updated_at = :updated_at " +
                "WHERE (email = sensitive_values.old_email)";

        String queryWithoutEmployee = "UPDATE safeguards " +
                "SET email = CONCAT(id, :email), " +
                "updated_at = :updated_at " +
                "WHERE (updated_at < :updated_at)";

        Map<String, Object> params = new HashMap<>();
        params.put("email", "@anonymous.com");
        params.put("updated_at", scriptUpdateTime);

        namedParameterJdbcTemplate.update(queryWithEmployee, params);
        namedParameterJdbcTemplate.update(queryWithoutEmployee, params);
    }

```

The class named AnonymizeEmployeeAssociations is designed to anonymize real values with generated fake data in other related tables associated with main (Employee) table in the database. It uses the javafaker library to generate fake data.

Key elements in this class include:

* **Instance variables**: The class has instance variables such as namedParameterJdbcTemplate, jdbcTemplate, faker, employee, additionalIndexes and scriptUpdateTime. The namedParameterJdbcTemplate and jdbcTemplate are Spring JDBC templates used for interacting with the database. The faker instance is used to generate fake data. The employee and additionalIndexes are HashMaps used to store employee information and index information respectively. The scriptUpdateTime is used for tracking the time of script execution.

* **anonymize() method**: This method calls a series of update methods that anonymize different parts of the database. These include updateUsers(),updateBooking(), updateBookingActions(), updateSafeguards().

* **addIndexes() and dropIndexes() methods**: These methods are used to add and drop indexes on the database tables to speed up the SQL queries.

* **update methods**: These methods are used to update the various tables in the database. They use SQL queries to update the records in the tables. Some of the update methods use JOIN operations to anonymize the data stored into `sensitive_values` table during `Employee` table anonymization process. In the provided code, queryWithEmployee and queryWithoutEmployee are SQL queries used to update different sets of records in the database.

>    1. **`queryWithEmployee`**: This SQL query is used for records that have a matching entry in the **sensitive_values** table. If an employee's old email is found in the sensitive_values table, the employee's email, first name, and last name are updated to the new anonymized values from the **sensitive_values** table.

>    2. **`queryWithoutEmployee`**: Conversely, this SQL query is used for records that do not have a matching entry in the **sensitive_values** table. If an employee's email is not found in the **sensitive_values** table, or if the record has not been updated since the script started running, the employee's email, first name, and last name are set to a default anonymized value.
 
  Using these two separate queries allows the anonymization process to handle different cases based on whether a matching anonymized value exists for an employee, providing a more fine-tuned approach to data anonymization.

### Results & OutComes:
1. **Significant Performance Improvement**: The performance of the anonymization process improved drastically, decreasing from over **22 hours** to approximately **40-50 minutes**. This represents a significant efficiency gain, making the process of anonymizing **10 million records** far more viable and less time-consuming.

2. **Optimized Anonymization Process**: With the proposed solution, dependent tables are not updated each time an Employee record is anonymized. Instead, the dependent tables are anonymized after all the Employee records have been anonymized. This strategy leads to a reduction in the number of queries executed, further contributing to improved performance.

3. **Data Swapping with UPDATE and JOINS**: The proposed solution leverages the power of SQL UPDATE and JOIN statements to swap data with a temporary table. This approach allows for efficient, bulk updating of records, which is a more performant operation compared to row-by-row updates.

### Lessons Learned:
1. **Understanding Database Operations**: The case study provided an opportunity to understand the complexity and time consumption of the database operations in anonymizing large data sets. It helped to understand how data is structured, queried, and manipulated in a database, specifically for large tables.

2. **Performance Optimization**: One of the key learnings was the knowledge of performance optimization in handling large databases. The solution proposed had significantly reduced the time taken to anonymize data from over 22 hours to around 40-50 minutes, which indicates the importance of efficient data management and processing techniques.

3. **Exploration of Data Anonymization Techniques**: This case study provided a practical platform to delve into and apply sophisticated data anonymization techniques, specifically **Synthetic Data Generation** and **Data** **Swapping**. In the current digital climate, maintaining data privacy and ensuring security are paramount. Through this case study, an efficient methodology to anonymize sensitive database information was demonstrated, thereby emphasizing the importance of data privacy. The use of Synthetic Data Generation helps to create artificial data that can be used in place of real data, thereby protecting sensitive information. Furthermore, Data Swapping was employed to interchange the data among records, effectively anonymizing the data while preserving the overall data distribution and relationships. These techniques played a vital role in accomplishing the successful anonymization of the database in this study.

4. **Working with Joins and Temp Tables**: The case study provided a hands-on experience on how to use SQL joins and temporary tables to perform complex data manipulation tasks. These are vital skills in data management and analytics.

5. **Understanding Dependencies**: The case study emphasized the importance of understanding dependencies in a database schema. By ensuring that dependent tables are updated only after all records in the Employee table are anonymized, the solution avoided unnecessary updates and improved performance.

6. **Software Development Best Practices**: The case study highlighted the importance of careful planning, testing, and implementation in the software development process. The successful solution was the result of a well-thought-out plan, rigorous testing, and careful implementation.

### **Use Cases**:
1. **Data Anonymization for Compliance**: Companies often need to conform to various data privacy regulations such as GDPR, CCPA, HIPAA, etc. In such cases, this solution can be used to anonymize sensitive data in the database, ensuring regulatory compliance.

2. **Data Sharing and Collaboration**: When sharing data between departments or with external partners for business purposes, it's crucial to ensure sensitive details are not exposed. This solution can be used to anonymize the data before sharing.

3. **Data Analysis and Research**: For data analysis and research purposes, analysts often need access to real-world data. However, they usually don't need to know the real identities behind the data. This solution can anonymize the database, allowing safe analysis.

4. **Software Testing**: In software development lifecycle, testing software with realistic data is crucial. However, using real data brings about privacy issues. The proposed solution can be used to anonymize data, creating a realistic but privacy-safe testing environment.

5. **Data Backup and Archiving**: Companies archive old data for record-keeping, but it's not always necessary to keep sensitive details in these records. This solution can anonymize the data before archiving, reducing potential risks.

In each of these use cases, the key benefit is the ability to use and manage data without exposing sensitive information, thereby improving privacy and security.

### **Conclusion**:
This case study demonstrates a significant improvement in anonymizing sensitive data in a database. The initial process took more than **22 hours** to anonymize **10 million** records. However, the proposed solution drastically reduced this time to around **40-50 minutes** - an incredible performance enhancement.

The solution involved swapping data with an UPDATE and JOIN operations on a temporary table, which avoids updating dependent tables every time an employee record is updated. Instead, the dependent tables are updated only after all employee records have been anonymized. This approach ensures efficiency by reducing the number of update operations on the database.

Overall, the case study highlights a practical method to handle sensitive data anonymization in a large database. It underscores the importance of well-planned strategies when dealing with large volumes of data, especially when performance and data privacy matters. The proposed solution in this case study could serve as a valuable reference for similar tasks in the future.