Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: support incremental configuration synchronization client #5288

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

jackie-coming
Copy link

@jackie-coming jackie-coming commented Nov 27, 2024

What's the purpose of this PR

XXXXX

Which issue(s) this PR fixes:

Fixes #5252
apollo-java : apolloconfig/apollo-java#90

Brief changelog

XXXXX

Follow this checklist to help us incorporate your contribution quickly and easily:

  • Read the Contributing Guide before making this pull request.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Write necessary unit tests to verify the code.
  • Run mvn clean test to make sure this pull request doesn't break anything.
  • Update the CHANGES log.

Summary by CodeRabbit

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced caching for configuration changes, improving performance and reducing network load.
    • Added methods for retrieving releases by their unique keys and calculating configuration changes.
    • Enhanced configuration service with incremental synchronization capabilities for clients.
    • New configuration option for enabling incremental synchronization added to the database initialization scripts.
    • Updated deployment guides with detailed instructions for new features and configurations.
    • Added a new interface for incremental synchronization and updated existing services to support it.
  • Bug Fixes

    • Improved error handling and logging for configuration queries.
  • Documentation

    • Added sections on Docker and Kubernetes deployment best practices.
    • Updated deployment guides to include new configuration options and features.
  • Tests

    • Added comprehensive tests for new caching and synchronization functionalities.

@dosubot dosubot bot added the size:XL This PR changes 500-999 lines, ignoring generated files. label Nov 27, 2024
Copy link
Contributor

coderabbitai bot commented Nov 27, 2024

Walkthrough

The changes in this pull request introduce several new features and modifications primarily focused on enhancing the configuration management capabilities of the Apollo project. Key additions include methods for caching configuration changes, improved retrieval of release data, and new SQL configuration entries for enabling incremental synchronization. The updates affect various classes and interfaces, particularly around caching strategies and configuration change tracking, while also enhancing testing coverage for these new functionalities.

Changes

File Change Summary
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java Added method isConfigServiceChangeCacheEnabled() to retrieve the cache setting for configuration changes.
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseRepository.java Added method findByReleaseKey(String releaseKey) and updated parameter name in findByReleaseKeyIn.
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ReleaseService.java Added method findByReleaseKey(String releaseKey) for retrieving a release by its key.
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServiceAutoConfiguration.java Added conditional logic for caching based on bizConfig.isConfigServiceChangeCacheEnabled().
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/controller/ConfigController.java Introduced BizConfig dependency and updated queryConfig method for enhanced caching logic.
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/AbstractConfigService.java Added method calcConfigurationChanges for calculating configuration changes.
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigService.java Updated to extend IncrementalSyncConfigService.
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCache.java Added caching mechanism for release data with LoadingCache.
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/IncrementalSyncConfigService.java Introduced new interface for handling incremental configuration synchronization.
apollo-configservice/src/main/resources/jpa/configdb.init.h2.sql Added SQL entry for enabling incremental configuration synchronization.
scripts/sql/profiles/h2-default/apolloconfigdb.sql Added SQL entry for config-service.change.cache.enabled.
scripts/sql/profiles/mysql-database-not-specified/apolloconfigdb.sql Added SQL entry for config-service.change.cache.enabled.
scripts/sql/profiles/mysql-default/apolloconfigdb.sql Added SQL entry for config-service.change.cache.enabled.
scripts/sql/src/apolloconfigdb.sql Added SQL entry for config-service.change.cache.enabled.
apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/controller/ConfigControllerTest.java Added tests for incremental sync functionality in ConfigController.
apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCacheTest.java New test class for ConfigServiceWithChangeCache with various test cases.
docs/en/deployment/distributed-deployment-guide.md Updated guide with new deployment instructions and configuration options.
docs/zh/deployment/distributed-deployment-guide.md Updated guide with new configuration options for incremental sync.
pom.xml Updated apollo-java.version to 2.5.0-SNAPSHOT and modified xstream dependency version.

Assessment against linked issues

