codeconsole commented on code in PR #16208:
URL: https://github.com/apache/grails-core/pull/16208#discussion_r4066327100


##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -1269,6 +1698,35 @@ public void close() {
         }
     }
 
+    /**
+     * Interrupts the background index build, if there is one. A build can run 
for minutes, so shutdown
+     * must not wait for it; the server carries on building what it was asked 
for.
+     *
+     * @return whether a build was running or queued, and so was cut short
+     */
+    private boolean shutDownIndexBuild() {
+        ExecutorService executor = this.indexBuildExecutor;
+        if (executor == null) {
+            return false;
+        }
+        boolean cutShort = indexBuildsInFlight.get() > 0;

Review Comment:
   Fixed in d8adb08fcf, the way you suggested. The in-flight counter is gone. A 
build that is running when `stop()` interrupts it records that it was cut short 
as it exits, from the abandoned branch, so one that finishes normally records 
nothing; a build still queued is picked up from `shutdownNow()`. `start()` 
waits for the interrupted worker to have exited, up to ten seconds, before it 
decides, which also covers @matrei's note that `stop()` does not wait. 
`BuildIndexesLifecycleSpec` has a build that has applied everything and is on 
its way out when `stop()` runs, and does not respond to the interrupt; 
`start()` then starts no second build. The counter would have re-run it.



##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -1282,7 +1740,14 @@ public void close() {
      * @return The {@link ConnectionSources}
      */
     protected static ConnectionSources<MongoClient, 
MongoConnectionSourceSettings> createDefaultConnectionSources(MongoClient 
mongoClient, PropertyResolver configuration, MongoMappingContext 
mappingContext, boolean closeable) {
-        MongoConnectionSourceSettings settings = new 
MongoConnectionSourceSettings();
+        // Bound from the configuration rather than left at the defaults: the 
client is supplied here, but
+        // the settings that describe how the datastore behaves (multiTenancy, 
stateless, transactional,
+        // buildIndexes, engine, flush mode) still come from grails.mongodb 
with grails.gorm fallbacks,
+        // exactly as they do when GORM creates the client itself. The 
connection details in them are
+        // unused - this client is already connected - so a configured URL is 
dropped: its database would
+        // otherwise take precedence over the mapping context's, which is the 
one this path always used.
+        MongoConnectionSourceSettings settings = 
buildConnectionSourceSettings(configuration);
+        settings.url(null);

Review Comment:
   I've left this one. Keeping the URL and rewriting its database has a trap: a 
connection string's database path is also the default authentication database 
whenever the URL gives no `authSource`. A named connection inheriting 
`mongodb://user:pass@prod-host/admin` rewritten to `/app` would start 
authenticating against `app`. Doing it safely means adding `authSource` when 
the credentials need it, which is more connection-string surgery than this PR 
should carry.
   
   The other route, taking the datastore's database from the mapping context, 
does not work either: per-connection children share the parent's mapping 
context, so every named connection would get the parent's database.
   
   As you say, it is not a regression — a named connection without its own 
`url` on this path fell back to the defaults before as well — and giving it its 
own `url` works on both sides of this PR. I'd rather leave the scrub, which 
does what the comment says, than trade it for an authentication change nobody 
asked for.



##########
grails-data-mongodb/docs/src/docs/asciidoc/querying/queryIndexes.adoc:
##########
@@ -142,6 +142,107 @@ WARNING: Dropping and recreating an index rebuilds it 
from scratch, during which
 A change to a TTL index's `expireAfterSeconds` is handled automatically and 
does not require `recreateOnConflict`, because GORM updates the expiry in place 
rather than rebuilding the index.
 
 
+==== Disabling Index Creation on Startup
+
+
+By default GORM creates and reconciles every index declared in a mapping block 
when the datastore starts. Set `buildIndexes` to `false` in 
`grails-app/conf/application.yml` to switch that off:
+
+[source,yaml]
+----
+grails:
+    mongodb:
+        buildIndexes: false
+----
+
+or, in `application.groovy`:
+
+[source,groovy]
+----
+grails {
+    mongodb {
+        buildIndexes = false
+    }
+}
+----
+
+With this setting no `createIndex` or `collMod` command is issued for any 
domain class, and the indexes already present on the server are left exactly as 
they are. Queries are unaffected and continue to use whichever indexes exist. 
This is useful when deploying against live data whose indexes are managed 
separately — by a DBA or a migration step — so that a deployment does not build 
an index against a large production collection, and so an application running 
against an older index set does not have those indexes reconciled underneath it.
+
+It also suppresses index creation for domain classes registered after startup. 
Declared at the top level the setting applies to every connection, and each 
connection can override it:
+
+[source,yaml]
+----
+grails:
+    mongodb:
+        buildIndexes: false
+        connections:
+            reporting:
+                url: mongodb://localhost/reporting
+                buildIndexes: true
+----
+
+NOTE: Because nothing is created, a collection that has never been initialised 
with `buildIndexes` enabled will have no declared indexes at all. Turn the 
setting off only where the indexes are already in place or are applied by other 
means. The setting governs only the indexes GORM derives from the mapping 
blocks; an explicit `createIndex` call made by application code against a 
collection is unaffected.
+
+==== Building Indexes in the Background
+
+
+MongoDB answers a `createIndex` command only once the index has been built, so 
by default the thread that creates the datastore — in an application, the 
startup thread — waits for every declared index before the application finishes 
starting. On an empty collection that is instant; on a large existing 
collection an index build can take minutes, and a deployment waits for all of 
them in turn.
+
+Set `buildIndexesAsync` to have the startup index build run on a background 
thread instead:
+
+[source,yaml]
+----
+grails:
+    mongodb:
+        buildIndexesAsync: true
+----
+
+Startup then continues without waiting. The indexes are still built one at a 
time per connection, on a daemon thread named 
`gorm-mongo-index-build-<connection>-<n>`, where `<n>` counts the workers 
started for that connection. Different connections can build indexes 
concurrently, including when they use the same MongoDB server. The thread is 
released after the build finishes and it has been idle for one second.

Review Comment:
   Right. `<n>` counts within one executor, and `start()` makes a new one. 
Fixed in d8adb08fcf: the guide now just gives an example name, 
`gorm-mongo-index-build-default-1`.



##########
grails-data-mongodb/core/build.gradle:
##########
@@ -149,7 +149,11 @@ dependencies {
         // test: GenericWebApplicationContext requires ServletContext on the 
classpath
     }
 
-    testImplementation 'org.slf4j:slf4j-nop' // Prevents warning about missing 
slf4j implementation during compilation and tests
+    testImplementation 'ch.qos.logback:logback-classic', {

Review Comment:
   Done in 0a12094fa4.



##########
grails-data-mongodb/core/src/main/groovy/org/grails/datastore/mapping/mongo/MongoDatastore.java:
##########
@@ -1241,6 +1500,14 @@ private boolean ownsClient() {
     @PreDestroy
     public void close() {
         MongoClient current = this.mongo;
+        shutDownIndexBuild();

Review Comment:
   Taken in d8adb08fcf: `stop()` and `start()` now cover every connection, like 
`close()`. `stop()` interrupts each connection's build before closing the 
client, and `start()` gives each a fresh executor and re-runs a build that was 
cut short, or requested while stopped. `BuildIndexesPerConnectionSpec` blocks a 
named connection's `createIndex`, stops the datastore, and asserts the child's 
build is abandoned as a shutdown with no error on its worker. It then starts 
the datastore and waits for the child's index.
   
   This interrupts the child's build; it does not close the child's client. 
`stop()` closes only the default connection's client, as it did before this PR, 
so a checkpoint with named connections still sees their sockets. That is the 
CRaC support's behaviour for named connections rather than something the index 
build introduced, so I've kept it out of this PR; it is worth an issue of its 
own.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to