diff --git a/compose.yaml b/compose.yaml
deleted file mode 100644
index 75c45c3d..00000000
--- a/compose.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-services:
- mongodb1:
- container_name: mongodb1
- image: dh-mirror.gitverse.ru/mongo:latest
- volumes:
- - mongodb1_data_container:/data/db
-
- pymongo_api:
- container_name: pymongo_api
- build:
- context: api_app
- dockerfile: Dockerfile
- image: kazhem/pymongo_api:1.0.0
- depends_on:
- - mongodb1
- ports:
- - 8080:8080
- environment:
- MONGODB_URL: "mongodb://mongodb1"
- MONGODB_DATABASE_NAME: "somedb"
-
-
-volumes:
- mongodb1_data_container:
\ No newline at end of file
diff --git a/diagrams.drawio b/diagrams.drawio
new file mode 100644
index 00000000..e0863dd3
--- /dev/null
+++ b/diagrams.drawio
@@ -0,0 +1,600 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.gitignore b/mongo-sharding-repl/.gitignore
similarity index 100%
rename from .gitignore
rename to mongo-sharding-repl/.gitignore
diff --git a/README.md b/mongo-sharding-repl/README.md
similarity index 69%
rename from README.md
rename to mongo-sharding-repl/README.md
index b6ddb826..115de256 100644
--- a/README.md
+++ b/mongo-sharding-repl/README.md
@@ -8,10 +8,22 @@
docker compose up -d
```
-Заполняем mongodb данными
+Инициализируем шарды mongodb и заполняем тестовыми данными:
```shell
-./scripts/mongo-init.sh
+./scripts/mongo-init-configSrv.sh
+```
+
+```shell
+./scripts/mongo-init-shards.sh
+```
+
+```shell
+./scripts/mongo-init-router.sh
+```
+
+```shell
+./scripts/mongo-init-fixtures.sh
```
## Как проверить
@@ -32,4 +44,4 @@ curl --silent http://ifconfig.me
## Доступные эндпоинты
-Список доступных эндпоинтов, swagger http://:8080/docs
\ No newline at end of file
+Список доступных эндпоинтов, swagger http://:8080/docs
diff --git a/api_app/Dockerfile b/mongo-sharding-repl/api_app/Dockerfile
similarity index 100%
rename from api_app/Dockerfile
rename to mongo-sharding-repl/api_app/Dockerfile
diff --git a/api_app/app.py b/mongo-sharding-repl/api_app/app.py
similarity index 100%
rename from api_app/app.py
rename to mongo-sharding-repl/api_app/app.py
diff --git a/api_app/requirements.txt b/mongo-sharding-repl/api_app/requirements.txt
similarity index 100%
rename from api_app/requirements.txt
rename to mongo-sharding-repl/api_app/requirements.txt
diff --git a/mongo-sharding-repl/compose.yaml b/mongo-sharding-repl/compose.yaml
new file mode 100644
index 00000000..3901bcb9
--- /dev/null
+++ b/mongo-sharding-repl/compose.yaml
@@ -0,0 +1,193 @@
+name: mongo-sharding-repl
+
+services:
+ # сервер конфигурации
+ configSrv:
+ image: mongo:latest
+ container_name: configSrv
+ restart: always
+ ports:
+ - "27019:27019"
+ volumes:
+ - config-data:/data/db
+ command:
+ [
+ "--configsvr",
+ "--replSet",
+ "config_server",
+ "--bind_ip_all",
+ "--port",
+ "27019"
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard1-1:
+ image: mongo:latest
+ container_name: shard1-1
+ restart: always
+ ports:
+ - "28011:27018"
+ volumes:
+ - shard1-1-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard1",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard1-2:
+ image: mongo:latest
+ container_name: shard1-2
+ restart: always
+ ports:
+ - "28012:27018"
+ volumes:
+ - shard1-2-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard1",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard1-3:
+ image: mongo:latest
+ container_name: shard1-3
+ restart: always
+ ports:
+ - "28013:27018"
+ volumes:
+ - shard1-3-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard1",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard2-1:
+ image: mongo:latest
+ container_name: shard2-1
+ restart: always
+ ports:
+ - "28021:27018"
+ volumes:
+ - shard2-1-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard2",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard2-2:
+ image: mongo:latest
+ container_name: shard2-2
+ restart: always
+ ports:
+ - "28022:27018"
+ volumes:
+ - shard2-2-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard2",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard2-3:
+ image: mongo:latest
+ container_name: shard2-3
+ restart: always
+ ports:
+ - "28023:27018"
+ volumes:
+ - shard2-3-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard2",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ mongos_router:
+ image: mongo:latest
+ container_name: mongos_router
+ restart: always
+ ports:
+ - "27021:27021"
+ command:
+ [
+ "mongos",
+ "--configdb",
+ "config_server/configSrv:27019",
+ "--bind_ip_all",
+ "--port",
+ "27021"
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ pymongo_api:
+ container_name: pymongo_api
+ build:
+ context: api_app
+ dockerfile: Dockerfile
+ image: kazhem/pymongo_api:1.0.0
+ depends_on:
+ - mongos_router
+ ports:
+ - 8080:8080
+ environment:
+ MONGODB_URL: "mongodb://mongos_router:27021"
+ MONGODB_DATABASE_NAME: "somedb"
+
+networks:
+ default:
+ driver: bridge
+
+volumes:
+ mongodb1_data_container:
+ config-data:
+ shard1-1-data:
+ shard1-2-data:
+ shard1-3-data:
+ shard2-1-data:
+ shard2-2-data:
+ shard2-3-data:
diff --git a/mongo-sharding-repl/scripts/mongo-init-configSrv.sh b/mongo-sharding-repl/scripts/mongo-init-configSrv.sh
new file mode 100755
index 00000000..93c5f702
--- /dev/null
+++ b/mongo-sharding-repl/scripts/mongo-init-configSrv.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+### Инициализация сервера конфигурации
+
+docker compose exec -T configSrv mongosh --port 27019 --quiet <:8080
+
+## Доступные эндпоинты
+
+Список доступных эндпоинтов, swagger http://:8080/docs
diff --git a/mongo-sharding/api_app/Dockerfile b/mongo-sharding/api_app/Dockerfile
new file mode 100644
index 00000000..46f6c9d0
--- /dev/null
+++ b/mongo-sharding/api_app/Dockerfile
@@ -0,0 +1,10 @@
+FROM python:3.12.1-slim
+WORKDIR /app
+EXPOSE 8080
+COPY requirements.txt ./
+# Устанавливаем зависимости python не пересобирая их
+RUN pip install --no-cache --no-cache-dir -r requirements.txt
+# Копирование кода приложения
+COPY app.py /app/
+ENTRYPOINT ["uvicorn"]
+CMD ["app:app", "--host", "0.0.0.0", "--port", "8080"]
diff --git a/mongo-sharding/api_app/app.py b/mongo-sharding/api_app/app.py
new file mode 100644
index 00000000..9b19c017
--- /dev/null
+++ b/mongo-sharding/api_app/app.py
@@ -0,0 +1,192 @@
+import json
+import logging
+import os
+import time
+from typing import List, Optional
+
+import motor.motor_asyncio
+from bson import ObjectId
+from fastapi import Body, FastAPI, HTTPException, status
+from fastapi_cache import FastAPICache
+from fastapi_cache.backends.redis import RedisBackend
+from fastapi_cache.decorator import cache
+from logmiddleware import RouterLoggingMiddleware, logging_config
+from pydantic import BaseModel, ConfigDict, EmailStr, Field
+from pydantic.functional_validators import BeforeValidator
+from pymongo import errors
+from redis import asyncio as aioredis
+from typing_extensions import Annotated
+
+# Configure JSON logging
+logging.config.dictConfig(logging_config)
+logger = logging.getLogger(__name__)
+
+app = FastAPI()
+app.add_middleware(
+ RouterLoggingMiddleware,
+ logger=logger,
+)
+
+DATABASE_URL = os.environ["MONGODB_URL"]
+DATABASE_NAME = os.environ["MONGODB_DATABASE_NAME"]
+REDIS_URL = os.getenv("REDIS_URL", None)
+
+
+def nocache(*args, **kwargs):
+ def decorator(func):
+ return func
+
+ return decorator
+
+
+if REDIS_URL:
+ cache = cache
+else:
+ cache = nocache
+
+
+client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URL)
+db = client[DATABASE_NAME]
+
+# Represents an ObjectId field in the database.
+# It will be represented as a `str` on the model so that it can be serialized to JSON.
+PyObjectId = Annotated[str, BeforeValidator(str)]
+
+
+@app.on_event("startup")
+async def startup():
+ if REDIS_URL:
+ redis = aioredis.from_url(REDIS_URL, encoding="utf8", decode_responses=True)
+ FastAPICache.init(RedisBackend(redis), prefix="api:cache")
+
+
+class UserModel(BaseModel):
+ """
+ Container for a single user record.
+ """
+
+ id: Optional[PyObjectId] = Field(alias="_id", default=None)
+ age: int = Field(...)
+ name: str = Field(...)
+
+
+class UserCollection(BaseModel):
+ """
+ A container holding a list of `UserModel` instances.
+ """
+
+ users: List[UserModel]
+
+
+@app.get("/")
+async def root():
+ collection_names = await db.list_collection_names()
+ collections = {}
+ for collection_name in collection_names:
+ collection = db.get_collection(collection_name)
+ collections[collection_name] = {
+ "documents_count": await collection.count_documents({})
+ }
+ try:
+ replica_status = await client.admin.command("replSetGetStatus")
+ replica_status = json.dumps(replica_status, indent=2, default=str)
+ except errors.OperationFailure:
+ replica_status = "No Replicas"
+
+ topology_description = client.topology_description
+ read_preference = client.client_options.read_preference
+ topology_type = topology_description.topology_type_name
+ replicaset_name = topology_description.replica_set_name
+
+ shards = None
+ if topology_type == "Sharded":
+ shards_list = await client.admin.command("listShards")
+ shards = {}
+ for shard in shards_list.get("shards", {}):
+ shards[shard["_id"]] = shard["host"]
+
+ cache_enabled = False
+ if REDIS_URL:
+ cache_enabled = FastAPICache.get_enable()
+
+ return {
+ "mongo_topology_type": topology_type,
+ "mongo_replicaset_name": replicaset_name,
+ "mongo_db": DATABASE_NAME,
+ "read_preference": str(read_preference),
+ "mongo_nodes": client.nodes,
+ "mongo_primary_host": client.primary,
+ "mongo_secondary_hosts": client.secondaries,
+ "mongo_address": client.address,
+ "mongo_is_primary": client.is_primary,
+ "mongo_is_mongos": client.is_mongos,
+ "collections": collections,
+ "shards": shards,
+ "cache_enabled": cache_enabled,
+ "status": "OK",
+ }
+
+
+@app.get("/{collection_name}/count")
+async def collection_count(collection_name: str):
+ collection = db.get_collection(collection_name)
+ items_count = await collection.count_documents({})
+ # status = await client.admin.command('replSetGetStatus')
+ # import ipdb; ipdb.set_trace()
+ return {"status": "OK", "mongo_db": DATABASE_NAME, "items_count": items_count}
+
+
+@app.get(
+ "/{collection_name}/users",
+ response_description="List all users",
+ response_model=UserCollection,
+ response_model_by_alias=False,
+)
+@cache(expire=60 * 1)
+async def list_users(collection_name: str):
+ """
+ List all of the user data in the database.
+ The response is unpaginated and limited to 1000 results.
+ """
+ time.sleep(1)
+ collection = db.get_collection(collection_name)
+ return UserCollection(users=await collection.find().to_list(1000))
+
+
+@app.get(
+ "/{collection_name}/users/{name}",
+ response_description="Get a single user",
+ response_model=UserModel,
+ response_model_by_alias=False,
+)
+async def show_user(collection_name: str, name: str):
+ """
+ Get the record for a specific user, looked up by `name`.
+ """
+
+ collection = db.get_collection(collection_name)
+ if (user := await collection.find_one({"name": name})) is not None:
+ return user
+
+ raise HTTPException(status_code=404, detail=f"User {name} not found")
+
+
+@app.post(
+ "/{collection_name}/users",
+ response_description="Add new user",
+ response_model=UserModel,
+ status_code=status.HTTP_201_CREATED,
+ response_model_by_alias=False,
+)
+async def create_user(collection_name: str, user: UserModel = Body(...)):
+ """
+ Insert a new user record.
+
+ A unique `id` will be created and provided in the response.
+ """
+ collection = db.get_collection(collection_name)
+ new_user = await collection.insert_one(
+ user.model_dump(by_alias=True, exclude=["id"])
+ )
+ created_user = await collection.find_one({"_id": new_user.inserted_id})
+ return created_user
diff --git a/mongo-sharding/api_app/requirements.txt b/mongo-sharding/api_app/requirements.txt
new file mode 100644
index 00000000..6dde742d
--- /dev/null
+++ b/mongo-sharding/api_app/requirements.txt
@@ -0,0 +1,6 @@
+fastapi==0.110.2
+uvicorn[standard]==0.29.0
+motor==3.5.0
+redis==4.4.2
+fastapi-cache2==0.2.0
+logmiddleware==0.0.4
\ No newline at end of file
diff --git a/mongo-sharding/compose.yaml b/mongo-sharding/compose.yaml
new file mode 100644
index 00000000..d6153c8a
--- /dev/null
+++ b/mongo-sharding/compose.yaml
@@ -0,0 +1,130 @@
+name: mongo-sharding
+
+services:
+ # сервер конфигурации
+ configSrv:
+ image: mongo:latest
+ container_name: configSrv
+ restart: always
+ ports:
+ - "27019:27019"
+ networks:
+ app-network:
+ ipv4_address: 173.17.0.10
+ volumes:
+ - config-data:/data/db
+ command:
+ [
+ "--configsvr",
+ "--replSet",
+ "config_server",
+ "--bind_ip_all",
+ "--port",
+ "27019"
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard1:
+ image: mongo:latest
+ container_name: shard1
+ restart: always
+ ports:
+ - "28001:27018"
+ networks:
+ app-network:
+ ipv4_address: 173.17.0.9
+ volumes:
+ - shard1-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard1",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard2:
+ image: mongo:latest
+ container_name: shard2
+ restart: always
+ ports:
+ - "28002:27018"
+ networks:
+ app-network:
+ ipv4_address: 173.17.0.8
+ volumes:
+ - shard2-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard2",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ mongos_router:
+ image: mongo:latest
+ container_name: mongos_router
+ restart: always
+ ports:
+ - "27021:27021"
+ networks:
+ app-network:
+ ipv4_address: 173.17.0.7
+ command:
+ [
+ "mongos",
+ "--configdb",
+ "config_server/configSrv:27019",
+ "--bind_ip_all",
+ "--port",
+ "27021"
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ pymongo_api:
+ container_name: pymongo_api
+ build:
+ context: api_app
+ dockerfile: Dockerfile
+ image: kazhem/pymongo_api:1.0.0
+ networks:
+ app-network:
+ ipv4_address: 173.17.0.11
+ depends_on:
+ - shard1
+ - shard2
+ - mongos_router
+ ports:
+ - 8080:8080
+ environment:
+ MONGODB_URL: "mongodb://mongos_router:27021"
+ MONGODB_DATABASE_NAME: "somedb"
+
+networks:
+ app-network:
+ driver: bridge
+ ipam:
+ driver: default
+ config:
+ - subnet: 173.17.0.0/16
+
+volumes:
+ mongodb1_data_container:
+ config-data:
+ shard1-data:
+ shard2-data:
diff --git a/mongo-sharding/scripts/mongo-init-configSrv.sh b/mongo-sharding/scripts/mongo-init-configSrv.sh
new file mode 100755
index 00000000..93c5f702
--- /dev/null
+++ b/mongo-sharding/scripts/mongo-init-configSrv.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+### Инициализация сервера конфигурации
+
+docker compose exec -T configSrv mongosh --port 27019 --quiet <:8080
+
+## Доступные эндпоинты
+
+Список доступных эндпоинтов, swagger http://:8080/docs
diff --git a/sharding-repl-cache/api_app/Dockerfile b/sharding-repl-cache/api_app/Dockerfile
new file mode 100644
index 00000000..46f6c9d0
--- /dev/null
+++ b/sharding-repl-cache/api_app/Dockerfile
@@ -0,0 +1,10 @@
+FROM python:3.12.1-slim
+WORKDIR /app
+EXPOSE 8080
+COPY requirements.txt ./
+# Устанавливаем зависимости python не пересобирая их
+RUN pip install --no-cache --no-cache-dir -r requirements.txt
+# Копирование кода приложения
+COPY app.py /app/
+ENTRYPOINT ["uvicorn"]
+CMD ["app:app", "--host", "0.0.0.0", "--port", "8080"]
diff --git a/sharding-repl-cache/api_app/app.py b/sharding-repl-cache/api_app/app.py
new file mode 100644
index 00000000..9b19c017
--- /dev/null
+++ b/sharding-repl-cache/api_app/app.py
@@ -0,0 +1,192 @@
+import json
+import logging
+import os
+import time
+from typing import List, Optional
+
+import motor.motor_asyncio
+from bson import ObjectId
+from fastapi import Body, FastAPI, HTTPException, status
+from fastapi_cache import FastAPICache
+from fastapi_cache.backends.redis import RedisBackend
+from fastapi_cache.decorator import cache
+from logmiddleware import RouterLoggingMiddleware, logging_config
+from pydantic import BaseModel, ConfigDict, EmailStr, Field
+from pydantic.functional_validators import BeforeValidator
+from pymongo import errors
+from redis import asyncio as aioredis
+from typing_extensions import Annotated
+
+# Configure JSON logging
+logging.config.dictConfig(logging_config)
+logger = logging.getLogger(__name__)
+
+app = FastAPI()
+app.add_middleware(
+ RouterLoggingMiddleware,
+ logger=logger,
+)
+
+DATABASE_URL = os.environ["MONGODB_URL"]
+DATABASE_NAME = os.environ["MONGODB_DATABASE_NAME"]
+REDIS_URL = os.getenv("REDIS_URL", None)
+
+
+def nocache(*args, **kwargs):
+ def decorator(func):
+ return func
+
+ return decorator
+
+
+if REDIS_URL:
+ cache = cache
+else:
+ cache = nocache
+
+
+client = motor.motor_asyncio.AsyncIOMotorClient(DATABASE_URL)
+db = client[DATABASE_NAME]
+
+# Represents an ObjectId field in the database.
+# It will be represented as a `str` on the model so that it can be serialized to JSON.
+PyObjectId = Annotated[str, BeforeValidator(str)]
+
+
+@app.on_event("startup")
+async def startup():
+ if REDIS_URL:
+ redis = aioredis.from_url(REDIS_URL, encoding="utf8", decode_responses=True)
+ FastAPICache.init(RedisBackend(redis), prefix="api:cache")
+
+
+class UserModel(BaseModel):
+ """
+ Container for a single user record.
+ """
+
+ id: Optional[PyObjectId] = Field(alias="_id", default=None)
+ age: int = Field(...)
+ name: str = Field(...)
+
+
+class UserCollection(BaseModel):
+ """
+ A container holding a list of `UserModel` instances.
+ """
+
+ users: List[UserModel]
+
+
+@app.get("/")
+async def root():
+ collection_names = await db.list_collection_names()
+ collections = {}
+ for collection_name in collection_names:
+ collection = db.get_collection(collection_name)
+ collections[collection_name] = {
+ "documents_count": await collection.count_documents({})
+ }
+ try:
+ replica_status = await client.admin.command("replSetGetStatus")
+ replica_status = json.dumps(replica_status, indent=2, default=str)
+ except errors.OperationFailure:
+ replica_status = "No Replicas"
+
+ topology_description = client.topology_description
+ read_preference = client.client_options.read_preference
+ topology_type = topology_description.topology_type_name
+ replicaset_name = topology_description.replica_set_name
+
+ shards = None
+ if topology_type == "Sharded":
+ shards_list = await client.admin.command("listShards")
+ shards = {}
+ for shard in shards_list.get("shards", {}):
+ shards[shard["_id"]] = shard["host"]
+
+ cache_enabled = False
+ if REDIS_URL:
+ cache_enabled = FastAPICache.get_enable()
+
+ return {
+ "mongo_topology_type": topology_type,
+ "mongo_replicaset_name": replicaset_name,
+ "mongo_db": DATABASE_NAME,
+ "read_preference": str(read_preference),
+ "mongo_nodes": client.nodes,
+ "mongo_primary_host": client.primary,
+ "mongo_secondary_hosts": client.secondaries,
+ "mongo_address": client.address,
+ "mongo_is_primary": client.is_primary,
+ "mongo_is_mongos": client.is_mongos,
+ "collections": collections,
+ "shards": shards,
+ "cache_enabled": cache_enabled,
+ "status": "OK",
+ }
+
+
+@app.get("/{collection_name}/count")
+async def collection_count(collection_name: str):
+ collection = db.get_collection(collection_name)
+ items_count = await collection.count_documents({})
+ # status = await client.admin.command('replSetGetStatus')
+ # import ipdb; ipdb.set_trace()
+ return {"status": "OK", "mongo_db": DATABASE_NAME, "items_count": items_count}
+
+
+@app.get(
+ "/{collection_name}/users",
+ response_description="List all users",
+ response_model=UserCollection,
+ response_model_by_alias=False,
+)
+@cache(expire=60 * 1)
+async def list_users(collection_name: str):
+ """
+ List all of the user data in the database.
+ The response is unpaginated and limited to 1000 results.
+ """
+ time.sleep(1)
+ collection = db.get_collection(collection_name)
+ return UserCollection(users=await collection.find().to_list(1000))
+
+
+@app.get(
+ "/{collection_name}/users/{name}",
+ response_description="Get a single user",
+ response_model=UserModel,
+ response_model_by_alias=False,
+)
+async def show_user(collection_name: str, name: str):
+ """
+ Get the record for a specific user, looked up by `name`.
+ """
+
+ collection = db.get_collection(collection_name)
+ if (user := await collection.find_one({"name": name})) is not None:
+ return user
+
+ raise HTTPException(status_code=404, detail=f"User {name} not found")
+
+
+@app.post(
+ "/{collection_name}/users",
+ response_description="Add new user",
+ response_model=UserModel,
+ status_code=status.HTTP_201_CREATED,
+ response_model_by_alias=False,
+)
+async def create_user(collection_name: str, user: UserModel = Body(...)):
+ """
+ Insert a new user record.
+
+ A unique `id` will be created and provided in the response.
+ """
+ collection = db.get_collection(collection_name)
+ new_user = await collection.insert_one(
+ user.model_dump(by_alias=True, exclude=["id"])
+ )
+ created_user = await collection.find_one({"_id": new_user.inserted_id})
+ return created_user
diff --git a/sharding-repl-cache/api_app/requirements.txt b/sharding-repl-cache/api_app/requirements.txt
new file mode 100644
index 00000000..6dde742d
--- /dev/null
+++ b/sharding-repl-cache/api_app/requirements.txt
@@ -0,0 +1,6 @@
+fastapi==0.110.2
+uvicorn[standard]==0.29.0
+motor==3.5.0
+redis==4.4.2
+fastapi-cache2==0.2.0
+logmiddleware==0.0.4
\ No newline at end of file
diff --git a/sharding-repl-cache/compose.yaml b/sharding-repl-cache/compose.yaml
new file mode 100644
index 00000000..d13f4966
--- /dev/null
+++ b/sharding-repl-cache/compose.yaml
@@ -0,0 +1,204 @@
+name: sharding-repl-cache
+
+services:
+ redis:
+ image: redis:latest
+ container_name: redis
+ restart: always
+ ports:
+ - "6379:6379"
+ volumes:
+ - redis-data:/data
+
+ # сервер конфигурации
+ configSrv:
+ image: mongo:latest
+ container_name: configSrv
+ restart: always
+ ports:
+ - "27019:27019"
+ volumes:
+ - config-data:/data/db
+ command:
+ [
+ "--configsvr",
+ "--replSet",
+ "config_server",
+ "--bind_ip_all",
+ "--port",
+ "27019"
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard1-1:
+ image: mongo:latest
+ container_name: shard1-1
+ restart: always
+ ports:
+ - "28011:27018"
+ volumes:
+ - shard1-1-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard1",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard1-2:
+ image: mongo:latest
+ container_name: shard1-2
+ restart: always
+ ports:
+ - "28012:27018"
+ volumes:
+ - shard1-2-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard1",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard1-3:
+ image: mongo:latest
+ container_name: shard1-3
+ restart: always
+ ports:
+ - "28013:27018"
+ volumes:
+ - shard1-3-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard1",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard2-1:
+ image: mongo:latest
+ container_name: shard2-1
+ restart: always
+ ports:
+ - "28021:27018"
+ volumes:
+ - shard2-1-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard2",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard2-2:
+ image: mongo:latest
+ container_name: shard2-2
+ restart: always
+ ports:
+ - "28022:27018"
+ volumes:
+ - shard2-2-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard2",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ shard2-3:
+ image: mongo:latest
+ container_name: shard2-3
+ restart: always
+ ports:
+ - "28023:27018"
+ volumes:
+ - shard2-3-data:/data/db
+ command:
+ [
+ "--shardsvr",
+ "--replSet",
+ "shard2",
+ "--bind_ip_all",
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ mongos_router:
+ image: mongo:latest
+ container_name: mongos_router
+ restart: always
+ ports:
+ - "27021:27021"
+ command:
+ [
+ "mongos",
+ "--configdb",
+ "config_server/configSrv:27019",
+ "--bind_ip_all",
+ "--port",
+ "27021"
+ ]
+ healthcheck:
+ test: [ "CMD", "mongo", "--eval", "db.adminCommand('ping')" ]
+ interval: 5s
+ start_period: 10s
+
+ pymongo_api:
+ container_name: pymongo_api
+ build:
+ context: api_app
+ dockerfile: Dockerfile
+ image: kazhem/pymongo_api:1.0.0
+ depends_on:
+ - mongos_router
+ ports:
+ - 8080:8080
+ environment:
+ MONGODB_URL: "mongodb://mongos_router:27021"
+ MONGODB_DATABASE_NAME: "somedb"
+ REDIS_URL: "redis://redis:6379"
+
+networks:
+ default:
+ driver: bridge
+
+volumes:
+ mongodb1_data_container:
+ config-data:
+ shard1-1-data:
+ shard1-2-data:
+ shard1-3-data:
+ shard2-1-data:
+ shard2-2-data:
+ shard2-3-data:
+ redis-data:
diff --git a/sharding-repl-cache/scripts/mongo-init-configSrv.sh b/sharding-repl-cache/scripts/mongo-init-configSrv.sh
new file mode 100755
index 00000000..93c5f702
--- /dev/null
+++ b/sharding-repl-cache/scripts/mongo-init-configSrv.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+### Инициализация сервера конфигурации
+
+docker compose exec -T configSrv mongosh --port 27019 --quiet <