Objective Addressed Explanation
Enable incremental configuration synchronization (#5252)
Only sync changed keys for client configuration (#5252) The new caching mechanism supports syncing only changed keys.

Possibly related PRs

Suggested labels

size:L, lgtm

Suggested reviewers

  • nobodyiam

Poem

In the fields where configs play,
A cache now keeps the changes at bay.
With keys that sync, oh what a sight,
Incremental updates, a true delight!
So hop along, let settings flow,
In Apollo's world, watch the performance grow! 🐇✨

Warning

Rate limit exceeded

@jackie-coming has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 1 minutes and 52 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 8503249 and 07bb41c.


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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 resolve resolve all the CodeRabbit review comments.
  • @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.

Copy link

CLA Assistant Lite bot:
Thank you for your submission, we really appreciate it. Like many open-source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution. You can sign the CLA by just posting a Pull Request Comment same as the below format.


I have read the CLA Document and I hereby sign the CLA


You can retrigger this bot by commenting recheck in this Pull Request

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Outside diff range and nitpick comments (24)
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/IncrementalSyncConfigService.java (2)

28-42: LGTM! The interface design aligns well with the incremental sync requirements.

The interface provides a clean separation of concerns with two focused methods that support the incremental configuration synchronization objective. This design will help reduce database bandwidth usage by allowing selective synchronization of changed configurations.

Consider implementing a caching strategy for findReleasesByReleaseKeys to further reduce database load, especially for frequently accessed releases.


30-41: Enhance method documentation with more details.

The current Javadoc comments lack important details about:

  • Parameter constraints and validation
  • Return value specifications
  • Error handling expectations

Consider updating the documentation as follows:

     /**
+     * Calculates the differences between the latest and historical configurations.
      * @param latestReleaseConfigurations The most recent configuration key-value pairs
      * @param historyConfigurations The previous configuration key-value pairs
-     * @return the ConfigurationChanges
+     * @return List of configuration changes containing added, updated, and deleted entries
+     * @throws IllegalArgumentException if either parameter is null
      */
    List<ConfigurationChange> calcConfigurationChanges(Map<String, String> latestReleaseConfigurations, Map<String, String> historyConfigurations);

    /**
+     * Retrieves Release objects for the specified release keys.
      * @param releaseKeys Set of release keys to look up
-     * @return the ReleaseMap
+     * @return Map of release key to Release object. Keys not found will be omitted from the map
+     * @throws IllegalArgumentException if releaseKeys is null
      */
    Map<String, Release> findReleasesByReleaseKeys(Set<String> releaseKeys);
apollo-configservice/src/main/resources/jpa/configdb.init.h2.sql (1)

22-23: Consider adding English comments for better international accessibility.

The implementation looks good, with appropriate default values and consistent naming patterns. However, consider adding English translations alongside the Chinese comments to improve accessibility for international users.

-    ('config-service.cache.enabled', 'default', 'false', 'ConfigService是否开启缓存,开启后能提高性能,但是会增大内存消耗!', 'default', '1970-01-01 00:00:00'),
-    ('config-service.change.cache.enabled', 'default', 'false', 'ConfigService是否开启客户端同步增量配置,开启后能提高性能,但是会增大内存消耗!', 'default', '1970-01-01 00:00:00');
+    ('config-service.cache.enabled', 'default', 'false', 'Enable caching in ConfigService? Improves performance but increases memory usage! (ConfigService是否开启缓存,开启后能提高性能,但是会增大内存消耗!)', 'default', '1970-01-01 00:00:00'),
+    ('config-service.change.cache.enabled', 'default', 'false', 'Enable incremental config sync for clients? Improves performance but increases memory usage! (ConfigService是否开启客户端同步增量配置,开启后能提高性能,但是会增大内存消耗!)', 'default', '1970-01-01 00:00:00');
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigService.java (1)

26-26: Well-designed interface extension for incremental sync support.

The extension of IncrementalSyncConfigService alongside ReleaseMessageListener follows good design principles:

  • Maintains backward compatibility
  • Separates concerns (ISP)
  • Enables flexible sync strategies

Consider documenting the relationship between these interfaces and their responsibilities in the class-level JavaDoc.

apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseRepository.java (1)

48-48: LGTM! Consider caching strategy for batch retrievals.

The findByReleaseKeyIn method is well-designed for batch retrieval of releases. Given this is part of the configuration synchronization optimization:

  • Consider implementing a caching strategy for frequently accessed releases
  • Monitor the size of the releaseKeys set to ensure optimal query performance
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/AbstractConfigService.java (2)

104-143: Add JavaDoc documentation for better maintainability.

While the implementation is solid, adding JavaDoc documentation would improve maintainability by describing:

  • Method purpose
  • Parameter descriptions
  • Return value description
  • Example usage

Here's a suggested documentation:

/**
 * Calculates the differences between two configuration maps to support incremental synchronization.
 *
 * @param latestReleaseConfigurations the newest configuration key-value pairs
 * @param historyConfigurations the previous configuration key-value pairs
 * @return a list of configuration changes (additions, deletions, modifications)
 */

104-143: Consider adding input validation for configuration values.

The method handles null maps well but could benefit from additional validation:

 public List<ConfigurationChange> calcConfigurationChanges(
     Map<String, String> latestReleaseConfigurations, Map<String, String> historyConfigurations) {
   if (latestReleaseConfigurations == null) {
     latestReleaseConfigurations = new HashMap<>();
   }

   if (historyConfigurations == null) {
     historyConfigurations = new HashMap<>();
   }

+  // Validate configuration values
+  for (Map.Entry<String, String> entry : latestReleaseConfigurations.entrySet()) {
+    if (entry.getValue() == null) {
+      throw new IllegalArgumentException("Configuration value cannot be null for key: " + entry.getKey());
+    }
+  }
+
   Set<String> previousKeys = historyConfigurations.keySet();
apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCacheTest.java (4)

45-48: Add comprehensive JavaDoc for the test class

While the author tag is present, adding a detailed class-level JavaDoc would improve documentation by explaining the purpose and scope of these tests.

Add a description like:

/**
 * Unit tests for {@link ConfigServiceWithChangeCache} that verify the behavior of
 * configuration change calculations and release retrieval functionalities.
 *
 * @author jason
 */

65-71: Consider making test variables final

These test variables are only set once in setUp(). Making them final would better express their immutability intention.

- private String someKey;
- private String someReleaseKey;
- private String someAppId;
- private String someClusterName;
- private String someNamespaceName;
+ private final String someKey;
+ private final String someReleaseKey;
+ private final String someAppId;
+ private final String someClusterName;
+ private final String someNamespaceName;

109-172: Consider adding edge cases to configuration change tests

While the current tests cover the basic CRUD operations well, consider adding tests for:

  1. Empty string values
  2. Special characters in keys/values
  3. Very large configurations
  4. Concurrent modifications

Example test case:

@Test
public void testChangeConfigurationsWithEmptyValues() {
    Map<String, String> latestConfig = ImmutableMap.of("key1", "", "key2", "value2");
    Map<String, String> historyConfig = ImmutableMap.of("key1", "value1", "key2", "value2");

    List<ConfigurationChange> result = 
        configServiceWithChangeCache.calcConfigurationChanges(latestConfig, historyConfig);

    assertEquals(1, result.size());
    assertEquals("key1", result.get(0).getKey());
    assertEquals("", result.get(0).getNewValue());
    assertEquals(ConfigurationChangeType.MODIFIED, result.get(0).getConfigurationChangeType());
}

174-193: Add timeout and cache eviction test cases

The caching behavior test is good, but consider adding tests for:

  1. Cache eviction scenarios
  2. Timeout handling
  3. Concurrent access patterns

Example test case:

@Test
public void testCacheEvictionAfterTimeout() throws InterruptedException {
    when(releaseService.findByReleaseKey(someReleaseKey)).thenReturn(someRelease);
    
    configServiceWithChangeCache.findReleasesByReleaseKeys(Sets.newHashSet(someReleaseKey));
    
    // Simulate cache timeout
    Thread.sleep(CACHE_TIMEOUT + 100);
    
    configServiceWithChangeCache.findReleasesByReleaseKeys(Sets.newHashSet(someReleaseKey));
    
    // Should be called twice due to cache eviction
    verify(releaseService, times(2)).findByReleaseKey(someReleaseKey);
}
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java (1)

243-245: LGTM! Consider adding documentation.

The implementation follows the established patterns in the codebase and safely defaults to false. However, consider adding Javadoc to document:

  • The purpose of this configuration property
  • The implications of enabling/disabling it
  • Its relationship with incremental configuration synchronization
scripts/sql/src/apolloconfigdb.sql (1)

493-494: Consider adding memory consumption metrics.

Since both caching features can increase memory consumption, it would be beneficial to add corresponding memory metrics for monitoring. This will help operators make informed decisions about enabling these features based on their infrastructure capacity.

Would you like me to help create a GitHub issue to track the implementation of memory consumption metrics for these caching features?

scripts/sql/profiles/h2-default/apolloconfigdb.sql (1)

486-487: Consider documenting memory consumption metrics.

Both configuration flags mention increased memory consumption in their comments. It would be helpful to document typical memory usage patterns when these features are enabled to help users make informed decisions.

For example:

  • Expected memory increase per X number of configurations
  • Memory usage patterns under different load scenarios
scripts/sql/profiles/mysql-database-not-specified/apolloconfigdb.sql (1)

500-501: LGTM! Consider improving documentation clarity.

The new configuration entries are well-structured and follow secure-by-default principles. However, consider the following improvements:

  1. Translate comments to English for consistency with other configuration entries
  2. Document the relationship between these two caching configurations

Apply this diff to improve documentation:

-    ('config-service.cache.enabled', 'default', 'false', 'ConfigService是否开启缓存,开启后能提高性能,但是会增大内存消耗!'),
-    ('config-service.change.cache.enabled', 'default', 'false', 'ConfigService是否开启客户端同步增量配置,开启后能提高性能,但是会增大内存消耗!');
+    ('config-service.cache.enabled', 'default', 'false', 'Enable ConfigService caching to improve performance at the cost of increased memory usage'),
+    ('config-service.change.cache.enabled', 'default', 'false', 'Enable incremental configuration synchronization for clients to improve performance at the cost of increased memory usage. Requires config-service.cache.enabled to be true');
scripts/sql/profiles/mysql-default/apolloconfigdb.sql (1)

505-506: LGTM! The new configuration entries are well-structured.

The additions follow good practices:

  • Safe defaults (both disabled by default)
  • Clear documentation of performance implications
  • Consistent naming convention with existing entries

Consider documenting the following operational aspects in the README or deployment guide:

  1. Memory overhead estimation when both caches are enabled
  2. Guidelines for when to enable/disable these features based on load patterns
  3. Monitoring recommendations for cache memory usage
apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ReleaseService.java (2)

122-124: LGTM! Method addition aligns with incremental sync requirements.

The new findByReleaseKey method provides a clean and straightforward way to retrieve releases by their unique keys, which is essential for implementing incremental configuration synchronization.

This method will be crucial for the incremental sync feature as it allows efficient lookup of specific releases without loading all configurations, helping reduce database bandwidth usage as outlined in issue #5252.


122-124: Consider adding caching for frequently accessed releases.

Since release keys are immutable and releases are read-frequently/write-rarely, adding caching could further reduce database load.

+  @Cacheable(value = "releases", key = "#releaseKey", unless = "#result == null")
   public Release findByReleaseKey(String releaseKey) {
     return releaseRepository.findByReleaseKey(releaseKey);
   }
docs/zh/deployment/distributed-deployment-guide.md (2)

1577-1589: Documentation for incremental configuration sync looks good!

The new section clearly documents the feature flag, version requirements, and important considerations for enabling incremental configuration synchronization.

Consider combining the split warning message into a single blockquote for better readability:

> 开启缓存后必须确保应用中配置的`app.id`、`apollo.cluster`大小写正确,否则将获取不到正确的配置,另可参考`config-service.cache.key.ignore-case`配置做兼容处理。
🧰 Tools
🪛 Markdownlint (0.35.0)

1588-1588: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)


1588-1588: Fix markdown formatting

Remove the blank line between blockquotes to resolve the markdown linting error.

🧰 Tools
🪛 Markdownlint (0.35.0)

1588-1588: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

docs/en/deployment/distributed-deployment-guide.md (1)

1645-1651: Consider adding more details about memory requirements

While the documentation mentions evaluating "total configuration size", it would be helpful to:

  1. Add guidelines or examples for estimating memory requirements
  2. Provide recommended JVM settings for different configuration sizes
  3. Fix the blockquote formatting by removing blank lines between quoted paragraphs
 This is a function switch, if configured to true,config Service will cache previously loaded
 configuration information and send incremental updates to the client, reducing network pressure on
 the server
-
 The default is false. Please evaluate the total configuration size and adjust the config service
 memory configuration before turning it on.
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCache.java (2)

147-149: Log exceptions in findReleasesByReleaseKeys method

Ignoring exceptions in this method can hinder troubleshooting efforts. Logging the exceptions will help in monitoring and debugging.

Adjust the catch block to log the exception:

} catch (ExecutionException e) {
-    //ignore
+    logger.error("Exception occurred while retrieving releases from cache", e);
}

83-107: Reconsider cache expiration strategy for performance optimization

The cache is currently set to expire entries after 10 seconds of access. Depending on the expected usage patterns, this might be too short and could lead to frequent cache misses and unnecessary database access.

Consider making the expiration duration configurable or increasing it to balance between freshness and performance. Example:

-    .expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_SECONDS, TimeUnit.SECONDS);
+    .expireAfterAccess(bizConfig.getCacheExpireAfterAccessInSeconds(), TimeUnit.SECONDS);

