At repo root folder, run the following command to start the server
docker compose -f Docker-Compose.yml up --build
and the server will run on port 8080
- PostgreSQL ensures data persistence and consistency.
- Redis provides high-speed caching, significantly improving redirect speed for high traffic.
- Utilizes an auto-increment ID with Base57 encoding to ensure uniqueness and control the length of the short URL.
- Employs a combination of machine ID and auto-increment ID to generate shortcodes ({machineID}-{AutoIncrID}), offering high efficiency and scalability across multiple nodes.
- Using Bloom Filters to filter out non-existent keys, ensures that requests for keys that do not exist do not reach the database.
- Uses Redis to implement a distributed lock, ensuring that even if multiple requests access the same key simultaneously, only one request will interact with the database.
- Using UUIDs
- Advantages: Simple and convenient.
- Disadvantages: Requires checking if the UUID has already been generated; if it has, a new one must be created, which is less efficient.
- Using a centralized server
- Advantages: High efficiency, centralized management, ability to generate shorter URLs, and can adjust length based on actual demand.
- Disadvantages: Not easily scalable; requires maintaining an additional service, leading to higher costs.
- Machine ID + Auto-increment ID ({machineID}-{AutoIncrID})
- Advantages: Highest efficiency, easy to scale, simple implementation, generates relatively short URLs that can increase in length as needed.
- Disadvantages: Requires a centralized service to issue machine IDs, though this service does not have heavy loading, making maintenance costs moderate.
- Decision: We chose this approach because it offers excellent performance and scalability, generates reasonably short URLs, and the maintenance cost is acceptable.
- Caching Non-Existent Keys
- Advantages: Low maintenance cost.
- Disadvantages: With high volumes of garbage requests, Redis might become overwhelmed, leading to eviction of valid cache entries. Additionally, these requests may still reach the database.
- Using Bloom Filters to Filter Non-Existent Keys
- Advantages: Ensures that requests for non-existent keys do not reach the database.
- Disadvantages: Higher maintenance cost, requires a dedicated Redis cluster, and necessitates managing backup, restoration, reconstruction, and write checks for the Bloom filter.
- Decision: We chose this solution because it delivers excellent performance. Although the maintenance cost is higher, it effectively filters out garbage requests.
- Cache key within pod
- Cache key in CDN
- Although each pod can generate IDs, a centralized service is still needed to assign machine IDs to each pod. For simplicity, this centralized machine ID service was not implemented.
- A config server is also required to provide the hostname for the short URL service to each pod.
- While Bloom filters may have a low probability of false positives, the impact is mitigated by the database's own caching mechanisms.
- To thoroughly mitigate this issue, we can cache the keys that resulted in false positives, reducing the chances of hitting the database.
- It's recommended to host the Bloom filter on a separate Redis cluster, not shared with the cache; otherwise, it might get evicted by Redis.
- We also need to consider the backup and restoration of the Bloom filter, as well as reconstruction if it gets cleared.
- Since Redis may lose data even after successful writes, it is recommended to perform a secondary check after a few seconds to ensure the key was added to the Bloom filter.
curl -X POST -H "Content-Type:application/json" http://localhost:8080/api/v1/urls -d '{ "url": "<original_url>", "expireAt": "2025-02-28T09:20:41Z"}'- url is available format
- expireAt is greater than now
{ "id": "<url_id>", "shortUrl": "http: //localhost:8080/<url_id>" }curl -L -X GET http://localhost:8080/<url_id> => REDIRECT to original URL- url_id is exist, and not expired
❯ go test ./... -cover -p=1 -count=1
github.com/sappy5678/dcard/cmd/api coverage: 0.0% of statements
ok github.com/sappy5678/dcard/pkg/domain 1.081s coverage: 83.3% of statements
github.com/sappy5678/dcard/pkg/service coverage: 0.0% of statements
ok github.com/sappy5678/dcard/pkg/service/shorturl 0.036s coverage: 0.0% of statements [no tests to run]
ok github.com/sappy5678/dcard/pkg/service/shorturl/cache 9.078s coverage: 79.3% of statements
ok github.com/sappy5678/dcard/pkg/service/shorturl/logservice 0.040s coverage: 100.0% of statements
ok github.com/sappy5678/dcard/pkg/service/shorturl/repository 9.495s coverage: 78.6% of statements
ok github.com/sappy5678/dcard/pkg/service/shorturl/shortcode 0.330s coverage: 92.3% of statements
ok github.com/sappy5678/dcard/pkg/service/shorturl/transport 0.075s coverage: 91.7% of statements
ok github.com/sappy5678/dcard/pkg/utl/config 0.046s coverage: 100.0% of statements
ok github.com/sappy5678/dcard/pkg/utl/locker 6.479s coverage: 100.0% of statements
ok github.com/sappy5678/dcard/pkg/utl/postgres 8.260s coverage: 83.3% of statements
ok github.com/sappy5678/dcard/pkg/utl/redis 5.924s coverage: 83.3% of statements
ok github.com/sappy5678/dcard/pkg/utl/server 0.045s coverage: 27.8% of statements
ok github.com/sappy5678/dcard/pkg/utl/zlog 0.039s coverage: 100.0% of statements