bookmark_borderMongoDB index with multiple filters

Recently, my current project has faced some loading issue with mongodb, to be specific, documentdb from AWS. We have mid-level instance and a lot of collection for data seperation purpose. For some of collections, it store more than millions data.

Our data structure like this:

type Course struct {
    ClassType int
    Prerequirement []*Course
    Teacher string // should be a Teach but for simplicity using string here.
    BuildNumber int
    
}

type Student struct {
    Grade int
    AdmittedAt time.Time
    StartedAt time.Time
    GraduationTime time.Time
    CoursesJoined []*Course
}

The data structure is just a mock, to clarify the function of indexes, we will consider some scenario later.

Before we jump into the index, we should know the basic index concept and when/where/how to create an index which is an index strategy.

I will skip when and where here, but want to emphasize ‘How to create an effective index’

The ESR (Equality, Sort, Range) Rule

Equality

“Equality” refers to an exact match on a single value. The following exact match queries scan the cars collection for documents whose model field exactly matches Cordoba.

db.cars.find( { model: "Cordoba" } )db.cars.find( { model: { $eq: "Cordoba" } } )

Index searches make efficient use of exact matches to limit the number of documents that need to be examined to satisfy a query. Place fields that require exact matches first in your index.

An index may have multiple keys for queries with exact matches. The index keys for equality matches can appear in any order. However, to satisfy an equality match with the index, all of the index keys for exact matches must come before any other index fields. MongoDB’s search algorithm eliminates any need to arrange the exact match fields in a particular order.

Exact matches should be selective. To reduce the number of index keys scanned, ensure equality tests eliminate at least 90% of possible document matches.

Sort

“Sort” determines the order for results. Sort follows equality matches because the equality matches reduce the number of documents that need to be sorted. Sorting after the equality matches also allows MongoDB to do a non-blocking sort.

An index can support sort operations when the query fields are a subset of the index keys. Sort operations on a subset of the index keys are only supported if the query includes equality conditions for all of the prefix keys that precede the sort keys. For more information see: Sort and Non-prefix Subset of an Index.

The following example queries the cars collection. The output is sorted by model:

db.cars.find( { manufacturer: "GM" } ).sort( { model: 1 } )

To improve query performance, create an index on the manufacturer and model fields:

db.cars.createIndex( { manufacturer: 1, model: 1 } )
  • manufacturer is the first key because it is an equality match.
  • model is indexed in the same order ( 1 ) as the query.

Range

“Range” filters scan fields. The scan doesn’t require an exact match, which means range filters are loosely bound to index keys. To improve query efficiency, make the range bounds as tight as possible and use equality matches to limit the number of documents that must be scanned.

Range filters resemble the following:

db.cars.find( { price: { $gte: 15000} } )db.cars.find( { age: { $lt: 10 } } )db.cars.find( { priorAccidents: { $ne: null } } )

MongoDB cannot do an index sort on the results of a range filter. Place the range filter after the sort predicate so MongoDB can use a non-blocking index sort. For more information on blocking sorts, see cursor.allowDiskUse().

Please keep these three words in your mind until you find a job that completely is irrelated to development, of course, until you win the power ball as well.

So we can start to think about several cases:

We want to find all students that are in Grade 1 and admitted in past 6 weeks, then sorted by start date descending.

at this case, we know Equality is grade, and Sort is the start time, the time range is based on admitted time, so we can easily create an index

{grade:1, admitted_at: -1, started_at:-1}