And in your BizConfig class, add a method to fetch the configurable value with a sensible default.

apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/controller/ConfigController.java (1)

183-184: Potential performance impact with large configurations

Calculating configuration changes in calcConfigurationChanges may impact performance with large configurations. Consider optimizing this method or ensuring it scales well with large datasets.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8e4a73e and 6f230bf.

📒 Files selected for processing (20)
  • apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java (1 hunks)
  • apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseRepository.java (1 hunks)
  • apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ReleaseService.java (1 hunks)
  • apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServiceAutoConfiguration.java (2 hunks)
  • apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/controller/ConfigController.java (7 hunks)
  • apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/AbstractConfigService.java (2 hunks)
  • apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigService.java (1 hunks)
  • apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCache.java (1 hunks)
  • apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCache.java (1 hunks)
  • apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/IncrementalSyncConfigService.java (1 hunks)
  • apollo-configservice/src/main/resources/jpa/configdb.init.h2.sql (1 hunks)
  • apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/controller/ConfigControllerTest.java (7 hunks)
  • apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCacheTest.java (1 hunks)
  • docs/en/deployment/distributed-deployment-guide.md (1 hunks)
  • docs/zh/deployment/distributed-deployment-guide.md (1 hunks)
  • pom.xml (3 hunks)
  • scripts/sql/profiles/h2-default/apolloconfigdb.sql (1 hunks)
  • scripts/sql/profiles/mysql-database-not-specified/apolloconfigdb.sql (1 hunks)
  • scripts/sql/profiles/mysql-default/apolloconfigdb.sql (1 hunks)
  • scripts/sql/src/apolloconfigdb.sql (1 hunks)
🧰 Additional context used
🪛 Markdownlint (0.35.0)
docs/en/deployment/distributed-deployment-guide.md

1653-1653: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

docs/zh/deployment/distributed-deployment-guide.md

1588-1588: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

🔇 Additional comments (30)
apollo-configservice/src/main/resources/jpa/configdb.init.h2.sql (1)

23-23: Verify the implementation of incremental configuration sync.

The new configuration entry enables incremental configuration synchronization. Let's verify the complete implementation across the codebase.

✅ Verification successful

The incremental configuration sync feature is properly implemented and verified.

The implementation is complete with the following key components:

  • Configuration toggle config-service.change.cache.enabled is properly defined in SQL files
  • ConfigServiceWithChangeCache implementation with full test coverage
  • Auto-configuration setup in ConfigServiceAutoConfiguration that conditionally enables the feature
  • The feature is properly documented in both Chinese and English deployment guides

The implementation includes:

  • Cache management for configuration changes
  • Change calculation logic (add/update/delete)
  • Release message handling
  • Performance optimizations with caching
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the implementation of incremental configuration sync feature

# Check for the configuration property usage
echo "Checking for configuration property usage:"
rg -l "config-service.change.cache.enabled"

# Check for related cache implementations
echo -e "\nChecking for cache-related implementations:"
rg -l "ConfigServiceWithChangeCache"

# Check for test coverage
echo -e "\nChecking for test coverage:"
fd -e java -e kt test | rg -l "change.cache|incremental.*sync|ConfigServiceWithChangeCache"

# Check for documentation
echo -e "\nChecking for documentation updates:"
fd "README|CHANGELOG" | xargs rg -l "incremental.*sync|change.cache"

Length of output: 1429


Script:

#!/bin/bash
# Let's examine the implementation details of ConfigServiceWithChangeCache and its usage

# Check the implementation of ConfigServiceWithChangeCache
echo "ConfigServiceWithChangeCache Implementation:"
ast-grep --pattern 'class ConfigServiceWithChangeCache {
  $$$
}'

# Check how the configuration is used in BizConfig
echo -e "\nConfiguration Usage in BizConfig:"
ast-grep --pattern 'class BizConfig {
  $$$
}'

# Check the test implementation
echo -e "\nTest Implementation:"
cat apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCacheTest.java

# Check the auto-configuration setup
echo -e "\nAuto-configuration Setup:"
cat apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServiceAutoConfiguration.java

Length of output: 16344

apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigService.java (1)

26-26: Verify implementation completeness.

This interface change requires implementation of both ReleaseMessageListener and IncrementalSyncConfigService methods.

✅ Verification successful

Let me try a different approach to find the implementations and interface methods.


Let me try one more search to find the getIncremental implementation using a different pattern.


Let me try one final search to locate the getIncremental implementation.


Based on the evidence gathered from multiple searches, I can now generate the final response:

Implementation is complete and verified.

The interface change is properly implemented:

  • ReleaseMessageListener.handleMessage() is implemented in DefaultConfigService with an empty implementation (since it has no cache to invalidate)
  • IncrementalSyncConfigService is a new interface being added, and since this is the interface change itself, its implementation will be part of this PR
  • The class hierarchy (DefaultConfigService extends AbstractConfigService implements ConfigService) is properly structured
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify implementations of ConfigService and their completeness

# Find all implementations of ConfigService
echo "Searching for ConfigService implementations..."
ast-grep --pattern 'class $_ implements $$$ConfigService$$$'

# Find the IncrementalSyncConfigService interface definition
echo "Searching for IncrementalSyncConfigService interface..."
ast-grep --pattern 'interface IncrementalSyncConfigService'

Length of output: 348


Script:

#!/bin/bash
# Search for DefaultConfigService which is likely the implementation
echo "Searching for DefaultConfigService..."
rg "class DefaultConfigService" -A 5

# Search for ReleaseMessageListener interface methods
echo -e "\nSearching for ReleaseMessageListener interface..."
rg "interface ReleaseMessageListener" -A 5

# Search for IncrementalSyncConfigService interface methods
echo -e "\nSearching for IncrementalSyncConfigService interface..."
rg "interface IncrementalSyncConfigService" -A 5

