Skip to content

Commit 9ddf463

Browse files
Copilotimnasnainaec
authored andcommitted
Convert MongoDB from standalone to replica set
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
1 parent c6afad4 commit 9ddf463

11 files changed

Lines changed: 306 additions & 26 deletions

File tree

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ Session.vim
3434
scripts/*.js
3535
!scripts/frontendScripts.js
3636
!scripts/jestTest.js
37-
!scripts/setupMongo.js
37+
!scripts/startDatabase.js
3838
database/*.js
3939
*.log
4040
*-debug.log*

.vscode/tasks.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@
2828
},
2929
{
3030
"label": "run-mongo",
31-
"command": "mongod",
31+
"command": "npm",
3232
"type": "process",
33-
"args": ["--dbpath", "${workspaceFolder}/mongo_database"],
33+
"args": ["run", "database"],
3434
"problemMatcher": "$tsc"
3535
}
3636
]

Backend/appsettings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"MongoDB": {
3-
"ConnectionString": "mongodb://localhost:27017",
4-
"ContainerConnectionString": "mongodb://database:27017",
3+
"ConnectionString": "mongodb://localhost:27017/?replicaSet=rs0",
4+
"ContainerConnectionString": "mongodb://database:27017/?replicaSet=rs0",
55
"CombineDatabase": "CombineDatabase"
66
},
77
"Logging": {

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,7 @@ npm run license-report-frontend
473473

474474
To browse the database locally during development, open [MongoDB Compass](https://www.mongodb.com/try/download/compass).
475475

476-
1. Under New Connection, enter `mongodb://localhost:27017`
476+
1. Under New Connection, enter `mongodb://localhost:27017/?replicaSet=rs0`
477477
2. Under Databases, select CombineDatabase
478478

479479
### Add or Update Dictionary Files

database/Dockerfile

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,21 @@
55
# - Intel/AMD 64-bit
66
# - ARM 64-bit
77
############################################################
8-
FROM mongo:7.0.28-jammy@sha256:8ddd3db4d2638eb914cce56284e2f0d6daf140bba31679b2af86f7d790a4c77e
8+
FROM mongo:7.0.30-jammy@sha256:aee9bae9f1a5507a51e19f24b015162cbcd7004695d99175dbccc427e20760e2
99

1010
WORKDIR /
1111

12-
RUN mkdir /data/semantic-domains
12+
RUN mkdir -p /data/semantic-domains /opt/thecombine
1313

1414
# Copy semantic domain import files
1515
COPY semantic_domains/* /data/semantic-domains/
1616

1717
# from https://hub.docker.com/_/mongo
18-
# Initializing a fresh instance
19-
# When a container is started for the first time it will execute files
20-
# with extensions .sh and .js that are found in /docker-entrypoint-initdb.d.
21-
# Files will be executed in alphabetical order. .js files will be executed
22-
# by mongosh (mongo on versions below 6) using the database specified by
23-
# the MONGO_INITDB_DATABASE variable, if it is present, or test otherwise.
24-
# You may also switch databases within the .js script.
25-
COPY init/* /docker-entrypoint-initdb.d/
18+
# Scripts in /docker-entrypoint-initdb.d run only on first startup of an empty
19+
# data directory. We intentionally keep setup scripts out of initdb.d and run
20+
# them from Kubernetes postStart to avoid first-boot race conditions.
21+
COPY init/update-semantic-domains.sh /opt/thecombine/update-semantic-domains.sh
22+
23+
# Replica set readiness/alignment runs from Kubernetes postStart hook on every
24+
# container start, so this script is intentionally kept out of initdb.d.
25+
COPY init/00-replica-set.js /opt/thecombine/00-replica-set.js

database/init/00-replica-set.js

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Ensure a single-node replica set is initialized and advertising the
2+
// expected host for this environment.
3+
//
4+
// MONGO_INITDB_REPLICA_HOST can be set to the resolvable hostname:port
5+
// used to advertise this member (e.g. "database:27017" in Kubernetes).
6+
const host = process.env.MONGO_INITDB_REPLICA_HOST || "localhost:27017";
7+
const maxWaitMs = 60 * 1000;
8+
const intervalMs = 1000;
9+
const start = Date.now();
10+
11+
/** Ensure the primary host is correctly configured */
12+
function ensurePrimaryHost(forceReconfig) {
13+
let conf;
14+
try {
15+
conf = rs.conf();
16+
} catch (error) {
17+
conf = db.getSiblingDB("local").system.replset.findOne();
18+
if (!forceReconfig || !conf) {
19+
throw error;
20+
}
21+
}
22+
23+
if (!conf.members?.length) {
24+
throw new Error("Replica set config has no members");
25+
}
26+
27+
if (conf.members[0].host !== host) {
28+
print(`Updating replica set member host to ${host}`);
29+
conf.members[0].host = host;
30+
conf.version = (conf.version || 1) + 1;
31+
rs.reconfig(conf, { force: forceReconfig });
32+
return false;
33+
}
34+
35+
return true;
36+
}
37+
38+
// Wait for replica set to be initialized.
39+
let replicaSetInitiated = false;
40+
while (Date.now() - start < maxWaitMs) {
41+
try {
42+
rs.initiate({ _id: "rs0", members: [{ _id: 0, host: host }] });
43+
print(`Initialized replica set with host ${host}`);
44+
replicaSetInitiated = true;
45+
break;
46+
} catch (err) {
47+
if (String(err).includes("already initialized")) {
48+
print("Replica set already initialized");
49+
replicaSetInitiated = true;
50+
break;
51+
}
52+
53+
print(`Replica set init deferred: ${err}`);
54+
}
55+
56+
sleep(intervalMs);
57+
}
58+
if (!replicaSetInitiated) {
59+
throw new Error(`Replica set not initialized after ${maxWaitMs}ms`);
60+
}
61+
62+
// Wait for this member to be PRIMARY with the correct host.
63+
while (Date.now() - start < maxWaitMs) {
64+
try {
65+
if (db.hello().isWritablePrimary) {
66+
if (ensurePrimaryHost(false)) {
67+
print(`Replica set is PRIMARY with correct host: ${host}`);
68+
quit(0);
69+
}
70+
} else {
71+
ensurePrimaryHost(true);
72+
}
73+
} catch (err) {
74+
print(`Host alignment deferred: ${err}`);
75+
}
76+
77+
sleep(intervalMs);
78+
}
79+
throw new Error(
80+
`Replica set did not reach PRIMARY state with host ${host} after ${maxWaitMs}ms`
81+
);