But the requirement changed a lot. For some reason, the principal wants to know all school students instead of Grade 1, because of the index prefix mechanism in MongoDB (https://www.mongodb.com/docs/manual/core/index-compound/#prefixes) without grade, this index won’t be hit when searching only by admitted time and start time.

In this case, we may want to change the index to

 {admitted_at: -1, started_at:-1, grade:1}. 

But for the same reason, it will request every query need to contain a prefix to take advantage of the index.

We want to find all students that have Type A course and That course’s teacher is Weihao and admitted in past 6 weeks, then sorted by start date descending.

In this case, cause we have a subdocument in our data structure, we need to create an index for the subdocument as well. So the index will looks like

{courses_joined.class_type:1, courses_joined.teacher:1, admited_at:-1, started_at:-1}

We still try to use a compound index to fulfill the requirement, which is good for extensibility but also bad for extensibility. Imagine one more scenario, what if someday the product manager asks you when the department of education has issued a new law that doesn’t allow searching students much specific. So you have to change the AND relation between course_joined.class_type and course_joined.teacher to OR relation. Then you find out the index is not hitting anymore as OR aggregation requires both fields have an independent index.

In this case, we should create multiple indexes and use MongoDB index intersection

{admited_at:-1, started_at:-1}
{courses_joined.class_type:1}
{courses_joined.teacher:1}

Other MongoDB article:

bookmark_borderAWS Lightsail WordPress SSL 证书一键renew

sudo /opt/bitnami/bncert-tool

AWS Lightsail WordPress的instance已经提前安装好了 bncert-tool, 如果之前已经配置过一次SSL的DNS records 和开启了HTTPS的话,遇到证书过期,只需要执行上面的command。然后他自动跑完之后就会签发证书了

2021-09-23 update

这两天发现之前的认证又失效了,于是准备继续去renew certificate,但是在跑的时候发现会报错说certificate expire,查了很久,刚开始觉得这个是一个悖论,因为expire所以需要renew,但是因为expire又不能renew。 实在头大,后来发现bitnami其实有自己的letencrypt的证书存储地址,所以在/etc/letencrypt/下面的证书并不会生效

最终的解决办法就是把 /opt/bitnami/letencrypt/certificate 下的证书删掉,重新跑就好了

这里我第二次授权的时候使用的是lego,但其实应该certbot也是可以的,只要确保过期的证书被删掉就好

lego在生产证书的时候,会自动生成在./.lego/certificate,所以还需要拷贝到对应的bitnami放证书的地方

doc:

https://go-acme.github.io/lego/usage/cli/examples/

https://docs.bitnami.com/general/how-to/generate-install-lets-encrypt-ssl/

2022-03-04 update
又踩了一个坑,因为renew报错显示需要先revoke 生效的certification,然后再重新申请,所以就直接删除了/opt/bitnami/letenscript/*, 然后发现apache直接down掉了,后来只能重新给apache生成了一个dummy的certificates,然后更改了bitnami的config,再重新生成就好了

https://docs.bitnami.com/aws/apps/wordpress/administration/create-ssl-certificate-apache/

bookmark_borderSwift-聊天记录平滑下拉加载(多种类 cell)

当我们在设计聊天界面的时候,由于聊天记录可能存放在服务端,又或者为了考虑内存的因素,我们偏向于在一开始仅仅展示一部分聊天记录,这时候我们就需要有一个方法来实现加载之前的聊天记录。通常是通过下拉来实现的(例如微信)

和下拉刷新的思想很接近,我们可以选择在tableview上加一个refresh control来实现下拉加载,但这里并不推荐使用这个方法(原因后面会提到)但是无论我们使用哪个方法,当我们获取到新的data的时候,需要把他添加到tableView的最上面,这样才符合逻辑。

Screen Shot 2018 12 30 at 6 17 23 PM
如图所示 来源:https://bluelemonbits.com/2018/12/30/reloading-inserting-dynamic-height-cells-and-keeping-scroll-position/

由于TableView的特性,在reload的data的时候,会自动跳转到新加入的cell的顶端,也就是上图所示绿色的最顶端,这样就会导致每次加载之后整个tableView都会产生突兀的跳转。

这时候我们需要的解决方法是像参考微信一样,在加入新的Cell的同时,保持我们当时的contentOffset。具体实现这个的代码如下:

let oldContentSizeHeight = ListView.contentSize.height
ListView.reloadData() 
ListView.layoutIfNeeded()
let newContentSizeHeight = ListView.contentSize.height
                ListView.setContentOffset(CGPoint(x:ListView.contentOffset.x, y:newContentSizeHeight - oldContentSizeHeight), animated: false)

简而言之,就是在reload data之前记录此时的content height,待reload结束之后再记录之后的height,通过相减来设置contentOffset

这时候我们需要了解tableView在计算content height的原理,如果只是简单的单一的cell,那么cell的height会是相同的,并且是在

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { }

这个delegate方法里设定的,但是如果我们的cell是多种类的呢,这时候iOS提供了一个estimate height 对于cell

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}

通过同时实现这个delegate和上一个delegate的方法,iOS会自动帮我们计算出合适的content height,这样就不会产生有问题height,从而导致跳转出现问题(换句话说,如果你不实现这个方法,那么计算你content height的方法,就是简单的cell number * [cell height])

通过以上方式,我们可以实现固定位置的上拉刷新,回到之前提到的问题,为什么不推荐使用refresh control,原因其实很简答,因为refresh control是通过加载一个新的uiView在tableView上面,然后设定一个阈值,当我们下拉到一定程度会调用。那对于用户的体验来讲,需要一直下拉刷新的话,并不让人愉悦。通常快速向上滑动的时候并不会触发这个调用,也就会导致我们需要很多次“两次操作”才能到达我们想要的聊天记录的位置。那么我这里用到的替代方法是:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView == ListView {
            if ListView.contentOffset.y < 0 {
                if !isLoading {
                    loadPreviousMessage()
                }
            }
        }
    }

因为TableView的实现是在scrollView上的,所以我们可以检测是否scrollView的contentOffset < 0 来检测用户是否想要加载之前的聊天记录,同时因为scrollView的特性是支持bounce的,所以向上快速滑动的同时,contentOffset也会出现 < 0的情况,这里为了避免多次调用,使用了一个变量 “isLoading”来控制。

这样设定之后,我们就可以简单的实现一个向上滑动来加载之前的聊天记录的功能

bookmark_borderFastlane 踩坑

  1. gem 版本过低不能安装fastlane,需要升级gem
  2. xcode-select 安装位置变化会导致
    ​sh: agvtool: command not found

    解决办法:

    sudo xcode-select -s /Applications/Xcode.app/Contents/Developer

     

  3. brew 安装的fastlane 的 action数量远小于 gem直接安装,需要reinstall fastlane 在用gem来安装 其中包括 build_app ~= gym   upload_to_testflight ~= pilot
  4. gem 安装之后的 $GEM_HOME 问题,如果不改$GEM_HOME的话,就需要自行把$PATH里加上在gem[version]/bin 这样
  5. 未完待续

bookmark_borderC++ String 详解

对于string来讲,如果是sizeof(std::string) 只有一个成员变量即指向字符串内容的指针,而并没有别的成员变量来记录实际字符串长度了。这个指针是指向内容的地址的

size(string)会返回string里的值,string =[1234567890] string.size() = 10, string.data() = [1234567890]. 指针指向的内存地址上的值是0x1ba8028。也就是说要想知道实际string在内存上占用的大小,需要去看string存的指针指向的内存的大小。