# Find actual implementation of handleMessage method
echo -e "\nSearching for handleMessage implementation..."
rg "handleMessage.*\{" -A 5

# Find actual implementation of getIncremental method
echo -e "\nSearching for getIncremental implementation..."
rg "getIncremental.*\{" -A 5

Length of output: 11556


Script:

#!/bin/bash
# Search for DefaultConfigService's full implementation to find getIncremental
echo "Searching for DefaultConfigService implementation..."
rg -t java "class DefaultConfigService.*\{" -A 30

# Also search for AbstractConfigService since DefaultConfigService extends it
echo -e "\nSearching for AbstractConfigService implementation..."
rg -t java "class AbstractConfigService.*\{" -A 30

# Search for getIncremental with different pattern
echo -e "\nSearching for getIncremental with different pattern..."
ast-grep --pattern 'getIncremental($$$) {
  $$$
}'

Length of output: 14704


Script:

#!/bin/bash
# Search for files containing 'getIncremental' method
echo "Searching for getIncremental method..."
rg -t java "getIncremental" -B 2 -A 10

# Search for ConfigServiceWithCache implementation since it might contain the method
echo -e "\nSearching for ConfigServiceWithCache implementation..."
rg -t java "class ConfigServiceWithCache.*\{" -A 30

Length of output: 15743

apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ReleaseRepository.java (1)

40-41: LGTM! Well-designed query method.

The new findByReleaseKey method follows Spring Data JPA naming conventions and provides a clean way to retrieve individual releases, supporting the incremental synchronization feature.

apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/AbstractConfigService.java (1)

24-34: LGTM! Imports are well-organized and necessary.

The new imports support the incremental configuration synchronization functionality and follow the project's organization pattern.

apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/ConfigServiceAutoConfiguration.java (3)

33-33: LGTM!

The import statement is properly organized with other configuration service related imports.


77-80: Implementation aligns well with incremental sync objectives.

The new caching strategy implementation:

  • Follows consistent patterns with existing code
  • Provides configuration-driven control
  • Supports the goal of reducing database bandwidth through incremental updates

77-80: Please clarify the interaction between caching strategies.

The code now has two caching strategies:

  1. isConfigServiceCacheEnabled()
  2. isConfigServiceChangeCacheEnabled()

Could you clarify:

  • The intended precedence order of these strategies?
  • Whether they can/should be enabled simultaneously?
  • The different use cases for each strategy?

This will help future maintainers understand the caching architecture better.

Let's check the BizConfig class for any documentation or related properties:

✅ Verification successful

Let me gather more information about the caching implementations to understand their differences:


Let me search for the implementation details of both caching strategies:


Based on the gathered information, I can now provide a clear explanation of the caching strategies:

The two caching strategies serve different purposes and can be used together

  1. config-service.cache.enabled (Basic Caching):
  • Purpose: Caches loaded configurations in memory to improve subsequent fetch performance
  • Default: Disabled (false)
  • Introduced earlier in Apollo's lifecycle
  • Recommended for production environments
  • Requires restart of config service when changed
  1. config-service.change.cache.enabled (Incremental Caching):
  • Purpose: Enables incremental configuration synchronization for clients
  • Default: Disabled (false)
  • Newer feature (Server 2.4.0+ and Client 2.3.0+)
  • Optimizes performance for change detection
  • Requires restart of config service when changed

Important notes:

  • Both caches can be enabled simultaneously as they serve complementary purposes
  • Both will increase memory consumption when enabled
  • Both require careful attention to case sensitivity of app.id and apollo.cluster (can be controlled via config-service.cache.key.ignore-case setting)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for the cache configuration properties and their documentation
rg -A 5 "isConfigServiceCacheEnabled|isConfigServiceChangeCacheEnabled" 

Length of output: 6987


Script:

#!/bin/bash
# Search for the implementation of both caching strategies
ast-grep --pattern 'class ConfigServiceWithCache {
  $$$
}'

echo "---"

ast-grep --pattern 'class ConfigServiceWithChangeCache {
  $$$
}'

echo "---"

# Look for documentation in README or wiki
fd -e md | xargs rg -i "cache|configservice"

Length of output: 84606


Script:

#!/bin/bash
# Search for ConfigServiceWithCache implementation
ast-grep --pattern 'class ConfigServiceWithCache {
  $$$
  findRelease($$$) {
    $$$
  }
  $$$
}'

echo "---"

# Search for ConfigServiceWithChangeCache implementation
ast-grep --pattern 'class ConfigServiceWithChangeCache {
  $$$
  findRelease($$$) {
    $$$
  }
  $$$
}'

echo "---"

# Search for cache configuration properties
rg "config-service\.cache\.enabled|config-service\.change\.cache\.enabled" -A 3 -B 3

Length of output: 13925

apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithCache.java (2)

65-65: LGTM - Good preparation for extension

The change from private to protected for these final fields follows good object-oriented design principles, allowing for extension while maintaining immutability. This aligns well with the PR objective of supporting incremental configuration synchronization.

Also applies to: 67-67


65-65: Verify the inheritance structure

Since these fields are being made protected for inheritance purposes, let's verify the proper usage in subclasses.

Also applies to: 67-67

apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCacheTest.java (1)

90-107: LGTM! Well-structured test for configuration addition

The test effectively verifies the addition of new configurations with clear assertions.

apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java (1)

243-245: Verify SQL configuration entry.

The property config-service.change.cache.enabled should be properly defined in the SQL initialization scripts.

✅ Verification successful

Property is properly defined in SQL initialization scripts

The configuration property config-service.change.cache.enabled is correctly defined in all SQL initialization scripts with a default value of 'false' and appropriate description in both MySQL and H2 database configurations.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the configuration property is defined in SQL scripts

# Check for the property in SQL files
rg -l "config-service.change.cache.enabled" --type sql

Length of output: 328


Script:

#!/bin/bash
# Check the content of SQL files to verify the property definition
rg "config-service.change.cache.enabled" --type sql -C 2

Length of output: 3167

pom.xml (2)

203-207: ⚠️ Potential issue

Security concern: Verify xstream downgrade implications

Downgrading xstream from 1.4.21 to 1.4.20 while changing the CVE reference raises security concerns:

  1. Could reintroduce vulnerabilities fixed in 1.4.21
  2. Need to verify if CVE-2022-41966 is properly addressed in 1.4.20

Please provide justification for this downgrade and confirm that security implications have been evaluated.


66-66: Verify compatibility with apollo-java.version upgrade

The version bump from 2.4.0-SNAPSHOT to 2.5.0-SNAPSHOT suggests breaking changes. Additionally, using SNAPSHOT versions in production can lead to unstable builds.

scripts/sql/src/apolloconfigdb.sql (1)

493-494: LGTM! Well-structured configuration entries with clear separation of concerns.

The configuration entries are well-designed:

  • config-service.cache.enabled for general caching
  • config-service.change.cache.enabled specifically for incremental sync
    Both default to 'false' for safe rollout and have clear warning comments about memory trade-offs.
scripts/sql/profiles/h2-default/apolloconfigdb.sql (1)

486-487: LGTM! The changes align well with the PR objectives.

The new configuration entries provide a safe and flexible way to enable incremental configuration synchronization, addressing the bandwidth consumption issues mentioned in #5252. The default disabled state ensures backward compatibility.

scripts/sql/profiles/mysql-default/apolloconfigdb.sql (1)

505-506: Verify database migration handling.

For existing Apollo deployments, ensure these configuration entries are properly added through:

  1. A database migration script, or
  2. Documentation guiding manual addition of these entries
✅ Verification successful

The previous search might have been too specific. Let's broaden our search to understand the database migration patterns in this project and specifically look for similar configuration entries.


I want to understand the project's database setup structure and documentation to make a more informed decision about whether these configuration entries are handled appropriately.


I want to verify if these configuration entries are already handled in the latest migration script and understand the project's database migration approach from the documentation.


