Skip to content

fix: Batch MySQL queries in getting ready tasks - #83

Merged
sitaowang1998 merged 5 commits into
y-scope:mainfrom
sitaowang1998:batch_ready_task
Mar 27, 2025
Merged

fix: Batch MySQL queries in getting ready tasks#83
sitaowang1998 merged 5 commits into
y-scope:mainfrom
sitaowang1998:batch_ready_task

Conversation

@sitaowang1998

@sitaowang1998 sitaowang1998 commented Mar 27, 2025

Copy link
Copy Markdown
Collaborator

Description

MySQL storage currently executes 2 queries for each task in get_ready_tasks. Scheduler needs 1 more query for each task to get job id, and 1 more query for each job to get job metadata. If tasks have data, then 1 more query is executed for each data to get locality. Same storage accesses are needed for get_task_timeout.

This pr introduces ScheduleTaskMetadata to encapsulate all metadata needed by scheduler. get_ready_tasks and get_task_timeout now returns vector of ScheduleTaskMetadata so that scheduler does not need extra storage access.

MySQL storage also batches queries in get_ready_tasks and get_task_time to reduce number of queries and runtime.

Checklist

  • The PR satisfies the contribution guidelines.
  • This is a breaking change and that has been indicated in the PR title, OR this isn't a
    breaking change.
  • Necessary docs have been updated, OR no docs need to be updated.

Validation performed

  • GitHub workflows pass
  • Unit tests pass in dev container
  • Integration tests pass in dev container

Summary by CodeRabbit

  • New Features
    • Introduced an enhanced task scheduling metadata system to support more detailed task assignment and management.
  • Refactor
    • Streamlined scheduling logic by simplifying locality checks.
    • Optimised task fetching and sorting for improved performance.
    • Updated storage interactions to align with the enhanced scheduling framework.

@sitaowang1998
sitaowang1998 requested a review from a team as a code owner March 27, 2025 04:42
@coderabbitai

coderabbitai Bot commented Mar 27, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

The pull request introduces the ScheduleTaskMetadata class to encapsulate scheduled task information and updates various components to use it instead of the previous Task type. The changes remove obsolete methods, such as the locality-checking function in FifoPolicy, and streamline task fetching and sorting mechanisms. Additionally, the method signatures in the storage components are modified from using tuples or Task objects to exclusively use ScheduleTaskMetadata, ensuring consistency in task metadata handling across the system.

Changes

File(s) Summary
src/spider/core/Task.hpp Added new ScheduleTaskMetadata class with constructors, accessor, and mutator methods for attributes like id, function_name, job_id, client_id, job_creation_time, hard_localities, and soft_localities. Included <chrono> for handling time attributes.
src/spider/scheduler/FifoPolicy.cpp
src/spider/scheduler/FifoPolicy.hpp
Removed the task_locality_satisfied method and its logic. Updated the member variable from std::vector<core::Task> to std::vector<core::ScheduleTaskMetadata>. Replaced old locality satisfaction checks with an inline lambda in schedule_next to directly check hard_localities. Also removed the absl::flat_hash_map data cache from the header.
src/spider/storage/MetadataStorage.hpp Updated the signatures of get_ready_tasks and get_task_timeout to use std::vector<ScheduleTaskMetadata>* instead of using Task objects or tuples. Removed the <tuple> header as tuple-based structures are no longer used in these method signatures.
src/spider/storage/mysql/MySqlStorage.cpp
src/spider/storage/mysql/MySqlStorage.hpp
Updated the method signatures in MySqlMetadataStorage to use ScheduleTaskMetadata for both get_ready_tasks and get_task_timeout. Modified the SQL queries to select appropriate fields (e.g. job_id, function_name) and streamlined the mapping of database records into the new task metadata structure, eliminating previous tuple handling methods.

Sequence Diagram(s)

sequenceDiagram
    participant Worker
    participant FifoPolicy
    participant TaskMetadata as ScheduleTaskMetadata
    Worker->>FifoPolicy: Request next task
    FifoPolicy->>TaskMetadata: Retrieve hard_localities and other metadata
    alt Task has no locality restrictions or meets requirements
        FifoPolicy-->>Worker: Assign task
    else
        FifoPolicy-->>Worker: Skip task
    end
Loading
sequenceDiagram
    participant Caller
    participant Storage as MySqlMetadataStorage
    participant Database
    Caller->>Storage: get_ready_tasks(conn, tasks)
    Storage->>Database: Execute SQL query (select job_id, function_name, etc.)
    Database-->>Storage: Return task records
    Storage->>Caller: Return populated ScheduleTaskMetadata list
Loading

Possibly related PRs


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f87d1f9 and 022bc8f.