deploy/helm/thecombine/charts/database/templates/database.yaml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,40 @@ spec:
4646
- image: {{ include "database.containerImage" . }}
4747
imagePullPolicy: {{ .Values.global.imagePullPolicy }}
4848
name: database
49+
args:
50+
- "--replSet"
51+
- "rs0"
52+
lifecycle:
53+
postStart:
54+
exec:
55+
command:
56+
- /bin/sh
57+
- -c
58+
- |
59+
set -e
60+
echo "[postStart] Waiting for mongod to accept connections"
61+
attempts=0
62+
until mongosh --quiet --host 127.0.0.1 --eval "db.adminCommand({ ping: 1 }).ok" >/dev/null 2>&1; do
63+
attempts=$((attempts + 1))
64+
if [ "${attempts}" -ge 120 ]; then
65+
echo "[postStart] Failed to connect to mongod after ${attempts} attempts, exiting"
66+
exit 1
67+
fi
68+
sleep 1
69+
done
70+
echo "[postStart] Ensuring replica set host"
71+
mongosh --quiet --host 127.0.0.1 /opt/thecombine/00-replica-set.js || exit $?
72+
needs_semantic_import="$(mongosh --quiet --host 127.0.0.1 --eval "const combineDb = db.getSiblingDB('CombineDatabase'); const treeCount = combineDb.SemanticDomainTree.countDocuments({}); const domainCount = combineDb.SemanticDomains.countDocuments({}); print(treeCount === 0 || domainCount === 0 ? 'yes' : 'no');")"
73+
if [ "${needs_semantic_import}" = "yes" ]; then
74+
/bin/bash /opt/thecombine/update-semantic-domains.sh
75+
fi
76+
env:
77+
- name: POD_IP
78+
valueFrom:
79+
fieldRef:
80+
fieldPath: status.podIP
81+
- name: MONGO_INITDB_REPLICA_HOST
82+
value: "$(POD_IP):27017"
4983
ports:
5084
- containerPort: 27017
5185
resources:

docs/deploy/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -415,7 +415,7 @@ Notes:
415415
rerun manually:
416416

417417
```console
418-
kubectl -n thecombine exec deployment/database -- /docker-entrypoint-initdb.d/update-semantic-domains.sh
418+
kubectl -n thecombine exec deployment/database -- /opt/thecombine/update-semantic-domains.sh
419419
```
420420

421421
## Maintenance

package.json

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88
"backend": "dotnet watch --project Backend/BackendFramework.csproj",
99
"build": "parcel build",
1010
"build:analyze": "npm run build -- --reporter @parcel/reporter-bundle-analyzer",
11-
"predatabase": "node scripts/setupMongo.js",
12-
"database": "mongod --dbpath=./mongo_database",
11+
"database": "node scripts/startDatabase.js",
1312
"drop-database": "tsc scripts/dropDB.ts && node scripts/dropDB.js",
1413
"find-circular-deps": "npx --ignore-scripts -y madge -c src/index.tsx --ts-config tsconfig.json",
1514
"fmt-backend": " dotnet format && dotnet format Backend.Tests",

scripts/setupMongo.js

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)