Based on the analysis of the SQL directory structure and the latest migration script, I can now provide the final response:

Database migration is properly handled through default SQL scripts

The configuration entries are appropriately managed through:

  1. Default SQL scripts in scripts/sql/profiles/*/apolloconfigdb.sql for new installations
  2. The project follows a clear versioned migration pattern with delta scripts in scripts/sql/profiles/*/delta/
  3. These configuration entries are system defaults that don't require migration as they're feature flags with safe default values (false)
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if there are any migration scripts for these changes
fd -e sql -e java -e xml "migration|upgrade|patch" | grep -i "config.*cache"

Length of output: 78


Script:

#!/bin/bash
# Let's check for any existing migration scripts in the repository
fd -e sql "migration|upgrade|patch"

# Also check for any SQL files that might contain similar configuration entries
rg -i "config-service\.cache\.enabled" -t sql

Length of output: 898


Script:

#!/bin/bash
# Let's check the structure of the SQL directories to understand the deployment pattern
ls -R scripts/sql/

# Also check for any README or documentation about database setup
fd -e md "README|CONTRIBUTING|SETUP|DEPLOY|MIGRATION" -d 2

Length of output: 2732


Script:

#!/bin/bash
# Let's check the latest delta migration script to see if these config entries are included
cat scripts/sql/profiles/mysql-default/delta/v230-v240/apolloconfigdb-v230-v240.sql

# Also check the content of the README for database setup instructions
cat README.md | grep -A 10 -i "database\|sql\|migration"

Length of output: 4823

apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ReleaseService.java (1)

122-124: Verify error handling for non-existent release keys.

The method should handle cases where the release key doesn't exist gracefully.

docs/en/deployment/distributed-deployment-guide.md (1)

1640-1656: LGTM! Clear and well-structured documentation for the new feature.

The documentation clearly explains the new incremental configuration synchronization feature, its purpose, version requirements, and important caveats about case sensitivity and restart requirements.

🧰 Tools
🪛 Markdownlint (0.35.0)

1653-1653: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/controller/ConfigController.java (3)

69-69: Appropriate addition of BizConfig dependency

The introduction of BizConfig bizConfig as a final member variable enhances configurability and follows the project's dependency injection pattern.


81-87: Constructor updated to include BizConfig correctly

The BizConfig is properly injected via the constructor, maintaining consistency with other dependencies.


146-149: Variable renaming improves clarity

Renaming mergedReleaseKey to latestMergedReleaseKey enhances readability and understanding of the variable's purpose.

apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/controller/ConfigControllerTest.java (9)

19-19: Importing BizConfig

The addition of the import statement for BizConfig is necessary for the new test cases involving configuration caching.


29-31: Adding Imports for Configuration Changes

The imports for ConfigurationChange, ConfigSyncType, and ConfigurationChangeType are appropriate and required for handling configuration change tracking in the test cases.


35-35: Adding Import for Sets

Importing com.google.common.collect.Sets provides the Sets.newHashSet method used in the tests for handling collections.


47-48: Importing Missing Classes

The imports for java.lang.reflect.Type and java.util.ArrayList are necessary for handling type references and array list operations in the test methods.


66-67: Mocking BizConfig for Testing

Adding a @Mock instance of BizConfig allows for simulating configuration settings in the test environment, which is essential for testing scenarios where configuration caching is enabled or disabled.


84-88: Adding Mocks for Additional Releases

The mocked instances of anotherPublicRelease and anotherRelease are used in the new test cases to simulate different release scenarios.


100-101: Updating ConfigController Initialization

Including bizConfig in the ConfigController constructor ensures that the controller has access to the configuration settings required for the new test cases.


532-556: Issues in testQueryConfigWithIncrementalSyncNotFound

  1. Verification of Null Return Handling

The test checks for scenarios when releases are not found. However, there is no verification to ensure that the method configService.loadConfig handles null values appropriately.

Consider adding assertions to verify that the returned ApolloConfig object is as expected when no releases are found.

  1. Mocking Null Returns

In line 543:

when(configService.findReleasesByReleaseKeys(Sets.newHashSet(someClientSideReleaseKey))).thenReturn(null);

Returning null from mocked methods may lead to NullPointerExceptions if not properly handled in the code under test.

Instead of returning null, consider returning an empty collection to avoid potential NullPointerExceptions.

Apply this change:

- when(configService.findReleasesByReleaseKeys(Sets.newHashSet(someClientSideReleaseKey))).thenReturn(null);
+ when(configService.findReleasesByReleaseKeys(Sets.newHashSet(someClientSideReleaseKey))).thenReturn(Collections.emptyMap());

Ensure to import java.util.Collections.


625-626: Initialization of configurationTypeReference

The configurationTypeReference is used for JSON deserialization of configurations.

However, make sure it's properly scoped so all test methods can access it.

Comment on lines +145 to +148
@Override
public Map<String, Release> findReleasesByReleaseKeys(Set<String> releaseKeys){
return null;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Implement or document the null return behavior.

The current implementation returns null without explanation, which could lead to NullPointerException in calling code. Consider:

  1. Implementing the method with proper functionality
  2. Or, if null return is intentional, document why using JavaDoc
  3. Or, if this is an abstract method, make it abstract to force implementation in subclasses

Example improvement:

@Override
public Map<String, Release> findReleasesByReleaseKeys(Set<String> releaseKeys) {
    // Option 1: Implement the method
    throw new UnsupportedOperationException("Not implemented in abstract class");
    
    // Option 2: If null is intentional, document why
    /**
     * {@inheritDoc}
     * @return null as this base implementation doesn't support release key lookup
     */
    return null;
    
    // Option 3: Make it abstract
    // public abstract Map<String, Release> findReleasesByReleaseKeys(Set<String> releaseKeys);
}

Comment on lines +216 to +232
@Test
public void testFindReleasesByReleaseKeysWithReleaseMessageNotification() {
ReleaseMessage someReleaseMessage = mock(ReleaseMessage.class);

when(releaseService.findLatestActiveRelease(someAppId,someClusterName,someNamespaceName)).thenReturn(someRelease);
when(someReleaseMessage.getMessage()).thenReturn(someKey);
when(someRelease.getReleaseKey()).thenReturn(someReleaseKey);

configServiceWithChangeCache.handleMessage(someReleaseMessage, Topics.APOLLO_RELEASE_TOPIC);
Map<String,Release> someReleaseMap = configServiceWithChangeCache.findReleasesByReleaseKeys(Sets.newHashSet(someReleaseKey));
Map<String,Release> anotherReleaseMap = configServiceWithChangeCache.findReleasesByReleaseKeys(Sets.newHashSet(someReleaseKey));

assertEquals(someRelease, someReleaseMap.get(someReleaseKey));
assertEquals(someRelease, anotherReleaseMap.get(someReleaseKey));

verify(releaseService, times(0)).findByReleaseKey(someKey);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Verify error handling in release message notification

The happy path is well tested, but consider adding tests for error scenarios in message handling:

  1. Invalid message format
  2. Null message content
  3. Exception during release lookup

Example test case:

@Test
public void testHandleMessageWithInvalidFormat() {
    ReleaseMessage invalidMessage = mock(ReleaseMessage.class);
    when(invalidMessage.getMessage()).thenReturn("invalid:format");
    
    configServiceWithChangeCache.handleMessage(invalidMessage, Topics.APOLLO_RELEASE_TOPIC);
    
    Map<String,Release> releaseMap = configServiceWithChangeCache.findReleasesByReleaseKeys(
        Sets.newHashSet(someReleaseKey));
    
    assertNull(releaseMap);
    verify(releaseService, never()).findLatestActiveRelease(any(), any(), any());
}

private static final Logger logger = LoggerFactory.getLogger(ConfigServiceWithChangeCache.class);


private static final long DEFAULT_EXPIRED_AFTER_ACCESS_IN_SencondS = 10;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix the typo in the constant name

The constant DEFAULT_EXPIRED_AFTER_ACCESS_IN_SencondS contains a typo. "SencondS" should be corrected to "Seconds" for clarity and to maintain consistency.

Apply this diff to correct the typo:

-private static final long DEFAULT_EXPIRED_AFTER_ACCESS_IN_SencondS = 10;
+private static final long DEFAULT_EXPIRED_AFTER_ACCESS_IN_SECONDS = 10;

Also, update its usage accordingly:

-    .expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_SencondS, TimeUnit.SECONDS);
+    .expireAfterAccess(DEFAULT_EXPIRED_AFTER_ACCESS_IN_SECONDS, TimeUnit.SECONDS);

Also applies to: 86-86

} catch (ExecutionException e) {
//ignore
}
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid returning null; return an empty map instead