📒 Files selected for processing (1)
  • src/spider/storage/mysql/MySqlStorage.cpp (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: non-storage-unit-tests (ubuntu-24.04)
  • GitHub Check: non-storage-unit-tests (ubuntu-22.04)
  • GitHub Check: lint
🔇 Additional comments (10)
src/spider/storage/mysql/MySqlStorage.cpp (10)

17-17: Added necessary include for hash map data structure.

Including the Abseil flat hash map is appropriate for the optimizations implemented in this PR. This container is generally more efficient than standard library alternatives for performance-critical code.


1234-1237: Good function signature change to support optimization.

Changing the function signature to accept ScheduleTaskMetadata instead of Task aligns with the PR objective to reduce extra storage accesses by storing all necessary metadata in a single structure.


1243-1247: SQL query optimized to fetch only required data.

The query is now more efficient, selecting only the necessary columns (id, func_name, job_id) for constructing the ScheduleTaskMetadata objects.


1268-1300: Efficient batch processing of job metadata.

This is a significant improvement that reduces database roundtrips by batching job metadata queries. The code sets up a single prepared statement and adds multiple job IDs to the batch for processing in one execution.


1302-1328: Effective batching of data locality queries.

The code efficiently retrieves locality information for multiple tasks in a single batch operation, which is a good optimization especially for tasks with large amounts of data.


1330-1333: Clean population of output tasks.

Straightforward transformation of the collected task metadata into the output vector.


1635-1640: Optimized task timeout query.

The SQL query is improved to only select the task IDs that have timed out, reducing the amount of data transferred from the database.


1655-1675: Efficient batch checking of non-timed-out tasks.

This is a good optimization that checks multiple tasks in a single query to determine which ones haven't timed out, rather than checking each individually.


1739-1765: Consistent batch processing of data locality.

This section mirrors the implementation in get_ready_tasks, maintaining a consistent pattern for batch processing of locality data, which is good for code maintainability.


1767-1770: Clean output population logic.

The final step of populating the output vector with the processed task metadata is straightforward and effective.

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🔭 Outside diff range comments (1)
src/spider/storage/mysql/MySqlStorage.cpp (1)

1629-1653: ⚠️ Potential issue

Potential SQL syntax error in the SELECT FROM query.
In the snippet below, the statement is incomplete and may not compile under typical SQL engines:

"SELECT FROM `task_instances` as `t1` JOIN `tasks` ON`t1`.`task_id` = `tasks`.`id` WHERE `t1.task_id` = ? ..."

You likely need to specify the selected columns. For example:

- "SELECT FROM `task_instances` as `t1` JOIN `tasks` ON`t1`.`task_id` = `tasks`.`id` WHERE `t1.task_id` = ? ..."
+ "SELECT t1.`task_id` FROM `task_instances` AS `t1` JOIN `tasks` ON t1.`task_id` = `tasks`.`id` WHERE t1.`task_id` = ? AND ..."

This fix ensures the query is valid and returns the correct columns.

🧹 Nitpick comments (1)
src/spider/core/Task.hpp (1)

146-159: Consider adding validation in setter and adder methods.

While the implementation is functional, adding validation for inputs in methods like add_hard_locality and add_soft_locality would improve robustness.

auto add_hard_locality(std::string const& locality) -> void {
+    if (!locality.empty()) {
        m_hard_localities.push_back(locality);
+    }
}

auto add_soft_locality(std::string const& locality) -> void {
+    if (!locality.empty()) {
        m_soft_localities.push_back(locality);
+    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e1446f and dfce727.

📒 Files selected for processing (6)
  • src/spider/core/Task.hpp (2 hunks)
  • src/spider/scheduler/FifoPolicy.cpp (1 hunks)
  • src/spider/scheduler/FifoPolicy.hpp (1 hunks)
  • src/spider/storage/MetadataStorage.hpp (2 hunks)
  • src/spider/storage/mysql/MySqlStorage.cpp (4 hunks)
  • src/spider/storage/mysql/MySqlStorage.hpp (2 hunks)
🧰 Additional context used
🧬 Code Definitions (4)
src/spider/storage/MetadataStorage.hpp (1)
src/spider/storage/DataStorage.hpp (15)
  • conn (23-23)
  • conn (25-29)
  • conn (30-34)
  • conn (35-35)
  • conn (37-37)
  • conn (38-38)
  • conn (39-43)
  • conn (44-48)
  • conn (49-53)
  • conn (54-58)
  • conn (59-59)
  • conn (61-61)
  • conn (63-63)
  • conn (65-70)
  • conn (71-76)
src/spider/storage/mysql/MySqlStorage.hpp (1)
src/spider/storage/MetadataStorage.hpp (16)
  • conn (27-27)
  • conn (29-29)
  • conn (30-30)
  • conn (32-33)
  • conn (35-40)
  • conn (41-47)
  • conn (48-49)
  • conn (50-51)
  • conn (52-53)
  • conn (54-58)
  • conn (59-63)
  • conn (64-68)
  • conn (69-69)
  • conn (70-70)
  • conn (71-72)
  • conn (73-73)
src/spider/storage/mysql/MySqlStorage.cpp (2)
src/spider/storage/MetadataStorage.hpp (16)
  • conn (27-27)
  • conn (29-29)
  • conn (30-30)
  • conn (32-33)
  • conn (35-40)
  • conn (41-47)
  • conn (48-49)
  • conn (50-51)
  • conn (52-53)
  • conn (54-58)
  • conn (59-63)
  • conn (64-68)
  • conn (69-69)
  • conn (70-70)
  • conn (71-72)
  • conn (73-73)
src/spider/core/Task.hpp (2)
  • client_id (146-146)
  • client_id (146-146)
src/spider/core/Task.hpp (1)
src/spider/core/Data.hpp (6)
  • nodiscard (20-20)
  • nodiscard (22-22)
  • nodiscard (24-26)
  • nodiscard (28-28)
  • locality (30-30)
  • locality (30-30)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: non-storage-unit-tests (ubuntu-24.04)
  • GitHub Check: non-storage-unit-tests (ubuntu-22.04)
  • GitHub Check: lint
🔇 Additional comments (15)
src/spider/core/Task.hpp (4)

4-4: Include of <chrono> added for time point handling.

The <chrono> header is correctly added to support the std::chrono::system_clock::time_point used in the new ScheduleTaskMetadata class.


113-125: New ScheduleTaskMetadata class looks well-structured.

The class design effectively encapsulates all necessary metadata for scheduled tasks with appropriate constructor parameters and member initialization.


126-144: Well-implemented accessor methods with appropriate const qualifiers.

The getter methods properly use [[nodiscard]] attribute, return by value or const reference as appropriate, and maintain const correctness.


161-169: Member variables layout is clean and well-organized.

The private section properly encapsulates all necessary metadata fields with clear naming conventions.

src/spider/scheduler/FifoPolicy.hpp (1)

37-37: Updated task collection type to use the new metadata structure.

The change from std::vector<core::Task> to std::vector<core::ScheduleTaskMetadata> aligns with the PR objective of optimizing task metadata handling.

src/spider/scheduler/FifoPolicy.cpp (3)

40-49: Simplified locality check with direct access to hard_localities.

The lambda function efficiently determines if a task can be scheduled on a worker by checking if the worker's address is in the task's hard localities list or if no hard localities are specified.


63-68: Improved task sorting based on job creation time.

The sort comparison now directly uses the job creation time from the metadata structure, simplifying the code and eliminating the need for additional job metadata queries.


58-61:

Details

❓ Verification inconclusive

Verify if two separate function calls are intentional.

Both get_ready_tasks and get_task_timeout are adding tasks to the same m_tasks vector. Ensure this is intentional and doesn't result in duplicate tasks.


🏁 Script executed:

#!/bin/bash
# Check if both methods can add the same task to the vector

# Look for implementation of get_ready_tasks and get_task_timeout
rg -A 10 "get_ready_tasks.*ScheduleTaskMetadata" --type cpp
rg -A 10 "get_task_timeout.*ScheduleTaskMetadata" --type cpp

# Look for how tasks are identified in these methods
rg "task.*id.*unique" --type cpp

Length of output: 4305


Action Required: Confirm Duplicate Handling in Task Fetching

At src/spider/scheduler/FifoPolicy.cpp (lines 58-61), there are two distinct calls—get_ready_tasks and get_task_timeout—both appending to the m_tasks vector. The grep results confirm their declarations in the storage interfaces but did not reveal any explicit duplicate filtering logic in the searched patterns. This leaves some uncertainty regarding whether the potential overlap of tasks is intentional or if additional de-duplication safeguards should be implemented.

  • Review the implementations of both functions (in MetadataStorage.hpp and MySqlStorage.hpp) to confirm that duplicate tasks are either prevented or intentionally allowed.
  • Manually verify that tasks identified as ready and those flagged for timeout do not overlap, or that any overlap is handled appropriately elsewhere in the code.
src/spider/storage/MetadataStorage.hpp (2)

80-81: Method signature updated to use ScheduleTaskMetadata.

The get_ready_tasks method now accepts a vector of ScheduleTaskMetadata instead of Task, aligning with the objective of batching queries and reducing storage accesses.


100-101: Method signature simplified to use ScheduleTaskMetadata.

The get_task_timeout method now returns tasks as ScheduleTaskMetadata objects instead of tuples of TaskInstance and Task, which streamlines the API and reduces the need for multiple queries.

src/spider/storage/mysql/MySqlStorage.hpp (2)

83-84: Adoption of ScheduleTaskMetadata aligns well with the batching approach.
Switching the parameter type to std::vector<ScheduleTaskMetadata>* is consistent with the new design goals. It eliminates the overhead of fetching additional information per task during scheduling.


99-100: Consistent signature update for get_task_timeout.
Ensuring that all downstream usages now rely on ScheduleTaskMetadata will help standardize metadata handling in timeouts.

src/spider/storage/mysql/MySqlStorage.cpp (3)

17-17: Include statement for absl::flat_hash_map.
Including <absl/container/flat_hash_map.h> reflects the shift towards more efficient hashing structures. Please confirm that your build environment reliably provides Abseil libraries and that you have no linking issues.


1234-1340: Refined get_ready_tasks method enhances performance with fewer queries and richer metadata.

  1. Bulk retrieval of job metadata via batched prepared statements (job_statement) reduces round-trips to the database.
  2. Collecting data locality pointers in a single pass is efficient; however, consider verifying correct handling of tasks that have no associated data. This ensures you don’t inadvertently miss tasks or introduce errors when data is absent.

Overall, this method effectively consolidates relevant scheduling metadata.


1654-1776: Timed-out tasks now return enriched scheduling metadata.

  1. The batching of job lookups (lines 1705–1716) is a solid approach to reduce multiple queries, but ensure that large batches are handled efficiently by the driver.
  2. Data locality gathering (lines 1739–1764) matches the logic from get_ready_tasks, maintaining consistency for tasks that time out.
  3. Confirm that you properly handle corner cases where tasks may not hold locality data.

Overall, this method neatly parallels get_ready_tasks for timeouts, improving performance and uniformity.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (1)
src/spider/storage/mysql/MySqlStorage.cpp (1)

1234-1341: ⚠️ Potential issue

Fix missing insertion for the first task ID.
When adding to job_id_to_task_ids at lines 1261-1266, the vector is only populated in the else branch, leaving out the first task.

Apply this diff to ensure you always store the task ID:

-if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
-    job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
-} else {
-    job_id_to_task_ids[job_id].emplace_back(task_id);
-}
+if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
+    job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
+}
+job_id_to_task_ids[job_id].emplace_back(task_id);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dfce727 and cc5b341.

📒 Files selected for processing (1)
  • src/spider/storage/mysql/MySqlStorage.cpp (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: non-storage-unit-tests (ubuntu-24.04)
  • GitHub Check: non-storage-unit-tests (ubuntu-22.04)
  • GitHub Check: lint
🔇 Additional comments (2)
src/spider/storage/mysql/MySqlStorage.cpp (2)

17-17: Header inclusion looks good.
No issues here.


1629-1630: Signature change looks consistent.
Switching from tuples to ScheduleTaskMetadata aligns well with the new structure.

Comment on lines +1635 to 1771
std::unique_ptr<sql::ResultSet> const task_timeout_res(statement->executeQuery(
"SELECT `t1`.`task_id` FROM `task_instances` as `t1` JOIN `tasks` ON "
"`t1`.`task_id` = `tasks`.`id` WHERE `tasks`.`timeout` > 0.0001 AND "
"TIMESTAMPDIFF(MICROSECOND, `t1`.`start_time`, CURRENT_TIMESTAMP()) > "
"`tasks`.`timeout` * 1000"
));
if (task_timeout_res->rowsCount() == 0) {
static_cast<MySqlConnection&>(conn)->commit();
return StorageErr{};
}

std::unique_ptr<sql::PreparedStatement> not_timeout_statement(
static_cast<MySqlConnection&>(conn)->prepareStatement(
"SELECT FROM `task_instances` as `t1` JOIN `tasks` ON`t1`.`task_id` = "
"`tasks`.`id` WHERE `t1.task_id` = ? AND TIMESTAMPDIFF(MICROSECOND, "
"`t1`.`start_time`, CURRENT_TIMESTAMP()) < `tasks`.`timeout` * 1000"
"SELECT `t1`.`task_id` FROM `task_instances` as `t1` JOIN `tasks` ON "
"`t1`.`task_id` = `tasks`.`id` WHERE `t1.task_id` = ? AND "
"TIMESTAMPDIFF(MICROSECOND, `t1`.`start_time`, CURRENT_TIMESTAMP()) < "
"`tasks`.`timeout` * 1000"
)
);

absl::flat_hash_set<boost::uuids::uuid> task_ids;
while (task_timeout_res->next()) {
boost::uuids::uuid const task_id
= read_id(task_timeout_res->getBinaryStream("task_id"));
task_ids.insert(task_id);
sql::bytes task_id_bytes = uuid_get_bytes(task_id);
not_timeout_statement->setBytes(1, &task_id_bytes);
not_timeout_statement->addBatch();
}
not_timeout_statement->execute();
std::unique_ptr<sql::ResultSet> const not_timeout_res(not_timeout_statement->getResultSet()
);
while (not_timeout_res->next()) {
boost::uuids::uuid const task_id = read_id(not_timeout_res->getBinaryStream("task_id"));
task_ids.erase(task_id);
}

if (task_ids.empty()) {
static_cast<MySqlConnection&>(conn)->commit();
return StorageErr{};
}

// Get task metadata
std::unique_ptr<sql::PreparedStatement> task_statement(
static_cast<MySqlConnection&>(conn)->prepareStatement(
"SELECT `id`, `func_name`, `state`, `timeout` FROM `tasks` WHERE `id` = ?"
"SELECT `id`, `func_name`, `job_id` FROM `tasks` WHERE `id` = ?"
)
);
while (res->next()) {
boost::uuids::uuid const task_instance_id = read_id(res->getBinaryStream("id"));
boost::uuids::uuid const task_id = read_id(res->getBinaryStream("task_id"));
for (boost::uuids::uuid const& task_id : task_ids) {
sql::bytes task_id_bytes = uuid_get_bytes(task_id);
// Check all task instance have timed out
not_timeout_statement->setBytes(1, &task_id_bytes);
std::unique_ptr<sql::ResultSet> not_timeout_res(not_timeout_statement->executeQuery());
if (not_timeout_res->rowsCount() > 0) {
continue;
task_statement->setBytes(1, &task_id_bytes);
task_statement->addBatch();
}
task_statement->execute();
std::unique_ptr<sql::ResultSet> const task_res(task_statement->getResultSet());

absl::flat_hash_map<boost::uuids::uuid, ScheduleTaskMetadata> new_tasks;
absl::flat_hash_map<boost::uuids::uuid, std::vector<boost::uuids::uuid>> job_id_to_task_ids;
while (task_res->next()) {
boost::uuids::uuid const task_id = read_id(task_res->getBinaryStream("id"));
boost::uuids::uuid const job_id = read_id(task_res->getBinaryStream("job_id"));
std::string const function_name = get_sql_string(task_res->getString("func_name"));
new_tasks.emplace(task_id, ScheduleTaskMetadata{task_id, function_name, job_id});
if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
} else {
job_id_to_task_ids[job_id].emplace_back(task_id);
}
}

// Fetch task
task_statement->setBytes(1, &task_id_bytes);
std::unique_ptr<sql::ResultSet> task_res(task_statement->executeQuery());
if (task_res->next()) {
Task const task = fetch_full_task(static_cast<MySqlConnection&>(conn), task_res);
tasks->emplace_back(TaskInstance{task_instance_id, task_id}, task);
// Get all job metadata
std::unique_ptr<sql::PreparedStatement> job_statement(
static_cast<MySqlConnection&>(conn)->prepareStatement(
"SELECT `id`, `client_id`, `creation_time` FROM `jobs` WHERE `id` = ?"
)
);
for (auto const& iter : job_id_to_task_ids) {
sql::bytes job_id_bytes = uuid_get_bytes(iter.first);
job_statement->setBytes(1, &job_id_bytes);
job_statement->addBatch();
}
job_statement->execute();
std::unique_ptr<sql::ResultSet> const job_res(job_statement->getResultSet());
while (job_res->next()) {
boost::uuids::uuid const job_id = read_id(job_res->getBinaryStream("id"));
boost::uuids::uuid const client_id = read_id(job_res->getBinaryStream("client_id"));
std::optional<std::chrono::system_clock::time_point> const optional_creation_time
= parse_timestamp(get_sql_string(job_res->getString("creation_time")));
if (false == optional_creation_time.has_value()) {
static_cast<MySqlConnection&>(conn)->rollback();
return StorageErr{
StorageErrType::OtherErr,
fmt::format(
"Cannot parse timestamp {}",
get_sql_string(job_res->getString("creation_time"))
)
};
}
for (boost::uuids::uuid const& task_id : job_id_to_task_ids[job_id]) {
new_tasks[task_id].set_client_id(client_id);
new_tasks[task_id].set_job_creation_time(optional_creation_time.value());
}
}

// Get all data localities
std::unique_ptr<sql::PreparedStatement> locality_statement(
static_cast<MySqlConnection&>(conn)->prepareStatement(
"SELECT `task_inputs`.`task_id`, `data`.`hard_locality`, "
"`data_locality`.`address` FROM `task_inputs` JOIN `data` ON "
"`task_inputs`.`data_id` = `data`.`id` JOIN `data_locality` ON `data`.`id` "
"= `data_locality`.`id` WHERE `task_inputs`.`task_id` = ? AND "
"`task_inputs`.`task_id` IS NOT NULL"
)
);
for (auto const& iter : new_tasks) {
sql::bytes task_id_bytes = uuid_get_bytes(iter.first);
locality_statement->setBytes(1, &task_id_bytes);
locality_statement->addBatch();
}
locality_statement->execute();
std::unique_ptr<sql::ResultSet> const locality_res(locality_statement->getResultSet());
while (locality_res->next()) {
boost::uuids::uuid const task_id = read_id(locality_res->getBinaryStream("task_id"));
bool const hard_locality = locality_res->getBoolean("hard_locality");
std::string const address = get_sql_string(locality_res->getString("address"));
if (hard_locality) {
new_tasks[task_id].add_hard_locality(address);
} else {
new_tasks[task_id].add_soft_locality(address);
}
}

// Add all tasks to the output
for (auto const& iter : new_tasks) {
tasks->emplace_back(iter.second);
}
} catch (sql::SQLException& e) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Resolve the identical bug in job_id_to_task_ids.
Similar to get_ready_tasks, the first task for each job is left out in lines 1698-1703.

Proposed fix:

-if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
-    job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
-} else {
-    job_id_to_task_ids[job_id].emplace_back(task_id);
-}
+if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
+    job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
+}
+job_id_to_task_ids[job_id].emplace_back(task_id);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
std::unique_ptr<sql::ResultSet> const task_timeout_res(statement->executeQuery(
"SELECT `t1`.`task_id` FROM `task_instances` as `t1` JOIN `tasks` ON "
"`t1`.`task_id` = `tasks`.`id` WHERE `tasks`.`timeout` > 0.0001 AND "
"TIMESTAMPDIFF(MICROSECOND, `t1`.`start_time`, CURRENT_TIMESTAMP()) > "
"`tasks`.`timeout` * 1000"
));
if (task_timeout_res->rowsCount() == 0) {
static_cast<MySqlConnection&>(conn)->commit();
return StorageErr{};
}
std::unique_ptr<sql::PreparedStatement> not_timeout_statement(
static_cast<MySqlConnection&>(conn)->prepareStatement(
"SELECT FROM `task_instances` as `t1` JOIN `tasks` ON`t1`.`task_id` = "
"`tasks`.`id` WHERE `t1.task_id` = ? AND TIMESTAMPDIFF(MICROSECOND, "
"`t1`.`start_time`, CURRENT_TIMESTAMP()) < `tasks`.`timeout` * 1000"
"SELECT `t1`.`task_id` FROM `task_instances` as `t1` JOIN `tasks` ON "
"`t1`.`task_id` = `tasks`.`id` WHERE `t1.task_id` = ? AND "
"TIMESTAMPDIFF(MICROSECOND, `t1`.`start_time`, CURRENT_TIMESTAMP()) < "
"`tasks`.`timeout` * 1000"
)
);
absl::flat_hash_set<boost::uuids::uuid> task_ids;
while (task_timeout_res->next()) {
boost::uuids::uuid const task_id
= read_id(task_timeout_res->getBinaryStream("task_id"));
task_ids.insert(task_id);
sql::bytes task_id_bytes = uuid_get_bytes(task_id);
not_timeout_statement->setBytes(1, &task_id_bytes);
not_timeout_statement->addBatch();
}
not_timeout_statement->execute();
std::unique_ptr<sql::ResultSet> const not_timeout_res(not_timeout_statement->getResultSet()
);
while (not_timeout_res->next()) {
boost::uuids::uuid const task_id = read_id(not_timeout_res->getBinaryStream("task_id"));
task_ids.erase(task_id);
}
if (task_ids.empty()) {
static_cast<MySqlConnection&>(conn)->commit();
return StorageErr{};
}
// Get task metadata
std::unique_ptr<sql::PreparedStatement> task_statement(
static_cast<MySqlConnection&>(conn)->prepareStatement(
"SELECT `id`, `func_name`, `state`, `timeout` FROM `tasks` WHERE `id` = ?"
"SELECT `id`, `func_name`, `job_id` FROM `tasks` WHERE `id` = ?"
)
);
while (res->next()) {
boost::uuids::uuid const task_instance_id = read_id(res->getBinaryStream("id"));
boost::uuids::uuid const task_id = read_id(res->getBinaryStream("task_id"));
for (boost::uuids::uuid const& task_id : task_ids) {
sql::bytes task_id_bytes = uuid_get_bytes(task_id);
// Check all task instance have timed out
not_timeout_statement->setBytes(1, &task_id_bytes);
std::unique_ptr<sql::ResultSet> not_timeout_res(not_timeout_statement->executeQuery());
if (not_timeout_res->rowsCount() > 0) {
continue;
task_statement->setBytes(1, &task_id_bytes);
task_statement->addBatch();
}
task_statement->execute();
std::unique_ptr<sql::ResultSet> const task_res(task_statement->getResultSet());
absl::flat_hash_map<boost::uuids::uuid, ScheduleTaskMetadata> new_tasks;
absl::flat_hash_map<boost::uuids::uuid, std::vector<boost::uuids::uuid>> job_id_to_task_ids;
while (task_res->next()) {
boost::uuids::uuid const task_id = read_id(task_res->getBinaryStream("id"));
boost::uuids::uuid const job_id = read_id(task_res->getBinaryStream("job_id"));
std::string const function_name = get_sql_string(task_res->getString("func_name"));
new_tasks.emplace(task_id, ScheduleTaskMetadata{task_id, function_name, job_id});
if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
} else {
job_id_to_task_ids[job_id].emplace_back(task_id);
}
}
// Fetch task
task_statement->setBytes(1, &task_id_bytes);
std::unique_ptr<sql::ResultSet> task_res(task_statement->executeQuery());
if (task_res->next()) {
Task const task = fetch_full_task(static_cast<MySqlConnection&>(conn), task_res);
tasks->emplace_back(TaskInstance{task_instance_id, task_id}, task);
// Get all job metadata
std::unique_ptr<sql::PreparedStatement> job_statement(
static_cast<MySqlConnection&>(conn)->prepareStatement(
"SELECT `id`, `client_id`, `creation_time` FROM `jobs` WHERE `id` = ?"
)
);
for (auto const& iter : job_id_to_task_ids) {
sql::bytes job_id_bytes = uuid_get_bytes(iter.first);
job_statement->setBytes(1, &job_id_bytes);
job_statement->addBatch();
}
job_statement->execute();
std::unique_ptr<sql::ResultSet> const job_res(job_statement->getResultSet());
while (job_res->next()) {
boost::uuids::uuid const job_id = read_id(job_res->getBinaryStream("id"));
boost::uuids::uuid const client_id = read_id(job_res->getBinaryStream("client_id"));
std::optional<std::chrono::system_clock::time_point> const optional_creation_time
= parse_timestamp(get_sql_string(job_res->getString("creation_time")));
if (false == optional_creation_time.has_value()) {
static_cast<MySqlConnection&>(conn)->rollback();
return StorageErr{
StorageErrType::OtherErr,
fmt::format(
"Cannot parse timestamp {}",
get_sql_string(job_res->getString("creation_time"))
)
};
}
for (boost::uuids::uuid const& task_id : job_id_to_task_ids[job_id]) {
new_tasks[task_id].set_client_id(client_id);
new_tasks[task_id].set_job_creation_time(optional_creation_time.value());
}
}
// Get all data localities
std::unique_ptr<sql::PreparedStatement> locality_statement(
static_cast<MySqlConnection&>(conn)->prepareStatement(
"SELECT `task_inputs`.`task_id`, `data`.`hard_locality`, "
"`data_locality`.`address` FROM `task_inputs` JOIN `data` ON "
"`task_inputs`.`data_id` = `data`.`id` JOIN `data_locality` ON `data`.`id` "
"= `data_locality`.`id` WHERE `task_inputs`.`task_id` = ? AND "
"`task_inputs`.`task_id` IS NOT NULL"
)
);
for (auto const& iter : new_tasks) {
sql::bytes task_id_bytes = uuid_get_bytes(iter.first);
locality_statement->setBytes(1, &task_id_bytes);
locality_statement->addBatch();
}
locality_statement->execute();
std::unique_ptr<sql::ResultSet> const locality_res(locality_statement->getResultSet());
while (locality_res->next()) {
boost::uuids::uuid const task_id = read_id(locality_res->getBinaryStream("task_id"));
bool const hard_locality = locality_res->getBoolean("hard_locality");
std::string const address = get_sql_string(locality_res->getString("address"));
if (hard_locality) {
new_tasks[task_id].add_hard_locality(address);
} else {
new_tasks[task_id].add_soft_locality(address);
}
}
// Add all tasks to the output
for (auto const& iter : new_tasks) {
tasks->emplace_back(iter.second);
}
} catch (sql::SQLException& e) {
while (task_res->next()) {
boost::uuids::uuid const task_id = read_id(task_res->getBinaryStream("id"));
boost::uuids::uuid const job_id = read_id(task_res->getBinaryStream("job_id"));
std::string const function_name = get_sql_string(task_res->getString("func_name"));
new_tasks.emplace(task_id, ScheduleTaskMetadata{task_id, function_name, job_id});
- if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
- job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
- } else {
- job_id_to_task_ids[job_id].emplace_back(task_id);
- }
+ if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
+ job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
+ }
+ job_id_to_task_ids[job_id].emplace_back(task_id);
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
src/spider/storage/mysql/MySqlStorage.cpp (2)

1261-1265: ⚠️ Potential issue

Fix task ID insertion logic for the first task in a job

There's a bug in the logic for populating job_id_to_task_ids. The first task for each job is added to the vector, but subsequent tasks for the same job may not be correctly added due to the conditional structure.

Apply this fix:

-if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
-    job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{task_id};
-} else {
-    job_id_to_task_ids[job_id].emplace_back(task_id);
-}
+if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
+    job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
+}
+job_id_to_task_ids[job_id].emplace_back(task_id);

