|
| 1 | +# sqlcache |
| 2 | + |
| 3 | +[](https://pkg.go.dev/prashanthpai/sqlcache?tab=doc) |
| 4 | +[](https://goreportcard.com/report/github.com/prashanthpai/sqlcache) |
| 5 | +[](https://opensource.org/licenses/MIT) |
| 6 | + |
| 7 | +sqlcache is an **experimental** caching middleware for `database/sql`. It |
| 8 | +leverages APIs provided by the handy [sqlmw](https://github.com/ngrok/sqlmw) |
| 9 | +project and is inspired from [slonik-interceptor-query-cache](https://github.com/gajus/slonik-interceptor-query-cache). |
| 10 | +This liberates your Go program from maintaining imperative code that |
| 11 | +implements the cache-aside pattern. Your program will perceive the |
| 12 | +database client/driver as a read-through cache. |
| 13 | + |
| 14 | +Tested with PostgreSQL database with [pgx](https://github.com/jackc/pgx/tree/master/stdlib) as the driver. |
| 15 | + |
| 16 | +Cache backends supported: |
| 17 | + |
| 18 | +* [ristretto](https://github.com/dgraph-io/ristretto) (in-memory) |
| 19 | +* [redis](https://github.com/go-redis/redis) |
| 20 | + |
| 21 | +It's easy to add other caching backends by implementing the `cache.Cacher` |
| 22 | +interface. |
| 23 | + |
| 24 | +## Usage |
| 25 | + |
| 26 | +Create a backend cache instance and install the interceptor: |
| 27 | + |
| 28 | +```go |
| 29 | +import ( |
| 30 | + "database/sql" |
| 31 | + |
| 32 | + redis "github.com/go-redis/redis/v7" |
| 33 | + "github.com/ngrok/sqlmw" |
| 34 | + "github.com/prashanthpai/sqlcache" |
| 35 | +) |
| 36 | + |
| 37 | +func main() { |
| 38 | + ... |
| 39 | + rc := redis.NewUniversalClient(&redis.UniversalOptions{ |
| 40 | + Addrs: []string{"127.0.0.1:6379"}, |
| 41 | + }) |
| 42 | + |
| 43 | + // create a sqlcache.Interceptor instance with the desired backend |
| 44 | + interceptor, err := sqlcache.NewInterceptor(&sqlcache.Config{ |
| 45 | + Cache: sqlcache.NewRedis(rc, "sqc"), |
| 46 | + }) |
| 47 | + ... |
| 48 | + |
| 49 | + // wrap pgx driver with the interceptor and register it |
| 50 | + sql.Register("pgx-with-cache", sqlmw.Driver(stdlib.GetDefaultDriver(), interceptor)) |
| 51 | + |
| 52 | + // open the database using the wrapped driver |
| 53 | + db, err := sql.Open("pgx-with-cache", dsn) |
| 54 | + ... |
| 55 | +``` |
| 56 | +
|
| 57 | +Caching is controlled using cache attributes which are SQL comments starting |
| 58 | +with `@cache-` prefix. Only queries with cache attributes are cached. |
| 59 | +
|
| 60 | +**Cache attributes:** |
| 61 | +
|
| 62 | +|Cache attribute|Description|Required?|Default| |
| 63 | +|---|---|---|---| |
| 64 | +|`@cache-ttl`|Number (in seconds) to cache the query for.|Yes|N/A| |
| 65 | +|`@cache-max-rows`|Don't cache if number of rows in query response exceeds this limit.|Yes|N/A| |
| 66 | +
|
| 67 | +Example query: |
| 68 | +
|
| 69 | +```go |
| 70 | +rows, err := db.QueryContext(context.TODO(), ` |
| 71 | + -- @cache-ttl 30 |
| 72 | + -- @cache-max-rows 10 |
| 73 | + SELECT name, pages FROM books WHERE pages > $1`, 100) |
| 74 | +``` |
| 75 | +
|
| 76 | +See [example/main.go](example/main.go) for a full working example. |
| 77 | +
|
| 78 | +## TODO |
| 79 | +
|
| 80 | +* Test against different postgres data types. |
| 81 | +* Check if deep copy of buffers can be removed. |
| 82 | +
|
| 83 | +### References |
| 84 | +
|
| 85 | +* A declarative way to cache PostgreSQL queries using Node.js: a [blog post](https://dev.to/gajus/a-declarative-way-to-cache-postgresql-queries-using-node-js-4fbo) by the author of [Slonik](https://github.com/gajus/slonik). |
| 86 | +* Declarative Caching with Postgres and Redis: Kyle Davis's [talk](https://youtu.be/IID2LQVztIM?t=1170) on Slonik + Redis. |
0 commit comments