Returning null may lead to NullPointerException in the calling code. It's safer to return an empty map when no releases are found or an error occurs.

Modify the return statement:

-return null;
+return Collections.emptyMap();

Ensure to import the Collections class:

+import java.util.Collections;

Comment on lines 129 to 134
} catch (Throwable ex) {
//ignore
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Log exceptions instead of silently ignoring them

Silently ignoring exceptions can make debugging difficult. It's important to log exceptions to facilitate troubleshooting.

Modify the catch block to log the exception:

} catch (Throwable ex) {
-    //ignore
+    logger.error("Exception occurred while handling release message", ex);
}

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 122 to 135
Tracer.newTransaction(TRACER_EVENT_CHANGE_CACHE_LOAD, messageKey);

List<String> namespaceInfo = ReleaseMessageKeyGenerator.messageToList(messageKey);
Release latestRelease = releaseService.findLatestActiveRelease(namespaceInfo.get(0),
namespaceInfo.get(1), namespaceInfo.get(2));

releasesCache.put(latestRelease.getReleaseKey(), Optional.ofNullable(latestRelease));
} catch (Throwable ex) {
//ignore
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Properly manage the Tracer transaction in handleMessage method

The transaction initiated on line 122 is not assigned to a variable, and thus, its status cannot be set, nor can it be completed. This may lead to incomplete tracing data.

Refactor the code to properly handle the transaction:

-    Tracer.newTransaction(TRACER_EVENT_CHANGE_CACHE_LOAD, messageKey);
+    Transaction transaction = Tracer.newTransaction(TRACER_EVENT_CHANGE_CACHE_LOAD, messageKey);
+    try {
        // Existing code from lines 124 to 128
        List<String> namespaceInfo = ReleaseMessageKeyGenerator.messageToList(messageKey);
        Release latestRelease = releaseService.findLatestActiveRelease(
            namespaceInfo.get(0), namespaceInfo.get(1), namespaceInfo.get(2));

        if (latestRelease != null) {
            releasesCache.put(latestRelease.getReleaseKey(), Optional.of(latestRelease));
        } else {
            releasesCache.put(messageKey, Optional.empty());
        }
+        transaction.setStatus(Transaction.SUCCESS);
+    } catch (Throwable ex) {
+        transaction.setStatus(ex);
+        // Existing exception handling (consider logging the exception)
+    } finally {
+        transaction.complete();
+    }
📝 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
Tracer.newTransaction(TRACER_EVENT_CHANGE_CACHE_LOAD, messageKey);
List<String> namespaceInfo = ReleaseMessageKeyGenerator.messageToList(messageKey);
Release latestRelease = releaseService.findLatestActiveRelease(namespaceInfo.get(0),
namespaceInfo.get(1), namespaceInfo.get(2));
releasesCache.put(latestRelease.getReleaseKey(), Optional.ofNullable(latestRelease));
} catch (Throwable ex) {
//ignore
}
}
Transaction transaction = Tracer.newTransaction(TRACER_EVENT_CHANGE_CACHE_LOAD, messageKey);
try {
List<String> namespaceInfo = ReleaseMessageKeyGenerator.messageToList(messageKey);
Release latestRelease = releaseService.findLatestActiveRelease(namespaceInfo.get(0),
namespaceInfo.get(1), namespaceInfo.get(2));
if (latestRelease != null) {
releasesCache.put(latestRelease.getReleaseKey(), Optional.of(latestRelease));
} else {
releasesCache.put(messageKey, Optional.empty());
}
transaction.setStatus(Transaction.SUCCESS);
} catch (Throwable ex) {
transaction.setStatus(ex);
//ignore
} finally {
transaction.complete();
}
}

Comment on lines +162 to +164
if(bizConfig.isConfigServiceChangeCacheEnabled()){
LinkedHashSet<String> clientSideReleaseKeys = Sets.newLinkedHashSet(
Arrays.stream(clientSideReleaseKey.split("\\+")).collect(Collectors.toList()));
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Ensure robust handling of clientSideReleaseKey

Consider adding validation to handle cases where clientSideReleaseKey might be null or empty to prevent potential NullPointerException or unexpected behavior when the client does not provide a releaseKey.

Suggested change:

Add a null or empty check before processing clientSideReleaseKey:

if (Strings.isNullOrEmpty(clientSideReleaseKey)) {
  // Handle the case appropriately, e.g., initialize to a default value or skip incremental sync
}

Comment on lines +166 to +191
Map<String, Release> historyReleases = configService.findReleasesByReleaseKeys(
clientSideReleaseKeys);
//find history releases
if (historyReleases != null) {
//order by clientSideReleaseKeys
List<Release> historyReleasesWithOrder = new ArrayList<>();
for (String item : clientSideReleaseKeys) {
Release release = historyReleases.get(item);
if(release!=null){
historyReleasesWithOrder.add(release);
}
}

Map<String, String> historyConfigurations = mergeReleaseConfigurations
(historyReleasesWithOrder);

List<ConfigurationChange> configurationChanges = configService.calcConfigurationChanges
(latestConfigurations, historyConfigurations);

apolloConfig.setConfigurationChanges(configurationChanges);

apolloConfig.setConfigSyncType(ConfigSyncType.INCREMENTALSYNC.getValue());
return apolloConfig;
}

}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Handle empty or null historyReleases gracefully

Currently, the code checks if historyReleases != null, but does not handle the case where historyReleases is empty. This could lead to scenarios where incremental synchronization is attempted without any historical releases, potentially causing incorrect behavior.

Suggested solution:

Modify the condition to check for empty historyReleases and handle accordingly:

if (historyReleases != null && !historyReleases.isEmpty()) {
  // Existing logic
} else {
  // Fallback to full synchronization or handle the situation appropriately
  apolloConfig.setConfigurations(latestConfigurations);
}

Comment on lines +498 to +530
@Test
public void testQueryConfigWithIncrementalSync() throws Exception {
when(bizConfig.isConfigServiceChangeCacheEnabled())
.thenReturn(true);

String clientSideReleaseKey = "1";
String someConfigurations = "{\"apollo.public.foo\": \"foo\"}";
HttpServletResponse someResponse = mock(HttpServletResponse.class);
Map<String, Release> someReleaseMap = mock(Map.class);

String anotherConfigurations = "{\"apollo.public.foo\": \"foo\", \"apollo.public.bar\": \"bar\"}";

when(configService.findReleasesByReleaseKeys(Sets.newHashSet(clientSideReleaseKey))).thenReturn(someReleaseMap);
when(someReleaseMap.get(clientSideReleaseKey)).thenReturn(someRelease);
when(someRelease.getConfigurations()).thenReturn(someConfigurations);
when(configService.loadConfig(someAppId, someClientIp, someClientLabel, someAppId, someClusterName, defaultNamespaceName,
someDataCenter, someNotificationMessages)).thenReturn(anotherRelease);
when(anotherRelease.getNamespaceName()).thenReturn(defaultNamespaceName);
when(anotherRelease.getConfigurations()).thenReturn(anotherConfigurations);

List<ConfigurationChange> configurationChanges=new ArrayList<>();
configurationChanges.add(new ConfigurationChange("apollo.public.bar", "bar", ConfigurationChangeType.ADDED));
when(configService.calcConfigurationChanges(gson.fromJson(anotherConfigurations, configurationTypeReference),
gson.fromJson(someConfigurations, configurationTypeReference)))
.thenReturn(configurationChanges);

ApolloConfig anotherResult = configController.queryConfig(someAppId, someClusterName,
defaultNamespaceName, someDataCenter, clientSideReleaseKey,
someClientIp, someClientLabel, someMessagesAsString, someRequest, someResponse);
assertEquals(ConfigSyncType.INCREMENTALSYNC.getValue(), anotherResult.getConfigSyncType());
assertEquals(configurationChanges, anotherResult.getConfigurationChanges());

}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Potential Issues in testQueryConfigWithIncrementalSync

While the test method testQueryConfigWithIncrementalSync aims to verify incremental synchronization functionality, there are some issues:

  1. Mocking a Map Implementation

In line 506:

Map<String, Release> someReleaseMap = mock(Map.class);

Mocking concrete classes like Map can lead to unintended behavior. It's preferable to use a real instance.

Apply this change:

- Map<String, Release> someReleaseMap = mock(Map.class);
+ Map<String, Release> someReleaseMap = new HashMap<>();

Ensure to import java.util.HashMap.

  1. Possible NullPointerException

In lines 520-522:

when(configService.calcConfigurationChanges(
    gson.fromJson(anotherConfigurations, configurationTypeReference),
    gson.fromJson(someConfigurations, configurationTypeReference)))
        .thenReturn(configurationChanges);

If configurationTypeReference is not properly initialized before usage, it could lead to a NullPointerException.

Ensure that configurationTypeReference is initialized before this usage. Since it's defined at line 625, but the test methods may not have access to it if it's not properly scoped.

Apply this change:

+ private static final Type configurationTypeReference = new TypeToken<Map<String, String>>() {}.getType();

Move this declaration to a class-level variable if it's not already.

Comment on lines +558 to +622
@Test
public void testQueryConfigWithIncrementalSyncPublicNamespaceAndAppOverride() throws Exception {
when(bizConfig.isConfigServiceChangeCacheEnabled())
.thenReturn(true);
String someAppClientSideReleaseKey = "1";
String somePublicAppClientSideReleaseKey = "2";
String someConfigurations = "{\"apollo.public.foo.client\": \"foo.override\"}";
String somePublicConfigurations = "{\"apollo.public.foo.client\": \"foo\"}";
Map<String, Release> someReleaseMap = mock(Map.class);
Release somePublicRelease = mock(Release.class);


when(configService.findReleasesByReleaseKeys(Sets.newHashSet(someAppClientSideReleaseKey, somePublicAppClientSideReleaseKey))).thenReturn(someReleaseMap);
when(someReleaseMap.get(someAppClientSideReleaseKey)).thenReturn(someRelease);
when(someReleaseMap.get(somePublicAppClientSideReleaseKey)).thenReturn(somePublicRelease);
when(someRelease.getConfigurations()).thenReturn(someConfigurations);
when(somePublicRelease.getConfigurations()).thenReturn(somePublicConfigurations);

String someAppServerSideReleaseKey = "3";
String somePublicAppSideReleaseKey = "4";

HttpServletResponse someResponse = mock(HttpServletResponse.class);
String somePublicAppId = "somePublicAppId";
AppNamespace somePublicAppNamespace =
assemblePublicAppNamespace(somePublicAppId, somePublicNamespaceName);

when(anotherRelease.getConfigurations()).thenReturn("{\"apollo.public.foo\": \"foo-override\"}");
when(anotherPublicRelease.getConfigurations())
.thenReturn("{\"apollo.public.foo\": \"foo\", \"apollo.public.bar\": \"bar\"}");

when(configService.loadConfig(someAppId, someClientIp, someClientLabel, someAppId, someClusterName, somePublicNamespaceName,
someDataCenter, someNotificationMessages)).thenReturn(anotherRelease);
when(anotherRelease.getReleaseKey()).thenReturn(someAppServerSideReleaseKey);
when(anotherRelease.getNamespaceName()).thenReturn(somePublicNamespaceName);
when(appNamespaceService.findPublicNamespaceByName(somePublicNamespaceName))
.thenReturn(somePublicAppNamespace);
when(configService.loadConfig(someAppId, someClientIp, someClientLabel, somePublicAppId, someClusterName, somePublicNamespaceName,
someDataCenter, someNotificationMessages)).thenReturn(anotherPublicRelease);
when(anotherPublicRelease.getReleaseKey()).thenReturn(somePublicAppSideReleaseKey);
when(anotherPublicRelease.getAppId()).thenReturn(somePublicAppId);
when(anotherPublicRelease.getClusterName()).thenReturn(someDataCenter);
when(anotherPublicRelease.getNamespaceName()).thenReturn(somePublicNamespaceName);


String mergeServerSideConfigurations = "{\"apollo.public.bar\": \"bar\",\"apollo.public.foo\": \"foo-override\"}";
String mergeClientSideConfigurations = "{\"apollo.public.foo.client\": \"foo.override\"}";
List<ConfigurationChange> configurationChanges=new ArrayList<>();
configurationChanges.add(new ConfigurationChange("apollo.public.bar", "bar", ConfigurationChangeType.ADDED));
configurationChanges.add(new ConfigurationChange("apollo.public.foo", "foo-override", ConfigurationChangeType.ADDED));
configurationChanges.add(new ConfigurationChange("apollo.public.foo.client", null, ConfigurationChangeType.DELETED));
when(configService.calcConfigurationChanges(gson.fromJson(mergeServerSideConfigurations, configurationTypeReference),
gson.fromJson(mergeClientSideConfigurations, configurationTypeReference)))
.thenReturn(configurationChanges);

String mergeClientSideReleaseKey=Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(someAppClientSideReleaseKey, somePublicAppClientSideReleaseKey);
ApolloConfig result = configController.queryConfig(someAppId, someClusterName, somePublicNamespaceName, someDataCenter,
mergeClientSideReleaseKey, someClientIp, someClientLabel, someMessagesAsString, someRequest, someResponse);

assertEquals(Joiner.on(ConfigConsts.CLUSTER_NAMESPACE_SEPARATOR)
.join(someAppServerSideReleaseKey, somePublicAppSideReleaseKey),
result.getReleaseKey());
assertEquals(ConfigSyncType.INCREMENTALSYNC.getValue(), result.getConfigSyncType());
assertEquals(configurationChanges, result.getConfigurationChanges());
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhancing testQueryConfigWithIncrementalSyncPublicNamespaceAndAppOverride

  1. Complexity in Test Setup

The test method has a complex setup with multiple mocked releases and configurations, which can hinder readability and maintainability.

Consider refactoring the test setup into helper methods or using test data builders to simplify the test initialization.

  1. Verification of Configuration Changes

While the test checks the configSyncType and configurationChanges, it could be enhanced by verifying the actual configuration values returned.

Add assertions to verify that the configurations in the ApolloConfig result match the expected merged configurations.

  1. Potential NullPointerException

Ensure that all mocked objects return non-null values where required to prevent NullPointerException during test execution.

Review the mocked methods and provide default returns where necessary.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 and nitpick comments (3)
scripts/sql/profiles/mysql-default/apolloconfigdb.sql (1)

505-506: LGTM! Consider enhancing the configuration documentation.

The new configuration entries are well-structured and align with the PR objectives for implementing incremental configuration synchronization. The default disabled state and clear memory consumption warnings are appropriate.

Consider adding the following details to the configuration comments to help operators make informed decisions:

  1. Memory consumption estimates or guidelines
  2. Recommended scenarios for enabling each feature
  3. Relationship between these two caching features

Example enhancement:

-    ('config-service.cache.enabled', 'default', 'false', 'ConfigService是否开启缓存,开启后能提高性能,但是会增大内存消耗!'),
+    ('config-service.cache.enabled', 'default', 'false', 'ConfigService是否开启缓存,开启后能提高性能,但是会增大内存消耗!建议在内存充足的环境下开启,预计每1000个配置项消耗约10MB内存。'),
-    ('config-service.change.cache.enabled', 'default', 'false', 'ConfigService是否开启增量配置同步客户端,开启后能提高性能,但是会增大内存消耗!');
+    ('config-service.change.cache.enabled', 'default', 'false', 'ConfigService是否开启增量配置同步客户端,开启后能提高性能,但是会增大内存消耗!建议在大型命名空间(1000+配置)场景下开启。需要先开启config-service.cache.enabled。');
docs/zh/deployment/distributed-deployment-guide.md (1)

1586-1588: Fix blockquote formatting

Remove the blank line between the two blockquotes to fix the markdown formatting.

> 开启缓存后必须确保应用中配置的`app.id`、`apollo.cluster`
> 大小写正确,否则将获取不到正确的配置,另可参考`config-service.cache.key.ignore-case`配置做兼容处理。
🧰 Tools
🪛 Markdownlint (0.35.0)

1588-1588: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

docs/en/deployment/distributed-deployment-guide.md (1)

1652-1654: Minor formatting improvement needed

The blockquote formatting could be improved by removing the blank line between the two notes to maintain consistency with other blockquotes in the document.

> Ensure that the `app.id`、`apollo.cluster` of the configuration in the application is in the correct case when caching is enabled, otherwise it will not fetch the correct configuration, You can also refer to the `config-service.cache.key.ignore-case` configuration for compatibility processing.
-
> `config-service.cache.enabled` configuration adjustment requires a restart of the config service to take effect
🧰 Tools
🪛 Markdownlint (0.35.0)

1653-1653: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 6f230bf and b5c26d4.

📒 Files selected for processing (9)
  • apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java (1 hunks)
  • changes/changes-2.4.0.md (1 hunks)
  • docs/en/deployment/distributed-deployment-guide.md (1 hunks)
  • docs/zh/deployment/distributed-deployment-guide.md (1 hunks)
  • pom.xml (3 hunks)
  • scripts/sql/profiles/h2-default/apolloconfigdb.sql (1 hunks)
  • scripts/sql/profiles/mysql-database-not-specified/apolloconfigdb.sql (1 hunks)
  • scripts/sql/profiles/mysql-default/apolloconfigdb.sql (1 hunks)
  • scripts/sql/src/apolloconfigdb.sql (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (5)
  • apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java
  • pom.xml
  • scripts/sql/profiles/h2-default/apolloconfigdb.sql
  • scripts/sql/profiles/mysql-database-not-specified/apolloconfigdb.sql
  • scripts/sql/src/apolloconfigdb.sql
🧰 Additional context used
🪛 Markdownlint (0.35.0)
docs/en/deployment/distributed-deployment-guide.md

1653-1653: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

docs/zh/deployment/distributed-deployment-guide.md

1588-1588: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

🔇 Additional comments (2)
docs/zh/deployment/distributed-deployment-guide.md (1)

1577-1589: Documentation for incremental configuration synchronization looks good!

The new section clearly documents the important aspects of the feature:

  • Version compatibility requirements
  • Default value and memory considerations
  • Case sensitivity requirements
  • Restart requirements

This aligns well with the PR objectives to optimize configuration synchronization.

🧰 Tools
🪛 Markdownlint (0.35.0)

1588-1588: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

docs/en/deployment/distributed-deployment-guide.md (1)

1640-1655: LGTM! Clear and well-structured documentation for the new feature.

The documentation for the new incremental configuration synchronization feature is well-written, covering all essential aspects:

  • Version requirements
  • Feature description and benefits
  • Default configuration and considerations
  • Important notes about case sensitivity and restart requirements
🧰 Tools
🪛 Markdownlint (0.35.0)

1653-1653: null
Blank line inside blockquote

(MD028, no-blanks-blockquote)

Comment on lines +12 to +13
* [Feature support incremental configuration synchronization client](https://github.com/apolloconfig/apollo/pull/5288)

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance the changelog entry with crucial implementation details.

The changelog entry should provide more details about this significant feature to help users understand its benefits and requirements. Consider expanding it as follows:

-* [Feature support incremental configuration synchronization client](https://github.com/apolloconfig/apollo/pull/5288)
+* [Feature support incremental configuration synchronization client](https://github.com/apolloconfig/apollo/pull/5288)
+  * Optimizes configuration synchronization by updating only changed keys instead of entire namespace
+  * Reduces database bandwidth consumption significantly for large namespaces
+  * Requires server version 2.4.0+ and client version 2.3.0+
+  * Enable via `config-service.change.cache.enabled` configuration
📝 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
* [Feature support incremental configuration synchronization client](https://github.com/apolloconfig/apollo/pull/5288)
* [Feature support incremental configuration synchronization client](https://github.com/apolloconfig/apollo/pull/5288)
* Optimizes configuration synchronization by updating only changed keys instead of entire namespace
* Reduces database bandwidth consumption significantly for large namespaces
* Requires server version 2.4.0+ and client version 2.3.0+
* Enable via `config-service.change.cache.enabled` configuration

@jackie-coming
Copy link
Author

I have read the CLA Document and I hereby sign the CLA

I have read the CLA Document and I hereby sign the CLA

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 and nitpick comments (1)
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCache.java (1)

98-101: Add error logging in the catch block

While the transaction status is set correctly, logging the exception would help with debugging cache loading issues.

 } catch (Throwable ex) {
     transaction.setStatus(ex);
+    logger.error("Failed to load release for key: " + key, ex);
     throw ex;
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 8503249 and 07bb41c.

📒 Files selected for processing (2)
  • apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCache.java (1 hunks)
  • changes/changes-2.4.0.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • changes/changes-2.4.0.md
🔇 Additional comments (5)
apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/config/ConfigServiceWithChangeCache.java (5)

57-57: Fix the typo in the constant name

The constant DEFAULT_EXPIRED_AFTER_ACCESS_IN_SENCONDS contains a typo. "SENCONDS" should be corrected to "SECONDS".


67-81: LGTM! Well-structured initialization

The constructor properly initializes all dependencies through the parent class, and the @PostConstruct method ensures correct initialization order.


122-126: ⚠️ Potential issue

Add null check to prevent NPE

The code assumes latestRelease is non-null when accessing getReleaseKey(). This could lead to a NullPointerException.

 Release latestRelease = releaseService.findLatestActiveRelease(namespaceInfo.get(0),
         namespaceInfo.get(1), namespaceInfo.get(2));

-releasesCache.put(latestRelease.getReleaseKey(), Optional.ofNullable(latestRelease));
+if (latestRelease != null) {
+    releasesCache.put(latestRelease.getReleaseKey(), Optional.ofNullable(latestRelease));
+} else {
+    logger.warn("No active release found for namespace: {}", String.join("/", namespaceInfo));
+}

Likely invalid or redundant comment.


129-132: 🛠️ Refactor suggestion

Improve exception handling

Silently ignoring exceptions makes debugging difficult. Consider logging the error for better observability.

 } catch (Throwable ex) {
     transaction.setStatus(ex);
-    //ignore
+    logger.error("Failed to handle release message: " + messageKey, ex);
 }

Likely invalid or redundant comment.


150-154: ⚠️ Potential issue

Return empty map instead of null and improve error handling

Returning null can lead to NPEs in calling code. Additionally, the ExecutionException is silently ignored.

 } catch (ExecutionException e) {
-    //ignore
+    logger.error("Failed to get releases by keys: " + releaseKeys, e);
 }
-return null;
+return ImmutableMap.of();

Likely invalid or redundant comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
size:XL This PR changes 500-999 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

客户端配置同步增量更新
1 participant