1698-1702: ⚠️ Potential issue

Fix the same task ID insertion bug in get_task_timeout

The same bug identified earlier is also present in the get_task_timeout method.

Apply this fix:

-if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
-    job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{task_id};
-} else {
-    job_id_to_task_ids[job_id].emplace_back(task_id);
-}
+if (job_id_to_task_ids.find(job_id) == job_id_to_task_ids.end()) {
+    job_id_to_task_ids[job_id] = std::vector<boost::uuids::uuid>{};
+}
+job_id_to_task_ids[job_id].emplace_back(task_id);
🧹 Nitpick comments (1)
src/spider/storage/mysql/MySqlStorage.cpp (1)

1234-1770: Consider extracting common task metadata fetching logic

Both get_ready_tasks and get_task_timeout methods share significant code duplication in how they fetch and populate task metadata. Consider extracting this common logic into a helper method to improve maintainability.

Example refactoring approach:

// Helper function to populate ScheduleTaskMetadata objects
void fetch_and_populate_task_metadata(
    MySqlConnection& conn,
    absl::flat_hash_set<boost::uuids::uuid>& task_ids,
    std::vector<ScheduleTaskMetadata>* tasks) {
    
    // Get task metadata
    absl::flat_hash_map<boost::uuids::uuid, ScheduleTaskMetadata> new_tasks;
    absl::flat_hash_map<boost::uuids::uuid, std::vector<boost::uuids::uuid>> job_id_to_task_ids;
    
    // Batch fetch task basic info
    // Batch fetch job metadata
    // Batch fetch locality info
    
    // Populate output vector
    for (auto const& iter : new_tasks) {
        tasks->emplace_back(iter.second);
    }
}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cc5b341 and f87d1f9.

📒 Files selected for processing (1)
  • src/spider/storage/mysql/MySqlStorage.cpp (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: lint
  • GitHub Check: non-storage-unit-tests (ubuntu-24.04)
  • GitHub Check: non-storage-unit-tests (ubuntu-22.04)
🔇 Additional comments (7)
src/spider/storage/mysql/MySqlStorage.cpp (7)

17-17: Good inclusion of flat_hash_map for improved performance

The addition of the Abseil flat_hash_map library is a great choice for this optimization task, as it provides better performance characteristics than std::unordered_map for this use case.


1234-1237: Nice API improvement changing return type to ScheduleTaskMetadata

This function signature change aligns well with the PR objectives of encapsulating all necessary task metadata for the scheduler, reducing the need for additional storage accesses.


1244-1247: Optimized SQL query selecting only required fields

Good job on modifying the SQL query to only select the fields needed for constructing the ScheduleTaskMetadata objects (id, func_name, job_id) rather than fetching unnecessary fields.


1254-1255: Effective use of hash maps for batching

Using hash maps to organize the data by task ID and job ID is a good approach for preparing the batch queries to reduce the number of database accesses.


1268-1300: Excellent batch processing for job metadata

Good implementation of batched queries for retrieving job metadata. This reduces the number of database round-trips compared to fetching metadata individually for each task.


1302-1328: Efficient batch processing for data locality

The implementation efficiently retrieves data locality information for multiple tasks in a single batch, which aligns well with the PR objective of reducing the number of queries.


1627-1630: Consistent API improvement for get_task_timeout

Good consistency in applying the same ScheduleTaskMetadata pattern to both get_ready_tasks and get_task_timeout methods.

Comment thread src/spider/storage/mysql/MySqlStorage.cpp Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant